modelrig 0.1.0 → 0.2.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 (309) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +41 -11
  3. package/data/PROVENANCE.md +19 -0
  4. package/data/pricing-manual.json +22 -0
  5. package/data/pricing-map.json +46362 -0
  6. package/data/pricing-overlay.json +419 -0
  7. package/dist/adapters/anthropic.d.ts +50 -6
  8. package/dist/adapters/anthropic.d.ts.map +1 -1
  9. package/dist/adapters/anthropic.js +123 -29
  10. package/dist/adapters/build-adapters.d.ts +11 -0
  11. package/dist/adapters/build-adapters.d.ts.map +1 -0
  12. package/dist/adapters/build-adapters.js +45 -0
  13. package/dist/adapters/deepseek.d.ts +6 -0
  14. package/dist/adapters/deepseek.d.ts.map +1 -1
  15. package/dist/adapters/deepseek.js +54 -1
  16. package/dist/adapters/gemini.d.ts.map +1 -1
  17. package/dist/adapters/gemini.js +46 -6
  18. package/dist/adapters/grok.d.ts +9 -0
  19. package/dist/adapters/grok.d.ts.map +1 -1
  20. package/dist/adapters/grok.js +67 -3
  21. package/dist/adapters/openai-compat.d.ts +6 -0
  22. package/dist/adapters/openai-compat.d.ts.map +1 -1
  23. package/dist/adapters/openai-compat.js +58 -1
  24. package/dist/adapters/openai-stream.d.ts +58 -0
  25. package/dist/adapters/openai-stream.d.ts.map +1 -0
  26. package/dist/adapters/openai-stream.js +120 -0
  27. package/dist/adapters/openai.d.ts +6 -0
  28. package/dist/adapters/openai.d.ts.map +1 -1
  29. package/dist/adapters/openai.js +60 -1
  30. package/dist/adapters/sampling.d.ts +28 -0
  31. package/dist/adapters/sampling.d.ts.map +1 -0
  32. package/dist/adapters/sampling.js +34 -0
  33. package/dist/adapters/tool-mapping.d.ts +92 -0
  34. package/dist/adapters/tool-mapping.d.ts.map +1 -0
  35. package/dist/adapters/tool-mapping.js +143 -0
  36. package/dist/artifacts/artifact.d.ts +137 -0
  37. package/dist/artifacts/artifact.d.ts.map +1 -0
  38. package/dist/artifacts/artifact.js +377 -0
  39. package/dist/artifacts/content-uploader.d.ts +87 -0
  40. package/dist/artifacts/content-uploader.d.ts.map +1 -0
  41. package/dist/artifacts/content-uploader.js +124 -0
  42. package/dist/artifacts/index.d.ts +63 -0
  43. package/dist/artifacts/index.d.ts.map +1 -0
  44. package/dist/artifacts/index.js +80 -0
  45. package/dist/artifacts/run-context.d.ts +129 -0
  46. package/dist/artifacts/run-context.d.ts.map +1 -0
  47. package/dist/artifacts/run-context.js +208 -0
  48. package/dist/artifacts/scrub.d.ts +60 -0
  49. package/dist/artifacts/scrub.d.ts.map +1 -0
  50. package/dist/artifacts/scrub.js +139 -0
  51. package/dist/artifacts/step.d.ts +26 -0
  52. package/dist/artifacts/step.d.ts.map +1 -0
  53. package/dist/artifacts/step.js +37 -0
  54. package/dist/artifacts/taint.d.ts +63 -0
  55. package/dist/artifacts/taint.d.ts.map +1 -0
  56. package/dist/artifacts/taint.js +76 -0
  57. package/dist/bakeoff/cache-sim.d.ts +99 -0
  58. package/dist/bakeoff/cache-sim.d.ts.map +1 -0
  59. package/dist/bakeoff/cache-sim.js +126 -0
  60. package/dist/bakeoff/eval-cases.d.ts +96 -0
  61. package/dist/bakeoff/eval-cases.d.ts.map +1 -0
  62. package/dist/bakeoff/eval-cases.js +105 -0
  63. package/dist/bakeoff/report.d.ts +41 -0
  64. package/dist/bakeoff/report.d.ts.map +1 -0
  65. package/dist/bakeoff/report.js +142 -0
  66. package/dist/bakeoff/samples.d.ts +15 -0
  67. package/dist/bakeoff/samples.d.ts.map +1 -1
  68. package/dist/bakeoff/samples.js +20 -0
  69. package/dist/cli.d.ts +1 -3
  70. package/dist/cli.d.ts.map +1 -1
  71. package/dist/cli.js +134 -116
  72. package/dist/config.d.ts +100 -0
  73. package/dist/config.d.ts.map +1 -1
  74. package/dist/config.js +104 -0
  75. package/dist/deprecations.d.ts +32 -0
  76. package/dist/deprecations.d.ts.map +1 -0
  77. package/dist/deprecations.js +59 -0
  78. package/dist/gateway/http-protocol.d.ts +175 -0
  79. package/dist/gateway/http-protocol.d.ts.map +1 -0
  80. package/dist/gateway/http-protocol.js +105 -0
  81. package/dist/index.d.ts +105 -18
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +266 -138
  84. package/dist/init/finder.d.ts +15 -0
  85. package/dist/init/finder.d.ts.map +1 -1
  86. package/dist/init/finder.js +46 -0
  87. package/dist/init/init-cli.d.ts +10 -0
  88. package/dist/init/init-cli.d.ts.map +1 -0
  89. package/dist/init/init-cli.js +86 -0
  90. package/dist/init/observe-cli.d.ts +19 -0
  91. package/dist/init/observe-cli.d.ts.map +1 -0
  92. package/dist/init/observe-cli.js +73 -0
  93. package/dist/init/observe.d.ts +81 -0
  94. package/dist/init/observe.d.ts.map +1 -0
  95. package/dist/init/observe.js +245 -0
  96. package/dist/registry/build.d.ts +94 -1
  97. package/dist/registry/build.d.ts.map +1 -1
  98. package/dist/registry/build.js +125 -4
  99. package/dist/registry/cache-policy.d.ts +94 -0
  100. package/dist/registry/cache-policy.d.ts.map +1 -0
  101. package/dist/registry/cache-policy.js +238 -0
  102. package/dist/registry/call-notes.d.ts.map +1 -1
  103. package/dist/registry/call-notes.js +40 -2
  104. package/dist/registry/effective-rate.d.ts +151 -0
  105. package/dist/registry/effective-rate.d.ts.map +1 -0
  106. package/dist/registry/effective-rate.js +156 -0
  107. package/dist/registry/facts.d.ts.map +1 -1
  108. package/dist/registry/facts.js +16 -0
  109. package/dist/registry/parity.d.ts +26 -0
  110. package/dist/registry/parity.d.ts.map +1 -1
  111. package/dist/registry/parity.js +43 -2
  112. package/dist/registry/pricing-shape.d.ts +113 -0
  113. package/dist/registry/pricing-shape.d.ts.map +1 -0
  114. package/dist/registry/pricing-shape.js +123 -0
  115. package/dist/registry/pricing.d.ts +25 -1
  116. package/dist/registry/pricing.d.ts.map +1 -1
  117. package/dist/registry/pricing.js +64 -6
  118. package/dist/rigindex/bootstrap.d.ts +36 -0
  119. package/dist/rigindex/bootstrap.d.ts.map +1 -0
  120. package/dist/rigindex/bootstrap.js +97 -0
  121. package/dist/rigindex/fit.d.ts +24 -0
  122. package/dist/rigindex/fit.d.ts.map +1 -0
  123. package/dist/rigindex/fit.js +76 -0
  124. package/dist/rigindex/gates.d.ts +52 -0
  125. package/dist/rigindex/gates.d.ts.map +1 -0
  126. package/dist/rigindex/gates.js +109 -0
  127. package/dist/rigindex/graph.d.ts +31 -0
  128. package/dist/rigindex/graph.d.ts.map +1 -0
  129. package/dist/rigindex/graph.js +120 -0
  130. package/dist/rigindex/index.d.ts +23 -0
  131. package/dist/rigindex/index.d.ts.map +1 -0
  132. package/dist/rigindex/index.js +112 -0
  133. package/dist/rigindex/types.d.ts +76 -0
  134. package/dist/rigindex/types.d.ts.map +1 -0
  135. package/dist/rigindex/types.js +8 -0
  136. package/dist/routes/cache-lint.d.ts +41 -0
  137. package/dist/routes/cache-lint.d.ts.map +1 -0
  138. package/dist/routes/cache-lint.js +81 -0
  139. package/dist/routes/loader.d.ts +37 -0
  140. package/dist/routes/loader.d.ts.map +1 -1
  141. package/dist/routes/loader.js +138 -0
  142. package/dist/routes/resolved-bundle.d.ts +79 -0
  143. package/dist/routes/resolved-bundle.d.ts.map +1 -0
  144. package/dist/routes/resolved-bundle.js +148 -0
  145. package/dist/routes/sampling-config.d.ts +32 -0
  146. package/dist/routes/sampling-config.d.ts.map +1 -0
  147. package/dist/routes/sampling-config.js +91 -0
  148. package/dist/run/ajv.d.ts +22 -0
  149. package/dist/run/ajv.d.ts.map +1 -0
  150. package/dist/run/ajv.js +32 -0
  151. package/dist/run/cache-handle.d.ts +58 -0
  152. package/dist/run/cache-handle.d.ts.map +1 -0
  153. package/dist/run/cache-handle.js +54 -0
  154. package/dist/run/cache-stamp.d.ts +65 -0
  155. package/dist/run/cache-stamp.d.ts.map +1 -0
  156. package/dist/run/cache-stamp.js +0 -0
  157. package/dist/run/capture-write.d.ts +33 -0
  158. package/dist/run/capture-write.d.ts.map +1 -0
  159. package/dist/run/capture-write.js +55 -0
  160. package/dist/run/cost.d.ts.map +1 -1
  161. package/dist/run/cost.js +1 -1
  162. package/dist/run/dispatch.d.ts +99 -0
  163. package/dist/run/dispatch.d.ts.map +1 -0
  164. package/dist/run/dispatch.js +191 -0
  165. package/dist/run/raw.d.ts +88 -0
  166. package/dist/run/raw.d.ts.map +1 -0
  167. package/dist/run/raw.js +286 -0
  168. package/dist/run/record-attempt.d.ts +40 -0
  169. package/dist/run/record-attempt.d.ts.map +1 -0
  170. package/dist/run/record-attempt.js +99 -0
  171. package/dist/run/resolve.d.ts +75 -2
  172. package/dist/run/resolve.d.ts.map +1 -1
  173. package/dist/run/resolve.js +126 -4
  174. package/dist/run/run.d.ts +14 -0
  175. package/dist/run/run.d.ts.map +1 -1
  176. package/dist/run/run.js +0 -0
  177. package/dist/run/stream.d.ts +49 -0
  178. package/dist/run/stream.d.ts.map +1 -0
  179. package/dist/run/stream.js +87 -0
  180. package/dist/run/validate.d.ts.map +1 -1
  181. package/dist/run/validate.js +6 -7
  182. package/dist/savings/cache-ledger.d.ts +108 -0
  183. package/dist/savings/cache-ledger.d.ts.map +1 -0
  184. package/dist/savings/cache-ledger.js +174 -0
  185. package/dist/savings/cache-reconcile.d.ts +64 -0
  186. package/dist/savings/cache-reconcile.d.ts.map +1 -0
  187. package/dist/savings/cache-reconcile.js +59 -0
  188. package/dist/savings/cache-stats.d.ts +63 -0
  189. package/dist/savings/cache-stats.d.ts.map +1 -0
  190. package/dist/savings/cache-stats.js +108 -0
  191. package/dist/savings/emit.d.ts +13 -0
  192. package/dist/savings/emit.d.ts.map +1 -1
  193. package/dist/savings/emit.js +25 -0
  194. package/dist/savings/swap-execute.d.ts +14 -0
  195. package/dist/savings/swap-execute.d.ts.map +1 -1
  196. package/dist/savings/swap-execute.js +44 -1
  197. package/dist/savings/swaps-client.d.ts +14 -0
  198. package/dist/savings/swaps-client.d.ts.map +1 -1
  199. package/dist/savings/swaps-client.js +16 -2
  200. package/dist/settings/api-fetcher.d.ts +39 -0
  201. package/dist/settings/api-fetcher.d.ts.map +1 -0
  202. package/dist/settings/api-fetcher.js +92 -0
  203. package/dist/settings/index.d.ts +10 -0
  204. package/dist/settings/index.d.ts.map +1 -0
  205. package/dist/settings/index.js +15 -0
  206. package/dist/settings/task-settings.d.ts +91 -0
  207. package/dist/settings/task-settings.d.ts.map +1 -0
  208. package/dist/settings/task-settings.js +116 -0
  209. package/dist/status.d.ts +77 -0
  210. package/dist/status.d.ts.map +1 -0
  211. package/dist/status.js +113 -0
  212. package/dist/telemetry/artifact-store.d.ts +48 -0
  213. package/dist/telemetry/artifact-store.d.ts.map +1 -0
  214. package/dist/telemetry/artifact-store.js +189 -0
  215. package/dist/telemetry/cache-miss-streak.d.ts +79 -0
  216. package/dist/telemetry/cache-miss-streak.d.ts.map +1 -0
  217. package/dist/telemetry/cache-miss-streak.js +99 -0
  218. package/dist/telemetry/capture.d.ts +3 -0
  219. package/dist/telemetry/capture.d.ts.map +1 -1
  220. package/dist/telemetry/capture.js +15 -8
  221. package/dist/telemetry/exporter-setup.d.ts +28 -0
  222. package/dist/telemetry/exporter-setup.d.ts.map +1 -0
  223. package/dist/telemetry/exporter-setup.js +34 -0
  224. package/dist/telemetry/exporter.d.ts +75 -0
  225. package/dist/telemetry/exporter.d.ts.map +1 -1
  226. package/dist/telemetry/exporter.js +324 -47
  227. package/dist/telemetry/health.d.ts +76 -0
  228. package/dist/telemetry/health.d.ts.map +1 -0
  229. package/dist/telemetry/health.js +92 -0
  230. package/dist/telemetry/ingest-protocol.d.ts +152 -3
  231. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  232. package/dist/telemetry/ingest-protocol.js +204 -4
  233. package/dist/telemetry/load-error-report.d.ts +24 -0
  234. package/dist/telemetry/load-error-report.d.ts.map +1 -0
  235. package/dist/telemetry/load-error-report.js +50 -0
  236. package/dist/telemetry/otel-map.d.ts +121 -0
  237. package/dist/telemetry/otel-map.d.ts.map +1 -0
  238. package/dist/telemetry/otel-map.js +224 -0
  239. package/dist/telemetry/otel-normalize.d.ts +104 -0
  240. package/dist/telemetry/otel-normalize.d.ts.map +1 -0
  241. package/dist/telemetry/otel-normalize.js +530 -0
  242. package/dist/telemetry/otlp-emitter.d.ts +52 -0
  243. package/dist/telemetry/otlp-emitter.d.ts.map +1 -0
  244. package/dist/telemetry/otlp-emitter.js +144 -0
  245. package/dist/telemetry/otlp-proto.d.ts +29 -0
  246. package/dist/telemetry/otlp-proto.d.ts.map +1 -0
  247. package/dist/telemetry/otlp-proto.js +266 -0
  248. package/dist/telemetry/sinks.d.ts +37 -1
  249. package/dist/telemetry/sinks.d.ts.map +1 -1
  250. package/dist/telemetry/sinks.js +121 -30
  251. package/dist/telemetry/store.d.ts +28 -1
  252. package/dist/telemetry/store.d.ts.map +1 -1
  253. package/dist/telemetry/store.js +218 -3
  254. package/dist/tenancy/api-key-format.d.ts +15 -5
  255. package/dist/tenancy/api-key-format.d.ts.map +1 -1
  256. package/dist/tenancy/api-key-format.js +24 -5
  257. package/dist/types/config-teaching.d.ts +62 -0
  258. package/dist/types/config-teaching.d.ts.map +1 -0
  259. package/dist/types/config-teaching.js +183 -0
  260. package/dist/types/provider.d.ts +122 -1
  261. package/dist/types/provider.d.ts.map +1 -1
  262. package/dist/types/provider.js +21 -1
  263. package/dist/types/route.d.ts +79 -1
  264. package/dist/types/route.d.ts.map +1 -1
  265. package/dist/types/telemetry.d.ts +117 -0
  266. package/dist/types/telemetry.d.ts.map +1 -1
  267. package/dist/validate-cli.d.ts +12 -0
  268. package/dist/validate-cli.d.ts.map +1 -0
  269. package/dist/validate-cli.js +57 -0
  270. package/dist/verify/capture.d.ts +66 -0
  271. package/dist/verify/capture.d.ts.map +1 -0
  272. package/dist/verify/capture.js +180 -0
  273. package/dist/verify/cli-run.d.ts +23 -0
  274. package/dist/verify/cli-run.d.ts.map +1 -0
  275. package/dist/verify/cli-run.js +57 -0
  276. package/dist/verify/diff.d.ts +48 -0
  277. package/dist/verify/diff.d.ts.map +1 -0
  278. package/dist/verify/diff.js +157 -0
  279. package/dist/verify/index.d.ts +17 -0
  280. package/dist/verify/index.d.ts.map +1 -0
  281. package/dist/verify/index.js +24 -0
  282. package/dist/verify/report.d.ts +14 -0
  283. package/dist/verify/report.d.ts.map +1 -0
  284. package/dist/verify/report.js +88 -0
  285. package/dist/verify/types.d.ts +101 -0
  286. package/dist/verify/types.d.ts.map +1 -0
  287. package/dist/verify/types.js +17 -0
  288. package/dist/verify/verify.d.ts +29 -0
  289. package/dist/verify/verify.d.ts.map +1 -0
  290. package/dist/verify/verify.js +148 -0
  291. package/dist/watch/evaluate.d.ts +162 -3
  292. package/dist/watch/evaluate.d.ts.map +1 -1
  293. package/dist/watch/evaluate.js +61 -0
  294. package/dist/watch/monitor.d.ts +116 -0
  295. package/dist/watch/monitor.d.ts.map +1 -0
  296. package/dist/watch/monitor.js +199 -0
  297. package/dist/watch/proposals.d.ts +20 -4
  298. package/dist/watch/proposals.d.ts.map +1 -1
  299. package/dist/watch/proposals.js +31 -3
  300. package/dist/watch/run.d.ts +33 -0
  301. package/dist/watch/run.d.ts.map +1 -1
  302. package/dist/watch/run.js +39 -0
  303. package/package.json +7 -1
  304. package/registry/LICENSE +20 -0
  305. package/registry/leaderboard.json +3639 -0
  306. package/registry/registry.json +7263 -0
  307. package/dist/telemetry/supabase-exporter.d.ts +0 -64
  308. package/dist/telemetry/supabase-exporter.d.ts.map +0 -1
  309. package/dist/telemetry/supabase-exporter.js +0 -211
