hecks 3.10.0 → 3.12.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 (542) 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 +158 -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 +70 -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/newcomer.rb +50 -0
  40. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
  41. data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
  42. data/lib/hecks/adapters/driving/ruby.rb +83 -0
  43. data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
  44. data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
  45. data/lib/hecks/behaviors/expectations/boot.rb +1 -1
  46. data/lib/hecks/bench/ruby_runner.rb +1 -1
  47. data/lib/hecks/bluebook/ask_resolution.rb +130 -0
  48. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
  49. data/lib/hecks/bluebook/assembly/contracts/read_models.rb +1 -1
  50. data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
  51. data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
  52. data/lib/hecks/bluebook/assembly/marks.rb +4 -15
  53. data/lib/hecks/bluebook/assembly/scalars.rb +39 -0
  54. data/lib/hecks/bluebook/attribute.rb +1 -1
  55. data/lib/hecks/bluebook/behaviour/command.rb +13 -8
  56. data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
  57. data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
  58. data/lib/hecks/bluebook/capabilities.rb +91 -9
  59. data/lib/hecks/bluebook/domain_port.rb +16 -4
  60. data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
  61. data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
  62. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
  63. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
  64. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
  65. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
  66. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
  67. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
  68. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  69. data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
  70. data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
  71. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
  72. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
  73. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
  74. data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
  75. data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
  76. data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
  77. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
  78. data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
  79. data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
  80. data/lib/hecks/bluebook/expression/projection.json +36 -0
  81. data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
  82. data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
  83. data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
  84. data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
  85. data/lib/hecks/bluebook/expression/resolver.rb +20 -1
  86. data/lib/hecks/bluebook/hecksagon.rb +23 -3
  87. data/lib/hecks/bluebook/lifecycle.rb +22 -3
  88. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
  89. data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
  90. data/lib/hecks/bluebook/meta_validator/readings.rb +24 -1
  91. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
  92. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +6 -3
  93. data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
  94. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
  95. data/lib/hecks/bluebook/meta_validator.rb +12 -12
  96. data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
  97. data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
  98. data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
  99. data/lib/hecks/bluebook/model_check.rb +8 -2
  100. data/lib/hecks/bluebook/policy.rb +4 -2
  101. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  102. data/lib/hecks/bluebook/synthesizer.rb +2 -0
  103. data/lib/hecks/bluebook.rb +2 -0
  104. data/lib/hecks/cli/command_table.rb +1 -1
  105. data/lib/hecks/cli/console.rb +2 -1
  106. data/lib/hecks/cli/document.rb +1 -1
  107. data/lib/hecks/cli/launcher_words.rb +4 -4
  108. data/lib/hecks/cli/mcp.rb +4 -4
  109. data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
  110. data/lib/hecks/cli/project_cli/templates.rb +5 -5
  111. data/lib/hecks/cli/project_cli.rb +9 -6
  112. data/lib/hecks/cli/run.rb +2 -2
  113. data/lib/hecks/cli/smoke_http/checks.rb +4 -1
  114. data/lib/hecks/cli/smoke_http/transport.rb +3 -3
  115. data/lib/hecks/cli/smoke_http.rb +3 -0
  116. data/lib/hecks/codemod.rb +5 -2
  117. data/lib/hecks/corpus/routes.rb +7 -0
  118. data/lib/hecks/corpus/rust.rb +3 -2
  119. data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -4
  120. data/lib/hecks/doc/reference/rendering.rb +6 -3
  121. data/lib/hecks/forms/app/commands.rb +8 -7
  122. data/lib/hecks/forms/app/queries.rb +2 -1
  123. data/lib/hecks/forms/app.rb +1 -1
  124. data/lib/hecks/forms/record_renderer.rb +1 -1
  125. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  126. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  127. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  128. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  129. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  130. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  131. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  132. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  133. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  134. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  135. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  136. data/lib/hecks/gate/stages.yml +33 -7
  137. data/lib/hecks/grammar/evolve.rb +1 -1
  138. data/lib/hecks/grammar/expression_operators.json +372 -0
  139. data/lib/hecks/grammar/operators.rb +3 -1
  140. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  141. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  142. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  143. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  144. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  145. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  146. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  147. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  148. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  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 +133 -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/terminal.rb +13 -11
  173. data/lib/hecks/hecks/codebase.behaviors +13 -1
  174. data/lib/hecks/hecks/codebase.bluebook +239 -5
  175. data/lib/hecks/hecks/custodian.behaviors +24 -6
  176. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  177. data/lib/hecks/hecks/deploy.behaviors +1 -1
  178. data/lib/hecks/hecks/deploy.world +1 -3
  179. data/lib/hecks/hecks/hecks.behaviors +1 -1
  180. data/lib/hecks/hecks/hecks.hecksagon +53 -18
  181. data/lib/hecks/hecks/hecks.world +13 -10
  182. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  183. data/lib/hecks/hecks/site.behaviors +1 -1
  184. data/lib/hecks/hecks/site.world +0 -1
  185. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  186. data/lib/hecks/indifferent_key.rb +20 -0
  187. data/lib/hecks/language/adapter.bluebook +13 -3
  188. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  189. data/lib/hecks/language/bluebook/bluebook.behaviors +23 -0
  190. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  191. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  192. data/lib/hecks/language/bluebook/policy.behaviors +19 -0
  193. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  194. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  195. data/lib/hecks/language/bluebook/vocabulary.bluebook +63 -4
  196. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  197. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  198. data/lib/hecks/language/oidc.json +10 -0
  199. data/lib/hecks/language/port.bluebook +4 -4
  200. data/lib/hecks/literal.rb +12 -3
  201. data/lib/hecks/ports/key_vault.rb +7 -2
  202. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  203. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  204. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  205. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  206. data/lib/hecks/projection_files/expression_tables.rb +7 -2
  207. data/lib/hecks/projections/deploy/box/compose.rb +6 -4
  208. data/lib/hecks/projections/deploy/box/edge.rb +62 -0
  209. data/lib/hecks/projections/deploy/box/logging.rb +99 -0
  210. data/lib/hecks/projections/deploy/box/roll_scripts.rb +16 -8
  211. data/lib/hecks/projections/deploy/box/settings/cdn.rb +40 -0
  212. data/lib/hecks/projections/deploy/box/settings/logs.rb +40 -0
  213. data/lib/hecks/projections/deploy/box/settings/patterns.rb +11 -0
  214. data/lib/hecks/projections/deploy/box/settings/types.rb +16 -1
  215. data/lib/hecks/projections/deploy/box/settings.rb +6 -2
  216. data/lib/hecks/projections/deploy/box/stack_yaml.rb +5 -2
  217. data/lib/hecks/projections/deploy/box/templates/Makefile.tmpl +16 -0
  218. data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +8 -4
  219. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +32 -0
  220. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  221. data/lib/hecks/projections/deploy/box/templates/edge.mk.tmpl +17 -0
  222. data/lib/hecks/projections/deploy/box/templates/edge.yaml.tmpl +65 -0
  223. data/lib/hecks/projections/deploy/box/templates/log-group.yaml.tmpl +12 -0
  224. data/lib/hecks/projections/deploy/box/templates/log-policy.yaml.tmpl +7 -0
  225. data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +5 -4
  226. data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +5 -4
  227. data/lib/hecks/projections/deploy/box.rb +15 -3
  228. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  229. data/lib/hecks/projections/deploy/shared.rb +5 -2
  230. data/lib/hecks/projections/model/deviations.rb +2 -2
  231. data/lib/hecks/projections/model.rb +2 -2
  232. data/lib/hecks/projections/oidc.rb +13 -1
  233. data/lib/hecks/projections/shape.rb +5 -2
  234. data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
  235. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  236. data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
  237. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  238. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  239. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  240. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  241. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  242. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  243. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  244. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  245. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  246. data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
  247. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  248. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  249. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  250. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  251. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  252. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  253. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  254. data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
  255. data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
  256. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
  258. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
  259. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
  261. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  262. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  265. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  266. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  268. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  271. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  272. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
  278. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
  281. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
  282. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
  284. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  285. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  286. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  287. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
  288. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  289. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  290. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
  291. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
  292. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  293. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
  294. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  295. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  296. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
  297. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
  298. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  299. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  300. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  301. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  302. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  303. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  304. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  305. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  306. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  307. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  308. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  309. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
  310. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  311. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  312. data/lib/hecks/projections/site/cms_editor.rb +49 -23
  313. data/lib/hecks/projections/site/role_probe.rb +2 -0
  314. data/lib/hecks/projections/site/site_root/workflow.rb +5 -0
  315. data/lib/hecks/projector/cli_audience.rb +22 -1
  316. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  317. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  318. data/lib/hecks/projector/cli_projector/usage.rb +15 -4
  319. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  320. data/lib/hecks/projector/docs_projector.rb +1 -1
  321. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  322. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  323. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  324. data/lib/hecks/projector/target.rb +11 -0
  325. data/lib/hecks/projector.rb +32 -5
  326. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  327. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  328. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  329. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  330. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  331. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  332. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  333. data/lib/hecks/release/approval.rb +64 -0
  334. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  335. data/lib/hecks/router/namespace_installer.rb +16 -16
  336. data/lib/hecks/router.rb +1 -1
  337. data/lib/hecks/runtime/best_effort.rb +22 -0
  338. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  339. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  340. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  341. data/lib/hecks/runtime/dispatcher.rb +4 -4
  342. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  343. data/lib/hecks/runtime/event.rb +5 -0
  344. data/lib/hecks/runtime/identity.rb +7 -6
  345. data/lib/hecks/runtime/interpreting.rb +2 -1
  346. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  347. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  348. data/lib/hecks/runtime/loader.rb +10 -10
  349. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  350. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  351. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  352. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  353. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  354. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  355. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  356. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  357. data/lib/hecks/runtime/registry/merging.rb +1 -1
  358. data/lib/hecks/runtime/registry/providers.rb +16 -0
  359. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  360. data/lib/hecks/runtime/registry/verification.rb +4 -1
  361. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  362. data/lib/hecks/runtime/registry.rb +1 -1
  363. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  364. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  365. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  366. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  367. data/lib/hecks/runtime/tenant_check.rb +5 -4
  368. data/lib/hecks/runtime/value/coercion.rb +2 -2
  369. data/lib/hecks/runtime/value/validation.rb +16 -4
  370. data/lib/hecks/runtime.rb +9 -9
  371. data/lib/hecks/rust_build/host.rb +11 -4
  372. data/lib/hecks/rust_build/prebuilt_binary.rb +101 -0
  373. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  374. data/lib/hecks/rust_build/wasm.rb +1 -1
  375. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  376. data/lib/hecks/rust_build.rb +11 -0
  377. data/lib/hecks/served_options.rb +53 -0
  378. data/lib/hecks/site/bluebook/site.bluebook +1 -0
  379. data/lib/hecks/storehouse/audit.rb +1 -1
  380. data/lib/hecks/storehouse/dispatching.rb +8 -6
  381. data/lib/hecks/storehouse/introspection.rb +2 -2
  382. data/lib/hecks/storehouse/querying.rb +6 -4
  383. data/lib/hecks/storehouse.rb +4 -4
  384. data/lib/hecks/tools/ci_gate_decision.rb +35 -0
  385. data/lib/hecks/tools/ci_gates.rb +20 -1
  386. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  387. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  388. data/lib/hecks/tools/lanes/files.rb +8 -0
  389. data/lib/hecks/tools/lanes/prebuilt_workflow.rb +81 -0
  390. data/lib/hecks/tools/lanes/rows.rb +6 -0
  391. data/lib/hecks/tools/lanes.rb +1 -0
  392. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  393. data/lib/hecks/tools/regeneration_run.rb +11 -4
  394. data/lib/hecks/tools/release_only.rb +111 -0
  395. data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
  396. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  397. data/lib/hecks/tools/tools_doc.rb +4 -4
  398. data/lib/hecks/version.rb +1 -1
  399. data/lib/hecks/vocabulary.rb +19 -5
  400. data/lib/hecks.rb +15 -15
  401. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  402. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  403. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  404. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  405. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  406. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  407. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  408. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  409. data/prebuilt/9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal +0 -0
  410. data/prebuilt/d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal +0 -0
  411. data/prebuilt/verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json +1 -0
  412. data/rust/.gitignore +6 -0
  413. data/rust/Cargo.toml +6 -0
  414. data/rust/build/src/binding_pass.rs +103 -0
  415. data/rust/build/src/cargo_sync.rs +6 -0
  416. data/rust/build/src/lineage_pass.rs +3 -3
  417. data/rust/build/src/main.rs +17 -5
  418. data/rust/build/src/pipeline.rs +48 -5
  419. data/rust/build/src/seams_pass.rs +123 -0
  420. data/rust/build/src/translation_pass.rs +167 -0
  421. data/rust/codegen/src/asks.rs +103 -0
  422. data/rust/codegen/src/commands.rs +13 -8
  423. data/rust/codegen/src/constraints.rs +25 -0
  424. data/rust/codegen/src/domain_generator.rs +2 -0
  425. data/rust/codegen/src/expr_emitter.rs +13 -0
  426. data/rust/codegen/src/json_codec.rs +54 -15
  427. data/rust/codegen/src/main.rs +1 -0
  428. data/rust/codegen/src/mutations.rs +128 -3
  429. data/rust/codegen/src/queries.rs +11 -2
  430. data/rust/codegen/src/reactions.rs +1 -1
  431. data/rust/codegen/src/types.rs +166 -8
  432. data/rust/host/HECKS_RELEASE +1 -1
  433. data/rust/host/src/api.rs +4 -4
  434. data/rust/host/src/bin/validate_harness.rs +43 -0
  435. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  436. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  437. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  438. data/rust/host/src/checkout.rs +35 -19
  439. data/rust/host/src/commerce_ir.rs +408 -2
  440. data/rust/host/src/expr_json.rs +118 -0
  441. data/rust/host/src/extension.rs +13 -1
  442. data/rust/host/src/lib.rs +1 -0
  443. data/rust/host/src/mint.rs +1 -1
  444. data/rust/host/src/payments/tests.rs +27 -1
  445. data/rust/host/src/payments.rs +12 -4
  446. data/rust/host/src/platform_signin/attack_tests.rs +1100 -0
  447. data/rust/host/src/platform_signin/config.rs +512 -0
  448. data/rust/host/src/platform_signin/cookie.rs +237 -0
  449. data/rust/host/src/platform_signin/extension.rs +190 -0
  450. data/rust/host/src/platform_signin/fetcher.rs +322 -0
  451. data/rust/host/src/platform_signin/handler.rs +251 -0
  452. data/rust/host/src/platform_signin/handler_tests.rs +554 -0
  453. data/rust/host/src/platform_signin/jti.rs +196 -0
  454. data/rust/host/src/platform_signin/jwks.rs +542 -0
  455. data/rust/host/src/platform_signin/jws.rs +249 -0
  456. data/rust/host/src/platform_signin/known_answer_tests.rs +237 -0
  457. data/rust/host/src/platform_signin/mapping_tests.rs +254 -0
  458. data/rust/host/src/platform_signin/mod.rs +75 -0
  459. data/rust/host/src/platform_signin/mode_tests.rs +382 -0
  460. data/rust/host/src/platform_signin/pages.rs +204 -0
  461. data/rust/host/src/platform_signin/query.rs +233 -0
  462. data/rust/host/src/platform_signin/session.rs +151 -0
  463. data/rust/host/src/platform_signin/settings.rs +385 -0
  464. data/rust/host/src/platform_signin/strict_json.rs +351 -0
  465. data/rust/host/src/platform_signin/test_support.rs +235 -0
  466. data/rust/host/src/platform_signin/ttl_tests.rs +94 -0
  467. data/rust/host/src/platform_signin/verify.rs +604 -0
  468. data/rust/host/src/platform_signin/wiring_support.rs +319 -0
  469. data/rust/host/src/query_step.rs +22 -4
  470. data/rust/host/src/reference_validate.rs +653 -108
  471. data/rust/host/src/resend.rs +2 -0
  472. data/rust/host/src/server.rs +19 -6
  473. data/rust/host/src/ui_schema.rs +132 -5
  474. data/rust/host/src/web/newsletter.rs +7 -8
  475. data/rust/host/src/web/newsletter_send.rs +38 -13
  476. data/rust/host/src/web/registrations.rs +229 -26
  477. data/rust/host/src/web.rs +53 -9
  478. data/rust/parser/src/emit.rs +38 -6
  479. data/rust/parser/src/expr/ast_json.rs +7 -1
  480. data/rust/parser/src/expr/resolver.rs +171 -6
  481. data/rust/parser/src/ir.rs +7 -0
  482. data/rust/parser/src/keywords.rs +24 -0
  483. data/rust/parser/src/main.rs +3 -0
  484. data/rust/parser/src/parse/aggregate.rs +2 -1
  485. data/rust/parser/src/parse/chapter.rs +2 -2
  486. data/rust/parser/src/parse/command.rs +2 -0
  487. data/rust/parser/src/parse/hecksagon.rs +5 -2
  488. data/rust/parser/src/parse/lifecycle.rs +81 -0
  489. data/rust/parser/src/parse/mod.rs +15 -1
  490. data/rust/parser/src/parse/policy.rs +13 -2
  491. data/rust/parser/src/parse/value_object.rs +34 -17
  492. data/rust/parser/src/ruby_value.rs +1 -1
  493. data/rust/src/exemplar/commands.rs +4 -4
  494. data/rust/src/exemplar/constraints.rs +1 -1
  495. data/rust/src/exemplar/json.rs +43 -1
  496. data/rust/src/kernel/cli.rs +7 -5
  497. data/rust/src/kernel/dispatch.rs +2 -2
  498. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  499. data/rust/src/kernel/expr.rs +66 -3
  500. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  501. data/rust/src/kernel/expression_operators/text.rs +26 -2
  502. data/rust/src/kernel/json.rs +15 -0
  503. data/rust/src/kernel/mod.rs +1 -1
  504. data/rust/src/kernel/orchestrate.rs +54 -5
  505. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  506. data/rust/src/kernel/pattern.rs +60 -0
  507. data/rust/src/kernel/reference_lookup.rs +90 -7
  508. metadata +178 -38
  509. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  510. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  511. data/lib/hecks/doors/cli_door.rb +0 -124
  512. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  513. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  514. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  515. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  516. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  517. data/lib/hecks/doors/cli_runner.rb +0 -66
  518. data/lib/hecks/doors/command_request.rb +0 -119
  519. data/lib/hecks/doors/handle.rb +0 -208
  520. data/lib/hecks/doors/json_door.rb +0 -132
  521. data/lib/hecks/doors/launcher_options.rb +0 -247
  522. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  523. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  524. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  525. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  526. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  527. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  528. data/lib/hecks/doors/mcp_door.rb +0 -156
  529. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  530. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  531. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  532. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  533. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  534. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  535. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  536. data/lib/hecks/doors/ruby_door.rb +0 -80
  537. data/lib/hecks/doors/usage_cache.rb +0 -139
  538. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
  539. data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
  540. data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
  541. data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
  542. data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
