modelrig 0.8.1 → 0.10.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 (382) hide show
  1. package/CHANGELOG.md +324 -0
  2. package/README.md +55 -29
  3. package/data/PROVENANCE.md +14 -0
  4. package/data/pricing-map.json +26 -0
  5. package/dist/adapters/attempt-meta-map.d.ts +8 -1
  6. package/dist/adapters/attempt-meta-map.d.ts.map +1 -1
  7. package/dist/adapters/attempt-meta-map.js +4 -0
  8. package/dist/adapters/build-adapters.js +2 -2
  9. package/dist/adapters/gemini.d.ts +28 -1
  10. package/dist/adapters/gemini.d.ts.map +1 -1
  11. package/dist/adapters/gemini.js +38 -9
  12. package/dist/adapters/grok.d.ts.map +1 -1
  13. package/dist/adapters/grok.js +53 -4
  14. package/dist/adapters/openai-compat.d.ts +8 -2
  15. package/dist/adapters/openai-compat.d.ts.map +1 -1
  16. package/dist/adapters/openai-compat.js +8 -1
  17. package/dist/adapters/openai-stream.d.ts +10 -2
  18. package/dist/adapters/openai-stream.d.ts.map +1 -1
  19. package/dist/adapters/openai-stream.js +5 -1
  20. package/dist/adapters/openai.d.ts +10 -1
  21. package/dist/adapters/openai.d.ts.map +1 -1
  22. package/dist/adapters/openai.js +57 -7
  23. package/dist/adapters/responses-api.d.ts +61 -0
  24. package/dist/adapters/responses-api.d.ts.map +1 -0
  25. package/dist/adapters/responses-api.js +124 -0
  26. package/dist/artifacts/artifact.d.ts +31 -12
  27. package/dist/artifacts/artifact.d.ts.map +1 -1
  28. package/dist/artifacts/artifact.js +218 -188
  29. package/dist/artifacts/index.d.ts +15 -5
  30. package/dist/artifacts/index.d.ts.map +1 -1
  31. package/dist/artifacts/index.js +19 -5
  32. package/dist/artifacts/run-context.d.ts +3 -24
  33. package/dist/artifacts/run-context.d.ts.map +1 -1
  34. package/dist/artifacts/run-context.js +3 -39
  35. package/dist/bakeoff/engine.d.ts +29 -0
  36. package/dist/bakeoff/engine.d.ts.map +1 -1
  37. package/dist/bakeoff/engine.js +47 -0
  38. package/dist/bakeoff/eval-cases-rig.d.ts +36 -0
  39. package/dist/bakeoff/eval-cases-rig.d.ts.map +1 -0
  40. package/dist/bakeoff/eval-cases-rig.js +27 -0
  41. package/dist/bakeoff/eval-cases.d.ts +19 -0
  42. package/dist/bakeoff/eval-cases.d.ts.map +1 -1
  43. package/dist/bakeoff/eval-cases.js +18 -1
  44. package/dist/bakeoff/golden-judge.d.ts +47 -0
  45. package/dist/bakeoff/golden-judge.d.ts.map +1 -0
  46. package/dist/bakeoff/golden-judge.js +49 -0
  47. package/dist/bakeoff/groundedness.d.ts +58 -0
  48. package/dist/bakeoff/groundedness.d.ts.map +1 -0
  49. package/dist/bakeoff/groundedness.js +165 -0
  50. package/dist/bakeoff/quality.d.ts +43 -0
  51. package/dist/bakeoff/quality.d.ts.map +1 -1
  52. package/dist/bakeoff/quality.js +31 -0
  53. package/dist/bakeoff/replay.d.ts +11 -0
  54. package/dist/bakeoff/replay.d.ts.map +1 -1
  55. package/dist/bakeoff/replay.js +12 -0
  56. package/dist/bakeoff/report.d.ts +7 -0
  57. package/dist/bakeoff/report.d.ts.map +1 -1
  58. package/dist/bakeoff/report.js +26 -0
  59. package/dist/cli-judge.d.ts +12 -0
  60. package/dist/cli-judge.d.ts.map +1 -0
  61. package/dist/cli-judge.js +48 -0
  62. package/dist/cli.d.ts.map +1 -1
  63. package/dist/cli.js +22 -42
  64. package/dist/config.d.ts +85 -29
  65. package/dist/config.d.ts.map +1 -1
  66. package/dist/config.js +109 -47
  67. package/dist/detect/detector.d.ts +4 -2
  68. package/dist/detect/detector.d.ts.map +1 -1
  69. package/dist/detect/safe-harbor.d.ts +57 -0
  70. package/dist/detect/safe-harbor.d.ts.map +1 -0
  71. package/dist/detect/safe-harbor.js +278 -0
  72. package/dist/doctor-cli.d.ts +7 -0
  73. package/dist/doctor-cli.d.ts.map +1 -0
  74. package/dist/doctor-cli.js +67 -0
  75. package/dist/doctor-preflight.d.ts +52 -0
  76. package/dist/doctor-preflight.d.ts.map +1 -0
  77. package/dist/doctor-preflight.js +112 -0
  78. package/dist/feedback/coverage.d.ts +11 -8
  79. package/dist/feedback/coverage.d.ts.map +1 -1
  80. package/dist/feedback/coverage.js +29 -16
  81. package/dist/feedback/grade.d.ts +58 -0
  82. package/dist/feedback/grade.d.ts.map +1 -0
  83. package/dist/feedback/grade.js +67 -0
  84. package/dist/gateway/http-protocol.d.ts +20 -1
  85. package/dist/gateway/http-protocol.d.ts.map +1 -1
  86. package/dist/gateway/http-protocol.js +1 -1
  87. package/dist/gateway/serve-client.d.ts +99 -0
  88. package/dist/gateway/serve-client.d.ts.map +1 -0
  89. package/dist/gateway/serve-client.js +360 -0
  90. package/dist/index.d.ts +68 -29
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +167 -46
  93. package/dist/init/observe-cli.js +1 -1
  94. package/dist/init/observe.d.ts +2 -2
  95. package/dist/init/observe.d.ts.map +1 -1
  96. package/dist/init/observe.js +11 -7
  97. package/dist/mask/arm.d.ts +112 -0
  98. package/dist/mask/arm.d.ts.map +1 -0
  99. package/dist/mask/arm.js +179 -0
  100. package/dist/mask/index.d.ts +2 -0
  101. package/dist/mask/index.d.ts.map +1 -1
  102. package/dist/mask/index.js +7 -1
  103. package/dist/mask/mask-policy-cli.d.ts +12 -4
  104. package/dist/mask/mask-policy-cli.d.ts.map +1 -1
  105. package/dist/mask/mask-policy-cli.js +34 -9
  106. package/dist/prompts/manifest.d.ts +128 -0
  107. package/dist/prompts/manifest.d.ts.map +1 -0
  108. package/dist/prompts/manifest.js +214 -0
  109. package/dist/prompts/prompt-fetch.d.ts +84 -0
  110. package/dist/prompts/prompt-fetch.d.ts.map +1 -0
  111. package/dist/prompts/prompt-fetch.js +120 -0
  112. package/dist/prompts/prompt-resolve.d.ts +76 -0
  113. package/dist/prompts/prompt-resolve.d.ts.map +1 -0
  114. package/dist/prompts/prompt-resolve.js +93 -0
  115. package/dist/prompts/slot-resolver.d.ts +91 -0
  116. package/dist/prompts/slot-resolver.d.ts.map +1 -0
  117. package/dist/prompts/slot-resolver.js +135 -0
  118. package/dist/prompts/tools-register.d.ts +60 -0
  119. package/dist/prompts/tools-register.d.ts.map +1 -0
  120. package/dist/prompts/tools-register.js +48 -0
  121. package/dist/prompts/version-hash.d.ts +29 -0
  122. package/dist/prompts/version-hash.d.ts.map +1 -0
  123. package/dist/prompts/version-hash.js +54 -0
  124. package/dist/prompts-export-cli.d.ts +21 -0
  125. package/dist/prompts-export-cli.d.ts.map +1 -0
  126. package/dist/prompts-export-cli.js +130 -0
  127. package/dist/query/grammar.d.ts +2 -2
  128. package/dist/query/grammar.d.ts.map +1 -1
  129. package/dist/query/grammar.js +3 -1
  130. package/dist/query/saved.d.ts +13 -0
  131. package/dist/query/saved.d.ts.map +1 -1
  132. package/dist/query/saved.js +67 -1
  133. package/dist/receipts/types.d.ts +1 -1
  134. package/dist/receipts/types.d.ts.map +1 -1
  135. package/dist/receipts/types.js +1 -0
  136. package/dist/registry/build.d.ts +43 -1
  137. package/dist/registry/build.d.ts.map +1 -1
  138. package/dist/registry/build.js +52 -0
  139. package/dist/registry/cache-policy.d.ts.map +1 -1
  140. package/dist/registry/cache-policy.js +22 -0
  141. package/dist/registry/pricing-shape.d.ts +67 -8
  142. package/dist/registry/pricing-shape.d.ts.map +1 -1
  143. package/dist/registry/pricing-shape.js +50 -13
  144. package/dist/registry/pricing.d.ts +6 -5
  145. package/dist/registry/pricing.d.ts.map +1 -1
  146. package/dist/registry/pricing.js +9 -5
  147. package/dist/registry/rows.d.ts.map +1 -1
  148. package/dist/registry/rows.js +1 -0
  149. package/dist/registry/select.d.ts +6 -1
  150. package/dist/registry/select.d.ts.map +1 -1
  151. package/dist/registry/select.js +11 -0
  152. package/dist/routes/call-config.d.ts +199 -0
  153. package/dist/routes/call-config.d.ts.map +1 -0
  154. package/dist/routes/call-config.js +343 -0
  155. package/dist/routes/destination-loader.d.ts +16 -0
  156. package/dist/routes/destination-loader.d.ts.map +1 -0
  157. package/dist/routes/destination-loader.js +29 -0
  158. package/dist/routes/draft.d.ts +7 -0
  159. package/dist/routes/draft.d.ts.map +1 -1
  160. package/dist/routes/draft.js +12 -0
  161. package/dist/routes/loader.d.ts.map +1 -1
  162. package/dist/routes/loader.js +31 -56
  163. package/dist/routes/objective-loader.d.ts +12 -0
  164. package/dist/routes/objective-loader.d.ts.map +1 -1
  165. package/dist/routes/objective-loader.js +82 -0
  166. package/dist/routes/output-loader.d.ts +42 -0
  167. package/dist/routes/output-loader.d.ts.map +1 -0
  168. package/dist/routes/output-loader.js +112 -0
  169. package/dist/routes/prompt-loader.d.ts +49 -0
  170. package/dist/routes/prompt-loader.d.ts.map +1 -0
  171. package/dist/routes/prompt-loader.js +113 -0
  172. package/dist/routes/resolved-bundle.d.ts +69 -0
  173. package/dist/routes/resolved-bundle.d.ts.map +1 -1
  174. package/dist/routes/resolved-bundle.js +76 -0
  175. package/dist/routes/screen-loader.d.ts +29 -1
  176. package/dist/routes/screen-loader.d.ts.map +1 -1
  177. package/dist/routes/screen-loader.js +59 -1
  178. package/dist/run/accept.d.ts +5 -0
  179. package/dist/run/accept.d.ts.map +1 -1
  180. package/dist/run/accept.js +2 -0
  181. package/dist/run/cost.d.ts +31 -2
  182. package/dist/run/cost.d.ts.map +1 -1
  183. package/dist/run/cost.js +57 -2
  184. package/dist/run/destination.d.ts +39 -0
  185. package/dist/run/destination.d.ts.map +1 -0
  186. package/dist/run/destination.js +45 -0
  187. package/dist/run/dispatch.d.ts +67 -16
  188. package/dist/run/dispatch.d.ts.map +1 -1
  189. package/dist/run/dispatch.js +133 -29
  190. package/dist/run/grounding.d.ts +28 -2
  191. package/dist/run/grounding.d.ts.map +1 -1
  192. package/dist/run/grounding.js +54 -10
  193. package/dist/run/raw-defaults.d.ts.map +1 -1
  194. package/dist/run/raw-defaults.js +6 -1
  195. package/dist/run/raw-finalize.d.ts +50 -0
  196. package/dist/run/raw-finalize.d.ts.map +1 -0
  197. package/dist/run/raw-finalize.js +266 -0
  198. package/dist/run/raw-knobs.d.ts +36 -0
  199. package/dist/run/raw-knobs.d.ts.map +1 -0
  200. package/dist/run/raw-knobs.js +99 -0
  201. package/dist/run/raw-request.d.ts +24 -0
  202. package/dist/run/raw-request.d.ts.map +1 -0
  203. package/dist/run/raw-request.js +69 -0
  204. package/dist/run/raw-schema.d.ts +76 -0
  205. package/dist/run/raw-schema.d.ts.map +1 -0
  206. package/dist/run/raw-schema.js +166 -0
  207. package/dist/run/raw-search-cost.d.ts +48 -0
  208. package/dist/run/raw-search-cost.d.ts.map +1 -0
  209. package/dist/run/raw-search-cost.js +78 -0
  210. package/dist/run/raw-support.d.ts +64 -6
  211. package/dist/run/raw-support.d.ts.map +1 -1
  212. package/dist/run/raw-support.js +84 -11
  213. package/dist/run/raw.d.ts +25 -26
  214. package/dist/run/raw.d.ts.map +1 -1
  215. package/dist/run/raw.js +30 -351
  216. package/dist/run/record-attempt.d.ts +12 -0
  217. package/dist/run/record-attempt.d.ts.map +1 -1
  218. package/dist/run/record-attempt.js +30 -12
  219. package/dist/run/repair-attempt.d.ts +68 -0
  220. package/dist/run/repair-attempt.d.ts.map +1 -0
  221. package/dist/run/repair-attempt.js +180 -0
  222. package/dist/run/resolve-destination.d.ts +49 -0
  223. package/dist/run/resolve-destination.d.ts.map +1 -0
  224. package/dist/run/resolve-destination.js +76 -0
  225. package/dist/run/routing-docs.d.ts.map +1 -1
  226. package/dist/run/routing-docs.js +10 -5
  227. package/dist/run/run.d.ts +30 -1
  228. package/dist/run/run.d.ts.map +1 -1
  229. package/dist/run/run.js +94 -172
  230. package/dist/run/telemetry-row.d.ts +92 -0
  231. package/dist/run/telemetry-row.d.ts.map +1 -0
  232. package/dist/run/telemetry-row.js +81 -0
  233. package/dist/run/validate.d.ts +12 -1
  234. package/dist/run/validate.d.ts.map +1 -1
  235. package/dist/run/validate.js +31 -0
  236. package/dist/run/web-search-pricing.d.ts +43 -0
  237. package/dist/run/web-search-pricing.d.ts.map +1 -0
  238. package/dist/run/web-search-pricing.js +56 -0
  239. package/dist/savings/odometer.d.ts +4 -0
  240. package/dist/savings/odometer.d.ts.map +1 -1
  241. package/dist/savings/odometer.js +20 -1
  242. package/dist/savings/swap-execute.d.ts +7 -0
  243. package/dist/savings/swap-execute.d.ts.map +1 -1
  244. package/dist/savings/swap-execute.js +30 -0
  245. package/dist/savings/swaps-client.d.ts +10 -0
  246. package/dist/savings/swaps-client.d.ts.map +1 -1
  247. package/dist/screen/fixtures/probe-corpora.d.ts +23 -0
  248. package/dist/screen/fixtures/probe-corpora.d.ts.map +1 -0
  249. package/dist/screen/fixtures/probe-corpora.js +69 -0
  250. package/dist/screen/index.d.ts +21 -0
  251. package/dist/screen/index.d.ts.map +1 -0
  252. package/dist/screen/index.js +64 -0
  253. package/dist/screen/model-screen.d.ts +111 -0
  254. package/dist/screen/model-screen.d.ts.map +1 -0
  255. package/dist/screen/model-screen.js +178 -0
  256. package/dist/screen/patterns.d.ts +51 -0
  257. package/dist/screen/patterns.d.ts.map +1 -0
  258. package/dist/screen/patterns.js +151 -0
  259. package/dist/screen/phi-gate.d.ts +52 -0
  260. package/dist/screen/phi-gate.d.ts.map +1 -0
  261. package/dist/screen/phi-gate.js +86 -0
  262. package/dist/screen/policy.d.ts +71 -0
  263. package/dist/screen/policy.d.ts.map +1 -0
  264. package/dist/screen/policy.js +334 -0
  265. package/dist/screen/probe.d.ts +118 -0
  266. package/dist/screen/probe.d.ts.map +1 -0
  267. package/dist/screen/probe.js +132 -0
  268. package/dist/screen/screen-cli.d.ts +16 -0
  269. package/dist/screen/screen-cli.d.ts.map +1 -0
  270. package/dist/screen/screen-cli.js +178 -0
  271. package/dist/screen/screen.d.ts +99 -0
  272. package/dist/screen/screen.d.ts.map +1 -0
  273. package/dist/screen/screen.js +0 -0
  274. package/dist/screen/sidecar.d.ts +69 -0
  275. package/dist/screen/sidecar.d.ts.map +1 -0
  276. package/dist/screen/sidecar.js +145 -0
  277. package/dist/screen/types.d.ts +43 -2
  278. package/dist/screen/types.d.ts.map +1 -1
  279. package/dist/settings/api-fetcher.d.ts +14 -3
  280. package/dist/settings/api-fetcher.d.ts.map +1 -1
  281. package/dist/settings/api-fetcher.js +61 -10
  282. package/dist/settings/grading-guide.d.ts +102 -0
  283. package/dist/settings/grading-guide.d.ts.map +1 -0
  284. package/dist/settings/grading-guide.js +23 -0
  285. package/dist/settings/index.d.ts +3 -2
  286. package/dist/settings/index.d.ts.map +1 -1
  287. package/dist/settings/index.js +2 -1
  288. package/dist/settings/task-settings.d.ts +44 -7
  289. package/dist/settings/task-settings.d.ts.map +1 -1
  290. package/dist/settings/task-settings.js +53 -12
  291. package/dist/status.d.ts +32 -20
  292. package/dist/status.d.ts.map +1 -1
  293. package/dist/status.js +51 -30
  294. package/dist/telemetry/artifact-store.d.ts +5 -6
  295. package/dist/telemetry/artifact-store.d.ts.map +1 -1
  296. package/dist/telemetry/artifact-store.js +21 -24
  297. package/dist/telemetry/attempt-meta.d.ts +76 -1
  298. package/dist/telemetry/attempt-meta.d.ts.map +1 -1
  299. package/dist/telemetry/attempt-meta.js +33 -1
  300. package/dist/telemetry/exporter-setup.d.ts +2 -0
  301. package/dist/telemetry/exporter-setup.d.ts.map +1 -1
  302. package/dist/telemetry/exporter-setup.js +6 -1
  303. package/dist/telemetry/exporter.d.ts +2 -2
  304. package/dist/telemetry/exporter.d.ts.map +1 -1
  305. package/dist/telemetry/exporter.js +22 -41
  306. package/dist/telemetry/grade-mappers.d.ts +25 -0
  307. package/dist/telemetry/grade-mappers.d.ts.map +1 -0
  308. package/dist/telemetry/grade-mappers.js +64 -0
  309. package/dist/telemetry/grades-sql.d.ts +56 -0
  310. package/dist/telemetry/grades-sql.d.ts.map +1 -0
  311. package/dist/telemetry/grades-sql.js +156 -0
  312. package/dist/telemetry/ingest-protocol.d.ts +55 -48
  313. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  314. package/dist/telemetry/ingest-protocol.js +48 -83
  315. package/dist/telemetry/org-identity.d.ts +44 -0
  316. package/dist/telemetry/org-identity.d.ts.map +1 -0
  317. package/dist/telemetry/org-identity.js +98 -0
  318. package/dist/telemetry/route-registration.d.ts +48 -0
  319. package/dist/telemetry/route-registration.d.ts.map +1 -0
  320. package/dist/telemetry/route-registration.js +77 -0
  321. package/dist/telemetry/sinks.d.ts +10 -1
  322. package/dist/telemetry/sinks.d.ts.map +1 -1
  323. package/dist/telemetry/sinks.js +52 -6
  324. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  325. package/dist/telemetry/store-migrations.js +64 -0
  326. package/dist/telemetry/store.d.ts +9 -6
  327. package/dist/telemetry/store.d.ts.map +1 -1
  328. package/dist/telemetry/store.js +8 -50
  329. package/dist/tenancy/api-key-format.d.ts +45 -1
  330. package/dist/tenancy/api-key-format.d.ts.map +1 -1
  331. package/dist/tenancy/api-key-format.js +50 -1
  332. package/dist/types/provider.d.ts +23 -3
  333. package/dist/types/provider.d.ts.map +1 -1
  334. package/dist/types/provider.js +26 -0
  335. package/dist/types/route.d.ts +154 -2
  336. package/dist/types/route.d.ts.map +1 -1
  337. package/dist/types/telemetry.d.ts +73 -48
  338. package/dist/types/telemetry.d.ts.map +1 -1
  339. package/dist/validate-cli.d.ts.map +1 -1
  340. package/dist/validate-cli.js +32 -0
  341. package/dist/verify/verify.d.ts +5 -0
  342. package/dist/verify/verify.d.ts.map +1 -1
  343. package/dist/verify/verify.js +2 -0
  344. package/dist/watch/evaluate.d.ts +84 -1
  345. package/dist/watch/evaluate.d.ts.map +1 -1
  346. package/dist/watch/evaluate.js +28 -0
  347. package/dist/watch/monitor.d.ts +8 -2
  348. package/dist/watch/monitor.d.ts.map +1 -1
  349. package/dist/watch/monitor.js +13 -5
  350. package/dist/watch/regret-monitor-cli.d.ts +16 -0
  351. package/dist/watch/regret-monitor-cli.d.ts.map +1 -0
  352. package/dist/watch/regret-monitor-cli.js +137 -0
  353. package/dist/watch/regret-monitor.d.ts +168 -0
  354. package/dist/watch/regret-monitor.d.ts.map +1 -0
  355. package/dist/watch/regret-monitor.js +304 -0
  356. package/dist/watch/upgrade.d.ts +62 -0
  357. package/dist/watch/upgrade.d.ts.map +1 -0
  358. package/dist/watch/upgrade.js +57 -0
  359. package/docs/agent-skill/SKILL.md +181 -9
  360. package/docs/how-it-fits.md +3 -2
  361. package/docs/instrumentation.md +219 -46
  362. package/docs/llms.txt +158 -35
  363. package/docs/managed-serving.md +150 -0
  364. package/docs/mcp-oracle.md +322 -0
  365. package/docs/migration-playbook.md +5 -4
  366. package/docs/quickstart.md +263 -89
  367. package/package.json +18 -4
  368. package/registry/leaderboard.json +74 -0
  369. package/registry/registry.json +232 -112
  370. package/screen.d.ts +16 -0
  371. package/templates/call-disposition-qa.json +77 -0
  372. package/templates/catalog-cleansing.json +77 -0
  373. package/templates/compliance-review.json +76 -0
  374. package/templates/cs-next-action.json +76 -0
  375. package/templates/document-extraction.json +78 -0
  376. package/templates/financial-classification.json +75 -0
  377. package/templates/lead-qualification.json +76 -0
  378. package/templates/medical-classification.json +71 -0
  379. package/templates/ticket-triage.json +76 -0
  380. package/dist/feedback/feedback.d.ts +0 -35
  381. package/dist/feedback/feedback.d.ts.map +0 -1
  382. package/dist/feedback/feedback.js +0 -39
