@hraness/ghostget 0.17.2

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 (500) hide show
  1. package/CHANGELOG.md +458 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1359 -0
  4. package/bunfig.toml +2 -0
  5. package/dist/apple-photos-client.js +704 -0
  6. package/dist/beeper-client.js +4602 -0
  7. package/dist/client.js +1605 -0
  8. package/dist/index-26yq8q16.js +31 -0
  9. package/dist/index-9t13b4k7.js +508 -0
  10. package/dist/index-axwm9b3v.js +5 -0
  11. package/dist/index-gwk7rbyj.js +30 -0
  12. package/dist/index.js +701 -0
  13. package/dist/messaging.js +1130 -0
  14. package/dist/omni-client.js +1406 -0
  15. package/dist/whatsapp-client.js +355 -0
  16. package/docs/imessage-direct-provider.md +106 -0
  17. package/docs/rental-listings.md +94 -0
  18. package/package.json +600 -0
  19. package/skills/ghostget/SKILL.md +251 -0
  20. package/skills/ghostget/agents/openai.yaml +4 -0
  21. package/skills/ghostget/references/adapter-contract.md +181 -0
  22. package/skills/ghostget/references/apple-photos.md +115 -0
  23. package/skills/ghostget/references/article-drafts.md +235 -0
  24. package/skills/ghostget/references/code-owned-provider-scaffold.md +134 -0
  25. package/skills/ghostget/references/cross-posting-video.md +139 -0
  26. package/skills/ghostget/references/cross-posting.md +82 -0
  27. package/skills/ghostget/references/derivation.md +390 -0
  28. package/skills/ghostget/references/hraness-social-profile-stats.json +269 -0
  29. package/skills/ghostget/references/install.md +66 -0
  30. package/skills/ghostget/references/linkedin-adapter.md +442 -0
  31. package/skills/ghostget/references/messaging.md +168 -0
  32. package/skills/ghostget/references/meta-comet-contract.md +153 -0
  33. package/skills/ghostget/references/platform-patterns.md +122 -0
  34. package/skills/ghostget/references/provider-plugins.md +496 -0
  35. package/skills/ghostget/references/reddit-flair.md +78 -0
  36. package/skills/ghostget/references/safety-and-state.md +214 -0
  37. package/skills/ghostget/references/settlement-and-duplicate-cleanup.md +79 -0
  38. package/skills/ghostget/references/social-platform-routing.md +66 -0
  39. package/skills/ghostget/references/social-profile-stats.md +195 -0
  40. package/skills/ghostget/references/social-video-platform-routing.md +89 -0
  41. package/skills/ghostget/references/whatsapp-adapter.md +101 -0
  42. package/skills/ghostget/references/x-adapter.md +332 -0
  43. package/skills/ghostget/references/x-ai-disclosure.md +71 -0
  44. package/src/apple-photos-cli.ts +639 -0
  45. package/src/apple-photos-client-types.ts +168 -0
  46. package/src/apple-photos-client.ts +201 -0
  47. package/src/apple-photos-contact-evidence.ts +838 -0
  48. package/src/apple-photos-local-source.ts +1131 -0
  49. package/src/args.ts +2053 -0
  50. package/src/article-draft-document.ts +417 -0
  51. package/src/article-draft-embeds.ts +123 -0
  52. package/src/article-draft-images.ts +214 -0
  53. package/src/assets/adapter-template/wrench-adapter.json +40 -0
  54. package/src/assets/adapters/beeper/wrench-web-adapter.json +1500 -0
  55. package/src/assets/adapters/beeper/wrench-web-adapter.v1.0.0.json +129 -0
  56. package/src/assets/adapters/beeper/wrench-web-adapter.v1.1.0.json +207 -0
  57. package/src/assets/adapters/beeper/wrench-web-adapter.v2.0.0.json +1463 -0
  58. package/src/assets/adapters/beeper/wrench-web-adapter.v2.1.0.json +1463 -0
  59. package/src/assets/adapters/beeper/wrench-web-adapter.v2.2.0.json +1475 -0
  60. package/src/assets/adapters/beeper/wrench-web-adapter.v2.3.0.json +1488 -0
  61. package/src/assets/adapters/bluesky/wrench-web-adapter.json +684 -0
  62. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.0.0.json +581 -0
  63. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.1.0.json +581 -0
  64. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.2.0.json +581 -0
  65. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.3.0.json +615 -0
  66. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.4.0.json +684 -0
  67. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.5.0.json +684 -0
  68. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.6.0.json +684 -0
  69. package/src/assets/adapters/clasificados/wrench-web-adapter.json +54 -0
  70. package/src/assets/adapters/facebook/wrench-web-adapter.json +750 -0
  71. package/src/assets/adapters/facebook/wrench-web-adapter.v1.0.0.json +706 -0
  72. package/src/assets/adapters/facebook/wrench-web-adapter.v1.1.0.json +703 -0
  73. package/src/assets/adapters/facebook/wrench-web-adapter.v1.2.0.json +750 -0
  74. package/src/assets/adapters/facebook-group/wrench-web-adapter.json +682 -0
  75. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.0.0.json +583 -0
  76. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.1.0.json +682 -0
  77. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.json +437 -0
  78. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.0.0.json +383 -0
  79. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.0.json +437 -0
  80. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.1.json +437 -0
  81. package/src/assets/adapters/facebook-page/wrench-web-adapter.json +880 -0
  82. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.0.0.json +740 -0
  83. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.1.0.json +880 -0
  84. package/src/assets/adapters/github/wrench-web-adapter.json +69 -0
  85. package/src/assets/adapters/github/wrench-web-adapter.v1.0.0.json +42 -0
  86. package/src/assets/adapters/github/wrench-web-adapter.v1.1.0.json +69 -0
  87. package/src/assets/adapters/gmail/wrench-adapter.json +161 -0
  88. package/src/assets/adapters/gmail/wrench-adapter.v1.2.0.json +157 -0
  89. package/src/assets/adapters/hacker-news/wrench-web-adapter.json +315 -0
  90. package/src/assets/adapters/hacker-news/wrench-web-adapter.v1.0.0.json +315 -0
  91. package/src/assets/adapters/imessage/wrench-web-adapter.json +236 -0
  92. package/src/assets/adapters/instagram/wrench-web-adapter.json +719 -0
  93. package/src/assets/adapters/instagram/wrench-web-adapter.v1.0.0.json +627 -0
  94. package/src/assets/adapters/instagram/wrench-web-adapter.v1.1.0.json +609 -0
  95. package/src/assets/adapters/instagram/wrench-web-adapter.v1.2.0.json +640 -0
  96. package/src/assets/adapters/instagram/wrench-web-adapter.v1.3.0.json +671 -0
  97. package/src/assets/adapters/instagram/wrench-web-adapter.v1.4.0.json +710 -0
  98. package/src/assets/adapters/instagram/wrench-web-adapter.v1.5.0.json +710 -0
  99. package/src/assets/adapters/instagram/wrench-web-adapter.v1.6.0.json +719 -0
  100. package/src/assets/adapters/instagram/wrench-web-adapter.v1.7.0.json +719 -0
  101. package/src/assets/adapters/linkedin/wrench-adapter.json +442 -0
  102. package/src/assets/adapters/linkedin/wrench-adapter.v0.4.0.json +124 -0
  103. package/src/assets/adapters/linkedin/wrench-adapter.v1.0.0.json +411 -0
  104. package/src/assets/adapters/linkedin/wrench-web-adapter.json +914 -0
  105. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.0.0.json +604 -0
  106. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.1.0.json +604 -0
  107. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.10.0.json +809 -0
  108. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.11.0.json +809 -0
  109. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.12.0.json +820 -0
  110. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.13.0.json +819 -0
  111. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json +819 -0
  112. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json +819 -0
  113. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json +874 -0
  114. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json +874 -0
  115. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.18.0.json +874 -0
  116. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.19.0.json +874 -0
  117. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.2.0.json +731 -0
  118. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.3.0.json +768 -0
  119. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.4.0.json +808 -0
  120. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.5.0.json +808 -0
  121. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.6.0.json +808 -0
  122. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.7.0.json +825 -0
  123. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.8.0.json +792 -0
  124. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.9.0.json +809 -0
  125. package/src/assets/adapters/reddit/wrench-web-adapter.json +855 -0
  126. package/src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json +577 -0
  127. package/src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json +604 -0
  128. package/src/assets/adapters/reddit/wrench-web-adapter.v1.10.0.json +712 -0
  129. package/src/assets/adapters/reddit/wrench-web-adapter.v1.11.0.json +712 -0
  130. package/src/assets/adapters/reddit/wrench-web-adapter.v1.12.0.json +712 -0
  131. package/src/assets/adapters/reddit/wrench-web-adapter.v1.2.0.json +653 -0
  132. package/src/assets/adapters/reddit/wrench-web-adapter.v1.3.0.json +712 -0
  133. package/src/assets/adapters/reddit/wrench-web-adapter.v1.4.0.json +713 -0
  134. package/src/assets/adapters/reddit/wrench-web-adapter.v1.5.0.json +712 -0
  135. package/src/assets/adapters/reddit/wrench-web-adapter.v1.6.0.json +713 -0
  136. package/src/assets/adapters/reddit/wrench-web-adapter.v1.7.0.json +713 -0
  137. package/src/assets/adapters/reddit/wrench-web-adapter.v1.8.0.json +713 -0
  138. package/src/assets/adapters/reddit/wrench-web-adapter.v1.9.0.json +713 -0
  139. package/src/assets/adapters/substack/wrench-web-adapter.json +936 -0
  140. package/src/assets/adapters/substack/wrench-web-adapter.v1.0.0.json +817 -0
  141. package/src/assets/adapters/substack/wrench-web-adapter.v1.1.0.json +812 -0
  142. package/src/assets/adapters/substack/wrench-web-adapter.v1.2.0.json +812 -0
  143. package/src/assets/adapters/substack/wrench-web-adapter.v1.3.0.json +902 -0
  144. package/src/assets/adapters/substack/wrench-web-adapter.v1.4.0.json +936 -0
  145. package/src/assets/adapters/substack/wrench-web-adapter.v1.5.0.json +936 -0
  146. package/src/assets/adapters/substack/wrench-web-adapter.v1.6.0.json +936 -0
  147. package/src/assets/adapters/threads/wrench-web-adapter.json +692 -0
  148. package/src/assets/adapters/threads/wrench-web-adapter.v1.0.0.json +629 -0
  149. package/src/assets/adapters/threads/wrench-web-adapter.v1.1.0.json +623 -0
  150. package/src/assets/adapters/threads/wrench-web-adapter.v1.2.0.json +622 -0
  151. package/src/assets/adapters/threads/wrench-web-adapter.v1.3.0.json +622 -0
  152. package/src/assets/adapters/threads/wrench-web-adapter.v1.4.0.json +622 -0
  153. package/src/assets/adapters/threads/wrench-web-adapter.v1.5.0.json +699 -0
  154. package/src/assets/adapters/threads/wrench-web-adapter.v1.6.0.json +693 -0
  155. package/src/assets/adapters/threads/wrench-web-adapter.v1.7.0.json +692 -0
  156. package/src/assets/adapters/tiktok/wrench-web-adapter.json +762 -0
  157. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.0.0.json +681 -0
  158. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.1.0.json +708 -0
  159. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.2.0.json +762 -0
  160. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.3.0.json +762 -0
  161. package/src/assets/adapters/twitch/wrench-web-adapter.json +42 -0
  162. package/src/assets/adapters/twitch/wrench-web-adapter.v1.0.0.json +42 -0
  163. package/src/assets/adapters/whatsapp/wrench-web-adapter.json +400 -0
  164. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.0.0.json +355 -0
  165. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.1.0.json +386 -0
  166. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.2.0.json +386 -0
  167. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.3.0.json +400 -0
  168. package/src/assets/adapters/x/wrench-adapter.json +749 -0
  169. package/src/assets/adapters/x/wrench-adapter.v1.0.0.json +700 -0
  170. package/src/assets/adapters/x/wrench-adapter.v1.1.0.json +707 -0
  171. package/src/assets/adapters/x/wrench-web-adapter.json +712 -0
  172. package/src/assets/adapters/x/wrench-web-adapter.v1.1.0.json +579 -0
  173. package/src/assets/adapters/x/wrench-web-adapter.v1.10.0.json +685 -0
  174. package/src/assets/adapters/x/wrench-web-adapter.v1.11.0.json +712 -0
  175. package/src/assets/adapters/x/wrench-web-adapter.v1.12.0.json +712 -0
  176. package/src/assets/adapters/x/wrench-web-adapter.v1.13.0.json +712 -0
  177. package/src/assets/adapters/x/wrench-web-adapter.v1.2.0.json +578 -0
  178. package/src/assets/adapters/x/wrench-web-adapter.v1.3.0.json +610 -0
  179. package/src/assets/adapters/x/wrench-web-adapter.v1.4.0.json +617 -0
  180. package/src/assets/adapters/x/wrench-web-adapter.v1.5.0.json +634 -0
  181. package/src/assets/adapters/x/wrench-web-adapter.v1.6.0.json +632 -0
  182. package/src/assets/adapters/x/wrench-web-adapter.v1.7.0.json +649 -0
  183. package/src/assets/adapters/x/wrench-web-adapter.v1.8.0.json +649 -0
  184. package/src/assets/adapters/x/wrench-web-adapter.v1.9.0.json +683 -0
  185. package/src/assets/adapters/youtube/wrench-web-adapter.json +561 -0
  186. package/src/assets/adapters/youtube/wrench-web-adapter.v1.0.0.json +465 -0
  187. package/src/assets/adapters/youtube/wrench-web-adapter.v1.1.0.json +519 -0
  188. package/src/assets/adapters/youtube/wrench-web-adapter.v1.2.0.json +561 -0
  189. package/src/assets/adapters/youtube/wrench-web-adapter.v1.3.0.json +561 -0
  190. package/src/assets/code-owned-provider-template/plugin.test.ts.template +54 -0
  191. package/src/assets/code-owned-provider-template/plugin.ts.template +102 -0
  192. package/src/assets/code-owned-provider-template/runtime.internal.test.ts.template +44 -0
  193. package/src/assets/code-owned-provider-template/runtime.ts.template +59 -0
  194. package/src/assets/code-owned-provider-template/wrench-adapter.json.template +33 -0
  195. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  196. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  197. package/src/assets/fonts/nebula-sans/PROVENANCE.md +14 -0
  198. package/src/assets.d.ts +4 -0
  199. package/src/auth.ts +1157 -0
  200. package/src/beeper-client-types.ts +169 -0
  201. package/src/beeper-client.ts +250 -0
  202. package/src/beeper-contact-interactions-cli.ts +180 -0
  203. package/src/beeper-contact-interactions.ts +1612 -0
  204. package/src/beeper-message-bundle-v1.ts +257 -0
  205. package/src/beeper-message-like-me-cli.ts +84 -0
  206. package/src/beeper-message-like-me-export.ts +2274 -0
  207. package/src/beeper-message-like-me-recovery.ts +931 -0
  208. package/src/beeper-message-like-me-source.ts +4034 -0
  209. package/src/browser-admission.ts +928 -0
  210. package/src/browser-snapshots.ts +260 -0
  211. package/src/browser.ts +3873 -0
  212. package/src/canonical-json.ts +60 -0
  213. package/src/catalog-cli.ts +638 -0
  214. package/src/cli.ts +208 -0
  215. package/src/client-types.ts +301 -0
  216. package/src/client.ts +2726 -0
  217. package/src/confirmed-write-failure.ts +44 -0
  218. package/src/confirmed-write-model.ts +72 -0
  219. package/src/confirmed-write-platform.ts +1055 -0
  220. package/src/confirmed-write-program.ts +224 -0
  221. package/src/confirmed-write-runtime.ts +16 -0
  222. package/src/cursor-token.ts +228 -0
  223. package/src/derivation-dnr-readiness.ts +367 -0
  224. package/src/derivation-file-chooser.ts +384 -0
  225. package/src/derivation-network-boundary.ts +835 -0
  226. package/src/derivation-network-guard.ts +782 -0
  227. package/src/derivation-network-proxy-helper.ts +440 -0
  228. package/src/derivation-network-proxy.ts +385 -0
  229. package/src/derive-command-helper.ts +693 -0
  230. package/src/derive-fixtures.ts +343 -0
  231. package/src/derive-review.ts +980 -0
  232. package/src/derive.ts +4754 -0
  233. package/src/fixtures/beeper-message-like-me-v1/accounts.ndjson +2 -0
  234. package/src/fixtures/beeper-message-like-me-v1/conversations.ndjson +1 -0
  235. package/src/fixtures/beeper-message-like-me-v1/manifest.json +1 -0
  236. package/src/fixtures/beeper-message-like-me-v1/messages.ndjson +2 -0
  237. package/src/fixtures/beeper-message-like-me-v1/participants.ndjson +3 -0
  238. package/src/fixtures/beeper-message-like-me-v1/reactions.ndjson +1 -0
  239. package/src/fixtures/beeper-message-like-me-v1/tombstones.ndjson +1 -0
  240. package/src/ghostget.ts +3672 -0
  241. package/src/gmail-capture.ts +1244 -0
  242. package/src/har-internal.ts +1242 -0
  243. package/src/har.ts +743 -0
  244. package/src/index.ts +218 -0
  245. package/src/invocation-read-platform.ts +55 -0
  246. package/src/invocation-read-program.ts +93 -0
  247. package/src/invocation-read-runtime.ts +11 -0
  248. package/src/linked-device-lifecycle-admission.ts +450 -0
  249. package/src/linked-device-lifecycle-journal.ts +1560 -0
  250. package/src/linked-device-lifecycle-runtime.ts +1602 -0
  251. package/src/local-cli-admission.ts +135 -0
  252. package/src/local-cli-contracts.ts +271 -0
  253. package/src/local-cli-execution.ts +98 -0
  254. package/src/local-cli-surface-contract.ts +1510 -0
  255. package/src/local-cli-tool-identity.ts +336 -0
  256. package/src/media/archive.ts +2325 -0
  257. package/src/media/args.ts +303 -0
  258. package/src/media/cli.ts +287 -0
  259. package/src/media/doctor.ts +475 -0
  260. package/src/media/ffmpeg.ts +1330 -0
  261. package/src/media/http-capture.ts +711 -0
  262. package/src/media/http-probe.ts +593 -0
  263. package/src/media/http.ts +387 -0
  264. package/src/media/index.ts +10 -0
  265. package/src/media/local-transcription.ts +613 -0
  266. package/src/media/lock.ts +321 -0
  267. package/src/media/manifest.ts +1712 -0
  268. package/src/media/metadata.ts +906 -0
  269. package/src/media/process.ts +723 -0
  270. package/src/media/revision.ts +166 -0
  271. package/src/media/runtime-closure.ts +1063 -0
  272. package/src/media/source-router.ts +187 -0
  273. package/src/media/transcriber-config.ts +1122 -0
  274. package/src/media/transcript-persistence-model.ts +15 -0
  275. package/src/media/transcript-persistence-platform.ts +89 -0
  276. package/src/media/transcript-persistence-program.ts +39 -0
  277. package/src/media/transcript-persistence-runtime.ts +24 -0
  278. package/src/media/transcript.ts +616 -0
  279. package/src/media/utf8-order.ts +30 -0
  280. package/src/media/whisper-cpp.ts +464 -0
  281. package/src/media/whisper-language.ts +58 -0
  282. package/src/media/yt-dlp.ts +439 -0
  283. package/src/message-like-me-agentic-messaging.ts +882 -0
  284. package/src/messaging-action-store.ts +793 -0
  285. package/src/messaging-runtime.ts +2136 -0
  286. package/src/messaging-store.ts +539 -0
  287. package/src/messaging-types.ts +1663 -0
  288. package/src/messaging.ts +334 -0
  289. package/src/model.ts +2671 -0
  290. package/src/oauth-google.ts +888 -0
  291. package/src/omni-client-types.ts +306 -0
  292. package/src/omni-client.ts +2225 -0
  293. package/src/omni-limits.ts +34 -0
  294. package/src/omni-model.ts +1614 -0
  295. package/src/omni-request.ts +379 -0
  296. package/src/omni-runtime.ts +1632 -0
  297. package/src/operation-deadline.ts +189 -0
  298. package/src/path-helper.ts +1656 -0
  299. package/src/pinned-https.ts +194 -0
  300. package/src/plan-assets.ts +832 -0
  301. package/src/platform-catalog.ts +1832 -0
  302. package/src/plugins/beeper-linked-device/plugin.ts +310 -0
  303. package/src/plugins/bluesky-web/plugin.ts +178 -0
  304. package/src/plugins/clasificados-web/plugin.ts +66 -0
  305. package/src/plugins/github-web/plugin.ts +80 -0
  306. package/src/plugins/gmail-official/plugin.ts +110 -0
  307. package/src/plugins/hacker-news-web/plugin.ts +52 -0
  308. package/src/plugins/imessage-direct/plugin.ts +195 -0
  309. package/src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch +1430 -0
  310. package/src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch +225 -0
  311. package/src/plugins/imessage-direct/vendor/provenance.json +80 -0
  312. package/src/plugins/linkedin-official/plugin.ts +84 -0
  313. package/src/plugins/linkedin-web/plugin.ts +412 -0
  314. package/src/plugins/meta-web/plugin.ts +317 -0
  315. package/src/plugins/reddit-web/flair.ts +492 -0
  316. package/src/plugins/reddit-web/plugin.ts +477 -0
  317. package/src/plugins/substack-web/plugin.ts +124 -0
  318. package/src/plugins/tiktok-web/plugin.ts +67 -0
  319. package/src/plugins/twitch-web/plugin.ts +54 -0
  320. package/src/plugins/whatsapp-linked-device/plugin.ts +105 -0
  321. package/src/plugins/x-official/plugin.ts +64 -0
  322. package/src/plugins/x-web/plugin.ts +488 -0
  323. package/src/plugins/youtube-web/plugin.ts +101 -0
  324. package/src/portable-run-recovery.ts +466 -0
  325. package/src/process-identity.ts +501 -0
  326. package/src/profile-clone-helper.ts +104 -0
  327. package/src/provider-catalog-views.ts +51 -0
  328. package/src/provider-context.ts +69 -0
  329. package/src/provider-contract-definitions-x.ts +45 -0
  330. package/src/provider-contract-definitions.ts +948 -0
  331. package/src/provider-contract-input-gmail.ts +176 -0
  332. package/src/provider-contract-input-linkedin.ts +77 -0
  333. package/src/provider-contract-input-x.ts +304 -0
  334. package/src/provider-contract-planning.ts +23 -0
  335. package/src/provider-contract-semantic-identity.ts +54 -0
  336. package/src/provider-contracts.ts +251 -0
  337. package/src/provider-http.ts +723 -0
  338. package/src/provider-plugin-auth.ts +36 -0
  339. package/src/provider-plugin-builtins.ts +229 -0
  340. package/src/provider-plugin-cleanup-barrier.ts +229 -0
  341. package/src/provider-plugin-cleanup-execution.ts +79 -0
  342. package/src/provider-plugin-cleanup-resource.ts +470 -0
  343. package/src/provider-plugin-contract-identity.ts +707 -0
  344. package/src/provider-plugin-host.ts +1972 -0
  345. package/src/provider-plugin-identifiers.ts +60 -0
  346. package/src/provider-plugin-invocation-lease.ts +889 -0
  347. package/src/provider-plugin-lifecycle-kernel.ts +566 -0
  348. package/src/provider-plugin-lifecycle.ts +1455 -0
  349. package/src/provider-plugin-module-analysis.ts +336 -0
  350. package/src/provider-plugin-package.ts +2530 -0
  351. package/src/provider-plugin-portable-authority.ts +106 -0
  352. package/src/provider-plugin-portable-catalog.ts +94 -0
  353. package/src/provider-plugin-portable-identity.ts +315 -0
  354. package/src/provider-plugin-portable-runtime.ts +2057 -0
  355. package/src/provider-plugin-protocol.ts +1625 -0
  356. package/src/provider-plugin-registry.ts +3342 -0
  357. package/src/provider-plugin-store.ts +2155 -0
  358. package/src/provider-plugin-test-fixture.ts +5 -0
  359. package/src/provider-plugin.ts +4907 -0
  360. package/src/provider-plugins.generated.ts +43 -0
  361. package/src/provider-plugins.ts +8 -0
  362. package/src/provider-subject.ts +41 -0
  363. package/src/provider.ts +315 -0
  364. package/src/providers/beeper-local-runtime.ts +5426 -0
  365. package/src/providers/beeper-local.ts +2739 -0
  366. package/src/providers/beeper-messaging.ts +462 -0
  367. package/src/providers/beeper-omni.ts +1159 -0
  368. package/src/providers/bluesky-web-runtime.ts +2895 -0
  369. package/src/providers/bluesky-web.ts +1614 -0
  370. package/src/providers/clasificados-web-runtime.ts +255 -0
  371. package/src/providers/clasificados-web.ts +503 -0
  372. package/src/providers/contact-projection.ts +301 -0
  373. package/src/providers/github-read-model.ts +94 -0
  374. package/src/providers/github-read-platform.ts +165 -0
  375. package/src/providers/github-read-program.ts +97 -0
  376. package/src/providers/github-web-runtime.ts +320 -0
  377. package/src/providers/github-web.ts +403 -0
  378. package/src/providers/gmail-api.ts +2834 -0
  379. package/src/providers/gmail-omni.ts +830 -0
  380. package/src/providers/gmail.ts +1130 -0
  381. package/src/providers/hacker-news-web-runtime.ts +220 -0
  382. package/src/providers/hacker-news-web.ts +809 -0
  383. package/src/providers/imessage-direct-install.ts +354 -0
  384. package/src/providers/imessage-direct-messaging.ts +532 -0
  385. package/src/providers/imessage-direct-omni.ts +400 -0
  386. package/src/providers/imessage-direct-runtime.ts +1498 -0
  387. package/src/providers/imessage-direct.ts +351 -0
  388. package/src/providers/instagram-video-foundations.ts +913 -0
  389. package/src/providers/instagram-web-profile-browser.ts +600 -0
  390. package/src/providers/iso-bmff.ts +338 -0
  391. package/src/providers/linkedin-company-platform.ts +42 -0
  392. package/src/providers/linkedin-company-program.ts +88 -0
  393. package/src/providers/linkedin-contact-failure.ts +84 -0
  394. package/src/providers/linkedin-contact-platform.ts +45 -0
  395. package/src/providers/linkedin-contact-program.ts +91 -0
  396. package/src/providers/linkedin-profile-activity-failure.ts +76 -0
  397. package/src/providers/linkedin-profile-activity-platform.ts +41 -0
  398. package/src/providers/linkedin-profile-activity-program.ts +67 -0
  399. package/src/providers/linkedin-read-failure.ts +52 -0
  400. package/src/providers/linkedin-self-platform.ts +53 -0
  401. package/src/providers/linkedin-self-program.ts +122 -0
  402. package/src/providers/linkedin-web-article-browser.ts +794 -0
  403. package/src/providers/linkedin-web-bootstrap.ts +146 -0
  404. package/src/providers/linkedin-web-contact.ts +830 -0
  405. package/src/providers/linkedin-web-feed-browser.ts +832 -0
  406. package/src/providers/linkedin-web-feed.ts +864 -0
  407. package/src/providers/linkedin-web-post-browser.ts +653 -0
  408. package/src/providers/linkedin-web-profile-browser.ts +683 -0
  409. package/src/providers/linkedin-web-runtime.ts +2388 -0
  410. package/src/providers/linkedin-web.ts +3305 -0
  411. package/src/providers/linkedin.ts +1885 -0
  412. package/src/providers/meta-bootstrap.ts +1043 -0
  413. package/src/providers/meta-facebook-group.ts +690 -0
  414. package/src/providers/meta-marketplace-relay.ts +747 -0
  415. package/src/providers/meta-omni.ts +373 -0
  416. package/src/providers/meta-relay-bundle.ts +1049 -0
  417. package/src/providers/meta-web-descriptors.ts +1740 -0
  418. package/src/providers/meta-web-runtime.ts +3388 -0
  419. package/src/providers/meta-web.ts +2931 -0
  420. package/src/providers/read-failure.ts +139 -0
  421. package/src/providers/reddit-omni.ts +462 -0
  422. package/src/providers/reddit-web-runtime.ts +1929 -0
  423. package/src/providers/reddit-web.ts +1611 -0
  424. package/src/providers/rental-listings.ts +460 -0
  425. package/src/providers/substack-omni.ts +406 -0
  426. package/src/providers/substack-video-mp4.ts +33 -0
  427. package/src/providers/substack-web-runtime.ts +2812 -0
  428. package/src/providers/substack-web.ts +1502 -0
  429. package/src/providers/tiktok-video-mp4.ts +33 -0
  430. package/src/providers/tiktok-web-runtime.ts +770 -0
  431. package/src/providers/tiktok-web.ts +2786 -0
  432. package/src/providers/twitch-web-runtime.ts +226 -0
  433. package/src/providers/twitch-web.ts +274 -0
  434. package/src/providers/whatsapp-account-identity.ts +55 -0
  435. package/src/providers/whatsapp-contact-projection-helper.ts +874 -0
  436. package/src/providers/whatsapp-contact-projection-protocol.ts +403 -0
  437. package/src/providers/whatsapp-interaction-projection-helper.ts +1452 -0
  438. package/src/providers/whatsapp-interaction-projection-protocol.ts +462 -0
  439. package/src/providers/whatsapp-message-export-projection-protocol.ts +532 -0
  440. package/src/providers/whatsapp-messaging.ts +132 -0
  441. package/src/providers/whatsapp-omni.ts +586 -0
  442. package/src/providers/whatsapp-web-runtime.ts +3108 -0
  443. package/src/providers/whatsapp-web.ts +1037 -0
  444. package/src/providers/x-image-provenance.ts +523 -0
  445. package/src/providers/x-made-with-ai.ts +106 -0
  446. package/src/providers/x-transaction-id.ts +413 -0
  447. package/src/providers/x-web-runtime.ts +3485 -0
  448. package/src/providers/x-web.ts +2332 -0
  449. package/src/providers/x.ts +1889 -0
  450. package/src/providers/youtube-web-runtime.ts +1400 -0
  451. package/src/providers/youtube-web.ts +1221 -0
  452. package/src/read-admission-runtime.ts +91 -0
  453. package/src/read-client.ts +521 -0
  454. package/src/read-effect-platform.ts +7 -0
  455. package/src/read-effect-runtime.ts +14 -0
  456. package/src/read-effect.ts +52 -0
  457. package/src/read-projection-admission.ts +866 -0
  458. package/src/read-projections.ts +3151 -0
  459. package/src/recovery.ts +1492 -0
  460. package/src/reviewed-template.ts +146 -0
  461. package/src/run-journal.ts +1272 -0
  462. package/src/runtime.ts +5116 -0
  463. package/src/scripts/generate-provider-plugin-catalog.ts +121 -0
  464. package/src/scripts/install-whatsapp-protocol.sh +232 -0
  465. package/src/scripts/recover-install-stages.ts +1491 -0
  466. package/src/scripts/resolve-state-home.ts +30 -0
  467. package/src/scripts/scaffold-web-provider.ts +973 -0
  468. package/src/scripts/sync-bundled-adapters.ts +695 -0
  469. package/src/session-secrets.ts +1183 -0
  470. package/src/state-helper.bunfig.toml +2 -0
  471. package/src/state-helper.ts +2914 -0
  472. package/src/storage.ts +3842 -0
  473. package/src/transport-policy.ts +2 -0
  474. package/src/usage.ts +182 -0
  475. package/src/version.ts +2 -0
  476. package/src/web-session-authentication-policy.ts +136 -0
  477. package/src/web-session-cleanup-admission.ts +2344 -0
  478. package/src/web-session-cleanup-join.ts +99 -0
  479. package/src/web-session-client.ts +996 -0
  480. package/src/web-session-contract-definitions.ts +820 -0
  481. package/src/web-session-contract-planning.ts +42 -0
  482. package/src/web-session-contracts.ts +228 -0
  483. package/src/web-session-cookies.ts +500 -0
  484. package/src/web-session-execution.ts +385 -0
  485. package/src/web-session-http.ts +373 -0
  486. package/src/web-session-read-errors.ts +19 -0
  487. package/src/web-session-read-runtime.ts +124 -0
  488. package/src/web-session-recovery.ts +818 -0
  489. package/src/web-session-sites.ts +58 -0
  490. package/src/web-session-template.ts +994 -0
  491. package/src/web-session.ts +93 -0
  492. package/src/whatsapp-client-binding.ts +21 -0
  493. package/src/whatsapp-client-types.ts +75 -0
  494. package/src/whatsapp-client.ts +395 -0
  495. package/src/whatsapp-export-coordinate.ts +17 -0
  496. package/src/whatsapp-message-bundle-v2.ts +195 -0
  497. package/src/whatsapp-message-like-me-cli.ts +195 -0
  498. package/src/whatsapp-message-like-me-export.ts +1072 -0
  499. package/src/whatsapp-message-like-me-source.ts +1012 -0
  500. package/tsconfig.json +67 -0