@@ -26,15 +26,15 @@ pub fn emit_check_invariants(exemplar: &Exemplar, vo: &Json, value_objects_by_na
26
26
  continue;
27
27
  }
28
28
  let field = naming::rust_ident_field(crate::attr::name(attr));
29
- if crate::attr::list(attr) {
30
- body.push(format!(" for item in &self.{field} {{ item.check_invariants()?; }}"));
31
- } else {
32
- body.push(format!(" self.{field}.check_invariants()?;"));
33
- }
29
+ body.push(nested_invariant_line(&field, crate::attr::list(attr), crate::attr::optional(attr)));
34
30
  }
35
31
 
36
32
  for attr in attributes {
37
33
  if crate::attr::list(attr) {
34
+ let field = format!("self.{}", naming::rust_ident_field(crate::attr::name(attr)));
35
+ if let Some(line) = crate::constraints::emit_list_pattern_check(exemplar, &field, attr, &name) {
36
+ body.push(format!(" {line}"));
37
+ }
38
38
  continue;
39
39
  }
40
40
  let field = format!("self.{}", naming::rust_ident_field(crate::attr::name(attr)));
@@ -64,6 +64,18 @@ pub fn emit_check_invariants(exemplar: &Exemplar, vo: &Json, value_objects_by_na
64
64
  format!("impl {name} {{\n pub fn check_invariants(&self) -> Result<(), crate::kernel::Refusal> {{\n{}\n Ok(())\n }}\n}}\n", body.join("\n"))
65
65
  }
66
66
 
67
+ /// The line that runs a nested value object's own invariants. An optional slot is an `Option` in
68
+ /// the record, so its invariants run only when it is present, as Ruby validates only what it is
69
+ /// given; a list's members each run theirs.
70
+ fn nested_invariant_line(field: &str, list: bool, optional: bool) -> String {
71
+ match (list, optional) {
72
+ (true, true) => format!(" if let Some(items) = &self.{field} {{ for item in items {{ item.check_invariants()?; }} }}"),
73
+ (true, false) => format!(" for item in &self.{field} {{ item.check_invariants()?; }}"),
74
+ (false, true) => format!(" if let Some(value) = &self.{field} {{ value.check_invariants()?; }}"),
75
+ (false, false) => format!(" self.{field}.check_invariants()?;"),
76
+ }
77
+ }
78
+
67
79
  pub fn emit_closed_set_table(exemplar: &Exemplar, vo: &Json) -> String {
68
80
  let name = naming::rust_ident(vo.get("name").and_then(Json::as_str).unwrap_or(""));
69
81
  let attributes = vo.get("attributes").map(Json::each).unwrap_or(&[]);
@@ -286,6 +298,83 @@ pub fn emit_record(exemplar: &Exemplar, aggregate: &Json, value_objects_by_name:
286
298
  format!("{struct_part}\n\n{fielded_part}")
287
299
  }
288
300
 
301
+ /// Copy of `ir` whose every `projects` field also carries the `type` of the remote field it reads,
302
+ /// so the projecting aggregate's pseudo-attribute and setter follow it (ADR 0025 addendum). A
303
+ /// single-field value object resolves to its one attribute's type, a reference to `String`, and a
304
+ /// field that resolves to nothing stays `String`.
305
+ pub fn with_resolved_projection_types(ir: &Json) -> Json {
306
+ let mut resolved = ir.clone();
307
+ let aggregates: Vec<Json> = ir.get("aggregates").map(Json::each).unwrap_or(&[]).to_vec();
308
+ let Some(list) = resolved.get_mut("aggregates").and_then(Json::as_array_mut) else {
309
+ return resolved;
310
+ };
311
+ for (aggregate, original) in list.iter_mut().zip(aggregates.iter()) {
312
+ let Some(fields) = aggregate.get_mut("projected_fields").and_then(Json::as_array_mut) else {
313
+ continue;
314
+ };
315
+ for field in fields.iter_mut() {
316
+ let scalar = projected_scalar_type(&aggregates, original, field, 0);
317
+ field.set("type", Json::String(scalar));
318
+ }
319
+ }
320
+ resolved
321
+ }
322
+
323
+ const PROJECTION_CHAIN_LIMIT: usize = 8;
324
+
325
+ // The scalar type a projection of `field` copies, following a chain of projections.
326
+ fn projected_scalar_type(aggregates: &[Json], owner: &Json, field: &Json, depth: usize) -> String {
327
+ let reference = field.get("reference").and_then(Json::as_str).unwrap_or("");
328
+ let remote = field.get("remote_field").and_then(Json::as_str).unwrap_or("");
329
+ let target = owner
330
+ .get("attributes")
331
+ .map(Json::each)
332
+ .unwrap_or(&[])
333
+ .iter()
334
+ .find(|a| crate::attr::name(a) == reference)
335
+ .and_then(|a| naming::reference_target(crate::attr::type_name(a)))
336
+ .and_then(|name| aggregates.iter().find(|a| a.get("name").and_then(Json::as_str) == Some(name)));
337
+ target.map(|t| remote_scalar_type(aggregates, t, remote, depth)).unwrap_or_else(|| "String".to_string())
338
+ }
339
+
340
+ fn remote_scalar_type(aggregates: &[Json], target: &Json, remote: &str, depth: usize) -> String {
341
+ let declared = target.get("attributes").map(Json::each).unwrap_or(&[]).iter().find(|a| crate::attr::name(a) == remote);
342
+ if let Some(attr) = declared {
343
+ return unwrapped_scalar_type(target, crate::attr::type_name(attr));
344
+ }
345
+ let chained = target.get("projected_fields").map(Json::each).unwrap_or(&[]).iter().find(|f| f.get("name").and_then(Json::as_str) == Some(remote));
346
+ match chained {
347
+ Some(next) if depth < PROJECTION_CHAIN_LIMIT => projected_scalar_type(aggregates, target, next, depth + 1),
348
+ _ => "String".to_string(),
349
+ }
350
+ }
351
+
352
+ // A single-field value object reads as its one attribute's type; anything else as itself.
353
+ fn unwrapped_scalar_type(aggregate: &Json, type_name: &str) -> String {
354
+ let sole = aggregate
355
+ .get("value_objects")
356
+ .map(Json::each)
357
+ .unwrap_or(&[])
358
+ .iter()
359
+ .find(|vo| vo.get("name").and_then(Json::as_str) == Some(type_name))
360
+ .map(|vo| vo.get("attributes").map(Json::each).unwrap_or(&[]))
361
+ .filter(|attrs| attrs.len() == 1 && !crate::attr::list(&attrs[0]))
362
+ .map(|attrs| crate::attr::type_name(&attrs[0]).to_string());
363
+ let resolved = sole.unwrap_or_else(|| type_name.to_string());
364
+ let resolved = if resolved == "Boolean" { "TrueClass".to_string() } else { resolved };
365
+ naming::effective_scalar_type(&resolved).unwrap_or("String").to_string()
366
+ }
367
+
368
+ // The `Value` reader a generated setter narrows with, by scalar type.
369
+ fn projection_reader(scalar: &str) -> &'static str {
370
+ match scalar {
371
+ "Integer" => "into_i64",
372
+ "Float" => "into_f64",
373
+ "TrueClass" | "FalseClass" => "into_bool",
374
+ _ => "into_string",
375
+ }
376
+ }
377
+
289
378
  /// Port of the retired Ruby generator's `types.rb#projected_field_pseudo_attributes` (ADR 0025).
290
379
  pub fn projected_field_pseudo_attributes(aggregate: &Json) -> Vec<Json> {
291
380
  aggregate
@@ -295,9 +384,10 @@ pub fn projected_field_pseudo_attributes(aggregate: &Json) -> Vec<Json> {
295
384
  .iter()
296
385
  .map(|field| {
297
386
  let name = field.get("name").and_then(Json::as_str).unwrap_or("").to_string();
387
+ let scalar = field.get("type").and_then(Json::as_str).unwrap_or("String").to_string();
298
388
  Json::Object(vec![
299
389
  ("name".to_string(), Json::String(name)),
300
- ("type".to_string(), Json::String("String".to_string())),
390
+ ("type".to_string(), Json::String(scalar)),
301
391
  ("list".to_string(), Json::Bool(false)),
302
392
  ("optional".to_string(), Json::Bool(true)),
303
393
  ])
@@ -329,11 +419,12 @@ pub fn emit_set_projected_field(aggregate: &Json) -> String {
329
419
  .map(|field| {
330
420
  let field_name = field.get("name").and_then(Json::as_str).unwrap_or("");
331
421
  let ident = naming::rust_ident_field(field_name);
332
- format!(" {} => self.{} = value,", naming::ruby_inspect_string(field_name), ident)
422
+ let reader = projection_reader(field.get("type").and_then(Json::as_str).unwrap_or("String"));
423
+ format!(" {} => self.{} = value.and_then(crate::kernel::Value::{}),", naming::ruby_inspect_string(field_name), ident, reader)
333
424
  })
334
425
  .collect();
335
426
  format!(
336
- "impl crate::kernel::SetProjectedField for {name} {{\n fn set_projected_field(&mut self, name: &'static str, value: Option<String>) {{\n match name {{\n{}\n _ => {{}}\n }}\n }}\n}}\n",
427
+ "impl crate::kernel::SetProjectedField for {name} {{\n fn set_projected_field(&mut self, name: &'static str, value: Option<crate::kernel::Value>) {{\n match name {{\n{}\n _ => {{}}\n }}\n }}\n}}\n",
337
428
  arms.join("\n")
338
429
  )
339
430
  }
@@ -404,4 +495,71 @@ mod tests {
404
495
  assert!(!from_json.contains("TrueClass::from_json"));
405
496
  assert!(to_json.contains("crate::kernel::Json::Bool(self.on)"));
406
497
  }
498
+
499
+ /// An Event whose `starts_at` is a single-field Integer value object, and a Booking projecting it.
500
+ fn event_and_booking() -> Json {
501
+ Json::parse(
502
+ r#"{"aggregates":[
503
+ {"name":"Event","attributes":[{"name":"starts_at","type":"StartsAt","list":false}],
504
+ "value_objects":[{"name":"StartsAt","attributes":[{"name":"value","type":"Integer","list":false}]}]},
505
+ {"name":"Booking","attributes":[{"name":"event","type":"Reference<Event>","list":false}],
506
+ "projected_fields":[{"name":"starts_at","reference":"event","remote_field":"starts_at"}]}
507
+ ]}"#,
508
+ )
509
+ .unwrap()
510
+ }
511
+
512
+ fn resolved_booking() -> Json {
513
+ with_resolved_projection_types(&event_and_booking()).get("aggregates").map(Json::each).unwrap_or(&[])[1].clone()
514
+ }
515
+
516
+ #[test]
517
+ fn a_projection_through_a_single_field_value_object_takes_its_scalar_type() {
518
+ let field = resolved_booking().get("projected_fields").map(Json::each).unwrap_or(&[])[0].clone();
519
+
520
+ assert_eq!(field.get("type").and_then(Json::as_str), Some("Integer"));
521
+ }
522
+
523
+ #[test]
524
+ fn the_projected_pseudo_attribute_follows_the_remote_type() {
525
+ let attrs = projected_field_pseudo_attributes(&resolved_booking());
526
+
527
+ assert_eq!(crate::attr::type_name(&attrs[0]), "Integer");
528
+ }
529
+
530
+ #[test]
531
+ fn the_projected_setter_narrows_with_the_remote_types_reader() {
532
+ let setter = emit_set_projected_field(&resolved_booking());
533
+
534
+ assert!(setter.contains("\"starts_at\" => self.starts_at = value.and_then(crate::kernel::Value::into_i64),"));
535
+ }
536
+
537
+ /// A value object holding a required and an optional value object, and a required and an
538
+ /// optional list of them.
539
+ fn holder_value_object() -> Json {
540
+ Json::parse(
541
+ r#"{
542
+ "name":"Holder",
543
+ "attributes":[
544
+ {"name":"required_note","type":"Note","list":false,"optional":false},
545
+ {"name":"note","type":"Note","list":false,"optional":true},
546
+ {"name":"required_notes","type":"Note","list":true,"optional":false},
547
+ {"name":"notes","type":"Note","list":true,"optional":true}
548
+ ]
549
+ }"#,
550
+ )
551
+ .unwrap()
552
+ }
553
+
554
+ #[test]
555
+ fn a_nested_value_object_checks_its_invariants_only_when_it_is_present() {
556
+ let note = Json::parse(r#"{"name":"Note","attributes":[{"name":"text","type":"String","list":false,"optional":false}]}"#).unwrap();
557
+ let by_name: HashMap<String, &Json> = HashMap::from([("Note".to_string(), &note)]);
558
+ let generated = emit_check_invariants(&Exemplar::load(), &holder_value_object(), &by_name, &HashMap::new());
559
+
560
+ assert!(generated.contains("self.required_note.check_invariants()?;"));
561
+ assert!(generated.contains("if let Some(value) = &self.note { value.check_invariants()?; }"));
562
+ assert!(generated.contains("for item in &self.required_notes { item.check_invariants()?; }"));
563
+ assert!(generated.contains("if let Some(items) = &self.notes { for item in items { item.check_invariants()?; } }"));
564
+ }
407
565
  }
@@ -1 +1 @@
1
- 3.10.0
1
+ 3.12.0
data/rust/host/src/api.rs CHANGED
@@ -1089,8 +1089,8 @@ mod tests {
1089
1089
  }
1090
1090
 
1091
1091
  #[test]
1092
- fn a_record_that_does_not_exist_refuses_with_json_doors_own_wording() {
1093
- // `JSON_DOOR.find!`'s own message, `id` inspected the way Ruby
1092
+ fn a_record_that_does_not_exist_refuses_with_json_adapters_own_wording() {
1093
+ // `JSON_ADAPTER.find!`'s own message, `id` inspected the way Ruby
1094
1094
  // inspects it.
1095
1095
  let refusal = not_found(&format!("no {} found for id {:?}", "Client", "acme"));
1096
1096
  assert_eq!(body(&refusal), json!({"error": "NotFound", "message": "no Client found for id \"acme\""}));
@@ -1365,7 +1365,7 @@ mod tests {
1365
1365
  fn every_state_styled() -> Value {
1366
1366
  json!({"states": {
1367
1367
  "Event": {"open": {"tone": "good"}, "closed": {"tone": "muted"}},
1368
- "Registration": {"active": {}, "archived": {"tone": "muted"}},
1368
+ "Registration": {"active": {}, "archived": {"tone": "muted"}, "cancelled": {"tone": "muted"}},
1369
1369
  "PaymentConnection": {"connected": {}, "enabled": {}, "disconnected": {}, "paused": {}}
1370
1370
  }})
1371
1371
  }
@@ -1590,7 +1590,7 @@ mod tests {
1590
1590
  assert!(!refused["message"].as_str().expect("a message").is_empty(), "{refused}");
1591
1591
 
1592
1592
  // An unknown command, and an unknown record, both refuse the
1593
- // way JsonDoor refuses them.
1593
+ // way Json refuses them.
1594
1594
  let unknown_command =
1595
1595
  command_route(&ir, "customers", "CUST-9001", "nope", "", &client, &wasm, &lineage, &invoker).await;
1596
1596
  assert_eq!(unknown_command["statusCode"], 404);
@@ -0,0 +1,43 @@
1
+ //! Rust side of the stored-value differential: runs the mint audit's structural layer
2
+ //! (`reference_validate::validate`) over each `{aggregate, id, state}` case on stdin against one
3
+ //! domain IR, and prints the violations of each. `spec/rust_host_reference_validate_conformance_spec.rb`
4
+ //! holds those to the answers the Ruby runtime gives the same values.
5
+
6
+ #[allow(dead_code)]
7
+ #[path = "../expr_json.rs"]
8
+ mod expr_json;
9
+ #[allow(dead_code)]
10
+ #[path = "../reference_validate.rs"]
11
+ mod reference_validate;
12
+
13
+ use serde_json::{json, Value};
14
+ use std::io::Read;
15
+
16
+ // Usage: `validate_harness < {"ir": <domain ir.json>, "cases": [{"aggregate": "Name", "id": "x", "state": {..}}, ..]}`.
17
+ // Prints `{"results": [{"violations": [..]} | {"error": ".."}, ..]}`, one per case, in order.
18
+ fn main() -> anyhow::Result<()> {
19
+ let mut input = String::new();
20
+ std::io::stdin().read_to_string(&mut input)?;
21
+ let request: Value = serde_json::from_str(&input)?;
22
+ let ir = request.get("ir").ok_or_else(|| anyhow::anyhow!("no \"ir\" on stdin"))?;
23
+ let cases = request.get("cases").and_then(Value::as_array).ok_or_else(|| anyhow::anyhow!("no \"cases\" array on stdin"))?;
24
+
25
+ let results: Vec<Value> = cases.iter().map(|case| answer(ir, case)).collect();
26
+ println!("{}", json!({ "results": results }));
27
+ Ok(())
28
+ }
29
+
30
+ fn answer(ir: &Value, case: &Value) -> Value {
31
+ let name = case.get("aggregate").and_then(Value::as_str).unwrap_or("");
32
+ let aggregate = ir
33
+ .get("aggregates")
34
+ .and_then(Value::as_array)
35
+ .and_then(|list| list.iter().find(|a| a.get("name").and_then(Value::as_str) == Some(name)));
36
+ match aggregate {
37
+ None => json!({ "error": format!("no aggregate {name:?} in the IR") }),
38
+ Some(aggregate) => {
39
+ let id = case.get("id").and_then(Value::as_str).unwrap_or("");
40
+ json!({ "violations": reference_validate::validate(ir, aggregate, id, &case["state"]) })
41
+ }
42
+ }
43
+ }
@@ -8,7 +8,7 @@ use crate::fuzz_support::*;
8
8
  use serde_json::{json, Value};
9
9
 
10
10
  const BINARY: &[&str] = &["or", "and", "compare", "include", "add", "modulo"];
11
- const UNARY: &[&str] = &["not", "sign_test", "empty", "to_s", "size", "presence", "assignment", "first", "last", "split", "starts_with", "ends_with", "matches_regex"];
11
+ const UNARY: &[&str] = &["not", "sign_test", "empty", "to_s", "size", "presence", "assignment", "first", "last", "split", "strip", "starts_with", "ends_with", "matches_regex"];
12
12
 
13
13
  const PATTERNS: &[&str] = &[
14
14
  "", "^$", "a", "(a*)*b", "(a|aa)+$", "[", "]", "[]", "[^]", "[a-", "(", ")", "(?", "(?P<x>", "\\", "\\d+", "\\D\\W\\S\\H", "\\Z", "[\\d]", "[\\Z]", "\\p{L}+", "\\p{Nope}",
@@ -43,7 +43,7 @@ fn node(rng: &mut Rng, depth: usize) -> Value {
43
43
  }
44
44
  4..=7 => {
45
45
  let op = *rng.pick(UNARY);
46
- let mut value = json!({"op": op, "receiver": node(rng, depth + 1), "expr": node(rng, depth + 1), "negated": rng.chance(2), "separator": random_text(rng), "substring": random_text(rng), "pattern": *rng.pick(PATTERNS), "flags": *rng.pick(&["", "i", "m", "x", "imx", "z"])});
46
+ let mut value = json!({"op": op, "receiver": node(rng, depth + 1), "expr": node(rng, depth + 1), "negated": rng.chance(2), "separator": random_text(rng), "side": *rng.pick(&["both", "left", "right", "sideways"]), "substring": random_text(rng), "pattern": *rng.pick(PATTERNS), "flags": *rng.pick(&["", "i", "m", "x", "imx", "z"])});
47
47
  value["cmp"] = json!({"less_than": rng.chance(2), "equal": rng.chance(2), "negated": rng.chance(2)});
48
48
  value
49
49
  }
@@ -50,6 +50,7 @@ fn every_provider_reader_answers_any_ir_shape() {
50
50
  let _ = newsletter_provider(&ir).map(|p| p.instance_prefix());
51
51
  let _ = newsletter_issues_provider(&ir).map(|p| p.issue_prefix());
52
52
  let _ = payments_provider(&ir).map(|p| p.instance_prefix());
53
+ let _ = checkout_windows(&ir);
53
54
  if let Some(p) = registrations_provider(&ir) {
54
55
  let _ = (p.event_prefix(), p.registration_prefix(), p.request_target());
55
56
  }
@@ -1,4 +1,4 @@
1
- //! Seeded fuzz of the HTTP front door (`admit`, `value_to_response`): hostile bodies, methods, URIs
1
+ //! Seeded fuzz of the HTTP front entry point (`admit`, `value_to_response`): hostile bodies, methods, URIs
2
2
  //! and headers must be answered, never panic, and a refused request must leave no trace.
3
3
  //! Everything here is in-process: `admit` is the whole of what runs before dispatch, so none of
4
4
  //! it needs Postgres or wasm.
@@ -7,10 +7,6 @@ use sha2::Sha256;
7
7
 
8
8
  type HmacSha256 = Hmac<Sha256>;
9
9
 
10
- // docs.stripe.com/webhooks#verify-events — 5 minutes, guards against a
11
- // replayed old payload even against a leaked (not yet rotated) secret.
12
- const TOLERANCE_SECONDS: i64 = 300;
13
-
14
10
  pub struct SignatureError(pub String);
15
11
 
16
12
  impl std::fmt::Display for SignatureError {
@@ -22,7 +18,10 @@ impl std::fmt::Display for SignatureError {
22
18
  // Signs `<timestamp>.<raw body>`, the exact bytes Stripe sent -- re-serializing
23
19
  // the JSON would disagree on whitespace or key order and break every match.
24
20
  // `now` is a parameter, not read internally, so a test can hold time fixed.
25
- pub fn verify_signature(payload: &str, header: &str, secret: &str, now: i64) -> Result<(), SignatureError> {
21
+ // `tolerance_seconds` is the freshness window the Checkout bluebook declares
22
+ // (`WebhookReceipt.tolerance`, read from the IR): it guards against a replayed
23
+ // old payload even against a leaked (not yet rotated) secret.
24
+ pub fn verify_signature(payload: &str, header: &str, secret: &str, now: i64, tolerance_seconds: i64) -> Result<(), SignatureError> {
26
25
  let mut timestamp: Option<i64> = None;
27
26
  let mut signatures: Vec<&str> = Vec::new();
28
27
  for part in header.split(',') {
@@ -39,9 +38,9 @@ pub fn verify_signature(payload: &str, header: &str, secret: &str, now: i64) ->
39
38
  if signatures.is_empty() {
40
39
  return Err(SignatureError("Stripe-Signature header has no v1= signature".to_string()));
41
40
  }
42
- if (now - timestamp).abs() > TOLERANCE_SECONDS {
41
+ if (now - timestamp).abs() > tolerance_seconds {
43
42
  return Err(SignatureError(format!(
44
- "timestamp {timestamp} is outside the {TOLERANCE_SECONDS}s tolerance (now: {now})"
43
+ "timestamp {timestamp} is outside the {tolerance_seconds}s tolerance (now: {now})"
45
44
  )));
46
45
  }
47
46
 
@@ -98,11 +97,19 @@ pub struct EmbeddedSession {
98
97
  pub client_secret: String,
99
98
  }
100
99
 
101
- // 31, not 30: Stripe requires an expiry at least 30 minutes out.
102
- pub const SESSION_HOLD_SECONDS: i64 = 31 * 60;
100
+ // 31, not 30: Stripe requires an expiry at least 30 minutes out. This floor is a
101
+ // fact about the processor, so it lives here in the adapter; the domain declares
102
+ // only how long a session holds its seat (`CheckoutSession.hold`), and a hold
103
+ // shorter than the floor is raised to it.
104
+ pub const STRIPE_MIN_HOLD_SECONDS: i64 = 31 * 60;
105
+
106
+ /// The hold actually sent to Stripe for a domain hold of `hold_seconds`.
107
+ pub fn effective_hold_seconds(hold_seconds: i64) -> i64 {
108
+ hold_seconds.max(STRIPE_MIN_HOLD_SECONDS)
109
+ }
103
110
 
104
- pub fn session_expires_at(now: i64) -> i64 {
105
- now + SESSION_HOLD_SECONDS
111
+ pub fn session_expires_at(now: i64, hold_seconds: i64) -> i64 {
112
+ now + effective_hold_seconds(hold_seconds)
106
113
  }
107
114
 
108
115
  // Errors carry Stripe's message when it refuses, never a secret.
@@ -158,6 +165,8 @@ pub async fn create_checkout_session(
158
165
  mod tests {
159
166
  use super::*;
160
167
 
168
+ const TOLERANCE_SECONDS: i64 = 300;
169
+
161
170
  fn sign(secret: &str, timestamp: i64, payload: &str) -> String {
162
171
  let signed_payload = format!("{timestamp}.{payload}");
163
172
  let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
@@ -195,7 +204,7 @@ mod tests {
195
204
  let now = 1_700_000_000;
196
205
  let header = format!("t={now},v1={}", sign(secret, now, payload));
197
206
 
198
- assert!(verify_signature(payload, &header, secret, now).is_ok());
207
+ assert!(verify_signature(payload, &header, secret, now, TOLERANCE_SECONDS).is_ok());
199
208
  }
200
209
 
201
210
  #[test]
@@ -207,7 +216,7 @@ mod tests {
207
216
  // Same header, different body — exactly what an attacker
208
217
  // intercepting and rewriting the request in transit would send.
209
218
  let tampered = r#"{"type":"checkout.session.expired"}"#;
210
- assert!(verify_signature(tampered, &header, secret, now).is_err());
219
+ assert!(verify_signature(tampered, &header, secret, now, TOLERANCE_SECONDS).is_err());
211
220
  }
212
221
 
213
222
  #[test]
@@ -216,7 +225,7 @@ mod tests {
216
225
  let now = 1_700_000_000;
217
226
  let header = format!("t={now},v1={}", sign("whsec_real", now, payload));
218
227
 
219
- assert!(verify_signature(payload, &header, "whsec_wrong", now).is_err());
228
+ assert!(verify_signature(payload, &header, "whsec_wrong", now, TOLERANCE_SECONDS).is_err());
220
229
  }
221
230
 
222
231
  #[test]
@@ -231,7 +240,14 @@ mod tests {
231
240
  // to be a separate, independent gate rather than folded into
232
241
  // "does the signature verify at all."
233
242
  let much_later = signed_at + TOLERANCE_SECONDS + 1;
234
- assert!(verify_signature(payload, &header, secret, much_later).is_err());
243
+ assert!(verify_signature(payload, &header, secret, much_later, TOLERANCE_SECONDS).is_err());
244
+ }
245
+
246
+ #[test]
247
+ fn a_hold_shorter_than_stripes_floor_is_raised_to_it() {
248
+ assert_eq!(effective_hold_seconds(1800), STRIPE_MIN_HOLD_SECONDS);
249
+ assert_eq!(effective_hold_seconds(3600), 3600);
250
+ assert_eq!(session_expires_at(1_000, 1800), 1_000 + STRIPE_MIN_HOLD_SECONDS);
235
251
  }
236
252
 
237
253
  #[test]
@@ -246,20 +262,20 @@ mod tests {
246
262
  sign(new_secret, now, payload)
247
263
  );
248
264
 
249
- assert!(verify_signature(payload, &header, old_secret, now).is_ok());
250
- assert!(verify_signature(payload, &header, new_secret, now).is_ok());
265
+ assert!(verify_signature(payload, &header, old_secret, now, TOLERANCE_SECONDS).is_ok());
266
+ assert!(verify_signature(payload, &header, new_secret, now, TOLERANCE_SECONDS).is_ok());
251
267
  }
252
268
 
253
269
  #[test]
254
270
  fn a_header_with_no_v1_at_all_is_rejected() {
255
271
  let now = 1_700_000_000;
256
272
  let header = format!("t={now}");
257
- assert!(verify_signature("{}", &header, "whsec_test", now).is_err());
273
+ assert!(verify_signature("{}", &header, "whsec_test", now, TOLERANCE_SECONDS).is_err());
258
274
  }
259
275
 
260
276
  #[test]
261
277
  fn a_header_with_no_timestamp_at_all_is_rejected() {
262
278
  let header = "v1=deadbeef".to_string();
263
- assert!(verify_signature("{}", &header, "whsec_test", 1_700_000_000).is_err());
279
+ assert!(verify_signature("{}", &header, "whsec_test", 1_700_000_000, TOLERANCE_SECONDS).is_err());
264
280
  }
265
281
  }