hecks 3.9.1 → 3.11.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 (504) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
  3. data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
  4. data/exe/hecks +4 -4
  5. data/lib/hecks/adapters/driven/d1.rb +2 -10
  6. data/lib/hecks/adapters/driven/heki.adapter +1 -1
  7. data/lib/hecks/adapters/driven/heki.rb +2 -12
  8. data/lib/hecks/adapters/driven/lambda.adapter +1 -1
  9. data/lib/hecks/adapters/driven/lambda.rb +3 -18
  10. data/lib/hecks/adapters/driven/postgres.rb +4 -11
  11. data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
  12. data/lib/hecks/adapters/driven/sqlite.rb +2 -10
  13. data/lib/hecks/adapters/driving/admission.rb +46 -0
  14. data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
  15. data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
  16. data/lib/hecks/adapters/driving/cli.rb +127 -0
  17. data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
  18. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
  19. data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
  20. data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
  21. data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
  22. data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
  23. data/lib/hecks/adapters/driving/command_request.rb +121 -0
  24. data/lib/hecks/adapters/driving/handle.rb +214 -0
  25. data/lib/hecks/adapters/driving/json.rb +136 -0
  26. data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
  27. data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
  28. data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
  29. data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
  30. data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
  31. data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
  32. data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
  33. data/lib/hecks/adapters/driving/mcp.rb +159 -0
  34. data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
  35. data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
  36. data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
  37. data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
  38. data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
  39. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
  40. data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
  41. data/lib/hecks/adapters/driving/ruby.rb +83 -0
  42. data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
  43. data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
  44. data/lib/hecks/behaviors/expectations/boot.rb +1 -1
  45. data/lib/hecks/bench/ruby_runner.rb +1 -1
  46. data/lib/hecks/bluebook/ask_resolution.rb +130 -0
  47. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
  48. data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
  49. data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
  50. data/lib/hecks/bluebook/assembly/marks.rb +1 -0
  51. data/lib/hecks/bluebook/attribute.rb +1 -1
  52. data/lib/hecks/bluebook/behaviour/command.rb +13 -8
  53. data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
  54. data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
  55. data/lib/hecks/bluebook/capabilities.rb +91 -9
  56. data/lib/hecks/bluebook/domain_port.rb +16 -4
  57. data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
  58. data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
  59. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
  60. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
  62. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
  63. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
  64. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
  65. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  66. data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
  67. data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
  68. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
  69. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
  70. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
  71. data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
  72. data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
  73. data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
  74. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
  77. data/lib/hecks/bluebook/expression/projection.json +36 -0
  78. data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
  79. data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
  80. data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
  81. data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
  82. data/lib/hecks/bluebook/expression/resolver.rb +20 -1
  83. data/lib/hecks/bluebook/hecksagon.rb +23 -3
  84. data/lib/hecks/bluebook/lifecycle.rb +22 -3
  85. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
  86. data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
  88. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
  90. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
  91. data/lib/hecks/bluebook/meta_validator.rb +12 -12
  92. data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
  93. data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
  94. data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
  95. data/lib/hecks/bluebook/model_check.rb +8 -2
  96. data/lib/hecks/bluebook/policy.rb +4 -2
  97. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  98. data/lib/hecks/bluebook/synthesizer.rb +2 -0
  99. data/lib/hecks/bluebook.rb +1 -0
  100. data/lib/hecks/cli/command_table.rb +1 -1
  101. data/lib/hecks/cli/console.rb +2 -1
  102. data/lib/hecks/cli/document.rb +1 -1
  103. data/lib/hecks/cli/launcher_words.rb +4 -4
  104. data/lib/hecks/cli/mcp.rb +4 -4
  105. data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
  106. data/lib/hecks/cli/project_cli/templates.rb +5 -5
  107. data/lib/hecks/cli/project_cli.rb +9 -6
  108. data/lib/hecks/cli/run.rb +2 -2
  109. data/lib/hecks/cli/smoke_http/checks.rb +4 -1
  110. data/lib/hecks/cli/smoke_http/transport.rb +3 -3
  111. data/lib/hecks/cli/smoke_http.rb +3 -0
  112. data/lib/hecks/codemod.rb +5 -2
  113. data/lib/hecks/corpus/routes.rb +7 -0
  114. data/lib/hecks/corpus/rust.rb +3 -2
  115. data/lib/hecks/deploy/bluebook/deploy.bluebook +124 -2
  116. data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
  117. data/lib/hecks/deploy/oidc.json +16 -3
  118. data/lib/hecks/doc/reference/rendering.rb +6 -3
  119. data/lib/hecks/forms/app/commands.rb +8 -7
  120. data/lib/hecks/forms/app/queries.rb +2 -1
  121. data/lib/hecks/forms/app.rb +1 -1
  122. data/lib/hecks/forms/record_renderer.rb +1 -1
  123. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  124. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  125. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  126. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  127. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  128. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  129. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  130. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  131. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  132. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  133. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  134. data/lib/hecks/gate/stages.yml +4 -10
  135. data/lib/hecks/grammar/evolve.rb +1 -1
  136. data/lib/hecks/grammar/expression_operators.json +372 -0
  137. data/lib/hecks/grammar/operators.rb +3 -1
  138. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  139. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  140. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  141. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  142. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  143. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  144. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  145. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  146. data/lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb +51 -0
  147. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  148. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
  149. data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
  150. data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
  151. data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
  152. data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
  153. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
  154. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
  155. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
  156. data/lib/hecks/hecks/adapters/local_files.rb +2 -2
  157. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
  158. data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
  159. data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
  160. data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
  161. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
  162. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
  163. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
  164. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
  165. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
  166. data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
  167. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
  168. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
  169. data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
  170. data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
  171. data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
  172. data/lib/hecks/hecks/adapters/site_toolchain/role_prober.rb +78 -0
  173. data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
  174. data/lib/hecks/hecks/adapters/terminal.rb +13 -11
  175. data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
  176. data/lib/hecks/hecks/codebase.behaviors +13 -1
  177. data/lib/hecks/hecks/codebase.bluebook +239 -5
  178. data/lib/hecks/hecks/custodian.behaviors +24 -6
  179. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  180. data/lib/hecks/hecks/deploy.behaviors +1 -1
  181. data/lib/hecks/hecks/deploy.world +1 -3
  182. data/lib/hecks/hecks/hecks.behaviors +1 -1
  183. data/lib/hecks/hecks/hecks.hecksagon +61 -18
  184. data/lib/hecks/hecks/hecks.world +11 -10
  185. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  186. data/lib/hecks/hecks/site.behaviors +1 -1
  187. data/lib/hecks/hecks/site.world +0 -1
  188. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  189. data/lib/hecks/indifferent_key.rb +20 -0
  190. data/lib/hecks/language/adapter.bluebook +13 -3
  191. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  192. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  193. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  194. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  195. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  196. data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
  197. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  198. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  199. data/lib/hecks/language/oidc.json +10 -0
  200. data/lib/hecks/language/port.bluebook +4 -4
  201. data/lib/hecks/literal.rb +12 -3
  202. data/lib/hecks/ports/key_vault.rb +7 -2
  203. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  204. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  205. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  206. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  207. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
  208. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  209. data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +15 -0
  210. data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
  211. data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
  212. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  213. data/lib/hecks/projections/deploy/shared.rb +5 -2
  214. data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
  215. data/lib/hecks/projections/model/deviations.rb +2 -2
  216. data/lib/hecks/projections/model.rb +2 -2
  217. data/lib/hecks/projections/oidc.rb +13 -1
  218. data/lib/hecks/projections/shape.rb +5 -2
  219. data/lib/hecks/projections/site/cms_editor/attributes.rb +95 -0
  220. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  221. data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
  222. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  223. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  224. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  225. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  226. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  227. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  228. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  229. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  230. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  231. data/lib/hecks/projections/site/cms_editor/media.rb +85 -0
  232. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  233. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  234. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  235. data/lib/hecks/projections/site/cms_editor/pretty.rb +40 -0
  236. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  237. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  238. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  239. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  240. data/lib/hecks/projections/site/cms_editor/schema.rb +161 -0
  241. data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
  242. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  243. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
  244. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
  245. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  246. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +87 -0
  247. data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
  248. data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
  249. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  250. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  251. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  252. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  253. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  254. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  255. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  256. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  258. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  259. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  261. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  262. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  265. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +133 -0
  266. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  268. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +43 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
  271. data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
  272. data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +214 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  278. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +123 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
  281. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  282. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +275 -0
  284. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
  285. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  286. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +92 -0
  287. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  288. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  289. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +63 -0
  290. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
  291. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  292. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  293. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  294. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  295. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  296. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  297. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  298. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  299. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  300. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  301. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  302. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +17 -0
  303. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  304. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  305. data/lib/hecks/projections/site/cms_editor.rb +133 -0
  306. data/lib/hecks/projections/site/role_probe.rb +74 -0
  307. data/lib/hecks/projector/cli_audience.rb +2 -1
  308. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  309. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  310. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  311. data/lib/hecks/projector/docs_projector.rb +1 -1
  312. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  313. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  314. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  315. data/lib/hecks/projector/target.rb +11 -0
  316. data/lib/hecks/projector.rb +32 -5
  317. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  318. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  319. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  320. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  321. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  322. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  323. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  324. data/lib/hecks/release/approval.rb +64 -0
  325. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  326. data/lib/hecks/router/namespace_installer.rb +16 -16
  327. data/lib/hecks/router.rb +1 -1
  328. data/lib/hecks/runtime/best_effort.rb +22 -0
  329. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  330. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  331. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  332. data/lib/hecks/runtime/dispatcher.rb +4 -4
  333. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  334. data/lib/hecks/runtime/event.rb +5 -0
  335. data/lib/hecks/runtime/identity.rb +7 -6
  336. data/lib/hecks/runtime/interpreting.rb +2 -1
  337. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  338. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  339. data/lib/hecks/runtime/loader.rb +10 -10
  340. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  341. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  342. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  343. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  344. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  345. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  346. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  347. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  348. data/lib/hecks/runtime/registry/merging.rb +1 -1
  349. data/lib/hecks/runtime/registry/providers.rb +16 -0
  350. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  351. data/lib/hecks/runtime/registry/verification.rb +4 -1
  352. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  353. data/lib/hecks/runtime/registry.rb +1 -1
  354. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  355. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  356. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  357. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  358. data/lib/hecks/runtime/tenant_check.rb +5 -4
  359. data/lib/hecks/runtime/value/coercion.rb +2 -2
  360. data/lib/hecks/runtime/value/validation.rb +16 -4
  361. data/lib/hecks/runtime.rb +9 -9
  362. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  363. data/lib/hecks/rust_build/wasm.rb +1 -1
  364. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  365. data/lib/hecks/rust_build.rb +11 -0
  366. data/lib/hecks/served_options.rb +53 -0
  367. data/lib/hecks/site/bluebook/site.bluebook +29 -1
  368. data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
  369. data/lib/hecks/storehouse/audit.rb +1 -1
  370. data/lib/hecks/storehouse/dispatching.rb +8 -6
  371. data/lib/hecks/storehouse/introspection.rb +2 -2
  372. data/lib/hecks/storehouse/querying.rb +6 -4
  373. data/lib/hecks/storehouse.rb +4 -4
  374. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  375. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  376. data/lib/hecks/tools/lanes/rows.rb +6 -0
  377. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  378. data/lib/hecks/tools/regeneration_run.rb +11 -4
  379. data/lib/hecks/tools/site_routes/cli.rb +1 -1
  380. data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
  381. data/lib/hecks/tools/site_routes.rb +14 -7
  382. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  383. data/lib/hecks/tools/tools_doc.rb +4 -4
  384. data/lib/hecks/version.rb +1 -1
  385. data/lib/hecks/vocabulary.rb +4 -4
  386. data/lib/hecks.rb +15 -15
  387. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  388. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  389. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  390. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  391. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  392. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  393. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  394. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  395. data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
  396. data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
  397. data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
  398. data/rust/.gitignore +6 -0
  399. data/rust/Cargo.toml +9 -1
  400. data/rust/build/src/binding_pass.rs +103 -0
  401. data/rust/build/src/cargo_sync.rs +6 -0
  402. data/rust/build/src/lineage_pass.rs +3 -3
  403. data/rust/build/src/main.rs +17 -5
  404. data/rust/build/src/pipeline.rs +48 -5
  405. data/rust/build/src/seams_pass.rs +123 -0
  406. data/rust/build/src/translation_pass.rs +167 -0
  407. data/rust/codegen/src/asks.rs +103 -0
  408. data/rust/codegen/src/bridging.rs +1 -1
  409. data/rust/codegen/src/commands.rs +13 -8
  410. data/rust/codegen/src/constraints.rs +25 -0
  411. data/rust/codegen/src/domain_generator.rs +11 -9
  412. data/rust/codegen/src/expr_emitter.rs +13 -0
  413. data/rust/codegen/src/json_codec.rs +54 -15
  414. data/rust/codegen/src/main.rs +2 -1
  415. data/rust/codegen/src/mutations.rs +174 -4
  416. data/rust/codegen/src/naming.rs +30 -0
  417. data/rust/codegen/src/queries.rs +11 -2
  418. data/rust/codegen/src/reactions.rs +1 -1
  419. data/rust/codegen/src/types.rs +166 -8
  420. data/rust/host/HECKS_RELEASE +1 -1
  421. data/rust/host/src/api.rs +4 -4
  422. data/rust/host/src/bin/validate_harness.rs +43 -0
  423. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  424. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  425. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  426. data/rust/host/src/checkout.rs +35 -19
  427. data/rust/host/src/commerce_ir.rs +408 -2
  428. data/rust/host/src/expr_json.rs +118 -0
  429. data/rust/host/src/mint.rs +1 -1
  430. data/rust/host/src/payments/tests.rs +27 -1
  431. data/rust/host/src/payments.rs +12 -4
  432. data/rust/host/src/query_step.rs +22 -4
  433. data/rust/host/src/reference_validate.rs +653 -108
  434. data/rust/host/src/resend.rs +2 -0
  435. data/rust/host/src/server.rs +19 -6
  436. data/rust/host/src/ui_schema.rs +132 -5
  437. data/rust/host/src/web/newsletter.rs +7 -8
  438. data/rust/host/src/web/newsletter_send.rs +38 -13
  439. data/rust/host/src/web/registrations.rs +229 -26
  440. data/rust/host/src/web.rs +3 -3
  441. data/rust/parser/src/emit.rs +38 -6
  442. data/rust/parser/src/expr/ast_json.rs +7 -1
  443. data/rust/parser/src/expr/resolver.rs +171 -6
  444. data/rust/parser/src/ir.rs +7 -0
  445. data/rust/parser/src/keywords.rs +24 -0
  446. data/rust/parser/src/main.rs +3 -0
  447. data/rust/parser/src/parse/aggregate.rs +2 -1
  448. data/rust/parser/src/parse/chapter.rs +2 -2
  449. data/rust/parser/src/parse/command.rs +2 -0
  450. data/rust/parser/src/parse/hecksagon.rs +5 -2
  451. data/rust/parser/src/parse/lifecycle.rs +81 -0
  452. data/rust/parser/src/parse/mod.rs +15 -1
  453. data/rust/parser/src/parse/policy.rs +13 -2
  454. data/rust/parser/src/parse/value_object.rs +13 -8
  455. data/rust/parser/src/ruby_value.rs +1 -1
  456. data/rust/src/exemplar/commands.rs +4 -4
  457. data/rust/src/exemplar/constraints.rs +1 -1
  458. data/rust/src/exemplar/json.rs +43 -1
  459. data/rust/src/kernel/cli.rs +7 -5
  460. data/rust/src/kernel/dispatch.rs +2 -2
  461. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  462. data/rust/src/kernel/expr.rs +73 -4
  463. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  464. data/rust/src/kernel/expression_operators/presence.rs +41 -3
  465. data/rust/src/kernel/expression_operators/text.rs +26 -2
  466. data/rust/src/kernel/json.rs +15 -0
  467. data/rust/src/kernel/mod.rs +1 -1
  468. data/rust/src/kernel/orchestrate.rs +54 -5
  469. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  470. data/rust/src/kernel/pattern.rs +60 -0
  471. data/rust/src/kernel/reference_lookup.rs +90 -7
  472. metadata +174 -36
  473. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  474. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  475. data/lib/hecks/doors/cli_door.rb +0 -124
  476. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  477. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  478. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  479. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  480. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  481. data/lib/hecks/doors/cli_runner.rb +0 -66
  482. data/lib/hecks/doors/command_request.rb +0 -119
  483. data/lib/hecks/doors/handle.rb +0 -208
  484. data/lib/hecks/doors/json_door.rb +0 -132
  485. data/lib/hecks/doors/launcher_options.rb +0 -247
  486. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  487. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  488. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  489. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  490. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  491. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  492. data/lib/hecks/doors/mcp_door.rb +0 -156
  493. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  494. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  495. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  496. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  497. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  498. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  499. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  500. data/lib/hecks/doors/ruby_door.rb +0 -80
  501. data/lib/hecks/doors/usage_cache.rb +0 -139
  502. data/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
  503. data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
  504. data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