@@ -0,0 +1,278 @@
1
+ "use strict";
2
+ /**
3
+ * SC-2 — deterministic HIPAA Safe Harbor identifier detectors (spec-sc2 §2).
4
+ *
5
+ * These EXTEND scrub v1 (S1: extend, don't twin) as new `Detector`s over the
6
+ * shared interface (src/detect/detector.ts) — `kind: "deterministic"`,
7
+ * `confidence: 1`. They live BESIDE scrub v1's five detectors, NOT inside
8
+ * `scrubContent`: adding one here never widens the custody scrub floor. The
9
+ * composition point is the screen policy `detectors: [...]` opt-in — a route
10
+ * turns a Safe Harbor detector on explicitly (spec-sc2 §2, builder confirmation:
11
+ * "never silently added to the custody scrub floor"). `scrubContent` and the
12
+ * five v1 detectors stay BYTE-IDENTICAL (golden).
13
+ *
14
+ * LEAK INVARIANT (spec-sc2 §1.4): a detector emits SPANS (offsets + type +
15
+ * confidence) — never the matched substring. The matched value is read only to
16
+ * validate (Luhn / VIN checksum / octet range / age bound) and is discarded
17
+ * immediately; nothing here returns, logs, or persists it.
18
+ *
19
+ * SCOPE (honest limits, stated in the coverage table): US-English-shaped,
20
+ * deterministic regex/checksum rules — NOT a model, NOT DLP. Free-text names and
21
+ * street addresses need the customer-run sidecar (src/screen/sidecar.ts); those
22
+ * classes are `sidecar`/`out-of-scope` in the coverage table, never claimed here.
23
+ * Recall on synthetic PHI is MEASURED by the SC-3 probe, not asserted here.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.SAFE_HARBOR_IDS = exports.SAFE_HARBOR_PACK_ID = void 0;
27
+ exports.vinValid = vinValid;
28
+ exports.isSafeHarborId = isSafeHarborId;
29
+ exports.safeHarborDetectors = safeHarborDetectors;
30
+ exports.safeHarborDetectorById = safeHarborDetectorById;
31
+ exports.safeHarborIdentities = safeHarborIdentities;
32
+ exports.safeHarborDetectorsFor = safeHarborDetectorsFor;
33
+ const scrub_1 = require("../artifacts/scrub");
34
+ /** Cap on spans emitted per detect() call ("per-result cap", spec-sc2 §2): a
35
+ * pathological document (a table of dates) yields COUNTS, not an unbounded span
36
+ * list. The count is truthful up to the cap; beyond it the receipt reads
37
+ * "≥ cap", which is enough to drive a policy action. */
38
+ const MAX_SPANS_PER_TYPE = 512;
39
+ /** Push a `{start,end,type,confidence:1}` span, capped. The matched value is the
40
+ * caller's local variable; it never enters the span. */
41
+ function emit(spans, type, start, len) {
42
+ if (spans.length >= MAX_SPANS_PER_TYPE)
43
+ return;
44
+ spans.push({ start, end: start + len, type, confidence: 1 });
45
+ }
46
+ /** Iterate global-regex matches with a fresh RegExp (so /g `lastIndex` never
47
+ * leaks across calls), passing each match to `onMatch`. */
48
+ function eachMatch(re, text, onMatch) {
49
+ const fresh = new RegExp(re.source, re.flags);
50
+ for (const m of text.matchAll(fresh))
51
+ onMatch(m[0], m.index ?? 0);
52
+ }
53
+ // ── date (Safe Harbor: dates except year — DOB/admit/discharge/death) ────────
54
+ // ISO YYYY-MM-DD, US M/D/YYYY (and M/D/YY), and month-name forms. Many formats;
55
+ // per-result cap applies. Conservative: a bare 4-digit year alone is NOT a date
56
+ // (Safe Harbor exempts the year), so the year-only shape is deliberately unmatched.
57
+ const MONTHS = "jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:t(?:ember)?)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?";
58
+ const DATE_ISO = /\b\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])\b/g;
59
+ const DATE_NUMERIC = /\b(?:0?[1-9]|1[0-2])[/-](?:0?[1-9]|[12]\d|3[01])[/-](?:\d{4}|\d{2})\b/g;
60
+ const DATE_MONTH_DMY = new RegExp(`\\b(?:0?[1-9]|[12]\\d|3[01])\\s+(?:${MONTHS})\\.?\\s+\\d{4}\\b`, "gi");
61
+ const DATE_MONTH_MDY = new RegExp(`\\b(?:${MONTHS})\\.?\\s+(?:0?[1-9]|[12]\\d|3[01])(?:st|nd|rd|th)?,?\\s+\\d{4}\\b`, "gi");
62
+ function findDates(text) {
63
+ const spans = [];
64
+ for (const re of [DATE_ISO, DATE_NUMERIC, DATE_MONTH_DMY, DATE_MONTH_MDY]) {
65
+ eachMatch(re, text, (v, i) => emit(spans, "pii:date", i, v.length));
66
+ }
67
+ return spans;
68
+ }
69
+ // ── age-over-89 (Safe Harbor: ages > 89 / dates indicating such age) ─────────
70
+ // A numeric RULE, not a regex-of-content (spec-sc2 §2): match an age-shaped
71
+ // phrase, then ACCEPT only when the number is 90..130 (a sane human-age bound —
72
+ // above it the digits are almost certainly something else, e.g. a quantity).
73
+ const AGE_CONTEXT = /\b(?:age[ds]?|aged)\b[^.\n\d]{0,12}(\d{2,3})\b|\b(\d{2,3})[ -]?(?:years?[ -]old|y(?:rs?)?[ -]?old|yo)\b/gi;
74
+ function findAgeOver89(text) {
75
+ const spans = [];
76
+ const fresh = new RegExp(AGE_CONTEXT.source, AGE_CONTEXT.flags);
77
+ for (const m of text.matchAll(fresh)) {
78
+ const digits = m[1] ?? m[2];
79
+ if (digits === undefined)
80
+ continue;
81
+ const n = Number.parseInt(digits, 10);
82
+ if (n < 90 || n > 130)
83
+ continue; // Safe Harbor floor is >89; 130 caps a human age
84
+ emit(spans, "pii:age_over_89", m.index ?? 0, m[0].length);
85
+ }
86
+ return spans;
87
+ }
88
+ // ── fax (Safe Harbor: telephone / fax) — label-anchored phone shape ──────────
89
+ const FAX = /\bfax\b[^.\n]{0,8}(?:\+?\d{1,3}[ .-]?)?(?:\(\d{3}\)|\d{3})[ .-]\d{3}[ .-]\d{4}\b/gi;
90
+ function findFax(text) {
91
+ const spans = [];
92
+ eachMatch(FAX, text, (v, i) => emit(spans, "pii:fax", i, v.length));
93
+ return spans;
94
+ }
95
+ // ── url (Safe Harbor: URLs; also the INJ-6 egress channel) ───────────────────
96
+ const URL_RE = /\bhttps?:\/\/[^\s"'<>)\]\\]+/gi;
97
+ function findUrls(text) {
98
+ const spans = [];
99
+ eachMatch(URL_RE, text, (v, i) => {
100
+ // Trim trailing sentence punctuation from the span length (the URL proper).
101
+ const trimmed = v.replace(/[.,;:]+$/, "");
102
+ emit(spans, "pii:url", i, trimmed.length);
103
+ });
104
+ return spans;
105
+ }
106
+ // ── ip (Safe Harbor: IP addresses) — v4 (octet-validated) + v6 ───────────────
107
+ const IPV4 = /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/g;
108
+ const IPV6 = /\b(?:[0-9A-Fa-f]{1,4}:){2,7}[0-9A-Fa-f]{1,4}\b|\b(?:[0-9A-Fa-f]{1,4}:){1,7}:\b/g;
109
+ function findIps(text) {
110
+ const spans = [];
111
+ eachMatch(IPV4, text, (v, i) => emit(spans, "pii:ip", i, v.length));
112
+ eachMatch(IPV6, text, (v, i) => {
113
+ if (v.split(":").filter((p) => p !== "").length < 2)
114
+ return; // avoid a lone "a:" style false hit
115
+ emit(spans, "pii:ip", i, v.length);
116
+ });
117
+ return spans;
118
+ }
119
+ // ── zip (Safe Harbor: geographic < state) — conservative, anchored ───────────
120
+ // A bare 5-digit run floods (SSN-comment doctrine). Claimed only when it is
121
+ // distinctive: ZIP+4 (`12345-6789`), a `ST 12345` state+ZIP shape, or a labelled
122
+ // `ZIP: 12345`. Street/city need the sidecar and are NOT claimed here.
123
+ const ZIP_PLUS4 = /\b\d{5}-\d{4}\b/g;
124
+ const ZIP_STATE = /\b[A-Z]{2}\s+\d{5}(?:-\d{4})?\b/g;
125
+ const ZIP_LABELLED = /\bzip(?:\s*code)?\b[^.\n\d]{0,6}\d{5}(?:-\d{4})?\b/gi;
126
+ function findZips(text) {
127
+ const spans = [];
128
+ for (const re of [ZIP_PLUS4, ZIP_STATE, ZIP_LABELLED]) {
129
+ eachMatch(re, text, (v, i) => emit(spans, "pii:zip", i, v.length));
130
+ }
131
+ return spans;
132
+ }
133
+ // ── account-id (health-plan beneficiary #, account #, member #) ──────────────
134
+ // Label-anchored + shape — a bare id is unclaimable without a false-positive
135
+ // flood, so the label is what makes it real (the MRN doctrine).
136
+ const ACCOUNT_ID = /\b(?:account|acct|member|beneficiary|policy|plan|subscriber|group)\b[^.\n]{0,4}(?:id|no|number|#)?\s*[:#]?\s*[A-Za-z0-9][A-Za-z0-9-]{3,}\b/gi;
137
+ function findAccountIds(text) {
138
+ const spans = [];
139
+ eachMatch(ACCOUNT_ID, text, (v, i) => emit(spans, "pii:account_id", i, v.length));
140
+ return spans;
141
+ }
142
+ // ── license-id (certificate / license #) — label-anchored ────────────────────
143
+ const LICENSE_ID = /\b(?:licen[cs]e|licence|certificate|cert|permit|dl|driver'?s?\s+licen[cs]e)\b[^.\n]{0,4}(?:id|no|number|#)?\s*[:#]?\s*[A-Za-z0-9][A-Za-z0-9-]{3,}\b/gi;
144
+ function findLicenseIds(text) {
145
+ const spans = [];
146
+ eachMatch(LICENSE_ID, text, (v, i) => emit(spans, "pii:license_id", i, v.length));
147
+ return spans;
148
+ }
149
+ // ── vin (vehicle identifier) — 17-char VIN with check-digit validation ───────
150
+ const VIN_SHAPE = /\b[A-HJ-NPR-Z0-9]{17}\b/gi;
151
+ const VIN_TRANSLIT = {
152
+ A: 1, B: 2, C: 3, D: 4, E: 5, F: 6, G: 7, H: 8, J: 1, K: 2, L: 3, M: 4, N: 5,
153
+ P: 7, R: 9, S: 2, T: 3, U: 4, V: 5, W: 6, X: 7, Y: 8, Z: 9,
154
+ };
155
+ const VIN_WEIGHTS = [8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2];
156
+ /** VIN check-digit rule (position 9): weighted transliteration mod 11, where 10
157
+ * ⇒ 'X'. Separates a real VIN from any 17 alphanumerics — the difference between
158
+ * a false-positive flood and a claimable identifier. */
159
+ function vinValid(vin) {
160
+ const up = vin.toUpperCase();
161
+ if (up.length !== 17)
162
+ return false;
163
+ let sum = 0;
164
+ for (let i = 0; i < 17; i += 1) {
165
+ const ch = up[i];
166
+ const val = ch >= "0" && ch <= "9" ? ch.charCodeAt(0) - 48 : VIN_TRANSLIT[ch];
167
+ if (val === undefined)
168
+ return false; // I/O/Q or other invalid char
169
+ sum += val * VIN_WEIGHTS[i];
170
+ }
171
+ const check = sum % 11;
172
+ const expected = check === 10 ? "X" : String(check);
173
+ return up[8] === expected;
174
+ }
175
+ function findVins(text) {
176
+ const spans = [];
177
+ eachMatch(VIN_SHAPE, text, (v, i) => {
178
+ if (!vinValid(v))
179
+ return;
180
+ emit(spans, "pii:vin", i, v.length);
181
+ });
182
+ return spans;
183
+ }
184
+ // ── device-id (device / serial identifiers) — MAC, IMEI, label-anchored ──────
185
+ const MAC = /\b(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}\b/g;
186
+ const IMEI_LABELLED = /\bimei\b[^.\n\d]{0,6}(\d{15})\b/gi;
187
+ const DEVICE_LABELLED = /\b(?:device|serial|udid|s\/n|sn)\b[^.\n]{0,4}(?:id|no|number|#)?\s*[:#]?\s*[A-Za-z0-9][A-Za-z0-9-]{3,}\b/gi;
188
+ function findDeviceIds(text) {
189
+ const spans = [];
190
+ eachMatch(MAC, text, (v, i) => emit(spans, "pii:device_id", i, v.length));
191
+ const fresh = new RegExp(IMEI_LABELLED.source, IMEI_LABELLED.flags);
192
+ for (const m of text.matchAll(fresh)) {
193
+ const digits = m[1];
194
+ if (digits === undefined || !(0, scrub_1.luhnValid)(digits))
195
+ continue; // IMEI is Luhn-valid
196
+ emit(spans, "pii:device_id", m.index ?? 0, m[0].length);
197
+ }
198
+ eachMatch(DEVICE_LABELLED, text, (v, i) => emit(spans, "pii:device_id", i, v.length));
199
+ return spans;
200
+ }
201
+ /** The SC-2 deterministic rule set (spec-sc2 §2). One rule per Safe Harbor class
202
+ * that has a deterministic shape; names/streets/biometrics are NOT here (they
203
+ * are `sidecar`/`out-of-scope` in the coverage table). */
204
+ const RULES = [
205
+ { type: "pii:date", id: "pii:date@v1", find: findDates },
206
+ { type: "pii:age_over_89", id: "pii:age_over_89@v1", find: findAgeOver89 },
207
+ { type: "pii:fax", id: "pii:fax@v1", find: findFax },
208
+ { type: "pii:url", id: "pii:url@v1", find: findUrls },
209
+ { type: "pii:ip", id: "pii:ip@v1", find: findIps },
210
+ { type: "pii:zip", id: "pii:zip@v1", find: findZips },
211
+ { type: "pii:account_id", id: "pii:account_id@v1", find: findAccountIds },
212
+ { type: "pii:license_id", id: "pii:license_id@v1", find: findLicenseIds },
213
+ { type: "pii:vin", id: "pii:vin@v1", find: findVins },
214
+ { type: "pii:device_id", id: "pii:device_id@v1", find: findDeviceIds },
215
+ ];
216
+ /** The registry id of the convenience PACK — configuring it in a route's
217
+ * `screen.detectors` opts in to ALL ten deterministic Safe Harbor detectors at
218
+ * once. Individual `pii:<slug>@v1` ids opt in to just that one. */
219
+ exports.SAFE_HARBOR_PACK_ID = "safe-harbor@v1";
220
+ /** All individual Safe Harbor detector ids, for policy validation. */
221
+ exports.SAFE_HARBOR_IDS = new Set(RULES.map((r) => r.id));
222
+ /** True when `id` names a Safe Harbor detector (a `pii:<slug>@v1` or the pack).
223
+ * The screen policy validator uses this to accept the id at load time. */
224
+ function isSafeHarborId(id) {
225
+ return id === exports.SAFE_HARBOR_PACK_ID || exports.SAFE_HARBOR_IDS.has(id);
226
+ }
227
+ /** Collapse overlapping spans of one detector to a single span so one underlying
228
+ * identifier matched by two sub-patterns (e.g. a ZIP caught by both the ZIP+4 and
229
+ * the state+ZIP rule) COUNTS ONCE. Sorted by start; a span whose range overlaps a
230
+ * kept span is dropped (conservative — an adjacency undercount is cheaper than a
231
+ * double count). Offsets only, never a value. */
232
+ function dedupeOverlaps(spans) {
233
+ const sorted = [...spans].sort((a, b) => a.start - b.start || b.end - a.end);
234
+ const kept = [];
235
+ let lastEnd = -1;
236
+ for (const s of sorted) {
237
+ if (s.start >= lastEnd) {
238
+ kept.push(s);
239
+ lastEnd = s.end;
240
+ }
241
+ }
242
+ return kept;
243
+ }
244
+ /** Wrap one rule as the shared `Detector` (kind deterministic, confidence 1). */
245
+ function detectorFor(rule) {
246
+ return {
247
+ id: rule.id,
248
+ kind: "deterministic",
249
+ detect: (input) => Promise.resolve(dedupeOverlaps(rule.find(input.text))),
250
+ };
251
+ }
252
+ /** Every Safe Harbor detector (one per type), for the pack. */
253
+ function safeHarborDetectors() {
254
+ return RULES.map(detectorFor);
255
+ }
256
+ /** The Safe Harbor detector for one id, or `undefined` if `id` is not one.
257
+ * `SAFE_HARBOR_PACK_ID` returns `undefined` here — the pack expands to
258
+ * {@link safeHarborDetectors}; a caller resolves the pack separately. */
259
+ function safeHarborDetectorById(id) {
260
+ const rule = RULES.find((r) => r.id === id);
261
+ return rule === undefined ? undefined : detectorFor(rule);
262
+ }
263
+ /** The detector identities (id + version) for a configured id — the pack
264
+ * expands to all ten; a single id resolves to one. Used to populate the
265
+ * receipt's `detectors[]` at resolve time (counts-only, never a value). */
266
+ function safeHarborIdentities(id) {
267
+ if (id === exports.SAFE_HARBOR_PACK_ID)
268
+ return RULES.map((r) => ({ id: r.id, version: "v1" }));
269
+ return exports.SAFE_HARBOR_IDS.has(id) ? [{ id, version: "v1" }] : [];
270
+ }
271
+ /** Build the Detector instances a configured id contributes (pack ⇒ all ten,
272
+ * single id ⇒ one, unknown ⇒ none). */
273
+ function safeHarborDetectorsFor(id) {
274
+ if (id === exports.SAFE_HARBOR_PACK_ID)
275
+ return safeHarborDetectors();
276
+ const one = safeHarborDetectorById(id);
277
+ return one === undefined ? [] : [one];
278
+ }
@@ -0,0 +1,7 @@
1
+ export interface DoctorOptions {
2
+ /** `--expect-org <slug>`: exit 1 (printing both slugs) when the key resolves
3
+ * to a different org — the valid-key-wrong-org mistake (M5). */
4
+ readonly expectOrg?: string;
5
+ }
6
+ export declare function runDoctorCli(opts?: DoctorOptions): Promise<number>;
7
+ //# sourceMappingURL=doctor-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-cli.d.ts","sourceRoot":"","sources":["../src/doctor-cli.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,aAAa;IAC5B;oEACgE;IAChE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAsB,YAAY,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuD5E"}
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runDoctorCli = runDoctorCli;
4
+ /**
5
+ * `modelrig doctor` — confirm, in one line, which org your key resolves to.
6
+ *
7
+ * The explicit form of the auth preflight (doctor-preflight.ts). Run it in a
8
+ * worker shell, a CI step, or setup step 0 to answer the question the missing-
9
+ * runs incident could not: is MODELRIG_API_KEY valid, and does it belong to the
10
+ * org you expect? Prints the key → org line (✓ on success, a loud line on 401),
11
+ * and exits non-zero when the key is rejected or unconfirmed — so CI can gate on
12
+ * it. `--expect-org <slug>` also exits non-zero when the key is valid but
13
+ * belongs to a different org, printing both slugs. Never prints the key itself
14
+ * (last-four only, via redactApiKey).
15
+ *
16
+ * Kept in its own file so cli.ts stays under the LOC cap (like status-cli.ts).
17
+ */
18
+ const config_1 = require("./config");
19
+ const doctor_preflight_1 = require("./doctor-preflight");
20
+ async function runDoctorCli(opts = {}) {
21
+ // An empty --expect-org would make the gate unable to fail: refuse it (usage error).
22
+ if (opts.expectOrg !== undefined && opts.expectOrg.trim() === "") {
23
+ console.error("[modelrig] usage: modelrig doctor --expect-org <slug> — the slug must not be empty.");
24
+ return 2;
25
+ }
26
+ // config.ts is the only env reader (package-invariants) — doctor reads the
27
+ // resolved config, never process.env.
28
+ const config = (0, config_1.loadConfigFromEnv)();
29
+ const exp = config.export;
30
+ if (exp === undefined) {
31
+ console.error([
32
+ "[modelrig] no export sink configured — nothing to check.",
33
+ "Set MODELRIG_API_KEY (a rig_sk_ key minted at app.modelrig.ai) so the SDK",
34
+ "ships telemetry to the hosted API. Without it, runs are recorded locally only.",
35
+ ].join("\n"));
36
+ return 1;
37
+ }
38
+ if (exp.mode !== "api") {
39
+ console.log(`[modelrig] export sink is ${exp.mode} mode (self-hosted control plane) — whoami is an ` +
40
+ "api-mode check, so there is nothing to confirm here.");
41
+ return 0;
42
+ }
43
+ const result = await (0, doctor_preflight_1.runAuthPreflight)(config);
44
+ if (result === null) {
45
+ // Unreachable given the api-mode guard above, but keep the contract total.
46
+ console.log("[modelrig] nothing to check.");
47
+ return 0;
48
+ }
49
+ if (result.ok) {
50
+ const expectOrg = opts.expectOrg?.trim();
51
+ if (expectOrg !== undefined && expectOrg !== "") {
52
+ const actual = (0, doctor_preflight_1.expectedOrgMismatch)(result, expectOrg);
53
+ if (actual !== null) {
54
+ console.error(`[modelrig] ORG MISMATCH: key resolves to «${actual}», --expect-org is «${expectOrg}» — ` +
55
+ `telemetry from this key lands in «${actual}». Mint a key in the «${expectOrg}» org at ` +
56
+ "app.modelrig.ai, or fix --expect-org.");
57
+ return 1;
58
+ }
59
+ console.log(`${result.line} (org «${expectOrg}» as expected)`);
60
+ return 0;
61
+ }
62
+ console.log(result.line);
63
+ return 0;
64
+ }
65
+ console.error(result.line);
66
+ return 1;
67
+ }
@@ -0,0 +1,52 @@
1
+ import type { RigConfig } from "./config";
2
+ /** The subset of the whoami response we render. Every field optional/narrowed —
3
+ * an older or partial server response degrades to "(unknown org)", never a throw. */
4
+ export interface WhoamiResponse {
5
+ readonly org?: {
6
+ readonly id?: string;
7
+ readonly name?: string;
8
+ readonly slug?: string;
9
+ } | null;
10
+ readonly key?: {
11
+ readonly name?: string;
12
+ readonly last_four?: string;
13
+ } | null;
14
+ readonly posture?: {
15
+ readonly telemetry?: string;
16
+ readonly artifact?: string;
17
+ } | null;
18
+ }
19
+ export interface PreflightResult {
20
+ readonly ok: boolean;
21
+ /** HTTP status, or "network_error" when the request never completed. */
22
+ readonly status: number | "network_error";
23
+ /** One human line, ready to print or log. */
24
+ readonly line: string;
25
+ readonly whoami?: WhoamiResponse;
26
+ }
27
+ /**
28
+ * Ask the configured api-mode sink who the key is. Returns null when there is
29
+ * nothing to check (no export, or a direct/local sink — whoami is an api-mode
30
+ * concept). Never throws: a network error is a `PreflightResult`, not an
31
+ * exception, so no caller can be broken by turning this on.
32
+ */
33
+ export declare function runAuthPreflight(config: RigConfig, fetchFn?: typeof fetch): Promise<PreflightResult | null>;
34
+ /**
35
+ * The shared whoami worker: `runAuthPreflight` (doctor) and the api sink's
36
+ * first-flush org line (telemetry/org-identity.ts) both call it, so the two
37
+ * surfaces can never disagree about what a key resolves to. Never throws.
38
+ */
39
+ export declare function whoamiPreflight(ingestUrl: string, apiKey: string, fetchFn?: typeof fetch,
40
+ /** Abort the request (e.g. `AbortSignal.timeout(ms)`) — the sink passes one so
41
+ * a hanging whoami can never hold the process open. An abort is a network_error. */
42
+ signal?: AbortSignal): Promise<PreflightResult>;
43
+ /** The org slug a successful preflight resolved, or null when unknown. */
44
+ export declare function resolvedOrgSlug(result: PreflightResult): string | null;
45
+ /**
46
+ * Compare a resolved org with the slug the caller expects (`--expect-org`,
47
+ * `createRig({ expectOrg })`). Returns null when they match, or the resolved
48
+ * slug (`"(unknown org)"` when the server did not name one) when they do not.
49
+ * Comparison is exact on the slug, case-insensitive — slugs are lowercase.
50
+ */
51
+ export declare function expectedOrgMismatch(result: PreflightResult, expectOrg: string): string | null;
52
+ //# sourceMappingURL=doctor-preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-preflight.d.ts","sourceRoot":"","sources":["../src/doctor-preflight.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C;qFACqF;AACrF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/F,QAAQ,CAAC,GAAG,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACvF;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAAC;IAC1C,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,SAAS,EACjB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAIjC;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,OAAO,KAAa;AAC7B;oFACoF;AACpF,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,eAAe,CAAC,CA6C1B;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAGtE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI7F"}
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAuthPreflight = runAuthPreflight;
4
+ exports.whoamiPreflight = whoamiPreflight;
5
+ exports.resolvedOrgSlug = resolvedOrgSlug;
6
+ exports.expectedOrgMismatch = expectedOrgMismatch;
7
+ /**
8
+ * Auth preflight — one legible line where there used to be silent 401 drops.
9
+ *
10
+ * Telemetry export is fire-and-forget by design: a rejected batch only logs a
11
+ * warning, never fails the pipeline. That is correct, but it means a WRONG key
12
+ * (revoked, or belonging to a different org than the console you watch) drops
13
+ * every run silently — the failure mode that cost InferWealth days of invisible
14
+ * telemetry. This module turns that into one line, by asking `GET /v1/whoami`
15
+ * (server-side companion) which org the key actually resolves to.
16
+ *
17
+ * `runAuthPreflight` is the pure worker: given the resolved config (config.ts is
18
+ * the only env reader) it returns a legible result and NEVER throws. `modelrig
19
+ * doctor` (doctor-cli.ts) calls it and prints the line — ✓ on success, a loud
20
+ * line on a rejected key.
21
+ *
22
+ * `--expect-org <slug>` (doctor) and `createRig({ expectOrg })` (the sink's
23
+ * first-flush line) compare the resolved slug with `expectedOrgMismatch` below.
24
+ *
25
+ * NOTE: there is no automatic boot-time preflight. The automatic safety net lives
26
+ * on the export path itself — the api sink emits a loud once-per-failure-streak
27
+ * "AUTH FAILED" warning when an ingest POST is rejected (telemetry/sinks.ts) — so
28
+ * a customer needs no extra env var: the one key they set is enough.
29
+ */
30
+ const api_key_format_1 = require("./tenancy/api-key-format");
31
+ /**
32
+ * Ask the configured api-mode sink who the key is. Returns null when there is
33
+ * nothing to check (no export, or a direct/local sink — whoami is an api-mode
34
+ * concept). Never throws: a network error is a `PreflightResult`, not an
35
+ * exception, so no caller can be broken by turning this on.
36
+ */
37
+ async function runAuthPreflight(config, fetchFn = fetch) {
38
+ const exp = config.export;
39
+ if (exp === undefined || exp.mode !== "api")
40
+ return null;
41
+ return whoamiPreflight(exp.ingestUrl, exp.apiKey, fetchFn);
42
+ }
43
+ /**
44
+ * The shared whoami worker: `runAuthPreflight` (doctor) and the api sink's
45
+ * first-flush org line (telemetry/org-identity.ts) both call it, so the two
46
+ * surfaces can never disagree about what a key resolves to. Never throws.
47
+ */
48
+ async function whoamiPreflight(ingestUrl, apiKey, fetchFn = fetch,
49
+ /** Abort the request (e.g. `AbortSignal.timeout(ms)`) — the sink passes one so
50
+ * a hanging whoami can never hold the process open. An abort is a network_error. */
51
+ signal) {
52
+ const base = new URL(ingestUrl).origin;
53
+ const redacted = (0, api_key_format_1.redactApiKey)(apiKey);
54
+ try {
55
+ const res = await fetchFn(`${base}/v1/whoami`, {
56
+ headers: { authorization: `Bearer ${apiKey}` },
57
+ ...(signal !== undefined ? { signal } : {}),
58
+ });
59
+ if (res.status === 401 || res.status === 403) {
60
+ return {
61
+ ok: false,
62
+ status: res.status,
63
+ line: `[modelrig] AUTH FAILED (${res.status}) at ${base} — key ${redacted} is not recognized ` +
64
+ "by this server/org. Check MODELRIG_API_KEY, and that the key belongs to the org you " +
65
+ "expect (mint it in that org at app.modelrig.ai). Telemetry is dropping silently until this is fixed.",
66
+ };
67
+ }
68
+ if (!res.ok) {
69
+ return {
70
+ ok: false,
71
+ status: res.status,
72
+ line: `[modelrig] whoami ${res.status} at ${base} — could not confirm the key's org (retry \`modelrig doctor\`).`,
73
+ };
74
+ }
75
+ const who = (await res.json().catch(() => null));
76
+ const org = who?.org ?? null;
77
+ const orgLabel = org !== null && org !== undefined ? `«${org.name ?? org.id ?? "?"}»` : "(unknown org)";
78
+ const posture = who?.posture?.telemetry;
79
+ const postureLabel = typeof posture === "string" ? `, telemetry: ${posture}` : "";
80
+ return {
81
+ ok: true,
82
+ status: res.status,
83
+ line: `[modelrig] key ${redacted} → org ${orgLabel} ✓ (ingest ${base}${postureLabel})`,
84
+ ...(who !== null ? { whoami: who } : {}),
85
+ };
86
+ }
87
+ catch (err) {
88
+ return {
89
+ ok: false,
90
+ status: "network_error",
91
+ line: `[modelrig] whoami unreachable at ${base} (${err instanceof Error ? err.message : String(err)}) — ` +
92
+ "export is fire-and-forget, so this only means the preflight could not confirm your key.",
93
+ };
94
+ }
95
+ }
96
+ /** The org slug a successful preflight resolved, or null when unknown. */
97
+ function resolvedOrgSlug(result) {
98
+ const slug = result.whoami?.org?.slug;
99
+ return typeof slug === "string" && slug !== "" ? slug : null;
100
+ }
101
+ /**
102
+ * Compare a resolved org with the slug the caller expects (`--expect-org`,
103
+ * `createRig({ expectOrg })`). Returns null when they match, or the resolved
104
+ * slug (`"(unknown org)"` when the server did not name one) when they do not.
105
+ * Comparison is exact on the slug, case-insensitive — slugs are lowercase.
106
+ */
107
+ function expectedOrgMismatch(result, expectOrg) {
108
+ const actual = resolvedOrgSlug(result);
109
+ if (actual !== null && actual.toLowerCase() === expectOrg.trim().toLowerCase())
110
+ return null;
111
+ return actual ?? "(unknown org)";
112
+ }
@@ -7,12 +7,12 @@
7
7
  * rung 0 — implicit signals only: conformance / repair / refusal rates
8
8
  * from telemetry. Enough confidence for schema-mechanical swap
9
9
  * proposals (the bake-off gate).
10
- * rung 1 — ≥ RUNG1_MIN_FEEDBACK human/agent verdicts attached: prose
10
+ * rung 1 — ≥ RUNG1_MIN_GRADES grades attached: prose
11
11
  * routes (no schema to conform to) gain proposal confidence.
12
12
  * rung 2+ — judges; Phase 5+, out of scope by decision.
13
13
  */
14
- import type { FeedbackRow, TelemetryRow } from "../types/telemetry";
15
- export declare const RUNG1_MIN_FEEDBACK = 10;
14
+ import type { GradeRow, TelemetryRow } from "../types/telemetry";
15
+ export declare const RUNG1_MIN_GRADES = 10;
16
16
  export interface RouteCoverage {
17
17
  readonly route: string;
18
18
  readonly samples: number;
@@ -20,13 +20,16 @@ export interface RouteCoverage {
20
20
  readonly conformRate: number;
21
21
  readonly repairRate: number;
22
22
  readonly refusalRate: number;
23
- readonly feedbackCount: number;
23
+ readonly gradeCount: number;
24
24
  readonly rung: 0 | 1;
25
25
  /** Teaching copy: what reaching the next rung takes and unlocks. */
26
26
  readonly nextRungUnlock: string;
27
27
  }
28
- /** Attribution: a feedback row joins a route via its inference id, or via
29
- * its episode key matching a row's run_id tag. Unmatched feedback attaches
30
- * to nothing (never guessed). */
31
- export declare function computeCoverage(rows: readonly TelemetryRow[], feedback: readonly FeedbackRow[]): RouteCoverage[];
28
+ /** Attribution: a grade joins a route via its inference-subject id, or via its
29
+ * run-subject (episode) id matching a row's run_id tag. Unmatched grades attach
30
+ * to nothing (never guessed). Grade primitive (R6): every grade counts toward
31
+ * readiness — a wrapper-written feedback IS now a grade, so this is
32
+ * behaviour-preserving for the Rung-1 threshold. Artifact-subject grades never
33
+ * resolve to a route here (no artifact→route join) and simply do not count. */
34
+ export declare function computeCoverage(rows: readonly TelemetryRow[], grades: readonly GradeRow[]): RouteCoverage[];
32
35
  //# sourceMappingURL=coverage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/feedback/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEpE,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;IACrB,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;iCAEiC;AACjC,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,YAAY,EAAE,EAC7B,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC/B,aAAa,EAAE,CA2EjB"}
1
+ {"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/feedback/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEjE,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;IACrB,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;;;;+EAK+E;AAC/E,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,YAAY,EAAE,EAC7B,MAAM,EAAE,SAAS,QAAQ,EAAE,GAC1B,aAAa,EAAE,CAqFjB"}