@@ -15,8 +15,16 @@
15
15
  */
16
16
  import type { SwapChange } from "./emit";
17
17
  import type { SwapEventRecord } from "./swaps-client";
18
+ import type { CacheContext } from "../watch/evaluate";
19
+ import type { CacheVerdict } from "../registry/effective-rate";
18
20
  import type { ProposalRecord, ProposalStatus } from "../watch/proposals";
19
21
  import type { RouteBundle } from "../types/route";
22
+ /** Narrow a proposal's `evidence.cache_context` jsonb (spec §5.5 — additive,
23
+ * present only on cache-aware proposals) to a typed `CacheContext`, or null when
24
+ * absent/malformed. Defensive: an older or non-cache proposal has no block, and
25
+ * a partial block must degrade to "no cache explanation", never throw on the
26
+ * safety-critical execute path. */
27
+ export declare function readCacheContext(evidence: Record<string, unknown> | null): CacheContext | null;
20
28
  export interface SwapExecuteDeps {
21
29
  readonly proposalId: string;
22
30
  /** THE gate — false throws before any read or write. */
@@ -39,6 +47,12 @@ export interface SwapExecuteResult {
39
47
  readonly change: SwapChange;
40
48
  readonly approvedBy: string;
41
49
  readonly executedAt: string;
50
+ /** Cache-aware verdict from the proposal's `cache_context` evidence (spec
51
+ * §5.5), or null on a non-cache proposal. Additive — never gates execution
52
+ * (the human already approved); surfaced so the CLI/console can show WHY. */
53
+ readonly cacheVerdict: CacheVerdict | null;
54
+ /** The [Briefing §30] one-line explanation for the approval prompt, or null. */
55
+ readonly cacheExplanation: string | null;
42
56
  }
43
57
  export declare function executeSwap(deps: SwapExecuteDeps): Promise<SwapExecuteResult>;
44
58
  //# sourceMappingURL=swap-execute.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"swap-execute.d.ts","sourceRoot":"","sources":["../../src/savings/swap-execute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AASlD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wDAAwD;IACxD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE;QAClB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;QAChD,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAChF,CAAC;IACF,QAAQ,CAAC,KAAK,EAAE;QAAE,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAClE,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,WAAW,CAAC;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,MAAM,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAuFnF"}
1
+ {"version":3,"file":"swap-execute.d.ts","sourceRoot":"","sources":["../../src/savings/swap-execute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;mCAImC;AACnC,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,YAAY,GAAG,IAAI,CAmB9F;AASD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wDAAwD;IACxD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE;QAClB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;QAChD,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAChF,CAAC;IACF,QAAQ,CAAC,KAAK,EAAE;QAAE,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAClE,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,WAAW,CAAC;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,MAAM,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;iFAE6E;IAC7E,QAAQ,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAC3C,gFAAgF;IAChF,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAwGnF"}
@@ -15,8 +15,36 @@
15
15
  * executed. Applying the YAML stays a human/git action.
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.readCacheContext = readCacheContext;
18
19
  exports.executeSwap = executeSwap;
19
20
  const emit_1 = require("./emit");
21
+ const evaluate_1 = require("../watch/evaluate");
22
+ /** Narrow a proposal's `evidence.cache_context` jsonb (spec §5.5 — additive,
23
+ * present only on cache-aware proposals) to a typed `CacheContext`, or null when
24
+ * absent/malformed. Defensive: an older or non-cache proposal has no block, and
25
+ * a partial block must degrade to "no cache explanation", never throw on the
26
+ * safety-critical execute path. */
27
+ function readCacheContext(evidence) {
28
+ const raw = evidence?.cache_context;
29
+ if (raw === null || typeof raw !== "object")
30
+ return null;
31
+ const c = raw;
32
+ const verdicts = ["cache_positive", "cache_marginal", "cache_negative", "no_cache_signal"];
33
+ if (typeof c.verdict !== "string" || !verdicts.includes(c.verdict))
34
+ return null;
35
+ const num = (v) => (typeof v === "number" ? v : 0);
36
+ return {
37
+ observed_hit_rate: num(c.observed_hit_rate),
38
+ observed_write_rate: num(c.observed_write_rate),
39
+ window_days: num(c.window_days),
40
+ incumbent_effective_in_per_mtok: num(c.incumbent_effective_in_per_mtok),
41
+ challenger_cold_in_per_mtok: num(c.challenger_cold_in_per_mtok),
42
+ switch_cost_usd: num(c.switch_cost_usd),
43
+ cache_option_value_usd: num(c.cache_option_value_usd),
44
+ breakeven_conformant_calls: typeof c.breakeven_conformant_calls === "number" ? c.breakeven_conformant_calls : Number.POSITIVE_INFINITY,
45
+ verdict: c.verdict,
46
+ };
47
+ }
20
48
  async function executeSwap(deps) {
21
49
  // The gate first — before any client call, so a non-approved invocation
22
50
  // has zero side effects (defense in depth with the CLI's fast-fail check).
@@ -83,5 +111,20 @@ async function executeSwap(deps) {
83
111
  revertedAt: null,
84
112
  });
85
113
  await deps.proposals.setStatus(proposal.id, "approved", "executed");
86
- return { swapId, route: proposal.route, change, approvedBy: deps.approvedBy, executedAt };
114
+ // Cache-aware explainability (spec §5.5) — additive, read-only. The verdict +
115
+ // prose ride along for the approval prompt / audit trail; they NEVER gate the
116
+ // swap (the human decision above is the only gate). Absent on non-cache
117
+ // proposals, in which case both are null and behavior is byte-identical.
118
+ const cacheContext = readCacheContext(proposal.evidence);
119
+ const incumbentLabel = change.incumbentCandidates.map((c) => `${c.provider}/${c.model}`).join(", ") || "incumbent";
120
+ const cacheExplanation = cacheContext !== null ? (0, evaluate_1.explainCacheContext)(cacheContext, incumbentLabel, winner.variant) : null;
121
+ return {
122
+ swapId,
123
+ route: proposal.route,
124
+ change,
125
+ approvedBy: deps.approvedBy,
126
+ executedAt,
127
+ cacheVerdict: cacheContext?.verdict ?? null,
128
+ cacheExplanation,
129
+ };
87
130
  }
@@ -29,12 +29,26 @@ export interface SwapEventRecord {
29
29
  export interface SwapsClientConfig {
30
30
  readonly supabaseUrl: string;
31
31
  readonly serviceRoleKey: string;
32
+ /**
33
+ * The tenant this client writes/reads for. When set (the hosted managed-loop
34
+ * §1 swap surface + odometer): `record` STAMPS `org_id` on the row (else it
35
+ * falls to the house-org column default — filing a customer's swap under ours)
36
+ * and `list` scopes to this org (the odometer must never sum another tenant's
37
+ * swaps). When omitted, behaviour is unchanged for the CLI/self-host lane.
38
+ * `get`/`orgOf`/`markReverted` are keyed by id and org-agnostic.
39
+ */
40
+ readonly orgId?: string;
32
41
  readonly fetchFn?: typeof fetch;
33
42
  }
34
43
  export interface SwapsClient {
35
44
  record(event: SwapEventRecord): Promise<void>;
45
+ /** All executed swaps for this client's org (when `orgId` set) or every org
46
+ * (unscoped) — newest first. The odometer reader's source. */
36
47
  list(): Promise<SwapEventRecord[]>;
37
48
  get(id: string): Promise<SwapEventRecord | null>;
49
+ /** The org that owns a swap, or null if no such swap — the tenant a revert
50
+ * must be authorized against (T11), mirroring `proposals.orgOf`. */
51
+ orgOf(id: string): Promise<string | null>;
38
52
  markReverted(id: string, revertedAt: string): Promise<void>;
39
53
  }
40
54
  export declare function createSwapsClient(config: SwapsClientConfig): SwapsClient;
@@ -1 +1 @@
1
- {"version":3,"file":"swaps-client.d.ts","sourceRoot":"","sources":["../../src/savings/swaps-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,+BAA+B,EAAE,MAAM,CAAC;IACjD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpF;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AA4BD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IACnC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACjD,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,WAAW,CAkExE"}
1
+ {"version":3,"file":"swaps-client.d.ts","sourceRoot":"","sources":["../../src/savings/swaps-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,+BAA+B,EAAE,MAAM,CAAC;IACjD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpF;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AA4BD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C;kEAC8D;IAC9D,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IACnC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACjD;wEACoE;IACpE,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,WAAW,CAiFxE"}
@@ -45,6 +45,9 @@ function createSwapsClient(config) {
45
45
  headers: { prefer: "resolution=ignore-duplicates,return=minimal" },
46
46
  body: JSON.stringify([
47
47
  {
48
+ // Stamp the tenant when scoped; else the house-org column default
49
+ // applies (single-tenant self-host). A hosted swap MUST carry its org.
50
+ ...(config.orgId !== undefined ? { org_id: config.orgId } : {}),
48
51
  id: event.id,
49
52
  proposal_id: event.proposalId,
50
53
  route: event.route,
@@ -59,7 +62,8 @@ function createSwapsClient(config) {
59
62
  });
60
63
  },
61
64
  async list() {
62
- const response = await request("swap_events?select=*&order=executed_at.desc", {
65
+ const scope = config.orgId !== undefined ? `&org_id=eq.${encodeURIComponent(config.orgId)}` : "";
66
+ const response = await request(`swap_events?select=*${scope}&order=executed_at.desc`, {
63
67
  method: "GET",
64
68
  });
65
69
  return (await response.json()).map(toRecord);
@@ -71,8 +75,18 @@ function createSwapsClient(config) {
71
75
  const rows = (await response.json());
72
76
  return rows.length > 0 ? toRecord(rows[0]) : null;
73
77
  },
78
+ async orgOf(id) {
79
+ const response = await request(`swap_events?select=org_id&id=eq.${encodeURIComponent(id)}`, {
80
+ method: "GET",
81
+ });
82
+ const rows = (await response.json());
83
+ return rows.length > 0 && typeof rows[0].org_id === "string" ? rows[0].org_id : null;
84
+ },
74
85
  async markReverted(id, revertedAt) {
75
- await request(`swap_events?id=eq.${encodeURIComponent(id)}`, {
86
+ // `reverted_at=is.null` so a concurrent second revert matches zero rows
87
+ // rather than overwriting the first revert's timestamp (a benign TOCTOU,
88
+ // hardened: the odometer's stop-accruing point stays the first revert).
89
+ await request(`swap_events?id=eq.${encodeURIComponent(id)}&reverted_at=is.null`, {
76
90
  method: "PATCH",
77
91
  body: JSON.stringify({ reverted_at: revertedAt }),
78
92
  });
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The api-mode task-settings fetcher (settings-plane §1.5): one authenticated
3
+ * GET to the hosted control plane per task, shaped into a `TaskSettings` for the
4
+ * resolver's cache. Kept apart from the pure resolver so the resolver stays
5
+ * network-free (unit-testable with a mock fetcher) and this file owns the one
6
+ * HTTP concern.
7
+ *
8
+ * Mirrors the api SINK's posture (telemetry/sinks.ts): same `Bearer rig_sk_`
9
+ * header, same "status only, never the body, never the URL in an error" leak
10
+ * discipline (C2). NEVER throws for a network/HTTP failure — it resolves `null`
11
+ * so the resolver stays fail-open (a settings read must never take a run down).
12
+ */
13
+ import type { RigConfig } from "../config";
14
+ import { type TaskSettingsFetcher, type TaskSettingsResolver } from "./task-settings";
15
+ export interface ApiTaskSettingsFetcherConfig {
16
+ /** Base URL of the hosted control plane, e.g. https://api.modelrig.ai — the
17
+ * SAME `ingestUrl` the api sink uses (the settings path hangs off the host). */
18
+ readonly ingestUrl: string;
19
+ /** `rig_sk_…` key. GET needs no special scope (settings ride the readable task
20
+ * row — see the server handler), so the ingest key suffices. Header only,
21
+ * never a URL (C2). */
22
+ readonly apiKey: string;
23
+ /** Injectable for tests; defaults to the global fetch. */
24
+ readonly fetchFn?: typeof fetch;
25
+ }
26
+ /**
27
+ * Build the api-mode fetcher. `fetch(task)` GETs `/v1/tasks/:task/settings` and
28
+ * returns the parsed settings, or null on ANY non-200, network error, or
29
+ * malformed body — the resolver reads null as "leave the cache as it is".
30
+ */
31
+ export declare function createApiTaskSettingsFetcher(config: ApiTaskSettingsFetcherConfig): TaskSettingsFetcher;
32
+ /**
33
+ * Construct AND start the api-mode settings resolver from a rig config, or null
34
+ * when the rig is not in api mode (local-only/direct keep env/YAML). Folds the
35
+ * fetcher + resolver + start into one call so the SDK entrypoint stays under the
36
+ * LOC cap. The resolver refreshes on `intervalMs` (the exporter interval).
37
+ */
38
+ export declare function startApiSettings(config: RigConfig, defaultIntervalMs: number): TaskSettingsResolver | null;
39
+ //# sourceMappingURL=api-fetcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-fetcher.d.ts","sourceRoot":"","sources":["../../src/settings/api-fetcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,WAAW,CAAC;AACtD,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,4BAA4B;IAC3C;oFACgF;IAChF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;2BAEuB;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAwBD;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,4BAA4B,GACnC,mBAAmB,CAsBrB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EACjB,iBAAiB,EAAE,MAAM,GACxB,oBAAoB,GAAG,IAAI,CAW7B"}
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ /**
3
+ * The api-mode task-settings fetcher (settings-plane §1.5): one authenticated
4
+ * GET to the hosted control plane per task, shaped into a `TaskSettings` for the
5
+ * resolver's cache. Kept apart from the pure resolver so the resolver stays
6
+ * network-free (unit-testable with a mock fetcher) and this file owns the one
7
+ * HTTP concern.
8
+ *
9
+ * Mirrors the api SINK's posture (telemetry/sinks.ts): same `Bearer rig_sk_`
10
+ * header, same "status only, never the body, never the URL in an error" leak
11
+ * discipline (C2). NEVER throws for a network/HTTP failure — it resolves `null`
12
+ * so the resolver stays fail-open (a settings read must never take a run down).
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.createApiTaskSettingsFetcher = createApiTaskSettingsFetcher;
16
+ exports.startApiSettings = startApiSettings;
17
+ const task_settings_1 = require("./task-settings");
18
+ const SCRUB_MODES = ["off", "detect", "redact", "block"];
19
+ /** Narrow an unknown JSON body into a `TaskSettings`, or null when the shape is
20
+ * not what the endpoint promises. A malformed 200 is treated like a miss
21
+ * (fail-open) rather than trusted — we never fabricate a capture-on from junk. */
22
+ function parseSettings(body) {
23
+ if (typeof body !== "object" || body === null)
24
+ return null;
25
+ const settings = body.settings;
26
+ if (typeof settings !== "object" || settings === null)
27
+ return null;
28
+ const s = settings;
29
+ if (typeof s.capture !== "boolean")
30
+ return null;
31
+ if (!(s.artifacts_enabled === null || typeof s.artifacts_enabled === "boolean"))
32
+ return null;
33
+ if (typeof s.artifact_content !== "boolean")
34
+ return null;
35
+ if (!SCRUB_MODES.includes(s.scrub))
36
+ return null;
37
+ return {
38
+ capture: s.capture,
39
+ artifacts_enabled: s.artifacts_enabled,
40
+ artifact_content: s.artifact_content,
41
+ scrub: s.scrub,
42
+ };
43
+ }
44
+ /**
45
+ * Build the api-mode fetcher. `fetch(task)` GETs `/v1/tasks/:task/settings` and
46
+ * returns the parsed settings, or null on ANY non-200, network error, or
47
+ * malformed body — the resolver reads null as "leave the cache as it is".
48
+ */
49
+ function createApiTaskSettingsFetcher(config) {
50
+ const base = config.ingestUrl.replace(/\/$/, "");
51
+ const fetchFn = config.fetchFn ?? fetch;
52
+ return {
53
+ async fetch(task) {
54
+ const url = `${base}/v1/tasks/${encodeURIComponent(task)}/settings`;
55
+ let response;
56
+ try {
57
+ response = await fetchFn(url, {
58
+ method: "GET",
59
+ headers: { authorization: `Bearer ${config.apiKey}` },
60
+ });
61
+ }
62
+ catch {
63
+ // Network throw — no status. Fail-open: the resolver keeps last-known.
64
+ return null;
65
+ }
66
+ // 404 (task not yet registered) and every other non-200 → null, no throw.
67
+ if (!response.ok)
68
+ return null;
69
+ const body = (await response.json().catch(() => null));
70
+ return parseSettings(body);
71
+ },
72
+ };
73
+ }
74
+ /**
75
+ * Construct AND start the api-mode settings resolver from a rig config, or null
76
+ * when the rig is not in api mode (local-only/direct keep env/YAML). Folds the
77
+ * fetcher + resolver + start into one call so the SDK entrypoint stays under the
78
+ * LOC cap. The resolver refreshes on `intervalMs` (the exporter interval).
79
+ */
80
+ function startApiSettings(config, defaultIntervalMs) {
81
+ if (config.export?.mode !== "api")
82
+ return null;
83
+ const resolver = (0, task_settings_1.createTaskSettingsResolver)({
84
+ fetcher: createApiTaskSettingsFetcher({
85
+ ingestUrl: config.export.ingestUrl,
86
+ apiKey: config.export.apiKey,
87
+ }),
88
+ intervalMs: config.export.intervalMs ?? defaultIntervalMs,
89
+ });
90
+ resolver.start();
91
+ return resolver;
92
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Control-plane settings resolution (settings-plane §1.5) — the barrel.
3
+ * Re-exported from the SDK entrypoint so `index.ts` stays under the LOC cap and
4
+ * the settings surface has one home.
5
+ */
6
+ export { createTaskSettingsResolver, effectiveCapture, DEFAULT_TASK_SETTINGS, } from "./task-settings";
7
+ export type { TaskSettings, TaskSettingsResolver, TaskSettingsFetcher } from "./task-settings";
8
+ export { createApiTaskSettingsFetcher, startApiSettings } from "./api-fetcher";
9
+ export type { ApiTaskSettingsFetcherConfig } from "./api-fetcher";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/settings/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,0BAA0B,EAC1B,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC/F,OAAO,EAAE,4BAA4B,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC/E,YAAY,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ /**
3
+ * Control-plane settings resolution (settings-plane §1.5) — the barrel.
4
+ * Re-exported from the SDK entrypoint so `index.ts` stays under the LOC cap and
5
+ * the settings surface has one home.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.startApiSettings = exports.createApiTaskSettingsFetcher = exports.DEFAULT_TASK_SETTINGS = exports.effectiveCapture = exports.createTaskSettingsResolver = void 0;
9
+ var task_settings_1 = require("./task-settings");
10
+ Object.defineProperty(exports, "createTaskSettingsResolver", { enumerable: true, get: function () { return task_settings_1.createTaskSettingsResolver; } });
11
+ Object.defineProperty(exports, "effectiveCapture", { enumerable: true, get: function () { return task_settings_1.effectiveCapture; } });
12
+ Object.defineProperty(exports, "DEFAULT_TASK_SETTINGS", { enumerable: true, get: function () { return task_settings_1.DEFAULT_TASK_SETTINGS; } });
13
+ var api_fetcher_1 = require("./api-fetcher");
14
+ Object.defineProperty(exports, "createApiTaskSettingsFetcher", { enumerable: true, get: function () { return api_fetcher_1.createApiTaskSettingsFetcher; } });
15
+ Object.defineProperty(exports, "startApiSettings", { enumerable: true, get: function () { return api_fetcher_1.startApiSettings; } });
@@ -0,0 +1,91 @@
1
+ /**
2
+ * SDK-side control-plane settings resolution (settings-plane §1.5).
3
+ *
4
+ * In api mode a task's governance settings — capture / artifacts / content /
5
+ * scrub — live in the CONTROL PLANE, not in YAML. The console, the API, and the
6
+ * MCP tool set them (Phase 1); the SDK PULLS them: once at boot and again on the
7
+ * exporter interval, cached, keyed by the task identity a run carries
8
+ * (`opts.task` — the E4 landing note: rig.run auto-step is NOT wired, so there is
9
+ * no ambient route context to key on). The control-plane value WINS over any
10
+ * YAML flag wherever a control plane is configured (spec §1.5); YAML `capture:`
11
+ * is deprecated and honored only by local-only rigs (see routes/loader.ts).
12
+ *
13
+ * ── FAIL-OPEN, ALWAYS ─────────────────────────────────────────────────────
14
+ * The run loop must never block on, or fail because of, this network read —
15
+ * same doctrine as the telemetry exporter. So:
16
+ * • `get(task)` is SYNCHRONOUS and reads only the cache; it never awaits.
17
+ * • a fetch that errors leaves the LAST-KNOWN value untouched (fail-open to
18
+ * last-known).
19
+ * • a task never yet fetched resolves to DEFAULT_TASK_SETTINGS (capture off —
20
+ * the safe floor), never a block.
21
+ *
22
+ * local-only rigs (no control plane) never construct this at all — env/YAML stay
23
+ * their only mechanism, unchanged.
24
+ */
25
+ import type { ScrubMode } from "../config";
26
+ /** The four governance settings, exactly as `GET /v1/tasks/:task/settings`
27
+ * returns them under `settings` (mirrors the server's `TaskSettings`). */
28
+ export interface TaskSettings {
29
+ readonly capture: boolean;
30
+ /** null = inherit the gateway/SDK default (§2). A boolean overrides it. */
31
+ readonly artifacts_enabled: boolean | null;
32
+ readonly artifact_content: boolean;
33
+ readonly scrub: ScrubMode;
34
+ }
35
+ /** The safe floor for a task the control plane has not answered for yet: capture
36
+ * OFF, artifacts inherit the default, content OFF, scrub the schema default. A
37
+ * run that reaches the cache before the first fetch lands sees this — never a
38
+ * block, never a silent capture-on. */
39
+ export declare const DEFAULT_TASK_SETTINGS: TaskSettings;
40
+ /** Pulls one task's settings from the control plane. Returns the settings, or
41
+ * `null` when the task is unknown (404) or the read is unreadable — the resolver
42
+ * treats null as "leave the cache as it is" (fail-open). NEVER throws for a
43
+ * network/HTTP failure; it resolves null so the caller stays fail-open. */
44
+ export interface TaskSettingsFetcher {
45
+ fetch(task: string): Promise<TaskSettings | null>;
46
+ }
47
+ export interface TaskSettingsResolver {
48
+ /** The effective settings for a task, from the cache only — synchronous,
49
+ * never throws, never awaits. Last-known if fetched before; DEFAULT otherwise. */
50
+ get(task: string): TaskSettings;
51
+ /** A copy of every tracked task's last-known settings — for `rig.status()` /
52
+ * the boot posture. Empty until the first run tracks a task. */
53
+ snapshot(): ReadonlyMap<string, TaskSettings>;
54
+ /** Register a task so the interval refresh keeps it fresh, and kick a
55
+ * one-shot fetch now if it has never been seen. Fire-and-forget; fail-open.
56
+ * Called on the first run that carries this `opts.task`. */
57
+ track(task: string): void;
58
+ /** Re-pull every tracked task once. The boot pull and each exporter-interval
59
+ * tick call this. Resolves after all fetches settle; never rejects. */
60
+ refresh(): Promise<void>;
61
+ /** Begin the interval loop (idempotent). */
62
+ start(): void;
63
+ /** Stop the interval loop (idempotent). */
64
+ stop(): void;
65
+ }
66
+ export interface TaskSettingsResolverConfig {
67
+ readonly fetcher: TaskSettingsFetcher;
68
+ /** Refresh cadence — the exporter interval, threaded from index.ts so the two
69
+ * loops beat together (spec §1.5: "refreshes on the exporter interval"). */
70
+ readonly intervalMs: number;
71
+ /** Injectable for tests; defaults to the real timers. */
72
+ readonly setInterval?: (fn: () => void, ms: number) => ReturnType<typeof setInterval>;
73
+ readonly clearInterval?: (handle: ReturnType<typeof setInterval>) => void;
74
+ }
75
+ /**
76
+ * Build the api-mode task-settings resolver. Holds a per-task cache and an
77
+ * interval that re-pulls every tracked task, so a console flip is reflected
78
+ * within one interval WITHOUT a redeploy (spec acceptance d). Every fetch is
79
+ * fail-open: a rejection or a null leaves the last-known value in place.
80
+ */
81
+ export declare function createTaskSettingsResolver(config: TaskSettingsResolverConfig): TaskSettingsResolver;
82
+ /**
83
+ * The EFFECTIVE capture flag for a run (spec §1.5 precedence). When a control
84
+ * plane is configured (resolver present) it WINS: the YAML flag is ignored
85
+ * entirely — deprecated, warned about at load (routes/loader.ts). Only a
86
+ * local-only rig (resolver null) lets the YAML flag govern. A run with no task
87
+ * identity to key on (`opts.task` absent) falls back to YAML even in api mode,
88
+ * because there is no control-plane row to resolve.
89
+ */
90
+ export declare function effectiveCapture(resolver: TaskSettingsResolver | null, task: string | undefined, yamlCapture: boolean): boolean;
91
+ //# sourceMappingURL=task-settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-settings.d.ts","sourceRoot":"","sources":["../../src/settings/task-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C;0EAC0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,iBAAiB,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;CAC3B;AAED;;;uCAGuC;AACvC,eAAO,MAAM,qBAAqB,EAAE,YAKnC,CAAC;AAEF;;;2EAG2E;AAC3E,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,oBAAoB;IACnC;sFACkF;IAClF,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAAC;IAChC;oEACgE;IAChE,QAAQ,IAAI,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC9C;;gEAE4D;IAC5D,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;2EACuE;IACvE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,4CAA4C;IAC5C,KAAK,IAAI,IAAI,CAAC;IACd,2CAA2C;IAC3C,IAAI,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC;gFAC4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yDAAyD;IACzD,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;IACtF,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,KAAK,IAAI,CAAC;CAC3E;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,0BAA0B,GACjC,oBAAoB,CA4DtB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,oBAAoB,GAAG,IAAI,EACrC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,WAAW,EAAE,OAAO,GACnB,OAAO,CAGT"}
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ /**
3
+ * SDK-side control-plane settings resolution (settings-plane §1.5).
4
+ *
5
+ * In api mode a task's governance settings — capture / artifacts / content /
6
+ * scrub — live in the CONTROL PLANE, not in YAML. The console, the API, and the
7
+ * MCP tool set them (Phase 1); the SDK PULLS them: once at boot and again on the
8
+ * exporter interval, cached, keyed by the task identity a run carries
9
+ * (`opts.task` — the E4 landing note: rig.run auto-step is NOT wired, so there is
10
+ * no ambient route context to key on). The control-plane value WINS over any
11
+ * YAML flag wherever a control plane is configured (spec §1.5); YAML `capture:`
12
+ * is deprecated and honored only by local-only rigs (see routes/loader.ts).
13
+ *
14
+ * ── FAIL-OPEN, ALWAYS ─────────────────────────────────────────────────────
15
+ * The run loop must never block on, or fail because of, this network read —
16
+ * same doctrine as the telemetry exporter. So:
17
+ * • `get(task)` is SYNCHRONOUS and reads only the cache; it never awaits.
18
+ * • a fetch that errors leaves the LAST-KNOWN value untouched (fail-open to
19
+ * last-known).
20
+ * • a task never yet fetched resolves to DEFAULT_TASK_SETTINGS (capture off —
21
+ * the safe floor), never a block.
22
+ *
23
+ * local-only rigs (no control plane) never construct this at all — env/YAML stay
24
+ * their only mechanism, unchanged.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.DEFAULT_TASK_SETTINGS = void 0;
28
+ exports.createTaskSettingsResolver = createTaskSettingsResolver;
29
+ exports.effectiveCapture = effectiveCapture;
30
+ /** The safe floor for a task the control plane has not answered for yet: capture
31
+ * OFF, artifacts inherit the default, content OFF, scrub the schema default. A
32
+ * run that reaches the cache before the first fetch lands sees this — never a
33
+ * block, never a silent capture-on. */
34
+ exports.DEFAULT_TASK_SETTINGS = {
35
+ capture: false,
36
+ artifacts_enabled: null,
37
+ artifact_content: false,
38
+ scrub: "detect",
39
+ };
40
+ /**
41
+ * Build the api-mode task-settings resolver. Holds a per-task cache and an
42
+ * interval that re-pulls every tracked task, so a console flip is reflected
43
+ * within one interval WITHOUT a redeploy (spec acceptance d). Every fetch is
44
+ * fail-open: a rejection or a null leaves the last-known value in place.
45
+ */
46
+ function createTaskSettingsResolver(config) {
47
+ const cache = new Map();
48
+ const tracked = new Set();
49
+ const setIntervalFn = config.setInterval ?? setInterval;
50
+ const clearIntervalFn = config.clearInterval ?? clearInterval;
51
+ let timer = null;
52
+ /** Pull one task and fold the result into the cache. Fail-open: a thrown
53
+ * error or a null result is swallowed and the cache is left untouched. */
54
+ async function pull(task) {
55
+ try {
56
+ const settings = await config.fetcher.fetch(task);
57
+ if (settings !== null)
58
+ cache.set(task, settings);
59
+ }
60
+ catch {
61
+ // Fail-open: keep the last-known value. A settings read must never take a
62
+ // run down, and a warning here would fire once per interval per task —
63
+ // noise. The value simply does not advance until the plane answers again.
64
+ }
65
+ }
66
+ return {
67
+ get(task) {
68
+ return cache.get(task) ?? exports.DEFAULT_TASK_SETTINGS;
69
+ },
70
+ snapshot() {
71
+ return new Map(cache);
72
+ },
73
+ track(task) {
74
+ if (tracked.has(task))
75
+ return;
76
+ tracked.add(task);
77
+ // First sighting: pull now so the value is fresh as soon as possible,
78
+ // rather than waiting up to a full interval. Fire-and-forget.
79
+ void pull(task);
80
+ },
81
+ async refresh() {
82
+ // Settle all, never reject — one task's failure must not abort the others'
83
+ // refresh (Promise.allSettled, though each pull already swallows its own).
84
+ await Promise.all([...tracked].map((t) => pull(t)));
85
+ },
86
+ start() {
87
+ if (timer !== null)
88
+ return;
89
+ timer = setIntervalFn(() => {
90
+ void this.refresh();
91
+ }, config.intervalMs);
92
+ // Never keep the process alive for a settings refresh (mirrors the
93
+ // exporter timer's posture).
94
+ timer.unref?.();
95
+ },
96
+ stop() {
97
+ if (timer !== null) {
98
+ clearIntervalFn(timer);
99
+ timer = null;
100
+ }
101
+ },
102
+ };
103
+ }
104
+ /**
105
+ * The EFFECTIVE capture flag for a run (spec §1.5 precedence). When a control
106
+ * plane is configured (resolver present) it WINS: the YAML flag is ignored
107
+ * entirely — deprecated, warned about at load (routes/loader.ts). Only a
108
+ * local-only rig (resolver null) lets the YAML flag govern. A run with no task
109
+ * identity to key on (`opts.task` absent) falls back to YAML even in api mode,
110
+ * because there is no control-plane row to resolve.
111
+ */
112
+ function effectiveCapture(resolver, task, yamlCapture) {
113
+ if (resolver === null || task === undefined)
114
+ return yamlCapture;
115
+ return resolver.get(task).capture;
116
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Rig posture — the boot banner + `/status` report (settings-plane §2).
3
+ *
4
+ * WS1's lesson, made a rule: "a default nobody can observe gets asked about
5
+ * instead of answered." The artifact namespace now defaults ON when a control
6
+ * plane is configured (§2 flip), so a user must be able to SEE whether it is on
7
+ * and WHY, without reading source. This module derives that posture from the
8
+ * resolved config (plus, for `/status`, the settings resolver's snapshot) and
9
+ * shapes it for a one-line boot banner and a fuller `/status` report.
10
+ *
11
+ * Pure formatters (no env, no network) except `emitBootBanner`, which writes the
12
+ * one banner line to stderr — kept here so index.ts calls it in a single line.
13
+ */
14
+ import type { RigConfig } from "./config";
15
+ import type { TaskSettings } from "./settings/task-settings";
16
+ export type ExportMode = "api" | "direct" | "local-only";
17
+ export interface ArtifactsPosture {
18
+ readonly enabled: boolean;
19
+ /** A human sentence naming WHY — so a flipped default is observable. */
20
+ readonly reason: string;
21
+ }
22
+ export interface RigStatus {
23
+ readonly exportMode: ExportMode;
24
+ readonly artifacts: ArtifactsPosture;
25
+ /** Content custody (E2-C) — its own gate, unchanged by the §2 metadata flip. */
26
+ readonly contentCustody: boolean;
27
+ readonly otlp: "configured" | "disabled";
28
+ /** Cache accounting posture (cache-aware-routing WS-A/WS-D). Derived from the
29
+ * policy registry — config-only, so `status` shows it anywhere. Live per-route
30
+ * hit rate + effective input rate are a control-plane read, surfaced on the
31
+ * console route cache page (and GET /v1/routes/:route/cache-stats), not here. */
32
+ readonly cache: CachePosture;
33
+ /** Per-task control-plane settings the resolver has pulled (empty until a run
34
+ * tracks a task, and always empty for a local-only rig). */
35
+ readonly tasks: ReadonlyArray<{
36
+ readonly task: string;
37
+ } & TaskSettings>;
38
+ }
39
+ export interface CachePosture {
40
+ /** The versioned policy registry the accounting math reads (all providers
41
+ * share one `as_of`). */
42
+ readonly policyVersion: string;
43
+ /** Providers whose caching ModelRig accounts for on its path (first-party
44
+ * seeded, `cache_supported: true`); fail-closed providers are omitted. */
45
+ readonly eligibleProviders: readonly string[];
46
+ }
47
+ /** The cache posture from the policy registry alone (WS-A) — which providers
48
+ * ModelRig accounts caching for, and the policy version. No telemetry: live
49
+ * hit rates live behind the control-plane rollup (WS-B / the console). */
50
+ export declare function cachePosture(): CachePosture;
51
+ export declare function exportModeOf(config: RigConfig): ExportMode;
52
+ /**
53
+ * The artifact-namespace posture as a boolean + a reason, derivable from config
54
+ * ALONE (so both the banner and `/status` agree). The four cases the §2 flip
55
+ * creates, each named honestly:
56
+ * on + control plane → the default is now on (a sink exists to ship to)
57
+ * on + local-only → an explicit MODELRIG_ARTIFACTS opt-in
58
+ * off + control plane → an explicit MODELRIG_ARTIFACTS=0 opt-out
59
+ * off + local-only → nothing to ship to; off is the local-only default
60
+ */
61
+ export declare function artifactsPosture(config: RigConfig): ArtifactsPosture;
62
+ /** Build the full status snapshot from config + the resolver's per-task cache. */
63
+ export declare function buildRigStatus(config: RigConfig, tasks?: ReadonlyMap<string, TaskSettings>): RigStatus;
64
+ /** The one-line boot banner (§2). Concise on purpose — mode, artifacts (+why),
65
+ * content, otlp. */
66
+ export declare function formatBootBanner(config: RigConfig): string;
67
+ /**
68
+ * Emit the boot banner to stderr — ONCE at rig construction, and ONLY when a
69
+ * control plane is configured (the case where the §2 default flip is in effect
70
+ * and the posture is non-obvious). A pure local-only rig with artifacts off is
71
+ * the boring, expected default and stays silent (no banner noise in tests or
72
+ * embedders). Stderr, so it never pollutes a command's stdout.
73
+ */
74
+ export declare function emitBootBanner(config: RigConfig): void;
75
+ /** Render a `/status` report as multi-line text for the CLI. */
76
+ export declare function formatStatusReport(status: RigStatus): string;
77
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,YAAY,CAAC;AAEzD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,gFAAgF;IAChF,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,UAAU,CAAC;IACzC;;;qFAGiF;IACjF,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B;gEAC4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,YAAY,CAAC,CAAC;CACzE;AAED,MAAM,WAAW,YAAY;IAC3B;6BACyB;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;8EAC0E;IAC1E,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/C;AAED;;0EAE0E;AAC1E,wBAAgB,YAAY,IAAI,YAAY,CAI3C;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,UAAU,CAE1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,gBAAgB,CAapE;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAC5B,MAAM,EAAE,SAAS,EACjB,KAAK,GAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAa,GACnD,SAAS,CASX;AAED;oBACoB;AACpB,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAS1D;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAGtD;AAED,gEAAgE;AAChE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAwB5D"}