@jinn-network/client 0.1.8 → 0.1.9-canary.050a41b1

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 (263) hide show
  1. package/dist/adapters/mech/adapter.d.ts +21 -1
  2. package/dist/adapters/mech/adapter.js +77 -10
  3. package/dist/adapters/mech/adapter.js.map +1 -1
  4. package/dist/adapters/mech/contracts.js +62 -28
  5. package/dist/adapters/mech/contracts.js.map +1 -1
  6. package/dist/adapters/mech/safe-revert.d.ts +4 -0
  7. package/dist/adapters/mech/safe-revert.js +5 -1
  8. package/dist/adapters/mech/safe-revert.js.map +1 -1
  9. package/dist/adapters/mech/safe.js +5 -1
  10. package/dist/adapters/mech/safe.js.map +1 -1
  11. package/dist/adapters/mech/verdict-code.js +1 -1
  12. package/dist/adapters/mech/verdict-code.js.map +1 -1
  13. package/dist/api/bootstrap-endpoint.d.ts +1 -0
  14. package/dist/api/bootstrap-endpoint.js +1 -0
  15. package/dist/api/bootstrap-endpoint.js.map +1 -1
  16. package/dist/api/discovery-endpoint.d.ts +1 -0
  17. package/dist/api/discovery-endpoint.js +24 -0
  18. package/dist/api/discovery-endpoint.js.map +1 -1
  19. package/dist/api/fleet-build.d.ts +1 -7
  20. package/dist/api/fleet-build.js +0 -7
  21. package/dist/api/fleet-build.js.map +1 -1
  22. package/dist/api/gather-status.d.ts +8 -2
  23. package/dist/api/gather-status.js +29 -117
  24. package/dist/api/gather-status.js.map +1 -1
  25. package/dist/api/loop-completion-build.d.ts +79 -0
  26. package/dist/api/loop-completion-build.js +155 -0
  27. package/dist/api/loop-completion-build.js.map +1 -0
  28. package/dist/api/peers.js +2 -0
  29. package/dist/api/peers.js.map +1 -1
  30. package/dist/api/setup-endpoints.d.ts +32 -0
  31. package/dist/api/setup-endpoints.js +93 -23
  32. package/dist/api/setup-endpoints.js.map +1 -1
  33. package/dist/api/solvernets-endpoints.js +3 -0
  34. package/dist/api/solvernets-endpoints.js.map +1 -1
  35. package/dist/api/status-build.d.ts +43 -33
  36. package/dist/api/status-build.js +3 -26
  37. package/dist/api/status-build.js.map +1 -1
  38. package/dist/api/status-rollup-build.d.ts +0 -4
  39. package/dist/api/status-rollup-build.js +0 -4
  40. package/dist/api/status-rollup-build.js.map +1 -1
  41. package/dist/build-info.json +4 -4
  42. package/dist/build-meta.json +1 -1
  43. package/dist/cli/commands/codedigest-revert-check.js +6 -2
  44. package/dist/cli/commands/codedigest-revert-check.js.map +1 -1
  45. package/dist/cli/commands/doctor.d.ts +3 -0
  46. package/dist/cli/commands/doctor.js +37 -2
  47. package/dist/cli/commands/doctor.js.map +1 -1
  48. package/dist/cli/commands/eval.d.ts +76 -0
  49. package/dist/cli/commands/eval.js +401 -0
  50. package/dist/cli/commands/eval.js.map +1 -0
  51. package/dist/cli/commands/rewards.d.ts +2 -0
  52. package/dist/cli/commands/rewards.js +30 -3
  53. package/dist/cli/commands/rewards.js.map +1 -1
  54. package/dist/cli/commands/solver-nets.js +68 -0
  55. package/dist/cli/commands/solver-nets.js.map +1 -1
  56. package/dist/cli/commands/status.js +0 -1
  57. package/dist/cli/commands/status.js.map +1 -1
  58. package/dist/cli/index.js +2 -0
  59. package/dist/cli/index.js.map +1 -1
  60. package/dist/config.d.ts +58 -7
  61. package/dist/config.js +96 -7
  62. package/dist/config.js.map +1 -1
  63. package/dist/daemon/ai-units-gate.d.ts +6 -6
  64. package/dist/daemon/ai-units-gate.js +11 -10
  65. package/dist/daemon/ai-units-gate.js.map +1 -1
  66. package/dist/daemon/balance-topup-loop.js +3 -0
  67. package/dist/daemon/balance-topup-loop.js.map +1 -1
  68. package/dist/daemon/creator.js +2 -0
  69. package/dist/daemon/creator.js.map +1 -1
  70. package/dist/daemon/daemon.d.ts +15 -0
  71. package/dist/daemon/daemon.js +78 -22
  72. package/dist/daemon/daemon.js.map +1 -1
  73. package/dist/daemon/eviction-loop.d.ts +7 -0
  74. package/dist/daemon/eviction-loop.js +16 -0
  75. package/dist/daemon/eviction-loop.js.map +1 -1
  76. package/dist/daemon/jinn-claim-loop.js +3 -0
  77. package/dist/daemon/jinn-claim-loop.js.map +1 -1
  78. package/dist/daemon/join-applier.d.ts +35 -0
  79. package/dist/daemon/join-applier.js +49 -0
  80. package/dist/daemon/join-applier.js.map +1 -0
  81. package/dist/daemon/loop-heartbeat.d.ts +34 -0
  82. package/dist/daemon/loop-heartbeat.js +39 -0
  83. package/dist/daemon/loop-heartbeat.js.map +1 -0
  84. package/dist/daemon/reward-claim-loop.js +3 -0
  85. package/dist/daemon/reward-claim-loop.js.map +1 -1
  86. package/dist/daemon/watchdog-loop.d.ts +84 -0
  87. package/dist/daemon/watchdog-loop.js +91 -0
  88. package/dist/daemon/watchdog-loop.js.map +1 -0
  89. package/dist/dashboard/assets/index-8tAiMbUV.css +1 -0
  90. package/dist/dashboard/assets/index-CSFVwGFh.js +167 -0
  91. package/dist/dashboard/index.html +2 -2
  92. package/dist/discovery/http.d.ts +7 -0
  93. package/dist/discovery/http.js +241 -25
  94. package/dist/discovery/http.js.map +1 -1
  95. package/dist/discovery/onchain.js +155 -1
  96. package/dist/discovery/onchain.js.map +1 -1
  97. package/dist/discovery/types.d.ts +106 -0
  98. package/dist/discovery/types.js +40 -0
  99. package/dist/discovery/types.js.map +1 -1
  100. package/dist/discovery/with-fallback.js +14 -0
  101. package/dist/discovery/with-fallback.js.map +1 -1
  102. package/dist/earning/bootstrap.d.ts +23 -0
  103. package/dist/earning/bootstrap.js +76 -27
  104. package/dist/earning/bootstrap.js.map +1 -1
  105. package/dist/earning/faucet.d.ts +1 -1
  106. package/dist/earning/faucet.js +2 -2
  107. package/dist/earning/faucet.js.map +1 -1
  108. package/dist/earning/safe-adapter.js +11 -0
  109. package/dist/earning/safe-adapter.js.map +1 -1
  110. package/dist/eval/eval-harness-run.d.ts +63 -0
  111. package/dist/eval/eval-harness-run.js +123 -0
  112. package/dist/eval/eval-harness-run.js.map +1 -0
  113. package/dist/eval/orchestrator.d.ts +163 -0
  114. package/dist/eval/orchestrator.js +232 -0
  115. package/dist/eval/orchestrator.js.map +1 -0
  116. package/dist/eval/paired.d.ts +68 -0
  117. package/dist/eval/paired.js +93 -0
  118. package/dist/eval/paired.js.map +1 -0
  119. package/dist/eval/resolve-slate-tasks.d.ts +35 -0
  120. package/dist/eval/resolve-slate-tasks.js +56 -0
  121. package/dist/eval/resolve-slate-tasks.js.map +1 -0
  122. package/dist/eval/screen-discovery.d.ts +22 -0
  123. package/dist/eval/screen-discovery.js +71 -0
  124. package/dist/eval/screen-discovery.js.map +1 -0
  125. package/dist/eval/screen-progress.d.ts +41 -0
  126. package/dist/eval/screen-progress.js +60 -0
  127. package/dist/eval/screen-progress.js.map +1 -0
  128. package/dist/eval/screen-runner.d.ts +30 -0
  129. package/dist/eval/screen-runner.js +289 -0
  130. package/dist/eval/screen-runner.js.map +1 -0
  131. package/dist/eval/screen.d.ts +107 -0
  132. package/dist/eval/screen.js +159 -0
  133. package/dist/eval/screen.js.map +1 -0
  134. package/dist/eval/slope.d.ts +29 -0
  135. package/dist/eval/slope.js +46 -0
  136. package/dist/eval/slope.js.map +1 -0
  137. package/dist/eval/train-sequence.d.ts +35 -0
  138. package/dist/eval/train-sequence.js +59 -0
  139. package/dist/eval/train-sequence.js.map +1 -0
  140. package/dist/eval/wilson.d.ts +45 -0
  141. package/dist/eval/wilson.js +48 -0
  142. package/dist/eval/wilson.js.map +1 -0
  143. package/dist/harnesses/engine/canonical-json.js +5 -3
  144. package/dist/harnesses/engine/canonical-json.js.map +1 -1
  145. package/dist/harnesses/engine/engine.d.ts +24 -0
  146. package/dist/harnesses/engine/engine.js +72 -9
  147. package/dist/harnesses/engine/engine.js.map +1 -1
  148. package/dist/harnesses/engine/persistence.d.ts +17 -0
  149. package/dist/harnesses/engine/persistence.js +28 -0
  150. package/dist/harnesses/engine/persistence.js.map +1 -1
  151. package/dist/harnesses/impls/hermes-agent/adapter.d.ts +2 -0
  152. package/dist/harnesses/impls/hermes-agent/adapter.js +8 -5
  153. package/dist/harnesses/impls/hermes-agent/adapter.js.map +1 -1
  154. package/dist/harnesses/impls/hermes-agent/bootstrap.d.ts +1 -0
  155. package/dist/harnesses/impls/hermes-agent/bootstrap.js +6 -1
  156. package/dist/harnesses/impls/hermes-agent/bootstrap.js.map +1 -1
  157. package/dist/harnesses/impls/hermes-agent/harness.d.ts +17 -3
  158. package/dist/harnesses/impls/hermes-agent/harness.js +68 -5
  159. package/dist/harnesses/impls/hermes-agent/harness.js.map +1 -1
  160. package/dist/harnesses/impls/index.d.ts +2 -0
  161. package/dist/harnesses/impls/index.js +2 -0
  162. package/dist/harnesses/impls/index.js.map +1 -1
  163. package/dist/harnesses/impls/learner/adapters/claude-code.js +5 -0
  164. package/dist/harnesses/impls/learner/adapters/claude-code.js.map +1 -1
  165. package/dist/harnesses/impls/learner/harness.d.ts +17 -1
  166. package/dist/harnesses/impls/learner/harness.js +51 -1
  167. package/dist/harnesses/impls/learner/harness.js.map +1 -1
  168. package/dist/harnesses/impls/learner/harvest.d.ts +2 -0
  169. package/dist/harnesses/impls/learner/harvest.js +7 -1
  170. package/dist/harnesses/impls/learner/harvest.js.map +1 -1
  171. package/dist/harnesses/impls/learner/plugin-path.js +1 -0
  172. package/dist/harnesses/impls/learner/plugin-path.js.map +1 -1
  173. package/dist/harnesses/impls/swe-rebench-v2-evaluator/harness.js +3 -1
  174. package/dist/harnesses/impls/swe-rebench-v2-evaluator/harness.js.map +1 -1
  175. package/dist/harnesses/impls/swe-rebench-v2-evaluator/index.d.ts +2 -2
  176. package/dist/harnesses/impls/swe-rebench-v2-evaluator/index.js +3 -1
  177. package/dist/harnesses/impls/swe-rebench-v2-evaluator/index.js.map +1 -1
  178. package/dist/harnesses/readiness-registry.d.ts +10 -0
  179. package/dist/harnesses/readiness-registry.js +13 -0
  180. package/dist/harnesses/readiness-registry.js.map +1 -1
  181. package/dist/harnesses/types.d.ts +14 -0
  182. package/dist/learner/revert-decision.d.ts +16 -1
  183. package/dist/learner/revert-decision.js +38 -18
  184. package/dist/learner/revert-decision.js.map +1 -1
  185. package/dist/learner/revert-stats.d.ts +14 -0
  186. package/dist/learner/revert-stats.js +42 -0
  187. package/dist/learner/revert-stats.js.map +1 -1
  188. package/dist/local-provider-url.d.ts +3 -0
  189. package/dist/local-provider-url.js +28 -0
  190. package/dist/local-provider-url.js.map +1 -0
  191. package/dist/main.js +94 -25
  192. package/dist/main.js.map +1 -1
  193. package/dist/plugins/learner/.claude-plugin/plugin.json +1 -1
  194. package/dist/plugins/learner/.codex-plugin/plugin.json +1 -1
  195. package/dist/plugins/learner/hooks/session-start +30 -1
  196. package/dist/plugins/learner/skills/learn/consolidator-prompt.md +4 -0
  197. package/dist/preflight/deployment-readiness.d.ts +147 -0
  198. package/dist/preflight/deployment-readiness.js +366 -0
  199. package/dist/preflight/deployment-readiness.js.map +1 -0
  200. package/dist/preflight/pidfile-liveness.d.ts +7 -1
  201. package/dist/preflight/pidfile-liveness.js +14 -0
  202. package/dist/preflight/pidfile-liveness.js.map +1 -1
  203. package/dist/rpc/transport.d.ts +36 -0
  204. package/dist/rpc/transport.js +123 -24
  205. package/dist/rpc/transport.js.map +1 -1
  206. package/dist/scripts/swe-rebench-v2-seed-pool.json +2 -1
  207. package/dist/solver-nets/registry.d.ts +19 -0
  208. package/dist/solver-nets/registry.js +92 -66
  209. package/dist/solver-nets/registry.js.map +1 -1
  210. package/dist/solver-types/_swe-rebench-v2-held-out-slate.d.ts +76 -0
  211. package/dist/solver-types/_swe-rebench-v2-held-out-slate.js +156 -0
  212. package/dist/solver-types/_swe-rebench-v2-held-out-slate.js.map +1 -0
  213. package/dist/solver-types/_swe-rebench-v2-pool-recovery.d.ts +81 -0
  214. package/dist/solver-types/_swe-rebench-v2-pool-recovery.js +116 -0
  215. package/dist/solver-types/_swe-rebench-v2-pool-recovery.js.map +1 -0
  216. package/dist/solver-types/_swe-rebench-v2-state.d.ts +9 -0
  217. package/dist/solver-types/_swe-rebench-v2-state.js +14 -0
  218. package/dist/solver-types/_swe-rebench-v2-state.js.map +1 -1
  219. package/dist/solver-types/_swe-rebench-v2-validated-pool.d.ts +30 -0
  220. package/dist/solver-types/_swe-rebench-v2-validated-pool.js +40 -0
  221. package/dist/solver-types/_swe-rebench-v2-validated-pool.js.map +1 -1
  222. package/dist/solver-types/slates/held-out-slate.swe-rebench-v2.v1.json +20 -0
  223. package/dist/solver-types/slates/held-out-slate.swe-rebench-v2.v2.json +19 -0
  224. package/dist/solver-types/slates/held-out-slate.swe-rebench-v2.v2.screening-report.json +628 -0
  225. package/dist/solver-types/solver-type.d.ts +8 -0
  226. package/dist/solver-types/swe-rebench-v2.d.ts +2 -0
  227. package/dist/solver-types/swe-rebench-v2.js +115 -10
  228. package/dist/solver-types/swe-rebench-v2.js.map +1 -1
  229. package/dist/solvernets/launched-record-dispatcher.d.ts +3 -0
  230. package/dist/solvernets/launched-record-dispatcher.js.map +1 -1
  231. package/dist/solvernets/registry-client-erc8004.js +29 -37
  232. package/dist/solvernets/registry-client-erc8004.js.map +1 -1
  233. package/dist/solvernets/registry-client.d.ts +6 -0
  234. package/dist/solvernets/store.js +7 -2
  235. package/dist/solvernets/store.js.map +1 -1
  236. package/dist/spend/ai-units-config.d.ts +10 -0
  237. package/dist/spend/ai-units-config.js +7 -1
  238. package/dist/spend/ai-units-config.js.map +1 -1
  239. package/dist/spend/ai-units.d.ts +51 -0
  240. package/dist/spend/ai-units.js +73 -0
  241. package/dist/spend/ai-units.js.map +1 -1
  242. package/dist/spend/record.js +12 -5
  243. package/dist/spend/record.js.map +1 -1
  244. package/dist/store/store.d.ts +91 -5
  245. package/dist/store/store.js +170 -7
  246. package/dist/store/store.js.map +1 -1
  247. package/dist/vendor/@jinn-network/sdk/dist/payloads/swe-rebench-v2.d.ts +108 -1
  248. package/dist/vendor/@jinn-network/sdk/dist/payloads/swe-rebench-v2.js +25 -1
  249. package/dist/vendor/@jinn-network/sdk/dist/solvernets/swe-rebench-v2-held-out-slate.d.ts +65 -0
  250. package/dist/vendor/@jinn-network/sdk/dist/solvernets/swe-rebench-v2-held-out-slate.js +123 -0
  251. package/dist/vendor/@jinn-network/sdk/dist/solvernets/swe-rebench-v2.d.ts +2 -2
  252. package/dist/vendor/@jinn-network/sdk/dist/solvernets/swe-rebench-v2.js +1 -1
  253. package/dist/vendor/@jinn-network/sdk/package.json +4 -0
  254. package/docker-compose.yml +3 -2
  255. package/package.json +22 -18
  256. package/plugins/learner/.claude-plugin/plugin.json +1 -1
  257. package/plugins/learner/.codex-plugin/plugin.json +1 -1
  258. package/plugins/learner/hooks/session-start +30 -1
  259. package/plugins/learner/skills/learn/consolidator-prompt.md +4 -0
  260. package/plugins/swe-rebench-v2-runtime/hooks/hooks.json +16 -0
  261. package/plugins/swe-rebench-v2-runtime/hooks/session-start +74 -0
  262. package/dist/dashboard/assets/index-CzKxvMcU.css +0 -32
  263. package/dist/dashboard/assets/index-yVemxHot.js +0 -351
