audit-tools 0.34.41 → 0.35.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 (328) hide show
  1. package/dist/audit/cli/args.d.ts +6 -0
  2. package/dist/audit/cli/args.d.ts.map +1 -1
  3. package/dist/audit/cli/args.js +21 -4
  4. package/dist/audit/cli/args.js.map +1 -1
  5. package/dist/audit/cli/auditStep.d.ts +6 -15
  6. package/dist/audit/cli/auditStep.d.ts.map +1 -1
  7. package/dist/audit/cli/auditStep.js +1 -1
  8. package/dist/audit/cli/auditStep.js.map +1 -1
  9. package/dist/audit/cli/dispatch/hostFanoutGate.d.ts +21 -61
  10. package/dist/audit/cli/dispatch/hostFanoutGate.d.ts.map +1 -1
  11. package/dist/audit/cli/dispatch/hostFanoutGate.js +18 -167
  12. package/dist/audit/cli/dispatch/hostFanoutGate.js.map +1 -1
  13. package/dist/audit/cli/dispatch/packetFilter.d.ts +2 -2
  14. package/dist/audit/cli/dispatch/packetFilter.d.ts.map +1 -1
  15. package/dist/audit/cli/dispatch/packetFilter.js +3 -1
  16. package/dist/audit/cli/dispatch/packetFilter.js.map +1 -1
  17. package/dist/audit/cli/dispatch/quotaPool.d.ts +2 -14
  18. package/dist/audit/cli/dispatch/quotaPool.d.ts.map +1 -1
  19. package/dist/audit/cli/dispatch/quotaPool.js +16 -6
  20. package/dist/audit/cli/dispatch/quotaPool.js.map +1 -1
  21. package/dist/audit/cli/dispatch/tierRouting.d.ts +2 -2
  22. package/dist/audit/cli/dispatch/tokenUsageObservation.d.ts.map +1 -1
  23. package/dist/audit/cli/dispatch/tokenUsageObservation.js +10 -2
  24. package/dist/audit/cli/dispatch/tokenUsageObservation.js.map +1 -1
  25. package/dist/audit/cli/dispatch.d.ts +14 -3
  26. package/dist/audit/cli/dispatch.d.ts.map +1 -1
  27. package/dist/audit/cli/dispatch.js +87 -65
  28. package/dist/audit/cli/dispatch.js.map +1 -1
  29. package/dist/audit/cli/forceSynthesisCommand.d.ts.map +1 -1
  30. package/dist/audit/cli/forceSynthesisCommand.js +9 -1
  31. package/dist/audit/cli/forceSynthesisCommand.js.map +1 -1
  32. package/dist/audit/cli/hybridDispatch.d.ts +4 -9
  33. package/dist/audit/cli/hybridDispatch.d.ts.map +1 -1
  34. package/dist/audit/cli/hybridDispatch.js +1 -1
  35. package/dist/audit/cli/hybridDispatch.js.map +1 -1
  36. package/dist/audit/cli/lineIndex.d.ts +2 -0
  37. package/dist/audit/cli/lineIndex.d.ts.map +1 -1
  38. package/dist/audit/cli/lineIndex.js +4 -0
  39. package/dist/audit/cli/lineIndex.js.map +1 -1
  40. package/dist/audit/cli/nextStepCommand.d.ts +0 -26
  41. package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
  42. package/dist/audit/cli/nextStepCommand.js +14 -358
  43. package/dist/audit/cli/nextStepCommand.js.map +1 -1
  44. package/dist/audit/cli/nextStepHelpers.d.ts +5 -16
  45. package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
  46. package/dist/audit/cli/nextStepHelpers.js +27 -157
  47. package/dist/audit/cli/nextStepHelpers.js.map +1 -1
  48. package/dist/audit/cli/quotaCommand.d.ts.map +1 -1
  49. package/dist/audit/cli/quotaCommand.js +1 -5
  50. package/dist/audit/cli/quotaCommand.js.map +1 -1
  51. package/dist/audit/cli/reviewRun.d.ts.map +1 -1
  52. package/dist/audit/cli/reviewRun.js +58 -23
  53. package/dist/audit/cli/reviewRun.js.map +1 -1
  54. package/dist/audit/cli/rollingAuditDispatch.d.ts +1 -1
  55. package/dist/audit/cli/semanticReviewStep.d.ts.map +1 -1
  56. package/dist/audit/cli/semanticReviewStep.js +35 -54
  57. package/dist/audit/cli/semanticReviewStep.js.map +1 -1
  58. package/dist/audit/cli/steps.d.ts +4 -4
  59. package/dist/audit/cli/steps.d.ts.map +1 -1
  60. package/dist/audit/cli/steps.js +0 -1
  61. package/dist/audit/cli/steps.js.map +1 -1
  62. package/dist/audit/cli/workPartitionRuntime.d.ts +11 -0
  63. package/dist/audit/cli/workPartitionRuntime.d.ts.map +1 -0
  64. package/dist/audit/cli/workPartitionRuntime.js +39 -0
  65. package/dist/audit/cli/workPartitionRuntime.js.map +1 -0
  66. package/dist/audit/contracts/wrapperResponse.d.ts +6 -6
  67. package/dist/audit/io/artifacts.d.ts +1 -3
  68. package/dist/audit/io/artifacts.d.ts.map +1 -1
  69. package/dist/audit/io/artifacts.js +0 -3
  70. package/dist/audit/io/artifacts.js.map +1 -1
  71. package/dist/audit/orchestrator/advance.d.ts.map +1 -1
  72. package/dist/audit/orchestrator/advance.js +0 -35
  73. package/dist/audit/orchestrator/advance.js.map +1 -1
  74. package/dist/audit/orchestrator/advanceTypes.d.ts +4 -63
  75. package/dist/audit/orchestrator/advanceTypes.d.ts.map +1 -1
  76. package/dist/audit/orchestrator/executorRunners.d.ts.map +1 -1
  77. package/dist/audit/orchestrator/executorRunners.js +9 -12
  78. package/dist/audit/orchestrator/executorRunners.js.map +1 -1
  79. package/dist/audit/orchestrator/executors.d.ts.map +1 -1
  80. package/dist/audit/orchestrator/executors.js +0 -5
  81. package/dist/audit/orchestrator/executors.js.map +1 -1
  82. package/dist/audit/orchestrator/hostInputPause.d.ts +1 -1
  83. package/dist/audit/orchestrator/hostInputPause.js +1 -1
  84. package/dist/audit/orchestrator/ingestionExecutors.d.ts.map +1 -1
  85. package/dist/audit/orchestrator/ingestionExecutors.js +4 -1
  86. package/dist/audit/orchestrator/ingestionExecutors.js.map +1 -1
  87. package/dist/audit/orchestrator/intakeExecutors.d.ts +1 -65
  88. package/dist/audit/orchestrator/intakeExecutors.d.ts.map +1 -1
  89. package/dist/audit/orchestrator/intakeExecutors.js +3 -425
  90. package/dist/audit/orchestrator/intakeExecutors.js.map +1 -1
  91. package/dist/audit/orchestrator/nextStep.d.ts +0 -13
  92. package/dist/audit/orchestrator/nextStep.d.ts.map +1 -1
  93. package/dist/audit/orchestrator/nextStep.js +0 -7
  94. package/dist/audit/orchestrator/nextStep.js.map +1 -1
  95. package/dist/audit/orchestrator/planningExecutors.d.ts.map +1 -1
  96. package/dist/audit/orchestrator/planningExecutors.js +4 -1
  97. package/dist/audit/orchestrator/planningExecutors.js.map +1 -1
  98. package/dist/audit/orchestrator/rollingDispatch.d.ts +1 -1
  99. package/dist/audit/orchestrator/rollingDispatch.d.ts.map +1 -1
  100. package/dist/audit/orchestrator/rollingDispatch.js +5 -4
  101. package/dist/audit/orchestrator/rollingDispatch.js.map +1 -1
  102. package/dist/audit/orchestrator/state.d.ts +0 -39
  103. package/dist/audit/orchestrator/state.d.ts.map +1 -1
  104. package/dist/audit/orchestrator/state.js +3 -29
  105. package/dist/audit/orchestrator/state.js.map +1 -1
  106. package/dist/audit/orchestrator/synthesisExecutors.d.ts +8 -2
  107. package/dist/audit/orchestrator/synthesisExecutors.d.ts.map +1 -1
  108. package/dist/audit/orchestrator/synthesisExecutors.js +7 -5
  109. package/dist/audit/orchestrator/synthesisExecutors.js.map +1 -1
  110. package/dist/audit/reporting/findingIdentity.d.ts +2 -2
  111. package/dist/audit/reporting/findingIdentity.js +2 -2
  112. package/dist/audit/reporting/synthesis.d.ts +7 -1
  113. package/dist/audit/reporting/synthesis.d.ts.map +1 -1
  114. package/dist/audit/reporting/synthesis.js +27 -7
  115. package/dist/audit/reporting/synthesis.js.map +1 -1
  116. package/dist/audit/reporting/workBlocks.d.ts +14 -3
  117. package/dist/audit/reporting/workBlocks.d.ts.map +1 -1
  118. package/dist/audit/reporting/workBlocks.js +163 -73
  119. package/dist/audit/reporting/workBlocks.js.map +1 -1
  120. package/dist/audit/types/activeDispatch.d.ts +8 -7
  121. package/dist/audit/types/activeDispatch.d.ts.map +1 -1
  122. package/dist/audit/types/auditState.d.ts +6 -6
  123. package/dist/audit/types/externalAnalyzer.d.ts +6 -6
  124. package/dist/audit/validation/auditResults.d.ts.map +1 -1
  125. package/dist/audit/validation/auditResults.js +6 -1
  126. package/dist/audit/validation/auditResults.js.map +1 -1
  127. package/dist/remediate/contractPipeline/phaseCut.d.ts +8 -0
  128. package/dist/remediate/contractPipeline/phaseCut.d.ts.map +1 -1
  129. package/dist/remediate/contractPipeline/phaseCut.js +63 -0
  130. package/dist/remediate/contractPipeline/phaseCut.js.map +1 -1
  131. package/dist/remediate/contractPipeline/phaseCutArtifact.d.ts.map +1 -1
  132. package/dist/remediate/contractPipeline/phaseCutArtifact.js +5 -3
  133. package/dist/remediate/contractPipeline/phaseCutArtifact.js.map +1 -1
  134. package/dist/remediate/phases/plan.d.ts.map +1 -1
  135. package/dist/remediate/phases/plan.js +35 -12
  136. package/dist/remediate/phases/plan.js.map +1 -1
  137. package/dist/remediate/steps/contractPipeline.d.ts +6 -2
  138. package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
  139. package/dist/remediate/steps/contractPipeline.js +90 -6
  140. package/dist/remediate/steps/contractPipeline.js.map +1 -1
  141. package/dist/remediate/steps/contractPipelinePrompts.d.ts.map +1 -1
  142. package/dist/remediate/steps/contractPipelinePrompts.js +4 -2
  143. package/dist/remediate/steps/contractPipelinePrompts.js.map +1 -1
  144. package/dist/remediate/steps/dispatch/marshal.d.ts +6 -0
  145. package/dist/remediate/steps/dispatch/marshal.d.ts.map +1 -1
  146. package/dist/remediate/steps/dispatch/marshal.js +16 -17
  147. package/dist/remediate/steps/dispatch/marshal.js.map +1 -1
  148. package/dist/remediate/steps/dispatch/waveScheduling.d.ts +7 -15
  149. package/dist/remediate/steps/dispatch/waveScheduling.d.ts.map +1 -1
  150. package/dist/remediate/steps/dispatch/waveScheduling.js +26 -19
  151. package/dist/remediate/steps/dispatch/waveScheduling.js.map +1 -1
  152. package/dist/remediate/steps/nextStep.d.ts +5 -7
  153. package/dist/remediate/steps/nextStep.d.ts.map +1 -1
  154. package/dist/remediate/steps/nextStep.js +100 -82
  155. package/dist/remediate/steps/nextStep.js.map +1 -1
  156. package/dist/remediate/steps/rollingSession.d.ts +2 -1
  157. package/dist/remediate/steps/rollingSession.d.ts.map +1 -1
  158. package/dist/remediate/steps/rollingSession.js +14 -8
  159. package/dist/remediate/steps/rollingSession.js.map +1 -1
  160. package/dist/remediate/steps/sessionConfigLoad.d.ts +3 -2
  161. package/dist/remediate/steps/sessionConfigLoad.d.ts.map +1 -1
  162. package/dist/remediate/steps/sessionConfigLoad.js +4 -10
  163. package/dist/remediate/steps/sessionConfigLoad.js.map +1 -1
  164. package/dist/remediate/validation/contractPipeline.d.ts +1 -1
  165. package/dist/remediate/validation/contractPipeline.d.ts.map +1 -1
  166. package/dist/remediate/validation/contractPipeline.js +7 -1
  167. package/dist/remediate/validation/contractPipeline.js.map +1 -1
  168. package/dist/remediate/validation/contractPipelineGates.d.ts +8 -0
  169. package/dist/remediate/validation/contractPipelineGates.d.ts.map +1 -1
  170. package/dist/remediate/validation/contractPipelineGates.js +89 -0
  171. package/dist/remediate/validation/contractPipelineGates.js.map +1 -1
  172. package/dist/shared/config/resolveSessionConfig.d.ts.map +1 -1
  173. package/dist/shared/config/resolveSessionConfig.js +6 -5
  174. package/dist/shared/config/resolveSessionConfig.js.map +1 -1
  175. package/dist/shared/decompose/workPartition.d.ts +66 -0
  176. package/dist/shared/decompose/workPartition.d.ts.map +1 -0
  177. package/dist/shared/decompose/workPartition.js +523 -0
  178. package/dist/shared/decompose/workPartition.js.map +1 -0
  179. package/dist/shared/dispatch/admissionLoop.d.ts +5 -44
  180. package/dist/shared/dispatch/admissionLoop.d.ts.map +1 -1
  181. package/dist/shared/dispatch/admissionLoop.js +20 -82
  182. package/dist/shared/dispatch/admissionLoop.js.map +1 -1
  183. package/dist/shared/dispatch/coordinator.d.ts +5 -5
  184. package/dist/shared/dispatch/coordinator.d.ts.map +1 -1
  185. package/dist/shared/dispatch/coordinator.js +12 -11
  186. package/dist/shared/dispatch/coordinator.js.map +1 -1
  187. package/dist/shared/dispatch/costRank.d.ts +5 -155
  188. package/dist/shared/dispatch/costRank.d.ts.map +1 -1
  189. package/dist/shared/dispatch/costRank.js +2 -179
  190. package/dist/shared/dispatch/costRank.js.map +1 -1
  191. package/dist/shared/dispatch/dispatchQuotaContract.d.ts +37 -38
  192. package/dist/shared/dispatch/dispatchQuotaContract.d.ts.map +1 -1
  193. package/dist/shared/dispatch/dispatchQuotaContract.js +0 -1
  194. package/dist/shared/dispatch/dispatchQuotaContract.js.map +1 -1
  195. package/dist/shared/dispatch/hybridDispatch.d.ts +6 -2
  196. package/dist/shared/dispatch/hybridDispatch.d.ts.map +1 -1
  197. package/dist/shared/dispatch/hybridDispatch.js +1 -1
  198. package/dist/shared/dispatch/hybridDispatch.js.map +1 -1
  199. package/dist/shared/dispatch/rollingDispatch.d.ts +4 -4
  200. package/dist/shared/dispatch/rollingDispatch.d.ts.map +1 -1
  201. package/dist/shared/dispatch/rollingDispatch.js +13 -11
  202. package/dist/shared/dispatch/rollingDispatch.js.map +1 -1
  203. package/dist/shared/dispatch/unifiedRolling.d.ts +2 -2
  204. package/dist/shared/dispatch/unifiedRolling.d.ts.map +1 -1
  205. package/dist/shared/dispatch/unifiedRolling.js +1 -1
  206. package/dist/shared/dispatch/unifiedRolling.js.map +1 -1
  207. package/dist/shared/engine/obligationEngine.d.ts +2 -2
  208. package/dist/shared/friction/stepBoundaryCapture.d.ts +5 -59
  209. package/dist/shared/friction/stepBoundaryCapture.d.ts.map +1 -1
  210. package/dist/shared/friction/stepBoundaryCapture.js +11 -107
  211. package/dist/shared/friction/stepBoundaryCapture.js.map +1 -1
  212. package/dist/shared/index.d.ts +13 -16
  213. package/dist/shared/index.d.ts.map +1 -1
  214. package/dist/shared/index.js +9 -13
  215. package/dist/shared/index.js.map +1 -1
  216. package/dist/shared/io/stateDir.d.ts +2 -2
  217. package/dist/shared/io/stateDir.js +2 -2
  218. package/dist/shared/providers/auditorSources.d.ts +10 -84
  219. package/dist/shared/providers/auditorSources.d.ts.map +1 -1
  220. package/dist/shared/providers/auditorSources.js +42 -261
  221. package/dist/shared/providers/auditorSources.js.map +1 -1
  222. package/dist/shared/providers/dispatchExclusion.d.ts +22 -0
  223. package/dist/shared/providers/dispatchExclusion.d.ts.map +1 -0
  224. package/dist/shared/providers/dispatchExclusion.js +23 -0
  225. package/dist/shared/providers/dispatchExclusion.js.map +1 -0
  226. package/dist/shared/providers/identity.d.ts +2 -66
  227. package/dist/shared/providers/identity.d.ts.map +1 -1
  228. package/dist/shared/providers/identity.js +2 -72
  229. package/dist/shared/providers/identity.js.map +1 -1
  230. package/dist/shared/providers/providerFactory.d.ts +0 -3
  231. package/dist/shared/providers/providerFactory.d.ts.map +1 -1
  232. package/dist/shared/providers/providerFactory.js +5 -32
  233. package/dist/shared/providers/providerFactory.js.map +1 -1
  234. package/dist/shared/providers/providerPathGuard.d.ts +3 -3
  235. package/dist/shared/providers/providerPathGuard.js +7 -7
  236. package/dist/shared/providers/providerPathGuard.js.map +1 -1
  237. package/dist/shared/quota/apiPool.d.ts +19 -22
  238. package/dist/shared/quota/apiPool.d.ts.map +1 -1
  239. package/dist/shared/quota/apiPool.js +66 -47
  240. package/dist/shared/quota/apiPool.js.map +1 -1
  241. package/dist/shared/quota/capacity.d.ts +29 -24
  242. package/dist/shared/quota/capacity.d.ts.map +1 -1
  243. package/dist/shared/quota/capacity.js +30 -4
  244. package/dist/shared/quota/capacity.js.map +1 -1
  245. package/dist/shared/quota/codexHostConfig.d.ts +6 -13
  246. package/dist/shared/quota/codexHostConfig.d.ts.map +1 -1
  247. package/dist/shared/quota/codexHostConfig.js +8 -14
  248. package/dist/shared/quota/codexHostConfig.js.map +1 -1
  249. package/dist/shared/quota/fileLock.d.ts +7 -0
  250. package/dist/shared/quota/fileLock.d.ts.map +1 -1
  251. package/dist/shared/quota/fileLock.js +32 -4
  252. package/dist/shared/quota/fileLock.js.map +1 -1
  253. package/dist/shared/quota/hostLimits.d.ts +1 -1
  254. package/dist/shared/quota/hostLimits.d.ts.map +1 -1
  255. package/dist/shared/quota/hostLimits.js +8 -11
  256. package/dist/shared/quota/hostLimits.js.map +1 -1
  257. package/dist/shared/quota/hostPool.d.ts +1 -1
  258. package/dist/shared/quota/hostPool.js +2 -2
  259. package/dist/shared/quota/hostPool.js.map +1 -1
  260. package/dist/shared/quota/limits.d.ts.map +1 -1
  261. package/dist/shared/quota/limits.js +30 -26
  262. package/dist/shared/quota/limits.js.map +1 -1
  263. package/dist/shared/quota/scheduler.d.ts +2 -3
  264. package/dist/shared/quota/scheduler.d.ts.map +1 -1
  265. package/dist/shared/quota/scheduler.js +1 -1
  266. package/dist/shared/quota/scheduler.js.map +1 -1
  267. package/dist/shared/quota/types.d.ts +10 -10
  268. package/dist/shared/quota/types.d.ts.map +1 -1
  269. package/dist/shared/quota/types.js +4 -7
  270. package/dist/shared/quota/types.js.map +1 -1
  271. package/dist/shared/repair/brokeredDispatch.d.ts.map +1 -1
  272. package/dist/shared/repair/brokeredDispatch.js +3 -0
  273. package/dist/shared/repair/brokeredDispatch.js.map +1 -1
  274. package/dist/shared/reporting/auditDeliverable.d.ts.map +1 -1
  275. package/dist/shared/reporting/auditDeliverable.js +1 -0
  276. package/dist/shared/reporting/auditDeliverable.js.map +1 -1
  277. package/dist/shared/rolling/pausedState.d.ts +7 -7
  278. package/dist/shared/rolling/pausedState.js +3 -3
  279. package/dist/shared/tokens.d.ts +2 -5
  280. package/dist/shared/tokens.d.ts.map +1 -1
  281. package/dist/shared/tokens.js +15 -15
  282. package/dist/shared/tokens.js.map +1 -1
  283. package/dist/shared/types/auditorDescriptor.d.ts +3 -2
  284. package/dist/shared/types/auditorDescriptor.d.ts.map +1 -1
  285. package/dist/shared/types/auditorDescriptor.js +3 -2
  286. package/dist/shared/types/auditorDescriptor.js.map +1 -1
  287. package/dist/shared/types/finding.d.ts +80 -0
  288. package/dist/shared/types/finding.d.ts.map +1 -1
  289. package/dist/shared/types/finding.js +17 -0
  290. package/dist/shared/types/finding.js.map +1 -1
  291. package/dist/shared/types/sessionConfig.d.ts +5 -5
  292. package/dist/shared/types/sessionConfig.js +1 -1
  293. package/dist/shared/validation/findingsReport.d.ts.map +1 -1
  294. package/dist/shared/validation/findingsReport.js +3 -0
  295. package/dist/shared/validation/findingsReport.js.map +1 -1
  296. package/docs/audit-pkg/development.md +3 -2
  297. package/docs/audit-pkg/operator-guide.md +13 -106
  298. package/docs/audit-pkg/product.md +8 -7
  299. package/docs/audit-pkg/release.md +38 -29
  300. package/opencode.json +2 -2
  301. package/package.json +7 -4
  302. package/skills/audit-code/SKILL.md +2 -1
  303. package/skills/audit-code/audit-code.prompt.md +4 -3
  304. package/skills/remediate-code/remediate-code.prompt.md +2 -1
  305. package/dist/audit/cli/providerConfirmationStep.d.ts +0 -106
  306. package/dist/audit/cli/providerConfirmationStep.d.ts.map +0 -1
  307. package/dist/audit/cli/providerConfirmationStep.js +0 -396
  308. package/dist/audit/cli/providerConfirmationStep.js.map +0 -1
  309. package/dist/audit/orchestrator/providerConfirmation.d.ts +0 -25
  310. package/dist/audit/orchestrator/providerConfirmation.d.ts.map +0 -1
  311. package/dist/audit/orchestrator/providerConfirmation.js +0 -60
  312. package/dist/audit/orchestrator/providerConfirmation.js.map +0 -1
  313. package/dist/shared/providers/providerConfirmation.d.ts +0 -141
  314. package/dist/shared/providers/providerConfirmation.d.ts.map +0 -1
  315. package/dist/shared/providers/providerConfirmation.js +0 -420
  316. package/dist/shared/providers/providerConfirmation.js.map +0 -1
  317. package/dist/shared/providers/proxyCatalog.d.ts +0 -158
  318. package/dist/shared/providers/proxyCatalog.d.ts.map +0 -1
  319. package/dist/shared/providers/proxyCatalog.js +0 -658
  320. package/dist/shared/providers/proxyCatalog.js.map +0 -1
  321. package/dist/shared/providers/sharedProviderConfirmation.d.ts +0 -802
  322. package/dist/shared/providers/sharedProviderConfirmation.d.ts.map +0 -1
  323. package/dist/shared/providers/sharedProviderConfirmation.js +0 -1865
  324. package/dist/shared/providers/sharedProviderConfirmation.js.map +0 -1
  325. package/dist/shared/types/providerConfirmation.d.ts +0 -334
  326. package/dist/shared/types/providerConfirmation.d.ts.map +0 -1
  327. package/dist/shared/types/providerConfirmation.js +0 -35
  328. package/dist/shared/types/providerConfirmation.js.map +0 -1
