@pome-sh/cli 0.1.1 → 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 (277) hide show
  1. package/README.md +5 -6
  2. package/dist/build-info.json +3 -3
  3. package/dist/src/cli/eval.js +26 -8
  4. package/dist/src/cli/init-sdk.d.ts +0 -3
  5. package/dist/src/cli/init-sdk.js +117 -24
  6. package/dist/src/cli/main.js +23 -69
  7. package/dist/src/cli/register.d.ts +9 -0
  8. package/dist/src/cli/register.js +51 -5
  9. package/dist/src/cli/session.d.ts +7 -1
  10. package/dist/src/cli/session.js +87 -27
  11. package/dist/src/demo/runDemo.js +3 -2
  12. package/dist/src/hosted/client.d.ts +41 -5
  13. package/dist/src/hosted/client.js +333 -19
  14. package/dist/src/hosted/errors.d.ts +11 -1
  15. package/dist/src/hosted/errors.js +8 -1
  16. package/dist/src/hosted/evalResultView.d.ts +2 -0
  17. package/dist/src/hosted/evalResultView.js +22 -0
  18. package/dist/src/hosted/uploadAndFinalize.d.ts +18 -1
  19. package/dist/src/hosted/uploadAndFinalize.js +51 -3
  20. package/dist/src/recorder/inspect.js +20 -2
  21. package/dist/src/runner/mergeAdapterSignals.js +2 -2
  22. package/dist/src/runner/ports.js +3 -0
  23. package/dist/src/runner/runScenario.d.ts +79 -2
  24. package/dist/src/runner/runScenario.js +94 -36
  25. package/dist/src/runner/runScenarioHosted.d.ts +37 -0
  26. package/dist/src/runner/runScenarioHosted.js +257 -58
  27. package/dist/src/runner/runTrialGroup.d.ts +1 -3
  28. package/dist/src/runner/runTrialGroup.js +1 -3
  29. package/dist/src/scenario/parseScenario.d.ts +5 -0
  30. package/dist/src/scenario/parseScenario.js +135 -8
  31. package/dist/src/scenario/scenarioSchema.d.ts +200 -3
  32. package/dist/src/scenario/scenarioSchema.js +12 -1
  33. package/dist/src/twin/twinHarness.d.ts +10 -0
  34. package/dist/src/twin/twinHarness.js +6 -2
  35. package/dist/src/twin/twinStart.d.ts +19 -0
  36. package/dist/src/twin/twinStart.js +156 -0
  37. package/node_modules/@pome-sh/sdk/dist/fidelity-inventory.d.ts +121 -0
  38. package/node_modules/@pome-sh/sdk/dist/fidelity-inventory.js +204 -0
  39. package/node_modules/@pome-sh/sdk/dist/fidelity-inventory.js.map +1 -0
  40. package/node_modules/@pome-sh/sdk/dist/parity.d.ts +55 -0
  41. package/node_modules/@pome-sh/sdk/dist/parity.js +112 -0
  42. package/node_modules/@pome-sh/sdk/dist/parity.js.map +1 -0
  43. package/node_modules/@pome-sh/sdk/dist/server-helpers.d.ts +15 -0
  44. package/node_modules/@pome-sh/sdk/dist/server-helpers.js +96 -0
  45. package/node_modules/@pome-sh/sdk/dist/server-helpers.js.map +1 -1
  46. package/node_modules/@pome-sh/sdk/dist/server.d.ts +1 -1
  47. package/node_modules/@pome-sh/sdk/dist/server.js +9 -7
  48. package/node_modules/@pome-sh/sdk/dist/server.js.map +1 -1
  49. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/errors.d.ts +39 -0
  50. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/errors.js +33 -0
  51. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/errors.js.map +1 -0
  52. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/evaluator-hooks.d.ts +13 -0
  53. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/evaluator-hooks.js +6 -0
  54. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/evaluator-hooks.js.map +1 -0
  55. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/finalize-shapes.d.ts +215 -0
  56. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/finalize-shapes.js +83 -0
  57. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/finalize-shapes.js.map +1 -0
  58. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/github-access-control.d.ts +175 -0
  59. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/github-access-control.js +182 -0
  60. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/github-access-control.js.map +1 -0
  61. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/identity.d.ts +90 -0
  62. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/identity.js +72 -0
  63. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/identity.js.map +1 -0
  64. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/index.d.ts +27 -0
  65. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/index.js +34 -0
  66. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/index.js.map +1 -0
  67. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/event-schema.d.ts +286 -0
  68. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/event-schema.js +33 -0
  69. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/event-schema.js.map +1 -0
  70. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/fixtures/data.d.ts +65 -0
  71. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/fixtures/data.js +455 -0
  72. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/fixtures/data.js.map +1 -0
  73. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/fixtures/index.d.ts +39 -0
  74. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/fixtures/index.js +47 -0
  75. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/fixtures/index.js.map +1 -0
  76. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/index.d.ts +25 -0
  77. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/index.js +27 -0
  78. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/index.js.map +1 -0
  79. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/legacy-shim.d.ts +100 -0
  80. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/legacy-shim.js +185 -0
  81. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/legacy-shim.js.map +1 -0
  82. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/map-span.d.ts +64 -0
  83. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/map-span.js +112 -0
  84. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/map-span.js.map +1 -0
  85. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/nano.d.ts +26 -0
  86. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/nano.js +61 -0
  87. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/nano.js.map +1 -0
  88. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/project.d.ts +42 -0
  89. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/project.js +96 -0
  90. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/project.js.map +1 -0
  91. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/semconv.d.ts +51 -0
  92. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/semconv.js +74 -0
  93. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/semconv.js.map +1 -0
  94. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/span-event.d.ts +134 -0
  95. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/span-event.js +152 -0
  96. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/span-event.js.map +1 -0
  97. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/recorder-events.d.ts +418 -0
  98. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/recorder-events.js +234 -0
  99. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/recorder-events.js.map +1 -0
  100. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/redaction.d.ts +2 -0
  101. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/redaction.js +152 -0
  102. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/redaction.js.map +1 -0
  103. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/rest.d.ts +198 -0
  104. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/rest.js +225 -0
  105. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/rest.js.map +1 -0
  106. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/run.d.ts +201 -0
  107. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/run.js +166 -0
  108. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/run.js.map +1 -0
  109. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/seed-state.d.ts +446 -0
  110. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/seed-state.js +167 -0
  111. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/seed-state.js.map +1 -0
  112. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/sessions.d.ts +53 -0
  113. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/sessions.js +51 -0
  114. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/sessions.js.map +1 -0
  115. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/task-vocab.d.ts +66 -0
  116. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/task-vocab.js +84 -0
  117. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/task-vocab.js.map +1 -0
  118. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/task.d.ts +483 -0
  119. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/task.js +60 -0
  120. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/task.js.map +1 -0
  121. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/package.json +54 -0
  122. package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/trace-contract.json +41 -0
  123. package/node_modules/@pome-sh/sdk/package.json +6 -2
  124. package/node_modules/@pome-sh/shared-types/dist/errors.d.ts +39 -0
  125. package/node_modules/@pome-sh/shared-types/dist/errors.js +33 -0
  126. package/node_modules/@pome-sh/shared-types/dist/errors.js.map +1 -0
  127. package/node_modules/@pome-sh/shared-types/dist/finalize-shapes.d.ts +252 -0
  128. package/node_modules/@pome-sh/shared-types/dist/finalize-shapes.js +122 -0
  129. package/node_modules/@pome-sh/shared-types/dist/finalize-shapes.js.map +1 -0
  130. package/node_modules/@pome-sh/shared-types/dist/identity.d.ts +90 -0
  131. package/node_modules/@pome-sh/shared-types/dist/identity.js +72 -0
  132. package/node_modules/@pome-sh/shared-types/dist/identity.js.map +1 -0
  133. package/node_modules/@pome-sh/shared-types/dist/index.d.ts +13 -1353
  134. package/node_modules/@pome-sh/shared-types/dist/index.js +14 -568
  135. package/node_modules/@pome-sh/shared-types/dist/index.js.map +1 -1
  136. package/node_modules/@pome-sh/shared-types/dist/otel/event-schema.d.ts +45 -0
  137. package/node_modules/@pome-sh/shared-types/dist/otel/legacy-shim.js +8 -0
  138. package/node_modules/@pome-sh/shared-types/dist/otel/legacy-shim.js.map +1 -1
  139. package/node_modules/@pome-sh/shared-types/dist/otel/semconv.js +7 -0
  140. package/node_modules/@pome-sh/shared-types/dist/otel/semconv.js.map +1 -1
  141. package/node_modules/@pome-sh/shared-types/dist/recorder-events.d.ts +62 -0
  142. package/node_modules/@pome-sh/shared-types/dist/recorder-events.js +36 -0
  143. package/node_modules/@pome-sh/shared-types/dist/recorder-events.js.map +1 -1
  144. package/node_modules/@pome-sh/shared-types/dist/rest.d.ts +245 -0
  145. package/node_modules/@pome-sh/shared-types/dist/rest.js +303 -0
  146. package/node_modules/@pome-sh/shared-types/dist/rest.js.map +1 -0
  147. package/node_modules/@pome-sh/shared-types/dist/run.d.ts +7 -0
  148. package/node_modules/@pome-sh/shared-types/dist/run.js +4 -0
  149. package/node_modules/@pome-sh/shared-types/dist/run.js.map +1 -1
  150. package/node_modules/@pome-sh/shared-types/dist/seed-envelope.d.ts +14 -0
  151. package/node_modules/@pome-sh/shared-types/dist/seed-envelope.js +46 -0
  152. package/node_modules/@pome-sh/shared-types/dist/seed-envelope.js.map +1 -0
  153. package/node_modules/@pome-sh/shared-types/dist/seed-state.d.ts +446 -0
  154. package/node_modules/@pome-sh/shared-types/dist/seed-state.js +167 -0
  155. package/node_modules/@pome-sh/shared-types/dist/seed-state.js.map +1 -0
  156. package/node_modules/@pome-sh/shared-types/dist/sessions.d.ts +53 -0
  157. package/node_modules/@pome-sh/shared-types/dist/sessions.js +51 -0
  158. package/node_modules/@pome-sh/shared-types/dist/sessions.js.map +1 -0
  159. package/node_modules/@pome-sh/shared-types/dist/task.d.ts +485 -0
  160. package/node_modules/@pome-sh/shared-types/dist/task.js +60 -0
  161. package/node_modules/@pome-sh/shared-types/dist/task.js.map +1 -0
  162. package/node_modules/@pome-sh/shared-types/package.json +2 -1
  163. package/node_modules/@pome-sh/shared-types/trace-contract.json +1 -1
  164. package/node_modules/@pome-sh/twin-github/FIDELITY.md +127 -67
  165. package/node_modules/@pome-sh/twin-github/README.md +3 -7
  166. package/node_modules/@pome-sh/twin-github/dist/examples/claude-github-agent.js +4 -20
  167. package/node_modules/@pome-sh/twin-github/dist/examples/claude-github-agent.js.map +1 -1
  168. package/node_modules/@pome-sh/twin-github/dist/src/access-control.d.ts +3 -3
  169. package/node_modules/@pome-sh/twin-github/dist/src/domain.d.ts +141 -3
  170. package/node_modules/@pome-sh/twin-github/dist/src/domain.js +0 -0
  171. package/node_modules/@pome-sh/twin-github/dist/src/domain.js.map +1 -1
  172. package/node_modules/@pome-sh/twin-github/dist/src/routes.js +2 -0
  173. package/node_modules/@pome-sh/twin-github/dist/src/routes.js.map +1 -1
  174. package/node_modules/@pome-sh/twin-github/dist/src/seed.d.ts +1 -1
  175. package/node_modules/@pome-sh/twin-github/dist/src/serializers.d.ts +2 -2
  176. package/node_modules/@pome-sh/twin-github/dist/src/server.js +7 -6
  177. package/node_modules/@pome-sh/twin-github/dist/src/server.js.map +1 -1
  178. package/node_modules/@pome-sh/twin-github/dist/src/tools.d.ts +168 -18
  179. package/node_modules/@pome-sh/twin-github/dist/src/tools.js +23 -0
  180. package/node_modules/@pome-sh/twin-github/dist/src/tools.js.map +1 -1
  181. package/node_modules/@pome-sh/twin-github/fidelity.inventory.json +773 -0
  182. package/node_modules/@pome-sh/twin-github/package.json +5 -4
  183. package/node_modules/@pome-sh/twin-slack/FIDELITY.md +121 -35
  184. package/node_modules/@pome-sh/twin-slack/README.md +2 -6
  185. package/node_modules/@pome-sh/twin-slack/dist/scripts/fidelity-parity.d.ts +1 -0
  186. package/node_modules/@pome-sh/twin-slack/dist/scripts/fidelity-parity.js +92 -0
  187. package/node_modules/@pome-sh/twin-slack/dist/scripts/fidelity-parity.js.map +1 -0
  188. package/node_modules/@pome-sh/twin-slack/dist/scripts/smoke.js +3 -3
  189. package/node_modules/@pome-sh/twin-slack/dist/scripts/smoke.js.map +1 -1
  190. package/node_modules/@pome-sh/twin-slack/dist/scripts/validate-mcp.js +1 -1
  191. package/node_modules/@pome-sh/twin-slack/dist/scripts/validate-mcp.js.map +1 -1
  192. package/node_modules/@pome-sh/twin-slack/dist/src/server.js +7 -6
  193. package/node_modules/@pome-sh/twin-slack/dist/src/server.js.map +1 -1
  194. package/node_modules/@pome-sh/twin-slack/dist/src/tools.d.ts +30 -4
  195. package/node_modules/@pome-sh/twin-slack/dist/src/tools.js +48 -1
  196. package/node_modules/@pome-sh/twin-slack/dist/src/tools.js.map +1 -1
  197. package/node_modules/@pome-sh/twin-slack/fidelity.inventory.json +377 -0
  198. package/node_modules/@pome-sh/twin-slack/package.json +5 -4
  199. package/node_modules/@pome-sh/twin-stripe/FIDELITY.md +151 -50
  200. package/node_modules/@pome-sh/twin-stripe/README.md +42 -45
  201. package/node_modules/@pome-sh/twin-stripe/dist/scripts/fidelity-parity.d.ts +1 -0
  202. package/node_modules/@pome-sh/twin-stripe/dist/scripts/fidelity-parity.js +186 -0
  203. package/node_modules/@pome-sh/twin-stripe/dist/scripts/fidelity-parity.js.map +1 -0
  204. package/node_modules/@pome-sh/twin-stripe/dist/scripts/smoke.js +8 -4
  205. package/node_modules/@pome-sh/twin-stripe/dist/scripts/smoke.js.map +1 -1
  206. package/node_modules/@pome-sh/twin-stripe/dist/src/db.js +26 -2
  207. package/node_modules/@pome-sh/twin-stripe/dist/src/db.js.map +1 -1
  208. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/billing.d.ts +101 -0
  209. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/billing.js +195 -0
  210. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/billing.js.map +1 -0
  211. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/charges.d.ts +15 -1
  212. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/charges.js +17 -5
  213. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/charges.js.map +1 -1
  214. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/customers.d.ts +47 -0
  215. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/customers.js +103 -0
  216. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/customers.js.map +1 -0
  217. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/events.d.ts +1 -1
  218. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/events.js.map +1 -1
  219. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/index.d.ts +6 -0
  220. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/index.js +3 -0
  221. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/index.js.map +1 -1
  222. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/payment-intents.d.ts +61 -2
  223. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/payment-intents.js +214 -52
  224. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/payment-intents.js.map +1 -1
  225. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/payment-methods.d.ts +45 -0
  226. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/payment-methods.js +159 -0
  227. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/payment-methods.js.map +1 -0
  228. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/refunds.d.ts +6 -0
  229. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/refunds.js +16 -0
  230. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/refunds.js.map +1 -1
  231. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/schema.d.ts +1 -0
  232. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/schema.js +144 -1
  233. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/schema.js.map +1 -1
  234. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/stripe-domain.d.ts +420 -43
  235. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/stripe-domain.js +442 -13
  236. package/node_modules/@pome-sh/twin-stripe/dist/src/domain/stripe-domain.js.map +1 -1
  237. package/node_modules/@pome-sh/twin-stripe/dist/src/errors.d.ts +19 -0
  238. package/node_modules/@pome-sh/twin-stripe/dist/src/errors.js +15 -1
  239. package/node_modules/@pome-sh/twin-stripe/dist/src/errors.js.map +1 -1
  240. package/node_modules/@pome-sh/twin-stripe/dist/src/ids.d.ts +2 -0
  241. package/node_modules/@pome-sh/twin-stripe/dist/src/ids.js +2 -0
  242. package/node_modules/@pome-sh/twin-stripe/dist/src/ids.js.map +1 -1
  243. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/_helpers.d.ts +8 -0
  244. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/_helpers.js +105 -1
  245. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/_helpers.js.map +1 -1
  246. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/billing.d.ts +4 -0
  247. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/billing.js +117 -0
  248. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/billing.js.map +1 -0
  249. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/customers.d.ts +4 -0
  250. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/customers.js +47 -0
  251. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/customers.js.map +1 -0
  252. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/index.js +6 -0
  253. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/index.js.map +1 -1
  254. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/payment-intents.js +28 -102
  255. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/payment-intents.js.map +1 -1
  256. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/payment-methods.d.ts +4 -0
  257. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/payment-methods.js +33 -0
  258. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/payment-methods.js.map +1 -0
  259. package/node_modules/@pome-sh/twin-stripe/dist/src/seed.d.ts +10 -8
  260. package/node_modules/@pome-sh/twin-stripe/dist/src/seed.js +3 -2
  261. package/node_modules/@pome-sh/twin-stripe/dist/src/seed.js.map +1 -1
  262. package/node_modules/@pome-sh/twin-stripe/dist/src/serializers.d.ts +181 -21
  263. package/node_modules/@pome-sh/twin-stripe/dist/src/serializers.js +257 -34
  264. package/node_modules/@pome-sh/twin-stripe/dist/src/serializers.js.map +1 -1
  265. package/node_modules/@pome-sh/twin-stripe/dist/src/server.js +7 -6
  266. package/node_modules/@pome-sh/twin-stripe/dist/src/server.js.map +1 -1
  267. package/node_modules/@pome-sh/twin-stripe/dist/src/tools.d.ts +107 -5
  268. package/node_modules/@pome-sh/twin-stripe/dist/src/tools.js +139 -6
  269. package/node_modules/@pome-sh/twin-stripe/dist/src/tools.js.map +1 -1
  270. package/node_modules/@pome-sh/twin-stripe/dist/src/types.d.ts +73 -0
  271. package/node_modules/@pome-sh/twin-stripe/dist/src/upstream-types.d.ts +8 -0
  272. package/node_modules/@pome-sh/twin-stripe/dist/src/x402.js +6 -1
  273. package/node_modules/@pome-sh/twin-stripe/dist/src/x402.js.map +1 -1
  274. package/node_modules/@pome-sh/twin-stripe/fidelity.inventory.json +425 -0
  275. package/node_modules/@pome-sh/twin-stripe/package.json +5 -4
  276. package/package.json +8 -8
  277. package/skills/pome-setup/SKILL.md +1 -1