@@ -1,11 +1,13 @@
1
1
  import { resolveCredentialId } from './credential.js';
2
- import { projectAiUnits, resolveReferenceCeiling } from './ai-units.js';
2
+ import { projectAiUnits, projectTaskUsdMicros, resolveReferenceCeiling, resolveReferenceCeilingUsdMicros, } from './ai-units.js';
3
3
  export function buildAiUnitsConfig(config, env,
4
4
  /** Optional home dir override for resolveCredentialId's disk probe — tests only. */
5
5
  homeDirOverride) {
6
6
  const { units_per_block, units_per_week } = resolveReferenceCeiling(env);
7
+ const { usd_micros_per_block, usd_micros_per_week } = resolveReferenceCeilingUsdMicros(env);
7
8
  const manifestCredentials = {};
8
9
  const manifestProjectedAiUnits = {};
10
+ const manifestProjectedUsdMicros = {};
9
11
  const manifestModels = {};
10
12
  for (const [manifestCid, entry] of Object.entries(config.joinedSolverNets ?? {})) {
11
13
  const credentialId = resolveCredentialId(entry.harness, env, homeDirOverride);
@@ -13,6 +15,7 @@ homeDirOverride) {
13
15
  continue;
14
16
  manifestCredentials[manifestCid] = credentialId;
15
17
  manifestProjectedAiUnits[manifestCid] = projectAiUnits(entry.harness, entry.model, credentialId);
18
+ manifestProjectedUsdMicros[manifestCid] = projectTaskUsdMicros(entry.harness, entry.model, credentialId);
16
19
  manifestModels[manifestCid] = entry.model;
17
20
  }
18
21
  if (Object.keys(manifestCredentials).length === 0)
@@ -20,8 +23,11 @@ homeDirOverride) {
20
23
  return {
21
24
  capPerBlock: units_per_block,
22
25
  capPerWeek: units_per_week,
26
+ capPerBlockUsdMicros: usd_micros_per_block,
27
+ capPerWeekUsdMicros: usd_micros_per_week,
23
28
  manifestCredentials,
24
29
  manifestProjectedAiUnits,
30
+ manifestProjectedUsdMicros,
25
31
  manifestModels,
26
32
  };
27
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ai-units-config.js","sourceRoot":"","sources":["../../src/spend/ai-units-config.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,mBAAmB,EAAqB,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAsBxE,MAAM,UAAU,kBAAkB,CAChC,MAA4C,EAC5C,GAAsB;AACtB,oFAAoF;AACpF,eAAwB;IAExB,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,GAAG,uBAAuB,CAAC,GAAG,CAAC,CAAC;IAEzE,MAAM,mBAAmB,GAAiC,EAAE,CAAC;IAC7D,MAAM,wBAAwB,GAAkC,EAAE,CAAC;IACnE,MAAM,cAAc,GAAuC,EAAE,CAAC;IAE9D,KAAK,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,IAAI,EAAE,CAAC,EAAE,CAAC;QACjF,MAAM,YAAY,GAAG,mBAAmB,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;QAC9E,IAAI,CAAC,YAAY;YAAE,SAAS;QAC5B,mBAAmB,CAAC,WAAW,CAAC,GAAG,YAAY,CAAC;QAChD,wBAAwB,CAAC,WAAW,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;QACjG,cAAc,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC;IAC5C,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAEpE,OAAO;QACL,WAAW,EAAE,eAAe;QAC5B,UAAU,EAAE,cAAc;QAC1B,mBAAmB;QACnB,wBAAwB;QACxB,cAAc;KACf,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"ai-units-config.js","sourceRoot":"","sources":["../../src/spend/ai-units-config.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,mBAAmB,EAAqB,MAAM,iBAAiB,CAAC;AACzE,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,uBAAuB,EACvB,gCAAgC,GACjC,MAAM,eAAe,CAAC;AAgCvB,MAAM,UAAU,kBAAkB,CAChC,MAA4C,EAC5C,GAAsB;AACtB,oFAAoF;AACpF,eAAwB;IAExB,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,GAAG,uBAAuB,CAAC,GAAG,CAAC,CAAC;IACzE,MAAM,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,GAAG,gCAAgC,CAAC,GAAG,CAAC,CAAC;IAE5F,MAAM,mBAAmB,GAAiC,EAAE,CAAC;IAC7D,MAAM,wBAAwB,GAAkC,EAAE,CAAC;IACnE,MAAM,0BAA0B,GAAkC,EAAE,CAAC;IACrE,MAAM,cAAc,GAAuC,EAAE,CAAC;IAE9D,KAAK,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,IAAI,EAAE,CAAC,EAAE,CAAC;QACjF,MAAM,YAAY,GAAG,mBAAmB,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;QAC9E,IAAI,CAAC,YAAY;YAAE,SAAS;QAC5B,mBAAmB,CAAC,WAAW,CAAC,GAAG,YAAY,CAAC;QAChD,wBAAwB,CAAC,WAAW,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;QACjG,0BAA0B,CAAC,WAAW,CAAC,GAAG,oBAAoB,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;QACzG,cAAc,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC;IAC5C,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAEpE,OAAO;QACL,WAAW,EAAE,eAAe;QAC5B,UAAU,EAAE,cAAc;QAC1B,oBAAoB,EAAE,oBAAoB;QAC1C,mBAAmB,EAAE,mBAAmB;QACxC,mBAAmB;QACnB,wBAAwB;QACxB,0BAA0B;QAC1B,cAAc;KACf,CAAC;AACJ,CAAC"}
@@ -18,6 +18,13 @@
18
18
  * The cap is layered with the existing per-credential USD `spendCaps`
19
19
  * spend-cap gate from PR #345/#346 — both gates run; the more
20
20
  * conservative one wins. This module owns the AI-units side only.
21
+ *
22
+ * Issue #1004: the gate's comparison now runs in **USD** (actual harvested
23
+ * spend vs a USD ceiling), not projected AI units. The peg below survives
24
+ * as the calibration constant deriving that USD ceiling and as the
25
+ * presentation-layer conversion for the legacy unit fields the SPA still
26
+ * reads. For subscription credentials the USD ceiling is a *proxy* budget —
27
+ * it bounds Jinn-attributable model cost, not the provider's plan quota.
21
28
  */
