2sagaco48 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/diagnostics.mjs +141 -0
  2. package/dist/compute.worker-j5ahIpF-.js +1 -0
  3. package/dist/graph.worker-DG6iGCB9.js +1 -0
  4. package/dist/index.html +2314 -0
  5. package/flatten-guide.md +248 -0
  6. package/index.html +12 -0
  7. package/package.json +38 -0
  8. package/src/App.orig.tsx +204 -0
  9. package/src/App.orig1.tsx +27 -0
  10. package/src/App.orig2.tsx +25 -0
  11. package/src/App.tsx +3 -0
  12. package/src/BaseApp.tsx +18 -0
  13. package/src/INTEGRATION_LEDGER.md +22 -0
  14. package/src/PERSIST_CANARY.orig.txt +1 -0
  15. package/src/PERSIST_CANARY.txt +1 -0
  16. package/src/REGRESSION_LEDGER.md +651 -0
  17. package/src/TEMPLATE_PIPELINE_AUDIT.md +208 -0
  18. package/src/WILLIAMS_PERSONA_GUIDE.md +62 -0
  19. package/src/chat/VeritasChat.tsx +69 -0
  20. package/src/chat/synthesis.ts +31 -0
  21. package/src/chat/tier.ts +45 -0
  22. package/src/components/AdaptersPage.orig.tsx +99 -0
  23. package/src/components/AdaptersPage.tsx +3 -0
  24. package/src/components/AdversarialPanel.tsx +136 -0
  25. package/src/components/AnchorBaselinePanel.tsx +75 -0
  26. package/src/components/AnswerPanel.tsx +60 -0
  27. package/src/components/ArtifactPanel.tsx +43 -0
  28. package/src/components/ChatApp.orig.tsx +1582 -0
  29. package/src/components/ChatApp.orig1.tsx +722 -0
  30. package/src/components/ChatApp.tsx +95 -0
  31. package/src/components/ChatAugmentPanels.tsx +73 -0
  32. package/src/components/CitationLedgerPanel.tsx +313 -0
  33. package/src/components/ComputeSandboxPanel.tsx +130 -0
  34. package/src/components/ConfigPanel.tsx +110 -0
  35. package/src/components/ControlPlanePage.tsx +195 -0
  36. package/src/components/CreativeTreeLifePage.tsx +234 -0
  37. package/src/components/CreativeTreeOfLifePanel.tsx +149 -0
  38. package/src/components/DebugTracePanel.tsx +137 -0
  39. package/src/components/DeepReasoningTrace.tsx +48 -0
  40. package/src/components/EntitySheetPanel.tsx +51 -0
  41. package/src/components/FailureModesPanel.tsx +104 -0
  42. package/src/components/GBSDashboard.tsx +239 -0
  43. package/src/components/GraphView.tsx +116 -0
  44. package/src/components/HUD.tsx +125 -0
  45. package/src/components/HypothesisPanel.tsx +67 -0
  46. package/src/components/InnovationGenomeEngine.tsx +792 -0
  47. package/src/components/InnovationPersonaGuide.tsx +348 -0
  48. package/src/components/InnovationPersonaPanel.tsx +289 -0
  49. package/src/components/LiveResourceHUD.tsx +109 -0
  50. package/src/components/LongWriterPanel.tsx +112 -0
  51. package/src/components/MainPipelineV10Bridge.tsx +72 -0
  52. package/src/components/MarkdownLite.tsx +63 -0
  53. package/src/components/MemoryInspector.tsx +38 -0
  54. package/src/components/MemoryMonitor.tsx +52 -0
  55. package/src/components/MemoryStressPanel.tsx +176 -0
  56. package/src/components/ModulesPage.tsx +171 -0
  57. package/src/components/PipelineDebugConsole.orig.tsx +1856 -0
  58. package/src/components/PipelineDebugConsole.tsx +3 -0
  59. package/src/components/PreFlightHUD.tsx +67 -0
  60. package/src/components/PrismaFetchTracePanel.tsx +87 -0
  61. package/src/components/ReportOSPanel.tsx +165 -0
  62. package/src/components/ResourceEstimatorPage.tsx +327 -0
  63. package/src/components/RichText.tsx +181 -0
  64. package/src/components/SagaOsPanel.tsx +605 -0
  65. package/src/components/SagaOsReference.tsx +421 -0
  66. package/src/components/SagaOsV2Panel.tsx +594 -0
  67. package/src/components/SagaStyleTab.tsx +382 -0
  68. package/src/components/SageBlueprintPanel.tsx +416 -0
  69. package/src/components/SharedChatInput.tsx +77 -0
  70. package/src/components/StatFinancePanel.tsx +190 -0
  71. package/src/components/StylePersonaPanel.tsx +128 -0
  72. package/src/components/TemplatesPage.tsx +300 -0
  73. package/src/components/TestPanel.tsx +63 -0
  74. package/src/components/TraceLog.tsx +89 -0
  75. package/src/components/V15CalibrationAugment.tsx +1134 -0
  76. package/src/components/V15CalibrationDialog.tsx +1871 -0
  77. package/src/components/V15Overlay.orig.tsx +120 -0
  78. package/src/components/V15Overlay.tsx +4 -0
  79. package/src/components/V15OverlayWrapper.tsx +362 -0
  80. package/src/components/V15Toggle.tsx +34 -0
  81. package/src/console/DiagnosticRecoveryPanel.tsx +358 -0
  82. package/src/console/EngineeringConsole.tsx +103 -0
  83. package/src/console/FictionSystemPanels.tsx +882 -0
  84. package/src/console/InspectorPanels.tsx +254 -0
  85. package/src/console/ManuscriptGraderPanel.tsx +182 -0
  86. package/src/console/RecursiveReviserPanel.tsx +245 -0
  87. package/src/console/SeedControlPanel.tsx +135 -0
  88. package/src/console/StoryControllerPanel.tsx +443 -0
  89. package/src/console/engineBridge.ts +258 -0
  90. package/src/console/gemini.ts +106 -0
  91. package/src/console/grading.ts +250 -0
  92. package/src/console/util.ts +45 -0
  93. package/src/fiction/continuity.ts +372 -0
  94. package/src/fiction/crash-protection.ts +111 -0
  95. package/src/fiction/diagnostics.ts +208 -0
  96. package/src/fiction/diff.ts +74 -0
  97. package/src/fiction/entity-state-machine.ts +214 -0
  98. package/src/fiction/nive-engine.ts +404 -0
  99. package/src/fiction/pipeline-selftest.ts +132 -0
  100. package/src/fiction/seed-control.ts +193 -0
  101. package/src/fiction/story-engine.ts +326 -0
  102. package/src/fiction/types.ts +68 -0
  103. package/src/flatten-guide.md +379 -0
  104. package/src/index.css +14 -0
  105. package/src/index.orig.css +13 -0
  106. package/src/lib/academic-sources.base.ts +188 -0
  107. package/src/lib/academic-sources.orig.ts +28 -0
  108. package/src/lib/academic-sources.ts +10 -0
  109. package/src/lib/advanced-math.ts +119 -0
  110. package/src/lib/adversarial-engine.base.ts +409 -0
  111. package/src/lib/adversarial-engine.ts +82 -0
  112. package/src/lib/ai-revision.ts +623 -0
  113. package/src/lib/app-state.tsx +426 -0
  114. package/src/lib/artifact-registry.ts +97 -0
  115. package/src/lib/artifacts.ts +180 -0
  116. package/src/lib/atlas-dr.ts +167 -0
  117. package/src/lib/browser-mtls.ts +127 -0
  118. package/src/lib/browser-queue.ts +221 -0
  119. package/src/lib/browser-search-scraper.base.ts +382 -0
  120. package/src/lib/browser-search-scraper.orig.ts +90 -0
  121. package/src/lib/browser-search-scraper.ts +10 -0
  122. package/src/lib/calc-interceptor.ts +82 -0
  123. package/src/lib/citation-lane-tap.ts +78 -0
  124. package/src/lib/citation-ledger-store.ts +369 -0
  125. package/src/lib/citation-ledger.ts +203 -0
  126. package/src/lib/compute-sandbox.ts +215 -0
  127. package/src/lib/compute.worker.ts +153 -0
  128. package/src/lib/connectors/gemini.ts +120 -0
  129. package/src/lib/connectors/jina.ts +233 -0
  130. package/src/lib/connectors/marketdata.ts +51 -0
  131. package/src/lib/connectors/prismafetch.base.ts +175 -0
  132. package/src/lib/connectors/prismafetch.ts +1 -0
  133. package/src/lib/connectors/serpapi.ts +65 -0
  134. package/src/lib/connectors/wikidata.ts +44 -0
  135. package/src/lib/constraints.ts +331 -0
  136. package/src/lib/continuation-detector.base.ts +153 -0
  137. package/src/lib/continuation-detector.ts +48 -0
  138. package/src/lib/contradraft.ts +168 -0
  139. package/src/lib/cors-proxy.ts +120 -0
  140. package/src/lib/coverage.ts +81 -0
  141. package/src/lib/debug/architecture-prescription.ts +556 -0
  142. package/src/lib/debug/covea-repair.ts +543 -0
  143. package/src/lib/debug/helios-ground.ts +319 -0
  144. package/src/lib/debug/intent-decomposer.ts +281 -0
  145. package/src/lib/debug/intent-lattice.ts +349 -0
  146. package/src/lib/debug/pipeline-diagnosis.ts +1149 -0
  147. package/src/lib/debug/pipeline-trace-bus.ts +424 -0
  148. package/src/lib/debug/prompt-forge.ts +678 -0
  149. package/src/lib/debug/repair-sites.ts +513 -0
  150. package/src/lib/debug/research-phase.ts +266 -0
  151. package/src/lib/debug/retrieval-context.ts +76 -0
  152. package/src/lib/debug/retrieval-hardener.ts +142 -0
  153. package/src/lib/debug/scraper-debug-runner.ts +576 -0
  154. package/src/lib/debug/scraper-forensics.ts +418 -0
  155. package/src/lib/debug/scraper-lane-roadmap.ts +136 -0
  156. package/src/lib/debug/self-test.ts +415 -0
  157. package/src/lib/debug/step-attribution.ts +211 -0
  158. package/src/lib/debug/template-rubric.ts +301 -0
  159. package/src/lib/debug/unified-innovation.ts +126 -0
  160. package/src/lib/debug/veritas-hybrid-scraper.ts +335 -0
  161. package/src/lib/defense-registry.ts +104 -0
  162. package/src/lib/defenses.ts +162 -0
  163. package/src/lib/deterministic-citation-ledger.ts +174 -0
  164. package/src/lib/elo-registry.base.ts +117 -0
  165. package/src/lib/elo-registry.ts +1 -0
  166. package/src/lib/entity-resolver.ts +291 -0
  167. package/src/lib/failure-modes.ts +267 -0
  168. package/src/lib/feature-registry.ts +69 -0
  169. package/src/lib/flaw-registry.ts +143 -0
  170. package/src/lib/flaws/_template.ts +52 -0
  171. package/src/lib/flaws/builtins.ts +63 -0
  172. package/src/lib/flaws/finance.ts +169 -0
  173. package/src/lib/flaws/fixers.ts +10 -0
  174. package/src/lib/flaws/index.ts +32 -0
  175. package/src/lib/flaws/legal.ts +157 -0
  176. package/src/lib/flaws/medical.ts +188 -0
  177. package/src/lib/flaws/original-defenses-pack.ts +81 -0
  178. package/src/lib/flaws/sample-declarative-pack.json +38 -0
  179. package/src/lib/flaws/selftest.ts +201 -0
  180. package/src/lib/flaws/software-extended.ts +125 -0
  181. package/src/lib/flaws/software-rn-webgl.ts +182 -0
  182. package/src/lib/flaws/statistics-advanced.ts +46 -0
  183. package/src/lib/flaws/statistics.ts +61 -0
  184. package/src/lib/gbse/config.ts +20 -0
  185. package/src/lib/gbse/engine.ts +226 -0
  186. package/src/lib/gbse/graph.ts +118 -0
  187. package/src/lib/gbse/graph.worker.ts +21 -0
  188. package/src/lib/gbse/tests.ts +117 -0
  189. package/src/lib/gbse/tiers.ts +65 -0
  190. package/src/lib/gbse/types.ts +94 -0
  191. package/src/lib/innovation-genome-engine-v2.orig.ts +1001 -0
  192. package/src/lib/innovation-genome-engine-v2.ts +3 -0
  193. package/src/lib/innovation-genome-engine.orig.ts +330 -0
  194. package/src/lib/innovation-genome-engine.ts +3 -0
  195. package/src/lib/innovation-genome-v10.orig.ts +314 -0
  196. package/src/lib/innovation-genome-v10.ts +2 -0
  197. package/src/lib/innovation-genome-v3.orig.ts +2015 -0
  198. package/src/lib/innovation-genome-v3.ts +2 -0
  199. package/src/lib/innovation-genome-v4.orig.ts +1959 -0
  200. package/src/lib/innovation-genome-v4.ts +2 -0
  201. package/src/lib/innovation-genome-v5.orig.ts +1210 -0
  202. package/src/lib/innovation-genome-v5.ts +2 -0
  203. package/src/lib/innovation-genome-v7.orig.ts +2549 -0
  204. package/src/lib/innovation-genome-v7.ts +2 -0
  205. package/src/lib/innovation-genome-v8.orig.ts +1485 -0
  206. package/src/lib/innovation-genome-v8.ts +2 -0
  207. package/src/lib/innovation-genome-v9.orig.ts +643 -0
  208. package/src/lib/innovation-genome-v9.ts +2 -0
  209. package/src/lib/jina.base.ts +293 -0
  210. package/src/lib/jina.ts +1 -0
  211. package/src/lib/live-telemetry.ts +61 -0
  212. package/src/lib/longwriter.ts +84 -0
  213. package/src/lib/manuscript-grade-v2.ts +298 -0
  214. package/src/lib/memory-governor.ts +173 -0
  215. package/src/lib/memory-stress-tests.ts +248 -0
  216. package/src/lib/model-intelligence.ts +48 -0
  217. package/src/lib/model-rotator.ts +75 -0
  218. package/src/lib/models.orig.ts +513 -0
  219. package/src/lib/models.ts +151 -0
  220. package/src/lib/n-deep.base.ts +592 -0
  221. package/src/lib/n-deep.ts +51 -0
  222. package/src/lib/nih-simulator.ts +56 -0
  223. package/src/lib/nih-vulnerability-fixes.ts +460 -0
  224. package/src/lib/omega-templates.base.ts +294 -0
  225. package/src/lib/omega-templates.ts +225 -0
  226. package/src/lib/omni-nexus.ts +64 -0
  227. package/src/lib/oracle-adapters.ts +67 -0
  228. package/src/lib/oracle-registry.ts +163 -0
  229. package/src/lib/orchestrator.ts +357 -0
  230. package/src/lib/output-boundary.ts +235 -0
  231. package/src/lib/overrides/vite-native-chaos-harness.ts +154 -0
  232. package/src/lib/overrides/vite-native-contract-plane.ts +245 -0
  233. package/src/lib/overrides/vite-native-doctor-plane.ts +208 -0
  234. package/src/lib/overrides/vite-native-knowledge-store.ts +267 -0
  235. package/src/lib/overrides/vite-native-policy-plane.ts +190 -0
  236. package/src/lib/overrides/vite-native-replay-plane.ts +197 -0
  237. package/src/lib/overrides/vite-native-runtime-plane.ts +106 -0
  238. package/src/lib/overrides/vite-native-scraper.ts +848 -0
  239. package/src/lib/overrides/vite-native-selftest-functional.ts +217 -0
  240. package/src/lib/overrides/vite-native-selftest.ts +183 -0
  241. package/src/lib/overrides/vite-native-snapshot-plane.ts +123 -0
  242. package/src/lib/philosophy-toolkit.ts +75 -0
  243. package/src/lib/pipeline.orig.ts +659 -0
  244. package/src/lib/pipeline.ts +42 -0
  245. package/src/lib/precache.ts +128 -0
  246. package/src/lib/py-sandbox.ts +205 -0
  247. package/src/lib/quality-score.ts +96 -0
  248. package/src/lib/quant-engine.ts +231 -0
  249. package/src/lib/quant-lib.ts +80 -0
  250. package/src/lib/reportos.ts +175 -0
  251. package/src/lib/research-os.ts +306 -0
  252. package/src/lib/resource-estimator.ts +114 -0
  253. package/src/lib/rpm-governor.ts +93 -0
  254. package/src/lib/runtime-estimator.ts +135 -0
  255. package/src/lib/sage/empty-space-v6.ts +560 -0
  256. package/src/lib/sage/empty-space-v7.ts +222 -0
  257. package/src/lib/sage/empty-space.ts +733 -0
  258. package/src/lib/sage/engine.ts +786 -0
  259. package/src/lib/sage/index.ts +17 -0
  260. package/src/lib/sage/judge.ts +246 -0
  261. package/src/lib/sage/prng.ts +53 -0
  262. package/src/lib/sage/reality-grounding.ts +380 -0
  263. package/src/lib/sage/repetition.ts +151 -0
  264. package/src/lib/sage/saga-os-v2-engines.ts +1870 -0
  265. package/src/lib/sage/saga-os-v2.ts +274 -0
  266. package/src/lib/sage/saga-os-v3-core.ts +1491 -0
  267. package/src/lib/sage/saga-os-v3-release.ts +739 -0
  268. package/src/lib/sage/saga-os-v4-aureate.ts +1161 -0
  269. package/src/lib/sage/saga-os-v4.ts +212 -0
  270. package/src/lib/sage/saga-os-v5.ts +205 -0
  271. package/src/lib/sage/saga-os-v6.ts +346 -0
  272. package/src/lib/sage/saga-os-v7.ts +314 -0
  273. package/src/lib/sage/saga-os-v8.ts +505 -0
  274. package/src/lib/sage/saga-os.ts +329 -0
  275. package/src/lib/sage/taxonomy.ts +403 -0
  276. package/src/lib/sage/unified-roll.ts +524 -0
  277. package/src/lib/sage/worked-sketches.ts +44 -0
  278. package/src/lib/sage.ts +166 -0
  279. package/src/lib/scraper-debug-bus.ts +53 -0
  280. package/src/lib/scraper-enhanced.orig.ts +153 -0
  281. package/src/lib/scraper-enhanced.ts +10 -0
  282. package/src/lib/scraper-hardener.base.orig.ts +155 -0
  283. package/src/lib/scraper-hardener.base.ts +317 -0
  284. package/src/lib/scraper-hardener.ts +1 -0
  285. package/src/lib/scraper-palisade/palisade-adjudicator.orig.ts +419 -0
  286. package/src/lib/scraper-palisade/palisade-adjudicator.ts +13 -0
  287. package/src/lib/scraper-vnext/arbiter-omega.orig.ts +688 -0
  288. package/src/lib/scraper-vnext/arbiter-omega.ts +13 -0
  289. package/src/lib/scraper-vnext/canonical-portfolio-augments.ts +479 -0
  290. package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.orig.ts +1529 -0
  291. package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.ts +13 -0
  292. package/src/lib/scraper-vnext/conclave-omega.ts +1830 -0
  293. package/src/lib/scraper-vnext/content-extractor-v2.ts +226 -0
  294. package/src/lib/scraper-vnext/diagnostics-suite.ts +36 -0
  295. package/src/lib/scraper-vnext/epistemic-packer.ts +97 -0
  296. package/src/lib/scraper-vnext/fusion-v2.ts +65 -0
  297. package/src/lib/scraper-vnext/hydra-reader.orig.ts +1216 -0
  298. package/src/lib/scraper-vnext/hydra-reader.ts +13 -0
  299. package/src/lib/scraper-vnext/native-scraper-browser-vnext.orig.ts +91 -0
  300. package/src/lib/scraper-vnext/native-scraper-browser-vnext.ts +13 -0
  301. package/src/lib/scraper-vnext/nexus-consensus.orig.ts +905 -0
  302. package/src/lib/scraper-vnext/nexus-consensus.ts +13 -0
  303. package/src/lib/scraper-vnext/portfolio-consensus-adjudicator.ts +804 -0
  304. package/src/lib/scraper-vnext/portfolio-consensus-memory.ts +377 -0
  305. package/src/lib/scraper-vnext/portfolio-terminal-governor.ts +548 -0
  306. package/src/lib/scraper-vnext/query-strategist.orig.ts +224 -0
  307. package/src/lib/scraper-vnext/query-strategist.ts +423 -0
  308. package/src/lib/scraper-vnext/retrieval-accelerator.ts +708 -0
  309. package/src/lib/scraper-vnext/retrieval-audit-augments.ts +477 -0
  310. package/src/lib/scraper-vnext/retrieval-control-plane.ts +904 -0
  311. package/src/lib/scraper-vnext/retrieval-policy-augments.ts +358 -0
  312. package/src/lib/scraper-vnext/safe-fetch-v2.ts +157 -0
  313. package/src/lib/scraper-vnext/sentinel-omega.ts +56 -0
  314. package/src/lib/scraper-vnext/sentinel-orchestrator.ts +69 -0
  315. package/src/lib/scraper-vnext/sibyl-oracle.orig.ts +413 -0
  316. package/src/lib/scraper-vnext/sibyl-oracle.ts +13 -0
  317. package/src/lib/scraper-vnext/smart-read-v2.ts +55 -0
  318. package/src/lib/scraper-vnext/spa-rescue-bridge.ts +608 -0
  319. package/src/lib/scraper-vnext/strata-engine.orig.ts +2666 -0
  320. package/src/lib/scraper-vnext/strata-engine.ts +13 -0
  321. package/src/lib/scraper-vnext/structured-source-adapter.orig.ts +369 -0
  322. package/src/lib/scraper-vnext/structured-source-adapter.ts +34 -0
  323. package/src/lib/scraper-vnext/terminal-complete.ts +224 -0
  324. package/src/lib/scraper-vnext/terminal-final.ts +277 -0
  325. package/src/lib/scraper-vnext/terminal-saturation.ts +345 -0
  326. package/src/lib/scraper-vnext/terminal-wire.ts +425 -0
  327. package/src/lib/scraper-vnext/vanguard-titanium.orig.ts +467 -0
  328. package/src/lib/scraper-vnext/vanguard-titanium.ts +17 -0
  329. package/src/lib/search-cache.ts +138 -0
  330. package/src/lib/sloop-runner.base.ts +223 -0
  331. package/src/lib/sloop-runner.ts +104 -0
  332. package/src/lib/sloop.ts +114 -0
  333. package/src/lib/small-model-adapters.ts +82 -0
  334. package/src/lib/sscp.ts +86 -0
  335. package/src/lib/system-map.ts +54 -0
  336. package/src/lib/uni-node.ts +86 -0
  337. package/src/lib/universal-rigor-guard.ts +433 -0
  338. package/src/lib/v15-gate-testbed.ts +274 -0
  339. package/src/lib/v15-gemini.base.ts +89 -0
  340. package/src/lib/v15-gemini.ts +1 -0
  341. package/src/lib/v15-grounding.orig.ts +241 -0
  342. package/src/lib/v15-grounding.ts +79 -0
  343. package/src/lib/v15-pipeline.base.ts +769 -0
  344. package/src/lib/v15-pipeline.orig.ts +1154 -0
  345. package/src/lib/v15-pipeline.ts +674 -0
  346. package/src/lib/v15-questions.ts +64 -0
  347. package/src/lib/v15-rate-limiter.orig.ts +91 -0
  348. package/src/lib/v15-rate-limiter.ts +48 -0
  349. package/src/lib/v15-state.base.ts +87 -0
  350. package/src/lib/v15-state.ts +28 -0
  351. package/src/lib/visual-table-generator.tsx +207 -0
  352. package/src/lib/wasm-runtime.ts +133 -0
  353. package/src/lib/williams-persona-guide.ts +265 -0
  354. package/src/lib/williams-style.base.ts +464 -0
  355. package/src/lib/williams-style.ts +59 -0
  356. package/src/lib/worker-pool.ts +96 -0
  357. package/src/lib/writing-tiers.ts +125 -0
  358. package/src/main.orig.tsx +10 -0
  359. package/src/main.tsx +10 -0
  360. package/src/utils/cn.orig.ts +6 -0
  361. package/src/utils/cn.ts +6 -0
  362. package/src/vite-env.d.ts +1 -0
  363. package/tsconfig.json +31 -0
  364. package/unify.mjs +537 -0
  365. package/vite.config.ts +18 -0
