@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,866 @@
1
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
2
+ import { join } from "node:path";
3
+ import { performance } from "node:perf_hooks";
4
+
5
+ import { canonicalJson } from "./canonical-json";
6
+ import {
7
+ currentProcessStartIdentity,
8
+ processOwnerStatus,
9
+ type ProcessOwnerIdentity,
10
+ } from "./process-identity";
11
+ import {
12
+ createPrivateJsonIfAbsent,
13
+ ensurePrivateStateDirectory,
14
+ readPrivateStateFileIfPresent,
15
+ removePrivateStateFileIfUnchanged,
16
+ ghostgetStateHome,
17
+ writePrivateJsonIfUnchanged,
18
+ } from "./storage";
19
+
20
+ type Environment = Readonly<Record<string, string | undefined>>;
21
+ type JsonRecord = Record<string, unknown>;
22
+
23
+ const CONTROL_DIRECTORY = "read-projection-control";
24
+ const ADMISSION_DIRECTORY = "admissions";
25
+ const INCARNATION_DIRECTORY = "incarnations";
26
+ const MAX_CONTROL_RECORD_BYTES = 4 * 1024;
27
+ const MAX_ACQUISITION_ATTEMPTS = 16;
28
+ export const READ_PROJECTION_SHORT_SETTLEMENT_WAIT_MS = 10_000;
29
+ export const READ_PROJECTION_TRANSITION_SETTLEMENT_WAIT_MS = 120_000;
30
+ const SETTLEMENT_WAIT_SLICE_MS = 10;
31
+ const settlementWaitState = new Int32Array(new SharedArrayBuffer(4));
32
+ const authIdPattern = /^[a-z][a-z0-9-]{0,47}$/u;
33
+ const digestPattern = /^[a-f0-9]{64}$/u;
34
+ const uuidPattern = /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/u;
35
+
36
+ export type ReadProjectionAdmissionOwner = ProcessOwnerIdentity & {
37
+ readonly token: string;
38
+ };
39
+
40
+ export type ReadProjectionAdmissionSettlementOptions = {
41
+ /** Monotonic time budget for a different process's exact-live owner. */
42
+ readonly maximumWaitMs?: number;
43
+ };
44
+
45
+ export type ReadProjectionAdmissionContentionReason =
46
+ | "active-owner"
47
+ | "same-process-owner"
48
+ | "settlement-exhausted";
49
+
50
+ export class ReadProjectionAdmissionContentionError extends Error {
51
+ readonly authId: string;
52
+ readonly owner: ReadProjectionAdmissionOwner;
53
+ readonly reason: ReadProjectionAdmissionContentionReason;
54
+
55
+ constructor(
56
+ authIdValue: string,
57
+ ownerValue: ReadProjectionAdmissionOwner,
58
+ reason: ReadProjectionAdmissionContentionReason,
59
+ options: { readonly cause?: unknown } = {},
60
+ ) {
61
+ const detail = reason === "settlement-exhausted"
62
+ ? "read projection transition did not settle within its bounded wait"
63
+ : reason === "same-process-owner"
64
+ ? "already has an active same-process read projection transition"
65
+ : "already has an active read projection transition";
66
+ super(`auth locator ${authIdValue} ${detail}`, options);
67
+ this.name = "ReadProjectionAdmissionContentionError";
68
+ this.authId = authIdValue;
69
+ this.owner = ownerValue;
70
+ this.reason = reason;
71
+ }
72
+ }
73
+
74
+ type ReadProjectionAdmissionClaim = {
75
+ readonly schemaVersion: 1;
76
+ readonly authId: string;
77
+ readonly owner: ReadProjectionAdmissionOwner;
78
+ };
79
+
80
+ type ReadProjectionIncarnation = {
81
+ readonly schemaVersion: 1;
82
+ readonly authId: string;
83
+ readonly incarnation: string;
84
+ };
85
+
86
+ type Snapshot<T> = {
87
+ readonly value: T;
88
+ readonly contentSha256: string;
89
+ };
90
+
91
+ export type ReadProjectionAuthAdmission = {
92
+ readonly authId: string;
93
+ readonly owner: ReadProjectionAdmissionOwner;
94
+ readonly release: () => void;
95
+ };
96
+
97
+ type HeldAdmission = {
98
+ depth: number;
99
+ readonly admission: ReadProjectionAuthAdmission;
100
+ };
101
+
102
+ const heldAdmissions = new Map<string, HeldAdmission>();
103
+
104
+ function errorValue(value: unknown, fallback: string): Error {
105
+ return value instanceof Error ? value : new Error(fallback, { cause: value });
106
+ }
107
+
108
+ function isThenable(value: unknown): boolean {
109
+ if (
110
+ value === null
111
+ || (typeof value !== "object" && typeof value !== "function")
112
+ ) return false;
113
+ const visited = new Set<object>();
114
+ let current: object | null = value;
115
+ while (current !== null) {
116
+ if (visited.has(current)) return true;
117
+ visited.add(current);
118
+ const descriptor = Object.getOwnPropertyDescriptor(current, "then");
119
+ if (descriptor !== undefined) {
120
+ return !("value" in descriptor) || typeof descriptor.value === "function";
121
+ }
122
+ current = Object.getPrototypeOf(current) as object | null;
123
+ }
124
+ return false;
125
+ }
126
+
127
+ function synchronousOperationResult<T>(operation: () => T): T {
128
+ const value = operation();
129
+ if (isThenable(value)) {
130
+ throw new Error(
131
+ "read projection admission operations must be synchronous and must not return thenables",
132
+ );
133
+ }
134
+ return value;
135
+ }
136
+
137
+ function hash(value: string): string {
138
+ return createHash("sha256").update(value, "utf8").digest("hex");
139
+ }
140
+
141
+ function strictRecord(value: unknown, label: string): JsonRecord {
142
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
143
+ throw new Error(`${label} must be an object`);
144
+ }
145
+ const prototype: unknown = Object.getPrototypeOf(value) as unknown;
146
+ if (prototype !== Object.prototype && prototype !== null) {
147
+ throw new Error(`${label} has an unsupported prototype`);
148
+ }
149
+ const descriptors = Object.getOwnPropertyDescriptors(value);
150
+ if (Reflect.ownKeys(descriptors).some((key) => typeof key !== "string")) {
151
+ throw new Error(`${label} has unsupported symbol fields`);
152
+ }
153
+ const result: JsonRecord = {};
154
+ for (const [key, descriptor] of Object.entries(descriptors)) {
155
+ if (!descriptor.enumerable || !("value" in descriptor)) {
156
+ throw new Error(`${label} has unsupported accessor fields`);
157
+ }
158
+ Object.defineProperty(result, key, {
159
+ value: descriptor.value,
160
+ enumerable: true,
161
+ configurable: false,
162
+ writable: false,
163
+ });
164
+ }
165
+ return result;
166
+ }
167
+
168
+ function exactKeys(
169
+ value: Readonly<JsonRecord>,
170
+ expected: readonly string[],
171
+ label: string,
172
+ ): void {
173
+ const actual = Object.keys(value).sort();
174
+ const wanted = [...expected].sort();
175
+ if (
176
+ actual.length !== wanted.length
177
+ || actual.some((key, index) => key !== wanted[index])
178
+ ) throw new Error(`${label} has unsupported fields`);
179
+ }
180
+
181
+ function authId(value: unknown): string {
182
+ if (typeof value !== "string" || !authIdPattern.test(value)) {
183
+ throw new Error("read projection auth ID must be lowercase kebab-case");
184
+ }
185
+ return value;
186
+ }
187
+
188
+ function digest(value: unknown, label: string): string {
189
+ if (typeof value !== "string" || !digestPattern.test(value)) {
190
+ throw new Error(`${label} is malformed`);
191
+ }
192
+ return value;
193
+ }
194
+
195
+ function owner(value: unknown): ReadProjectionAdmissionOwner {
196
+ const record = strictRecord(value, "read projection admission owner");
197
+ exactKeys(
198
+ record,
199
+ ["pid", "token", "bootId", "processStartId"],
200
+ "read projection admission owner",
201
+ );
202
+ if (
203
+ typeof record.pid !== "number"
204
+ || !Number.isSafeInteger(record.pid)
205
+ || record.pid < 1
206
+ || record.pid > 2_147_483_647
207
+ || typeof record.token !== "string"
208
+ || !uuidPattern.test(record.token)
209
+ ) throw new Error("read projection admission owner is malformed");
210
+ return Object.freeze({
211
+ pid: record.pid,
212
+ token: record.token,
213
+ bootId: digest(record.bootId, "read projection admission boot identity"),
214
+ processStartId: digest(
215
+ record.processStartId,
216
+ "read projection admission process identity",
217
+ ),
218
+ });
219
+ }
220
+
221
+ function admissionClaim(value: unknown): ReadProjectionAdmissionClaim {
222
+ const record = strictRecord(value, "read projection admission");
223
+ exactKeys(
224
+ record,
225
+ ["schemaVersion", "authId", "owner"],
226
+ "read projection admission",
227
+ );
228
+ if (record.schemaVersion !== 1) {
229
+ throw new Error("read projection admission version is unsupported");
230
+ }
231
+ return Object.freeze({
232
+ schemaVersion: 1,
233
+ authId: authId(record.authId),
234
+ owner: owner(record.owner),
235
+ });
236
+ }
237
+
238
+ function incarnationRecord(value: unknown): ReadProjectionIncarnation {
239
+ const record = strictRecord(value, "read projection auth incarnation");
240
+ exactKeys(
241
+ record,
242
+ ["schemaVersion", "authId", "incarnation"],
243
+ "read projection auth incarnation",
244
+ );
245
+ if (record.schemaVersion !== 1) {
246
+ throw new Error("read projection auth incarnation version is unsupported");
247
+ }
248
+ return Object.freeze({
249
+ schemaVersion: 1,
250
+ authId: authId(record.authId),
251
+ incarnation: digest(
252
+ record.incarnation,
253
+ "read projection auth incarnation",
254
+ ),
255
+ });
256
+ }
257
+
258
+ function controlDirectory(environment: Environment): string {
259
+ return join(ghostgetStateHome(environment), CONTROL_DIRECTORY);
260
+ }
261
+
262
+ function admissionsDirectory(environment: Environment): string {
263
+ return join(controlDirectory(environment), ADMISSION_DIRECTORY);
264
+ }
265
+
266
+ function incarnationsDirectory(environment: Environment): string {
267
+ return join(controlDirectory(environment), INCARNATION_DIRECTORY);
268
+ }
269
+
270
+ function authCoordinate(id: string): string {
271
+ return hash(`wrench-read-projection-auth-coordinate-v1\0${id}`);
272
+ }
273
+
274
+ function admissionPath(id: string, environment: Environment): string {
275
+ return join(admissionsDirectory(environment), `${authCoordinate(id)}.json`);
276
+ }
277
+
278
+ function incarnationPath(id: string, environment: Environment): string {
279
+ return join(incarnationsDirectory(environment), `${authCoordinate(id)}.json`);
280
+ }
281
+
282
+ function canonicalSnapshot<T>(value: T): Snapshot<T> {
283
+ const content = `${canonicalJson(value)}\n`;
284
+ return Object.freeze({ value, contentSha256: hash(content) });
285
+ }
286
+
287
+ function parseCanonicalSnapshot<T>(
288
+ content: string,
289
+ label: string,
290
+ parse: (value: unknown) => T,
291
+ ): Snapshot<T> {
292
+ let parsed: unknown;
293
+ try {
294
+ parsed = JSON.parse(content) as unknown;
295
+ } catch {
296
+ throw new Error(`${label} is malformed JSON`);
297
+ }
298
+ const value = parse(parsed);
299
+ if (content !== `${canonicalJson(value)}\n`) {
300
+ throw new Error(`${label} is not canonical JSON`);
301
+ }
302
+ return canonicalSnapshot(value);
303
+ }
304
+
305
+ function readAdmissionClaim(
306
+ id: string,
307
+ environment: Environment,
308
+ ): Snapshot<ReadProjectionAdmissionClaim> | null {
309
+ const content = readPrivateStateFileIfPresent(
310
+ admissionPath(id, environment),
311
+ MAX_CONTROL_RECORD_BYTES,
312
+ "read projection admission",
313
+ environment,
314
+ );
315
+ if (content === null) return null;
316
+ const snapshot = parseCanonicalSnapshot(
317
+ content,
318
+ "read projection admission",
319
+ admissionClaim,
320
+ );
321
+ if (snapshot.value.authId !== id) {
322
+ throw new Error("read projection admission does not match its coordinate");
323
+ }
324
+ return snapshot;
325
+ }
326
+
327
+ function readIncarnation(
328
+ id: string,
329
+ environment: Environment,
330
+ ): Snapshot<ReadProjectionIncarnation> | null {
331
+ const content = readRawIncarnation(id, environment);
332
+ if (content === null) return null;
333
+ const snapshot = parseCanonicalSnapshot(
334
+ content.content,
335
+ "read projection auth incarnation",
336
+ incarnationRecord,
337
+ );
338
+ if (snapshot.value.authId !== id) {
339
+ throw new Error(
340
+ "read projection auth incarnation does not match its coordinate",
341
+ );
342
+ }
343
+ return snapshot;
344
+ }
345
+
346
+ function readRawIncarnation(
347
+ id: string,
348
+ environment: Environment,
349
+ ): { readonly content: string; readonly contentSha256: string } | null {
350
+ const content = readPrivateStateFileIfPresent(
351
+ incarnationPath(id, environment),
352
+ MAX_CONTROL_RECORD_BYTES,
353
+ "read projection auth incarnation",
354
+ environment,
355
+ );
356
+ if (content === null) return null;
357
+ return Object.freeze({ content, contentSha256: hash(content) });
358
+ }
359
+
360
+ function newOwner(): ReadProjectionAdmissionOwner {
361
+ const identity = currentProcessStartIdentity();
362
+ return Object.freeze({
363
+ pid: process.pid,
364
+ token: randomUUID(),
365
+ bootId: identity.bootId,
366
+ processStartId: identity.processStartId,
367
+ });
368
+ }
369
+
370
+ function sameOwner(
371
+ left: ReadProjectionAdmissionOwner,
372
+ right: ReadProjectionAdmissionOwner,
373
+ ): boolean {
374
+ return left.pid === right.pid
375
+ && left.token === right.token
376
+ && left.bootId === right.bootId
377
+ && left.processStartId === right.processStartId;
378
+ }
379
+
380
+ function assertAdmissionHeld(
381
+ admission: ReadProjectionAuthAdmission,
382
+ environment: Environment,
383
+ ): void {
384
+ const current = readAdmissionClaim(admission.authId, environment);
385
+ if (
386
+ current === null
387
+ || !sameOwner(current.value.owner, admission.owner)
388
+ || processOwnerStatus(current.value.owner) !== "exact-live-owner"
389
+ ) {
390
+ throw new Error("read projection admission authority is not exact and live");
391
+ }
392
+ }
393
+
394
+ function acquiredAdmission(
395
+ id: string,
396
+ claim: ReadProjectionAdmissionClaim,
397
+ snapshot: Snapshot<ReadProjectionAdmissionClaim>,
398
+ environment: Environment,
399
+ options: {
400
+ /** Test-only seam invoked once after an exact claim loses mutation arbitration. */
401
+ readonly afterReleaseContentionForTest?: () => void;
402
+ } = {},
403
+ ): ReadProjectionAuthAdmission {
404
+ let released = false;
405
+ return Object.freeze({
406
+ authId: id,
407
+ owner: claim.owner,
408
+ release: () => {
409
+ if (released) return;
410
+ const deadline = performance.now()
411
+ + READ_PROJECTION_SHORT_SETTLEMENT_WAIT_MS;
412
+ let contentionReported = false;
413
+ for (;;) {
414
+ assertAdmissionHeld(
415
+ { authId: id, owner: claim.owner, release: () => {} },
416
+ environment,
417
+ );
418
+ if (removePrivateStateFileIfUnchanged(
419
+ admissionPath(id, environment),
420
+ { expectedCurrentContentSha256: snapshot.contentSha256 },
421
+ environment,
422
+ )) {
423
+ released = true;
424
+ return;
425
+ }
426
+
427
+ const current = readAdmissionClaim(id, environment);
428
+ if (
429
+ current === null
430
+ || current.contentSha256 !== snapshot.contentSha256
431
+ || !sameOwner(current.value.owner, claim.owner)
432
+ ) {
433
+ throw new Error("read projection admission changed before release");
434
+ }
435
+ if (!contentionReported) {
436
+ options.afterReleaseContentionForTest?.();
437
+ contentionReported = true;
438
+ }
439
+ const remaining = deadline - performance.now();
440
+ if (remaining <= 0) {
441
+ throw new Error(
442
+ "read projection admission could not be released within its bounded wait",
443
+ );
444
+ }
445
+ Atomics.wait(
446
+ settlementWaitState,
447
+ 0,
448
+ 0,
449
+ Math.min(SETTLEMENT_WAIT_SLICE_MS, Math.ceil(remaining)),
450
+ );
451
+ }
452
+ },
453
+ });
454
+ }
455
+
456
+ function isStateMutationCreateContention(error: unknown): boolean {
457
+ return error instanceof Error
458
+ && error.message.includes("state file mutation is already active");
459
+ }
460
+
461
+ export function acquireReadProjectionAuthAdmission(
462
+ authIdValue: string,
463
+ environment: Environment = process.env,
464
+ options: {
465
+ /** Test-only seam that throws after the claim is durably created. */
466
+ readonly afterCreateCommitForTest?: () => void;
467
+ /** Test-only seam invoked once after an exact claim loses mutation arbitration. */
468
+ readonly afterReleaseContentionForTest?: () => void;
469
+ } = {},
470
+ ): ReadProjectionAuthAdmission {
471
+ if (
472
+ options.afterCreateCommitForTest !== undefined
473
+ && typeof options.afterCreateCommitForTest !== "function"
474
+ ) {
475
+ throw new Error("read projection admission fault injection is malformed");
476
+ }
477
+ if (
478
+ options.afterReleaseContentionForTest !== undefined
479
+ && typeof options.afterReleaseContentionForTest !== "function"
480
+ ) {
481
+ throw new Error("read projection admission fault injection is malformed");
482
+ }
483
+ if (
484
+ (
485
+ options.afterCreateCommitForTest !== undefined
486
+ || options.afterReleaseContentionForTest !== undefined
487
+ )
488
+ && process.env.NODE_ENV !== "test"
489
+ ) {
490
+ throw new Error(
491
+ "read projection admission fault injection is available only in tests",
492
+ );
493
+ }
494
+ const id = authId(authIdValue);
495
+ ensurePrivateStateDirectory(admissionsDirectory(environment), environment);
496
+ for (let attempt = 0; attempt < MAX_ACQUISITION_ATTEMPTS; attempt += 1) {
497
+ const observed = readAdmissionClaim(id, environment);
498
+ if (observed !== null) {
499
+ const status = processOwnerStatus(observed.value.owner);
500
+ if (status === "exact-live-owner") {
501
+ throw new ReadProjectionAdmissionContentionError(
502
+ id,
503
+ observed.value.owner,
504
+ "active-owner",
505
+ );
506
+ }
507
+ if (status === "unknown") {
508
+ throw new Error(
509
+ `auth locator ${id} read projection owner cannot be inspected safely`,
510
+ );
511
+ }
512
+ removePrivateStateFileIfUnchanged(
513
+ admissionPath(id, environment),
514
+ { expectedCurrentContentSha256: observed.contentSha256 },
515
+ environment,
516
+ );
517
+ continue;
518
+ }
519
+
520
+ const claim = admissionClaim({
521
+ schemaVersion: 1,
522
+ authId: id,
523
+ owner: newOwner(),
524
+ });
525
+ const snapshot = canonicalSnapshot(claim);
526
+ let created = false;
527
+ let currentAfterCreate: Snapshot<ReadProjectionAdmissionClaim> | null
528
+ | undefined;
529
+ try {
530
+ created = createPrivateJsonIfAbsent(
531
+ admissionPath(id, environment),
532
+ claim,
533
+ { environment },
534
+ ).created;
535
+ if (created) options.afterCreateCommitForTest?.();
536
+ } catch (error) {
537
+ let committed: Snapshot<ReadProjectionAdmissionClaim> | null;
538
+ try {
539
+ committed = readAdmissionClaim(id, environment);
540
+ } catch (reconciliationError) {
541
+ throw new AggregateError(
542
+ [
543
+ errorValue(error, "read projection admission creation failed"),
544
+ errorValue(
545
+ reconciliationError,
546
+ "read projection admission reconciliation failed",
547
+ ),
548
+ ],
549
+ `auth locator ${id} read projection admission creation could not be reconciled`,
550
+ );
551
+ }
552
+ if (
553
+ committed !== null
554
+ && committed.contentSha256 === snapshot.contentSha256
555
+ && sameOwner(committed.value.owner, claim.owner)
556
+ ) {
557
+ return acquiredAdmission(id, claim, snapshot, environment, options);
558
+ }
559
+ if (!isStateMutationCreateContention(error)) throw error;
560
+ currentAfterCreate = committed;
561
+ }
562
+ if (created) {
563
+ return acquiredAdmission(id, claim, snapshot, environment, options);
564
+ }
565
+
566
+ const current = currentAfterCreate
567
+ ?? readAdmissionClaim(id, environment);
568
+ if (current === null) continue;
569
+ const status = processOwnerStatus(current.value.owner);
570
+ if (status === "exact-live-owner") {
571
+ throw new ReadProjectionAdmissionContentionError(
572
+ id,
573
+ current.value.owner,
574
+ "active-owner",
575
+ );
576
+ }
577
+ if (status === "unknown") {
578
+ throw new Error(
579
+ `auth locator ${id} read projection owner cannot be inspected safely`,
580
+ );
581
+ }
582
+ removePrivateStateFileIfUnchanged(
583
+ admissionPath(id, environment),
584
+ { expectedCurrentContentSha256: current.contentSha256 },
585
+ environment,
586
+ );
587
+ }
588
+ throw new Error(
589
+ `auth locator ${id} read projection admission could not be acquired`,
590
+ );
591
+ }
592
+
593
+ function withAcquiredReadProjectionAuthAdmission<T>(
594
+ id: string,
595
+ environment: Environment,
596
+ operation: () => T,
597
+ acquire: () => ReadProjectionAuthAdmission,
598
+ ): T {
599
+ const heldKey = `${ghostgetStateHome(environment)}\0${id}`;
600
+ const held = heldAdmissions.get(heldKey);
601
+ if (held !== undefined) {
602
+ assertAdmissionHeld(held.admission, environment);
603
+ held.depth += 1;
604
+ try {
605
+ return synchronousOperationResult(operation);
606
+ } finally {
607
+ held.depth -= 1;
608
+ }
609
+ }
610
+
611
+ const admission = acquire();
612
+ const acquired: HeldAdmission = { depth: 1, admission };
613
+ heldAdmissions.set(heldKey, acquired);
614
+ let outcome: { readonly ok: true; readonly value: T }
615
+ | { readonly ok: false; readonly error: Error };
616
+ try {
617
+ outcome = { ok: true, value: synchronousOperationResult(operation) };
618
+ } catch (error) {
619
+ outcome = {
620
+ ok: false,
621
+ error: errorValue(error, "read projection operation failed"),
622
+ };
623
+ }
624
+ acquired.depth -= 1;
625
+ heldAdmissions.delete(heldKey);
626
+ let releaseError: Error | null = null;
627
+ try {
628
+ admission.release();
629
+ } catch (error) {
630
+ releaseError = errorValue(error, "read projection admission release failed");
631
+ }
632
+ if (!outcome.ok && releaseError !== null) {
633
+ throw new AggregateError(
634
+ [outcome.error, releaseError],
635
+ `auth locator ${id} read projection operation and admission release both failed`,
636
+ );
637
+ }
638
+ if (!outcome.ok) throw outcome.error;
639
+ if (releaseError !== null) throw releaseError;
640
+ return outcome.value;
641
+ }
642
+
643
+ export function withReadProjectionAuthAdmission<T>(
644
+ authIdValue: string,
645
+ environment: Environment,
646
+ operation: () => T,
647
+ ): T {
648
+ const id = authId(authIdValue);
649
+ return withAcquiredReadProjectionAuthAdmission(
650
+ id,
651
+ environment,
652
+ operation,
653
+ () => acquireReadProjectionAuthAdmission(id, environment),
654
+ );
655
+ }
656
+
657
+ function settlementWaitMilliseconds(
658
+ options: ReadProjectionAdmissionSettlementOptions,
659
+ ): number {
660
+ const value = options.maximumWaitMs
661
+ ?? READ_PROJECTION_SHORT_SETTLEMENT_WAIT_MS;
662
+ if (
663
+ typeof value !== "number"
664
+ || !Number.isSafeInteger(value)
665
+ || value < 0
666
+ || value > READ_PROJECTION_TRANSITION_SETTLEMENT_WAIT_MS
667
+ ) {
668
+ throw new Error(
669
+ `read projection admission settlement wait must be an integer from 0 to ${READ_PROJECTION_TRANSITION_SETTLEMENT_WAIT_MS} milliseconds`,
670
+ );
671
+ }
672
+ return value;
673
+ }
674
+
675
+ function acquireSettledReadProjectionAuthAdmission(
676
+ id: string,
677
+ environment: Environment,
678
+ options: ReadProjectionAdmissionSettlementOptions,
679
+ ): ReadProjectionAuthAdmission {
680
+ const maximumWaitMs = settlementWaitMilliseconds(options);
681
+ const deadline = performance.now() + maximumWaitMs;
682
+ let active: ReadProjectionAdmissionContentionError;
683
+ while (true) {
684
+ try {
685
+ return acquireReadProjectionAuthAdmission(id, environment);
686
+ } catch (error) {
687
+ if (!(error instanceof ReadProjectionAdmissionContentionError)) {
688
+ throw error;
689
+ }
690
+ active = error;
691
+ }
692
+
693
+ if (active.owner.pid === process.pid) {
694
+ throw new ReadProjectionAdmissionContentionError(
695
+ id,
696
+ active.owner,
697
+ "same-process-owner",
698
+ { cause: active },
699
+ );
700
+ }
701
+ const remaining = deadline - performance.now();
702
+ if (remaining <= 0) {
703
+ throw new ReadProjectionAdmissionContentionError(
704
+ id,
705
+ active.owner,
706
+ "settlement-exhausted",
707
+ { cause: active },
708
+ );
709
+ }
710
+ Atomics.wait(
711
+ settlementWaitState,
712
+ 0,
713
+ 0,
714
+ Math.min(SETTLEMENT_WAIT_SLICE_MS, Math.ceil(remaining)),
715
+ );
716
+ }
717
+ }
718
+
719
+ /**
720
+ * Wait for a bounded budget when an exact-live owner is in another process,
721
+ * then run one synchronous state transition. Direct acquisition and auth
722
+ * mutations retain immediate contention semantics; this path is for read
723
+ * settlement.
724
+ */
725
+ export function withSettledReadProjectionAuthAdmission<T>(
726
+ authIdValue: string,
727
+ environment: Environment,
728
+ operation: () => T,
729
+ options: ReadProjectionAdmissionSettlementOptions = {},
730
+ ): T {
731
+ const id = authId(authIdValue);
732
+ settlementWaitMilliseconds(options);
733
+ return withAcquiredReadProjectionAuthAdmission(
734
+ id,
735
+ environment,
736
+ operation,
737
+ () => acquireSettledReadProjectionAuthAdmission(id, environment, options),
738
+ );
739
+ }
740
+
741
+ function ensureIncarnationUnderAdmission(
742
+ id: string,
743
+ environment: Environment,
744
+ ): Snapshot<ReadProjectionIncarnation> {
745
+ ensurePrivateStateDirectory(incarnationsDirectory(environment), environment);
746
+ const current = readIncarnation(id, environment);
747
+ if (current !== null) return current;
748
+ const value = incarnationRecord({
749
+ schemaVersion: 1,
750
+ authId: id,
751
+ incarnation: randomBytes(32).toString("hex"),
752
+ });
753
+ createPrivateJsonIfAbsent(
754
+ incarnationPath(id, environment),
755
+ value,
756
+ { environment },
757
+ );
758
+ const created = readIncarnation(id, environment);
759
+ if (created === null) {
760
+ throw new Error("read projection auth incarnation could not be ensured");
761
+ }
762
+ return created;
763
+ }
764
+
765
+ export function ensureReadProjectionAuthIncarnation(
766
+ authIdValue: string,
767
+ environment: Environment = process.env,
768
+ ): string {
769
+ const id = authId(authIdValue);
770
+ return withReadProjectionAuthAdmission(id, environment, () =>
771
+ ensureIncarnationUnderAdmission(id, environment).value.incarnation);
772
+ }
773
+
774
+ export function rotateReadProjectionAuthIncarnation(
775
+ authIdValue: string,
776
+ environment: Environment = process.env,
777
+ ): string {
778
+ const id = authId(authIdValue);
779
+ return withReadProjectionAuthAdmission(id, environment, () => {
780
+ ensurePrivateStateDirectory(incarnationsDirectory(environment), environment);
781
+ const current = readRawIncarnation(id, environment);
782
+ let previousIncarnation: string | null = null;
783
+ if (current !== null) {
784
+ try {
785
+ const parsed = parseCanonicalSnapshot(
786
+ current.content,
787
+ "read projection auth incarnation",
788
+ incarnationRecord,
789
+ );
790
+ if (parsed.value.authId === id) {
791
+ previousIncarnation = parsed.value.incarnation;
792
+ }
793
+ } catch {
794
+ // Mutation rotation is the one recovery authority for malformed but
795
+ // bounded incarnation state. Query identity reads remain fail-closed.
796
+ }
797
+ }
798
+ let incarnation: string;
799
+ do {
800
+ incarnation = randomBytes(32).toString("hex");
801
+ } while (incarnation === previousIncarnation);
802
+ const replacement = incarnationRecord({
803
+ schemaVersion: 1,
804
+ authId: id,
805
+ incarnation,
806
+ });
807
+ const replaced = current === null
808
+ ? createPrivateJsonIfAbsent(
809
+ incarnationPath(id, environment),
810
+ replacement,
811
+ { environment },
812
+ ).created
813
+ : writePrivateJsonIfUnchanged(
814
+ incarnationPath(id, environment),
815
+ replacement,
816
+ { expectedCurrentContentSha256: current.contentSha256 },
817
+ );
818
+ if (!replaced) {
819
+ throw new Error(
820
+ `auth locator ${id} read projection incarnation changed concurrently`,
821
+ );
822
+ }
823
+ return replacement.incarnation;
824
+ });
825
+ }
826
+
827
+ export function removeReadProjectionAuthIncarnation(
828
+ authIdValue: string,
829
+ environment: Environment = process.env,
830
+ ): boolean {
831
+ const id = authId(authIdValue);
832
+ return withReadProjectionAuthAdmission(id, environment, () => {
833
+ ensurePrivateStateDirectory(incarnationsDirectory(environment), environment);
834
+ const current = readRawIncarnation(id, environment);
835
+ if (current === null) return false;
836
+ if (!removePrivateStateFileIfUnchanged(
837
+ incarnationPath(id, environment),
838
+ { expectedCurrentContentSha256: current.contentSha256 },
839
+ environment,
840
+ )) {
841
+ throw new Error(
842
+ `auth locator ${id} read projection incarnation changed concurrently before removal`,
843
+ );
844
+ }
845
+ return true;
846
+ });
847
+ }
848
+
849
+ export function projectionAuthIdentityHash(
850
+ authIdValue: string,
851
+ exactAuthContentHashValue: string,
852
+ environment: Environment = process.env,
853
+ ): string {
854
+ const id = authId(authIdValue);
855
+ const exactAuthContentHash = digest(
856
+ exactAuthContentHashValue,
857
+ "exact auth content hash",
858
+ );
859
+ return withReadProjectionAuthAdmission(id, environment, () => {
860
+ const incarnation = ensureIncarnationUnderAdmission(id, environment)
861
+ .value.incarnation;
862
+ return hash(
863
+ `wrench-read-projection-auth-identity-v1\0${id}\0${exactAuthContentHash}\0${incarnation}`,
864
+ );
865
+ });
866
+ }