okengine 0.1.4

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 (674) hide show
  1. package/AGENTS.md +91 -0
  2. package/README.md +327 -0
  3. package/docs/spec/complete-example.md +1188 -0
  4. package/docs/spec/console.md +739 -0
  5. package/docs/spec/four-applications.md +1188 -0
  6. package/docs/spec/unified-theory.md +489 -0
  7. package/package.json +152 -0
  8. package/spec/manifest.v1.schema.json +624 -0
  9. package/src/auth/api-keys.ts +168 -0
  10. package/src/auth/attenuation.ts +116 -0
  11. package/src/auth/auth.test.ts +273 -0
  12. package/src/auth/cross-plane.ts +98 -0
  13. package/src/auth/index.ts +112 -0
  14. package/src/auth/invites.ts +66 -0
  15. package/src/auth/invoke-as.ts +49 -0
  16. package/src/auth/operator.ts +178 -0
  17. package/src/auth/planes.ts +102 -0
  18. package/src/auth/plugin.ts +44 -0
  19. package/src/auth/roles.ts +84 -0
  20. package/src/auth/sessions.ts +418 -0
  21. package/src/auth/tables.ts +131 -0
  22. package/src/cli/args.ts +50 -0
  23. package/src/cli/branch.ts +75 -0
  24. package/src/cli/build.ts +86 -0
  25. package/src/cli/client-add.test.ts +140 -0
  26. package/src/cli/client-add.ts +164 -0
  27. package/src/cli/completion.test.ts +58 -0
  28. package/src/cli/completion.ts +296 -0
  29. package/src/cli/dev-app-runner.ts +77 -0
  30. package/src/cli/dev.test.ts +435 -0
  31. package/src/cli/dev.ts +594 -0
  32. package/src/cli/doc-drift.test.ts +146 -0
  33. package/src/cli/doc-drift.ts +397 -0
  34. package/src/cli/docker-cli.test.ts +221 -0
  35. package/src/cli/docker.ts +127 -0
  36. package/src/cli/doctor-diff-examples.ts +92 -0
  37. package/src/cli/doctor-diff.test.ts +91 -0
  38. package/src/cli/doctor-diff.ts +274 -0
  39. package/src/cli/doctor-pii.test.ts +98 -0
  40. package/src/cli/doctor-pii.ts +151 -0
  41. package/src/cli/doctor.test.ts +150 -0
  42. package/src/cli/doctor.ts +223 -0
  43. package/src/cli/eval.ts +122 -0
  44. package/src/cli/exit.ts +19 -0
  45. package/src/cli/gates-list.test.ts +47 -0
  46. package/src/cli/gates-list.ts +84 -0
  47. package/src/cli/images.ts +277 -0
  48. package/src/cli/index.ts +99 -0
  49. package/src/cli/json-out.test.ts +129 -0
  50. package/src/cli/load-config.ts +71 -0
  51. package/src/cli/mcp-from-console.ts +50 -0
  52. package/src/cli/privacy-erase.ts +73 -0
  53. package/src/cli/registry.ts +455 -0
  54. package/src/cli/safe-defaults.test.ts +92 -0
  55. package/src/cli/schema.ts +196 -0
  56. package/src/cli/stack.ts +82 -0
  57. package/src/cli/start.ts +106 -0
  58. package/src/cli/upgrade.ts +105 -0
  59. package/src/cli/vault-cmd.ts +140 -0
  60. package/src/client/budget-entry.ts +34 -0
  61. package/src/client/budget.test.ts +17 -0
  62. package/src/client/create.test.ts +159 -0
  63. package/src/client/create.ts +152 -0
  64. package/src/client/errors.ts +60 -0
  65. package/src/client/index.ts +35 -0
  66. package/src/client/notes-contract.test.ts +289 -0
  67. package/src/client/transport.test.ts +123 -0
  68. package/src/client/transport.ts +254 -0
  69. package/src/client/types.ts +282 -0
  70. package/src/compiler/aot.test.ts +248 -0
  71. package/src/compiler/aot.ts +182 -0
  72. package/src/compiler/differential.test.ts +241 -0
  73. package/src/compiler/dynamic.ts +54 -0
  74. package/src/compiler/effects-infer.ts +541 -0
  75. package/src/compiler/emit.ts +40 -0
  76. package/src/compiler/extract.test.ts +177 -0
  77. package/src/compiler/extract.ts +1368 -0
  78. package/src/compiler/fixtures/raw/raw-unannotated.ts +28 -0
  79. package/src/compiler/fixtures/skyport/oke.config.ts +24 -0
  80. package/src/compiler/fixtures/skyport/src/ai.ts +15 -0
  81. package/src/compiler/fixtures/skyport/src/app.ts +10 -0
  82. package/src/compiler/fixtures/skyport/src/channels.ts +6 -0
  83. package/src/compiler/fixtures/skyport/src/core.ts +3 -0
  84. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +57 -0
  85. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +11 -0
  86. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +14 -0
  87. package/src/compiler/fixtures/skyport/src/flows/support/index.ts +20 -0
  88. package/src/compiler/fixtures/skyport/src/gates.ts +14 -0
  89. package/src/compiler/fixtures/skyport/src/journeys.ts +9 -0
  90. package/src/compiler/fixtures/skyport/src/schema.ts +3 -0
  91. package/src/compiler/fixtures/skyport/src/vault.ts +6 -0
  92. package/src/compiler/fixtures/skyport.expected.json +241 -0
  93. package/src/compiler/fixtures/triggers/five-triggers.ts +69 -0
  94. package/src/compiler/http-parse.ts +214 -0
  95. package/src/compiler/index.ts +67 -0
  96. package/src/compiler/response.ts +89 -0
  97. package/src/compiler/sucrose.ts +238 -0
  98. package/src/config/index.ts +167 -0
  99. package/src/console/budget.test.ts +73 -0
  100. package/src/console/index.ts +30 -0
  101. package/src/console/server/access.test.ts +370 -0
  102. package/src/console/server/access.ts +777 -0
  103. package/src/console/server/ai.test.ts +172 -0
  104. package/src/console/server/ai.ts +632 -0
  105. package/src/console/server/app.ts +294 -0
  106. package/src/console/server/auth-rate.test.ts +81 -0
  107. package/src/console/server/auth-rate.ts +72 -0
  108. package/src/console/server/bind.ts +25 -0
  109. package/src/console/server/channels.test.ts +179 -0
  110. package/src/console/server/channels.ts +469 -0
  111. package/src/console/server/claim.test.ts +66 -0
  112. package/src/console/server/claim.ts +134 -0
  113. package/src/console/server/clock.test.ts +239 -0
  114. package/src/console/server/clock.ts +390 -0
  115. package/src/console/server/console.test.ts +338 -0
  116. package/src/console/server/diff.test.ts +299 -0
  117. package/src/console/server/diff.ts +352 -0
  118. package/src/console/server/dry-run.audit.test.ts +118 -0
  119. package/src/console/server/flows-invoke.test.ts +148 -0
  120. package/src/console/server/flows.ts +3633 -0
  121. package/src/console/server/gates.test.ts +360 -0
  122. package/src/console/server/gates.ts +869 -0
  123. package/src/console/server/index.ts +141 -0
  124. package/src/console/server/live.test.ts +30 -0
  125. package/src/console/server/live.ts +109 -0
  126. package/src/console/server/plugin.ts +50 -0
  127. package/src/console/server/plugins.test.ts +131 -0
  128. package/src/console/server/plugins.ts +429 -0
  129. package/src/console/server/runs-pii.ts +98 -0
  130. package/src/console/server/security-headers.ts +97 -0
  131. package/src/console/server/security.gate.test.ts +286 -0
  132. package/src/console/server/serve.ts +319 -0
  133. package/src/console/server/signals.test.ts +88 -0
  134. package/src/console/server/signals.ts +282 -0
  135. package/src/console/server/state.ts +982 -0
  136. package/src/console/server/store.test.ts +233 -0
  137. package/src/console/server/store.ts +861 -0
  138. package/src/console/server/structural.ts +95 -0
  139. package/src/console/server/vault.test.ts +251 -0
  140. package/src/console/server/vault.ts +430 -0
  141. package/src/console/ui/access/AccessA11yView.tsx +237 -0
  142. package/src/console/ui/access/a11y.test.tsx +122 -0
  143. package/src/console/ui/access/acknowledgement.test.ts +13 -0
  144. package/src/console/ui/access/acknowledgement.ts +35 -0
  145. package/src/console/ui/access/blast-radius.test.ts +26 -0
  146. package/src/console/ui/access/blast-radius.ts +40 -0
  147. package/src/console/ui/access/confirmation.test.ts +38 -0
  148. package/src/console/ui/access/confirmation.ts +36 -0
  149. package/src/console/ui/access/fixture.ts +203 -0
  150. package/src/console/ui/access/grantable.test.ts +17 -0
  151. package/src/console/ui/access/grantable.ts +36 -0
  152. package/src/console/ui/access/hygiene.test.ts +12 -0
  153. package/src/console/ui/access/hygiene.ts +39 -0
  154. package/src/console/ui/access/index.ts +54 -0
  155. package/src/console/ui/access/provenance.test.ts +12 -0
  156. package/src/console/ui/access/provenance.ts +29 -0
  157. package/src/console/ui/access/search.test.ts +17 -0
  158. package/src/console/ui/access/search.ts +72 -0
  159. package/src/console/ui/access/types.ts +111 -0
  160. package/src/console/ui/ai/AiA11yView.tsx +265 -0
  161. package/src/console/ui/ai/a11y.test.tsx +99 -0
  162. package/src/console/ui/ai/findings.ts +41 -0
  163. package/src/console/ui/ai/fixture.ts +182 -0
  164. package/src/console/ui/ai/format.ts +90 -0
  165. package/src/console/ui/ai/group.test.ts +37 -0
  166. package/src/console/ui/ai/group.ts +88 -0
  167. package/src/console/ui/ai/index.ts +66 -0
  168. package/src/console/ui/ai/promotion.test.ts +77 -0
  169. package/src/console/ui/ai/promotion.ts +150 -0
  170. package/src/console/ui/ai/search.test.ts +40 -0
  171. package/src/console/ui/ai/search.ts +89 -0
  172. package/src/console/ui/ai/types.ts +139 -0
  173. package/src/console/ui/architecture/ArchitectureA11yView.tsx +163 -0
  174. package/src/console/ui/architecture/a11y.test.tsx +97 -0
  175. package/src/console/ui/architecture/boundary.ts +42 -0
  176. package/src/console/ui/architecture/declared.ts +165 -0
  177. package/src/console/ui/architecture/fixture.ts +74 -0
  178. package/src/console/ui/architecture/index.ts +84 -0
  179. package/src/console/ui/architecture/layers.ts +64 -0
  180. package/src/console/ui/architecture/layout.ts +68 -0
  181. package/src/console/ui/architecture/pathologies.ts +260 -0
  182. package/src/console/ui/architecture/search.ts +145 -0
  183. package/src/console/ui/architecture/traffic.ts +102 -0
  184. package/src/console/ui/architecture/types.ts +144 -0
  185. package/src/console/ui/architecture/view.test.ts +165 -0
  186. package/src/console/ui/architecture/view.ts +516 -0
  187. package/src/console/ui/channels/ChannelsA11yView.tsx +180 -0
  188. package/src/console/ui/channels/a11y.test.tsx +96 -0
  189. package/src/console/ui/channels/confirmation.test.ts +37 -0
  190. package/src/console/ui/channels/confirmation.ts +27 -0
  191. package/src/console/ui/channels/fallback.test.ts +15 -0
  192. package/src/console/ui/channels/fallback.ts +20 -0
  193. package/src/console/ui/channels/findings.ts +38 -0
  194. package/src/console/ui/channels/fixture.ts +134 -0
  195. package/src/console/ui/channels/group.ts +25 -0
  196. package/src/console/ui/channels/index.ts +57 -0
  197. package/src/console/ui/channels/locale.test.ts +17 -0
  198. package/src/console/ui/channels/locale.ts +31 -0
  199. package/src/console/ui/channels/mask.test.ts +14 -0
  200. package/src/console/ui/channels/mask.ts +12 -0
  201. package/src/console/ui/channels/search.ts +58 -0
  202. package/src/console/ui/channels/taxonomy.test.ts +17 -0
  203. package/src/console/ui/channels/taxonomy.ts +49 -0
  204. package/src/console/ui/channels/types.ts +117 -0
  205. package/src/console/ui/clock/ClockA11yView.tsx +182 -0
  206. package/src/console/ui/clock/a11y.test.tsx +99 -0
  207. package/src/console/ui/clock/confirmation.test.ts +32 -0
  208. package/src/console/ui/clock/confirmation.ts +34 -0
  209. package/src/console/ui/clock/findings.ts +40 -0
  210. package/src/console/ui/clock/fixture.ts +111 -0
  211. package/src/console/ui/clock/health.test.ts +25 -0
  212. package/src/console/ui/clock/health.ts +61 -0
  213. package/src/console/ui/clock/index.ts +49 -0
  214. package/src/console/ui/clock/search.test.ts +39 -0
  215. package/src/console/ui/clock/search.ts +76 -0
  216. package/src/console/ui/clock/timeline.test.ts +44 -0
  217. package/src/console/ui/clock/timeline.ts +41 -0
  218. package/src/console/ui/clock/types.ts +78 -0
  219. package/src/console/ui/clock/waiting-on.test.ts +34 -0
  220. package/src/console/ui/clock/waiting-on.ts +83 -0
  221. package/src/console/ui/confirmation.unify.test.ts +92 -0
  222. package/src/console/ui/diff/DiffA11yView.tsx +85 -0
  223. package/src/console/ui/diff/a11y.test.tsx +99 -0
  224. package/src/console/ui/diff/fixture.ts +85 -0
  225. package/src/console/ui/diff/group.test.ts +58 -0
  226. package/src/console/ui/diff/group.ts +97 -0
  227. package/src/console/ui/diff/index.ts +38 -0
  228. package/src/console/ui/diff/search.test.ts +32 -0
  229. package/src/console/ui/diff/search.ts +75 -0
  230. package/src/console/ui/diff/summary.ts +74 -0
  231. package/src/console/ui/diff/types.ts +48 -0
  232. package/src/console/ui/dist/assets/index-B71Yl_SS.js +10 -0
  233. package/src/console/ui/dist/assets/panel-access-Dd37LU2c.js +64 -0
  234. package/src/console/ui/dist/assets/panel-ai-CC7LR6-J.js +1 -0
  235. package/src/console/ui/dist/assets/panel-architecture-B5b3iKCz.js +1 -0
  236. package/src/console/ui/dist/assets/panel-channels-CeNjTKXp.js +1 -0
  237. package/src/console/ui/dist/assets/panel-clock-DgFTLoHV.js +1 -0
  238. package/src/console/ui/dist/assets/panel-diff-DxehccqB.js +1 -0
  239. package/src/console/ui/dist/assets/panel-flows-BtrVn-Eg.js +45 -0
  240. package/src/console/ui/dist/assets/panel-gates-Z9MKRGdH.js +1 -0
  241. package/src/console/ui/dist/assets/panel-overview-Bd48d9km.js +1 -0
  242. package/src/console/ui/dist/assets/panel-plugins-DWd0TowH.js +1 -0
  243. package/src/console/ui/dist/assets/panel-runs-BwsWqKeB.js +1 -0
  244. package/src/console/ui/dist/assets/panel-signals-9najbZY2.js +1 -0
  245. package/src/console/ui/dist/assets/panel-store-OHkP2pDp.js +1 -0
  246. package/src/console/ui/dist/assets/panel-traces-tn2JoY8U.js +1 -0
  247. package/src/console/ui/dist/assets/panel-vault-BbfWdox0.js +1 -0
  248. package/src/console/ui/dist/assets/rolldown-runtime-CNC7AqOf.js +1 -0
  249. package/src/console/ui/dist/assets/style-Cnl7WLya.css +3 -0
  250. package/src/console/ui/dist/index.html +14 -0
  251. package/src/console/ui/flows/FlowsA11yView.tsx +155 -0
  252. package/src/console/ui/flows/a11y.test.tsx +105 -0
  253. package/src/console/ui/flows/buffer.test.ts +91 -0
  254. package/src/console/ui/flows/buffer.ts +118 -0
  255. package/src/console/ui/flows/confirmation.ts +131 -0
  256. package/src/console/ui/flows/contract.test.ts +89 -0
  257. package/src/console/ui/flows/contract.ts +353 -0
  258. package/src/console/ui/flows/fixture.ts +81 -0
  259. package/src/console/ui/flows/graph.test.ts +101 -0
  260. package/src/console/ui/flows/graph.ts +549 -0
  261. package/src/console/ui/flows/index.ts +87 -0
  262. package/src/console/ui/flows/save-as-test.test.ts +47 -0
  263. package/src/console/ui/flows/save-as-test.ts +99 -0
  264. package/src/console/ui/flows/search.ts +255 -0
  265. package/src/console/ui/flows/tiers.ts +140 -0
  266. package/src/console/ui/gates/GatesA11yView.tsx +192 -0
  267. package/src/console/ui/gates/a11y.test.tsx +116 -0
  268. package/src/console/ui/gates/audit.test.ts +40 -0
  269. package/src/console/ui/gates/audit.ts +63 -0
  270. package/src/console/ui/gates/denial.test.ts +36 -0
  271. package/src/console/ui/gates/denial.ts +51 -0
  272. package/src/console/ui/gates/findings.ts +19 -0
  273. package/src/console/ui/gates/fixture.ts +189 -0
  274. package/src/console/ui/gates/group.test.ts +30 -0
  275. package/src/console/ui/gates/group.ts +102 -0
  276. package/src/console/ui/gates/index.ts +47 -0
  277. package/src/console/ui/gates/search.test.ts +49 -0
  278. package/src/console/ui/gates/search.ts +115 -0
  279. package/src/console/ui/gates/types.ts +121 -0
  280. package/src/console/ui/overview/OverviewA11yView.tsx +152 -0
  281. package/src/console/ui/overview/a11y.test.tsx +113 -0
  282. package/src/console/ui/overview/busiest.ts +26 -0
  283. package/src/console/ui/overview/compose.ts +133 -0
  284. package/src/console/ui/overview/cost.test.ts +39 -0
  285. package/src/console/ui/overview/cost.ts +138 -0
  286. package/src/console/ui/overview/fixture.ts +161 -0
  287. package/src/console/ui/overview/golden.ts +57 -0
  288. package/src/console/ui/overview/index.ts +59 -0
  289. package/src/console/ui/overview/rank.test.ts +136 -0
  290. package/src/console/ui/overview/rank.ts +210 -0
  291. package/src/console/ui/overview/slo.test.ts +73 -0
  292. package/src/console/ui/overview/slo.ts +229 -0
  293. package/src/console/ui/overview/types.ts +121 -0
  294. package/src/console/ui/overview/verdict.ts +131 -0
  295. package/src/console/ui/plugins/PluginsA11yView.tsx +227 -0
  296. package/src/console/ui/plugins/a11y.test.tsx +97 -0
  297. package/src/console/ui/plugins/command.test.ts +22 -0
  298. package/src/console/ui/plugins/command.ts +27 -0
  299. package/src/console/ui/plugins/findings.ts +39 -0
  300. package/src/console/ui/plugins/fixture.ts +233 -0
  301. package/src/console/ui/plugins/group.test.ts +38 -0
  302. package/src/console/ui/plugins/group.ts +67 -0
  303. package/src/console/ui/plugins/index.ts +40 -0
  304. package/src/console/ui/plugins/search.ts +67 -0
  305. package/src/console/ui/plugins/types.ts +115 -0
  306. package/src/console/ui/runs/RunsA11yView.tsx +267 -0
  307. package/src/console/ui/runs/a11y.test.tsx +99 -0
  308. package/src/console/ui/runs/explain.test.ts +23 -0
  309. package/src/console/ui/runs/explain.ts +32 -0
  310. package/src/console/ui/runs/fixture.ts +189 -0
  311. package/src/console/ui/runs/group.test.ts +24 -0
  312. package/src/console/ui/runs/group.ts +85 -0
  313. package/src/console/ui/runs/histogram.test.ts +31 -0
  314. package/src/console/ui/runs/histogram.ts +85 -0
  315. package/src/console/ui/runs/index.ts +75 -0
  316. package/src/console/ui/runs/project.ts +122 -0
  317. package/src/console/ui/runs/query.test.ts +50 -0
  318. package/src/console/ui/runs/query.ts +283 -0
  319. package/src/console/ui/runs/search.test.ts +45 -0
  320. package/src/console/ui/runs/search.ts +141 -0
  321. package/src/console/ui/runs/trace-link.test.ts +30 -0
  322. package/src/console/ui/runs/trace-link.ts +99 -0
  323. package/src/console/ui/runs/types.ts +137 -0
  324. package/src/console/ui/shell/App.tsx +56 -0
  325. package/src/console/ui/shell/client.ts +1715 -0
  326. package/src/console/ui/shell/components/ui.tsx +110 -0
  327. package/src/console/ui/shell/index.html +12 -0
  328. package/src/console/ui/shell/layout/Shell.tsx +95 -0
  329. package/src/console/ui/shell/main.tsx +281 -0
  330. package/src/console/ui/shell/panels/Access.tsx +5 -0
  331. package/src/console/ui/shell/panels/Ai.tsx +5 -0
  332. package/src/console/ui/shell/panels/Architecture.tsx +5 -0
  333. package/src/console/ui/shell/panels/Channels.tsx +5 -0
  334. package/src/console/ui/shell/panels/Clock.tsx +5 -0
  335. package/src/console/ui/shell/panels/Diff.tsx +5 -0
  336. package/src/console/ui/shell/panels/Flows.tsx +5 -0
  337. package/src/console/ui/shell/panels/Gates.tsx +5 -0
  338. package/src/console/ui/shell/panels/Overview.tsx +6 -0
  339. package/src/console/ui/shell/panels/Plugins.tsx +5 -0
  340. package/src/console/ui/shell/panels/Runs.tsx +5 -0
  341. package/src/console/ui/shell/panels/Signals.tsx +5 -0
  342. package/src/console/ui/shell/panels/Store.tsx +5 -0
  343. package/src/console/ui/shell/panels/Traces.tsx +5 -0
  344. package/src/console/ui/shell/panels/Vault.tsx +5 -0
  345. package/src/console/ui/shell/panels/access/AccessPanel.tsx +813 -0
  346. package/src/console/ui/shell/panels/ai/AiPanel.tsx +563 -0
  347. package/src/console/ui/shell/panels/architecture/ArchitecturePanel.tsx +377 -0
  348. package/src/console/ui/shell/panels/channels/ChannelsPanel.tsx +454 -0
  349. package/src/console/ui/shell/panels/clock/ClockPanel.tsx +479 -0
  350. package/src/console/ui/shell/panels/diff/DiffPanel.tsx +275 -0
  351. package/src/console/ui/shell/panels/flows/ContractEditor.tsx +301 -0
  352. package/src/console/ui/shell/panels/flows/FlowDrawer.tsx +350 -0
  353. package/src/console/ui/shell/panels/flows/FlowsPanel.tsx +502 -0
  354. package/src/console/ui/shell/panels/gates/GatesPanel.tsx +482 -0
  355. package/src/console/ui/shell/panels/overview/OverviewPanel.tsx +428 -0
  356. package/src/console/ui/shell/panels/plugins/PluginsPanel.tsx +413 -0
  357. package/src/console/ui/shell/panels/runs/RunsPanel.tsx +649 -0
  358. package/src/console/ui/shell/panels/signals/SignalsPanel.tsx +840 -0
  359. package/src/console/ui/shell/panels/store/StorePanel.tsx +787 -0
  360. package/src/console/ui/shell/panels/traces/TracesPanel.tsx +539 -0
  361. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +449 -0
  362. package/src/console/ui/shell/plugin/Sandbox.tsx +29 -0
  363. package/src/console/ui/shell/setup/Wizard.tsx +189 -0
  364. package/src/console/ui/shell/styles.css +75 -0
  365. package/src/console/ui/signals/SignalsA11yView.tsx +276 -0
  366. package/src/console/ui/signals/a11y.test.tsx +99 -0
  367. package/src/console/ui/signals/confirmation.test.ts +39 -0
  368. package/src/console/ui/signals/confirmation.ts +63 -0
  369. package/src/console/ui/signals/dry-run.test.ts +18 -0
  370. package/src/console/ui/signals/dry-run.ts +36 -0
  371. package/src/console/ui/signals/durable.test.ts +22 -0
  372. package/src/console/ui/signals/durable.ts +41 -0
  373. package/src/console/ui/signals/findings.ts +33 -0
  374. package/src/console/ui/signals/fixture.ts +242 -0
  375. package/src/console/ui/signals/group.test.ts +32 -0
  376. package/src/console/ui/signals/group.ts +49 -0
  377. package/src/console/ui/signals/index.ts +52 -0
  378. package/src/console/ui/signals/monitor.test.ts +30 -0
  379. package/src/console/ui/signals/monitor.ts +79 -0
  380. package/src/console/ui/signals/schema-form.test.ts +34 -0
  381. package/src/console/ui/signals/schema-form.ts +112 -0
  382. package/src/console/ui/signals/search.ts +102 -0
  383. package/src/console/ui/signals/types.ts +71 -0
  384. package/src/console/ui/store/StoreA11yView.tsx +199 -0
  385. package/src/console/ui/store/a11y.test.tsx +99 -0
  386. package/src/console/ui/store/cache-view.test.ts +13 -0
  387. package/src/console/ui/store/cache-view.ts +36 -0
  388. package/src/console/ui/store/confirmation.test.ts +38 -0
  389. package/src/console/ui/store/confirmation.ts +55 -0
  390. package/src/console/ui/store/dry-run.test.ts +15 -0
  391. package/src/console/ui/store/dry-run.ts +29 -0
  392. package/src/console/ui/store/fixture.ts +163 -0
  393. package/src/console/ui/store/group.test.ts +21 -0
  394. package/src/console/ui/store/group.ts +41 -0
  395. package/src/console/ui/store/index.ts +40 -0
  396. package/src/console/ui/store/search.ts +78 -0
  397. package/src/console/ui/store/types.ts +65 -0
  398. package/src/console/ui/store/will-not-fire.test.ts +19 -0
  399. package/src/console/ui/store/will-not-fire.ts +40 -0
  400. package/src/console/ui/traces/TracesA11yView.tsx +200 -0
  401. package/src/console/ui/traces/a11y.test.tsx +97 -0
  402. package/src/console/ui/traces/chain.test.ts +61 -0
  403. package/src/console/ui/traces/chain.ts +144 -0
  404. package/src/console/ui/traces/critical-path.test.ts +24 -0
  405. package/src/console/ui/traces/critical-path.ts +59 -0
  406. package/src/console/ui/traces/filter.test.ts +78 -0
  407. package/src/console/ui/traces/filter.ts +112 -0
  408. package/src/console/ui/traces/fixture.ts +126 -0
  409. package/src/console/ui/traces/fold.test.ts +67 -0
  410. package/src/console/ui/traces/fold.ts +170 -0
  411. package/src/console/ui/traces/index.ts +64 -0
  412. package/src/console/ui/traces/mini.ts +65 -0
  413. package/src/console/ui/traces/replay.test.ts +24 -0
  414. package/src/console/ui/traces/replay.ts +35 -0
  415. package/src/console/ui/traces/sampling.test.ts +26 -0
  416. package/src/console/ui/traces/sampling.ts +66 -0
  417. package/src/console/ui/traces/search.ts +148 -0
  418. package/src/console/ui/traces/tier.ts +72 -0
  419. package/src/console/ui/traces/types.ts +125 -0
  420. package/src/console/ui/tsconfig.json +20 -0
  421. package/src/console/ui/vault/VaultA11yView.tsx +177 -0
  422. package/src/console/ui/vault/a11y.test.tsx +97 -0
  423. package/src/console/ui/vault/blast-radius.test.ts +35 -0
  424. package/src/console/ui/vault/blast-radius.ts +55 -0
  425. package/src/console/ui/vault/confirmation.test.ts +41 -0
  426. package/src/console/ui/vault/confirmation.ts +39 -0
  427. package/src/console/ui/vault/dormant.test.ts +37 -0
  428. package/src/console/ui/vault/dormant.ts +28 -0
  429. package/src/console/ui/vault/export-safe.test.ts +30 -0
  430. package/src/console/ui/vault/export-safe.ts +65 -0
  431. package/src/console/ui/vault/fixture.ts +75 -0
  432. package/src/console/ui/vault/group.test.ts +25 -0
  433. package/src/console/ui/vault/group.ts +50 -0
  434. package/src/console/ui/vault/index.ts +45 -0
  435. package/src/console/ui/vault/search.ts +62 -0
  436. package/src/console/ui/vault/types.ts +57 -0
  437. package/src/console/ui/vite.config.ts +84 -0
  438. package/src/console/xss.gate.test.ts +118 -0
  439. package/src/docker/compose.ts +226 -0
  440. package/src/docker/credentials.ts +25 -0
  441. package/src/docker/derive.ts +83 -0
  442. package/src/docker/docker.test.ts +185 -0
  443. package/src/docker/dockerfile.integration.test.ts +170 -0
  444. package/src/docker/dockerfile.ts +39 -0
  445. package/src/docker/helpers.ts +103 -0
  446. package/src/docker/index.ts +52 -0
  447. package/src/docker/pin.ts +90 -0
  448. package/src/docker/recipes/index.ts +33 -0
  449. package/src/docker/recipes/postgres.ts +18 -0
  450. package/src/docker/recipes/redis.ts +18 -0
  451. package/src/docker/stack.integration.test.ts +140 -0
  452. package/src/docker/stack.ts +62 -0
  453. package/src/docker/types.ts +134 -0
  454. package/src/drivers/ai-anthropic.ts +128 -0
  455. package/src/drivers/ai-mock.ts +82 -0
  456. package/src/drivers/ai-openai-compatible.ts +139 -0
  457. package/src/drivers/ai-providers.test.ts +163 -0
  458. package/src/drivers/ai-types.ts +83 -0
  459. package/src/drivers/bun-native-completeness.test.ts +155 -0
  460. package/src/drivers/channel-console.ts +118 -0
  461. package/src/drivers/channel-fcm.ts +96 -0
  462. package/src/drivers/channel-resend.ts +27 -0
  463. package/src/drivers/channel-smtp.ts +33 -0
  464. package/src/drivers/channel-types.ts +160 -0
  465. package/src/drivers/channel-unifonic.ts +79 -0
  466. package/src/drivers/channel-wa-cloud.ts +84 -0
  467. package/src/drivers/channel-webpush.ts +317 -0
  468. package/src/drivers/conformance.test.ts +89 -0
  469. package/src/drivers/conformance.ts +162 -0
  470. package/src/drivers/fs.ts +73 -0
  471. package/src/drivers/index.ts +194 -0
  472. package/src/drivers/kv-lua.ts +222 -0
  473. package/src/drivers/memory.ts +472 -0
  474. package/src/drivers/pgvector.ts +130 -0
  475. package/src/drivers/postgres.ts +221 -0
  476. package/src/drivers/redis.ts +254 -0
  477. package/src/drivers/s3.ts +129 -0
  478. package/src/drivers/signal-engine.ts +690 -0
  479. package/src/drivers/signal-memory.ts +27 -0
  480. package/src/drivers/signal-nats.ts +131 -0
  481. package/src/drivers/signal-postgres.ts +1089 -0
  482. package/src/drivers/signal-redis.ts +275 -0
  483. package/src/drivers/signal-types.ts +328 -0
  484. package/src/drivers/sqlite.ts +53 -0
  485. package/src/drivers/types.ts +334 -0
  486. package/src/drivers/vault-dotenv-parse.ts +56 -0
  487. package/src/drivers/vault-env.ts +101 -0
  488. package/src/drivers/vault-infisical.ts +61 -0
  489. package/src/drivers/vault-managed.ts +42 -0
  490. package/src/drivers/vault-memory.ts +38 -0
  491. package/src/drivers/vault-openbao.ts +80 -0
  492. package/src/drivers/vault-sops.ts +252 -0
  493. package/src/drivers/vault-types.ts +84 -0
  494. package/src/elements/ai/declare.ts +206 -0
  495. package/src/elements/ai/eval.ts +113 -0
  496. package/src/elements/ai/pii.ts +108 -0
  497. package/src/elements/ai/runtime.ts +540 -0
  498. package/src/elements/ai/schema.ts +194 -0
  499. package/src/elements/ai.test.ts +393 -0
  500. package/src/elements/ai.ts +56 -0
  501. package/src/elements/channel/consent.ts +81 -0
  502. package/src/elements/channel/costs.test.ts +62 -0
  503. package/src/elements/channel/costs.ts +130 -0
  504. package/src/elements/channel/declare.ts +141 -0
  505. package/src/elements/channel/email-auth.test.ts +47 -0
  506. package/src/elements/channel/email-auth.ts +109 -0
  507. package/src/elements/channel/locale.ts +97 -0
  508. package/src/elements/channel/mask.test.ts +19 -0
  509. package/src/elements/channel/mask.ts +60 -0
  510. package/src/elements/channel/mime.ts +23 -0
  511. package/src/elements/channel/outcomes.test.ts +40 -0
  512. package/src/elements/channel/outcomes.ts +126 -0
  513. package/src/elements/channel/receipts.ts +121 -0
  514. package/src/elements/channel/runtime.ts +500 -0
  515. package/src/elements/channel/suppression.ts +136 -0
  516. package/src/elements/channel.test.ts +232 -0
  517. package/src/elements/channel.ts +109 -0
  518. package/src/elements/clock/actions.ts +170 -0
  519. package/src/elements/clock/declare.ts +58 -0
  520. package/src/elements/clock/dst.ts +197 -0
  521. package/src/elements/clock/durable.ts +140 -0
  522. package/src/elements/clock/duration.ts +29 -0
  523. package/src/elements/clock/health.test.ts +103 -0
  524. package/src/elements/clock/health.ts +112 -0
  525. package/src/elements/clock/leader.ts +127 -0
  526. package/src/elements/clock/reconcile.ts +241 -0
  527. package/src/elements/clock/runtime.ts +243 -0
  528. package/src/elements/clock/schedule.ts +262 -0
  529. package/src/elements/clock/time-travel.ts +53 -0
  530. package/src/elements/clock.test.ts +365 -0
  531. package/src/elements/clock.ts +80 -0
  532. package/src/elements/gate/declare.ts +134 -0
  533. package/src/elements/gate/permissions.ts +104 -0
  534. package/src/elements/gate/runtime.ts +177 -0
  535. package/src/elements/gate/strategies.ts +388 -0
  536. package/src/elements/gate.test.ts +145 -0
  537. package/src/elements/gate.ts +41 -0
  538. package/src/elements/index.ts +169 -0
  539. package/src/elements/signal/chaos-child.ts +47 -0
  540. package/src/elements/signal/declare.ts +77 -0
  541. package/src/elements/signal/dry-run-replay.test.ts +95 -0
  542. package/src/elements/signal/dry-run-write-isolation.test.ts +94 -0
  543. package/src/elements/signal/reconcile.test.ts +23 -0
  544. package/src/elements/signal/reconcile.ts +122 -0
  545. package/src/elements/signal/runtime.ts +94 -0
  546. package/src/elements/signal.test.ts +457 -0
  547. package/src/elements/signal.ts +26 -0
  548. package/src/elements/store/cache.ts +197 -0
  549. package/src/elements/store/classify.ts +125 -0
  550. package/src/elements/store/declare.ts +200 -0
  551. package/src/elements/store/files-policy.test.ts +31 -0
  552. package/src/elements/store/files-policy.ts +70 -0
  553. package/src/elements/store/replica.ts +68 -0
  554. package/src/elements/store/runtime.ts +393 -0
  555. package/src/elements/store/sql-condition.ts +205 -0
  556. package/src/elements/store/sql-session.ts +579 -0
  557. package/src/elements/store/table.ts +282 -0
  558. package/src/elements/store.test.ts +370 -0
  559. package/src/elements/store.ts +99 -0
  560. package/src/elements/vault/declare.ts +143 -0
  561. package/src/elements/vault/fingerprint.ts +30 -0
  562. package/src/elements/vault/redact.ts +52 -0
  563. package/src/elements/vault/runtime.ts +461 -0
  564. package/src/elements/vault.test.ts +296 -0
  565. package/src/elements/vault.ts +44 -0
  566. package/src/index.ts +183 -0
  567. package/src/kernel/adopt-routes.ts +194 -0
  568. package/src/kernel/app.ts +1068 -0
  569. package/src/kernel/auth-resolve.ts +93 -0
  570. package/src/kernel/boot.test.ts +173 -0
  571. package/src/kernel/boot.ts +439 -0
  572. package/src/kernel/budget-entry.ts +55 -0
  573. package/src/kernel/budget.test.ts +19 -0
  574. package/src/kernel/call.test.ts +83 -0
  575. package/src/kernel/capability.ts +113 -0
  576. package/src/kernel/dry-run.test.ts +64 -0
  577. package/src/kernel/dry-run.ts +171 -0
  578. package/src/kernel/effects.test.ts +89 -0
  579. package/src/kernel/effects.ts +140 -0
  580. package/src/kernel/errors.registry.test.ts +39 -0
  581. package/src/kernel/errors.ts +225 -0
  582. package/src/kernel/flow.test.ts +154 -0
  583. package/src/kernel/flow.ts +290 -0
  584. package/src/kernel/fx.test.ts +245 -0
  585. package/src/kernel/fx.ts +996 -0
  586. package/src/kernel/hook-timing.test.ts +72 -0
  587. package/src/kernel/hook-timing.ts +190 -0
  588. package/src/kernel/hooks.test.ts +181 -0
  589. package/src/kernel/hooks.ts +275 -0
  590. package/src/kernel/index.ts +278 -0
  591. package/src/kernel/journal.ts +405 -0
  592. package/src/kernel/journey.ts +75 -0
  593. package/src/kernel/on.ts +74 -0
  594. package/src/kernel/pipeline.test.ts +349 -0
  595. package/src/kernel/pipeline.ts +245 -0
  596. package/src/kernel/plug.ts +82 -0
  597. package/src/kernel/plugin/capabilities.test.ts +84 -0
  598. package/src/kernel/plugin/conflicts.test.ts +106 -0
  599. package/src/kernel/plugin/decorate.test.ts +111 -0
  600. package/src/kernel/plugin/scoping.test.ts +138 -0
  601. package/src/kernel/plugin.ts +485 -0
  602. package/src/kernel/rate-limit.ts +25 -0
  603. package/src/kernel/registry.ts +398 -0
  604. package/src/kernel/router.test.ts +114 -0
  605. package/src/kernel/router.ts +556 -0
  606. package/src/kernel/routing-budget.test.ts +19 -0
  607. package/src/kernel/run-telemetry.ts +70 -0
  608. package/src/kernel/triggers.ts +324 -0
  609. package/src/kernel/unit.ts +88 -0
  610. package/src/manifest/diff.test.ts +866 -0
  611. package/src/manifest/diff.ts +1670 -0
  612. package/src/manifest/fixtures/base.manifest.json +112 -0
  613. package/src/manifest/fixtures/skyport.excerpt.json +51 -0
  614. package/src/manifest/fixtures/skyport.manifest.json +153 -0
  615. package/src/manifest/index.ts +27 -0
  616. package/src/manifest/types.ts +358 -0
  617. package/src/manifest/undeclared.test.ts +82 -0
  618. package/src/manifest/undeclared.ts +65 -0
  619. package/src/manifest/validate.test.ts +105 -0
  620. package/src/manifest/validate.ts +174 -0
  621. package/src/mcp/authorization.ts +340 -0
  622. package/src/mcp/confirmation.ts +174 -0
  623. package/src/mcp/data.ts +90 -0
  624. package/src/mcp/index.ts +80 -0
  625. package/src/mcp/injection.gate.test.ts +176 -0
  626. package/src/mcp/mcp.test.ts +348 -0
  627. package/src/mcp/protocol.ts +172 -0
  628. package/src/mcp/server.ts +283 -0
  629. package/src/mcp/session.ts +139 -0
  630. package/src/mcp/tools.ts +391 -0
  631. package/src/plugins/catalogue.test.ts +48 -0
  632. package/src/plugins/catalogue.ts +113 -0
  633. package/src/plugins/node-import-scan.test.ts +28 -0
  634. package/src/plugins/node-import-scan.ts +98 -0
  635. package/src/plugins/supply-chain.test.ts +48 -0
  636. package/src/plugins/supply-chain.ts +460 -0
  637. package/src/release/index.ts +30 -0
  638. package/src/release/limits.ts +20 -0
  639. package/src/release/measure.ts +443 -0
  640. package/src/release/publish.ts +56 -0
  641. package/src/runs/bench.test.ts +93 -0
  642. package/src/runs/collect.ts +144 -0
  643. package/src/runs/drivers/clickhouse.ts +108 -0
  644. package/src/runs/drivers/files.ts +201 -0
  645. package/src/runs/drivers/memory.ts +94 -0
  646. package/src/runs/drivers/postgres.ts +110 -0
  647. package/src/runs/duckdb.ts +99 -0
  648. package/src/runs/index.ts +82 -0
  649. package/src/runs/outlier.ts +180 -0
  650. package/src/runs/parquet.ts +210 -0
  651. package/src/runs/privacy.ts +49 -0
  652. package/src/runs/runs.test.ts +324 -0
  653. package/src/runs/runtime.ts +229 -0
  654. package/src/runs/shred.ts +243 -0
  655. package/src/runs/telemetry.ts +10 -0
  656. package/src/runs/types.ts +179 -0
  657. package/src/runtime/bun.ts +140 -0
  658. package/src/runtime/cold-start.bench.ts +20 -0
  659. package/src/runtime/index.ts +46 -0
  660. package/src/runtime/primitives.ts +242 -0
  661. package/src/runtime/security.test.ts +170 -0
  662. package/src/runtime/security.ts +168 -0
  663. package/src/runtime/serve.test.ts +223 -0
  664. package/src/runtime/types.ts +217 -0
  665. package/src/runtime/web-standard.ts +77 -0
  666. package/src/test/create-test-app.test.ts +161 -0
  667. package/src/test/create-test-app.ts +533 -0
  668. package/src/test/index.ts +21 -0
  669. package/src/test/provisions.integration.test.ts +174 -0
  670. package/src/upgrade/codemods.test.ts +58 -0
  671. package/src/upgrade/codemods.ts +83 -0
  672. package/src/validation/index.ts +23 -0
  673. package/src/validation/standard-schema.test.ts +96 -0
  674. package/src/validation/standard-schema.ts +316 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Consent / opt-out — Channel will not send without consent.