@@ -0,0 +1,358 @@
1
+ /**
2
+ * retrieval-policy-augments.ts
3
+ * ============================================================================
4
+ * ADDITIVE companion to canonical retrieval-control-plane.ts. Fills the exact
5
+ * three sockets that canonical declares-but-does-not-implement, reimplementing
6
+ * NONE of its racing / crawling / discovery / hedging / caching machinery.
7
+ *
8
+ * SOCKET 1 — robotsDecision plug.
9
+ * crawl() accepts `robotsDecision?: (url, signal) => Promise<
10
+ * "allow" | "deny" | "unknown">` and honors robotsMode, but ships no
11
+ * default implementation. makeRobotsDecision() returns EXACTLY that
12
+ * function, RFC-9309 based, caching via canonical cachedValue() (24h).
13
+ *
14
+ * SOCKET 2 — challenge/SPA gate.
15
+ * canonical would accept a Cloudflare/Turnstile interstitial (HTTP 200)
16
+ * as real content. withChallengeGate() wraps ANY CrawlPayload reader so a
17
+ * detected hard challenge THROWS — turning silent garbage acceptance into
18
+ * a clean failure that canonical's HostGovernor circuit-breaker + hedging
19
+ * naturally route around instead of silently swallowing garbage.
20
+ *
21
+ * SOCKET 3 — value-aware priority.
22
+ * crawl() assigns seeds a flat priority=100. domainPriority() scores by
23
+ * domain reputation so a caller can order seeds highest-evidence-first;
24
+ * if a run is cancelled or time-budgeted, the collected subset is the
25
+ * BEST subset, not merely the first-arrived one. Pure scorer + seed
26
+ * reorderer — composes at the call site, touches no crawl internals.
27
+ *
28
+ * REUSES canonical cachedValue() for robots TTL — zero new persistence.
29
+ * NO trust score is altered. NO racing/crawl/discovery is reimplemented.
30
+ *
31
+ * HONEST CEILING (reaffirmed, not worked around): fetch() cannot set
32
+ * User-Agent (Chrome drops custom values), Accept-Encoding (forbidden
33
+ * request header — silent no-op), or Origin/Referer/Host/Sec-*.
34
+ * Browser-identity spoofing is impossible from a static page and is not
35
+ * attempted. "Detection avoidance" here = recognition + politeness only.
36
+ *
37
+ * Not executed in this environment.
38
+ * ============================================================================ */
39
+
40
+ import { cachedValue } from "./retrieval-accelerator";
41
+ import type {
42
+ ScheduleContext,
43
+ RetrievalPolicy,
44
+ CrawlPayload,
45
+ } from "./retrieval-control-plane";
46
+
47
+ // ═══════════════════════════════════════════════════════════════════════════
48
+ // SOCKET 2 — BOT-CHALLENGE / SPA-SHELL DETECTION
49
+ // ============================================================================
50
+
51
+ export type BlockKind =
52
+ | "none"
53
+ | "cloudflare-challenge"
54
+ | "cloudflare-turnstile"
55
+ | "captcha"
56
+ | "spa-shell";
57
+
58
+ export interface BlockDetection {
59
+ blocked: boolean;
60
+ kind: BlockKind;
61
+ softShell: boolean;
62
+ markers: string[];
63
+ }
64
+
65
+ const CHALLENGE_MARKERS: RegExp[] = [
66
+ /checking your browser before accessing/i,
67
+ /please stand by, while we are checking your browser/i,
68
+ /cf-browser-verification/i,
69
+ /cf_chl_opt/i,
70
+ /__cf_chl_/i,
71
+ /just a moment/i,
72
+ ];
73
+ const TURNSTILE_MARKERS: RegExp[] = [
74
+ /cf-turnstile/i,
75
+ /challenges\.cloudflare\.com\/turnstile/i,
76
+ /cdn-cgi\/challenge-platform/i,
77
+ ];
78
+ const CAPTCHA_MARKERS: RegExp[] = [
79
+ /www\.google\.com\/recaptcha/i,
80
+ /\bg-recaptcha\b/i,
81
+ /\bhcaptcha\.com\b/i,
82
+ /verify you are human/i,
83
+ ];
84
+ const SPA_SHELL_MARKERS: RegExp[] = [
85
+ /<div\s+id=["'](?:app|root|__next|__nuxt)["']\s*>\s*<\/div>/i,
86
+ /__NEXT_DATA__[^<]*"props"\s*:\s*\{\s*\}/i,
87
+ ];
88
+
89
+ export function detectPageBlock(html: string): BlockDetection {
90
+ const sample = (html || "").slice(0, 60_000);
91
+ const markers: string[] = [];
92
+
93
+ for (const re of CHALLENGE_MARKERS) if (re.test(sample)) markers.push(re.source);
94
+ if (markers.length > 0) {
95
+ return { blocked: true, kind: "cloudflare-challenge", softShell: false, markers };
96
+ }
97
+ for (const re of TURNSTILE_MARKERS) if (re.test(sample)) markers.push(re.source);
98
+ if (markers.length > 0) {
99
+ return { blocked: true, kind: "cloudflare-turnstile", softShell: false, markers };
100
+ }
101
+ for (const re of CAPTCHA_MARKERS) if (re.test(sample)) markers.push(re.source);
102
+ if (markers.length > 0) {
103
+ return { blocked: true, kind: "captcha", softShell: false, markers };
104
+ }
105
+
106
+ let spa = false;
107
+ for (const re of SPA_SHELL_MARKERS) if (re.test(sample)) { spa = true; markers.push(re.source); }
108
+ if (!spa && html.length > 4000) {
109
+ const textOnly = html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
110
+ if (textOnly.length < 200) { spa = true; markers.push("low-text-ratio"); }
111
+ }
112
+ if (spa) return { blocked: false, kind: "spa-shell", softShell: true, markers };
113
+
114
+ return { blocked: false, kind: "none", softShell: false, markers: [] };
115
+ }
116
+
117
+ export function withChallengeGate<T>(
118
+ reader: (url: string, ctx: ScheduleContext, policy: RetrievalPolicy) => Promise<CrawlPayload<T>>,
119
+ onBlock?: (url: string, detection: BlockDetection) => void,
120
+ ): (url: string, ctx: ScheduleContext, policy: RetrievalPolicy) => Promise<CrawlPayload<T>> {
121
+ return async (url, ctx, policy) => {
122
+ const payload = await reader(url, ctx, policy);
123
+ const detection = detectPageBlock(payload.text || "");
124
+ if (detection.blocked) {
125
+ onBlock?.(url, detection);
126
+ throw new Error(`challenge_detected:${detection.kind}`);
127
+ }
128
+ return payload;
129
+ };
130
+ }
131
+
132
+ // ═══════════════════════════════════════════════════════════════════════════
133
+ // SOCKET 1 — RFC-9309 ROBOTS.TXT (plugs canonical's robotsDecision seam)
134
+ // ============================================================================
135
+
136
+ const ROBOTS_TTL_MS = 24 * 60 * 60 * 1000;
137
+ const ROBOTS_MAX_BYTES = 512 * 1024;
138
+
139
+ function parseRobotsTxt(text: string): Array<{ allow: boolean; path: string }> {
140
+ const rules: Array<{ allow: boolean; path: string }> = [];
141
+ let inWildcard = false;
142
+ let sawUA = false;
143
+ for (const raw of text.split(/\r?\n/)) {
144
+ const line = raw.replace(/#.*$/, "").trim();
145
+ if (!line) continue;
146
+ const idx = line.indexOf(":");
147
+ if (idx < 0) continue;
148
+ const key = line.slice(0, idx).trim().toLowerCase();
149
+ const value = line.slice(idx + 1).trim();
150
+ if (key === "user-agent") { sawUA = true; inWildcard = value === "*"; continue; }
151
+ if (!sawUA || !inWildcard) continue;
152
+ if (key === "disallow" && value) rules.push({ allow: false, path: value });
153
+ else if (key === "allow" && value) rules.push({ allow: true, path: value });
154
+ }
155
+ return rules;
156
+ }
157
+
158
+ function matchesRule(path: string, rulePath: string): boolean {
159
+ if (!rulePath) return false;
160
+ if (rulePath.endsWith("$")) return path === rulePath.slice(0, -1);
161
+ return path.startsWith(rulePath);
162
+ }
163
+
164
+ export interface RobotsAdvisory {
165
+ decision: "allow" | "deny" | "unknown";
166
+ checkedPath: string;
167
+ matchedRule?: string;
168
+ }
169
+
170
+ async function robotsRulesFor(origin: string, signal?: AbortSignal) {
171
+ const { value } = await cachedValue<Array<{ allow: boolean; path: string }>>(
172
+ `rcp-robots\u0000${origin}`,
173
+ ROBOTS_TTL_MS,
174
+ async () => {
175
+ try {
176
+ const ctrl = new AbortController();
177
+ const timer = setTimeout(() => ctrl.abort(), 4000);
178
+ signal?.addEventListener("abort", () => ctrl.abort(), { once: true });
179
+ const res = await fetch(`${origin}/robots.txt`, {
180
+ signal: ctrl.signal, credentials: "omit", referrerPolicy: "no-referrer",
181
+ });
182
+ clearTimeout(timer);
183
+ return res.ok ? parseRobotsTxt((await res.text()).slice(0, ROBOTS_MAX_BYTES)) : [];
184
+ } catch {
185
+ return [];
186
+ }
187
+ },
188
+ );
189
+ return value;
190
+ }
191
+
192
+ export async function checkRobotsAdvisory(
193
+ targetUrl: string,
194
+ signal?: AbortSignal,
195
+ ): Promise<RobotsAdvisory> {
196
+ let origin: string, path: string;
197
+ try {
198
+ const u = new URL(targetUrl);
199
+ origin = u.origin;
200
+ path = u.pathname + u.search;
201
+ } catch {
202
+ return { decision: "unknown", checkedPath: targetUrl };
203
+ }
204
+ const rules = await robotsRulesFor(origin, signal);
205
+ let best: { allow: boolean; path: string } | null = null;
206
+ for (const r of rules) {
207
+ if (matchesRule(path, r.path) && (!best || r.path.length > best.path.length)) best = r;
208
+ }
209
+ const decision: "allow" | "deny" = best ? (best.allow ? "allow" : "deny") : "allow";
210
+ return { decision, checkedPath: path, matchedRule: best?.path };
211
+ }
212
+
213
+ export function makeRobotsDecision(): (
214
+ url: string,
215
+ signal?: AbortSignal,
216
+ ) => Promise<"allow" | "deny" | "unknown"> {
217
+ return async (url, signal) => (await checkRobotsAdvisory(url, signal)).decision;
218
+ }
219
+
220
+ // ═══════════════════════════════════════════════════════════════════════════
221
+ // SOCKET 3 — VALUE-AWARE PRIORITY
222
+ // ============================================================================
223
+
224
+ export function assertPublicUrl(raw: string): URL {
225
+ if (!raw || typeof raw !== "string" || !raw.trim()) throw new TypeError("URL empty");
226
+ if (raw.length > 4096) throw new TypeError("URL too long");
227
+ let u: URL;
228
+ try { u = new URL(raw); } catch { throw new TypeError("URL invalid"); }
229
+ if (u.protocol !== "http:" && u.protocol !== "https:") throw new TypeError("scheme disallowed");
230
+ if (u.username || u.password) throw new TypeError("credentials disallowed");
231
+ const h = u.hostname.toLowerCase();
232
+ const BLOCKED = new Set(["localhost","localhost.localdomain","metadata.google.internal","metadata.amazonaws.com","metadata.azure.com"]);
233
+ if (BLOCKED.has(h) || h.endsWith(".localhost") || h.endsWith(".local") || h.endsWith(".internal")) throw new TypeError("target disallowed");
234
+ u.hash = "";
235
+ return u;
236
+ }
237
+
238
+ const DOMAIN_PRIORITY: Record<string, { priority: number; tier: string }> = {
239
+ "arxiv.org": { priority: 0.95, tier: "academic" },
240
+ "doi.org": { priority: 0.95, tier: "academic" },
241
+ "nih.gov": { priority: 0.95, tier: "gov" },
242
+ "cdc.gov": { priority: 0.93, tier: "gov" },
243
+ "who.int": { priority: 0.93, tier: "gov" },
244
+ "pubmed.ncbi.nlm.nih.gov": { priority: 0.93, tier: "academic" },
245
+ "nature.com": { priority: 0.92, tier: "academic" },
246
+ "science.org": { priority: 0.92, tier: "academic" },
247
+ "ieee.org": { priority: 0.90, tier: "academic" },
248
+ "acm.org": { priority: 0.90, tier: "academic" },
249
+ "openalex.org": { priority: 0.88, tier: "academic" },
250
+ "crossref.org": { priority: 0.88, tier: "academic" },
251
+ "semanticscholar.org": { priority: 0.87, tier: "academic" },
252
+ "wikipedia.org": { priority: 0.85, tier: "reference" },
253
+ "reuters.com": { priority: 0.82, tier: "news" },
254
+ "apnews.com": { priority: 0.82, tier: "news" },
255
+ "bbc.com": { priority: 0.78, tier: "news" },
256
+ "bloomberg.com": { priority: 0.78, tier: "news" },
257
+ "archive.org": { priority: 0.75, tier: "reference" },
258
+ "github.com": { priority: 0.65, tier: "general" },
259
+ "stackoverflow.com": { priority: 0.60, tier: "general" },
260
+ "medium.com": { priority: 0.40, tier: "low" },
261
+ "substack.com": { priority: 0.40, tier: "low" },
262
+ "reddit.com": { priority: 0.35, tier: "low" },
263
+ };
264
+
265
+ function hostOf(url: string): string {
266
+ try { return new URL(url).hostname.toLowerCase().replace(/^www\./, ""); }
267
+ catch { return ""; }
268
+ }
269
+
270
+ export interface DomainScore { url: string; priority: number; tier: string; }
271
+
272
+ export function domainPriority(url: string): DomainScore {
273
+ const host = hostOf(url);
274
+ let entry = DOMAIN_PRIORITY[host];
275
+ if (!entry) {
276
+ for (const [suffix, v] of Object.entries(DOMAIN_PRIORITY)) {
277
+ if (host === suffix || host.endsWith("." + suffix)) { entry = v; break; }
278
+ }
279
+ }
280
+ if (!entry) {
281
+ const tld = host.split(".").pop() || "";
282
+ if (tld === "gov") entry = { priority: 0.80, tier: "gov" };
283
+ else if (tld === "edu") entry = { priority: 0.75, tier: "academic" };
284
+ else if (tld === "int") entry = { priority: 0.78, tier: "gov" };
285
+ else if (tld === "org") entry = { priority: 0.55, tier: "general" };
286
+ else entry = { priority: 0.45, tier: "general" };
287
+ }
288
+ return { url, priority: entry.priority, tier: entry.tier };
289
+ }
290
+
291
+ export function prioritizeSeeds(urls: string[]): string[] {
292
+ return urls
293
+ .map(domainPriority)
294
+ .sort((a, b) => b.priority - a.priority || a.url.localeCompare(b.url))
295
+ .map((s) => s.url);
296
+ }
297
+
298
+ // ═══════════════════════════════════════════════════════════════════════════
299
+ // DIAGNOSTICS
300
+ // ═══════════════════════════════════════════════════════════════════════════
301
+
302
+ export async function runPolicyAugmentDiagnostics(): Promise<{
303
+ ok: boolean; checks: Array<{ id: string; passed: boolean; detail: string }>;
304
+ }> {
305
+ const checks: Array<{ id: string; passed: boolean; detail: string }> = [];
306
+ const add = (id: string, p: boolean, d: string) => checks.push({ id, passed: p, detail: d });
307
+
308
+ const cf = detectPageBlock("<html><body>Please stand by, while we are checking your browser...</body></html>");
309
+ add("detect-cf-challenge", cf.blocked && cf.kind === "cloudflare-challenge", cf.kind);
310
+
311
+ const ts = detectPageBlock('<div class="cf-turnstile" data-sitekey="x"></div><article>Some real content alongside the widget in a longer paragraph for realism.</article>');
312
+ add("detect-turnstile", ts.blocked && ts.kind === "cloudflare-turnstile", ts.kind);
313
+
314
+ const clean = detectPageBlock("<article><p>This is a normal article with substantial content about widget markets and quarterly growth figures spanning multiple sentences for realism.</p></article>");
315
+ add("detect-clean", !clean.blocked && !clean.softShell, clean.kind);
316
+
317
+ const spa = detectPageBlock('<html><body><div id="__next"></div><script src="/app.js"></script></body></html>');
318
+ add("detect-spa-soft-not-thrown", !spa.blocked && spa.softShell && spa.kind === "spa-shell", spa.kind);
319
+
320
+ const badReader = async () => ({ value: 1, text: "Just a moment... checking your browser", contentType: "text/html" });
321
+ const goodReader = async () => ({ value: 2, text: "A".repeat(50) + " normal readable article body content here.", contentType: "text/html" });
322
+ let threw = false;
323
+ try {
324
+ await withChallengeGate(badReader as any)("https://x.test/", {} as any, {} as any);
325
+ } catch { threw = true; }
326
+ add("gate-throws-on-challenge", threw, `threw=${threw}`);
327
+
328
+ let passed = false;
329
+ try {
330
+ const r = await withChallengeGate(goodReader as any)("https://x.test/", {} as any, {} as any);
331
+ passed = (r as any).value === 2;
332
+ } catch { /* */ }
333
+ add("gate-passes-clean", passed, `passed=${passed}`);
334
+
335
+ const rules = parseRobotsTxt("User-agent: *\nDisallow: /private\nAllow: /private/public\n");
336
+ add("robots-disallow-parsed", rules.some((r) => !r.allow && r.path === "/private"), JSON.stringify(rules));
337
+
338
+ const bestPublic = rules.filter((r) => matchesRule("/private/public/x", r.path))
339
+ .reduce((a, b) => (!a || b.path.length > a.path.length ? b : a), null as any);
340
+ add("robots-longest-match-allow-wins", bestPublic?.allow === true, JSON.stringify(bestPublic));
341
+
342
+ const named = parseRobotsTxt("User-agent: SpecificBot\nDisallow: /\n");
343
+ add("robots-ignores-named-agent", named.length === 0, JSON.stringify(named));
344
+
345
+ const ordered = prioritizeSeeds([
346
+ "https://reddit.com/r/test",
347
+ "https://arxiv.org/abs/2024.1",
348
+ "https://medium.com/a",
349
+ "https://www.nih.gov/x",
350
+ "https://data.census.gov/y",
351
+ ]);
352
+ const firstTier = domainPriority(ordered[0]).tier;
353
+ add("priority-high-value-first", firstTier === "academic" || firstTier === "gov", `first=${ordered[0]}`);
354
+ add("priority-low-last", domainPriority(ordered[ordered.length - 1]).priority < domainPriority(ordered[0]).priority, `last=${ordered[ordered.length - 1]}`);
355
+ add("priority-tld-gov-fallback", domainPriority("https://data.census.gov/y").tier === "gov", "census→gov");
356
+
357
+ return { ok: checks.every((c) => c.passed), checks };
358
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * safe-fetch-v2.ts
3
+ * ────────────────────────────────────────────────────────────────────────────
4
+ * Bounded, validated, local-first browser fetch routing.
5
+ * Order: MicroCache → direct → Jina Reader → adaptive-ordered public proxy waves.
6
+ */
7
+ import { PROXY_FLEET, type ProxyDef } from "@/lib/scraper-hardener";
8
+
9
+ export type BrowserFetchSource = "direct" | "jina-reader" | "proxy";
10
+
11
+ export interface BrowserFetchOptions {
12
+ signal?: AbortSignal; timeoutMs?: number; maxBytes?: number; allowJinaReader?: boolean; allowPublicProxies?: boolean; preferProxy?: string; bypassCache?: boolean; cacheTtlMs?: number;
13
+ }
14
+ export interface BrowserFetchResult {
15
+ text: string; title: string; sourceUrl: string; source: BrowserFetchSource; proxy?: string; contentType: string; bytesRead: number; truncated: boolean; format: "html-or-text" | "markdown"; fromCache?: boolean;
16
+ }
17
+
18
+ interface CircuitState { failures: number; openUntil: number; }
19
+ const CIRCUITS = new Map<string, CircuitState>();
20
+ const DEFAULT_TIMEOUT_MS = 8_000, DEFAULT_MAX_BYTES = 2_000_000, CIRCUIT_FAILURE_THRESHOLD = 2, CIRCUIT_OPEN_MS = 60_000, PROXY_WAVE_SIZE = 2, DEFAULT_CACHE_TTL_MS = 300_000, CACHE_MAX_ENTRIES = 64, EWMA_ALPHA = 0.3, ROUTER_NEUTRAL_PRIOR_MS = 4_000;
21
+ const BLOCKED_HOSTNAMES = new Set(["localhost", "localhost.localdomain", "metadata.google.internal", "metadata.amazonaws.com"]);
22
+
23
+ export class BrowserFetchError extends Error { readonly attempts: string[]; constructor(message: string, attempts: string[]) { super(message); this.name = "BrowserFetchError"; this.attempts = attempts.slice(); } }
24
+
25
+ interface CacheEntry { result: BrowserFetchResult; expiresAt: number; }
26
+ const FETCH_CACHE = new Map<string, CacheEntry>();
27
+ function cacheGet(key: string): BrowserFetchResult | null { const entry = FETCH_CACHE.get(key); if (!entry) return null; if (Date.now() >= entry.expiresAt) { FETCH_CACHE.delete(key); return null; } FETCH_CACHE.delete(key); FETCH_CACHE.set(key, entry); return entry.result; }
28
+ function cacheSet(key: string, result: BrowserFetchResult, ttlMs: number): void { if (ttlMs <= 0) return; if (FETCH_CACHE.size >= CACHE_MAX_ENTRIES) { const oldest = FETCH_CACHE.keys().next().value; if (oldest !== undefined) FETCH_CACHE.delete(oldest); } FETCH_CACHE.set(key, { result, expiresAt: Date.now() + ttlMs }); }
29
+
30
+ interface ProxyHealth { ewmaMs: number; successes: number; failures: number; }
31
+ const PROXY_HEALTH = new Map<string, ProxyHealth>();
32
+ function recordProxyOutcome(name: string, ok: boolean, elapsedMs: number): void { const prior = PROXY_HEALTH.get(name) || { ewmaMs: ROUTER_NEUTRAL_PRIOR_MS, successes: 0, failures: 0 }; const sample = ok ? elapsedMs : Math.max(elapsedMs, ROUTER_NEUTRAL_PRIOR_MS * 2); PROXY_HEALTH.set(name, { ewmaMs: prior.ewmaMs + EWMA_ALPHA * (sample - prior.ewmaMs), successes: prior.successes + (ok ? 1 : 0), failures: prior.failures + (ok ? 0 : 1) }); }
33
+ function proxyScore(name: string): number { const h = PROXY_HEALTH.get(name); if (!h) return ROUTER_NEUTRAL_PRIOR_MS; const total = h.successes + h.failures, fRate = total > 0 ? h.failures / total : 0; return h.ewmaMs * (1 + 3 * fRate); }
34
+ function adaptivelyOrderedFleet(exclude?: ProxyDef): ProxyDef[] { return PROXY_FLEET.filter((p) => p !== exclude).slice().sort((a, b) => proxyScore(a.name) - proxyScore(b.name)); }
35
+
36
+ function normalizeHostname(h: string): string { return h.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "").replace(/\.$/, ""); }
37
+ function parseIPv4(h: string): number[] | null { const pts = h.split("."); if (pts.length !== 4) return null; const ns = pts.map(p => (/^\d{1,3}$/.test(p) ? Number(p) : NaN)); if (ns.some(n => !Number.isInteger(n) || n < 0 || n > 255)) return null; return ns; }
38
+ function isBlockedIPv4(h: string): boolean { const ip = parseIPv4(h); if (!ip) return false; const [a, b, c] = ip; return (a === 0 || a === 10 || a === 127 || (a === 100 && b >= 64 && b <= 127) || (a === 169 && b === 254) || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 0 && c === 0) || (a === 192 && b === 0 && c === 2) || (a === 192 && b === 168) || (a === 198 && (b === 18 || b === 19)) || (a === 198 && b === 51 && c === 100) || (a === 203 && b === 0 && c === 113) || a >= 224); }
39
+ function parseIPv6Words(h: string): number[] | null {
40
+ let host = normalizeHostname(h); if (!host.includes(":") || host.includes("%")) return null;
41
+ if (host.includes(".")) { const lc = host.lastIndexOf(":"); if (lc < 0) return null; const v4 = parseIPv4(host.slice(lc + 1)); if (!v4) return null; host = `${host.slice(0, lc)}:${((v4[0] << 8) | v4[1]).toString(16)}:${((v4[2] << 8) | v4[3]).toString(16)}`; }
42
+ const pts = host.split("::"); if (pts.length > 2) return null;
43
+ const pSide = (s: string): number[] | null => { if (!s) return []; const vs = s.split(":").map(p => (/^[0-9a-f]{1,4}$/i.test(p) ? parseInt(p, 16) : NaN)); return vs.some(n => !Number.isFinite(n)) ? null : vs; };
44
+ const L = pSide(pts[0]), R = pSide(pts[1] || ""); if (!L || !R) return null;
45
+ if (pts.length === 1) return L.length === 8 ? L : null;
46
+ const zs = 8 - L.length - R.length; if (zs < 1) return null;
47
+ return [...L, ...Array.from({ length: zs }, () => 0), ...R];
48
+ }
49
+ function isBlockedIPv6(h: string): boolean {
50
+ const host = normalizeHostname(h); if (!host.includes(":")) return false; if (host.includes("%")) return true;
51
+ const w = parseIPv6Words(host); if (!w) return true;
52
+ if (w.every(x => x === 0) || (w.slice(0, 7).every(x => x === 0) && w[7] === 1)) return true;
53
+ const f = w[0]; if ((f & 0xfe00) === 0xfc00 || (f & 0xffc0) === 0xfe80 || (f & 0xff00) === 0xff00) return true;
54
+ if (w.slice(0, 5).every(x => x === 0) && w[5] === 0xffff) return isBlockedIPv4([w[6] >>> 8, w[6] & 0xff, w[7] >>> 8, w[7] & 0xff].join("."));
55
+ if (w.slice(0, 6).every(x => x === 0)) return isBlockedIPv4([w[6] >>> 8, w[6] & 0xff, w[7] >>> 8, w[7] & 0xff].join("."));
56
+ return false;
57
+ }
58
+ function hasSensitiveQuery(u: URL): boolean { const exact = new Set(["authorization", "bearer", "code", "credential", "credentials", "jwt", "key", "password", "secret", "session", "sessionid", "signature", "sig", "token"]); for (const k of u.searchParams.keys()) { const c = k.toLowerCase().replace(/[^a-z0-9]/g, ""); if (exact.has(c) || /apikey|accesskey|accesstoken|authtoken|refreshtoken|clientsecret|sessiontoken|signedtoken/.test(c)) return true; } return false; }
59
+ export function normalizePublicTarget(raw: string): URL {
60
+ if (!raw || !raw.trim()) throw new TypeError("URL empty"); let u: URL; try { u = new URL(raw); } catch { throw new TypeError("URL invalid"); }
61
+ if (u.protocol !== "http:" && u.protocol !== "https:") throw new TypeError("scheme disallowed"); if (u.username || u.password) throw new TypeError("credentials disallowed");
62
+ const h = normalizeHostname(u.hostname); if (!h || BLOCKED_HOSTNAMES.has(h) || h.endsWith(".localhost") || h.endsWith(".local") || h.endsWith(".internal") || h.endsWith(".home.arpa") || h.endsWith(".invalid") || h.endsWith(".test") || isBlockedIPv4(h) || isBlockedIPv6(h)) throw new TypeError("target disallowed");
63
+ u.hash = ""; return u;
64
+ }
65
+ function canDisclose(u: URL): boolean { return !hasSensitiveQuery(u); }
66
+
67
+ function createLinkedSignal(sigs: (AbortSignal | undefined)[], timeoutMs: number) {
68
+ const ctrl = new AbortController(); const listeners: { s: AbortSignal; l: () => void }[] = []; const abort = () => { if (!ctrl.signal.aborted) ctrl.abort(); };
69
+ for (const s of sigs) { if (s) { if (s.aborted) { abort(); break; } const l = () => abort(); s.addEventListener("abort", l, { once: true }); listeners.push({ s, l }); } }
70
+ const t = setTimeout(abort, timeoutMs); return { signal: ctrl.signal, cleanup: () => { clearTimeout(t); for (const e of listeners) e.s.removeEventListener("abort", e.l); } };
71
+ }
72
+ async function readBoundedText(res: Response, max: number): Promise<{ text: string; bytesRead: number; truncated: boolean }> {
73
+ if (!res.body) throw new Error("STREAMING_RESPONSE_BODY_UNAVAILABLE");
74
+ const ct = res.headers.get("content-type") || "", charset = ct.match(/charset\s*=\s*["']?([^;"'\s]+)/i)?.[1] || "utf-8";
75
+ let dec = new TextDecoder(charset, { fatal: false }); const reader = res.body.getReader(); let read = 0, trunc = false, text = "";
76
+ try {
77
+ while (true) {
78
+ const { value, done } = await reader.read(); if (done) break;
79
+ const rem = max - read; if (rem <= 0) { trunc = true; try { await reader.cancel(); } catch {} break; }
80
+ const chunk = value.byteLength <= rem ? value : value.subarray(0, rem); read += chunk.byteLength; text += dec.decode(chunk, { stream: true });
81
+ if (chunk.byteLength < value.byteLength) { trunc = true; try { await reader.cancel(); } catch {} break; }
82
+ }
83
+ text += dec.decode();
84
+ } finally { reader.releaseLock(); }
85
+ return { text, bytesRead: read, truncated: trunc };
86
+ }
87
+ function looksUsable(text: string): boolean { const t = text.trim(); return t.length >= 80 && !/^(?:error|forbidden|unauthorized|rate limit|too many requests|access denied)\b/i.test(t); }
88
+
89
+ async function fetchTextOnce(url: string, opts: { signal?: AbortSignal; timeoutMs: number; maxBytes: number; accept: string; redirect: RequestRedirect; validateFinalUrl?: boolean }): Promise<{ text: string; contentType: string; bytesRead: number; truncated: boolean }> {
90
+ const linked = createLinkedSignal([opts.signal], opts.timeoutMs);
91
+ try {
92
+ const res = await fetch(url, { method: "GET", mode: "cors", signal: linked.signal, credentials: "omit", referrerPolicy: "no-referrer", redirect: opts.redirect, headers: { Accept: opts.accept } });
93
+ if (opts.validateFinalUrl && res.url) normalizePublicTarget(res.url);
94
+ if (!res.ok) throw new Error(`HTTP_${res.status}`);
95
+ const body = await readBoundedText(res, opts.maxBytes); if (!looksUsable(body.text)) throw new Error("UNUSABLE_RESPONSE");
96
+ return { ...body, contentType: res.headers.get("content-type") || "" };
97
+ } finally { linked.cleanup(); }
98
+ }
99
+
100
+ function circuitAvailable(name: string): boolean { const s = CIRCUITS.get(name); if (!s) return true; if (Date.now() >= s.openUntil) { CIRCUITS.delete(name); return true; } return s.failures < CIRCUIT_FAILURE_THRESHOLD; }
101
+ function circuitSuccess(name: string): void { CIRCUITS.delete(name); }
102
+ function circuitFailure(name: string): void { const p = CIRCUITS.get(name), f = (p?.failures || 0) + 1; CIRCUITS.set(name, { failures: f, openUntil: f >= CIRCUIT_FAILURE_THRESHOLD ? Date.now() + CIRCUIT_OPEN_MS : 0 }); }
103
+
104
+ async function tryProxy(proxy: ProxyDef, target: URL, opts: { signal?: AbortSignal; timeoutMs: number; maxBytes: number }): Promise<BrowserFetchResult> {
105
+ if (!circuitAvailable(proxy.name)) throw new Error(`${proxy.name}:CIRCUIT_OPEN`); const start = Date.now();
106
+ try {
107
+ const res = await fetchTextOnce(proxy.build(target.toString()), { signal: opts.signal, timeoutMs: opts.timeoutMs, maxBytes: opts.maxBytes, accept: "text/html,application/xhtml+xml,text/plain;q=0.9,application/json;q=0.8,*/*;q=0.5", redirect: "follow" });
108
+ const unwrapped = proxy.unwrap ? proxy.unwrap(res.text) : res.text, bounded = unwrapped.slice(0, opts.maxBytes); if (!looksUsable(bounded)) throw new Error("UNUSABLE_RESPONSE");
109
+ circuitSuccess(proxy.name); recordProxyOutcome(proxy.name, true, Date.now() - start);
110
+ return { text: bounded, title: "", sourceUrl: target.toString(), source: "proxy", proxy: proxy.name, contentType: res.contentType, bytesRead: res.bytesRead, truncated: res.truncated || unwrapped.length > opts.maxBytes, format: "html-or-text" };
111
+ } catch (error) { if (!opts.signal?.aborted) { circuitFailure(proxy.name); recordProxyOutcome(proxy.name, false, Date.now() - start); } throw error; }
112
+ }
113
+
114
+ function firstSuccessful<T>(factories: Array<(signal: AbortSignal) => Promise<T>>, parentSignal?: AbortSignal): Promise<T> {
115
+ return new Promise((resolve, reject) => {
116
+ if (factories.length === 0) { reject(new Error("No candidates.")); return; }
117
+ const ctrl = new AbortController(); const errors: string[] = []; let rem = factories.length, settled = false;
118
+ const onAbort = () => ctrl.abort(); if (parentSignal?.aborted) ctrl.abort(); else parentSignal?.addEventListener("abort", onAbort, { once: true });
119
+ for (const f of factories) { f(ctrl.signal).then(v => { if (settled) return; settled = true; ctrl.abort(); parentSignal?.removeEventListener("abort", onAbort); resolve(v); }, e => { errors.push(e instanceof Error ? e.message : "err"); rem--; if (!settled && rem === 0) { settled = true; parentSignal?.removeEventListener("abort", onAbort); reject(new Error(errors.join(" | "))); } }); }
120
+ });
121
+ }
122
+
123
+ async function tryJina(target: URL, opts: { signal?: AbortSignal; timeoutMs: number; maxBytes: number }): Promise<BrowserFetchResult> {
124
+ const res = await fetchTextOnce(`https://r.jina.ai/${target.toString()}`, { signal: opts.signal, timeoutMs: opts.timeoutMs, maxBytes: opts.maxBytes, accept: "text/plain,text/markdown;q=0.9,application/json;q=0.8", redirect: "follow" });
125
+ let title = "", content = ""; try { const p = JSON.parse(res.text), d = p?.data ?? p; title = typeof d?.title === "string" ? d.title : ""; content = typeof d?.content === "string" ? d.content : ""; } catch { content = res.text; }
126
+ if (!looksUsable(content)) throw new Error("JINA_UNUSABLE_RESPONSE");
127
+ return { text: content.slice(0, opts.maxBytes), title, sourceUrl: target.toString(), source: "jina-reader", contentType: res.contentType, bytesRead: res.bytesRead, truncated: res.truncated || content.length > opts.maxBytes, format: "markdown" };
128
+ }
129
+
130
+ export async function fetchBrowserTextV2(rawUrl: string, options?: BrowserFetchOptions): Promise<BrowserFetchResult> {
131
+ if (options?.timeoutMs !== undefined && (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)) throw new TypeError("timeoutMs must be a positive finite number.");
132
+ if (options?.maxBytes !== undefined && (!Number.isFinite(options.maxBytes) || options.maxBytes <= 0)) throw new TypeError("maxBytes must be a positive finite number.");
133
+ const target = normalizePublicTarget(rawUrl), timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS, maxBytes = options?.maxBytes ?? DEFAULT_MAX_BYTES, ttl = options?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, key = `${target.toString()}\u0000${maxBytes}`;
134
+ if (!options?.bypassCache) { const hit = cacheGet(key); if (hit) return { ...hit, fromCache: true }; }
135
+ const allowJina = options?.allowJinaReader !== false, allowProxies = options?.allowPublicProxies !== false;
136
+ const attempts: string[] = [];
137
+ try {
138
+ const direct = await fetchTextOnce(target.toString(), { signal: options?.signal, timeoutMs, maxBytes, accept: "text/html,application/xhtml+xml,text/plain;q=0.9,application/json;q=0.8,*/*;q=0.5", redirect: "manual", validateFinalUrl: true });
139
+ const res: BrowserFetchResult = { text: direct.text, title: "", sourceUrl: target.toString(), source: "direct", contentType: direct.contentType, bytesRead: direct.bytesRead, truncated: direct.truncated, format: "html-or-text" };
140
+ if (!options?.bypassCache) cacheSet(key, res, ttl); return res;
141
+ } catch (e) { attempts.push(`direct:${err(e)}`); }
142
+ if (canDisclose(target)) {
143
+ if (allowJina) { try { const res = await tryJina(target, { signal: options?.signal, timeoutMs, maxBytes }); if (!options?.bypassCache) cacheSet(key, res, ttl); return res; } catch (e) { attempts.push(`jina:${err(e)}`); } }
144
+ if (allowProxies) {
145
+ const pref = options?.preferProxy ? PROXY_FLEET.find(p => p.name === options.preferProxy) : undefined;
146
+ if (pref) { try { const res = await tryProxy(pref, target, { signal: options?.signal, timeoutMs, maxBytes }); if (!options?.bypassCache) cacheSet(key, res, ttl); return res; } catch (e) { attempts.push(`${pref.name}:${err(e)}`); } }
147
+ const fleet = adaptivelyOrderedFleet(pref);
148
+ for (let i = 0; i < fleet.length; i += PROXY_WAVE_SIZE) {
149
+ if (options?.signal?.aborted) break;
150
+ const wave = fleet.slice(i, i + PROXY_WAVE_SIZE);
151
+ try { const res = await firstSuccessful(wave.map(p => (s: AbortSignal) => tryProxy(p, target, { signal: s, timeoutMs, maxBytes })), options?.signal); if (!options?.bypassCache) cacheSet(key, res, ttl); return res; } catch (e) { attempts.push(`proxy-wave:${err(e)}`); }
152
+ }
153
+ }
154
+ } else attempts.push("blocked_sensitive_query");
155
+ throw new BrowserFetchError(`No retrieval path succeeded for ${target.hostname}.`, attempts);
156
+ }
157
+ function err(e: any): string { return e instanceof Error ? e.message : String(e); }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * sentinel-omega.ts
3
+ * ============================================================================
4
+ * Additive canonical Sentinel Omega lane for the portfolio orchestrator.
5
+ * Self-contained omega grounding over Conclave and Sibyl engines.
6
+ */
7
+
8
+ import { sibylResearch } from "./sibyl-oracle";
9
+
10
+ export async function groundWithSentinelOmega(
11
+ query: string,
12
+ opts?: {
13
+ signal?: AbortSignal;
14
+ depth?: number;
15
+ allowJina?: boolean;
16
+ allowPublicProxies?: boolean;
17
+ allowWayback?: boolean;
18
+ onDebug?: (msg: string) => void;
19
+ }
20
+ ): Promise<{
21
+ ok: boolean;
22
+ provider: string;
23
+ evidenceBlock: string;
24
+ sources: Array<{ title: string; url: string; content: string }>;
25
+ claims: any[];
26
+ counts: Record<string, number>;
27
+ }> {
28
+ try {
29
+ const sibyl = await sibylResearch(query, opts);
30
+ if (sibyl.ok && sibyl.evidenceBlock) {
31
+ return {
32
+ ok: true,
33
+ provider: `sentinel-omega(${sibyl.provider})`,
34
+ evidenceBlock: sibyl.evidenceBlock,
35
+ sources: (sibyl.sources || []).map((s) => ({
36
+ title: s.title || "Untitled",
37
+ url: s.canonicalUrl || s.url,
38
+ content: s.contentSample || "",
39
+ })),
40
+ claims: sibyl.claims || [],
41
+ counts: { supported: sibyl.sources?.length || 0 },
42
+ };
43
+ }
44
+ } catch (e) {
45
+ opts?.onDebug?.(`sentinel-omega sibyl fail: ${e instanceof Error ? e.message : String(e)}`);
46
+ }
47
+
48
+ return {
49
+ ok: false,
50
+ provider: "sentinel-omega(unresolved)",
51
+ evidenceBlock: "",
52
+ sources: [],
53
+ claims: [],
54
+ counts: {},
55
+ };
56
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * sentinel-orchestrator.ts
3
+ * ============================================================================
4
+ * Additive canonical Sentinel Orchestrator lane for the portfolio orchestrator.
5
+ * Delegates to Arbiter-Omega + Palisade to adjudicate search evidence.
6
+ */
7
+
8
+ import { arbiterResearch } from "./arbiter-omega";
9
+ import { palisadeGround } from "../scraper-palisade/palisade-adjudicator";
10
+
11
+ export async function sentinelGround(
12
+ query: string,
13
+ opts?: {
14
+ signal?: AbortSignal;
15
+ depth?: number;
16
+ allowJina?: boolean;
17
+ allowPublicProxies?: boolean;
18
+ allowWayback?: boolean;
19
+ onDebug?: (msg: string) => void;
20
+ }
21
+ ): Promise<{
22
+ ok: boolean;
23
+ provider: string;
24
+ evidenceBlock: string;
25
+ sources: Array<{ title: string; url: string; content: string }>;
26
+ claims: any[];
27
+ counts: Record<string, number>;
28
+ }> {
29
+ try {
30
+ const palisade = await palisadeGround(query, opts);
31
+ if (palisade.ok && palisade.evidenceBlock) {
32
+ return {
33
+ ok: true,
34
+ provider: `sentinel-orchestrator(${palisade.provider})`,
35
+ evidenceBlock: palisade.evidenceBlock,
36
+ sources: (palisade as any).sources || [],
37
+ claims: palisade.claims || [],
38
+ counts: (palisade as any).counts || {},
39
+ };
40
+ }
41
+ } catch (e) {
42
+ opts?.onDebug?.(`sentinel-orchestrator palisade fail: ${e instanceof Error ? e.message : String(e)}`);
43
+ }
44
+
45
+ try {
46
+ const arb = await arbiterResearch(query, opts);
47
+ return {
48
+ ok: arb.ok,
49
+ provider: `sentinel-orchestrator(${arb.provider})`,
50
+ evidenceBlock: arb.evidenceBlock || "",
51
+ sources: (arb.sources || []).map((s) => ({
52
+ title: s.title || "Untitled",
53
+ url: s.canonicalUrl || s.url,
54
+ content: s.content || "",
55
+ })),
56
+ claims: arb.claims || [],
57
+ counts: {},
58
+ };
59
+ } catch (e) {
60
+ return {
61
+ ok: false,
62
+ provider: "sentinel-orchestrator(failed)",
63
+ evidenceBlock: "",
64
+ sources: [],
65
+ claims: [],
66
+ counts: {},
67
+ };
68
+ }
69
+ }