monkeypatchlist 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +61 -0
  3. package/README.md +183 -2
  4. package/catalog/catalog.json +552211 -0
  5. package/catalog/catalog.schema.json +787 -0
  6. package/catalog/changelog.json +3 -0
  7. package/dist/bin/monkeypatchlist.d.ts +2 -0
  8. package/dist/bin/monkeypatchlist.js +3 -0
  9. package/dist/catalog.d.ts +561 -0
  10. package/dist/catalog.js +58 -0
  11. package/dist/cli.d.ts +84 -0
  12. package/dist/cli.js +470 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +1 -0
  15. package/dist/lockfile-input.d.ts +9 -0
  16. package/dist/lockfile-input.js +91 -0
  17. package/dist/packages/analysis/binding.d.ts +2 -0
  18. package/dist/packages/analysis/binding.js +5 -0
  19. package/dist/packages/analysis/index.d.ts +10 -0
  20. package/dist/packages/analysis/index.js +13 -0
  21. package/dist/packages/analysis/job.d.ts +33 -0
  22. package/dist/packages/analysis/job.js +85 -0
  23. package/dist/packages/analysis/lib/absence-tags.d.ts +34 -0
  24. package/dist/packages/analysis/lib/absence-tags.js +340 -0
  25. package/dist/packages/analysis/lib/ast.d.ts +28 -0
  26. package/dist/packages/analysis/lib/ast.js +64 -0
  27. package/dist/packages/analysis/lib/binding.d.ts +36 -0
  28. package/dist/packages/analysis/lib/binding.js +66 -0
  29. package/dist/packages/analysis/lib/budget.d.ts +80 -0
  30. package/dist/packages/analysis/lib/budget.js +31 -0
  31. package/dist/packages/analysis/lib/catch-arms.d.ts +19 -0
  32. package/dist/packages/analysis/lib/catch-arms.js +351 -0
  33. package/dist/packages/analysis/lib/chunk-loads.d.ts +160 -0
  34. package/dist/packages/analysis/lib/chunk-loads.js +882 -0
  35. package/dist/packages/analysis/lib/chunk-registry.d.ts +45 -0
  36. package/dist/packages/analysis/lib/chunk-registry.js +334 -0
  37. package/dist/packages/analysis/lib/code-routes.d.ts +52 -0
  38. package/dist/packages/analysis/lib/code-routes.js +921 -0
  39. package/dist/packages/analysis/lib/collisions.d.ts +22 -0
  40. package/dist/packages/analysis/lib/collisions.js +103 -0
  41. package/dist/packages/analysis/lib/definitions.d.ts +76 -0
  42. package/dist/packages/analysis/lib/definitions.js +782 -0
  43. package/dist/packages/analysis/lib/early-exits.d.ts +39 -0
  44. package/dist/packages/analysis/lib/early-exits.js +161 -0
  45. package/dist/packages/analysis/lib/effect.d.ts +138 -0
  46. package/dist/packages/analysis/lib/effect.js +800 -0
  47. package/dist/packages/analysis/lib/global-object.d.ts +581 -0
  48. package/dist/packages/analysis/lib/global-object.js +8240 -0
  49. package/dist/packages/analysis/lib/keys.d.ts +33 -0
  50. package/dist/packages/analysis/lib/keys.js +82 -0
  51. package/dist/packages/analysis/lib/line-starts.d.ts +41 -0
  52. package/dist/packages/analysis/lib/line-starts.js +79 -0
  53. package/dist/packages/analysis/lib/link-group.d.ts +51 -0
  54. package/dist/packages/analysis/lib/link-group.js +487 -0
  55. package/dist/packages/analysis/lib/module-links.d.ts +117 -0
  56. package/dist/packages/analysis/lib/module-links.js +1060 -0
  57. package/dist/packages/analysis/lib/nested-code.d.ts +38 -0
  58. package/dist/packages/analysis/lib/nested-code.js +200 -0
  59. package/dist/packages/analysis/lib/numeric.d.ts +16 -0
  60. package/dist/packages/analysis/lib/numeric.js +216 -0
  61. package/dist/packages/analysis/lib/once-flags.d.ts +12 -0
  62. package/dist/packages/analysis/lib/once-flags.js +190 -0
  63. package/dist/packages/analysis/lib/parameter-arguments.d.ts +12 -0
  64. package/dist/packages/analysis/lib/parameter-arguments.js +34 -0
  65. package/dist/packages/analysis/lib/parse.d.ts +10 -0
  66. package/dist/packages/analysis/lib/parse.js +39 -0
  67. package/dist/packages/analysis/lib/partial.d.ts +60 -0
  68. package/dist/packages/analysis/lib/partial.js +518 -0
  69. package/dist/packages/analysis/lib/print.d.ts +3 -0
  70. package/dist/packages/analysis/lib/print.js +150 -0
  71. package/dist/packages/analysis/lib/reachability.d.ts +137 -0
  72. package/dist/packages/analysis/lib/reachability.js +1295 -0
  73. package/dist/packages/analysis/lib/resolver.d.ts +13 -0
  74. package/dist/packages/analysis/lib/resolver.js +2886 -0
  75. package/dist/packages/analysis/lib/restores.d.ts +42 -0
  76. package/dist/packages/analysis/lib/restores.js +482 -0
  77. package/dist/packages/analysis/lib/rewritten-deletes.d.ts +6 -0
  78. package/dist/packages/analysis/lib/rewritten-deletes.js +142 -0
  79. package/dist/packages/analysis/lib/scope.d.ts +110 -0
  80. package/dist/packages/analysis/lib/scope.js +443 -0
  81. package/dist/packages/analysis/lib/script-loads.d.ts +56 -0
  82. package/dist/packages/analysis/lib/script-loads.js +359 -0
  83. package/dist/packages/analysis/lib/summaries.d.ts +177 -0
  84. package/dist/packages/analysis/lib/summaries.js +498 -0
  85. package/dist/packages/analysis/lib/types.d.ts +632 -0
  86. package/dist/packages/analysis/lib/types.js +128 -0
  87. package/dist/packages/analysis/lib/wrap.d.ts +34 -0
  88. package/dist/packages/analysis/lib/wrap.js +407 -0
  89. package/dist/packages/analysis/lib/writes.d.ts +358 -0
  90. package/dist/packages/analysis/lib/writes.js +1612 -0
  91. package/dist/packages/analysis/visit-order.d.ts +3 -0
  92. package/dist/packages/analysis/visit-order.js +23 -0
  93. package/dist/packages/attribution/index.d.ts +233 -0
  94. package/dist/packages/attribution/index.js +444 -0
  95. package/dist/packages/attribution/lib/containing-package.d.ts +76 -0
  96. package/dist/packages/attribution/lib/containing-package.js +304 -0
  97. package/dist/packages/attribution/lib/first-party.d.ts +37 -0
  98. package/dist/packages/attribution/lib/first-party.js +153 -0
  99. package/dist/packages/attribution/lib/lockfile-graph.d.ts +99 -0
  100. package/dist/packages/attribution/lib/lockfile-graph.js +158 -0
  101. package/dist/packages/attribution/lib/lockfile.d.ts +33 -0
  102. package/dist/packages/attribution/lib/lockfile.js +330 -0
  103. package/dist/packages/attribution/lib/path-rules.d.ts +29 -0
  104. package/dist/packages/attribution/lib/path-rules.js +84 -0
  105. package/dist/packages/attribution/lib/pnpm.d.ts +4 -0
  106. package/dist/packages/attribution/lib/pnpm.js +133 -0
  107. package/dist/packages/attribution/lib/remote-url.d.ts +7 -0
  108. package/dist/packages/attribution/lib/remote-url.js +24 -0
  109. package/dist/packages/attribution/lib/source-map.d.ts +51 -0
  110. package/dist/packages/attribution/lib/source-map.js +125 -0
  111. package/dist/packages/attribution/lib/yaml.d.ts +9 -0
  112. package/dist/packages/attribution/lib/yaml.js +277 -0
  113. package/dist/packages/attribution/lib/yarn.d.ts +7 -0
  114. package/dist/packages/attribution/lib/yarn.js +227 -0
  115. package/dist/packages/baseline/index.d.ts +45 -0
  116. package/dist/packages/baseline/index.js +41 -0
  117. package/dist/packages/baseline/lib/error.d.ts +15 -0
  118. package/dist/packages/baseline/lib/error.js +25 -0
  119. package/dist/packages/baseline/lib/format.d.ts +49 -0
  120. package/dist/packages/baseline/lib/format.js +22 -0
  121. package/dist/packages/baseline/lib/migrations.d.ts +19 -0
  122. package/dist/packages/baseline/lib/migrations.js +7 -0
  123. package/dist/packages/baseline/lib/previous.d.ts +23 -0
  124. package/dist/packages/baseline/lib/previous.js +64 -0
  125. package/dist/packages/baseline/lib/read.d.ts +39 -0
  126. package/dist/packages/baseline/lib/read.js +219 -0
  127. package/dist/packages/baseline/lib/report.d.ts +25 -0
  128. package/dist/packages/baseline/lib/report.js +104 -0
  129. package/dist/packages/baseline/lib/skew.d.ts +51 -0
  130. package/dist/packages/baseline/lib/skew.js +75 -0
  131. package/dist/packages/compare/index.d.ts +120 -0
  132. package/dist/packages/compare/index.js +274 -0
  133. package/dist/packages/compare/lib/catalog-notes.d.ts +73 -0
  134. package/dist/packages/compare/lib/catalog-notes.js +224 -0
  135. package/dist/packages/completeness/index.d.ts +246 -0
  136. package/dist/packages/completeness/index.js +249 -0
  137. package/dist/packages/confidence/index.d.ts +54 -0
  138. package/dist/packages/confidence/index.js +71 -0
  139. package/dist/packages/config/index.d.ts +107 -0
  140. package/dist/packages/config/index.js +95 -0
  141. package/dist/packages/config/lib/command-line.d.ts +23 -0
  142. package/dist/packages/config/lib/command-line.js +239 -0
  143. package/dist/packages/config/lib/config-file.d.ts +11 -0
  144. package/dist/packages/config/lib/config-file.js +119 -0
  145. package/dist/packages/config/lib/scan-config-id.d.ts +28 -0
  146. package/dist/packages/config/lib/scan-config-id.js +54 -0
  147. package/dist/packages/config/lib/schema.d.ts +2 -0
  148. package/dist/packages/config/lib/schema.js +31 -0
  149. package/dist/packages/config/lib/types.d.ts +64 -0
  150. package/dist/packages/config/lib/types.js +6 -0
  151. package/dist/packages/config/lib/urls.d.ts +6 -0
  152. package/dist/packages/config/lib/urls.js +9 -0
  153. package/dist/packages/discovery/index.d.ts +277 -0
  154. package/dist/packages/discovery/index.js +1950 -0
  155. package/dist/packages/discovery/lib/import-map.d.ts +68 -0
  156. package/dist/packages/discovery/lib/import-map.js +180 -0
  157. package/dist/packages/discovery/lib/requests.d.ts +58 -0
  158. package/dist/packages/discovery/lib/requests.js +591 -0
  159. package/dist/packages/discovery/lib/scopes.d.ts +6 -0
  160. package/dist/packages/discovery/lib/scopes.js +286 -0
  161. package/dist/packages/discovery/lib/source-maps.d.ts +56 -0
  162. package/dist/packages/discovery/lib/source-maps.js +209 -0
  163. package/dist/packages/discovery/lib/swept-code.d.ts +27 -0
  164. package/dist/packages/discovery/lib/swept-code.js +107 -0
  165. package/dist/packages/discovery/lib/syntax.d.ts +17 -0
  166. package/dist/packages/discovery/lib/syntax.js +39 -0
  167. package/dist/packages/findings/index.d.ts +106 -0
  168. package/dist/packages/findings/index.js +254 -0
  169. package/dist/packages/findings/lib/scope.d.ts +38 -0
  170. package/dist/packages/findings/lib/scope.js +110 -0
  171. package/dist/packages/parallel/index.d.ts +2 -0
  172. package/dist/packages/parallel/index.js +5 -0
  173. package/dist/packages/parallel/lib/protocol.d.ts +15 -0
  174. package/dist/packages/parallel/lib/protocol.js +2 -0
  175. package/dist/packages/parallel/lib/runner.d.ts +36 -0
  176. package/dist/packages/parallel/lib/runner.js +96 -0
  177. package/dist/packages/parallel/lib/schedule.d.ts +16 -0
  178. package/dist/packages/parallel/lib/schedule.js +52 -0
  179. package/dist/packages/parallel/lib/worker.d.ts +1 -0
  180. package/dist/packages/parallel/lib/worker.js +18 -0
  181. package/dist/packages/policy/index.d.ts +36 -0
  182. package/dist/packages/policy/index.js +22 -0
  183. package/dist/packages/report/explain.d.ts +26 -0
  184. package/dist/packages/report/explain.js +99 -0
  185. package/dist/packages/report/index.d.ts +210 -0
  186. package/dist/packages/report/index.js +388 -0
  187. package/dist/packages/report/terminal.d.ts +11 -0
  188. package/dist/packages/report/terminal.js +331 -0
  189. package/dist/packages/source-store/fixture.d.ts +22 -0
  190. package/dist/packages/source-store/fixture.js +58 -0
  191. package/dist/packages/source-store/index.d.ts +278 -0
  192. package/dist/packages/source-store/index.js +322 -0
  193. package/dist/packages/source-store/lib/addresses.d.ts +6 -0
  194. package/dist/packages/source-store/lib/addresses.js +91 -0
  195. package/dist/packages/source-store/lib/connection.d.ts +15 -0
  196. package/dist/packages/source-store/lib/connection.js +135 -0
  197. package/dist/packages/source-store/lib/content-types.d.ts +16 -0
  198. package/dist/packages/source-store/lib/content-types.js +75 -0
  199. package/dist/packages/source-store/lib/data-urls.d.ts +10 -0
  200. package/dist/packages/source-store/lib/data-urls.js +85 -0
  201. package/dist/packages/source-store/lib/deadline.d.ts +18 -0
  202. package/dist/packages/source-store/lib/deadline.js +44 -0
  203. package/dist/packages/source-store/lib/exchange.d.ts +29 -0
  204. package/dist/packages/source-store/lib/exchange.js +112 -0
  205. package/dist/packages/source-store/lib/integrity.d.ts +19 -0
  206. package/dist/packages/source-store/lib/integrity.js +86 -0
  207. package/dist/packages/source-store/lib/outcomes.d.ts +12 -0
  208. package/dist/packages/source-store/lib/outcomes.js +22 -0
  209. package/dist/packages/source-store/lib/proxy.d.ts +11 -0
  210. package/dist/packages/source-store/lib/proxy.js +53 -0
  211. package/dist/packages/source-store/lib/session.d.ts +12 -0
  212. package/dist/packages/source-store/lib/session.js +139 -0
  213. package/dist/packages/source-store/lib/urls.d.ts +6 -0
  214. package/dist/packages/source-store/lib/urls.js +15 -0
  215. package/dist/packages/source-store/resolve.d.ts +59 -0
  216. package/dist/packages/source-store/resolve.js +45 -0
  217. package/dist/scan.d.ts +65 -0
  218. package/dist/scan.js +592 -0
  219. package/package.json +95 -4
  220. package/schema/config.schema.json +133 -0
  221. package/schema/report.schema.json +1905 -0