22
29
  /**
23
30
  * Calibration peg: the USD cost of one GPT-5.4-mini Codex-Plus 6h block.
@@ -26,6 +33,11 @@
26
33
  *
27
34
  * Conservative — biases toward over-warning rather than under-warning so
28
35
  * the ceiling pauses early rather than late.
36
+ *
37
+ * Issue #1004: demoted to a calibration constant — it derives
38
+ * {@link REFERENCE_CEILING_USD_MICROS} and converts the legacy unit fields
39
+ * for presentation, but no longer appears in the gate's comparison. Kept
40
+ * exported because the SPA's `HarnessFootprintPanel.tsx` imports it (#1006).
29
41
  */
30
42
  export declare const GPT_5_4_MINI_USD_PER_BLOCK = 0.5;
31
43
  /**
@@ -40,6 +52,21 @@ export declare const REFERENCE_CEILING: {
40
52
  readonly units_per_block: number;
41
53
  readonly units_per_week: number;
42
54
  };
55
+ /**
56
+ * USD-micros ceiling (issue #1004). The gate now compares **actual USD
57
+ * spend** against this ceiling rather than projected AI units. Derived
58
+ * directly from {@link REFERENCE_CEILING} through the GPT-5.4-mini peg:
59
+ * usd_micros = units / 100 * GPT_5_4_MINI_USD_PER_BLOCK * 1_000_000.
60
+ * Default 100 units/block => $0.50/block (500_000 micros); 2800 units/week
61
+ * => $14/week (14_000_000 micros). Calibration is preserved; only the
62
+ * comparison unit changed. For subscription credentials this USD budget is
63
+ * a *proxy* — it bounds Jinn-attributable model cost, not the provider's
64
+ * plan quota directly.
65
+ */
66
+ export declare const REFERENCE_CEILING_USD_MICROS: {
67
+ readonly usd_micros_per_block: number;
68
+ readonly usd_micros_per_week: number;
69
+ };
43
70
  /**
44
71
  * Project the AI-unit cost of one task for a harness/model combination.
45
72
  *
@@ -64,6 +91,20 @@ export declare const REFERENCE_CEILING: {
64
91
  * call-site clarity and future-proofing.
65
92
  */
