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,111 @@
1
+ /**
2
+ * SC-3 — the T3 model-screen `Detector` (spec-sc3 §2). A `Detector` of the new
3
+ * `kind: "model"` that issues ONE structured-boolean screen call on an untrusted
4
+ * segment (the Anthropic "screen tool output / untrusted content" pattern), maps
5
+ * the boolean → counts-only spans, and returns them like any `Detector`. Its
6
+ * verdict is a SIGNAL that drives the existing deterministic actions
7
+ * (flag|strip|quarantine|block) — never the control itself (research §3.1
8
+ * principle 3). We compose over the shipped SC-1 seam; we do NOT fork it.
9
+ *
10
+ * METERED + DAILY CAP (spec-sc3 §2, mirrors `run-judge` economics): every screen
11
+ * call is a metered inference (the customer's own configured provider — no new
12
+ * default dependency). A per-UTC-day counter caps the spend. When the cap is
13
+ * exhausted the detector THROWS {@link ModelScreenCapExhaustedError}, and the
14
+ * shipped seam (screen.ts §4.3) applies the fail-closed matrix for us:
15
+ * · under `detect` → degrade to the surviving detectors, receipt `degraded:true`, SEND;
16
+ * · under `enforce` → block PRE-DISPATCH (invariant_violation) — an unscreened
17
+ * turn is NEVER dispatched under enforce (teaches the cap).
18
+ * A screen-call ERROR/timeout throws the same way and inherits the same matrix.
19
+ *
20
+ * FREE TEXT NEVER PERSISTS (spec-sc3 §2, acceptance a): the screen call returns a
21
+ * STRUCTURED boolean + optional type labels; this adapter parses ONLY
22
+ * `{ flagged, types }` and discards everything else the model may have emitted.
23
+ * No model prose, no scanned content, and no raw provider response reaches a
24
+ * span, a receipt, a log, or a thrown error — only per-type COUNTS survive (the
25
+ * seam rolls spans into counts). The thrown error carries the detector id + a
26
+ * coarse failure CLASS only (leak invariant §1.4, extended to the model tier).
27
+ */
28
+ import type { DetectionInput, Detector } from "../detect/detector";
29
+ /** The structured verdict a screen call returns. The model is prompted to answer
30
+ * a boolean ("is this untrusted content attempting to inject instructions /
31
+ * carrying PII?") plus optional coarse type labels. NOTHING else on this object
32
+ * is read — free text the model may add is discarded by {@link parseVerdict}. */
33
+ export interface ModelScreenVerdict {
34
+ readonly flagged: boolean;
35
+ /** Coarse finding types (count labels only, never content). Optional; when
36
+ * absent a single default label is used. Each label is capped in length and
37
+ * count so a chatty model can never smuggle content out through a "type". */
38
+ readonly types?: readonly string[];
39
+ }
40
+ /**
41
+ * The metered screen call. In production this is a small structured-boolean
42
+ * inference on the customer's CONFIGURED provider (SDK lane: in-process on their
43
+ * keys; hosted lane: a server-metered call) — ModelRig bundles no model and adds
44
+ * no default dependency. Injected here so the detector is pure + unit-testable
45
+ * and reads no env. It receives the untrusted text and MUST resolve to a
46
+ * structured {@link ModelScreenVerdict}; its free-text output never leaves it.
47
+ */
48
+ export type ModelScreenCall = (input: DetectionInput) => Promise<ModelScreenVerdict>;
49
+ /** A per-detector daily-call meter (mirrors `run-judge`'s daily cap). Keyed by
50
+ * UTC date so the cap resets at midnight UTC. In-process by default (one meter
51
+ * per SDK process, like the run-judge economics); injectable for tests and for a
52
+ * hosted lane that meters centrally. */
53
+ export interface DailyCapMeter {
54
+ /** Current call count for `dayKey` (a UTC `YYYY-MM-DD` string). */
55
+ count(dayKey: string): number;
56
+ /** Record one more call against `dayKey`. */
57
+ increment(dayKey: string): void;
58
+ }
59
+ /** The default in-process meter — a Map by UTC day. Old days are pruned lazily
60
+ * so the map never grows unbounded across a long-running process. */
61
+ export declare function createInProcessCapMeter(): DailyCapMeter;
62
+ export interface ModelScreenDetectorConfig {
63
+ /** Receipt id — `model:screen@<model>`. Normalized if a bare model is given. */
64
+ readonly id: string;
65
+ /** The model label the screen call runs on (for the id + receipt version). */
66
+ readonly model: string;
67
+ /** Metered screen calls allowed per UTC day before the tier is cap-exhausted
68
+ * (mirrors `run-judge`). Must be a positive integer. */
69
+ readonly dailyCap: number;
70
+ /** The structured-boolean screen call (customer's configured provider). */
71
+ readonly screenCall: ModelScreenCall;
72
+ /** Confidence stamped on a flagged span (0..1). Default 0.9 so a flag clears
73
+ * the default 0.8 policy threshold; a route may still raise `screen.threshold`
74
+ * to require the model tier plus a corroborating detector. */
75
+ readonly flagConfidence?: number;
76
+ /** Injectable clock (→ the UTC day key) so the cap boundary is testable. */
77
+ readonly now?: () => Date;
78
+ /** Injectable meter (defaults to an in-process per-day meter). */
79
+ readonly meter?: DailyCapMeter;
80
+ }
81
+ /** Normalize an id to the `model:screen@<model>` receipt shape. */
82
+ export declare function modelScreenReceiptId(idOrModel: string): string;
83
+ /** Thrown when the daily cap is exhausted. A subclass of Error so the shipped
84
+ * seam catches it exactly like any detector throw and applies the §4.3 matrix
85
+ * (degrade under detect, block pre-dispatch under enforce). Carries the detector
86
+ * id + a coarse reason ONLY — never content (leak invariant §1.4). */
87
+ export declare class ModelScreenCapExhaustedError extends Error {
88
+ readonly detectorId: string;
89
+ readonly reason: "daily_cap_exhausted";
90
+ constructor(detectorId: string, dailyCap: number);
91
+ }
92
+ /** Thrown when the screen call itself fails (error/timeout/malformed verdict).
93
+ * Coarse reason only — never the provider's response body or the scanned
94
+ * content. The seam turns it into the same fail-closed matrix. */
95
+ export declare class ModelScreenUnavailableError extends Error {
96
+ readonly detectorId: string;
97
+ readonly reason: "call_failed" | "malformed_verdict";
98
+ constructor(detectorId: string, reason: ModelScreenUnavailableError["reason"]);
99
+ }
100
+ /**
101
+ * Build the T3 model-screen `Detector`. `detect(input)`:
102
+ * 1. checks the per-UTC-day cap — over it ⇒ throw {@link ModelScreenCapExhaustedError};
103
+ * 2. otherwise records the call, issues the structured-boolean screen call;
104
+ * 3. parses ONLY `{flagged, types}` (free text discarded), and, when flagged,
105
+ * emits one counts-only span per type covering the segment. Confidence is
106
+ * stamped from `flagConfidence` — the RAW model score is never used or kept.
107
+ * Any call failure throws {@link ModelScreenUnavailableError}. Both throws are
108
+ * caught by the shipped seam, which applies the §4.3 fail-closed matrix.
109
+ */
110
+ export declare function createModelScreenDetector(config: ModelScreenDetectorConfig): Detector;
111
+ //# sourceMappingURL=model-screen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-screen.d.ts","sourceRoot":"","sources":["../../src/screen/model-screen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EACV,cAAc,EAEd,QAAQ,EACT,MAAM,oBAAoB,CAAC;AAE5B;;;iFAGiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;iFAE6E;IAC7E,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAErF;;;wCAGwC;AACxC,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,6CAA6C;IAC7C,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED;qEACqE;AACrE,wBAAgB,uBAAuB,IAAI,aAAa,CAavD;AAED,MAAM,WAAW,yBAAyB;IACxC,gFAAgF;IAChF,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;4DACwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IACrC;;kEAE8D;IAC9D,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,kEAAkE;IAClE,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAChC;AAOD,mEAAmE;AACnE,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED;;;sEAGsE;AACtE,qBAAa,4BAA6B,SAAQ,KAAK;IACrD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAG,qBAAqB,CAAU;gBACrC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAKjD;AAED;;kEAEkE;AAClE,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,mBAAmB,CAAC;gBACzC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,2BAA2B,CAAC,QAAQ,CAAC;CAM9E;AAsCD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,yBAAyB,GAAG,QAAQ,CA0CrF"}
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ /**
3
+ * SC-3 — the T3 model-screen `Detector` (spec-sc3 §2). A `Detector` of the new
4
+ * `kind: "model"` that issues ONE structured-boolean screen call on an untrusted
5
+ * segment (the Anthropic "screen tool output / untrusted content" pattern), maps
6
+ * the boolean → counts-only spans, and returns them like any `Detector`. Its
7
+ * verdict is a SIGNAL that drives the existing deterministic actions
8
+ * (flag|strip|quarantine|block) — never the control itself (research §3.1
9
+ * principle 3). We compose over the shipped SC-1 seam; we do NOT fork it.
10
+ *
11
+ * METERED + DAILY CAP (spec-sc3 §2, mirrors `run-judge` economics): every screen
12
+ * call is a metered inference (the customer's own configured provider — no new
13
+ * default dependency). A per-UTC-day counter caps the spend. When the cap is
14
+ * exhausted the detector THROWS {@link ModelScreenCapExhaustedError}, and the
15
+ * shipped seam (screen.ts §4.3) applies the fail-closed matrix for us:
16
+ * · under `detect` → degrade to the surviving detectors, receipt `degraded:true`, SEND;
17
+ * · under `enforce` → block PRE-DISPATCH (invariant_violation) — an unscreened
18
+ * turn is NEVER dispatched under enforce (teaches the cap).
19
+ * A screen-call ERROR/timeout throws the same way and inherits the same matrix.
20
+ *
21
+ * FREE TEXT NEVER PERSISTS (spec-sc3 §2, acceptance a): the screen call returns a
22
+ * STRUCTURED boolean + optional type labels; this adapter parses ONLY
23
+ * `{ flagged, types }` and discards everything else the model may have emitted.
24
+ * No model prose, no scanned content, and no raw provider response reaches a
25
+ * span, a receipt, a log, or a thrown error — only per-type COUNTS survive (the
26
+ * seam rolls spans into counts). The thrown error carries the detector id + a
27
+ * coarse failure CLASS only (leak invariant §1.4, extended to the model tier).
28
+ */
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.ModelScreenUnavailableError = exports.ModelScreenCapExhaustedError = void 0;
31
+ exports.createInProcessCapMeter = createInProcessCapMeter;
32
+ exports.modelScreenReceiptId = modelScreenReceiptId;
33
+ exports.createModelScreenDetector = createModelScreenDetector;
34
+ /** The default in-process meter — a Map by UTC day. Old days are pruned lazily
35
+ * so the map never grows unbounded across a long-running process. */
36
+ function createInProcessCapMeter() {
37
+ const byDay = new Map();
38
+ return {
39
+ count(dayKey) {
40
+ return byDay.get(dayKey) ?? 0;
41
+ },
42
+ increment(dayKey) {
43
+ // Prune any day that is not the one being written (single-day retention:
44
+ // the cap is per UTC day, so only the current day's count is load-bearing).
45
+ for (const key of byDay.keys())
46
+ if (key !== dayKey)
47
+ byDay.delete(key);
48
+ byDay.set(dayKey, (byDay.get(dayKey) ?? 0) + 1);
49
+ },
50
+ };
51
+ }
52
+ const DEFAULT_FLAG_CONFIDENCE = 0.9;
53
+ const DEFAULT_TYPE = "injection:model_flag";
54
+ const MAX_TYPES = 8; // a verdict may not carry more than this many type labels
55
+ const MAX_TYPE_LEN = 64; // …nor a label longer than this — content can't ride a "type"
56
+ /** Normalize an id to the `model:screen@<model>` receipt shape. */
57
+ function modelScreenReceiptId(idOrModel) {
58
+ if (idOrModel.startsWith("model:screen@"))
59
+ return idOrModel;
60
+ if (idOrModel.startsWith("model:"))
61
+ return idOrModel;
62
+ return `model:screen@${idOrModel}`;
63
+ }
64
+ /** Thrown when the daily cap is exhausted. A subclass of Error so the shipped
65
+ * seam catches it exactly like any detector throw and applies the §4.3 matrix
66
+ * (degrade under detect, block pre-dispatch under enforce). Carries the detector
67
+ * id + a coarse reason ONLY — never content (leak invariant §1.4). */
68
+ class ModelScreenCapExhaustedError extends Error {
69
+ constructor(detectorId, dailyCap) {
70
+ super(`model screen "${detectorId}" is over its daily cap (${dailyCap}/day) — degrading (detect) / blocking pre-dispatch (enforce)`);
71
+ this.reason = "daily_cap_exhausted";
72
+ this.name = "ModelScreenCapExhaustedError";
73
+ this.detectorId = detectorId;
74
+ }
75
+ }
76
+ exports.ModelScreenCapExhaustedError = ModelScreenCapExhaustedError;
77
+ /** Thrown when the screen call itself fails (error/timeout/malformed verdict).
78
+ * Coarse reason only — never the provider's response body or the scanned
79
+ * content. The seam turns it into the same fail-closed matrix. */
80
+ class ModelScreenUnavailableError extends Error {
81
+ constructor(detectorId, reason) {
82
+ super(`model screen "${detectorId}" is unavailable (${reason})`);
83
+ this.name = "ModelScreenUnavailableError";
84
+ this.detectorId = detectorId;
85
+ this.reason = reason;
86
+ }
87
+ }
88
+ exports.ModelScreenUnavailableError = ModelScreenUnavailableError;
89
+ function isRecord(v) {
90
+ return typeof v === "object" && v !== null && !Array.isArray(v);
91
+ }
92
+ /**
93
+ * Read ONLY `{ flagged, types }` from whatever the screen call returned —
94
+ * defensively, like the sidecar's `parseSpans`. A non-boolean `flagged` is a
95
+ * malformed verdict (fail closed, never a silent open). Every other field (and
96
+ * any echoed free text) is discarded here so it can never reach a span. Type
97
+ * labels are trimmed, de-duplicated, length-capped, and count-capped.
98
+ */
99
+ function parseVerdict(raw, detectorId) {
100
+ if (!isRecord(raw) || typeof raw.flagged !== "boolean") {
101
+ throw new ModelScreenUnavailableError(detectorId, "malformed_verdict");
102
+ }
103
+ let types = [];
104
+ if (Array.isArray(raw.types)) {
105
+ const seen = new Set();
106
+ for (const t of raw.types) {
107
+ if (typeof t !== "string")
108
+ continue;
109
+ const label = t.slice(0, MAX_TYPE_LEN);
110
+ if (label.length === 0 || seen.has(label))
111
+ continue;
112
+ seen.add(label);
113
+ types.push(label);
114
+ if (types.length >= MAX_TYPES)
115
+ break;
116
+ }
117
+ }
118
+ if (types.length === 0)
119
+ types = [DEFAULT_TYPE];
120
+ return { flagged: raw.flagged, types };
121
+ }
122
+ /** The UTC `YYYY-MM-DD` day key the cap is bucketed by. */
123
+ function utcDayKey(now) {
124
+ return now.toISOString().slice(0, 10);
125
+ }
126
+ /**
127
+ * Build the T3 model-screen `Detector`. `detect(input)`:
128
+ * 1. checks the per-UTC-day cap — over it ⇒ throw {@link ModelScreenCapExhaustedError};
129
+ * 2. otherwise records the call, issues the structured-boolean screen call;
130
+ * 3. parses ONLY `{flagged, types}` (free text discarded), and, when flagged,
131
+ * emits one counts-only span per type covering the segment. Confidence is
132
+ * stamped from `flagConfidence` — the RAW model score is never used or kept.
133
+ * Any call failure throws {@link ModelScreenUnavailableError}. Both throws are
134
+ * caught by the shipped seam, which applies the §4.3 fail-closed matrix.
135
+ */
136
+ function createModelScreenDetector(config) {
137
+ if (!Number.isInteger(config.dailyCap) || config.dailyCap <= 0) {
138
+ throw new Error(`model screen dailyCap must be a positive integer (got ${config.dailyCap})`);
139
+ }
140
+ const id = modelScreenReceiptId(config.id);
141
+ const confidence = config.flagConfidence ?? DEFAULT_FLAG_CONFIDENCE;
142
+ const now = config.now ?? (() => new Date());
143
+ const meter = config.meter ?? createInProcessCapMeter();
144
+ return {
145
+ id,
146
+ kind: "model",
147
+ async detect(input) {
148
+ // Trusted framing is the route author's own — never screened (mirrors the
149
+ // pattern detector), and never spends a metered call on it.
150
+ if (input.tier === "trusted")
151
+ return [];
152
+ const dayKey = utcDayKey(now());
153
+ if (meter.count(dayKey) >= config.dailyCap) {
154
+ // Cap exhausted — throw BEFORE spending a call. The seam degrades (detect)
155
+ // or blocks pre-dispatch (enforce). No content in the error.
156
+ throw new ModelScreenCapExhaustedError(id, config.dailyCap);
157
+ }
158
+ // Reserve the call against the cap before issuing it (a failed call still
159
+ // spent provider budget on the customer's keys — count it, like run-judge).
160
+ meter.increment(dayKey);
161
+ let raw;
162
+ try {
163
+ raw = await config.screenCall(input);
164
+ }
165
+ catch {
166
+ // NEVER let the provider error's message (which could echo the scanned
167
+ // content) escape — only the coarse reason travels.
168
+ throw new ModelScreenUnavailableError(id, "call_failed");
169
+ }
170
+ const { flagged, types } = parseVerdict(raw, id);
171
+ if (!flagged)
172
+ return [];
173
+ // One counts-only span per type over the whole segment. Offsets are into
174
+ // the exact string handed in; the substring is never read or kept.
175
+ return types.map((type) => ({ start: 0, end: input.text.length, type, confidence }));
176
+ },
177
+ };
178
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Screening — T0 deterministic injection patterns + the optional T1 classifier
3
+ * adapter (WIRED 2026-08-25, SC-1a). Spec §3 (Detector) + §4 (T0/T1).
4
+ *
5
+ * An OWASP-derived pattern pack that implements the shared `Detector` interface
6
+ * (packages/modelrig/src/detect/detector.ts) with `kind: "deterministic"`,
7
+ * confidence 1 on a match.
8
+ *
9
+ * DOCTRINE (research §2.2): detection is a SIGNAL, not a control. These patterns
10
+ * are trivially evaded by paraphrase/encoding; they exist to DRIVE the
11
+ * deterministic actions (flag/strip/quarantine) and to populate a MEASURED
12
+ * false-positive rate, NEVER to claim prevention (claim-lint, spec §7).
13
+ *
14
+ * LEAK INVARIANT (spec §1.4): a detector emits SPANS (offsets + type +
15
+ * confidence) — never the matched substring. Nothing in this file returns,
16
+ * logs, or persists a matched value.
17
+ */
18
+ import type { Detector } from "../detect/detector";
19
+ /** The registry id + version of the deterministic pack. Bumped when the pattern
20
+ * set changes so a receipt's `detectors[].version` pins what ran. */
21
+ export declare const PATTERNS_ID = "patterns@v1";
22
+ /**
23
+ * The T0 pattern detector. Runs only on untrusted/semi tiers — the template's
24
+ * own trusted text is never scanned (it is the route author's, by construction).
25
+ * Deterministic: confidence 1 on every match, versioned as `patterns@v1`.
26
+ */
27
+ export declare function createPatternDetector(): Detector;
28
+ /** Thrown by {@link createClassifierDetector} when the optional peer dep or the
29
+ * local model file is absent — a TYPED signal the engine catches to degrade to
30
+ * T0 with `degraded: true` (spec §4.3), never a run-failing surprise. */
31
+ export declare class ScreenDetectorUnavailableError extends Error {
32
+ readonly detectorId: string;
33
+ constructor(detectorId: string, reason: string);
34
+ }
35
+ /**
36
+ * The optional T1 in-process classifier adapter (A3: OPTIONAL peer dependency;
37
+ * NO weights bundled; NO default npm dep). Absent peer dep OR absent model file
38
+ * ⇒ throws {@link ScreenDetectorUnavailableError} so the engine degrades to T0
39
+ * with a once-per-process notice and `degraded: true` on the receipt.
40
+ *
41
+ * ModelRig NEVER bundles or re-hosts weights — the customer fetches them with
42
+ * `modelrig screen pull <detector>` to a local cache and points `modelPath` at
43
+ * the result. The runtime peer (`onnxruntime-node` or a WASM transformers
44
+ * backend) is likewise the customer's to install.
45
+ */
46
+ export declare function createClassifierDetector(opts: {
47
+ readonly id: string;
48
+ readonly modelPath: string;
49
+ readonly runtime: "onnx" | "wasm";
50
+ }): Detector;
51
+ //# sourceMappingURL=patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../src/screen/patterns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAiC,MAAM,oBAAoB,CAAC;AAElF;qEACqE;AACrE,eAAO,MAAM,WAAW,gBAAgB,CAAC;AA+DzC;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,QAAQ,CAehD;AAED;;yEAEyE;AACzE,qBAAa,8BAA+B,SAAQ,KAAK;IACvD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAChB,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAK/C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC,GAAG,QAAQ,CAiCX"}
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ /**
3
+ * Screening — T0 deterministic injection patterns + the optional T1 classifier
4
+ * adapter (WIRED 2026-08-25, SC-1a). Spec §3 (Detector) + §4 (T0/T1).
5
+ *
6
+ * An OWASP-derived pattern pack that implements the shared `Detector` interface
7
+ * (packages/modelrig/src/detect/detector.ts) with `kind: "deterministic"`,
8
+ * confidence 1 on a match.
9
+ *
10
+ * DOCTRINE (research §2.2): detection is a SIGNAL, not a control. These patterns
11
+ * are trivially evaded by paraphrase/encoding; they exist to DRIVE the
12
+ * deterministic actions (flag/strip/quarantine) and to populate a MEASURED
13
+ * false-positive rate, NEVER to claim prevention (claim-lint, spec §7).
14
+ *
15
+ * LEAK INVARIANT (spec §1.4): a detector emits SPANS (offsets + type +
16
+ * confidence) — never the matched substring. Nothing in this file returns,
17
+ * logs, or persists a matched value.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.ScreenDetectorUnavailableError = exports.PATTERNS_ID = void 0;
21
+ exports.createPatternDetector = createPatternDetector;
22
+ exports.createClassifierDetector = createClassifierDetector;
23
+ const node_fs_1 = require("node:fs");
24
+ /** The registry id + version of the deterministic pack. Bumped when the pattern
25
+ * set changes so a receipt's `detectors[].version` pins what ran. */
26
+ exports.PATTERNS_ID = "patterns@v1";
27
+ /** The T0 pack. Deliberately narrow, US-English-shaped (A8), and biased toward
28
+ * classic injection SHAPES over broad keyword nets so the measured FP rate stays
29
+ * honest. Each entry is a signal, not a guarantee. */
30
+ const INJECTION_PATTERNS = [
31
+ // "ignore the previous instructions" / "disregard all prior rules" family.
32
+ {
33
+ type: "injection:instruction_override",
34
+ re: /\b(?:ignore|disregard|forget|override)\b[^.?!\n]{0,40}\b(?:previous|prior|above|earlier|all|any)\b[^.?!\n]{0,20}\b(?:instruction|instructions|prompt|prompts|rule|rules|context|direction|directions)\b/gi,
35
+ },
36
+ // "you are now …", "new instructions:", "act as", "pretend to be" — role hijack.
37
+ {
38
+ type: "injection:role_override",
39
+ re: /\b(?:you\s+are\s+now|from\s+now\s+on|new\s+(?:instructions?|role|persona|system\s+prompt)|act\s+as(?:\s+if)?|pretend\s+to\s+be|roleplay\s+as|switch\s+to\s+developer\s+mode)\b/gi,
40
+ },
41
+ // An untrusted segment carrying its own "system:" / "assistant:" role label —
42
+ // the classic transcript-smuggling shape the raw lane's JSON-turn guard closes.
43
+ {
44
+ type: "injection:role_label",
45
+ re: /(?:^|\n)\s*(?:system|assistant|developer)\s*:/gi,
46
+ },
47
+ // Chat-template / instruction delimiters smuggled into data.
48
+ {
49
+ type: "injection:delimiter",
50
+ re: /<\/?(?:system|assistant|user|instructions?)>|\[\/?INST\]|<\|(?:im_start|im_end|system|user|assistant|endoftext)\|>/gi,
51
+ },
52
+ // "reveal your system prompt" / "print your instructions" — prompt-leak probe.
53
+ {
54
+ type: "injection:prompt_leak",
55
+ re: /\b(?:reveal|print|repeat|show|output|display|tell\s+me)\b[^.?!\n]{0,30}\b(?:system\s+prompt|your\s+instructions|initial\s+prompt|the\s+prompt\s+above|your\s+rules)\b/gi,
56
+ },
57
+ // "email/exfiltrate the api key/password/secret" — data-exfiltration steering.
58
+ {
59
+ type: "injection:exfiltration",
60
+ re: /\b(?:send|exfiltrate|post|email|upload|leak|forward|transmit)\b[^.?!\n]{0,40}\b(?:api[\s_-]?key|password|secret|secrets|token|credential|credentials|private\s+key)\b/gi,
61
+ },
62
+ ];
63
+ /** Unicode tag plane (U+E0000–U+E007F) — invisible chars abused to smuggle
64
+ * instructions past a human reviewer. Detected as its own finding TYPE; the
65
+ * assembler strips them from delivered content, but their PRESENCE in untrusted
66
+ * input is itself a signal worth counting. */
67
+ const UNICODE_TAG_RE = /[\u{E0000}-\u{E007F}]+/gu;
68
+ /** Collect the spans one pattern matches in `text`. Offsets are into the exact
69
+ * string handed in; the matched substring is discarded immediately. */
70
+ function spansFor(type, re, text) {
71
+ const spans = [];
72
+ for (const m of text.matchAll(re)) {
73
+ const start = m.index ?? 0;
74
+ spans.push({ start, end: start + m[0].length, type, confidence: 1 });
75
+ }
76
+ return spans;
77
+ }
78
+ /**
79
+ * The T0 pattern detector. Runs only on untrusted/semi tiers — the template's
80
+ * own trusted text is never scanned (it is the route author's, by construction).
81
+ * Deterministic: confidence 1 on every match, versioned as `patterns@v1`.
82
+ */
83
+ function createPatternDetector() {
84
+ return {
85
+ id: exports.PATTERNS_ID,
86
+ kind: "deterministic",
87
+ detect(input) {
88
+ // Trusted content is the route's own framing — never an injection source.
89
+ if (input.tier === "trusted")
90
+ return Promise.resolve([]);
91
+ const spans = [];
92
+ for (const p of INJECTION_PATTERNS)
93
+ spans.push(...spansFor(p.type, p.re, input.text));
94
+ spans.push(...spansFor("injection:unicode_tag", UNICODE_TAG_RE, input.text));
95
+ // Reading order keeps the (offsets-only) span list stable and testable.
96
+ spans.sort((a, b) => a.start - b.start || a.end - b.end);
97
+ return Promise.resolve(spans);
98
+ },
99
+ };
100
+ }
101
+ /** Thrown by {@link createClassifierDetector} when the optional peer dep or the
102
+ * local model file is absent — a TYPED signal the engine catches to degrade to
103
+ * T0 with `degraded: true` (spec §4.3), never a run-failing surprise. */
104
+ class ScreenDetectorUnavailableError extends Error {
105
+ constructor(detectorId, reason) {
106
+ super(`screen detector "${detectorId}" is unavailable: ${reason}`);
107
+ this.name = "ScreenDetectorUnavailableError";
108
+ this.detectorId = detectorId;
109
+ }
110
+ }
111
+ exports.ScreenDetectorUnavailableError = ScreenDetectorUnavailableError;
112
+ /**
113
+ * The optional T1 in-process classifier adapter (A3: OPTIONAL peer dependency;
114
+ * NO weights bundled; NO default npm dep). Absent peer dep OR absent model file
115
+ * ⇒ throws {@link ScreenDetectorUnavailableError} so the engine degrades to T0
116
+ * with a once-per-process notice and `degraded: true` on the receipt.
117
+ *
118
+ * ModelRig NEVER bundles or re-hosts weights — the customer fetches them with
119
+ * `modelrig screen pull <detector>` to a local cache and points `modelPath` at
120
+ * the result. The runtime peer (`onnxruntime-node` or a WASM transformers
121
+ * backend) is likewise the customer's to install.
122
+ */
123
+ function createClassifierDetector(opts) {
124
+ const peer = opts.runtime === "onnx" ? "onnxruntime-node" : "@xenova/transformers";
125
+ // A3: the peer is an OPTIONAL dependency resolved at RUNTIME. `require` is used
126
+ // (not a static import) precisely so a missing peer is a catchable throw, not a
127
+ // load-time crash / a bundled dependency.
128
+ let runtimeModule;
129
+ try {
130
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
131
+ runtimeModule = require(peer);
132
+ }
133
+ catch {
134
+ throw new ScreenDetectorUnavailableError(opts.id, `optional peer dependency "${peer}" is not installed (install it and run \`modelrig screen pull ${opts.id}\`)`);
135
+ }
136
+ if (!(0, node_fs_1.existsSync)(opts.modelPath)) {
137
+ throw new ScreenDetectorUnavailableError(opts.id, `model file not found at "${opts.modelPath}" — fetch it with \`modelrig screen pull ${opts.id}\` (weights are never bundled, A3)`);
138
+ }
139
+ // The concrete inference wiring (tokenize → run → score → span) is delegated to
140
+ // the loaded runtime; this SC-1a scaffold proves the OPTIONAL-load contract and
141
+ // the fail-closed degrade path. A configured-and-present classifier tier is a
142
+ // Matt-env run (live weights); the deterministic T0 path is what ships.
143
+ void runtimeModule;
144
+ return {
145
+ id: opts.id,
146
+ kind: "classifier",
147
+ detect(_input) {
148
+ return Promise.resolve([]);
149
+ },
150
+ };
151
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * SC-2 — the gated `phi` route posture (spec-sc2 §4). Deterministic, fail-closed,
3
+ * dependency-free. Runs at ROUTE LOAD (the same place `require:` / `mask:` /
4
+ * `screen:` are validated), so a mis-postured PHI route REFUSES TO LOAD with a
5
+ * teaching error rather than silently serving PHI on an unmasked, un-screened,
6
+ * content-retaining route.
7
+ *
8
+ * A route classified `phi` (route-level `classification: phi`) MUST satisfy ALL:
9
+ * 1. mask effective mode ≥ redact — i.e. mask rewrites (mode `mask` or `block`,
10
+ * CS-1); `off`/`detect` do NOT mask and fail the gate.
11
+ * 2. `require: zero_retention` OR a `sidecar` detector present in
12
+ * `screen.detectors` (the managed-custody + sidecar path).
13
+ *
14
+ * This does NOT claim de-identification (that is a banned claim, §6). It enforces
15
+ * that PHI never rides an unmasked, un-screened, content-retaining route. The
16
+ * gate is code-visible and lane-independent: it checks the mask mode, the
17
+ * `require` list, and the presence of a sidecar detector — all resolved at load.
18
+ * `modelrig validate` prints {@link describePhiPosture}: the resolved posture and
19
+ * the specific unmet requirement.
20
+ */
21
+ import type { DataClassification } from "../artifacts/classification";
22
+ import type { ResolvedMaskPolicy } from "../mask/types";
23
+ import type { Requirement } from "../types/route";
24
+ import type { ResolvedScreenPolicy } from "./types";
25
+ /** The load-time inputs the gate reads — all already resolved by the loader. */
26
+ export interface PhiPostureInput {
27
+ /** The route-level `classification`, if any. The gate fires only for `phi`. */
28
+ readonly classification?: DataClassification;
29
+ readonly mask?: ResolvedMaskPolicy;
30
+ readonly screen?: ResolvedScreenPolicy;
31
+ readonly require: readonly Requirement[];
32
+ }
33
+ /** The resolved posture — what the gate saw and whether it passed. */
34
+ export interface PhiPosture {
35
+ readonly isPhi: boolean;
36
+ readonly maskRedacts: boolean;
37
+ readonly zeroRetention: boolean;
38
+ readonly sidecarPresent: boolean;
39
+ readonly satisfied: boolean;
40
+ }
41
+ /** Resolve the posture for a route (whether or not it is `phi`). */
42
+ export declare function resolvePhiPosture(input: PhiPostureInput): PhiPosture;
43
+ /**
44
+ * Teaching-style problems for a mis-postured `phi` route (CE-2 fixHint pattern) —
45
+ * EMPTY when the route is not `phi` or the posture is satisfied. Each string
46
+ * names the SPECIFIC unmet requirement so the author knows exactly what to add.
47
+ */
48
+ export declare function checkPhiPosture(input: PhiPostureInput): readonly string[];
49
+ /** A one-line human posture summary for `modelrig validate` (spec-sc2 §4): the
50
+ * resolved posture and, when unsatisfied, the unmet requirement. */
51
+ export declare function describePhiPosture(input: PhiPostureInput): string;
52
+ //# sourceMappingURL=phi-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phi-gate.d.ts","sourceRoot":"","sources":["../../src/screen/phi-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,cAAc,CAAC,EAAE,kBAAkB,CAAC;IAC7C,QAAQ,CAAC,IAAI,CAAC,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;CAC1C;AAED,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAcD,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,UAAU,CAYpE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,MAAM,EAAE,CAkBzE;AAED;oEACoE;AACpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAYjE"}
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ /**
3
+ * SC-2 — the gated `phi` route posture (spec-sc2 §4). Deterministic, fail-closed,
4
+ * dependency-free. Runs at ROUTE LOAD (the same place `require:` / `mask:` /
5
+ * `screen:` are validated), so a mis-postured PHI route REFUSES TO LOAD with a
6
+ * teaching error rather than silently serving PHI on an unmasked, un-screened,
7
+ * content-retaining route.
8
+ *
9
+ * A route classified `phi` (route-level `classification: phi`) MUST satisfy ALL:
10
+ * 1. mask effective mode ≥ redact — i.e. mask rewrites (mode `mask` or `block`,
11
+ * CS-1); `off`/`detect` do NOT mask and fail the gate.
12
+ * 2. `require: zero_retention` OR a `sidecar` detector present in
13
+ * `screen.detectors` (the managed-custody + sidecar path).
14
+ *
15
+ * This does NOT claim de-identification (that is a banned claim, §6). It enforces
16
+ * that PHI never rides an unmasked, un-screened, content-retaining route. The
17
+ * gate is code-visible and lane-independent: it checks the mask mode, the
18
+ * `require` list, and the presence of a sidecar detector — all resolved at load.
19
+ * `modelrig validate` prints {@link describePhiPosture}: the resolved posture and
20
+ * the specific unmet requirement.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.resolvePhiPosture = resolvePhiPosture;
24
+ exports.checkPhiPosture = checkPhiPosture;
25
+ exports.describePhiPosture = describePhiPosture;
26
+ /** A mask policy "redacts" (rewrites content) iff its effective mode is `mask`
27
+ * or `block` — `off`/`detect` scan but do not rewrite (CS-1 §3). */
28
+ function maskRedacts(mask) {
29
+ return mask !== undefined && (mask.policy.mode === "mask" || mask.policy.mode === "block");
30
+ }
31
+ /** A sidecar detector is present iff the resolved screen policy carries a
32
+ * `sidecar:*` detector identity (built from `screen.sidecar` at resolve time). */
33
+ function hasSidecar(screen) {
34
+ return screen !== undefined && screen.detectorMeta.some((d) => d.id.startsWith("sidecar:"));
35
+ }
36
+ /** Resolve the posture for a route (whether or not it is `phi`). */
37
+ function resolvePhiPosture(input) {
38
+ const isPhi = input.classification === "phi";
39
+ const maskR = maskRedacts(input.mask);
40
+ const zdr = input.require.includes("zero_retention");
41
+ const sidecar = hasSidecar(input.screen);
42
+ return {
43
+ isPhi,
44
+ maskRedacts: maskR,
45
+ zeroRetention: zdr,
46
+ sidecarPresent: sidecar,
47
+ satisfied: maskR && (zdr || sidecar),
48
+ };
49
+ }
50
+ /**
51
+ * Teaching-style problems for a mis-postured `phi` route (CE-2 fixHint pattern) —
52
+ * EMPTY when the route is not `phi` or the posture is satisfied. Each string
53
+ * names the SPECIFIC unmet requirement so the author knows exactly what to add.
54
+ */
55
+ function checkPhiPosture(input) {
56
+ const posture = resolvePhiPosture(input);
57
+ if (!posture.isPhi || posture.satisfied)
58
+ return [];
59
+ const problems = [];
60
+ if (!posture.maskRedacts) {
61
+ problems.push("a `phi`-classified route must mask its content — set `mask.mode: mask` (or `block`); " +
62
+ "`off`/`detect` do not redact. PHI must never ride an unmasked route.");
63
+ }
64
+ if (!posture.zeroRetention && !posture.sidecarPresent) {
65
+ problems.push("a `phi`-classified route must either `require: [zero_retention]` OR configure a `screen.sidecar` " +
66
+ "(a customer-run de-id detector, present in the resolved `screen.detectors` as `sidecar:<digest>`). " +
67
+ "PHI must never ride a content-retaining route without the sidecar.");
68
+ }
69
+ return problems;
70
+ }
71
+ /** A one-line human posture summary for `modelrig validate` (spec-sc2 §4): the
72
+ * resolved posture and, when unsatisfied, the unmet requirement. */
73
+ function describePhiPosture(input) {
74
+ const p = resolvePhiPosture(input);
75
+ if (!p.isPhi)
76
+ return "";
77
+ const parts = [
78
+ `mask≥redact=${p.maskRedacts ? "yes" : "NO"}`,
79
+ `zero_retention=${p.zeroRetention ? "yes" : "no"}`,
80
+ `sidecar=${p.sidecarPresent ? "yes" : "no"}`,
81
+ ];
82
+ const verdict = p.satisfied
83
+ ? "OK — PHI posture satisfied"
84
+ : `UNMET — ${checkPhiPosture(input).join(" ")}`;
85
+ return `phi posture [${parts.join(" · ")}] → ${verdict}`;
86
+ }