@@ -1,658 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { mkdir } from "node:fs/promises";
3
- import { dirname, join } from "node:path";
4
- import { writeJsonFile } from "../io/json.js";
5
- import { resolveAuditCodeStateDir } from "../io/stateDir.js";
6
- import { validateSessionConfig } from "../validation/sessionConfig.js";
7
- /**
8
- * The POPULATE cache for the proxy lane: discovered via `GET <proxy>/v1/models` +
9
- * `GET <proxy>/model/info` (OpenAI-compatible surfaces) expanded into ready-to-fold
10
- * `claude-worker` {@link DispatchableSource}s, written once per populate (Gate-0 build
11
- * / explicit refresh) and READ by resolve — never fetched mid-resolve.
12
- *
13
- * Named `catalog-cache.json`, NOT the `catalog-<auditor-id>.json` the reserved-name
14
- * comment at `auditorSources.ts` anticipates: populate/resolve run on the AMBIENT
15
- * path, where no auditor id exists to key on. The cache is machine-level like the
16
- * declaration beside it — and it is a CACHE of live discovery state, not resolved
17
- * per-auditor capability, so an auditor-id key would assert an isolation the data
18
- * doesn't have. Per-auditor never-inherit still holds: every resolve re-proves proxy
19
- * REACH itself; the cache only supplies the expansion.
20
- */
21
- export const PROXY_CATALOG_FILENAME = "catalog-cache.json";
22
- /** Top-K models expanded per backend provider when the operator declares no `top_k`. */
23
- export const DEFAULT_PROXY_TOP_K = 3;
24
- /**
25
- * USD-per-token → USD-per-million-tokens conversion factor. LiteLLM
26
- * `/model/info` advertises `input_cost_per_token` / `output_cost_per_token`
27
- * per TOKEN; the `cost_per_mtok` source field is per MILLION tokens
28
- * (INV-SCC-03 unit discipline).
29
- */
30
- const PER_TOKEN_TO_PER_MTOK = 1_000_000;
31
- /** Timeout (ms) for populate-time model verification probes. */
32
- export const POPULATE_PROBE_TIMEOUT_MS = 3000;
33
- /** Concurrency limit for populate-time model verification probes. */
34
- export const POPULATE_PROBE_CONCURRENCY = 4;
35
- /**
36
- * A same-endpoint cache younger than this skips the registry fetch + probes
37
- * entirely (see the freshness short-circuit in {@link populateProxyCatalog}).
38
- * Refresh THROTTLE — how often we are willing to PAY for a refresh, which is a
39
- * different question from how long an unrefreshed cache may still be believed
40
- * ({@link PROXY_CATALOG_MAX_READ_AGE_MS}). The two thresholds share one age
41
- * derivation and nothing else.
42
- */
43
- export const POPULATE_CACHE_FRESH_TTL_MS = 10 * 60_000;
44
- /**
45
- * Past this age a cache is returned STALE by {@link readProxyCatalog} — the
46
- * expansion still travels, with a verdict attached, so the caller decides rather
47
- * than consuming an unbounded-age cache blind.
48
- *
49
- * A day: comfortably longer than any single run (a mid-run flip to stale would be
50
- * churn, not signal), and short enough that a roster the operator changed — a model
51
- * added to or dropped from the proxy config — cannot go unnoticed indefinitely.
52
- * The cache is machine-global and tool-written, so being wrong here costs one
53
- * repopulate, never data.
54
- */
55
- export const PROXY_CATALOG_MAX_READ_AGE_MS = 24 * 60 * 60_000;
56
- /** Resolve the populate-cache path for this machine (state dir via `io/stateDir.ts`). */
57
- export function resolveProxyCatalogPath(homeDir) {
58
- return join(resolveAuditCodeStateDir(homeDir), PROXY_CATALOG_FILENAME);
59
- }
60
- /**
61
- * Shape version of the on-disk cache. Bump whenever a written entry's meaning changes,
62
- * so a cache written by older code degrades to "absent" rather than being read under
63
- * new semantics. The cache is tool-written and fully regenerable, so degrading is free —
64
- * `readProxyCatalog` returns null, and `populateProxyCatalogIfMissing` then repopulates.
65
- *
66
- * v2: entries no longer carry a tool-stamped `id`. A v1 entry does, and since `id` now
67
- * outranks the `service` derivation in `dispatchableSourceId`, reading one under v2
68
- * semantics would key a proxied lane to `claude-worker:<service>/<model>` instead of
69
- * `<service>/<model>` — silently splitting it from the direct lane to the same backend
70
- * and reopening the double-grant boundary.
71
- */
72
- export const PROXY_CATALOG_VERSION = 2;
73
- function finiteNonNegative(value) {
74
- return typeof value === "number" && Number.isFinite(value) && value >= 0
75
- ? value
76
- : undefined;
77
- }
78
- /**
79
- * Edge adapter: proxy `/model/info` response shape → neutral ModelAdvert contract.
80
- * Maps from proxy-specific field names to a generic contract.
81
- *
82
- * Returns:
83
- * - { advert, filtered: false | undefined } for valid models
84
- * - { filtered: true } for models filtered by eligibility rules
85
- * - undefined for invalid data (missing modelName/model_info)
86
- */
87
- function adaptProxyModelInfo(modelInfo) {
88
- const modelName = modelInfo.model_name;
89
- if (typeof modelName !== "string" || modelName.trim().length === 0)
90
- return undefined;
91
- const info = modelInfo.model_info;
92
- if (info === null || typeof info !== "object")
93
- return undefined;
94
- const infoObj = info;
95
- const proxyProvider = infoObj.litellm_provider;
96
- const provider = typeof proxyProvider === "string" ? proxyProvider : undefined;
97
- const mode = infoObj.mode;
98
- const supportsToolCalls = infoObj.supports_tool_calls;
99
- // Check eligibility: mode must be "chat" or absent (unknown ≠ incapable).
100
- // supports_tool_calls false → skip; absent or true → keep.
101
- if (typeof mode === "string" && mode !== "chat")
102
- return { filtered: true };
103
- if (supportsToolCalls === false)
104
- return { filtered: true };
105
- return {
106
- advert: {
107
- alias: modelName.trim(),
108
- ...(provider !== undefined ? { provider } : {}),
109
- context_tokens: finiteNonNegative(infoObj.max_input_tokens),
110
- input_cost_per_token: finiteNonNegative(infoObj.input_cost_per_token),
111
- output_cost_per_token: finiteNonNegative(infoObj.output_cost_per_token),
112
- mode: typeof mode === "string" ? mode : undefined,
113
- supports_tool_calls: typeof supportsToolCalls === "boolean" ? supportsToolCalls : undefined,
114
- // Operator-declared rank via advert custom key (consumed as-is when present).
115
- declared_rank: finiteNonNegative(infoObj.capability_rank),
116
- },
117
- };
118
- }
119
- /**
120
- * Best-effort "higher = better" score for ranking. Uses a flat `score` field
121
- * when present; absent/non-finite → null (unscored models rank last).
122
- */
123
- function deriveScore(entry) {
124
- if (typeof entry.score === "number" && Number.isFinite(entry.score)) {
125
- return entry.score;
126
- }
127
- return null;
128
- }
129
- /**
130
- * Tolerantly extract context-window field from a registry entry or its nested
131
- * `capability` block. Checks for `context_length`, `context_tokens`, `max_context`,
132
- * `context_window` (first finite positive number wins).
133
- */
134
- function deriveContextTokens(entry) {
135
- for (const key of ["context_length", "context_tokens", "max_context", "context_window"]) {
136
- const value = finiteNonNegative(entry[key]);
137
- if (value !== undefined)
138
- return value;
139
- }
140
- const capability = entry.capability;
141
- if (capability !== null && typeof capability === "object") {
142
- for (const key of ["context_length", "context_tokens", "max_context", "context_window"]) {
143
- const value = finiteNonNegative(capability[key]);
144
- if (value !== undefined)
145
- return value;
146
- }
147
- }
148
- return undefined;
149
- }
150
- /**
151
- * Discover models via the neutral proxy contract: `GET /v1/models` (required baseline)
152
- * returns the alias roster; `GET /model/info` (optional enrichment) provides per-model
153
- * metadata. Tolerant at every step: missing/unparseable/malformed → graceful degradation.
154
- * Eligibility filters remove models from the pool entirely (mode != 'chat' or
155
- * supports_tool_calls === false).
156
- *
157
- * Returns { adverts, filtered } where adverts is a map of alias → ModelAdvert,
158
- * and filtered is a set of aliases that failed the eligibility filter.
159
- */
160
- async function discoverModelAdverts(endpoint, fetchImpl, authHeader) {
161
- const adverts = new Map();
162
- const filtered = new Set();
163
- try {
164
- const headers = { "content-type": "application/json" };
165
- if (authHeader)
166
- headers.authorization = authHeader;
167
- // Try /model/info first (richer advert); tolerate absent/unparseable
168
- try {
169
- const response = await fetchImpl(`${endpoint}/model/info`, { headers });
170
- if (response.ok) {
171
- const payload = await response.json();
172
- // Target the current array form: {data: [...]}
173
- if (payload !== null && typeof payload === "object") {
174
- const data = payload.data;
175
- if (Array.isArray(data)) {
176
- for (const raw of data) {
177
- if (raw !== null && typeof raw === "object") {
178
- const result = adaptProxyModelInfo(raw);
179
- if (result) {
180
- if (result.filtered) {
181
- // Track which aliases failed the eligibility filter
182
- const modelName = raw.model_name;
183
- if (typeof modelName === "string" && modelName.trim().length > 0) {
184
- filtered.add(modelName.trim());
185
- }
186
- }
187
- else if (result.advert) {
188
- // Successful adaptation
189
- adverts.set(result.advert.alias, result.advert);
190
- }
191
- }
192
- // If result is undefined, it's invalid data (not eligibility filter)
193
- }
194
- }
195
- }
196
- }
197
- }
198
- }
199
- catch {
200
- // /model/info absent/error → degrade to roster-only (no enrichment)
201
- }
202
- }
203
- catch {
204
- // No adverts → degrade to roster-only (carrier of aliases only)
205
- }
206
- return { adverts, filtered };
207
- }
208
- /**
209
- * Discover the model roster via the neutral proxy contract: `GET /v1/models`
210
- * (OpenAI-compatible list). Returns { aliases, error? }; absent/unparseable → empty aliases + reason.
211
- */
212
- async function discoverModelRoster(endpoint, fetchImpl, authHeader) {
213
- try {
214
- const headers = { "content-type": "application/json" };
215
- if (authHeader)
216
- headers.authorization = authHeader;
217
- const response = await fetchImpl(`${endpoint}/v1/models`, { headers });
218
- if (!response.ok) {
219
- return {
220
- aliases: [],
221
- error: `HTTP ${response.status}`,
222
- };
223
- }
224
- const payload = await response.json();
225
- if (payload === null || typeof payload !== "object")
226
- return { aliases: [] };
227
- const data = payload.data;
228
- if (!Array.isArray(data))
229
- return { aliases: [] };
230
- const aliases = [];
231
- for (const entry of data) {
232
- if (entry !== null && typeof entry === "object") {
233
- const id = entry.id;
234
- if (typeof id === "string" && id.trim().length > 0) {
235
- aliases.push(id.trim());
236
- }
237
- }
238
- }
239
- return { aliases };
240
- }
241
- catch (err) {
242
- const reason = err instanceof Error ? err.message : String(err);
243
- return { aliases: [], error: reason };
244
- }
245
- }
246
- /**
247
- * Expand discovered models into `claude-worker` sources: per backend provider,
248
- * the top-K models by best-effort score (higher = better; unscored last).
249
- * Shape per the plan's identity section: the transport never enters the identity —
250
- * `service` + `model` (alias) key quota; `endpoint` is the proxy url the
251
- * launch transport fronts the spawn with; `worker_kind` is `agentic` by definition.
252
- *
253
- * Provider derivation: advert `provider` (from /model/info) > slash-prefix of alias
254
- * (e.g., `anthropic/claude-*` → `anthropic`) > default shared `"proxy"` bucket
255
- * (coarse pool identity at degradation rung, revisitable per owner decision).
256
- */
257
- function expandSources(discovered, options) {
258
- const byProvider = new Map();
259
- // Dedup by (provider, alias): first row wins.
260
- const seen = new Set();
261
- for (const model of discovered) {
262
- const identity = `${model.provider}/${model.alias}`;
263
- if (seen.has(identity))
264
- continue;
265
- seen.add(identity);
266
- const bucket = byProvider.get(model.provider) ?? [];
267
- bucket.push(model);
268
- byProvider.set(model.provider, bucket);
269
- }
270
- const sources = [];
271
- const dropped = [];
272
- // Stable, content-derived order (provider, then score desc, then alias) so
273
- // re-populate over identical state emits byte-identical sources.
274
- for (const provider of [...byProvider.keys()].sort()) {
275
- const bucket = byProvider.get(provider);
276
- const ordered = bucket.sort((a, b) => (b.score ?? Number.NEGATIVE_INFINITY) - (a.score ?? Number.NEGATIVE_INFINITY) ||
277
- a.alias.localeCompare(b.alias));
278
- const ranked = ordered.slice(0, options.topK);
279
- // Truncation is never silent. With every `score` null the comparator's first
280
- // term is NaN (-Infinity - -Infinity), so the sort degenerates to
281
- // `localeCompare` and `top_k` keeps the ALPHABETICALLY-first models — which
282
- // once kept a *flash* model and dropped every frontier one. The order still
283
- // has to be deterministic (a content-derived array order is an artifact
284
- // invariant), so the fix is not a different order: it is that every model
285
- // top_k discards reaches the operator through the same `dropped[]` channel
286
- // as an unreachable model, and says WHICH basis chose it.
287
- const rankedCount = ordered.filter((m) => m.score !== null).length;
288
- const basis = rankedCount === 0
289
- ? `no model in this backend advertises a rank, so the cut is ALPHABETICAL, not by capability`
290
- : rankedCount === ordered.length
291
- ? `cut by advertised rank`
292
- : `only ${rankedCount} of ${ordered.length} models advertise a rank; unranked models sort last and were cut alphabetically among themselves`;
293
- for (const model of ordered.slice(options.topK)) {
294
- dropped.push({
295
- id: `claude-worker:${provider}/${model.alias}`,
296
- reason: `top_k=${options.topK} kept ${ranked.length} of ${ordered.length} models for backend ` +
297
- `"${provider}" — ${basis}. Raise proxy.top_k, or advertise a rank, to keep it.`,
298
- });
299
- }
300
- for (const model of ranked) {
301
- // Cost precedence: operator-declared (free-to-operator axis) > advert price
302
- // > absent (falls through to models.dev catalog / tier downstream).
303
- const cost = options.costPerMtok ?? model.costPerMtok;
304
- sources.push({
305
- // No `id` stamped, deliberately. `id` is an operator OVERRIDE that outranks
306
- // derivation (spec/backend-identity-axes.md); a tool-stamped transport-shaped
307
- // id would claim that override and re-split the very identity `service` exists
308
- // to merge — a proxied lane and a direct lane to the same service+model would
309
- // key to two CapacityPools, reopening the double-grant boundary. Leaving it
310
- // absent lets `dispatchableSourceId` derive `service[#account]/model`, which
311
- // dedups the two lanes by construction.
312
- transport: "claude-worker",
313
- endpoint: options.endpoint,
314
- service: provider,
315
- model: model.alias, // Alias VERBATIM — the proxy's routing key
316
- worker_kind: "agentic",
317
- ...(options.apiKeyEnv !== undefined ? { api_key_env: options.apiKeyEnv } : {}),
318
- ...(cost !== undefined ? { cost_per_mtok: cost } : {}),
319
- // Step C: per-model capability (raw rank, LOWER = better) rides the source →
320
- // CapacityPool.declaredCapabilityRank → the admission capability floor.
321
- ...(model.capabilityRank !== undefined
322
- ? { capability_rank: model.capabilityRank }
323
- : {}),
324
- ...(model.contextTokens !== undefined
325
- ? { quota: { context_tokens: model.contextTokens } }
326
- : {}),
327
- });
328
- }
329
- }
330
- return { sources, dropped };
331
- }
332
- /**
333
- * Probe a single model via Anthropic-compatible `/v1/messages` to verify it is
334
- * actually reachable. Returns { dropped: true, reason } if the model should be
335
- * excluded (404/unavailable), or { dropped: false } to keep it.
336
- */
337
- async function probeModelViaMessages(alias, backendProvider, endpoint, fetchImpl, timeoutMs, authHeader) {
338
- const controller = new AbortController();
339
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
340
- try {
341
- const headers = {
342
- "anthropic-version": "2023-06-01",
343
- "content-type": "application/json",
344
- };
345
- if (authHeader)
346
- headers.authorization = authHeader;
347
- const response = await fetchImpl(`${endpoint}/v1/messages`, {
348
- method: "POST",
349
- headers,
350
- body: JSON.stringify({
351
- model: alias, // Route on proxy alias, not backend namespace
352
- max_tokens: 1,
353
- messages: [{ role: "user", content: "hi" }],
354
- }),
355
- signal: controller.signal,
356
- });
357
- // Check for 404 or model-not-found patterns
358
- if (response.status === 404) {
359
- return { dropped: true, reason: "HTTP 404" };
360
- }
361
- if (response.status >= 400) {
362
- let bodyText = "";
363
- try {
364
- bodyText = await response.text();
365
- }
366
- catch {
367
- // If body read fails, just use status code
368
- }
369
- const patterns = [/model_not_found/i, /\bmay not exist\b/i, /no such model/i];
370
- if (patterns.some((p) => p.test(bodyText))) {
371
- return { dropped: true, reason: `HTTP ${response.status} (model unavailable)` };
372
- }
373
- }
374
- // 200, 401, 429, 5xx, etc. → keep the model
375
- return { dropped: false };
376
- }
377
- catch (error) {
378
- // Transport failure (timeout, network error) → fail-open, keep the model
379
- return { dropped: false };
380
- }
381
- finally {
382
- clearTimeout(timeoutId);
383
- }
384
- }
385
- /**
386
- * Probe models with bounded concurrency using a simple worker pool.
387
- */
388
- async function probeModelsWithConcurrency(aliases, backendProvider, endpoint, fetchImpl, timeoutMs, concurrency, authHeader) {
389
- const results = [];
390
- let index = 0;
391
- const worker = async () => {
392
- while (index < aliases.length) {
393
- const currentIndex = index++;
394
- const alias = aliases[currentIndex];
395
- const probeResult = await probeModelViaMessages(alias, backendProvider, endpoint, fetchImpl, timeoutMs, authHeader);
396
- results[currentIndex] = {
397
- alias,
398
- dropped: probeResult.dropped,
399
- reason: probeResult.reason,
400
- };
401
- }
402
- };
403
- const workers = Array(Math.min(concurrency, aliases.length))
404
- .fill(null)
405
- .map(() => worker());
406
- await Promise.all(workers);
407
- // results is index-assigned, so already in order.
408
- return results;
409
- }
410
- /**
411
- * POPULATE: discover and expand models from an OpenAI-compatible proxy
412
- * (`GET <endpoint>/v1/models` + `GET <endpoint>/model/info`) into
413
- * ready-to-fold `claude-worker` sources, then write to the machine-level cache.
414
- * Network-bound and cacheable — runs at Gate-0 build / explicit refresh, NEVER
415
- * inside `resolveAmbientSources` (which only READS via {@link readProxyCatalog}).
416
- * Never throws: a failed fetch returns `{written:false, reason}` and leaves any
417
- * prior cache untouched; an empty discovery WRITES an empty expansion (fresh
418
- * knowledge — resolve reports the lane unexpanded with a reason).
419
- */
420
- export async function populateProxyCatalog(options) {
421
- const endpoint = options.endpoint.replace(/\/+$/u, "");
422
- const doFetch = options.fetchImpl ?? fetch;
423
- // Build auth header when api_key_env is declared and the env var is set.
424
- let authHeader;
425
- if (options.apiKeyEnv) {
426
- const keyValue = process.env[options.apiKeyEnv];
427
- if (keyValue?.trim()) {
428
- authHeader = `Bearer ${keyValue}`;
429
- }
430
- }
431
- // Freshness short-circuit: populate carries live per-model probes — real
432
- // `/v1/messages` POSTs through the proxy that cost seconds and burn quota.
433
- // A same-endpoint cache younger than the TTL answers instead. This is a
434
- // REFRESH throttle, a tighter window than the read-side staleness bound;
435
- // the age itself comes from the ONE derivation in `readProxyCatalog` (on this
436
- // call's clock), so the two thresholds can never disagree about how old a file is.
437
- const cached = readProxyCatalog({ homeDir: options.homeDir, now: options.now });
438
- if (cached &&
439
- cached.endpoint === endpoint &&
440
- cached.age_ms >= 0 &&
441
- cached.age_ms < POPULATE_CACHE_FRESH_TTL_MS) {
442
- return {
443
- sources: cached.sources,
444
- written: false,
445
- reason: `cache is fresh (fetched ${Math.round(cached.age_ms / 1000)}s ago); refresh skipped.`,
446
- dropped: [],
447
- };
448
- }
449
- // Discover model roster (required baseline: /v1/models).
450
- const { aliases, error: rosterError } = await discoverModelRoster(endpoint, doFetch, authHeader);
451
- if (aliases.length === 0) {
452
- return {
453
- sources: [],
454
- written: false,
455
- reason: rosterError
456
- ? `GET ${endpoint}/v1/models failed: ${rosterError}`
457
- : `GET ${endpoint}/v1/models returned no models or was unreachable.`,
458
- dropped: [],
459
- };
460
- }
461
- // Discover model adverts (optional enrichment: /model/info).
462
- const { adverts, filtered } = await discoverModelAdverts(endpoint, doFetch, authHeader);
463
- // Build discovered models: join roster + adverts, deriving provider.
464
- const discovered = [];
465
- for (const alias of aliases) {
466
- // Skip models that failed the eligibility filter
467
- if (filtered.has(alias))
468
- continue;
469
- const advert = adverts.get(alias);
470
- // Provider derivation: advert provider > slash-prefix > default "proxy" bucket.
471
- let provider;
472
- if (advert?.provider) {
473
- provider = advert.provider;
474
- }
475
- else if (alias.includes("/")) {
476
- provider = alias.split("/")[0];
477
- }
478
- else {
479
- provider = "proxy";
480
- }
481
- // Cost blend: mean of input/output when both present; otherwise the one
482
- // present. UNIT DISCIPLINE (INV-SCC-03 / COR-6c1ad0ba): the advert fields
483
- // (`input_cost_per_token` / `output_cost_per_token`, LiteLLM /model/info
484
- // convention) are USD per TOKEN; `cost_per_mtok`'s contract is USD per
485
- // MILLION tokens, so the blend converts ×1e6. Writing the per-token value
486
- // straight through underprices every proxied lane 1,000,000x and lets it
487
- // incorrectly dominate cost-first routing.
488
- const availableCosts = [];
489
- if (advert?.input_cost_per_token !== undefined) {
490
- availableCosts.push(advert.input_cost_per_token * PER_TOKEN_TO_PER_MTOK);
491
- }
492
- if (advert?.output_cost_per_token !== undefined) {
493
- availableCosts.push(advert.output_cost_per_token * PER_TOKEN_TO_PER_MTOK);
494
- }
495
- let costPerMtok;
496
- if (availableCosts.length === 2) {
497
- costPerMtok = (availableCosts[0] + availableCosts[1]) / 2;
498
- }
499
- else if (availableCosts.length === 1) {
500
- costPerMtok = availableCosts[0];
501
- }
502
- discovered.push({
503
- alias,
504
- provider,
505
- score: advert?.declared_rank !== undefined ? -advert.declared_rank : null,
506
- capabilityRank: advert?.declared_rank,
507
- costPerMtok,
508
- contextTokens: advert?.context_tokens,
509
- });
510
- }
511
- // Probe to verify models are reachable and drop 404s.
512
- // Group by provider for parallel probing per provider.
513
- const byProvider = new Map();
514
- for (const model of discovered) {
515
- const bucket = byProvider.get(model.provider) ?? [];
516
- bucket.push(model.alias);
517
- byProvider.set(model.provider, bucket);
518
- }
519
- const droppedAliases = new Set();
520
- const dropped = [];
521
- for (const [provider, providerAliases] of byProvider) {
522
- const probeResults = await probeModelsWithConcurrency(providerAliases, provider, endpoint, doFetch, POPULATE_PROBE_TIMEOUT_MS, POPULATE_PROBE_CONCURRENCY, authHeader);
523
- for (const result of probeResults) {
524
- if (result.dropped) {
525
- droppedAliases.add(result.alias);
526
- dropped.push({
527
- id: `claude-worker:${provider}/${result.alias}`,
528
- reason: result.reason ?? "model unavailable",
529
- });
530
- }
531
- }
532
- }
533
- // Filter out dropped models and expand sources.
534
- const toExpand = discovered.filter((m) => !droppedAliases.has(m.alias));
535
- const expanded = expandSources(toExpand, {
536
- endpoint,
537
- topK: options.topK ?? DEFAULT_PROXY_TOP_K,
538
- costPerMtok: options.costPerMtok,
539
- apiKeyEnv: options.apiKeyEnv,
540
- });
541
- const sources = expanded.sources;
542
- // top_k truncation joins the unreachable-model drops in the ONE operator-facing
543
- // channel, so "the frontier model isn't there" always carries its reason.
544
- dropped.push(...expanded.dropped);
545
- const catalog = {
546
- version: PROXY_CATALOG_VERSION,
547
- fetched_at: (options.now?.() ?? new Date()).toISOString(),
548
- endpoint,
549
- sources,
550
- };
551
- const path = resolveProxyCatalogPath(options.homeDir);
552
- try {
553
- await mkdir(dirname(path), { recursive: true });
554
- await writeJsonFile(path, catalog);
555
- }
556
- catch (error) {
557
- return {
558
- sources,
559
- written: false,
560
- reason: `could not write ${path}: ${error instanceof Error ? error.message : String(error)}.`,
561
- dropped,
562
- };
563
- }
564
- return { sources, written: true, dropped };
565
- }
566
- function defaultReadCatalogFile(path) {
567
- try {
568
- return readFileSync(path, "utf8");
569
- }
570
- catch {
571
- return null;
572
- }
573
- }
574
- /** Human-readable magnitude for an age/skew, in the largest unit that still reads. */
575
- function describeAgeMs(ms) {
576
- const abs = Math.abs(ms);
577
- if (abs < 90 * 60_000)
578
- return `${Math.round(abs / 60_000)}m`;
579
- if (abs < 48 * 3_600_000)
580
- return `${(abs / 3_600_000).toFixed(1)}h`;
581
- return `${(abs / 86_400_000).toFixed(1)}d`;
582
- }
583
- /**
584
- * READ the populate cache, with the read-side age rule applied: every result carries
585
- * `age_ms` and a `stale` verdict against {@link PROXY_CATALOG_MAX_READ_AGE_MS}, so an
586
- * old cache reaches its caller labelled rather than indistinguishable from a fresh one.
587
- * Degrades to `null` on absent / unparseable / structurally-invalid content — never
588
- * throws (same bar as `readSourceDeclaration`: the resolve path must not be failable by
589
- * a bad file). Cached sources are held to the shared source validator, so a hand-edited
590
- * or version-skewed cache degrades to "no cache" rather than admitting a half-checked
591
- * pool.
592
- */
593
- export function readProxyCatalog(deps = {}) {
594
- const path = resolveProxyCatalogPath(deps.homeDir);
595
- const raw = (deps.readCatalogFile ?? defaultReadCatalogFile)(path);
596
- if (raw === null)
597
- return null;
598
- let parsed;
599
- try {
600
- parsed = JSON.parse(raw);
601
- }
602
- catch {
603
- return null;
604
- }
605
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
606
- return null;
607
- }
608
- const { version, fetched_at, endpoint, sources } = parsed;
609
- // Degrade a foreign-shape cache to "absent" rather than reading it under current
610
- // semantics. A pre-versioned (v1) file carries tool-stamped ids that would now
611
- // outrank the service derivation and re-split a proxied lane from its direct twin.
612
- // Regenerable by design — the caller repopulates.
613
- if (version !== PROXY_CATALOG_VERSION)
614
- return null;
615
- if (typeof fetched_at !== "string" || typeof endpoint !== "string")
616
- return null;
617
- if (!Array.isArray(sources))
618
- return null;
619
- // An unparseable stamp is a corrupt file, not an old one: no honest age can be
620
- // derived from it, and aging it from a substituted default would launder it into
621
- // a fresh-looking cache. Refuse it the way every other invalid field is refused.
622
- const fetchedMs = Date.parse(fetched_at);
623
- if (!Number.isFinite(fetchedMs))
624
- return null;
625
- const issues = validateSessionConfig({ sources });
626
- if (issues.some((issue) => issue.severity === "error"))
627
- return null;
628
- const ageMs = (deps.now?.() ?? new Date()).getTime() - fetchedMs;
629
- const catalog = {
630
- version,
631
- fetched_at,
632
- endpoint,
633
- sources: sources,
634
- age_ms: ageMs,
635
- };
636
- // A stamp ahead of our clock proves nothing about freshness — it is skew or a hand
637
- // edit, and believing it would pin the cache fresh for as long as the stamp says.
638
- // Not fresh and not refused: stale, with the anomaly named. Mirrors the populate
639
- // short-circuit, which likewise refuses to count a future stamp as young.
640
- if (ageMs < 0) {
641
- return {
642
- ...catalog,
643
- stale: true,
644
- stale_reason: `populate cache is stamped ${describeAgeMs(ageMs)} in the future ("${fetched_at}") ` +
645
- `relative to this machine's clock, so its age cannot be trusted — re-run the populate.`,
646
- };
647
- }
648
- if (ageMs >= PROXY_CATALOG_MAX_READ_AGE_MS) {
649
- return {
650
- ...catalog,
651
- stale: true,
652
- stale_reason: `populate cache was fetched ${describeAgeMs(ageMs)} ago (max ${describeAgeMs(PROXY_CATALOG_MAX_READ_AGE_MS)}), ` +
653
- `so its expansion may no longer match the proxy's roster — re-run the populate.`,
654
- };
655
- }
656
- return { ...catalog, stale: false };
657
- }
658
- //# sourceMappingURL=proxyCatalog.js.map