66
93
  export declare function projectAiUnits(harness: string | undefined, model: string | undefined, _credentialId?: string | null): number | null;
94
+ /**
95
+ * Project the per-task cost of one harness/model combination in USD micros
96
+ * (issue #1004). This is the in-flight debit the gate books for a claim
97
+ * before its actual cost is harvested. Same harness classification as
98
+ * {@link projectAiUnits}:
99
+ * - `0` for harnesses that make no marginal LLM call,
100
+ * - `null` when a paid-LLM harness's model is unknown to the cost table
101
+ * (gate fails open with a warn),
102
+ * - otherwise `round(estimateModelCost(model).usd * 1_000_000)`.
103
+ *
104
+ * `_credentialId` does not change the projection (the model costs the same
105
+ * regardless of auth path); it labels the accounting bucket only.
106
+ */
107
+ export declare function projectTaskUsdMicros(harness: string | undefined, model: string | undefined, _credentialId?: string | null): number | null;
67
108
  /**
68
109
  * Resolve the active ceiling from env. CI / tests override the baked-in
69
110
  * 100 units via `JINN_AI_UNITS_CEILING_OVERRIDE`. Accepts:
@@ -78,6 +119,16 @@ export declare function resolveReferenceCeiling(env: NodeJS.ProcessEnv): {
78
119
  units_per_block: number;
79
120
  units_per_week: number;
80
121
  };
122
+ /**
123
+ * Resolve the active USD-micros ceiling from env (issue #1004). Reuses
124
+ * {@link resolveReferenceCeiling} so the `JINN_AI_UNITS_CEILING_OVERRIDE`
125
+ * parsing (integer or `<block>:<week>`, malformed-warn, default fallback)
126
+ * lives in one place, then converts both bounds through the peg.
127
+ */
128
+ export declare function resolveReferenceCeilingUsdMicros(env: NodeJS.ProcessEnv): {
129
+ usd_micros_per_block: number;
130
+ usd_micros_per_week: number;
131
+ };
81
132
  /** Start of the next 6h UTC-aligned block after `now`. */
82
133
  export declare function blockResetsAtUtc(now: Date): Date;
83
134
  /**
@@ -18,6 +18,13 @@
18
18
  * The cap is layered with the existing per-credential USD `spendCaps`
19
19
  * spend-cap gate from PR #345/#346 — both gates run; the more
20
20
  * conservative one wins. This module owns the AI-units side only.
21
+ *
22
+ * Issue #1004: the gate's comparison now runs in **USD** (actual harvested
23
+ * spend vs a USD ceiling), not projected AI units. The peg below survives
24
+ * as the calibration constant deriving that USD ceiling and as the
25
+ * presentation-layer conversion for the legacy unit fields the SPA still
26
+ * reads. For subscription credentials the USD ceiling is a *proxy* budget —
27
+ * it bounds Jinn-attributable model cost, not the provider's plan quota.
21
28
  */
22
29
  import { estimateModelCost } from '../harnesses/cost-estimates.js';