@@ -0,0 +1,980 @@
1
+ import { MAX_HAR_ENTRIES } from "./har";
2
+ import {
3
+ analyzeInternalHarValue,
4
+ INTERNAL_HAR_REVIEW_BOUNDS,
5
+ isReviewedLinkedInArticleRoute,
6
+ isReviewedInternalDynamicMapField,
7
+ isReviewedTwitchGraphQlRoute,
8
+ parseBoundedInternalHarUrl,
9
+ reviewedInternalFieldNameForUrl,
10
+ reviewedXGraphQlVariableFieldName,
11
+ type BoundedInternalHarUrl,
12
+ type InternalHarCandidate,
13
+ } from "./har-internal";
14
+
15
+ type JsonRecord = Record<string, unknown>;
16
+
17
+ const MAX_REVIEW_LIMIT = 100;
18
+ const MAX_FIXTURES = 50;
19
+ const MAX_FIXTURE_VALUE_BYTES = 16 * 1024;
20
+ const MAX_FIXTURE_BYTES = 64 * 1024;
21
+ const MAX_MATCHES_PER_FIXTURE = 100;
22
+ const MAX_FIELD_NAMES = 50;
23
+ const MAX_FIELD_NAME_CHARACTERS = 128;
24
+ const MAX_WALK_NODES = 10_000;
25
+ const MAX_WALK_DEPTH = INTERNAL_HAR_REVIEW_BOUNDS.maxTraversalDepth;
26
+ const MAX_ARRAY_ITEMS = INTERNAL_HAR_REVIEW_BOUNDS.maxArrayItems;
27
+ const MAX_OBJECT_ENTRIES = INTERNAL_HAR_REVIEW_BOUNDS.maxObjectEntries;
28
+ const MAX_OBJECT_KEY_INSPECTIONS = INTERNAL_HAR_REVIEW_BOUNDS.maxObjectEntries;
29
+ const MAX_FORM_PARAMETERS = INTERNAL_HAR_REVIEW_BOUNDS.maxFormParameters;
30
+ const MAX_FORM_NAME_BYTES = INTERNAL_HAR_REVIEW_BOUNDS.maxFieldNameCharacters;
31
+ const MAX_JSON_TEXT_BYTES = INTERNAL_HAR_REVIEW_BOUNDS.maxJsonTextBytes;
32
+ const MAX_PATH_SEGMENTS = INTERNAL_HAR_REVIEW_BOUNDS.maxPathSegments;
33
+
34
+ const isRecord = (value: unknown): value is JsonRecord =>
35
+ typeof value === "object" && value !== null && !Array.isArray(value);
36
+
37
+ const sensitiveCredentialTokens: ReadonlySet<string> = new Set([
38
+ "auth",
39
+ "authentication",
40
+ "authorization",
41
+ "assertion",
42
+ "basic",
43
+ "bearer",
44
+ "cookie",
45
+ "cookies",
46
+ "credential",
47
+ "credentials",
48
+ "creds",
49
+ "csrf",
50
+ "header",
51
+ "headers",
52
+ "hmac",
53
+ "jwt",
54
+ "keytab",
55
+ "login",
56
+ "macaroon",
57
+ "nonce",
58
+ "oauth",
59
+ "otp",
60
+ "passphrase",
61
+ "passcode",
62
+ "passwd",
63
+ "password",
64
+ "pin",
65
+ "private",
66
+ "pwd",
67
+ "saml",
68
+ "secret",
69
+ "secrets",
70
+ "session",
71
+ "sid",
72
+ "signature",
73
+ "signin",
74
+ "signon",
75
+ "sso",
76
+ "token",
77
+ "tokens",
78
+ "xsrf",
79
+ ]);
80
+
81
+ const compactCredentialAlias =
82
+ /(?:authorization|authentication|oauth|bearer|jwt|csrf|xsrf|apikey|accesskey|accesstoken|refreshtoken|idtoken|clientsecret|privatekey|password|passwd|passphrase|credential|cookie|session|jsessionid|phpsessid|signature|hmac)/u;
83
+
84
+ function credentialKeyTokens(value: string): readonly string[] {
85
+ return value
86
+ .replace(/([a-z0-9])([A-Z])/gu, "$1 $2")
87
+ .toLowerCase()
88
+ .split(/[^a-z0-9]+/gu)
89
+ .filter((part) => part.length > 0);
90
+ }
91
+
92
+ function isSensitiveCredentialKey(value: string): boolean {
93
+ // An oversized key is not safe to classify: avoid allocating normalized
94
+ // copies of attacker-controlled text and treat its whole subtree as opaque.
95
+ if (value.length > INTERNAL_HAR_REVIEW_BOUNDS.maxFieldNameCharacters) return true;
96
+ const tokens = credentialKeyTokens(value);
97
+ if (tokens.some((token) => sensitiveCredentialTokens.has(token))) return true;
98
+ for (let index = 1; index < tokens.length; index += 1) {
99
+ if (
100
+ tokens[index] === "key"
101
+ && ["access", "api", "client", "private", "secret"].includes(tokens[index - 1] ?? "")
102
+ ) return true;
103
+ }
104
+ const compact = tokens.join("");
105
+ if (compact === "") return false;
106
+ if (compactCredentialAlias.test(compact)) return true;
107
+ if (
108
+ compact === "auth"
109
+ || compact.endsWith("auth")
110
+ || (compact.startsWith("auth") && !compact.startsWith("author"))
111
+ || compact.endsWith("token")
112
+ || compact.endsWith("tokens")
113
+ || compact.endsWith("secret")
114
+ || compact.endsWith("secrets")
115
+ || compact.endsWith("sid")
116
+ || compact.endsWith("header")
117
+ || compact.endsWith("headers")
118
+ ) return true;
119
+ return false;
120
+ }
121
+
122
+ function isSensitiveCredentialKeyForExchange(
123
+ twitchGraphQlPostExchange: boolean,
124
+ value: string,
125
+ ): boolean {
126
+ if (
127
+ twitchGraphQlPostExchange
128
+ && (value === "channelLogin" || value === "login")
129
+ ) return false;
130
+ return isSensitiveCredentialKey(value);
131
+ }
132
+
133
+ function isExactMimeType(value: string, expected: string): boolean {
134
+ return value.split(";", 1)[0]?.trim() === expected;
135
+ }
136
+
137
+ export type DerivationReviewFixtures = Readonly<Record<string, string>>;
138
+ export type DerivationReviewFieldNames = readonly string[];
139
+
140
+ export type DerivationReviewSelection =
141
+ | { readonly kind: "list"; readonly offset: number; readonly limit: number }
142
+ | {
143
+ readonly kind: "entry";
144
+ readonly entryIndex: number;
145
+ readonly fixtures: DerivationReviewFixtures;
146
+ readonly fieldNames?: never;
147
+ }
148
+ | {
149
+ readonly kind: "entry";
150
+ readonly entryIndex: number;
151
+ readonly fixtures?: never;
152
+ readonly fieldNames: DerivationReviewFieldNames;
153
+ };
154
+
155
+ export type DerivationReviewListEntry = {
156
+ readonly entryIndex: number;
157
+ readonly method: string;
158
+ readonly origin: string;
159
+ readonly path: string;
160
+ readonly operationType: InternalHarCandidate["operationType"];
161
+ readonly statuses: readonly number[];
162
+ readonly queryNames: readonly string[];
163
+ readonly revisions: readonly string[];
164
+ readonly requestFieldCount: number;
165
+ readonly responseFieldCount: number;
166
+ };
167
+
168
+ export type DerivationReviewMatch = {
169
+ readonly label: string;
170
+ readonly locations: readonly string[];
171
+ readonly truncated: boolean;
172
+ };
173
+
174
+ export type DerivationReviewFieldNameMatch = {
175
+ readonly candidateIndex: number;
176
+ readonly locations: readonly string[];
177
+ readonly truncated: boolean;
178
+ readonly valueTypes: readonly DerivationReviewFieldValueType[];
179
+ };
180
+
181
+ export type DerivationReviewFieldValueType =
182
+ | "null"
183
+ | "boolean"
184
+ | "number"
185
+ | "string"
186
+ | "array"
187
+ | "object";
188
+
189
+ export type DerivationReviewResult =
190
+ | {
191
+ readonly schemaVersion: 1;
192
+ readonly kind: "list";
193
+ readonly targetOrigin: string;
194
+ readonly totalHarEntries: number;
195
+ readonly reviewableEntries: number;
196
+ readonly offset: number;
197
+ readonly limit: number;
198
+ readonly nextOffset: number | null;
199
+ readonly entries: readonly DerivationReviewListEntry[];
200
+ readonly warnings: readonly string[];
201
+ }
202
+ | {
203
+ readonly schemaVersion: 1;
204
+ readonly kind: "entry";
205
+ readonly targetOrigin: string;
206
+ readonly totalHarEntries: number;
207
+ readonly entryIndex: number;
208
+ readonly structure: InternalHarCandidate;
209
+ readonly fixtureMatches: readonly DerivationReviewMatch[];
210
+ readonly fieldNameMatches: readonly DerivationReviewFieldNameMatch[];
211
+ readonly warnings: readonly string[];
212
+ };
213
+
214
+ function parseReviewFixtures(
215
+ value: unknown,
216
+ minimumEntries: 0 | 1,
217
+ ): DerivationReviewFixtures {
218
+ if (!isRecord(value)) throw new Error("review fixtures must be a JSON object");
219
+ const entries: [string, unknown][] = [];
220
+ for (const label in value) {
221
+ if (!Object.prototype.hasOwnProperty.call(value, label)) continue;
222
+ if (entries.length >= MAX_FIXTURES) {
223
+ throw new Error(`review fixtures must contain ${minimumEntries}-${MAX_FIXTURES} labeled string values`);
224
+ }
225
+ const descriptor = Object.getOwnPropertyDescriptor(value, label);
226
+ if (descriptor === undefined || !("value" in descriptor)) {
227
+ throw new Error("review fixture properties must be plain values");
228
+ }
229
+ entries.push([label, descriptor.value]);
230
+ }
231
+ if (entries.length < minimumEntries) {
232
+ throw new Error(`review fixtures must contain ${minimumEntries}-${MAX_FIXTURES} labeled string values`);
233
+ }
234
+ let totalBytes = 0;
235
+ const fixtures: Record<string, string> = {};
236
+ for (const [label, fixture] of entries) {
237
+ if (!/^[a-z][a-z0-9_-]{0,63}$/u.test(label)) {
238
+ throw new Error("review fixture labels must be lowercase identifiers containing only letters, digits, underscore, or hyphen");
239
+ }
240
+ if (typeof fixture !== "string") throw new Error(`review fixture ${label} must be a string`);
241
+ const bytes = Buffer.byteLength(fixture, "utf8");
242
+ if (bytes < 1 || bytes > MAX_FIXTURE_VALUE_BYTES) {
243
+ throw new Error(`review fixture ${label} must contain 1-${MAX_FIXTURE_VALUE_BYTES} UTF-8 bytes`);
244
+ }
245
+ totalBytes += bytes + Buffer.byteLength(label, "utf8");
246
+ if (totalBytes > MAX_FIXTURE_BYTES) throw new Error(`review fixtures exceed ${MAX_FIXTURE_BYTES} UTF-8 bytes`);
247
+ fixtures[label] = fixture;
248
+ }
249
+ return Object.freeze(fixtures);
250
+ }
251
+
252
+ export function parseDerivationReviewFixtures(value: unknown): DerivationReviewFixtures {
253
+ return parseReviewFixtures(value, 1);
254
+ }
255
+
256
+ export function parseDerivationReviewFieldNames(
257
+ value: unknown,
258
+ ): DerivationReviewFieldNames {
259
+ if (!Array.isArray(value) || value.length < 1 || value.length > MAX_FIELD_NAMES) {
260
+ throw new Error(`review field names must be a JSON array containing 1-${MAX_FIELD_NAMES} unique strings`);
261
+ }
262
+ const ownKeys = Reflect.ownKeys(Object.getOwnPropertyDescriptors(value));
263
+ if (
264
+ ownKeys.some((key) =>
265
+ typeof key !== "string"
266
+ || (key !== "length" && !/^(?:0|[1-9][0-9]*)$/u.test(key)))
267
+ || ownKeys.length !== value.length + 1
268
+ ) {
269
+ throw new Error("review field names must be a plain JSON array");
270
+ }
271
+ const names: string[] = [];
272
+ const unique = new Set<string>();
273
+ for (let index = 0; index < value.length; index += 1) {
274
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
275
+ if (descriptor === undefined || !("value" in descriptor)) {
276
+ throw new Error("review field name entries must be plain values");
277
+ }
278
+ const candidate = descriptor.value;
279
+ if (
280
+ typeof candidate !== "string"
281
+ || candidate.length < 1
282
+ || candidate.length > MAX_FIELD_NAME_CHARACTERS
283
+ || !/^[_A-Za-z$][_0-9A-Za-z$.-]{0,127}$/u.test(candidate)
284
+ ) {
285
+ throw new Error("review field names must be bounded schema-key strings");
286
+ }
287
+ if (isSensitiveCredentialKey(candidate)) {
288
+ throw new Error("review field names must not identify credential-like schema keys");
289
+ }
290
+ if (unique.has(candidate)) {
291
+ throw new Error("review field names must be unique exact strings");
292
+ }
293
+ unique.add(candidate);
294
+ names.push(candidate);
295
+ }
296
+ return Object.freeze(names);
297
+ }
298
+
299
+ function parseHarEntries(value: unknown): readonly unknown[] {
300
+ if (!isRecord(value) || !isRecord(value.log) || !Array.isArray(value.log.entries)) {
301
+ throw new Error("private review HAR must contain log.entries[]");
302
+ }
303
+ if (value.log.entries.length > MAX_HAR_ENTRIES) {
304
+ throw new Error(`private review HAR contains more than ${MAX_HAR_ENTRIES} entries`);
305
+ }
306
+ return value.log.entries;
307
+ }
308
+
309
+ function exactTarget(value: string): URL {
310
+ let target: URL;
311
+ try {
312
+ target = new URL(value);
313
+ } catch {
314
+ throw new Error("private review target origin is invalid");
315
+ }
316
+ if (target.protocol !== "https:" || target.origin !== value || target.username !== "" || target.password !== "") {
317
+ throw new Error("private review target must be an exact HTTPS origin");
318
+ }
319
+ return target;
320
+ }
321
+
322
+ function reviewableRequest(
323
+ entry: unknown,
324
+ targetOrigin: string,
325
+ ): ({ readonly entry: JsonRecord } & BoundedInternalHarUrl) | null {
326
+ if (!isRecord(entry) || !isRecord(entry.request)) return null;
327
+ if (typeof entry.request.url !== "string" || typeof entry.request.method !== "string") return null;
328
+ const boundedUrl = parseBoundedInternalHarUrl(entry.request.url);
329
+ if (boundedUrl === null || entry.request.method.length > 16) return null;
330
+ const { url } = boundedUrl;
331
+ const method = entry.request.method.toUpperCase();
332
+ if (
333
+ url.origin !== targetOrigin
334
+ || url.username !== ""
335
+ || url.password !== ""
336
+ || !/^(?:GET|HEAD|POST|PUT|PATCH|DELETE)$/u.test(method)
337
+ || /\.(?:css|js|png|jpe?g|gif|svg|woff2?|ico|map)$/iu.test(url.pathname)
338
+ ) return null;
339
+ return { entry, ...boundedUrl };
340
+ }
341
+
342
+ function exactCandidate(entry: unknown, targetOrigin: string): InternalHarCandidate | null {
343
+ const evidence = analyzeInternalHarValue(
344
+ { log: { entries: [entry] } },
345
+ "private-review",
346
+ targetOrigin,
347
+ new Date("2000-01-01T00:00:00.000Z"),
348
+ );
349
+ return evidence.candidates.length === 1 ? evidence.candidates[0] ?? null : null;
350
+ }
351
+
352
+ type MatchState = {
353
+ readonly values: readonly { readonly label: string; readonly value: string }[];
354
+ readonly locations: Map<string, Set<string>>;
355
+ readonly truncated: Set<string>;
356
+ readonly fieldNames: readonly string[];
357
+ readonly fieldNameLocations: readonly Set<string>[];
358
+ readonly fieldNameTruncated: Set<number>;
359
+ readonly fieldNameValueTypes: readonly Set<DerivationReviewFieldValueType>[];
360
+ readonly twitchGraphQlPostExchange: boolean;
361
+ readonly xGraphQlRequest: boolean;
362
+ readonly url: URL;
363
+ readonly maximumDepth: number;
364
+ visited: number;
365
+ };
366
+
367
+ type JsonObjectKeyReview = "structural" | "x-graphql-variable";
368
+
369
+ function addMatch(state: MatchState, label: string, location: string): void {
370
+ const locations = state.locations.get(label);
371
+ if (locations === undefined) return;
372
+ if (locations.has(location)) return;
373
+ if (locations.size >= MAX_MATCHES_PER_FIXTURE) {
374
+ state.truncated.add(label);
375
+ return;
376
+ }
377
+ locations.add(location);
378
+ }
379
+
380
+ function truncateEveryFixture(state: MatchState): void {
381
+ for (const fixture of state.values) state.truncated.add(fixture.label);
382
+ for (let candidateIndex = 0; candidateIndex < state.fieldNames.length; candidateIndex += 1) {
383
+ state.fieldNameTruncated.add(candidateIndex);
384
+ }
385
+ }
386
+
387
+ function matchObjectKey(
388
+ state: MatchState,
389
+ key: string,
390
+ location: string,
391
+ redactObjectKeys: boolean,
392
+ value: unknown,
393
+ ): void {
394
+ if (redactObjectKeys) return;
395
+ const valueType: DerivationReviewFieldValueType | null = value === null
396
+ ? "null"
397
+ : Array.isArray(value)
398
+ ? "array"
399
+ : typeof value === "boolean"
400
+ ? "boolean"
401
+ : typeof value === "number"
402
+ ? "number"
403
+ : typeof value === "string"
404
+ ? "string"
405
+ : isRecord(value)
406
+ ? "object"
407
+ : null;
408
+ for (let candidateIndex = 0; candidateIndex < state.fieldNames.length; candidateIndex += 1) {
409
+ if (key !== state.fieldNames[candidateIndex]) continue;
410
+ if (valueType === null) {
411
+ state.fieldNameTruncated.add(candidateIndex);
412
+ continue;
413
+ }
414
+ state.fieldNameValueTypes[candidateIndex]?.add(valueType);
415
+ const locations = state.fieldNameLocations[candidateIndex];
416
+ if (locations === undefined || locations.has(location)) continue;
417
+ if (locations.size >= MAX_MATCHES_PER_FIXTURE) {
418
+ state.fieldNameTruncated.add(candidateIndex);
419
+ continue;
420
+ }
421
+ locations.add(location);
422
+ }
423
+ }
424
+
425
+ function hasSearchableContent(state: MatchState, value: unknown): boolean {
426
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return true;
427
+ if (Array.isArray(value)) return value.length > 0;
428
+ if (!isRecord(value)) return false;
429
+ let inspected = 0;
430
+ for (const key in value) {
431
+ if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
432
+ if (inspected >= MAX_OBJECT_KEY_INSPECTIONS) return true;
433
+ inspected += 1;
434
+ if (key.length > INTERNAL_HAR_REVIEW_BOUNDS.maxFieldNameCharacters) return true;
435
+ if (!isSensitiveCredentialKeyForExchange(state.twitchGraphQlPostExchange, key)) return true;
436
+ }
437
+ return false;
438
+ }
439
+
440
+ function matchPrimitive(state: MatchState, value: unknown, location: string): void {
441
+ if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") return;
442
+ const rendered = String(value);
443
+ for (const fixture of state.values) {
444
+ if (rendered === fixture.value) addMatch(state, fixture.label, location);
445
+ }
446
+ }
447
+
448
+ function walkJson(
449
+ state: MatchState,
450
+ value: unknown,
451
+ location: string,
452
+ depth = 0,
453
+ redactObjectKeys = false,
454
+ keyReview: JsonObjectKeyReview = "structural",
455
+ ): void {
456
+ if (depth >= state.maximumDepth) {
457
+ if (hasSearchableContent(state, value)) truncateEveryFixture(state);
458
+ return;
459
+ }
460
+ if (state.visited >= MAX_WALK_NODES) {
461
+ if (hasSearchableContent(state, value)) truncateEveryFixture(state);
462
+ return;
463
+ }
464
+ state.visited += 1;
465
+ matchPrimitive(state, value, location);
466
+ if (Array.isArray(value)) {
467
+ const items = value.slice(0, MAX_ARRAY_ITEMS);
468
+ if (value.length > items.length) truncateEveryFixture(state);
469
+ for (let index = 0; index < items.length; index += 1) {
470
+ walkJson(state, items[index], `${location}[]`, depth + 1, false, "structural");
471
+ if (
472
+ state.visited >= MAX_WALK_NODES
473
+ && items.slice(index + 1).some((candidate) => hasSearchableContent(state, candidate))
474
+ ) {
475
+ truncateEveryFixture(state);
476
+ return;
477
+ }
478
+ }
479
+ return;
480
+ }
481
+ if (!isRecord(value)) return;
482
+ const searchedEntries: [string, unknown][] = [];
483
+ let rawKeyOverflow = false;
484
+ let safeEntryOverflow = false;
485
+ let inspectedKeys = 0;
486
+ for (const key in value) {
487
+ if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
488
+ if (inspectedKeys >= MAX_OBJECT_KEY_INSPECTIONS) {
489
+ rawKeyOverflow = true;
490
+ break;
491
+ }
492
+ inspectedKeys += 1;
493
+ if (key.length > INTERNAL_HAR_REVIEW_BOUNDS.maxFieldNameCharacters) {
494
+ rawKeyOverflow = true;
495
+ continue;
496
+ }
497
+ if (isSensitiveCredentialKeyForExchange(state.twitchGraphQlPostExchange, key)) continue;
498
+ if (searchedEntries.length >= MAX_OBJECT_ENTRIES) {
499
+ safeEntryOverflow = true;
500
+ break;
501
+ }
502
+ searchedEntries.push([key, value[key]]);
503
+ }
504
+ if (rawKeyOverflow || safeEntryOverflow) truncateEveryFixture(state);
505
+ for (let index = 0; index < searchedEntries.length; index += 1) {
506
+ const entry = searchedEntries[index];
507
+ if (entry === undefined) continue;
508
+ const [key, child] = entry;
509
+ // A redacted key is not a safe traversal boundary: descending would still
510
+ // let a fixture probe reveal whether a credential subtree contains a value
511
+ // and return its sanitized location. Treat every sensitive-key value as an
512
+ // opaque leaf, including nested objects and arrays.
513
+ const renderedKey = redactObjectKeys
514
+ ? ":dynamic"
515
+ : keyReview === "x-graphql-variable"
516
+ ? reviewedXGraphQlVariableFieldName(key)
517
+ : reviewedInternalFieldNameForUrl(state.url, key);
518
+ matchObjectKey(
519
+ state,
520
+ key,
521
+ `${location}.:candidate-field`,
522
+ redactObjectKeys,
523
+ child,
524
+ );
525
+ const childLocation = `${location}.${renderedKey}`;
526
+ const childKeyReview = state.xGraphQlRequest
527
+ && location === "request.body"
528
+ && key === "variables"
529
+ ? "x-graphql-variable"
530
+ : "structural";
531
+ walkJson(
532
+ state,
533
+ child,
534
+ childLocation,
535
+ depth + 1,
536
+ !redactObjectKeys
537
+ && isRecord(child)
538
+ && (isReviewedInternalDynamicMapField(key) || renderedKey === ":dynamic"),
539
+ childKeyReview,
540
+ );
541
+ if (
542
+ state.visited >= MAX_WALK_NODES
543
+ && searchedEntries
544
+ .slice(index + 1)
545
+ .some(([, candidate]) => hasSearchableContent(state, candidate))
546
+ ) {
547
+ truncateEveryFixture(state);
548
+ return;
549
+ }
550
+ }
551
+ }
552
+
553
+ type ParsedJsonText =
554
+ | { readonly kind: "parsed"; readonly value: unknown }
555
+ | { readonly kind: "invalid" }
556
+ | { readonly kind: "oversized" };
557
+
558
+ function parsedJsonText(value: unknown): ParsedJsonText {
559
+ if (typeof value !== "string") return { kind: "invalid" };
560
+ if (
561
+ value.length > INTERNAL_HAR_REVIEW_BOUNDS.maxRawStringCharacters
562
+ || Buffer.byteLength(value, "utf8") > MAX_JSON_TEXT_BYTES
563
+ ) return { kind: "oversized" };
564
+ try {
565
+ return { kind: "parsed", value: JSON.parse(value) as unknown };
566
+ } catch {
567
+ return { kind: "invalid" };
568
+ }
569
+ }
570
+
571
+ function decodedFormComponent(value: string): string | null {
572
+ try {
573
+ return decodeURIComponent(value.replaceAll("+", " "));
574
+ } catch {
575
+ return null;
576
+ }
577
+ }
578
+
579
+ function matchFormUrlEncodedText(state: MatchState, value: string): void {
580
+ const occurrences = new Map<string, number>();
581
+ let cursor = 0;
582
+ let inspected = 0;
583
+ let searched = 0;
584
+ while (cursor <= value.length) {
585
+ if (inspected >= MAX_FORM_PARAMETERS) {
586
+ if (cursor < value.length) truncateEveryFixture(state);
587
+ return;
588
+ }
589
+ const separator = value.indexOf("&", cursor);
590
+ const end = separator === -1 ? value.length : separator;
591
+ const pair = value.slice(cursor, end);
592
+ cursor = separator === -1 ? value.length + 1 : end + 1;
593
+ if (pair === "") continue;
594
+ inspected += 1;
595
+ const equals = pair.indexOf("=");
596
+ const rawName = equals === -1 ? pair : pair.slice(0, equals);
597
+ if (Buffer.byteLength(rawName, "utf8") > MAX_FORM_NAME_BYTES) {
598
+ truncateEveryFixture(state);
599
+ continue;
600
+ }
601
+ const name = decodedFormComponent(rawName);
602
+ if (name === null) {
603
+ truncateEveryFixture(state);
604
+ continue;
605
+ }
606
+ if (isSensitiveCredentialKey(name)) continue;
607
+ if (searched >= MAX_FORM_PARAMETERS) {
608
+ truncateEveryFixture(state);
609
+ return;
610
+ }
611
+ searched += 1;
612
+ const rawValue = equals === -1 ? "" : pair.slice(equals + 1);
613
+ const decodedValue = decodedFormComponent(rawValue);
614
+ if (decodedValue === null) {
615
+ truncateEveryFixture(state);
616
+ continue;
617
+ }
618
+ const safeName = reviewedInternalFieldNameForUrl(state.url, name);
619
+ const occurrence = occurrences.get(safeName) ?? 0;
620
+ occurrences.set(safeName, occurrence + 1);
621
+ matchPrimitive(state, decodedValue, `request.form.${safeName}[${occurrence}]`);
622
+ }
623
+ }
624
+
625
+ const reviewedRequestFixtureHeaders = new Set(["x-restli-method"] as const);
626
+ const reviewedResponseFixtureHeaders = new Set(["x-restli-id"] as const);
627
+
628
+ /**
629
+ * Match only reviewed non-secret contract facts. Header values never appear in
630
+ * review output; a match returns the fixed header-name location alone.
631
+ */
632
+ function matchReviewedHeaderFixtures(
633
+ state: MatchState,
634
+ value: unknown,
635
+ location: "request.header" | "response.header",
636
+ reviewedNames: ReadonlySet<string>,
637
+ ): void {
638
+ if (!Array.isArray(value)) return;
639
+ if (value.length > INTERNAL_HAR_REVIEW_BOUNDS.maxHeaderItems) truncateEveryFixture(state);
640
+ const occurrences = new Map<string, number>();
641
+ for (const candidate of value.slice(0, INTERNAL_HAR_REVIEW_BOUNDS.maxHeaderItems)) {
642
+ if (!isRecord(candidate) || typeof candidate.name !== "string" || typeof candidate.value !== "string") continue;
643
+ const name = candidate.name.toLowerCase();
644
+ if (!reviewedNames.has(name)) continue;
645
+ if (
646
+ candidate.value.length > INTERNAL_HAR_REVIEW_BOUNDS.maxRawStringCharacters
647
+ || Buffer.byteLength(candidate.value, "utf8") > MAX_JSON_TEXT_BYTES
648
+ ) {
649
+ truncateEveryFixture(state);
650
+ continue;
651
+ }
652
+ const occurrence = occurrences.get(name) ?? 0;
653
+ occurrences.set(name, occurrence + 1);
654
+ matchPrimitive(state, candidate.value, `${location}.${name}[${occurrence}]`);
655
+ }
656
+ }
657
+
658
+ function matchEntryProbes(
659
+ entry: JsonRecord,
660
+ boundedUrl: BoundedInternalHarUrl,
661
+ fixtures: DerivationReviewFixtures,
662
+ fieldNames: DerivationReviewFieldNames,
663
+ ): {
664
+ readonly fixtureMatches: readonly DerivationReviewMatch[];
665
+ readonly fieldNameMatches: readonly DerivationReviewFieldNameMatch[];
666
+ } {
667
+ const { url } = boundedUrl;
668
+ const values = Object.entries(fixtures).map(([label, value]) => ({ label, value }));
669
+ const state: MatchState = {
670
+ values,
671
+ locations: new Map(values.map(({ label }) => [label, new Set<string>()])),
672
+ truncated: new Set(),
673
+ fieldNames,
674
+ fieldNameLocations: fieldNames.map(() => new Set<string>()),
675
+ fieldNameTruncated: new Set(),
676
+ fieldNameValueTypes: fieldNames.map(
677
+ () => new Set<DerivationReviewFieldValueType>(),
678
+ ),
679
+ twitchGraphQlPostExchange: isRecord(entry.request)
680
+ && entry.request.method === "POST"
681
+ && isReviewedTwitchGraphQlRoute(url),
682
+ xGraphQlRequest: isRecord(entry.request)
683
+ && entry.request.method === "POST"
684
+ && url.origin === "https://x.com"
685
+ && /^\/i\/api\/graphql\/[A-Za-z0-9_-]{20,64}\/[A-Z][A-Za-z0-9_]{2,100}$/u.test(url.pathname),
686
+ url,
687
+ maximumDepth: isReviewedLinkedInArticleRoute(url)
688
+ ? INTERNAL_HAR_REVIEW_BOUNDS.maxLinkedInArticleTraversalDepth
689
+ : MAX_WALK_DEPTH,
690
+ visited: 0,
691
+ };
692
+
693
+ if (boundedUrl.pathTruncated || boundedUrl.queryTruncated) truncateEveryFixture(state);
694
+
695
+ if (boundedUrl.pathTruncated) {
696
+ truncateEveryFixture(state);
697
+ } else {
698
+ const rawPathSegments = url.pathname.split("/").filter((segment) => segment !== "");
699
+ if (rawPathSegments.length > MAX_PATH_SEGMENTS) {
700
+ truncateEveryFixture(state);
701
+ } else {
702
+ const pathSegments: string[] = [];
703
+ let invalidPath = false;
704
+ for (const raw of rawPathSegments) {
705
+ try {
706
+ pathSegments.push(decodeURIComponent(raw));
707
+ } catch {
708
+ invalidPath = true;
709
+ break;
710
+ }
711
+ }
712
+ if (invalidPath) {
713
+ truncateEveryFixture(state);
714
+ } else if (!pathSegments.some(isSensitiveCredentialKey)) {
715
+ pathSegments.forEach((segment, index) => {
716
+ matchPrimitive(state, segment, `request.path.segment[${index}]`);
717
+ });
718
+ }
719
+ }
720
+ }
721
+
722
+ const queryOccurrences = new Map<string, number>();
723
+ for (const [name, raw] of url.searchParams) {
724
+ if (name.length > INTERNAL_HAR_REVIEW_BOUNDS.maxFieldNameCharacters) {
725
+ truncateEveryFixture(state);
726
+ continue;
727
+ }
728
+ if (isSensitiveCredentialKey(name)) continue;
729
+ const safeName = reviewedInternalFieldNameForUrl(state.url, name);
730
+ const occurrence = queryOccurrences.get(safeName) ?? 0;
731
+ queryOccurrences.set(safeName, occurrence + 1);
732
+ const location = `request.query.${safeName}[${occurrence}]`;
733
+ const parsed = parsedJsonText(raw);
734
+ if (parsed.kind === "oversized") {
735
+ truncateEveryFixture(state);
736
+ continue;
737
+ }
738
+ matchPrimitive(state, raw, location);
739
+ if (parsed.kind === "parsed") walkJson(state, parsed.value, location);
740
+ }
741
+
742
+ const request = entry.request;
743
+ if (isRecord(request)) {
744
+ matchReviewedHeaderFixtures(state, request.headers, "request.header", reviewedRequestFixtureHeaders);
745
+ }
746
+ if (isRecord(request) && isRecord(request.postData)) {
747
+ const rawMimeType = request.postData.mimeType;
748
+ const oversizedMimeType = typeof rawMimeType === "string"
749
+ && rawMimeType.length > INTERNAL_HAR_REVIEW_BOUNDS.maxFieldNameCharacters;
750
+ const mimeType = typeof rawMimeType === "string"
751
+ && rawMimeType.length <= INTERNAL_HAR_REVIEW_BOUNDS.maxFieldNameCharacters
752
+ ? rawMimeType.toLowerCase()
753
+ : "";
754
+ if (
755
+ oversizedMimeType
756
+ && (
757
+ (typeof request.postData.text === "string" && request.postData.text.length > 0)
758
+ || (Array.isArray(request.postData.params) && request.postData.params.length > 0)
759
+ )
760
+ ) truncateEveryFixture(state);
761
+ if (mimeType.includes("json")) {
762
+ const parsed = parsedJsonText(request.postData.text);
763
+ if (parsed.kind === "parsed") walkJson(state, parsed.value, "request.body");
764
+ else if (parsed.kind === "oversized") truncateEveryFixture(state);
765
+ } else if (Array.isArray(request.postData.params)) {
766
+ const occurrences = new Map<string, number>();
767
+ const searchedParameters: JsonRecord[] = [];
768
+ let safeParameterOverflow = false;
769
+ const inspectedCount = Math.min(request.postData.params.length, MAX_FORM_PARAMETERS);
770
+ for (let index = 0; index < inspectedCount; index += 1) {
771
+ const parameter: unknown = request.postData.params[index];
772
+ if (!isRecord(parameter) || typeof parameter.name !== "string") {
773
+ safeParameterOverflow = true;
774
+ continue;
775
+ }
776
+ if (parameter.name.length > INTERNAL_HAR_REVIEW_BOUNDS.maxFieldNameCharacters) {
777
+ safeParameterOverflow = true;
778
+ continue;
779
+ }
780
+ if (isSensitiveCredentialKey(parameter.name)) continue;
781
+ if (searchedParameters.length >= MAX_FORM_PARAMETERS) {
782
+ safeParameterOverflow = true;
783
+ break;
784
+ }
785
+ searchedParameters.push(parameter);
786
+ }
787
+ if (
788
+ safeParameterOverflow
789
+ || request.postData.params.length > MAX_FORM_PARAMETERS
790
+ ) truncateEveryFixture(state);
791
+ for (const parameter of searchedParameters) {
792
+ if (!isRecord(parameter) || typeof parameter.name !== "string") continue;
793
+ const safeName = reviewedInternalFieldNameForUrl(state.url, parameter.name);
794
+ const occurrence = occurrences.get(safeName) ?? 0;
795
+ occurrences.set(safeName, occurrence + 1);
796
+ if (typeof parameter.value === "string") {
797
+ if (
798
+ parameter.value.length > INTERNAL_HAR_REVIEW_BOUNDS.maxRawStringCharacters
799
+ || Buffer.byteLength(parameter.value, "utf8") > MAX_JSON_TEXT_BYTES
800
+ ) {
801
+ truncateEveryFixture(state);
802
+ } else {
803
+ matchPrimitive(state, parameter.value, `request.form.${safeName}[${occurrence}]`);
804
+ }
805
+ }
806
+ }
807
+ } else if (typeof request.postData.text === "string") {
808
+ if (
809
+ request.postData.text.length > INTERNAL_HAR_REVIEW_BOUNDS.maxRawStringCharacters
810
+ || Buffer.byteLength(request.postData.text, "utf8") > MAX_JSON_TEXT_BYTES
811
+ ) {
812
+ truncateEveryFixture(state);
813
+ } else if (
814
+ state.twitchGraphQlPostExchange
815
+ && isExactMimeType(mimeType, "text/plain")
816
+ ) {
817
+ const parsed = parsedJsonText(request.postData.text);
818
+ if (parsed.kind === "parsed") walkJson(state, parsed.value, "request.body");
819
+ else if (parsed.kind === "oversized") truncateEveryFixture(state);
820
+ } else if (mimeType.includes("application/x-www-form-urlencoded")) {
821
+ matchFormUrlEncodedText(state, request.postData.text);
822
+ } else if (mimeType.includes("multipart/form-data")) {
823
+ if (request.postData.text.length > 0) truncateEveryFixture(state);
824
+ } else {
825
+ matchPrimitive(state, request.postData.text, "request.body:text");
826
+ }
827
+ }
828
+ }
829
+
830
+ if (isRecord(entry.response)) {
831
+ matchReviewedHeaderFixtures(state, entry.response.headers, "response.header", reviewedResponseFixtureHeaders);
832
+ }
833
+ if (isRecord(entry.response) && isRecord(entry.response.content)) {
834
+ const rawMimeType = entry.response.content.mimeType;
835
+ const oversizedMimeType = typeof rawMimeType === "string"
836
+ && rawMimeType.length > INTERNAL_HAR_REVIEW_BOUNDS.maxFieldNameCharacters;
837
+ const mimeType = typeof rawMimeType === "string"
838
+ && rawMimeType.length <= INTERNAL_HAR_REVIEW_BOUNDS.maxFieldNameCharacters
839
+ ? rawMimeType.toLowerCase()
840
+ : "";
841
+ const encoding = entry.response.content.encoding;
842
+ const hasResponseText = typeof entry.response.content.text === "string"
843
+ && entry.response.content.text.length > 0;
844
+ if (oversizedMimeType && hasResponseText) truncateEveryFixture(state);
845
+ if (mimeType.includes("json")) {
846
+ if (encoding === "base64") {
847
+ if (hasResponseText) truncateEveryFixture(state);
848
+ } else {
849
+ const parsed = parsedJsonText(entry.response.content.text);
850
+ if (parsed.kind === "parsed") walkJson(state, parsed.value, "response.body");
851
+ else if (parsed.kind === "oversized") truncateEveryFixture(state);
852
+ }
853
+ }
854
+ }
855
+
856
+ return {
857
+ fixtureMatches: values.map(({ label }) => ({
858
+ label,
859
+ locations: [...(state.locations.get(label) ?? [])].sort(),
860
+ truncated: state.truncated.has(label),
861
+ })),
862
+ fieldNameMatches: fieldNames.map((_name, candidateIndex) => ({
863
+ candidateIndex,
864
+ locations: [...(state.fieldNameLocations[candidateIndex] ?? [])].sort(),
865
+ truncated: state.fieldNameTruncated.has(candidateIndex),
866
+ valueTypes: [...(state.fieldNameValueTypes[candidateIndex] ?? [])].sort(),
867
+ })),
868
+ };
869
+ }
870
+
871
+ const warnings = [
872
+ "Private review is structural only: it cannot authorize or execute a request.",
873
+ "Fixture values are matched only in first-party path/query/body, reviewed non-secret contract headers, and JSON response content; values, cookies, credentials, and unreviewed headers are never returned.",
874
+ ] as const;
875
+
876
+ export function reviewDerivationHarValue(
877
+ value: unknown,
878
+ targetOriginValue: string,
879
+ selection: DerivationReviewSelection,
880
+ ): DerivationReviewResult {
881
+ const target = exactTarget(targetOriginValue);
882
+ const entries = parseHarEntries(value);
883
+ if (selection.kind === "entry") {
884
+ if (!Number.isSafeInteger(selection.entryIndex) || selection.entryIndex < 0 || selection.entryIndex >= entries.length) {
885
+ throw new Error("private review entry index is outside the captured HAR");
886
+ }
887
+ const entry = entries[selection.entryIndex];
888
+ const reviewable = reviewableRequest(entry, target.origin);
889
+ const candidate = reviewable === null ? null : exactCandidate(entry, target.origin);
890
+ if (reviewable === null || candidate === null) {
891
+ throw new Error("selected HAR entry is not a reviewable first-party API exchange");
892
+ }
893
+ if (selection.fixtures !== undefined && selection.fieldNames !== undefined) {
894
+ throw new Error("private review entry probes are mutually exclusive");
895
+ }
896
+ const fixtures = selection.fixtures === undefined
897
+ ? Object.freeze({})
898
+ : parseReviewFixtures(selection.fixtures, 0);
899
+ const fieldNames = selection.fieldNames === undefined
900
+ ? Object.freeze([])
901
+ : parseDerivationReviewFieldNames(selection.fieldNames);
902
+ const matches = matchEntryProbes(
903
+ reviewable.entry,
904
+ reviewable,
905
+ fixtures,
906
+ fieldNames,
907
+ );
908
+ return {
909
+ schemaVersion: 1,
910
+ kind: "entry",
911
+ targetOrigin: target.origin,
912
+ totalHarEntries: entries.length,
913
+ entryIndex: selection.entryIndex,
914
+ structure: candidate,
915
+ fixtureMatches: matches.fixtureMatches,
916
+ fieldNameMatches: matches.fieldNameMatches,
917
+ warnings,
918
+ };
919
+ }
920
+
921
+ if (
922
+ !Number.isSafeInteger(selection.offset)
923
+ || selection.offset < 0
924
+ || !Number.isSafeInteger(selection.limit)
925
+ || selection.limit < 1
926
+ || selection.limit > MAX_REVIEW_LIMIT
927
+ ) throw new Error("private review list bounds are invalid");
928
+ const selected: DerivationReviewListEntry[] = [];
929
+ let reviewableEntries = 0;
930
+ for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 1) {
931
+ const entry = entries[entryIndex];
932
+ if (reviewableRequest(entry, target.origin) === null) continue;
933
+ const reviewableIndex = reviewableEntries;
934
+ reviewableEntries += 1;
935
+ if (reviewableIndex < selection.offset || selected.length >= selection.limit) continue;
936
+ const candidate = exactCandidate(entry, target.origin);
937
+ if (candidate === null) continue;
938
+ selected.push({
939
+ entryIndex,
940
+ method: candidate.method,
941
+ origin: candidate.origin,
942
+ path: candidate.path,
943
+ operationType: candidate.operationType,
944
+ statuses: candidate.statuses,
945
+ queryNames: candidate.queryNames,
946
+ revisions: candidate.revisions,
947
+ requestFieldCount: candidate.requestFieldPaths.length,
948
+ responseFieldCount: candidate.responseFieldPaths.length,
949
+ });
950
+ }
951
+ const nextOffset = selection.offset + selected.length < reviewableEntries
952
+ ? selection.offset + selected.length
953
+ : null;
954
+ return {
955
+ schemaVersion: 1,
956
+ kind: "list",
957
+ targetOrigin: target.origin,
958
+ totalHarEntries: entries.length,
959
+ reviewableEntries,
960
+ offset: selection.offset,
961
+ limit: selection.limit,
962
+ nextOffset,
963
+ entries: selected,
964
+ warnings,
965
+ };
966
+ }
967
+
968
+ export function reviewDerivationHarText(
969
+ text: string,
970
+ targetOrigin: string,
971
+ selection: DerivationReviewSelection,
972
+ ): DerivationReviewResult {
973
+ let value: unknown;
974
+ try {
975
+ value = JSON.parse(text) as unknown;
976
+ } catch {
977
+ throw new Error("private review HAR is not valid JSON");
978
+ }
979
+ return reviewDerivationHarValue(value, targetOrigin, selection);
980
+ }