3
+ */
4
+
5
+ import type { ChannelMedium } from "../../manifest/types.ts";
6
+
7
+ /** Opt-out record. */
8
+ export interface OptOut {
9
+ readonly subject: string;
10
+ readonly medium: ChannelMedium | "all";
11
+ readonly at: number;
12
+ }
13
+
14
+ /** Consent store surface. */
15
+ export interface ConsentStore {
16
+ /**
17
+ * Whether the subject has opted out of this medium.
18
+ *
19
+ * @param subject - User / address id
20
+ * @param medium - Channel medium
21
+ */
22
+ isOptedOut(subject: string, medium: ChannelMedium): boolean;
23
+ /**
24
+ * Record an opt-out.
25
+ *
26
+ * @param subject - User / address id
27
+ * @param medium - Medium or `all`
28
+ */
29
+ optOut(subject: string, medium: ChannelMedium | "all"): void;
30
+ /**
31
+ * Clear an opt-out (re-consent).
32
+ *
33
+ * @param subject - User / address id
34
+ * @param medium - Medium or `all`
35
+ */
36
+ optIn(subject: string, medium: ChannelMedium | "all"): void;
37
+ /** Snapshot of all opt-outs. */
38
+ list(): readonly OptOut[];
39
+ }
40
+
41
+ /**
42
+ * Create an in-memory consent store.
43
+ */
44
+ export function createConsentStore(): ConsentStore {
45
+ const rows: OptOut[] = [];
46
+
47
+ function matches(
48
+ subject: string,
49
+ medium: ChannelMedium,
50
+ ): boolean {
51
+ return rows.some(
52
+ (r) =>
53
+ r.subject === subject &&
54
+ (r.medium === "all" || r.medium === medium),
55
+ );
56
+ }
57
+
58
+ return {
59
+ isOptedOut(subject, medium) {
60
+ return matches(subject, medium);
61
+ },
62
+ optOut(subject, medium) {
63
+ if (
64
+ !rows.some((r) => r.subject === subject && r.medium === medium)
65
+ ) {
66
+ rows.push({ subject, medium, at: Date.now() });
67
+ }
68
+ },
69
+ optIn(subject, medium) {
70
+ for (let i = rows.length - 1; i >= 0; i--) {
71
+ const r = rows[i]!;
72
+ if (r.subject === subject && r.medium === medium) {
73
+ rows.splice(i, 1);
74
+ }
75
+ }
76
+ },
77
+ list() {
78
+ return [...rows];
79
+ },
80
+ };
81
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Fallback weekly cost delta (console §9.9 · §9.12).
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { fallbackWeeklyCostDelta } from "./costs.ts";
7
+ import type { DeliveryReceipt } from "./receipts.ts";
8
+
9
+ describe("fallbackWeeklyCostDelta", () => {
10
+ test("projects fallback rate into a weekly USD delta", () => {
11
+ const weekStart = Date.UTC(2026, 6, 20);
12
+ const receipts: DeliveryReceipt[] = [
13
+ {
14
+ id: "1",
15
+ template: "otp-code",
16
+ to: "a",
17
+ medium: "any",
18
+ status: "fallback",
19
+ attempts: [
20
+ { driverId: "wa-cloud", ok: false, at: weekStart + 1 },
21
+ { driverId: "unifonic", ok: true, at: weekStart + 2 },
22
+ ],
23
+ at: weekStart + 3,
24
+ },
25
+ {
26
+ id: "2",
27
+ template: "otp-code",
28
+ to: "b",
29
+ medium: "whatsapp",
30
+ status: "sent",
31
+ attempts: [{ driverId: "wa-cloud", ok: true, at: weekStart + 4 }],
32
+ at: weekStart + 5,
33
+ },
34
+ {
35
+ id: "3",
36
+ template: "otp-code",
37
+ to: "c",
38
+ medium: "any",
39
+ status: "fallback",
40
+ attempts: [
41
+ { driverId: "wa-cloud", ok: false, at: weekStart + 6 },
42
+ { driverId: "unifonic", ok: true, at: weekStart + 7 },
43
+ ],
44
+ at: weekStart + 8,
45
+ },
46
+ ];
47
+
48
+ const delta = fallbackWeeklyCostDelta(receipts, {
49
+ weekStartMs: weekStart,
50
+ weekEndMs: weekStart + 86_400_000 * 7,
51
+ costs: { whatsapp: 0.005, sms: 0.0075 },
52
+ });
53
+
54
+ expect(delta.totalCount).toBe(3);
55
+ expect(delta.fallbackCount).toBe(2);
56
+ expect(delta.fallbackRate).toBeCloseTo(2 / 3, 5);
57
+ // Each fallback: sms 0.0075 − whatsapp 0.005 = 0.0025
58
+ expect(delta.weeklyDeltaUsd).toBeCloseTo(0.005, 5);
59
+ expect(delta.primaryMedium).toBe("whatsapp");
60
+ expect(delta.fallbackMedium).toBe("sms");
61
+ });
62
+ });
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Channel medium unit costs — fallback rate as a weekly bill (console §9.9 · §9.12).
3
+ */
4
+
5
+ import type { ChannelMedium } from "../../manifest/types.ts";
6
+ import type { DeliveryReceipt } from "./receipts.ts";
7
+
8
+ /** USD per message by medium. */
9
+ export type MediumCosts = Readonly<Partial<Record<ChannelMedium, number>>>;
10
+
11
+ /**
12
+ * Default unit costs (USD / message). Overridable on the runtime.
13
+ * Rough public-cloud ballparks — the unit that matters is the weekly delta.
14
+ */
15
+ export const DEFAULT_MEDIUM_COSTS: MediumCosts = {
16
+ email: 0.0001,
17
+ sms: 0.0075,
18
+ whatsapp: 0.005,
19
+ push: 0.00005,
20
+ any: 0.005,
21
+ };
22
+
23
+ /** Weekly fallback cost projection. */
24
+ export interface FallbackWeeklyCostDelta {
25
+ /** Fraction of sends that used a fallback (0–1). */
26
+ readonly fallbackRate: number;
27
+ /** Count of fallback receipts in the window. */
28
+ readonly fallbackCount: number;
29
+ /** Total sends in the window. */
30
+ readonly totalCount: number;
31
+ /** Extra USD this week vs primary-medium-only. */
32
+ readonly weeklyDeltaUsd: number;
33
+ /** Primary (first attempted) medium for the sample. */
34
+ readonly primaryMedium: string;
35
+ /** Winning fallback medium (last success) when available. */
36
+ readonly fallbackMedium: string;
37
+ }
38
+
39
+ /**
40
+ * Cost of one medium.
41
+ *
42
+ * @param medium - Channel medium
43
+ * @param costs - Unit cost table
44
+ */
45
+ export function costOf(
46
+ medium: string,
47
+ costs: MediumCosts = DEFAULT_MEDIUM_COSTS,
48
+ ): number {
49
+ return costs[medium as ChannelMedium] ?? costs.any ?? 0.005;
50
+ }
51
+
52
+ /**
53
+ * Project fallback rate into a weekly cost delta vs primary-medium-only.
54
+ *
55
+ * For each fallback receipt: delta = cost(winner) − cost(primary attempt).
56
+ * Summed over the week window (not per-call — §9.12).
57
+ *
58
+ * @param receipts - Delivery receipts
59
+ * @param options - Costs + week window
60
+ */
61
+ export function fallbackWeeklyCostDelta(
62
+ receipts: readonly DeliveryReceipt[],
63
+ options: {
64
+ readonly costs?: MediumCosts;
65
+ readonly weekStartMs: number;
66
+ readonly weekEndMs?: number;
67
+ readonly now?: number;
68
+ },
69
+ ): FallbackWeeklyCostDelta {
70
+ const costs = options.costs ?? DEFAULT_MEDIUM_COSTS;
71
+ const end = options.weekEndMs ?? options.now ?? Date.now();
72
+ const inWeek = receipts.filter(
73
+ (r) => r.at >= options.weekStartMs && r.at <= end,
74
+ );
75
+ const fallbacks = inWeek.filter((r) => r.status === "fallback");
76
+
77
+ let weeklyDeltaUsd = 0;
78
+ let primaryMedium = "whatsapp";
79
+ let fallbackMedium = "sms";
80
+
81
+ for (const r of fallbacks) {
82
+ const first = r.attempts[0];
83
+ const winner =
84
+ [...r.attempts].reverse().find((a) => a.ok) ?? r.attempts.at(-1);
85
+ if (!first || !winner) continue;
86
+ const primary = mediumFromDriver(first.driverId, r.medium);
87
+ const fb = mediumFromDriver(winner.driverId, r.medium);
88
+ primaryMedium = primary;
89
+ fallbackMedium = fb;
90
+ weeklyDeltaUsd += costOf(fb, costs) - costOf(primary, costs);
91
+ }
92
+
93
+ // If winner is cheaper, delta can be negative — still report it.
94
+ const totalCount = inWeek.length;
95
+ const fallbackCount = fallbacks.length;
96
+ return {
97
+ fallbackRate: totalCount === 0 ? 0 : fallbackCount / totalCount,
98
+ fallbackCount,
99
+ totalCount,
100
+ weeklyDeltaUsd,
101
+ primaryMedium,
102
+ fallbackMedium,
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Best-effort map of driver id → medium for cost accounting.
108
+ *
109
+ * @param driverId - Driver / provider id
110
+ * @param receiptMedium - Receipt medium fallback
111
+ */
112
+ function mediumFromDriver(driverId: string, receiptMedium: string): string {
113
+ const id = driverId.toLowerCase();
114
+ if (id.includes("wa") || id.includes("whatsapp")) return "whatsapp";
115
+ if (id.includes("sms") || id.includes("unifonic") || id.includes("twilio")) {
116
+ return "sms";
117
+ }
118
+ if (id.includes("push") || id.includes("fcm") || id.includes("webpush")) {
119
+ return "push";
120
+ }
121
+ if (
122
+ id.includes("smtp") ||
123
+ id.includes("resend") ||
124
+ id.includes("ses") ||
125
+ id.includes("email")
126
+ ) {
127
+ return "email";
128
+ }
129
+ return receiptMedium === "any" ? "email" : receiptMedium;
130
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Channel declaration — templates with i18n, medium helpers.
3
+ *
4
+ * Physics: email · SMS · WhatsApp · push.
5
+ */
6
+
7
+ import type { ChannelMedium } from "../../manifest/types.ts";
8
+
9
+ /** Options for a medium binder (`channel.email`, …). */
10
+ export interface ChannelMediumOptions {
11
+ readonly from?: string;
12
+ readonly sender?: string;
13
+ }
14
+
15
+ /** Options for {@link channel.template} / medium `.template`. */
16
+ export interface ChannelTemplateOptions {
17
+ readonly medium?: ChannelMedium;
18
+ readonly locales?: string[];
19
+ readonly schema?: unknown;
20
+ readonly from?: string;
21
+ }
22
+
23
+ /** Declared channel template handle. */
24
+ export interface ChannelTemplateDecl {
25
+ readonly kind: "template";
26
+ readonly name: string;
27
+ readonly medium: ChannelMedium;
28
+ readonly locales?: string[];
29
+ readonly schema?: unknown;
30
+ readonly from?: string;
31
+ }
32
+
33
+ /** Medium binder that can declare templates. */
34
+ export interface ChannelMediumBinder {
35
+ readonly medium: ChannelMedium;
36
+ readonly from?: string;
37
+ /**
38
+ * Declare a template on this medium.
39
+ *
40
+ * @param name - Template id
41
+ * @param options - Schema / locales
42
+ */
43
+ template(
44
+ name: string,
45
+ options?: Omit<ChannelTemplateOptions, "medium" | "from">,
46
+ ): ChannelTemplateDecl;
47
+ }
48
+
49
+ /**
50
+ * Create a medium binder.
51
+ *
52
+ * @param medium - Medium id
53
+ * @param options - Default from / sender
54
+ */
55
+ function mediumBinder(
56
+ medium: ChannelMedium,
57
+ options: ChannelMediumOptions = {},
58
+ ): ChannelMediumBinder {
59
+ const from = options.from ?? options.sender;
60
+ return {
61
+ medium,
62
+ ...(from !== undefined ? { from } : {}),
63
+ template(name, opts = {}) {
64
+ return {
65
+ kind: "template",
66
+ name,
67
+ medium,
68
+ ...(opts.locales ? { locales: opts.locales } : {}),
69
+ ...(opts.schema !== undefined ? { schema: opts.schema } : {}),
70
+ ...(from !== undefined ? { from } : {}),
71
+ };
72
+ },
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Shape of the {@link channel} element namespace.
78
+ */
79
+ export interface ChannelNamespace {
80
+ /**
81
+ * Medium-agnostic template.
82
+ *
83
+ * @param name - Template id
84
+ * @param options - Medium / locales / schema
85
+ */
86
+ template(name: string, options?: ChannelTemplateOptions): ChannelTemplateDecl;
87
+ /** Email medium binder. */
88
+ email(options?: ChannelMediumOptions): ChannelMediumBinder;
89
+ /** SMS medium binder. */
90
+ sms(options?: ChannelMediumOptions): ChannelMediumBinder;
91
+ /** WhatsApp medium binder. */
92
+ whatsapp(options?: ChannelMediumOptions): ChannelMediumBinder;
93
+ /** Push medium binder. */
94
+ push(options?: ChannelMediumOptions): ChannelMediumBinder;
95
+ }
96
+
97
+ /**
98
+ * Channel element namespace.
99
+ */
100
+ export const channel: ChannelNamespace = {
101
+ /**
102
+ * Medium-agnostic template.
103
+ *
104
+ * @param name - Template id
105
+ * @param options - Medium / locales / schema
106
+ */
107
+ template(
108
+ name: string,
109
+ options: ChannelTemplateOptions = {},
110
+ ): ChannelTemplateDecl {
111
+ if (!name) throw new TypeError("channel.template: name is required");
112
+ return {
113
+ kind: "template",
114
+ name,
115
+ medium: options.medium ?? "email",
116
+ ...(options.locales ? { locales: options.locales } : {}),
117
+ ...(options.schema !== undefined ? { schema: options.schema } : {}),
118
+ ...(options.from !== undefined ? { from: options.from } : {}),
119
+ };
120
+ },
121
+
122
+ /** Email medium binder. */
123
+ email(options?: ChannelMediumOptions): ChannelMediumBinder {
124
+ return mediumBinder("email", options);
125
+ },
126
+
127
+ /** SMS medium binder. */
128
+ sms(options?: ChannelMediumOptions): ChannelMediumBinder {
129
+ return mediumBinder("sms", options);
130
+ },
131
+
132
+ /** WhatsApp medium binder. */
133
+ whatsapp(options?: ChannelMediumOptions): ChannelMediumBinder {
134
+ return mediumBinder("whatsapp", options);
135
+ },
136
+
137
+ /** Push medium binder. */
138
+ push(options?: ChannelMediumOptions): ChannelMediumBinder {
139
+ return mediumBinder("push", options);
140
+ },
141
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * SPF / DKIM / DMARC verification with injectable DNS.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { domainFromFrom, verifyEmailAuth } from "./email-auth.ts";
7
+
8
+ describe("verifyEmailAuth", () => {
9
+ test("extracts domain from From address", () => {
10
+ expect(domainFromFrom("noreply@skyport.dev")).toBe("skyport.dev");
11
+ });
12
+
13
+ test("classifies SPF DKIM DMARC from TXT records", async () => {
14
+ const result = await verifyEmailAuth("skyport.dev", {
15
+ dkimSelector: "oke",
16
+ now: () => 1,
17
+ async lookup(name) {
18
+ if (name === "skyport.dev") return ["v=spf1 include:_spf.google.com ~all"];
19
+ if (name === "oke._domainkey.skyport.dev") {
20
+ return ["v=DKIM1; k=rsa; p=MIIB"];
21
+ }
22
+ if (name === "_dmarc.skyport.dev") {
23
+ return ["v=DMARC1; p=quarantine; rua=mailto:d@skyport.dev"];
24
+ }
25
+ return [];
26
+ },
27
+ });
28
+ expect(result).toEqual({
29
+ domain: "skyport.dev",
30
+ spf: "pass",
31
+ dkim: "pass",
32
+ dmarc: "pass",
33
+ checkedAt: 1,
34
+ });
35
+ });
36
+
37
+ test("missing records are missing, not fail", async () => {
38
+ const result = await verifyEmailAuth("empty.test", {
39
+ async lookup() {
40
+ return [];
41
+ },
42
+ });
43
+ expect(result.spf).toBe("missing");
44
+ expect(result.dkim).toBe("missing");
45
+ expect(result.dmarc).toBe("missing");
46
+ });
47
+ });
@@ -0,0 +1,109 @@
1
+ /**
2
+ * SPF / DKIM / DMARC verification for sending domains (console §9.9).
3
+ *
4
+ * DNS is injected — the element never opens a side-channel.
5
+ */
6
+
7
+ /** Auth check result for one mechanism. */
8
+ export type AuthCheckStatus = "pass" | "fail" | "missing";
9
+
10
+ /** SPF / DKIM / DMARC verification result. */
11
+ export interface EmailAuthResult {
12
+ readonly domain: string;
13
+ readonly spf: AuthCheckStatus;
14
+ readonly dkim: AuthCheckStatus;
15
+ readonly dmarc: AuthCheckStatus;
16
+ readonly checkedAt: number;
17
+ }
18
+
19
+ /** Injectable DNS TXT lookup. */
20
+ export type DnsTxtLookup = (name: string) => Promise<readonly string[]>;
21
+
22
+ /** Options for {@link verifyEmailAuth}. */
23
+ export interface VerifyEmailAuthOptions {
24
+ readonly lookup: DnsTxtLookup;
25
+ readonly dkimSelector?: string;
26
+ readonly now?: () => number;
27
+ }
28
+
29
+ /**
30
+ * Extract the domain from a From address / domain string.
31
+ *
32
+ * @param from - `noreply@example.com` or `example.com`
33
+ */
34
+ export function domainFromFrom(from: string): string {
35
+ const trimmed = from.trim();
36
+ if (trimmed.includes("@")) {
37
+ return trimmed.split("@")[1]?.toLowerCase() ?? trimmed.toLowerCase();
38
+ }
39
+ // Strip display-name angle brackets: Name <a@b.c>
40
+ const angle = trimmed.match(/<([^>]+)>/);
41
+ if (angle?.[1]?.includes("@")) {
42
+ return angle[1].split("@")[1]!.toLowerCase();
43
+ }
44
+ return trimmed.toLowerCase();
45
+ }
46
+
47
+ /**
48
+ * Verify SPF, DKIM (selector), and DMARC TXT records for a domain.
49
+ *
50
+ * @param domainOrFrom - Domain or From address
51
+ * @param options - DNS lookup + optional DKIM selector
52
+ */
53
+ export async function verifyEmailAuth(
54
+ domainOrFrom: string,
55
+ options: VerifyEmailAuthOptions,
56
+ ): Promise<EmailAuthResult> {
57
+ const domain = domainFromFrom(domainOrFrom);
58
+ const selector = options.dkimSelector ?? "oke";
59
+ const now = options.now ?? (() => Date.now());
60
+
61
+ const [spfRecords, dkimRecords, dmarcRecords] = await Promise.all([
62
+ safeLookup(options.lookup, domain),
63
+ safeLookup(options.lookup, `${selector}._domainkey.${domain}`),
64
+ safeLookup(options.lookup, `_dmarc.${domain}`),
65
+ ]);
66
+
67
+ return {
68
+ domain,
69
+ spf: classifySpf(spfRecords),
70
+ dkim: classifyDkim(dkimRecords),
71
+ dmarc: classifyDmarc(dmarcRecords),
72
+ checkedAt: now(),
73
+ };
74
+ }
75
+
76
+ async function safeLookup(
77
+ lookup: DnsTxtLookup,
78
+ name: string,
79
+ ): Promise<readonly string[]> {
80
+ try {
81
+ return await lookup(name);
82
+ } catch {
83
+ return [];
84
+ }
85
+ }
86
+
87
+ function classifySpf(records: readonly string[]): AuthCheckStatus {
88
+ const joined = records.join(" ");
89
+ if (!joined) return "missing";
90
+ if (/v=spf1/i.test(joined)) {
91
+ return /[-~?]all|all/i.test(joined) ? "pass" : "fail";
92
+ }
93
+ return "missing";
94
+ }
95
+
96
+ function classifyDkim(records: readonly string[]): AuthCheckStatus {
97
+ const joined = records.join(" ");
98
+ if (!joined) return "missing";
99
+ return /v=DKIM1/i.test(joined) || /p=/i.test(joined) ? "pass" : "fail";
100
+ }
101
+
102
+ function classifyDmarc(records: readonly string[]): AuthCheckStatus {
103
+ const joined = records.join(" ");
104
+ if (!joined) return "missing";
105
+ if (/v=DMARC1/i.test(joined)) {
106
+ return /p=(none|quarantine|reject)/i.test(joined) ? "pass" : "fail";
107
+ }
108
+ return "missing";
109
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Locale resolution chain — profile → Accept-Language → default (console §9.9).
3
+ */
4
+
5
+ /** One step in the locale resolution chain. */
6
+ export type LocaleChainStep =
7
+ | `profile:${string}`
8
+ | `accept-language:${string}`
9
+ | `default:${string}`;
10
+
11
+ /** Result of {@link resolveLocale}. */
12
+ export interface LocaleResolution {
13
+ readonly locale: string;
14
+ readonly chain: readonly LocaleChainStep[];
15
+ }
16
+
17
+ /** Inputs for locale resolution. */
18
+ export interface ResolveLocaleOptions {
19
+ readonly profileLocale?: string;
20
+ readonly acceptLanguage?: string;
21
+ readonly defaultLocale?: string;
22
+ /** Explicit override (wins; still recorded in the chain when provided). */
23
+ readonly locale?: string;
24
+ }
25
+
26
+ /**
27
+ * Whether a locale is written right-to-left.
28
+ *
29
+ * @param locale - BCP 47 locale tag
30
+ */
31
+ export function isRtlLocale(locale: string): boolean {
32
+ const base = locale.toLowerCase().split("-")[0] ?? locale;
33
+ return (
34
+ base === "ar" ||
35
+ base === "he" ||
36
+ base === "fa" ||
37
+ base === "ur" ||
38
+ base === "yi"
39
+ );
40
+ }
41
+
42
+ /**
43
+ * Parse the first language tag from an Accept-Language header.
44
+ *
45
+ * @param header - Raw Accept-Language value
46
+ */
47
+ export function parseAcceptLanguage(header: string | undefined): string | undefined {
48
+ if (!header?.trim()) return undefined;
49
+ const first = header.split(",")[0]?.trim();
50
+ if (!first) return undefined;
51
+ const tag = first.split(";")[0]?.trim();
52
+ return tag || undefined;
53
+ }
54
+
55
+ /**
56
+ * Resolve locale with a full chain for the Console to display.
57
+ *
58
+ * Precedence: explicit `locale` → profile → Accept-Language → default.
59
+ *
60
+ * @param options - Resolution inputs
61
+ */
62
+ export function resolveLocale(
63
+ options: ResolveLocaleOptions = {},
64
+ ): LocaleResolution {
65
+ const defaultLocale = options.defaultLocale ?? "en";
66
+ const chain: LocaleChainStep[] = [];
67
+
68
+ if (options.locale?.trim()) {
69
+ const locale = options.locale.trim();
70
+ chain.push(`profile:${locale}`);
71
+ return { locale, chain };
72
+ }
73
+
74
+ if (options.profileLocale?.trim()) {
75
+ const locale = options.profileLocale.trim();
76
+ chain.push(`profile:${locale}`);
77
+ return { locale, chain };
78
+ }
79
+
80
+ const fromHeader = parseAcceptLanguage(options.acceptLanguage);
81
+ if (fromHeader) {
82
+ chain.push(`accept-language:${fromHeader}`);
83
+ return { locale: fromHeader, chain };
84
+ }
85
+
86
+ chain.push(`default:${defaultLocale}`);
87
+ return { locale: defaultLocale, chain };
88
+ }
89
+
90
+ /**
91
+ * Format a locale chain for display.
92
+ *
93
+ * @param chain - Resolution steps
94
+ */
95
+ export function formatLocaleChain(chain: readonly LocaleChainStep[]): string {
96
+ return chain.join(" → ");
97
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Recipient PII masking.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { maskRecipient } from "./mask.ts";
7
+
8
+ describe("maskRecipient", () => {
9
+ test("masks email local + domain", () => {
10
+ expect(maskRecipient("alice@example.com")).toBe("a***@e***.com");
11
+ });
12
+
13
+ test("masks phone keeping prefix and last digits", () => {
14
+ const masked = maskRecipient("+966501234567");
15
+ expect(masked.startsWith("+966")).toBe(true);
16
+ expect(masked.endsWith("567")).toBe(true);
17
+ expect(masked).toContain("***");
18
+ });
19
+ });