23
30
  import { CLAUDE_CODE_HARNESS, CODEX_HARNESS, HERMES_AGENT_HARNESS, canonicalHarnessName, } from '../harnesses/names.js';
@@ -28,6 +35,11 @@ import { CLAUDE_CODE_HARNESS, CODEX_HARNESS, HERMES_AGENT_HARNESS, canonicalHarn
28
35
  *
29
36
  * Conservative — biases toward over-warning rather than under-warning so
30
37
  * the ceiling pauses early rather than late.
38
+ *
39
+ * Issue #1004: demoted to a calibration constant — it derives
40
+ * {@link REFERENCE_CEILING_USD_MICROS} and converts the legacy unit fields
41
+ * for presentation, but no longer appears in the gate's comparison. Kept
42
+ * exported because the SPA's `HarnessFootprintPanel.tsx` imports it (#1006).
31
43
  */
32
44
  export const GPT_5_4_MINI_USD_PER_BLOCK = 0.5;
33
45
  /**
@@ -42,6 +54,25 @@ export const REFERENCE_CEILING = {
42
54
  units_per_block: 100,
43
55
  units_per_week: 100 * 28,
44
56
  };
57
+ /**
58
+ * USD-micros ceiling (issue #1004). The gate now compares **actual USD
59
+ * spend** against this ceiling rather than projected AI units. Derived
60
+ * directly from {@link REFERENCE_CEILING} through the GPT-5.4-mini peg:
61
+ * usd_micros = units / 100 * GPT_5_4_MINI_USD_PER_BLOCK * 1_000_000.
62
+ * Default 100 units/block => $0.50/block (500_000 micros); 2800 units/week
63
+ * => $14/week (14_000_000 micros). Calibration is preserved; only the
64
+ * comparison unit changed. For subscription credentials this USD budget is
65
+ * a *proxy* — it bounds Jinn-attributable model cost, not the provider's
66
+ * plan quota directly.
67
+ */
68
+ export const REFERENCE_CEILING_USD_MICROS = {
69
+ usd_micros_per_block: unitsToUsdMicros(REFERENCE_CEILING.units_per_block),
70
+ usd_micros_per_week: unitsToUsdMicros(REFERENCE_CEILING.units_per_week),
71
+ };
72
+ /** Convert an AI-unit count to USD micros through the GPT-5.4-mini peg. */
73
+ function unitsToUsdMicros(units) {
74
+ return Math.round((units / 100) * GPT_5_4_MINI_USD_PER_BLOCK * 1_000_000);
75
+ }
45
76
  /** 6h block in milliseconds — UTC blocks start at 00:00 / 06:00 / 12:00 / 18:00. */
46
77
  const SIX_HOUR_BLOCK_MS = 6 * 60 * 60 * 1_000;
47
78
  /** 7 days in milliseconds — UTC-aligned. */
@@ -85,6 +116,35 @@ export function projectAiUnits(harness, model, _credentialId) {
85
116
  return null;
86
117
  return (cost.usd / GPT_5_4_MINI_USD_PER_BLOCK) * 100;
87
118
  }
119
+ /**
120
+ * Project the per-task cost of one harness/model combination in USD micros
121
+ * (issue #1004). This is the in-flight debit the gate books for a claim
122
+ * before its actual cost is harvested. Same harness classification as
123
+ * {@link projectAiUnits}:
124
+ * - `0` for harnesses that make no marginal LLM call,
125
+ * - `null` when a paid-LLM harness's model is unknown to the cost table
126
+ * (gate fails open with a warn),
127
+ * - otherwise `round(estimateModelCost(model).usd * 1_000_000)`.
128
+ *
129
+ * `_credentialId` does not change the projection (the model costs the same
130
+ * regardless of auth path); it labels the accounting bucket only.
131
+ */
132
+ export function projectTaskUsdMicros(harness, model, _credentialId) {
133
+ if (!harness)
134
+ return null;
135
+ const canonical = canonicalHarnessName(harness);
136
+ const isPaidLlmHarness = canonical === CLAUDE_CODE_HARNESS ||
137
+ canonical === CODEX_HARNESS ||
138
+ canonical === HERMES_AGENT_HARNESS;
139
+ if (!isPaidLlmHarness)
140
+ return 0;
141
+ if (!model)
142
+ return null;
143
+ const cost = estimateModelCost(model);
144
+ if (!cost)
145
+ return null;
146
+ return Math.round(cost.usd * 1_000_000);
147
+ }
88
148
  /**
89
149
  * Resolve the active ceiling from env. CI / tests override the baked-in
90
150
  * 100 units via `JINN_AI_UNITS_CEILING_OVERRIDE`. Accepts:
@@ -118,6 +178,19 @@ export function resolveReferenceCeiling(env) {
118
178
  warnMalformedOverride(raw);
119
179
  return { units_per_block: REFERENCE_CEILING.units_per_block, units_per_week: REFERENCE_CEILING.units_per_week };
120
180
  }
181
+ /**
182
+ * Resolve the active USD-micros ceiling from env (issue #1004). Reuses
183
+ * {@link resolveReferenceCeiling} so the `JINN_AI_UNITS_CEILING_OVERRIDE`
184
+ * parsing (integer or `<block>:<week>`, malformed-warn, default fallback)
185
+ * lives in one place, then converts both bounds through the peg.
186
+ */
187
+ export function resolveReferenceCeilingUsdMicros(env) {
188
+ const units = resolveReferenceCeiling(env);
189
+ return {
190
+ usd_micros_per_block: unitsToUsdMicros(units.units_per_block),
191
+ usd_micros_per_week: unitsToUsdMicros(units.units_per_week),
192
+ };
193
+ }
121
194
  function warnMalformedOverride(raw) {
122
195
  // One-time warn per resolve call — the function is only invoked at
123
196
  // startup, so a module-level memo is unnecessary. Surfaces the operator's
@@ -1 +1 @@
1
- {"version":3,"file":"ai-units.js","sourceRoot":"","sources":["../../src/spend/ai-units.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ai-units.js","sourceRoot":"","sources":["../../src/spend/ai-units.ts"],"names":[],"mappings":"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"}
@@ -22,11 +22,18 @@ export function recordTaskCost(store, args, env = process.env) {
22
22
  model: usage.model,
23
23
  detail: usage.estimated ? 'estimated' : 'observed',
24
24
  });
25
- // Issue #815: fill actual_cost_usd_micros on the per-request claimed
26
- // row + set claim_status='delivered'. ai_units stays as captured at
27
- // claim time (estimates are the gate input, never recomputed).
28
- // Idempotent: no-op when no claimed row exists for this request id.
29
- store.finalizeClaimDelivered(args.requestId, actualCostUsdMicros);
25
+ // Issue #1004: fill actual_cost_usd_micros on the per-request claimed
26
+ // row + set claim_status='delivered'. The gate's accumulator reads this
27
+ // column (COALESCE actual, estimated) so the delivered actual replaces
28
+ // the claim-time estimate in the running total. For subscription
29
+ // credentials the resulting USD figure is a *proxy* budget — it bounds
30
+ // Jinn-attributable model cost, not the provider's plan quota directly.
31
+ // `usage.estimated` is carried through (AC4) so a telemetry-less harness
32
+ // such as Hermes, whose actual cost is itself a heuristic, is not
33
+ // presented as metered by the gate. ai_units stays as captured at claim
34
+ // time (legacy unit surface, #1006). Idempotent: no-op when no claimed
35
+ // row exists for this request id.
36
+ store.finalizeClaimDelivered(args.requestId, actualCostUsdMicros, usage.estimated);
30
37
  }
31
38
  catch (err) {
32
39
  console.warn(`[spend] failed to record task cost for ${args.requestId}: ` +
@@ -1 +1 @@
1
- {"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/spend/record.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAY,EACZ,IAMC,EACD,MAAyB,OAAO,CAAC,GAAG;IAEpC,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC5D,IAAI,CAAC,YAAY;YAAE,OAAO;QAC1B,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7E,MAAM,mBAAmB,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;QAClE,KAAK,CAAC,mBAAmB,CAAC;YACxB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,IAAI,EAAE,WAAW;YACjB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,YAAY;YACZ,aAAa,EAAE,mBAAmB;YAClC,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU;SACnD,CAAC,CAAC;QACH,qEAAqE;QACrE,oEAAoE;QACpE,+DAA+D;QAC/D,oEAAoE;QACpE,KAAK,CAAC,sBAAsB,CAAC,IAAI,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CACV,0CAA0C,IAAI,CAAC,SAAS,IAAI;YAC1D,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACxD,CAAC;IACJ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/spend/record.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAY,EACZ,IAMC,EACD,MAAyB,OAAO,CAAC,GAAG;IAEpC,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC5D,IAAI,CAAC,YAAY;YAAE,OAAO;QAC1B,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7E,MAAM,mBAAmB,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;QAClE,KAAK,CAAC,mBAAmB,CAAC;YACxB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,IAAI,EAAE,WAAW;YACjB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,YAAY;YACZ,aAAa,EAAE,mBAAmB;YAClC,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU;SACnD,CAAC,CAAC;QACH,sEAAsE;QACtE,wEAAwE;QACxE,uEAAuE;QACvE,iEAAiE;QACjE,uEAAuE;QACvE,wEAAwE;QACxE,yEAAyE;QACzE,kEAAkE;QAClE,wEAAwE;QACxE,uEAAuE;QACvE,kCAAkC;QAClC,KAAK,CAAC,sBAAsB,CAAC,IAAI,CAAC,SAAS,EAAE,mBAAmB,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACrF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CACV,0CAA0C,IAAI,CAAC,SAAS,IAAI;YAC1D,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACxD,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -344,6 +344,39 @@ export declare class Store {
344
344
  * Issue #815.
345
345
  */
346
346
  aiUnitsThisWeek(credentialId: string, now?: Date): number;
347
+ /**
348
+ * Actual-spend accumulator for the current 6h UTC block (issue #1004).
349
+ *
350
+ * Sums `COALESCE(actual_cost_usd_micros, estimated_cost_usd_micros, 0)`
351
+ * over rows whose `claim_status` is `'claimed'` or `'delivered'`:
352
+ * - delivered rows contribute the real harvested cost (`actual_*`),
353
+ * - in-flight claimed rows contribute their estimate so a burst of
354
+ * concurrent claims cannot slip the cap before any of them deliver,
355
+ * - failed claims (status `'claim_failed'`) are excluded.
356
+ *
357
+ * `estimated` is true iff the summed figure includes any estimate-backed
358
+ * cost: an in-flight `claimed` row with no `actual_cost_usd_micros` yet,
359
+ * OR a `delivered` row whose actual cost is itself a heuristic
360
+ * (`actual_cost_estimated = 1` — a telemetry-less harness such as Hermes).
361
+ * It is false only when every contributing row is harvested actual
362
+ * telemetry. The gate surfaces this so an estimate-backed figure is not
363
+ * presented as metered. Block boundaries mirror `aiUnitsThisBlock`.
364
+ */
365
+ usdMicrosThisBlock(credentialId: string, now?: Date): {
366
+ usdMicros: number;
367
+ estimated: boolean;
368
+ };
369
+ /**
370
+ * Actual-spend accumulator for the trailing 7-day rolling window from
371
+ * `now` (issue #1004). Same COALESCE + claim_status filter + `estimated`
372
+ * semantics as {@link usdMicrosThisBlock}.
373
+ */
374
+ usdMicrosThisWeek(credentialId: string, now?: Date): {
375
+ usdMicros: number;
376
+ estimated: boolean;
377
+ };
378
+ /** Shared COALESCE-sum + estimate-flag query for the USD accumulators. */
379
+ private sumUsdMicros;
347
380
  /**
348
381
  * True iff an `ai_units_cap_reached` row exists for the given
349
382
  * (credentialId, window, blockId). Used by the daemon to hydrate the
@@ -357,12 +390,22 @@ export declare class Store {
357
390
  hasAiUnitsCapReachedFor(credentialId: string, window: 'block' | 'week', blockId: string): boolean;
358
391
  /**
359
392
  * Mark the per-request `claimed` row as `delivered` and record
360
- * `actual_cost_usd_micros`. The `ai_units` projection captured at
361
- * claim time is intentionally NOT recomputed the issue spec says
362
- * "estimates are the gate input, never recomputed". Idempotent: a
363
- * no-op when no `claimed` row exists.
393
+ * `actual_cost_usd_micros` (issue #1004 the gate's accumulator now
394
+ * reads this column via COALESCE, so a delivered row's real harvested
395
+ * cost replaces its claim-time estimate in the running total). The
396
+ * `ai_units` projection captured at claim time is intentionally NOT
397
+ * recomputed — it remains the per-task estimate for the legacy unit
398
+ * surfaces. For subscription credentials the resulting USD figure is a
399
+ * *proxy* budget, not an exact bound on the provider's plan quota.
400
+ *
401
+ * `actualCostEstimated` (issue #1004, AC4) records whether the actual
402
+ * cost is itself a heuristic — true for a telemetry-less harness such as
403
+ * Hermes whose `harvestHarnessUsage` falls back to an a-priori estimate,
404
+ * false when the figure is harvested telemetry. The gate reads it so a
405
+ * delivered-but-heuristic row reports `estimated: true` rather than being
406
+ * presented as metered. Idempotent: a no-op when no `claimed` row exists.
364
407
  */
365
- finalizeClaimDelivered(requestId: string, actualCostUsdMicros: number): void;
408
+ finalizeClaimDelivered(requestId: string, actualCostUsdMicros: number, actualCostEstimated: boolean): void;
366
409
  /** Newer events first, then ascending id for `jinn logs --follow` (oldest in batch printed first in caller). */
367
410
  getActivityEventsAfterId(afterId: number, limit: number): ActivityEventRow[];
368
411
  /**
@@ -499,6 +542,49 @@ export declare class Store {
499
542
  queryEnvelopeProjections(query?: EnvelopeProjectionQuery): EnvelopeProjection[];
500
543
  saveErc8004Anchor(input: Erc8004AnchorInput): void;
501
544
  listErc8004AnchorsByEnvelopeCids(envelopeCids: readonly string[]): Erc8004AnchorRow[];
545
+ /**
546
+ * Upsert one held-out eval result (issue #818). PK is
547
+ * `(checkpoint_cid, slate_version, instance_id)` so a re-run overwrites.
548
+ * `passed` is null for `unscorable` rows.
549
+ */
550
+ recordEvalResult(args: EvalResultRecord): void;
551
+ /**
552
+ * Aggregate the eval results for a (checkpoint, slate version):
553
+ * `scorable` = rows with `unscorable = 0`; `passed` = scorable rows with
554
+ * `passed = 1`. Unscorable rows are counted separately and never enter the
555
+ * denominator. A checkpoint with no rows yields all-zero (the orchestrator
556
+ * reads this to detect a not-yet-evaluated parent).
557
+ */
558
+ getEvalAggregate(checkpoint_cid: string, slate_version: string): EvalAggregate;
559
+ /**
560
+ * Distinct `slate_hash` values recorded for a (checkpoint, slate version).
561
+ * The eval orchestrator reads this to detect slate-content drift under a
562
+ * stable version label — the held-out exam is only an honest before/after
563
+ * when the parent and child were scored on the SAME slate content (defeating
564
+ * confounder #1, task-selection). Empty when the checkpoint has no rows.
565
+ */
566
+ getEvalSlateHashes(checkpoint_cid: string, slate_version: string): string[];
567
+ /** Per-task eval results for a (checkpoint, slate version), ordered by instance_id. */
568
+ getEvalResults(checkpoint_cid: string, slate_version: string): EvalResultRow[];
502
569
  close(): void;
503
570
  }
571
+ export interface EvalResultRecord {
572
+ checkpoint_cid: string;
573
+ slate_hash: string;
574
+ slate_version: string;
575
+ instance_id: string;
576
+ /** Pass/fail; ignored (stored NULL) when `unscorable` is true. */
577
+ passed: boolean | null;
578
+ unscorable: boolean;
579
+ code_digest: string;
580
+ run_at_ms: number;
581
+ test_log_excerpt?: string | null;
582
+ }
583
+ /** A persisted eval result read back from the store (same shape as the record written). */
584
+ export type EvalResultRow = EvalResultRecord;
585
+ export interface EvalAggregate {
586
+ passed: number;
587
+ scorable: number;
588
+ unscorable: number;
589
+ }
504
590
  export {};
@@ -288,6 +288,25 @@ CREATE TABLE IF NOT EXISTS capture_spans (
288
288
  );
289
289
  CREATE INDEX IF NOT EXISTS capture_spans_session ON capture_spans (session_id);
290
290
 
291
+ -- Held-out checkpoint eval results (issue #818). One row per
292
+ -- (checkpoint, slate version, instance). Scores are only comparable WITHIN a
293
+ -- slate version; slate_hash is stored so a version-bump / hash-drift is
294
+ -- detectable. passed is nullable: an unscorable row (Docker down, disk
295
+ -- floor) carries passed = NULL and is EXCLUDED from the denominator -- never
296
+ -- coerced to a fail.
297
+ CREATE TABLE IF NOT EXISTS eval_results (
298
+ checkpoint_cid TEXT NOT NULL,
299
+ slate_hash TEXT NOT NULL,
300
+ slate_version TEXT NOT NULL,
301
+ instance_id TEXT NOT NULL,
302
+ passed INTEGER,
303
+ unscorable INTEGER NOT NULL DEFAULT 0,
304
+ code_digest TEXT NOT NULL,
305
+ run_at_ms INTEGER NOT NULL,
306
+ test_log_excerpt TEXT,
307
+ PRIMARY KEY (checkpoint_cid, slate_version, instance_id)
308
+ );
309
+
291
310
  `;
292
311
  export class Store {
293
312
  /** Exposed for engine persistence layer — treat as package-internal. */
@@ -400,6 +419,12 @@ export class Store {
400
419
  addActivityColumn('claim_status', 'claim_status TEXT');
401
420
  addActivityColumn('estimated_cost_usd_micros', 'estimated_cost_usd_micros INTEGER');
402
421
  addActivityColumn('actual_cost_usd_micros', 'actual_cost_usd_micros INTEGER');
422
+ // Issue #1004 (AC4): whether actual_cost_usd_micros is estimate-backed
423
+ // (1) or harvested telemetry (0/null). A telemetry-less harness such as
424
+ // Hermes still writes a NON-null actual cost via finalizeClaimDelivered,
425
+ // so the column distinguishes a heuristic figure from a metered one. The
426
+ // gate's estimated flag reads this so a heuristic is not shown as metered.
427
+ addActivityColumn('actual_cost_estimated', 'actual_cost_estimated INTEGER');
403
428
  this.db.exec(`CREATE INDEX IF NOT EXISTS idx_activity_events_credential ON activity_events (credential_id, ts)`);
404
429
  // Per-request lookup for the completion-time update path that fills
405
430
  // actualCostUsdMicros / sets claim_status='delivered'.
@@ -913,6 +938,56 @@ export class Store {
913
938
  AND claim_status IN ('claimed', 'delivered')`).get({ cid: credentialId, weekStart });
914
939
  return row.total ?? 0;
915
940
  }
941
+ /**
942
+ * Actual-spend accumulator for the current 6h UTC block (issue #1004).
943
+ *
944
+ * Sums `COALESCE(actual_cost_usd_micros, estimated_cost_usd_micros, 0)`
945
+ * over rows whose `claim_status` is `'claimed'` or `'delivered'`:
946
+ * - delivered rows contribute the real harvested cost (`actual_*`),
947
+ * - in-flight claimed rows contribute their estimate so a burst of
948
+ * concurrent claims cannot slip the cap before any of them deliver,
949
+ * - failed claims (status `'claim_failed'`) are excluded.
950
+ *
951
+ * `estimated` is true iff the summed figure includes any estimate-backed
952
+ * cost: an in-flight `claimed` row with no `actual_cost_usd_micros` yet,
953
+ * OR a `delivered` row whose actual cost is itself a heuristic
954
+ * (`actual_cost_estimated = 1` — a telemetry-less harness such as Hermes).
955
+ * It is false only when every contributing row is harvested actual
956
+ * telemetry. The gate surfaces this so an estimate-backed figure is not
957
+ * presented as metered. Block boundaries mirror `aiUnitsThisBlock`.
958
+ */
959
+ usdMicrosThisBlock(credentialId, now = new Date()) {
960
+ const startOfDay = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
961
+ const sinceDayStart = now.getTime() - startOfDay;
962
+ const sixHoursMs = 6 * 60 * 60 * 1_000;
963
+ const blocksIn = Math.min(Math.floor(sinceDayStart / sixHoursMs), 3);
964
+ const blockStart = new Date(startOfDay + blocksIn * sixHoursMs).toISOString();
965
+ const blockEnd = new Date(startOfDay + (blocksIn + 1) * sixHoursMs).toISOString();
966
+ return this.sumUsdMicros(credentialId, blockStart, blockEnd);
967
+ }
968
+ /**
969
+ * Actual-spend accumulator for the trailing 7-day rolling window from
970
+ * `now` (issue #1004). Same COALESCE + claim_status filter + `estimated`
971
+ * semantics as {@link usdMicrosThisBlock}.
972
+ */
973
+ usdMicrosThisWeek(credentialId, now = new Date()) {
974
+ const weekStart = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1_000).toISOString();
975
+ return this.sumUsdMicros(credentialId, weekStart, undefined);
976
+ }
977
+ /** Shared COALESCE-sum + estimate-flag query for the USD accumulators. */
978
+ sumUsdMicros(credentialId, fromIso, toIso) {
979
+ const upper = toIso ? 'AND ts < @to' : '';
980
+ const row = this.db
981
+ .prepare(`SELECT
982
+ COALESCE(SUM(COALESCE(actual_cost_usd_micros, estimated_cost_usd_micros, 0)), 0) AS total,
983
+ COALESCE(SUM(CASE WHEN actual_cost_usd_micros IS NULL OR actual_cost_estimated = 1 THEN 1 ELSE 0 END), 0) AS estimatedRows
984
+ FROM activity_events
985
+ WHERE credential_id = @cid
986
+ AND ts IS NOT NULL AND ts >= @from ${upper}
987
+ AND claim_status IN ('claimed', 'delivered')`)
988
+ .get({ cid: credentialId, from: fromIso, to: toIso });
989
+ return { usdMicros: row.total ?? 0, estimated: (row.estimatedRows ?? 0) > 0 };
990
+ }
916
991
  /**
917
992
  * True iff an `ai_units_cap_reached` row exists for the given
918
993
  * (credentialId, window, blockId). Used by the daemon to hydrate the
@@ -935,16 +1010,31 @@ export class Store {
935
1010
  }
936
1011
  /**
937
1012
  * Mark the per-request `claimed` row as `delivered` and record
938
- * `actual_cost_usd_micros`. The `ai_units` projection captured at
939
- * claim time is intentionally NOT recomputed the issue spec says
940
- * "estimates are the gate input, never recomputed". Idempotent: a
941
- * no-op when no `claimed` row exists.
1013
+ * `actual_cost_usd_micros` (issue #1004 the gate's accumulator now
1014
+ * reads this column via COALESCE, so a delivered row's real harvested
1015
+ * cost replaces its claim-time estimate in the running total). The
1016
+ * `ai_units` projection captured at claim time is intentionally NOT
1017
+ * recomputed — it remains the per-task estimate for the legacy unit
1018
+ * surfaces. For subscription credentials the resulting USD figure is a
1019
+ * *proxy* budget, not an exact bound on the provider's plan quota.
1020
+ *
1021
+ * `actualCostEstimated` (issue #1004, AC4) records whether the actual
1022
+ * cost is itself a heuristic — true for a telemetry-less harness such as
1023
+ * Hermes whose `harvestHarnessUsage` falls back to an a-priori estimate,
1024
+ * false when the figure is harvested telemetry. The gate reads it so a
1025
+ * delivered-but-heuristic row reports `estimated: true` rather than being
1026
+ * presented as metered. Idempotent: a no-op when no `claimed` row exists.
942
1027
  */
943
- finalizeClaimDelivered(requestId, actualCostUsdMicros) {
1028
+ finalizeClaimDelivered(requestId, actualCostUsdMicros, actualCostEstimated) {
944
1029
  this.db.prepare(`UPDATE activity_events
945
1030
  SET claim_status = 'delivered',
946
- actual_cost_usd_micros = @actual
947
- WHERE request_id = @req AND claim_status = 'claimed'`).run({ req: requestId, actual: actualCostUsdMicros });
1031
+ actual_cost_usd_micros = @actual,
1032
+ actual_cost_estimated = @estimated
1033
+ WHERE request_id = @req AND claim_status = 'claimed'`).run({
1034
+ req: requestId,
1035
+ actual: actualCostUsdMicros,
1036
+ estimated: actualCostEstimated ? 1 : 0,
1037
+ });
948
1038
  }
949
1039
  /** Newer events first, then ascending id for `jinn logs --follow` (oldest in batch printed first in caller). */
950
1040
  getActivityEventsAfterId(afterId, limit) {
@@ -1819,6 +1909,79 @@ export class Store {
1819
1909
  anchoredAt: r.anchored_at,
1820
1910
  }));
1821
1911
  }
1912
+ /**
1913
+ * Upsert one held-out eval result (issue #818). PK is
1914
+ * `(checkpoint_cid, slate_version, instance_id)` so a re-run overwrites.
1915
+ * `passed` is null for `unscorable` rows.
1916
+ */
1917
+ recordEvalResult(args) {
1918
+ this.db
1919
+ .prepare(`INSERT INTO eval_results
1920
+ (checkpoint_cid, slate_hash, slate_version, instance_id, passed, unscorable, code_digest, run_at_ms, test_log_excerpt)
1921
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1922
+ ON CONFLICT(checkpoint_cid, slate_version, instance_id) DO UPDATE SET
1923
+ slate_hash = excluded.slate_hash,
1924
+ passed = excluded.passed,
1925
+ unscorable = excluded.unscorable,
1926
+ code_digest = excluded.code_digest,
1927
+ run_at_ms = excluded.run_at_ms,
1928
+ test_log_excerpt = excluded.test_log_excerpt`)
1929
+ .run(args.checkpoint_cid, args.slate_hash, args.slate_version, args.instance_id, args.unscorable ? null : args.passed ? 1 : 0, args.unscorable ? 1 : 0, args.code_digest, args.run_at_ms, args.test_log_excerpt ?? null);
1930
+ }
1931
+ /**
1932
+ * Aggregate the eval results for a (checkpoint, slate version):
1933
+ * `scorable` = rows with `unscorable = 0`; `passed` = scorable rows with
1934
+ * `passed = 1`. Unscorable rows are counted separately and never enter the
1935
+ * denominator. A checkpoint with no rows yields all-zero (the orchestrator
1936
+ * reads this to detect a not-yet-evaluated parent).
1937
+ */
1938
+ getEvalAggregate(checkpoint_cid, slate_version) {
1939
+ const row = this.db
1940
+ .prepare(`SELECT
1941
+ COALESCE(SUM(CASE WHEN unscorable = 0 AND passed = 1 THEN 1 ELSE 0 END), 0) AS passed,
1942
+ COALESCE(SUM(CASE WHEN unscorable = 0 THEN 1 ELSE 0 END), 0) AS scorable,
1943
+ COALESCE(SUM(CASE WHEN unscorable = 1 THEN 1 ELSE 0 END), 0) AS unscorable
1944
+ FROM eval_results
1945
+ WHERE checkpoint_cid = ? AND slate_version = ?`)
1946
+ .get(checkpoint_cid, slate_version);
1947
+ return { passed: row.passed, scorable: row.scorable, unscorable: row.unscorable };
1948
+ }
1949
+ /**
1950
+ * Distinct `slate_hash` values recorded for a (checkpoint, slate version).
1951
+ * The eval orchestrator reads this to detect slate-content drift under a
1952
+ * stable version label — the held-out exam is only an honest before/after
1953
+ * when the parent and child were scored on the SAME slate content (defeating
1954
+ * confounder #1, task-selection). Empty when the checkpoint has no rows.
1955
+ */
1956
+ getEvalSlateHashes(checkpoint_cid, slate_version) {
1957
+ const rows = this.db
1958
+ .prepare(`SELECT DISTINCT slate_hash
1959
+ FROM eval_results
1960
+ WHERE checkpoint_cid = ? AND slate_version = ?
1961
+ ORDER BY slate_hash`)
1962
+ .all(checkpoint_cid, slate_version);
1963
+ return rows.map((r) => r.slate_hash);
1964
+ }
1965
+ /** Per-task eval results for a (checkpoint, slate version), ordered by instance_id. */
1966
+ getEvalResults(checkpoint_cid, slate_version) {
1967
+ const rows = this.db
1968
+ .prepare(`SELECT checkpoint_cid, slate_hash, slate_version, instance_id, passed, unscorable, code_digest, run_at_ms, test_log_excerpt
1969
+ FROM eval_results
1970
+ WHERE checkpoint_cid = ? AND slate_version = ?
1971
+ ORDER BY instance_id`)
1972
+ .all(checkpoint_cid, slate_version);
1973
+ return rows.map((r) => ({
1974
+ checkpoint_cid: r.checkpoint_cid,
1975
+ slate_hash: r.slate_hash,
1976
+ slate_version: r.slate_version,
1977
+ instance_id: r.instance_id,
1978
+ passed: r.passed === null ? null : r.passed === 1,
1979
+ unscorable: r.unscorable === 1,
1980
+ code_digest: r.code_digest,
1981
+ run_at_ms: r.run_at_ms,
1982
+ test_log_excerpt: r.test_log_excerpt,
1983
+ }));
1984
+ }
1822
1985
  close() {
1823
1986
  this.db.close();
1824
1987
  }