@@ -12,6 +12,14 @@ pub struct Comparison {
12
12
  pub negated: bool,
13
13
  }
14
14
 
15
+ /// Which ends of a string `Expr::Strip` trims.
16
+ #[derive(Debug, Clone, Copy)]
17
+ pub enum StripSide {
18
+ Both,
19
+ Left,
20
+ Right,
21
+ }
22
+
15
23
  #[derive(Debug, Clone, Copy)]
16
24
  pub enum BlockMode {
17
25
  All,
@@ -35,6 +43,9 @@ pub enum Expr {
35
43
  Bool { value: bool },
36
44
  Nil,
37
45
  Add { left: Box<Expr>, right: Box<Expr> },
46
+ Sub { left: Box<Expr>, right: Box<Expr> },
47
+ Mul { left: Box<Expr>, right: Box<Expr> },
48
+ Div { left: Box<Expr>, right: Box<Expr> },
38
49
  SignTest { cmp: Comparison, receiver: Box<Expr> },
39
50
  Empty { receiver: Box<Expr> },
40
51
  ToS { receiver: Box<Expr> },
@@ -48,6 +59,7 @@ pub enum Expr {
48
59
  Presence { receiver: Box<Expr>, negated: bool },
49
60
  Assignment { receiver: Box<Expr>, negated: bool },
50
61
  Split { receiver: Box<Expr>, separator: String },
62
+ Strip { receiver: Box<Expr>, side: StripSide },
51
63
  StartsWith { receiver: Box<Expr>, substring: String },
52
64
  EndsWith { receiver: Box<Expr>, substring: String },
53
65
  First { receiver: Box<Expr> },
@@ -95,6 +107,9 @@ pub fn parse(json: &Json) -> Result<Expr, String> {
95
107
  "bool" => Expr::Bool { value: field("value")?.as_bool().ok_or_else(|| format!("bool's value isn't a boolean: {json}"))? },
96
108
  "nil" => Expr::Nil,
97
109
  "add" => Expr::Add { left: Box::new(expr("left")?), right: Box::new(expr("right")?) },
110
+ "sub" => Expr::Sub { left: Box::new(expr("left")?), right: Box::new(expr("right")?) },
111
+ "mul" => Expr::Mul { left: Box::new(expr("left")?), right: Box::new(expr("right")?) },
112
+ "div" => Expr::Div { left: Box::new(expr("left")?), right: Box::new(expr("right")?) },
98
113
  "sign_test" => Expr::SignTest { cmp: comparison()?, receiver: Box::new(expr("receiver")?) },
99
114
  "empty" => Expr::Empty { receiver: Box::new(expr("receiver")?) },
100
115
  "to_s" => Expr::ToS { receiver: Box::new(expr("receiver")?) },
@@ -136,6 +151,15 @@ pub fn parse(json: &Json) -> Result<Expr, String> {
136
151
  negated: field("negated")?.as_bool().ok_or_else(|| format!("assignment's negated isn't a boolean: {json}"))?,
137
152
  },
138
153
  "split" => Expr::Split { receiver: Box::new(expr("receiver")?), separator: str_field("separator")? },
154
+ "strip" => Expr::Strip {
155
+ receiver: Box::new(expr("receiver")?),
156
+ side: match str_field("side")?.as_str() {
157
+ "both" => StripSide::Both,
158
+ "left" => StripSide::Left,
159
+ "right" => StripSide::Right,
160
+ other => return Err(format!("strip's side {other:?} is none of both/left/right: {json}")),
161
+ },
162
+ },
139
163
  "starts_with" => Expr::StartsWith { receiver: Box::new(expr("receiver")?), substring: str_field("substring")? },
140
164
  "ends_with" => Expr::EndsWith { receiver: Box::new(expr("receiver")?), substring: str_field("substring")? },
141
165
  "first" => Expr::First { receiver: Box::new(expr("receiver")?) },
@@ -374,6 +398,45 @@ fn add(left: &Value, right: &Value) -> Result<Value, String> {
374
398
  }
375
399
  }
376
400
 
401
+ /// `Resolver.checked_arithmetic`: the same 64-bit and finiteness limits `add` holds to, named for
402
+ /// the operation that overflowed.
403
+ fn arithmetic(
404
+ name: &str,
405
+ symbol: &str,
406
+ left: &Value,
407
+ right: &Value,
408
+ int_op: fn(i64, i64) -> Option<i64>,
409
+ float_op: fn(f64, f64) -> f64,
410
+ ) -> Result<Value, String> {
411
+ let lhs = require_number(left, name)?;
412
+ let rhs = require_number(right, name)?;
413
+ if let (Value::Int(a), Value::Int(b)) = (&lhs, &rhs) {
414
+ return int_op(*a, *b)
415
+ .map(Value::Int)
416
+ .ok_or_else(|| format!("{name} overflowed: {a} {symbol} {b} does not fit in a 64-bit integer"));
417
+ }
418
+ let result = float_op(numeric(&lhs).unwrap_or_default(), numeric(&rhs).unwrap_or_default());
419
+ if result.is_finite() {
420
+ Ok(Value::Float(result))
421
+ } else {
422
+ Err(format!("{name} overflowed: {} {symbol} {} is not a finite number", to_s(&lhs)?, to_s(&rhs)?))
423
+ }
424
+ }
425
+
426
+ /// `Resolver.divide`: a zero divisor faults first; integers floor toward negative infinity.
427
+ fn divide(left: &Value, right: &Value) -> Result<Value, String> {
428
+ if numeric(&require_number(right, "division")?) == Some(0.0) {
429
+ return Err("divided by 0".to_string());
430
+ }
431
+ arithmetic("division", "/", left, right, floor_div, |a, b| a / b)
432
+ }
433
+
434
+ /// Floored integer division; `None` only for `i64::MIN / -1`, which does not fit in 64 bits.
435
+ fn floor_div(a: i64, b: i64) -> Option<i64> {
436
+ let quotient = a.checked_div(b)?;
437
+ Some(if a % b != 0 && ((a < 0) != (b < 0)) { quotient - 1 } else { quotient })
438
+ }
439
+
377
440
  /// `Resolver.apply_modulo`: floored like Ruby's `%`, so the result takes the divisor's sign.
378
441
  fn modulo(receiver: &Value, divisor: &Value) -> Result<Value, String> {
379
442
  let receiver = require_number(receiver, "modulo")?;
@@ -423,6 +486,18 @@ fn split(text: &str, separator: &str) -> Vec<Value> {
423
486
  parts.into_iter().map(Value::Str).collect()
424
487
  }
425
488
 
489
+ /// Ruby's `String#strip` family: trims null, tab, line feed, vertical tab, form feed, carriage
490
+ /// return and space, never a Unicode space (Rust's own `trim` would drop U+00A0 too).
491
+ fn strip(text: &str, side: StripSide) -> String {
492
+ let stripped = |c: char| matches!(c, '\0' | '\t' | '\n' | '\u{b}' | '\u{c}' | '\r' | ' ');
493
+ match side {
494
+ StripSide::Both => text.trim_matches(stripped),
495
+ StripSide::Left => text.trim_start_matches(stripped),
496
+ StripSide::Right => text.trim_end_matches(stripped),
497
+ }
498
+ .to_string()
499
+ }
500
+
426
501
  /// Compiles a Ruby `Regexp` source for the `regex` crate.
427
502
  ///
428
503
  /// Ruby's `\d \w \s \h` are ASCII and its `^ $` are always line anchors, where `regex` reads
@@ -545,6 +620,13 @@ fn eval(expr: &Expr, instance: &Json, scope: Option<&Scope>) -> Result<Value, St
545
620
  }
546
621
  }
547
622
  Expr::Add { left, right } => add(&recur(left)?, &recur(right)?),
623
+ Expr::Sub { left, right } => {
624
+ arithmetic("subtraction", "-", &recur(left)?, &recur(right)?, i64::checked_sub, |a, b| a - b)
625
+ }
626
+ Expr::Mul { left, right } => {
627
+ arithmetic("multiplication", "*", &recur(left)?, &recur(right)?, i64::checked_mul, |a, b| a * b)
628
+ }
629
+ Expr::Div { left, right } => divide(&recur(left)?, &recur(right)?),
548
630
  Expr::Modulo { receiver, divisor } => modulo(&recur(receiver)?, &recur(divisor)?),
549
631
  Expr::SignTest { cmp, receiver } => {
550
632
  let v = recur(receiver)?;
@@ -587,6 +669,10 @@ fn eval(expr: &Expr, instance: &Json, scope: Option<&Scope>) -> Result<Value, St
587
669
  Value::Array(items) => Ok(items.into_iter().next_back().unwrap_or(Value::Nil)),
588
670
  other => Err(format!("last expects a list, got {}", describe(&other))),
589
671
  },
672
+ Expr::Strip { receiver, side } => match recur(receiver)? {
673
+ Value::Str(text) => Ok(Value::Str(strip(&text, *side))),
674
+ other => Err(format!("strip expects a string, got {}", describe(&other))),
675
+ },
590
676
  Expr::StartsWith { receiver, substring } => match recur(receiver)? {
591
677
  Value::Str(text) => Ok(Value::Bool(text.starts_with(substring.as_str()))),
592
678
  other => Err(format!("start_with? expects a string, got {}", describe(&other))),
@@ -828,6 +914,25 @@ mod tests {
828
914
  );
829
915
  }
830
916
 
917
+ #[test]
918
+ fn sub_mul_div_follow_rubys_limits_and_floor_integer_division() {
919
+ let op = |name: &str, l: Json, r: Json| json!({"op":name,"left":l,"right":r});
920
+ assert_eq!(run(op("sub", int(5), int(2)), json!({})), Ok(Value::Int(3)));
921
+ assert_eq!(run(op("mul", int(6), json!({"op":"float","value":0.5})), json!({})), Ok(Value::Float(3.0)));
922
+ assert_eq!(run(op("div", int(-7), int(2)), json!({})), Ok(Value::Int(-4)));
923
+ assert_eq!(run(op("div", int(7), int(-2)), json!({})), Ok(Value::Int(-4)));
924
+ assert_eq!(run(op("div", int(7), int(0)), json!({})), Err("divided by 0".to_string()));
925
+ assert_eq!(
926
+ run(op("mul", int(i64::MAX), int(2)), json!({})),
927
+ Err("multiplication overflowed: 9223372036854775807 * 2 does not fit in a 64-bit integer".to_string())
928
+ );
929
+ assert_eq!(
930
+ run(op("div", int(i64::MIN), int(-1)), json!({})),
931
+ Err("division overflowed: -9223372036854775808 / -1 does not fit in a 64-bit integer".to_string())
932
+ );
933
+ assert_eq!(run(op("sub", lookup_of("a"), int(1)), json!({"a": null})), Err("subtraction expects a number, got nil".to_string()));
934
+ }
935
+
831
936
  #[test]
832
937
  fn modulo_floors_like_ruby_for_integers_and_floats() {
833
938
  let modulo = |r: Json, d: Json| json!({"op":"modulo","receiver":r,"divisor":d});
@@ -1035,6 +1140,19 @@ mod tests {
1035
1140
  assert_eq!(run(call("first", lookup_of("x")), json!({"x": null})), Err("first expects a list, got nil".to_string()));
1036
1141
  }
1037
1142
 
1143
+ #[test]
1144
+ fn strip_trims_rubys_whitespace_and_needs_a_string() {
1145
+ let strip_of = |side: &str, text: &str| run(json!({"op":"strip","receiver":lookup_of("x"),"side":side}), json!({"x": text}));
1146
+ assert_eq!(strip_of("both", " \t a b \0"), Ok(Value::Str("a b".into())));
1147
+ assert_eq!(strip_of("left", " a "), Ok(Value::Str("a ".into())));
1148
+ assert_eq!(strip_of("right", " a "), Ok(Value::Str(" a".into())));
1149
+ assert_eq!(strip_of("both", "\u{a0}a"), Ok(Value::Str("\u{a0}a".into())));
1150
+ assert_eq!(
1151
+ run(json!({"op":"strip","receiver":lookup_of("x"),"side":"both"}), json!({"x": null})),
1152
+ Err("strip expects a string, got nil".to_string())
1153
+ );
1154
+ }
1155
+
1038
1156
  #[test]
1039
1157
  fn starts_with_and_ends_with_need_a_string() {
1040
1158
  let starts = json!({"op":"starts_with","receiver":lookup_of("x"),"substring":"ab"});
@@ -815,7 +815,7 @@ pub async fn audit_before_mint<C: GenericClient>(
815
815
  // `layer_one!(violations, aggregate, after)` exactly.
816
816
  if let Some(aggregate_ir) = ir_aggregates.get(aggregate.name.as_str()) {
817
817
  for (id, state) in &after {
818
- violations.extend(reference_validate::validate(aggregate_ir, id, state));
818
+ violations.extend(reference_validate::validate(ir, aggregate_ir, id, state));
819
819
  }
820
820
  }
821
821
 
@@ -735,6 +735,21 @@ async fn archiving_a_paid_registration_frees_its_seat_and_restoring_it_takes_the
735
735
  assert_eq!(web::seats_left(&read, "CheckoutFixture", &crate::commerce_ir::fixture_payments(), "archive-me"), Some(0));
736
736
  }
737
737
 
738
+ #[tokio::test]
739
+ async fn cancelling_a_paid_registration_reaches_a_state_the_fixture_declares_as_freeing_its_seat() {
740
+ let t = tenant("hecks_pay_test_capacity_cancel").await;
741
+ t.schedule_event_with_capacity("cancel-me", 1).await;
742
+ let (_, body) = t.register("cancel-me").await;
743
+ let reference = body["registration_id"].as_str().unwrap().to_string();
744
+ assert_eq!(t.settle(&reference).await, 200);
745
+
746
+ let cancelled = registration_command(&t, "Cancel", &reference).await;
747
+ assert!(cancelled.accepted, "{:?}", cancelled.result);
748
+ let state = registration_status(&t, &reference).await;
749
+ let declared = crate::commerce_ir::registrations_provider(&crate::commerce_ir::fixture_ir()).expect("registrations");
750
+ assert!(declared.frees_seat.contains(&state), "{state} is one of {:?}", declared.frees_seat);
751
+ }
752
+
738
753
  #[tokio::test]
739
754
  async fn only_an_active_registration_can_be_archived_and_only_an_archived_one_restored() {
740
755
  let t = tenant("hecks_pay_test_archive_guards").await;
@@ -763,7 +778,7 @@ async fn a_stripe_session_expires_about_thirty_one_minutes_after_it_is_created()
763
778
  let sent = t.fake.requests_to("/v1/checkout/sessions");
764
779
  assert_eq!(sent.len(), 1);
765
780
  let expires_at: i64 = sent[0].body.split('&').find_map(|pair| pair.strip_prefix("expires_at=")).expect("an expires_at form field").parse().unwrap();
766
- let hold = crate::checkout::SESSION_HOLD_SECONDS;
781
+ let hold = crate::checkout::effective_hold_seconds(crate::commerce_ir::checkout_windows_binding().session_hold);
767
782
  assert!((before + hold..=after + hold).contains(&expires_at), "expires_at {expires_at} is not {hold}s after creation ({before}..{after})");
768
783
  assert!((30 * 60..=35 * 60).contains(&hold), "Stripe's minimum is 30 minutes, and the hold should stay close to it");
769
784
  }
@@ -1398,3 +1413,14 @@ fn purpose_tokens_only_verify_for_their_own_purpose_and_secret() {
1398
1413
  assert!(auth::verify_purpose_token("other", "one", &token).is_none());
1399
1414
  assert!(auth::verify_account_token("k", &token).is_none(), "not a session cookie");
1400
1415
  }
1416
+
1417
+ // ADR 0099: the processor and the modes are this adapter's own words, but the domain still has to
1418
+ // admit them. The fixture's source is the bluebook as the domain wrote it.
1419
+ #[test]
1420
+ fn the_adapter_words_are_ones_the_connection_domain_admits() {
1421
+ let ir = crate::commerce_ir::fixture_ir();
1422
+ let source = ir["source_text"].as_str().expect("the fixture carries its bluebook source");
1423
+ assert!(source.contains(&format!("one_of: {MODES:?}")), "ConnectionMode must admit exactly {MODES:?}");
1424
+ let payments = std::fs::read_to_string(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../spec/fixtures/rust_host/checkout_fixture/bluebook/payments.bluebook")).expect("payments bluebook");
1425
+ assert!(payments.contains(&format!("one_of: [\"mock_stripe\", \"{PROCESSOR}\"]")), "Payment's Processor must admit {PROCESSOR}");
1426
+ }
@@ -19,7 +19,15 @@ use keystore::{KeyStore, StoredDocument, StoredKeys};
19
19
 
20
20
  const CONNECTION_SLUG: &str = "payments";
21
21
  const STRIPE_API_BASE: &str = "https://api.stripe.com";
22
+ /// ADAPTER FACT (ADR 0099): the two environments a Stripe account has, named by the key prefix
23
+ /// (`sk_test_`, `sk_live_`) and the env vars read here. The domain's `ConnectionMode` enumerates
24
+ /// the same words; `the_adapter_words_are_ones_the_connection_domain_admits` holds the two
25
+ /// together, so a renamed mode fails a test instead of a request.
22
26
  const MODES: [&str; 2] = ["test", "live"];
27
+ /// ADAPTER FACT (ADR 0099): this module is the Stripe adapter, so the processor it reports to the
28
+ /// domain is its own name. The domain's `Processor` enumerates the processors it admits; the same
29
+ /// test holds the word to it.
30
+ const PROCESSOR: &str = "stripe";
23
31
  /// The reserved `account_ref` of a connection that uses the business's own
24
32
  /// Stripe account directly. It is the only kind of connection this host charges.
25
33
  pub const SELF_ACCOUNT: &str = "self";
@@ -217,7 +225,7 @@ pub fn checkout_plan(connection: Option<&Connection>, platform: &PlatformConfig)
217
225
  match connection.status.as_str() {
218
226
  "enabled" => {
219
227
  let (key, publishable_key) = platform.direct_pair(&connection.mode);
220
- if connection.processor != "stripe" || !connection.is_direct() || key.is_empty() {
228
+ if connection.processor != PROCESSOR || !connection.is_direct() || key.is_empty() {
221
229
  CheckoutPlan::Paused
222
230
  } else if publishable_key.is_empty() {
223
231
  eprintln!(
@@ -298,7 +306,7 @@ fn require_operator(caller: &Caller) -> Result<(), Value> {
298
306
  // modes have keys. Built field by field, so a credential can never appear here:
299
307
  // none is ever read into this value.
300
308
  fn connection_json(connection: Option<&Connection>, platform: &PlatformConfig, caller: &Caller) -> Value {
301
- let label = connection.filter(|c| c.processor == "stripe").map(|_| "Stripe");
309
+ let label = connection.filter(|c| c.processor == PROCESSOR).map(|_| "Stripe");
302
310
  let present = |value: Option<&str>| value.filter(|s| !s.is_empty()).map(Value::from).unwrap_or(Value::Null);
303
311
  json!({
304
312
  "status": connection.map(|c| c.status.as_str()).unwrap_or("not_connected"),
@@ -442,7 +450,7 @@ async fn direct_route(caller: &Caller, raw_body: &str, platform: &PlatformConfig
442
450
  }
443
451
  let display_name = own_account_display_name(platform, platform.direct_key(&mode)).await;
444
452
  let facts = json!({
445
- "processor": {"value": "stripe"},
453
+ "processor": {"value": PROCESSOR},
446
454
  "account_ref": {"value": SELF_ACCOUNT},
447
455
  "mode": {"value": mode},
448
456
  "display_name": {"value": display_name},
@@ -700,7 +708,7 @@ async fn save_keys_route(caller: &Caller, body: &Value, platform: &PlatformConfi
700
708
  return connection_response(caller, &fresh, client, wasm_path, config).await;
701
709
  }
702
710
  let facts = json!({
703
- "processor": {"value": "stripe"},
711
+ "processor": {"value": PROCESSOR},
704
712
  "account_ref": {"value": SELF_ACCOUNT},
705
713
  "mode": {"value": mode},
706
714
  "display_name": {"value": display_name},
@@ -1,7 +1,7 @@
1
1
  //! The wire's query step: `{"query": "Aggregate.Query", "args": {...}}`.
2
2
  //!
3
3
  //! A declared query that filters stored records is answered by the kernel against current
4
- //! state, the way the Ruby launcher and doors answer it. A query that `returns` a value object
4
+ //! state, the way the Ruby launcher and entry points answer it. A query that `returns` a value object
5
5
  //! is answered from outside the domain (a hecksagon `answers_query` binding); this host binds
6
6
  //! no adapter, so it refuses that question rather than answer it from the aggregate's rows, and
7
7
  //! it writes nothing to the journal for either.
@@ -25,15 +25,24 @@ pub enum Plan {
25
25
  ///
26
26
  /// @param domain_ir the domain's IR, when this host has one (`HECKS_IR_PATH`)
27
27
  /// @param domain the domain's name, used to qualify a short question
28
- /// @param question `Aggregate.Query`, or `Domain::Aggregate.Query`
28
+ /// @param question `Aggregate.Query`, or `Chapter::Aggregate.Query` for this domain or one of its
29
+ /// attached chapters
29
30
  /// @return a kernel question for a declared derivable query, a refusal for an unknown query on a
30
31
  /// known aggregate or for a query answered outside the domain
31
32
  pub fn plan(domain_ir: Option<&Value>, domain: &str, question: &str) -> Plan {
32
- let short = question.rsplit_once("::").map_or(question, |(_, rest)| rest);
33
- let qualified = format!("{domain}::{short}");
33
+ // A question that names its own chapter (`Chapter::Aggregate.Query`) is asked of that chapter:
34
+ // the kernel's query table is keyed by the chapter's own domain name, so re-qualifying it with
35
+ // this host's would send an attached chapter's query to a table that does not hold it.
36
+ let (owner, short) = question.rsplit_once("::").unwrap_or((domain, question));
37
+ let qualified = format!("{owner}::{short}");
34
38
  let Some((aggregate_name, query_name)) = short.split_once('.') else {
35
39
  return Plan::Kernel(qualified);
36
40
  };
41
+ // This host's IR describes only its own domain's aggregates; another chapter's question is the
42
+ // kernel's to answer or refuse.
43
+ if owner != domain {
44
+ return Plan::Kernel(qualified);
45
+ }
37
46
  let aggregate = domain_ir
38
47
  .and_then(|ir| ir.get("aggregates"))
39
48
  .and_then(|v| v.as_array())
@@ -131,6 +140,15 @@ mod tests {
131
140
  );
132
141
  }
133
142
 
143
+ #[test]
144
+ fn a_question_naming_an_attached_chapter_keeps_that_chapter() {
145
+ let ir = domain();
146
+ assert_eq!(plan(Some(&ir), "Banking", "Media::MediaItem.Active"), Plan::Kernel("Media::MediaItem.Active".into()));
147
+ // The host's own aggregate of the same name is not mistaken for the chapter's.
148
+ assert_eq!(plan(Some(&ir), "Banking", "Media::Ledger.Ir"), Plan::Kernel("Media::Ledger.Ir".into()));
149
+ assert_eq!(plan(None, "Banking", "Media::MediaItem.Listing"), Plan::Kernel("Media::MediaItem.Listing".into()));
150
+ }
151
+
134
152
  #[test]
135
153
  fn what_this_ir_does_not_show_is_left_to_the_kernel() {
136
154
  let ir = domain();