@@ -0,0 +1,246 @@
1
+ import type { Realm } from '../../catalog.ts';
2
+ import type { AnalyzeGroupOutput, Budget, GeneratedLocation, LimitationKind } from '../analysis/index.ts';
3
+ import type { Confidence } from '../confidence/index.ts';
4
+ import type { Introducers } from '../attribution/index.ts';
5
+ /** A scan's completeness levels (§13). */
6
+ export declare const levels: readonly ["complete", "complete-with-limitations", "incomplete"];
7
+ export type Level = (typeof levels)[number];
8
+ /**
9
+ * A limitation record (§13): `{kind, artifact (URL or path, plus hash if known), location (for in-code kinds),
10
+ * detail}`. The resolver's records are these, with a URL and a hash; discovery's may lack the hash.
11
+ */
12
+ export interface LimitationRecord {
13
+ kind: LimitationKind;
14
+ artifact: {
15
+ url?: string;
16
+ path?: string;
17
+ hash?: string;
18
+ };
19
+ location?: GeneratedLocation;
20
+ detail: string;
21
+ }
22
+ /**
23
+ * The fixed level of each limitation kind (§13 Limitation kinds). There are no thresholds and nothing is
24
+ * configurable, so the same records always give the same level.
25
+ */
26
+ export declare const kindLevels: Record<LimitationKind, Exclude<Level, 'complete'>>;
27
+ /**
28
+ * Why an artifact failed (§13 Counters `failed` by reason): a failure record names it, or the crash guard fired before
29
+ * the link group that would have analyzed it ran (`crash-guard`). The crash guard raises no record: it is not a
30
+ * limitation kind.
31
+ */
32
+ declare const failureReasons: readonly ["parse-failure", "unreadable", "fetch-failed", "sri-mismatch", "crash-guard"];
33
+ export type FailureReason = (typeof failureReasons)[number];
34
+ /** The blind-spot counters (§13 Counters), and the kind each one counts. */
35
+ declare const blindSpotKinds: {
36
+ readonly dynamicCode: "dynamic-code";
37
+ readonly unresolvedReference: "unresolved-reference";
38
+ readonly realmUncertain: "realm-uncertain";
39
+ readonly unresolvedHolder: "unresolved-holder";
40
+ };
41
+ /**
42
+ * An artifact of the scan: a URL in one realm (§2 Realm), run as one source type, since a file a page runs both as a
43
+ * classic script and as a module is an artifact each way (§2 Files that don't run as referenced).
44
+ */
45
+ export interface ArtifactKey {
46
+ url: string;
47
+ realm: Realm;
48
+ sourceType: 'script' | 'module';
49
+ }
50
+ /** The artifact fields the aggregator reads: which records name it, what they may be missing, and how it was found. */
51
+ export interface CountedArtifact extends ArtifactKey {
52
+ path: string | null;
53
+ hash: string | null;
54
+ discovery: 'reference' | 'injected' | 'sweep';
55
+ sourceMap: SourceMapPairing;
56
+ /** How many candidate maps failed verification against it (§2 Source maps). */
57
+ staleCandidates: number;
58
+ /** The maps a remote artifact names that weren't read (§17 Content types), by why. */
59
+ unreadMaps: UnreadMaps;
60
+ }
61
+ /**
62
+ * The maps a remote artifact's `SourceMap` header or `sourceMappingURL` annotation names that weren't read (§17 Content
63
+ * types), by why: the safety floor `refused` the fetch, it `failed`, or the map is a build file that
64
+ * exists but can't be read (`unreadable`). They change these counters only, never completeness. A missing build file
65
+ * is counted nowhere, as a local artifact's is (§2 Source maps).
66
+ */
67
+ export interface UnreadMaps {
68
+ refused: number;
69
+ failed: number;
70
+ unreadable: number;
71
+ }
72
+ /**
73
+ * The build files the sweep found and skipped (§2 Sweep, Content that isn't JavaScript): the JavaScript and WebAssembly
74
+ * files no URL mapping serves (`excluded`), by build-relative path, and how many WebAssembly files a mapping serves
75
+ * (`nonJs`). They are known, and counted as skipped. `otherFiles` counts the other files a mapping serves that no
76
+ * reference found, which the sweep takes for neither JavaScript nor WebAssembly: informational, and not artifacts.
77
+ */
78
+ export interface SweptFiles {
79
+ excluded: readonly string[];
80
+ nonJs: number;
81
+ otherFiles: number;
82
+ }
83
+ /**
84
+ * The methods that pair a source map with an artifact (§2 Source maps), in the order they are tried: in the build
85
+ * directory, then in each source-map directory.
86
+ */
87
+ export declare const pairingMethods: readonly ["header", "annotation", "sidecar", "debug-id", "external-debug-id", "external-annotation", "external-sidecar"];
88
+ export type PairingMethod = (typeof pairingMethods)[number];
89
+ /**
90
+ * An artifact's source map (§2 Source maps, §15 Artifacts): the method that paired the first map that verified, and
91
+ * the map's path: build-relative, or for a source-map directory's map, the directory as declared joined with its path
92
+ * there (null for a `data:` URL or a fetched map). With none, the method is `none`, and the status says whether any
93
+ * candidate was found: each failed verification (`stale`, with the first one's path), or none was (`none`).
94
+ */
95
+ export type SourceMapPairing = {
96
+ method: PairingMethod;
97
+ path: string | null;
98
+ status: 'verified';
99
+ } | {
100
+ method: 'none';
101
+ path: string | null;
102
+ status: 'stale';
103
+ } | {
104
+ method: 'none';
105
+ path: null;
106
+ status: 'none';
107
+ };
108
+ /**
109
+ * The attribution block (§13): missing source maps are not a coverage limitation, so they are counted here. Every
110
+ * artifact is counted once, with a map, without one, or with only broken (`stale`) ones, and each one with a map under
111
+ * the method that paired it. `staleCandidates` counts every candidate map that failed verification (§2 Source maps),
112
+ * once for each artifact it was tried for: those a later method's map replaced, and those of an artifact with only
113
+ * broken maps. `unreadMaps` counts the maps remote artifacts name that weren't read, the same way (§17).
114
+ * `cappedFindings` counts the findings whose attribution confidence is capped below `high` (§9), by the level it is
115
+ * capped at.
116
+ */
117
+ export interface AttributionBlock {
118
+ artifacts: {
119
+ withMap: number;
120
+ withoutMap: number;
121
+ withBrokenMap: number;
122
+ };
123
+ pairings: Record<PairingMethod, number>;
124
+ staleCandidates: number;
125
+ unreadMaps: UnreadMaps;
126
+ /** Each source-map directory's maps, in the order the directories were declared (§2 Source maps). */
127
+ sourceMapDirectories: SourceMapDirectoryCounts[];
128
+ cappedFindings: {
129
+ medium: number;
130
+ low: number;
131
+ };
132
+ /** Why introducers are unknown (§10 Unknown is not empty): null with a usable lockfile, and its evidence. */
133
+ introducers: Introducers;
134
+ }
135
+ /**
136
+ * What became of the maps of one source-map directory (§13 Attribution block), named by its `path` as declared. Its
137
+ * maps are its `.map` files, in any case, and any other file a mirrored annotation named. `found` counts them all,
138
+ * `paired` those that verified against an artifact through an external method, even one an earlier map had already
139
+ * paired (§2 Source maps), and `orphaned` the rest, which verified against no artifact: `orphanedMaps` lists their
140
+ * paths, relative to the directory, sorted. `stale` counts the orphaned maps that failed verification against an
141
+ * artifact a method paired them with. A directory that can't be read has none.
142
+ */
143
+ export interface SourceMapDirectoryCounts {
144
+ path: string;
145
+ found: number;
146
+ paired: number;
147
+ stale: number;
148
+ orphaned: number;
149
+ orphanedMaps: string[];
150
+ }
151
+ /** HTML content that is not analyzed (§13 Counters, §19 HTML rules): informational, like `nonJs`. */
152
+ export interface HtmlCounters {
153
+ nomodule: number;
154
+ inlineHandlers: number;
155
+ }
156
+ /** What the resolver seam reports of one link group's work (§19): its steps, its step budget, and its caps. */
157
+ export type GroupStatistics = AnalyzeGroupOutput['stats'];
158
+ /**
159
+ * Scan statistics (§13 Counters, §18): the steps of every link group analyzed, their step budgets and how many of them
160
+ * ran out, each cap's hits summed over them, keyed in code-unit order, and the budget and caps the scan ran with.
161
+ * Budgets and caps are deterministic, so these are too. `crashGuard` says whether the wall-clock crash guard fired,
162
+ * which alone makes a scan `incomplete` without a record.
163
+ */
164
+ export interface Statistics {
165
+ steps: number;
166
+ stepBudget: number;
167
+ /** How many link groups exhausted their step budget (§18). Any makes a missing baseline entry `unconfirmed` (§11). */
168
+ budgetExhausted: number;
169
+ caps: Record<string, number>;
170
+ /**
171
+ * How many finding occurrences statically false catalog checks pruned (§7), plus the writes `catch` arms whose `try`
172
+ * can't throw dropped (§5 Resolver capabilities 9), summed over the link groups.
173
+ */
174
+ pruned: number;
175
+ limits: Budget;
176
+ crashGuard: 'fired' | 'not-fired';
177
+ }
178
+ export interface Counters {
179
+ artifacts: {
180
+ /** Every artifact of the scan: the denominator. */
181
+ known: number;
182
+ analyzed: number;
183
+ failed: Record<FailureReason, number>;
184
+ /**
185
+ * Artifacts whose code was never analyzed, and that don't make the scan `incomplete`: skipped `offline` (§17
186
+ * Modes), `refused` by the safety floor (§17 Safety floor), `excluded` or `nonJs` (§2).
187
+ */
188
+ skipped: {
189
+ offline: number;
190
+ refused: number;
191
+ excluded: number;
192
+ nonJs: number;
193
+ };
194
+ sweepOnly: number;
195
+ /**
196
+ * The files a mapping serves that no reference found, and that the sweep takes for neither JavaScript nor
197
+ * WebAssembly (§2 Sweep). Informational: they are not artifacts, so not in `known`.
198
+ */
199
+ otherFiles: number;
200
+ };
201
+ blindSpots: Record<keyof typeof blindSpotKinds, number>;
202
+ html: HtmlCounters;
203
+ statistics: Statistics;
204
+ }
205
+ export interface Completeness {
206
+ level: Level;
207
+ counters: Counters;
208
+ /** Sorted by artifact, location, kind and detail. */
209
+ limitations: LimitationRecord[];
210
+ /** The build files the sweep excluded because no URL mapping serves them (§2 Sweep), by path, sorted. */
211
+ excluded: string[];
212
+ attribution: AttributionBlock;
213
+ }
214
+ /**
215
+ * Applies §13 to every limitation record of a scan, each distinct record once. The level is the worst of the records'
216
+ * fixed levels, and `incomplete` when the crash guard fired. An artifact counts as failed when a failure record names
217
+ * it (all but a `parse-failure` one only when its bytes weren't read), as skipped offline when an `offline-skipped` record
218
+ * does, as refused when a `remote-refused` one does and its bytes weren't read, as failed by the crash guard when the
219
+ * guard stopped its link group, and as analyzed otherwise. The files the sweep excluded, and the WebAssembly files it
220
+ * found, are known too, and counted as skipped, so that `known` is the sum of the others. `sweepOnly` counts the
221
+ * artifacts only the sweep found. Each record that names a known artifact carries that artifact's path and hash.
222
+ */
223
+ export declare function aggregate(input: {
224
+ artifacts: readonly CountedArtifact[];
225
+ limitations: readonly LimitationRecord[];
226
+ html: HtmlCounters;
227
+ /** The stats of each link group analyzed, as the resolver seam returned them. */
228
+ statistics: readonly GroupStatistics[];
229
+ /** The step budget per source byte and the caps the link groups ran with (§18). */
230
+ budget: Budget;
231
+ /** Whether the wall-clock crash guard fired (§13, §18), and the artifacts whose link groups it kept from running. */
232
+ crashGuard: {
233
+ fired: boolean;
234
+ stopped: readonly ArtifactKey[];
235
+ };
236
+ swept: SweptFiles;
237
+ /** The scan's findings, whose attribution confidence the attribution block counts (§13). */
238
+ findings: readonly {
239
+ confidence: Pick<Confidence, 'attribution'>;
240
+ }[];
241
+ /** Each source-map directory's counts, in the order the directories were declared. */
242
+ sourceMapDirectories?: readonly SourceMapDirectoryCounts[];
243
+ /** Why introducers are unknown (§10): `no-lockfile` when not given. */
244
+ introducers?: Introducers;
245
+ }): Completeness;
246
+ export {};
@@ -0,0 +1,249 @@
1
+ // Completeness (§13, §19 Failures are data): the one completeness aggregator. Every module reports what it could not
2
+ // analyze as limitation records in its return value; this module applies §13's table to all of them, gives the scan
3
+ // its level, and counts. No other module decides `incomplete`.
4
+ import { compareStrings } from "../findings/index.js";
5
+ /** A scan's completeness levels (§13). */
6
+ export const levels = ['complete', 'complete-with-limitations', 'incomplete'];
7
+ /**
8
+ * The fixed level of each limitation kind (§13 Limitation kinds). There are no thresholds and nothing is
9
+ * configurable, so the same records always give the same level.
10
+ */
11
+ export const kindLevels = {
12
+ 'parse-failure': 'incomplete',
13
+ unreadable: 'incomplete',
14
+ 'source-maps-unpaired': 'incomplete',
15
+ 'fetch-failed': 'incomplete',
16
+ 'sri-mismatch': 'incomplete',
17
+ 'remote-refused': 'complete-with-limitations',
18
+ 'offline-skipped': 'complete-with-limitations',
19
+ 'dynamic-code': 'complete-with-limitations',
20
+ 'unresolved-reference': 'complete-with-limitations',
21
+ 'realm-uncertain': 'complete-with-limitations',
22
+ 'unresolved-holder': 'complete-with-limitations',
23
+ 'unlinked-module-call': 'complete-with-limitations',
24
+ 'analysis-budget': 'complete-with-limitations',
25
+ };
26
+ /** The kinds that mean an artifact failed: it is known, but its code was never analyzed (§13 Counters). */
27
+ const failureKinds = ['parse-failure', 'unreadable', 'fetch-failed', 'sri-mismatch'];
28
+ /** The kinds of record that name an artifact whose bytes weren't read. */
29
+ const unreadKinds = new Set([
30
+ 'unreadable',
31
+ 'fetch-failed',
32
+ 'sri-mismatch',
33
+ 'offline-skipped',
34
+ 'remote-refused',
35
+ ]);
36
+ /**
37
+ * Why an artifact failed (§13 Counters `failed` by reason): a failure record names it, or the crash guard fired before
38
+ * the link group that would have analyzed it ran (`crash-guard`). The crash guard raises no record: it is not a
39
+ * limitation kind.
40
+ */
41
+ const failureReasons = [...failureKinds, 'crash-guard'];
42
+ /** The blind-spot counters (§13 Counters), and the kind each one counts. */
43
+ const blindSpotKinds = {
44
+ dynamicCode: 'dynamic-code',
45
+ unresolvedReference: 'unresolved-reference',
46
+ realmUncertain: 'realm-uncertain',
47
+ unresolvedHolder: 'unresolved-holder',
48
+ };
49
+ /**
50
+ * The methods that pair a source map with an artifact (§2 Source maps), in the order they are tried: in the build
51
+ * directory, then in each source-map directory.
52
+ */
53
+ export const pairingMethods = [
54
+ 'header',
55
+ 'annotation',
56
+ 'sidecar',
57
+ 'debug-id',
58
+ 'external-debug-id',
59
+ 'external-annotation',
60
+ 'external-sidecar',
61
+ ];
62
+ /**
63
+ * Applies §13 to every limitation record of a scan, each distinct record once. The level is the worst of the records'
64
+ * fixed levels, and `incomplete` when the crash guard fired. An artifact counts as failed when a failure record names
65
+ * it (all but a `parse-failure` one only when its bytes weren't read), as skipped offline when an `offline-skipped` record
66
+ * does, as refused when a `remote-refused` one does and its bytes weren't read, as failed by the crash guard when the
67
+ * guard stopped its link group, and as analyzed otherwise. The files the sweep excluded, and the WebAssembly files it
68
+ * found, are known too, and counted as skipped, so that `known` is the sum of the others. `sweepOnly` counts the
69
+ * artifacts only the sweep found. Each record that names a known artifact carries that artifact's path and hash.
70
+ */
71
+ export function aggregate(input) {
72
+ const { artifacts, html, swept } = input;
73
+ // §13's kinds are a closed list. A record of any other kind is a bug in the module that returned it (§19).
74
+ for (const { kind } of input.limitations) {
75
+ if (!Object.hasOwn(kindLevels, kind))
76
+ throw new Error(`a limitation record has a kind outside §13: ${kind}`);
77
+ }
78
+ // A record names a URL, and one URL can be several artifacts: a record of bytes that weren't read carries the hash
79
+ // of none of them, rather than that of another artifact of its URL whose bytes were (§11, §17 Integrity).
80
+ const byUrl = new Map();
81
+ for (const artifact of artifacts) {
82
+ const known = byUrl.get(artifact.url) ?? {};
83
+ byUrl.set(artifact.url, artifact.hash === null ? { ...known, unread: artifact } : { ...known, read: artifact });
84
+ }
85
+ const limitations = unique(input.limitations.map((record) => normalized(record, byUrl))).sort(compareRecords);
86
+ const levels = new Set(limitations.map(({ kind }) => kindLevels[kind]));
87
+ if (input.crashGuard.fired)
88
+ levels.add('incomplete');
89
+ const level = levels.has('incomplete')
90
+ ? 'incomplete'
91
+ : levels.has('complete-with-limitations')
92
+ ? 'complete-with-limitations'
93
+ : 'complete';
94
+ const failed = Object.fromEntries(failureReasons.map((reason) => [reason, 0]));
95
+ const excluded = [...swept.excluded].sort(compareStrings);
96
+ const skipped = { offline: 0, refused: 0, excluded: excluded.length, nonJs: swept.nonJs };
97
+ const kindsByUrl = new Map();
98
+ for (const { kind, artifact } of limitations) {
99
+ if (artifact.url !== undefined)
100
+ kindsByUrl.set(artifact.url, (kindsByUrl.get(artifact.url) ?? new Set()).add(kind));
101
+ }
102
+ const key = ({ url, realm, sourceType }) => JSON.stringify([realm, sourceType, url]);
103
+ const stopped = new Set(input.crashGuard.stopped.map(key));
104
+ let analyzed = 0;
105
+ for (const artifact of artifacts) {
106
+ const kinds = kindsByUrl.get(artifact.url) ?? new Set();
107
+ // A record names a URL, and one URL can be several artifacts whose bytes weren't all withheld: an integrity value
108
+ // belongs to a reference, and a URL fetched in another realm may carry none (§17 Integrity), and one key URL
109
+ // reached through two fetched URLs is an artifact of each (§11). Only an artifact whose bytes weren't read is the
110
+ // one that failed to be read. A parse failure is of bytes that were.
111
+ const failure = failureKinds.find((kind) => kinds.has(kind) && (kind === 'parse-failure' || artifact.hash === null));
112
+ if (failure !== undefined)
113
+ failed[failure]++;
114
+ else if (kinds.has('offline-skipped'))
115
+ skipped.offline++;
116
+ // A record names a URL, not a realm, and one URL can be refused as a worker's script or a module but read as a
117
+ // classic script of the window (§17 Content types): only an artifact whose bytes weren't read is the refused one.
118
+ else if (kinds.has('remote-refused') && artifact.hash === null)
119
+ skipped.refused++;
120
+ else if (stopped.has(key(artifact)))
121
+ failed['crash-guard']++;
122
+ else
123
+ analyzed++;
124
+ }
125
+ const count = (kind) => limitations.filter((record) => record.kind === kind).length;
126
+ const blindSpots = Object.fromEntries(Object.entries(blindSpotKinds).map(([counter, kind]) => [counter, count(kind)]));
127
+ const known = artifacts.length + skipped.excluded + skipped.nonJs;
128
+ const sweepOnly = artifacts.filter(({ discovery }) => discovery === 'sweep').length;
129
+ return {
130
+ level,
131
+ counters: {
132
+ artifacts: { known, analyzed, failed, skipped, sweepOnly, otherFiles: swept.otherFiles },
133
+ blindSpots,
134
+ html: { nomodule: html.nomodule, inlineHandlers: html.inlineHandlers },
135
+ statistics: {
136
+ ...statistics(input.statistics),
137
+ limits: input.budget,
138
+ crashGuard: input.crashGuard.fired ? 'fired' : 'not-fired',
139
+ },
140
+ },
141
+ limitations,
142
+ excluded,
143
+ attribution: {
144
+ ...attributionBlock(artifacts, input.findings),
145
+ sourceMapDirectories: [...(input.sourceMapDirectories ?? [])],
146
+ introducers: input.introducers ?? { reason: 'no-lockfile', evidence: null },
147
+ },
148
+ };
149
+ }
150
+ /** Sums the link groups' steps and step budgets, counts those that ran out, and sums each cap over the groups. */
151
+ function statistics(groups) {
152
+ const caps = new Map();
153
+ let steps = 0;
154
+ let stepBudget = 0;
155
+ let budgetExhausted = 0;
156
+ let pruned = 0;
157
+ for (const group of groups) {
158
+ pruned += group.pruned ?? 0;
159
+ steps += group.steps;
160
+ stepBudget += group.budget;
161
+ if (group.exhausted)
162
+ budgetExhausted++;
163
+ for (const [cap, count] of Object.entries(group.caps))
164
+ caps.set(cap, (caps.get(cap) ?? 0) + count);
165
+ }
166
+ const sorted = Object.fromEntries([...caps].sort(([a], [b]) => compareStrings(a, b)));
167
+ return { steps, stepBudget, budgetExhausted, caps: sorted, pruned };
168
+ }
169
+ /**
170
+ * Counts the artifacts with, without and with broken source maps, those with one by pairing method, the candidate maps
171
+ * that failed verification, the maps of remote artifacts that weren't read (§17), and the findings whose attribution
172
+ * confidence is capped (§13).
173
+ */
174
+ function attributionBlock(artifacts, findings) {
175
+ const pairings = Object.fromEntries(pairingMethods.map((method) => [method, 0]));
176
+ const counts = { withMap: 0, withoutMap: 0, withBrokenMap: 0 };
177
+ let staleCandidates = 0;
178
+ const unreadMaps = { refused: 0, failed: 0, unreadable: 0 };
179
+ for (const { sourceMap, staleCandidates: stale, unreadMaps: unread } of artifacts) {
180
+ if (sourceMap.status === 'verified') {
181
+ counts.withMap++;
182
+ pairings[sourceMap.method]++;
183
+ }
184
+ else if (sourceMap.status === 'stale')
185
+ counts.withBrokenMap++;
186
+ else
187
+ counts.withoutMap++;
188
+ staleCandidates += stale;
189
+ unreadMaps.refused += unread.refused;
190
+ unreadMaps.failed += unread.failed;
191
+ unreadMaps.unreadable += unread.unreadable;
192
+ }
193
+ const capped = (level) => findings.filter(({ confidence }) => confidence.attribution.level === level).length;
194
+ return {
195
+ artifacts: counts,
196
+ pairings,
197
+ staleCandidates,
198
+ unreadMaps,
199
+ cappedFindings: { medium: capped('medium'), low: capped('low') },
200
+ };
201
+ }
202
+ /** A record with its keys in a fixed order, and the path and hash of the artifact it names when they are known. */
203
+ function normalized(record, byUrl) {
204
+ const both = record.artifact.url === undefined ? undefined : byUrl.get(record.artifact.url);
205
+ const known = unreadKinds.has(record.kind) ? (both?.unread ?? both?.read) : (both?.read ?? both?.unread);
206
+ const url = record.artifact.url;
207
+ const path = record.artifact.path ?? known?.path ?? undefined;
208
+ const hash = record.artifact.hash ?? known?.hash ?? undefined;
209
+ return {
210
+ kind: record.kind,
211
+ artifact: {
212
+ ...(url === undefined ? {} : { url }),
213
+ ...(path === undefined ? {} : { path }),
214
+ ...(hash === undefined ? {} : { hash }),
215
+ },
216
+ ...(record.location === undefined ? {} : { location: location(record.location) }),
217
+ detail: record.detail,
218
+ };
219
+ }
220
+ function location({ start, end, line, column }) {
221
+ return { start, end, line, column };
222
+ }
223
+ /**
224
+ * The normalized records, each once. Code analyzed more than once, as a script is in each realm that runs it (§2
225
+ * Realm), reports the same limitation at the same place each time, and it is one blind spot.
226
+ */
227
+ function unique(records) {
228
+ const seen = new Set();
229
+ return records.filter((record) => {
230
+ const key = JSON.stringify(record);
231
+ if (seen.has(key))
232
+ return false;
233
+ seen.add(key);
234
+ return true;
235
+ });
236
+ }
237
+ /**
238
+ * Records sort by artifact URL (or path), then location, kind and detail. Normalized records that tie on all of those
239
+ * (they differ only in what is known of their artifact) sort by their JSON, so the order is total and never depends on
240
+ * the order the modules returned them in.
241
+ */
242
+ function compareRecords(a, b) {
243
+ return (compareStrings(a.artifact.url ?? a.artifact.path ?? '', b.artifact.url ?? b.artifact.path ?? '') ||
244
+ (a.location?.start ?? -1) - (b.location?.start ?? -1) ||
245
+ (a.location?.end ?? -1) - (b.location?.end ?? -1) ||
246
+ compareStrings(a.kind, b.kind) ||
247
+ compareStrings(a.detail, b.detail) ||
248
+ compareStrings(JSON.stringify(a), JSON.stringify(b)));
249
+ }
@@ -0,0 +1,54 @@
1
+ /** The levels, highest first. */
2
+ export declare const levels: readonly ["high", "medium", "low"];
3
+ export type Level = (typeof levels)[number];
4
+ /** The closed list of target-confidence reasons, with the level each gives (§9 Target confidence). */
5
+ export declare const targetReasons: {
6
+ readonly 'realm-assumed': "medium";
7
+ readonly 'holder-alternatives': "medium";
8
+ readonly 'member-alternatives': "medium";
9
+ readonly 'direct-eval-scope': "medium";
10
+ readonly 'with-scope': "low";
11
+ };
12
+ export type TargetReason = keyof typeof targetReasons;
13
+ /** The closed list of mutation-confidence reasons, with the level each gives (§9 Mutation confidence). */
14
+ export declare const mutationReasons: {
15
+ readonly 'outcome-undecidable': "medium";
16
+ readonly 'wildcard-outcome-mixed': "medium";
17
+ readonly 'const-collision': "low";
18
+ };
19
+ export type MutationReason = keyof typeof mutationReasons;
20
+ /** The closed list of attribution-confidence reasons, with the level each gives (§9 Attribution confidence). */
21
+ export declare const attributionReasons: {
22
+ readonly 'token-check-n/a': "medium";
23
+ readonly 'sidecar-not-token-verified': "medium";
24
+ readonly 'framework-path-rule': "medium";
25
+ readonly 'first-party-inferred': "medium";
26
+ readonly 'prebundled-dist': "medium";
27
+ readonly 'yarn-slug': "low";
28
+ readonly 'line-separator': "medium";
29
+ readonly 'no-map': "low";
30
+ readonly 'escaped-source': "low";
31
+ readonly 'token-check-failed': "low";
32
+ readonly vendored: "low";
33
+ readonly 'foreign-scheme': "low";
34
+ readonly 'ignore-list-demoted': "low";
35
+ };
36
+ export type AttributionReason = keyof typeof attributionReasons;
37
+ /** One dimension's confidence: its level, and the reasons that hold it below `high` (none at `high`). */
38
+ export interface Dimension<Reason extends string> {
39
+ level: Level;
40
+ reasons: Reason[];
41
+ }
42
+ export interface Confidence {
43
+ target: Dimension<TargetReason>;
44
+ mutation: Dimension<MutationReason>;
45
+ attribution: Dimension<AttributionReason>;
46
+ }
47
+ export declare const targetConfidence: (reasons?: Iterable<TargetReason>) => Dimension<"realm-assumed" | "holder-alternatives" | "member-alternatives" | "direct-eval-scope" | "with-scope">;
48
+ export declare const mutationConfidence: (reasons?: Iterable<MutationReason>) => Dimension<"outcome-undecidable" | "wildcard-outcome-mixed" | "const-collision">;
49
+ export declare const attributionConfidence: (reasons?: Iterable<AttributionReason>) => Dimension<"framework-path-rule" | "token-check-n/a" | "sidecar-not-token-verified" | "first-party-inferred" | "prebundled-dist" | "yarn-slug" | "line-separator" | "no-map" | "escaped-source" | "token-check-failed" | "vendored" | "foreign-scheme" | "ignore-list-demoted">;
50
+ /**
51
+ * A finding's confidence from its occurrences' (§9 Occurrences): per dimension, the highest level among them, because
52
+ * one certain site proves the mutation exists. The finding carries the reasons of the occurrences at that level.
53
+ */
54
+ export declare function rollUp(occurrences: readonly Confidence[]): Confidence;
@@ -0,0 +1,71 @@
1
+ // Confidence (§9): analytical certainty about a finding, never a measure of risk. It is recorded for three dimensions:
2
+ // target (is the named target the one written?), mutation (does the write take effect as stated?) and attribution (are
3
+ // the containing package and the original location right?). The resolver gives target and mutation reasons,
4
+ // attribution gives attribution reasons, and findings rolls occurrences up into their finding.
5
+ /** The levels, highest first. */
6
+ export const levels = ['high', 'medium', 'low'];
7
+ /** The closed list of target-confidence reasons, with the level each gives (§9 Target confidence). */
8
+ export const targetReasons = {
9
+ 'realm-assumed': 'medium',
10
+ 'holder-alternatives': 'medium',
11
+ 'member-alternatives': 'medium',
12
+ 'direct-eval-scope': 'medium',
13
+ 'with-scope': 'low',
14
+ };
15
+ /** The closed list of mutation-confidence reasons, with the level each gives (§9 Mutation confidence). */
16
+ export const mutationReasons = {
17
+ 'outcome-undecidable': 'medium',
18
+ 'wildcard-outcome-mixed': 'medium',
19
+ 'const-collision': 'low',
20
+ };
21
+ /** The closed list of attribution-confidence reasons, with the level each gives (§9 Attribution confidence). */
22
+ export const attributionReasons = {
23
+ 'token-check-n/a': 'medium',
24
+ 'sidecar-not-token-verified': 'medium',
25
+ 'framework-path-rule': 'medium',
26
+ 'first-party-inferred': 'medium',
27
+ 'prebundled-dist': 'medium',
28
+ 'yarn-slug': 'low',
29
+ 'line-separator': 'medium',
30
+ 'no-map': 'low',
31
+ 'escaped-source': 'low',
32
+ 'token-check-failed': 'low',
33
+ vendored: 'low',
34
+ 'foreign-scheme': 'low',
35
+ 'ignore-list-demoted': 'low',
36
+ };
37
+ export const targetConfidence = (reasons = []) => dimension(targetReasons, reasons);
38
+ export const mutationConfidence = (reasons = []) => dimension(mutationReasons, reasons);
39
+ export const attributionConfidence = (reasons = []) => dimension(attributionReasons, reasons);
40
+ /**
41
+ * A dimension from its reasons: its level is the lowest level among them, and `high` when there are none. Each reason
42
+ * is listed once, in the order of §9's table.
43
+ */
44
+ function dimension(table, reasons) {
45
+ const given = new Set(reasons);
46
+ const listed = Object.keys(table).filter((reason) => given.has(reason));
47
+ if (listed.length !== given.size)
48
+ throw new Error(`not a confidence reason of this dimension: ${[...given].join(', ')}`);
49
+ return { level: lowest(listed.map((reason) => table[reason])), reasons: listed };
50
+ }
51
+ /**
52
+ * A finding's confidence from its occurrences' (§9 Occurrences): per dimension, the highest level among them, because
53
+ * one certain site proves the mutation exists. The finding carries the reasons of the occurrences at that level.
54
+ */
55
+ export function rollUp(occurrences) {
56
+ return {
57
+ target: highest(occurrences.map((confidence) => confidence.target), targetReasons),
58
+ mutation: highest(occurrences.map((confidence) => confidence.mutation), mutationReasons),
59
+ attribution: highest(occurrences.map((confidence) => confidence.attribution), attributionReasons),
60
+ };
61
+ }
62
+ function highest(dimensions, table) {
63
+ if (dimensions.length === 0)
64
+ throw new Error('a finding has at least one occurrence');
65
+ const level = levels[Math.min(...dimensions.map((dimension) => levels.indexOf(dimension.level)))];
66
+ const reasons = dimensions.filter((dimension) => dimension.level === level).flatMap((dimension) => dimension.reasons);
67
+ return dimension(table, reasons);
68
+ }
69
+ function lowest(given) {
70
+ return levels[Math.max(0, ...given.map((level) => levels.indexOf(level)))];
71
+ }