@@ -1,8 +1,108 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  import { createEvalSessionResponseSchema, createSessionResponseSchema, finalizeResponseSchema, sessionPublicSchema, submitResultResponseSchema, } from "../types/shared.js";
3
+ import { z } from "zod";
3
4
  import { HostedAuthError, HostedOrchError, HostedQuotaError, } from "./errors.js";
5
+ // Multi-twin (M3): provenance marker for `per_twin`. A single-twin OLD cloud
6
+ // ships NO `per_twin` key; `createSessionResponseSchema` then SYNTHESIZES one
7
+ // (host-rewrites api.pome.sh→mcp.pome.sh with no `/mcp` suffix). Consumers that
8
+ // build agent env from `per_twin[twin].mcp_url` must NOT trust that synthesized
9
+ // value — it would drift POME_*_MCP_URL off origin/main's `${twin_url}/mcp`. We
10
+ // record, on the parsed object as a NON-ENUMERABLE symbol (never serialized,
11
+ // never spread), whether the raw wire body actually carried a `per_twin` key.
12
+ const PER_TWIN_PROVENANCE = Symbol("pome.perTwinFromCloud");
13
+ function markPerTwinProvenance(session, fromCloud) {
14
+ Object.defineProperty(session, PER_TWIN_PROVENANCE, {
15
+ value: fromCloud,
16
+ enumerable: false,
17
+ configurable: true,
18
+ });
19
+ return session;
20
+ }
21
+ /** True when the cloud's create-session body actually returned a `per_twin`
22
+ * key (empty `{}` counts) — i.e. `per_twin` was NOT synthesized by the schema
23
+ * from a legacy `twin_url`-only response. Consumers use this to decide whether
24
+ * `per_twin[twin].mcp_url` is trustworthy. */
25
+ export function perTwinReturnedByCloud(session) {
26
+ return (session[PER_TWIN_PROVENANCE] === true);
27
+ }
28
+ /** Whether a raw create-session wire body carried a usable `per_twin` map
29
+ * (before schema synthesis). A `per_twin: null` counts as absent — the schema
30
+ * would synthesize entries for it, and synthesized URLs must not be trusted.
31
+ * Exported so the runner's env unit test can model the exact provenance the
32
+ * client stamps. */
33
+ export function rawBodyHadPerTwin(raw) {
34
+ if (typeof raw !== "object" || raw === null)
35
+ return false;
36
+ const pt = raw.per_twin;
37
+ return typeof pt === "object" && pt !== null;
38
+ }
39
+ // Keep these readers wire-identical to the additive exports in
40
+ // @pome-sh/shared-types 0.6.1. The CLI intentionally remains installable
41
+ // against 0.6.0 until that package batch has published.
42
+ // Scored finalize responses stay non-strict so additive M7 keys strip.
43
+ const finalizeScoredResponseSchema = finalizeResponseSchema.strip();
44
+ const finalizeStatusUrlSchema = z
45
+ .string()
46
+ .min(1)
47
+ .refine((value) => {
48
+ if (value.startsWith("/") && !value.startsWith("//"))
49
+ return true;
50
+ try {
51
+ new URL(value);
52
+ return true;
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }, { message: "Invalid status_url" });
58
+ const finalizeAcceptedResponseSchema = z.object({
59
+ evaluation_id: z.string().min(1),
60
+ run_id: z.string().min(1),
61
+ status: z.literal("queued"),
62
+ status_url: finalizeStatusUrlSchema,
63
+ }).strict();
64
+ const finalizeStatusIdentitySchema = {
65
+ evaluation_id: z.string().min(1),
66
+ run_id: z.string().min(1),
67
+ };
68
+ const finalizeFailureErrorSchema = z.object({
69
+ type: z.string().min(1),
70
+ message: z.string().min(1),
71
+ details: z.record(z.string(), z.unknown()).optional(),
72
+ }).strict();
73
+ const finalizeStatusResponseSchema = z.discriminatedUnion("status", [
74
+ z.object({
75
+ ...finalizeStatusIdentitySchema,
76
+ status: z.literal("queued"),
77
+ }).strict(),
78
+ z.object({
79
+ ...finalizeStatusIdentitySchema,
80
+ status: z.literal("running"),
81
+ }).strict(),
82
+ z.object({
83
+ ...finalizeStatusIdentitySchema,
84
+ status: z.literal("failed"),
85
+ error: finalizeFailureErrorSchema,
86
+ }).strict(),
87
+ z.object({
88
+ ...finalizeStatusIdentitySchema,
89
+ status: z.literal("completed"),
90
+ result: finalizeScoredResponseSchema,
91
+ }).strict(),
92
+ ]);
93
+ class RetryableFinalizeStatusError extends HostedOrchError {
94
+ retryAfterMs;
95
+ constructor(error, retryAfterMs) {
96
+ super(error.message, error.requestId, error.status);
97
+ this.retryAfterMs = retryAfterMs;
98
+ this.name = "RetryableFinalizeStatusError";
99
+ }
100
+ }
4
101
  export function createHostedClient(config) {
5
102
  const timeoutMs = config.timeoutMs ?? 30_000;
103
+ const finalizeTimeoutMs = config.finalizeTimeoutMs ?? 5 * 60_000;
104
+ const finalizePollInitialDelayMs = Math.max(1, config.finalizePollInitialDelayMs ?? 500);
105
+ const finalizePollMaxDelayMs = Math.max(finalizePollInitialDelayMs, config.finalizePollMaxDelayMs ?? 5_000);
6
106
  const authHeaders = config.authScheme === "bearer"
7
107
  ? { authorization: `Bearer ${config.apiKey}` }
8
108
  : { "x-api-key": config.apiKey };
@@ -13,7 +113,7 @@ export function createHostedClient(config) {
13
113
  json = text.length ? JSON.parse(text) : {};
14
114
  }
15
115
  catch {
16
- throw new HostedOrchError(`non-JSON response (status ${res.status})`);
116
+ throw new HostedOrchError(`non-JSON response (status ${res.status})`, undefined, res.status);
17
117
  }
18
118
  if (!res.ok) {
19
119
  // Cloud's REST envelope is `{ error: { type, message, request_id } }`.
@@ -33,37 +133,56 @@ export function createHostedClient(config) {
33
133
  // labeled at-capacity state instead of a generic quota message.
34
134
  throw new HostedQuotaError(msg, reqId, cloudDetails);
35
135
  }
36
- throw new HostedOrchError(msg, reqId, res.status);
136
+ // Carry the envelope `error.type` (e.g. `multi_twin_unsupported`) so
137
+ // callers can map specific rejections to friendly hints.
138
+ throw new HostedOrchError(msg, reqId, res.status, cloudErr?.type);
37
139
  }
38
140
  try {
39
- return parse(json);
141
+ return parse(json, res);
40
142
  }
41
143
  catch (err) {
42
- throw new HostedOrchError(`unexpected response shape: ${err instanceof Error ? err.message : String(err)}`);
144
+ throw new HostedOrchError(`unexpected response shape: ${err instanceof Error ? err.message : String(err)}`, undefined, res.status);
43
145
  }
44
146
  }
45
- async function postJson(path, body, parse) {
147
+ async function postJson(path, body, parse, options) {
46
148
  const ctrl = new AbortController();
47
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
48
- let res;
149
+ const timer = setTimeout(() => ctrl.abort(), options?.requestTimeoutMs ?? timeoutMs);
150
+ const abortFromCaller = () => ctrl.abort(options?.signal?.reason);
151
+ options?.signal?.addEventListener("abort", abortFromCaller, { once: true });
152
+ if (options?.signal?.aborted)
153
+ abortFromCaller();
49
154
  try {
50
- res = await fetch(`${config.baseUrl}${path}`, {
155
+ const res = await fetch(`${config.baseUrl}${path}`, {
51
156
  method: "POST",
52
157
  headers: {
53
158
  ...authHeaders,
54
159
  "content-type": "application/json",
160
+ ...options?.headers,
55
161
  },
56
162
  body: JSON.stringify(body),
57
163
  signal: ctrl.signal,
58
164
  });
165
+ if (res.ok &&
166
+ options?.acceptedStatuses &&
167
+ !options.acceptedStatuses.includes(res.status)) {
168
+ throw new HostedOrchError(`unexpected HTTP status ${res.status}`, undefined, res.status);
169
+ }
170
+ // Keep the timeout and caller cancellation active while consuming the
171
+ // body. fetch() resolves after headers, before a streamed body finishes.
172
+ return await readResponse(res, parse);
59
173
  }
60
174
  catch (err) {
175
+ if (err instanceof HostedAuthError ||
176
+ err instanceof HostedQuotaError ||
177
+ err instanceof HostedOrchError) {
178
+ throw err;
179
+ }
61
180
  throw new HostedOrchError(err instanceof Error ? err.message : "network error");
62
181
  }
63
182
  finally {
64
183
  clearTimeout(timer);
184
+ options?.signal?.removeEventListener("abort", abortFromCaller);
65
185
  }
66
- return readResponse(res, parse);
67
186
  }
68
187
  async function getJsonBearer(url, bearer, parse) {
69
188
  const ctrl = new AbortController();
@@ -103,6 +222,170 @@ export function createHostedClient(config) {
103
222
  }
104
223
  return readResponse(res, parse);
105
224
  }
225
+ function retryAfterMs(value) {
226
+ if (value === null)
227
+ return null;
228
+ const seconds = Number(value);
229
+ if (Number.isFinite(seconds) && seconds >= 0) {
230
+ return seconds * 1_000;
231
+ }
232
+ const at = Date.parse(value);
233
+ return Number.isNaN(at) ? null : Math.max(0, at - Date.now());
234
+ }
235
+ function nextFinalizePollDelayMs(currentDelayMs, serverDelayMs) {
236
+ if (serverDelayMs !== null) {
237
+ // Retry-After: 0 is legal, but accepting it literally on every response
238
+ // creates a hot authenticated loop. The configured initial delay is the
239
+ // lower bound for server hints.
240
+ return Math.max(serverDelayMs, finalizePollInitialDelayMs);
241
+ }
242
+ return Math.min(Math.max(currentDelayMs * 2, finalizePollInitialDelayMs), finalizePollMaxDelayMs);
243
+ }
244
+ async function sleepForPoll(ms, signal) {
245
+ if (signal?.aborted) {
246
+ throw new HostedOrchError("finalize aborted");
247
+ }
248
+ if (ms <= 0)
249
+ return;
250
+ await new Promise((resolve, reject) => {
251
+ let settled = false;
252
+ const timer = setTimeout(() => {
253
+ settled = true;
254
+ signal?.removeEventListener("abort", onAbort);
255
+ resolve();
256
+ }, ms);
257
+ const onAbort = () => {
258
+ if (settled)
259
+ return;
260
+ settled = true;
261
+ clearTimeout(timer);
262
+ signal?.removeEventListener("abort", onAbort);
263
+ reject(new HostedOrchError("finalize aborted"));
264
+ };
265
+ signal?.addEventListener("abort", onAbort, { once: true });
266
+ // Re-check after subscribe — abort between the early check and
267
+ // addEventListener would otherwise hang until the delay elapses.
268
+ if (signal?.aborted)
269
+ onAbort();
270
+ });
271
+ }
272
+ function resolveFinalizeStatusUrl(statusUrl) {
273
+ const apiOrigin = new URL(config.baseUrl).origin;
274
+ let resolved;
275
+ try {
276
+ resolved = new URL(statusUrl, apiOrigin);
277
+ }
278
+ catch {
279
+ throw new HostedOrchError("finalize status_url is not a valid URL");
280
+ }
281
+ if (resolved.origin !== apiOrigin) {
282
+ throw new HostedOrchError("finalize status_url must use the configured API origin");
283
+ }
284
+ return resolved;
285
+ }
286
+ async function getFinalizeStatus(url, signal, requestTimeoutMs) {
287
+ const ctrl = new AbortController();
288
+ const timer = setTimeout(() => ctrl.abort(), requestTimeoutMs);
289
+ const abortFromCaller = () => ctrl.abort(signal?.reason);
290
+ signal?.addEventListener("abort", abortFromCaller, { once: true });
291
+ if (signal?.aborted)
292
+ abortFromCaller();
293
+ try {
294
+ const res = await fetch(url, {
295
+ method: "GET",
296
+ headers: authHeaders,
297
+ signal: ctrl.signal,
298
+ // Node fetch preserves custom x-api-key headers across a cross-origin
299
+ // redirect. Refuse redirects so same-origin validation remains true
300
+ // for the server that actually receives tenant credentials.
301
+ redirect: "manual",
302
+ });
303
+ if (res.status >= 300 && res.status < 400) {
304
+ throw new HostedOrchError("finalize status request must not redirect", undefined, res.status);
305
+ }
306
+ const delay = retryAfterMs(res.headers.get("retry-after"));
307
+ // Keep both timeout signals active until the full body is consumed.
308
+ let status;
309
+ try {
310
+ status = await readResponse(res, (raw) => finalizeStatusResponseSchema.parse(raw));
311
+ }
312
+ catch (err) {
313
+ if (err instanceof HostedOrchError &&
314
+ err.status !== undefined &&
315
+ err.status >= 500) {
316
+ throw new RetryableFinalizeStatusError(err, delay);
317
+ }
318
+ throw err;
319
+ }
320
+ return { status, retryAfterMs: delay };
321
+ }
322
+ catch (err) {
323
+ if (signal?.aborted) {
324
+ throw new HostedOrchError("finalize aborted");
325
+ }
326
+ if (err instanceof HostedAuthError ||
327
+ err instanceof HostedQuotaError ||
328
+ err instanceof HostedOrchError) {
329
+ throw err;
330
+ }
331
+ throw new HostedOrchError(err instanceof Error ? err.message : "network error");
332
+ }
333
+ finally {
334
+ clearTimeout(timer);
335
+ signal?.removeEventListener("abort", abortFromCaller);
336
+ }
337
+ }
338
+ async function pollFinalize(accepted, deadline, signal) {
339
+ const statusUrl = resolveFinalizeStatusUrl(accepted.status_url);
340
+ let delayMs = finalizePollInitialDelayMs;
341
+ while (true) {
342
+ const remainingBeforeDelay = deadline - Date.now();
343
+ if (remainingBeforeDelay <= 0) {
344
+ throw new HostedOrchError("asynchronous finalize timed out");
345
+ }
346
+ await sleepForPoll(Math.min(delayMs, remainingBeforeDelay), signal);
347
+ const remainingBeforeRequest = deadline - Date.now();
348
+ if (remainingBeforeRequest <= 0) {
349
+ throw new HostedOrchError("asynchronous finalize timed out");
350
+ }
351
+ let polled;
352
+ try {
353
+ polled = await getFinalizeStatus(statusUrl, signal, Math.min(timeoutMs, remainingBeforeRequest));
354
+ }
355
+ catch (err) {
356
+ if (signal?.aborted)
357
+ throw err;
358
+ if (err instanceof HostedOrchError &&
359
+ (err.status === undefined || err.status >= 500)) {
360
+ // The evaluation is durable. A transient status transport failure
361
+ // must not turn a still-running evaluation into a CLI failure.
362
+ delayMs = nextFinalizePollDelayMs(delayMs, err instanceof RetryableFinalizeStatusError
363
+ ? err.retryAfterMs
364
+ : null);
365
+ continue;
366
+ }
367
+ throw err;
368
+ }
369
+ if (polled.status.evaluation_id !== accepted.evaluation_id ||
370
+ polled.status.run_id !== accepted.run_id) {
371
+ throw new HostedOrchError("finalize status identifiers do not match the accepted evaluation");
372
+ }
373
+ if (polled.status.status === "completed") {
374
+ if (polled.status.result.run_id !== accepted.run_id) {
375
+ throw new HostedOrchError("finalize result run_id does not match the accepted evaluation");
376
+ }
377
+ return polled.status.result;
378
+ }
379
+ if (polled.status.status === "failed") {
380
+ const { type, message, details } = polled.status.error;
381
+ if (type === "quota_exceeded") {
382
+ throw new HostedQuotaError(message, undefined, details);
383
+ }
384
+ throw new HostedOrchError(`${type}: ${message}`);
385
+ }
386
+ delayMs = nextFinalizePollDelayMs(delayMs, polled.retryAfterMs);
387
+ }
388
+ }
106
389
  return {
107
390
  async createSession(input) {
108
391
  const body = {
@@ -121,7 +404,7 @@ export function createHostedClient(config) {
121
404
  if (input.groupId) {
122
405
  body.group_id = input.groupId;
123
406
  }
124
- return postJson("/v1/sessions", body, (raw) => createSessionResponseSchema.parse(raw));
407
+ return postJson("/v1/sessions", body, (raw) => markPerTwinProvenance(createSessionResponseSchema.parse(raw), rawBodyHadPerTwin(raw)));
125
408
  },
126
409
  async abandonSession(sessionId, input) {
127
410
  const body = {};
@@ -210,22 +493,39 @@ export function createHostedClient(config) {
210
493
  throw new HostedOrchError("POST /v1/sessions/:id/meta-upload-url returned unexpected shape");
211
494
  });
212
495
  },
213
- async requestStateUploadUrl(sessionId) {
214
- return postJson(`/v1/sessions/${encodeURIComponent(sessionId)}/state-upload-url`, {}, (raw) => {
496
+ async requestStateUploadUrl(sessionId, twins) {
497
+ // Multi-twin (M3): a `{ twins }` body asks the cloud to sign a per-twin
498
+ // pair for each twin (returned under `per_twin`). Single-twin callers
499
+ // pass nothing and the body stays `{}` — byte-identical to pre-M3.
500
+ const body = twins && twins.length > 0 ? { twins } : {};
501
+ return postJson(`/v1/sessions/${encodeURIComponent(sessionId)}/state-upload-url`, body, (raw) => {
215
502
  const isEntry = (x) => typeof x === "object" &&
216
503
  x !== null &&
217
504
  typeof x.url === "string" &&
218
505
  typeof x.key === "string";
219
- if (typeof raw === "object" &&
220
- raw !== null &&
221
- isEntry(raw.state_initial) &&
222
- isEntry(raw.state_final)) {
223
- return raw;
506
+ const isPair = (x) => typeof x === "object" &&
507
+ x !== null &&
508
+ isEntry(x.state_initial) &&
509
+ isEntry(x.state_final);
510
+ if (isPair(raw)) {
511
+ // Validate `per_twin` when present; drop it silently if malformed
512
+ // rather than failing the whole request (best-effort, like the
513
+ // rest of the upload surface).
514
+ const perTwinRaw = raw.per_twin;
515
+ let perTwin;
516
+ if (typeof perTwinRaw === "object" &&
517
+ perTwinRaw !== null &&
518
+ !Array.isArray(perTwinRaw) &&
519
+ Object.values(perTwinRaw).every(isPair)) {
520
+ perTwin = perTwinRaw;
521
+ }
522
+ return { ...raw, per_twin: perTwin };
224
523
  }
225
524
  throw new HostedOrchError("POST /v1/sessions/:id/state-upload-url returned unexpected shape");
226
525
  });
227
526
  },
228
- async finalize(sessionId, input) {
527
+ async finalize(sessionId, input, options) {
528
+ const deadline = Date.now() + finalizeTimeoutMs;
229
529
  const body = {
230
530
  stop_reason: input.stopReason,
231
531
  exit_code: input.exitCode,
@@ -250,7 +550,21 @@ export function createHostedClient(config) {
250
550
  if (input.signalsStorageKey !== undefined) {
251
551
  body.signals_storage_key = input.signalsStorageKey;
252
552
  }
253
- return postJson(`/v1/sessions/${encodeURIComponent(sessionId)}/finalize`, body, (raw) => finalizeResponseSchema.parse(raw));
553
+ if (input.perTwinStateKeys !== undefined &&
554
+ Object.keys(input.perTwinStateKeys).length > 0) {
555
+ body.per_twin_state_keys = input.perTwinStateKeys;
556
+ }
557
+ const initial = await postJson(`/v1/sessions/${encodeURIComponent(sessionId)}/finalize`, body, (raw, response) => response.status === 202
558
+ ? finalizeAcceptedResponseSchema.parse(raw)
559
+ : finalizeScoredResponseSchema.parse(raw), {
560
+ headers: { prefer: "respond-async" },
561
+ signal: options?.signal,
562
+ acceptedStatuses: [200, 201, 202],
563
+ requestTimeoutMs: Math.min(timeoutMs, finalizeTimeoutMs),
564
+ });
565
+ return "status_url" in initial
566
+ ? pollFinalize(initial, deadline, options?.signal)
567
+ : initial;
254
568
  },
255
569
  async submitResult(sessionId, input) {
256
570
  return postJson(`/v1/sessions/${encodeURIComponent(sessionId)}/result`, {
@@ -14,10 +14,20 @@ export declare class HostedQuotaError extends Error {
14
14
  export declare class HostedOrchError extends Error {
15
15
  readonly requestId?: string | undefined;
16
16
  readonly status?: number | undefined;
17
+ /** Machine-readable `error.type` from the cloud envelope, when present
18
+ * (e.g. `multi_twin_unsupported` so the CLI can map an old-cloud
19
+ * multi-twin rejection to a friendly hint). Undefined for
20
+ * network/parse failures and the twin-pod 401 shape. */
21
+ readonly type?: string | undefined;
17
22
  /** `status` is the HTTP status that produced this error, when one exists
18
23
  * (network/parse failures leave it undefined). `pome eval` uses it to
19
24
  * scope its reaped-session retry to 404/410 only (FDRS-656 review). */
20
- constructor(message: string, requestId?: string | undefined, status?: number | undefined);
25
+ constructor(message: string, requestId?: string | undefined, status?: number | undefined,
26
+ /** Machine-readable `error.type` from the cloud envelope, when present
27
+ * (e.g. `multi_twin_unsupported` so the CLI can map an old-cloud
28
+ * multi-twin rejection to a friendly hint). Undefined for
29
+ * network/parse failures and the twin-pod 401 shape. */
30
+ type?: string | undefined);
21
31
  }
22
32
  /** Bad invocation — missing scenario file, unknown flag value, etc. Maps
23
33
  * to documented exit code 5 ("usage error"). F0-5a. */
@@ -49,13 +49,20 @@ export class HostedQuotaError extends Error {
49
49
  export class HostedOrchError extends Error {
50
50
  requestId;
51
51
  status;
52
+ type;
52
53
  /** `status` is the HTTP status that produced this error, when one exists
53
54
  * (network/parse failures leave it undefined). `pome eval` uses it to
54
55
  * scope its reaped-session retry to 404/410 only (FDRS-656 review). */
55
- constructor(message, requestId, status) {
56
+ constructor(message, requestId, status,
57
+ /** Machine-readable `error.type` from the cloud envelope, when present
58
+ * (e.g. `multi_twin_unsupported` so the CLI can map an old-cloud
59
+ * multi-twin rejection to a friendly hint). Undefined for
60
+ * network/parse failures and the twin-pod 401 shape. */
61
+ type) {
56
62
  super(message);
57
63
  this.requestId = requestId;
58
64
  this.status = status;
65
+ this.type = type;
59
66
  this.name = "HostedOrchError";
60
67
  }
61
68
  }
@@ -33,6 +33,8 @@ export type ScoreStatus = "pass" | "fail" | "unevaluated";
33
33
  export declare function scoreStatus(score: Score, passThreshold: number): ScoreStatus;
34
34
  export declare function scenarioPassed(score: Score, passThreshold: number): boolean;
35
35
  export declare function markerFor(outcome: CriterionOutcome): string;
36
+ export declare function criterionMarkerLabel(criterion: WireCriterion): string;
37
+ export declare function twinSkipSuffix(result: CriterionResult): string;
36
38
  export declare function scoreCountsSummary(score: Score): string;
37
39
  export declare function runScoreLine(score: Score, passThreshold: number, unevaluatedNumericLabel: string): string;
38
40
  export {};
@@ -52,6 +52,28 @@ export function markerFor(outcome) {
52
52
  return "-";
53
53
  }
54
54
  }
55
+ // Multi-twin (M3): the per-criterion bracket for terminal display —
56
+ // `[code]` / `[model]`, plus the `:<twin>` suffix when the criterion attributes
57
+ // to a specific twin (so a `[D:slack]`/`[P:github]` marker survives into the
58
+ // UNEVAL / criteria list). A bare (primary-twin) criterion renders `[code]`
59
+ // unchanged.
60
+ export function criterionMarkerLabel(criterion) {
61
+ return criterion.twin ? `[${criterion.type}:${criterion.twin}]` : `[${criterion.type}]`;
62
+ }
63
+ // Multi-twin (M3): when the cloud could not evaluate a criterion for a
64
+ // twin-related reason (a twin-tagged criterion, or a `no_matching_predicate`
65
+ // skip), name the twin inline so the UNEVAL line explains WHICH twin's timeline
66
+ // came up empty. Returns "" when there's nothing twin-specific to add.
67
+ export function twinSkipSuffix(result) {
68
+ const twin = result.criterion.twin;
69
+ if (!twin)
70
+ return "";
71
+ const outcome = outcomeOf(result);
72
+ const twinRelated = outcome === "skipped" ||
73
+ outcome === "errored" ||
74
+ /no_matching_predicate|no matching predicate/i.test(result.reason);
75
+ return twinRelated ? ` (twin: ${twin})` : "";
76
+ }
55
77
  export function scoreCountsSummary(score) {
56
78
  return `${score.passed ?? 0} passed, ${score.failed ?? 0} failed, ${score.skipped ?? 0} skipped, ${score.errored ?? 0} errored`;
57
79
  }
@@ -1,5 +1,5 @@
1
1
  import type { HostedClient } from "./client.js";
2
- import type { FinalizeResponse } from "../types/shared.js";
2
+ import type { FinalizeResponse, PerTwinStateKeys } from "../types/shared.js";
3
3
  import type { Score } from "./evalResultView.js";
4
4
  /** The narrow client surface the upload orchestration needs. `pome eval`
5
5
  * mocks exactly this in tests instead of the full HostedClient. */
@@ -14,12 +14,29 @@ export interface RunBlobs {
14
14
  /** D18.1 — the run's meta.json (spec_version + twin_versions + the rest of
15
15
  * writeRunArtifactsCore's payload), as written to / read from disk. */
16
16
  metaJson: string;
17
+ /** Multi-twin (M3): the session's twins. When >1, per-twin state upload URLs
18
+ * are requested and each twin's state blobs are uploaded under its own
19
+ * storage prefix. Absent / length ≤1 keeps the single-twin path unchanged. */
20
+ twins?: string[];
21
+ /** Multi-twin (M3): per-twin initial/final state JSON, keyed by twin id.
22
+ * Includes the primary twin (whose blobs also go to the legacy top-level
23
+ * pair). Only consumed when `twins.length > 1` and the cloud returns a
24
+ * `per_twin` upload block. */
25
+ perTwinState?: Record<string, {
26
+ initialJson: string;
27
+ finalJson: string;
28
+ }>;
17
29
  }
18
30
  export interface UploadedBlobKeys {
19
31
  eventsKey: string | null;
20
32
  stateInitialKey: string | null;
21
33
  stateFinalKey: string | null;
22
34
  signalsKey: string | null;
35
+ /** Multi-twin (M3): per-twin state storage keys, keyed by twin id. Each entry
36
+ * carries at least one uploaded key (empty entries are dropped so the
37
+ * finalize contract's ">=1 key per entry" invariant holds). Undefined when
38
+ * the session is single-twin or the cloud returned no `per_twin` block. */
39
+ perTwinStateKeys?: PerTwinStateKeys;
23
40
  /** D18.1 — the storage key the presigned PUT landed at, or null whenever
24
41
  * the upload didn't happen, INCLUDING the feature-detection case (older
25
42
  * control plane 404s meta-upload-url). Never distinguished from any other
@@ -6,16 +6,29 @@
6
6
  // without duplicating them. Behavior-preserving for the hosted run path:
7
7
  // warning text, the 30s PUT timeout, and the null-key fallbacks all match
8
8
  // the pre-extraction runner.
9
+ import { gzipSync } from "node:zlib";
9
10
  import { outcomeOf } from "./evalResultView.js";
10
11
  import { redactSecrets } from "../recorder/redaction.js";
12
+ // Gzip EVERY blob before PUT. The storage edge (Cloudflare in front of
13
+ // Supabase) runs a WAF managed rule that 403-blocks any PUT whose plaintext
14
+ // body contains certain literal keys (e.g. an admin-flag key present in slack
15
+ // twin state exports), which silently drops per-twin state uploads and makes
16
+ // their criteria skip as state_missing. Gzipped bodies sail past the rule.
17
+ // One code path here so every caller (events, per-twin state, signals,
18
+ // meta.json) inherits it; the cloud's blob readers transparently gunzip via
19
+ // the content-encoding header (shipped in the paired reader release before
20
+ // this CLI change is released).
11
21
  async function putBlob(url, body, contentType, label) {
12
22
  const ctrl = new AbortController();
13
23
  const timer = setTimeout(() => ctrl.abort(), 30_000);
14
24
  try {
15
25
  const putRes = await fetch(url, {
16
26
  method: "PUT",
17
- headers: { "content-type": contentType },
18
- body,
27
+ headers: {
28
+ "content-type": contentType,
29
+ "content-encoding": "gzip",
30
+ },
31
+ body: gzipSync(Buffer.from(body, "utf8")),
19
32
  signal: ctrl.signal,
20
33
  });
21
34
  if (!putRes.ok) {
@@ -72,15 +85,49 @@ export async function uploadRunBlobs(client, sessionId, blobs) {
72
85
  }
73
86
  }
74
87
  async function uploadStates() {
88
+ const multiTwin = (blobs.twins?.length ?? 0) > 1;
75
89
  try {
76
- const upload = await client.requestStateUploadUrl(sessionId);
90
+ // Multi-twin (M3): ask for a per-twin URL block; single-twin passes
91
+ // nothing so the request body stays `{}` (byte-identical to pre-M3).
92
+ const upload = await client.requestStateUploadUrl(sessionId, multiTwin ? blobs.twins : undefined);
93
+ // Top-level (primary-twin) pair — always uploaded, legacy state_*_storage_key.
77
94
  const [initialOk, finalOk] = await Promise.all([
78
95
  putBlob(upload.state_initial.url, blobs.stateInitialJson, "application/json", "state_initial.json"),
79
96
  putBlob(upload.state_final.url, blobs.stateFinalJson, "application/json", "state_final.json"),
80
97
  ]);
98
+ // Multi-twin: upload each twin's blobs to its own storage prefix and
99
+ // collect the keys. Only when the cloud actually returned a `per_twin`
100
+ // block (an older cloud omits it → we fall back to the primary-only
101
+ // top-level keys, degrading gracefully). Entries with no uploaded key are
102
+ // dropped so the finalize contract's ">=1 key per entry" holds.
103
+ let perTwinStateKeys;
104
+ if (multiTwin && upload.per_twin) {
105
+ const collected = {};
106
+ for (const twin of blobs.twins ?? []) {
107
+ const pair = upload.per_twin[twin];
108
+ const state = blobs.perTwinState?.[twin];
109
+ if (!pair || !state)
110
+ continue;
111
+ const [iOk, fOk] = await Promise.all([
112
+ putBlob(pair.state_initial.url, state.initialJson, "application/json", `state_initial.${twin}.json`),
113
+ putBlob(pair.state_final.url, state.finalJson, "application/json", `state_final.${twin}.json`),
114
+ ]);
115
+ const entry = {};
116
+ if (iOk)
117
+ entry.state_initial_key = pair.state_initial.key;
118
+ if (fOk)
119
+ entry.state_final_key = pair.state_final.key;
120
+ if (entry.state_initial_key !== undefined || entry.state_final_key !== undefined) {
121
+ collected[twin] = entry;
122
+ }
123
+ }
124
+ if (Object.keys(collected).length > 0)
125
+ perTwinStateKeys = collected;
126
+ }
81
127
  return {
82
128
  initialKey: initialOk ? upload.state_initial.key : null,
83
129
  finalKey: finalOk ? upload.state_final.key : null,
130
+ perTwinStateKeys,
84
131
  };
85
132
  }
86
133
  catch (err) {
@@ -124,6 +171,7 @@ export async function uploadRunBlobs(client, sessionId, blobs) {
124
171
  eventsKey,
125
172
  stateInitialKey: stateKeys.initialKey,
126
173
  stateFinalKey: stateKeys.finalKey,
174
+ perTwinStateKeys: stateKeys.perTwinStateKeys,
127
175
  metaKey,
128
176
  signalsKey,
129
177
  };