hecks 3.13.0 → 4.0.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 (440) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/adapters/fake_processor.rb +34 -0
  3. data/examples/pizzas/adapters/payment_signature.rb +66 -0
  4. data/examples/pizzas/adapters/payment_webhook.rb +87 -0
  5. data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
  6. data/examples/pizzas/bluebook/pizzas.bluebook +4 -4
  7. data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
  8. data/examples/pizzas/config.ru +22 -0
  9. data/lib/hecks/adapters/driven/prism.rb +0 -2
  10. data/lib/hecks/adapters/driven.rb +0 -2
  11. data/lib/hecks/adapters/driving/json.rb +1 -7
  12. data/lib/hecks/bluebook/dsl/attribute_collector.rb +15 -5
  13. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
  14. data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
  15. data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
  16. data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
  17. data/lib/hecks/bluebook/hecksagon.rb +1 -1
  18. data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
  19. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
  20. data/lib/hecks/bluebook/meta_validator.rb +0 -11
  21. data/lib/hecks/bluebook/named_pattern.rb +33 -0
  22. data/lib/hecks/bluebook/translation.rb +18 -10
  23. data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
  24. data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
  25. data/lib/hecks/bluebook/translation_expression.rb +39 -0
  26. data/lib/hecks/bluebook/translation_plan.rb +70 -0
  27. data/lib/hecks/bluebook.rb +3 -0
  28. data/lib/hecks/chapters.rb +3 -3
  29. data/lib/hecks/cli/domain_stub.rb +1 -1
  30. data/lib/hecks/cli/interview_aggregate.rb +4 -4
  31. data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
  32. data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
  33. data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
  34. data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
  35. data/lib/hecks/composer/adapters/jev.adapter +3 -0
  36. data/lib/hecks/composer/adapters/jev.rb +66 -0
  37. data/lib/hecks/composer/agent.rb +113 -0
  38. data/lib/hecks/composer/assistant.rb +110 -0
  39. data/lib/hecks/composer/behavior_run.rb +53 -0
  40. data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
  41. data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
  42. data/lib/hecks/composer/board.rb +65 -0
  43. data/lib/hecks/composer/board_edit/command_text.rb +39 -0
  44. data/lib/hecks/composer/board_edit/source.rb +84 -0
  45. data/lib/hecks/composer/board_edit.rb +144 -0
  46. data/lib/hecks/composer/check.rb +89 -0
  47. data/lib/hecks/composer/domains.rb +81 -0
  48. data/lib/hecks/composer/jobs.rb +134 -0
  49. data/lib/hecks/composer/layout.rb +37 -0
  50. data/lib/hecks/composer/live.rb +150 -0
  51. data/lib/hecks/composer/outline.rb +48 -0
  52. data/lib/hecks/composer/page.html +989 -0
  53. data/lib/hecks/composer/panels.rb +142 -0
  54. data/lib/hecks/composer/parse.rb +83 -0
  55. data/lib/hecks/composer/plain.rb +28 -0
  56. data/lib/hecks/composer/ports/composer_agent.port +7 -0
  57. data/lib/hecks/composer/session.rb +141 -0
  58. data/lib/hecks/composer/toolbox.rb +166 -0
  59. data/lib/hecks/composer/web.rb +156 -0
  60. data/lib/hecks/composer/workbench.rb +51 -0
  61. data/lib/hecks/composer/workspace.rb +81 -0
  62. data/lib/hecks/composer.rb +17 -0
  63. data/lib/hecks/corpus/adapters.rb +22 -0
  64. data/lib/hecks/corpus/rust.rb +1 -1
  65. data/lib/hecks/corpus/worlds.rb +19 -0
  66. data/lib/hecks/corpus.rb +5 -0
  67. data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -34
  68. data/lib/hecks/forms/app/commands.rb +2 -3
  69. data/lib/hecks/forms/app/queries.rb +2 -3
  70. data/lib/hecks/forms/app/records.rb +17 -5
  71. data/lib/hecks/forms/command_form_renderer.rb +42 -16
  72. data/lib/hecks/forms/field_controls.rb +2 -2
  73. data/lib/hecks/forms/field_renderer.rb +1 -1
  74. data/lib/hecks/forms/field_shape.rb +36 -17
  75. data/lib/hecks/forms/index_renderer.rb +2 -1
  76. data/lib/hecks/forms/page.rb +1 -1
  77. data/lib/hecks/forms/params.rb +0 -13
  78. data/lib/hecks/forms/query_form_renderer.rb +7 -6
  79. data/lib/hecks/forms/record_renderer.rb +11 -7
  80. data/lib/hecks/forms/rule_words.rb +100 -0
  81. data/lib/hecks/forms/value_object_shape.rb +2 -1
  82. data/lib/hecks/framework/bluebook/change.bluebook +2 -2
  83. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
  84. data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
  85. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
  86. data/lib/hecks/fuzzing/replay/filters.rb +2 -6
  87. data/lib/hecks/gate/stages.yml +25 -0
  88. data/lib/hecks/grammar/evolve/seeds.rb +0 -6
  89. data/lib/hecks/grammar/evolve.rb +0 -3
  90. data/lib/hecks/grammar/translation.bluebook +1 -1
  91. data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
  92. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
  93. data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
  94. data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
  95. data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
  96. data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
  97. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
  98. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
  99. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
  100. data/lib/hecks/hecks/codebase.behaviors +6 -0
  101. data/lib/hecks/hecks/codebase.bluebook +58 -37
  102. data/lib/hecks/hecks/composer.behaviors +68 -0
  103. data/lib/hecks/hecks/context_map.hecksagon +1 -1
  104. data/lib/hecks/hecks/custodian.behaviors +0 -6
  105. data/lib/hecks/hecks/custodian.bluebook +15 -92
  106. data/lib/hecks/hecks/hecks.bluebook +2 -2
  107. data/lib/hecks/hecks/hecks.hecksagon +2 -2
  108. data/lib/hecks/hecks/hecks.world +1 -1
  109. data/lib/hecks/hecks/rehearsal.bluebook +10 -16
  110. data/lib/hecks/hecks/tooling.bluebook +22 -22
  111. data/lib/hecks/kernel_capabilities/templates.rb +4 -4
  112. data/lib/hecks/kernel_capabilities.rb +1 -1
  113. data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
  114. data/lib/hecks/language/bluebook/command.bluebook +1 -0
  115. data/lib/hecks/language/bluebook/entity.bluebook +1 -0
  116. data/lib/hecks/language/bluebook/query.bluebook +1 -0
  117. data/lib/hecks/language/bluebook/shape.bluebook +8 -7
  118. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
  119. data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
  120. data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
  121. data/lib/hecks/ports/access_control.rb +2 -2
  122. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
  123. data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
  124. data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
  125. data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
  126. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
  127. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
  128. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
  129. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
  130. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
  131. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
  132. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
  133. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
  134. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
  135. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
  136. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
  137. data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
  138. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
  139. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
  140. data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
  141. data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
  142. data/lib/hecks/ports.rb +0 -1
  143. data/lib/hecks/projection_files.rb +3 -1
  144. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  145. data/lib/hecks/projections/glossary/entries.rb +10 -4
  146. data/lib/hecks/projections/glossary/groups.rb +42 -0
  147. data/lib/hecks/projections/glossary/html/page.rb +7 -2
  148. data/lib/hecks/projections/glossary/html/parser.rb +1 -1
  149. data/lib/hecks/projections/glossary/html/sections.rb +31 -11
  150. data/lib/hecks/projections/glossary/html.rb +1 -2
  151. data/lib/hecks/projections/glossary/markdown.rb +31 -28
  152. data/lib/hecks/projections/glossary/mermaid.rb +3 -0
  153. data/lib/hecks/projections/glossary/page.css +143 -153
  154. data/lib/hecks/projections/glossary/page.js +63 -3
  155. data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
  156. data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
  157. data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
  158. data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
  159. data/lib/hecks/projections/glossary/sentences.rb +13 -3
  160. data/lib/hecks/projections/glossary/slugs.rb +4 -1
  161. data/lib/hecks/projections/glossary.rb +2 -1
  162. data/lib/hecks/projections/plain_english.rb +97 -0
  163. data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
  164. data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
  165. data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
  166. data/lib/hecks/projections/rust_vocabulary.rb +14 -6
  167. data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
  168. data/lib/hecks/projections/statements/actions.rb +99 -0
  169. data/lib/hecks/projections/statements/relationships.rb +86 -0
  170. data/lib/hecks/projections/statements.rb +15 -24
  171. data/lib/hecks/projector/exporter/adapters.rb +112 -0
  172. data/lib/hecks/projector/exporter/translations.rb +18 -35
  173. data/lib/hecks/projector/exporter/worlds.rb +103 -0
  174. data/lib/hecks/projector/exporter.rb +5 -1
  175. data/lib/hecks/quality_control/adapters/agent.rb +6 -7
  176. data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
  177. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
  178. data/lib/hecks/quality_control/quality_control.bluebook +51 -51
  179. data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
  180. data/lib/hecks/runtime/outbox.rb +2 -9
  181. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
  182. data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
  183. data/lib/hecks/runtime/value.rb +13 -0
  184. data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
  185. data/lib/hecks/rust_build/wasm.rb +0 -12
  186. data/lib/hecks/rust_build/write_if_changed.rb +12 -12
  187. data/lib/hecks/rust_format.rb +77 -0
  188. data/lib/hecks/site/bluebook/site.bluebook +5 -5
  189. data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
  190. data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
  191. data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
  192. data/lib/hecks/tools/fuzz_sweep.rb +1 -3
  193. data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
  194. data/lib/hecks/tools/translation_audit.rb +8 -10
  195. data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
  196. data/lib/hecks/version.rb +1 -1
  197. data/lib/hecks/vocabulary.rb +2 -1
  198. data/lib/hecks.rb +3 -0
  199. data/prebuilt/{58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
  200. data/prebuilt/{2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
  201. data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +1 -0
  202. data/rust/Cargo.toml +40 -0
  203. data/rust/build/Cargo.toml +36 -0
  204. data/rust/build/clippy.toml +4 -0
  205. data/rust/build/src/adapters_pass.rs +196 -0
  206. data/rust/build/src/binding_pass.rs +54 -13
  207. data/rust/build/src/build_artifact.rs +23 -5
  208. data/rust/build/src/cargo_sync.rs +69 -26
  209. data/rust/build/src/lineage_pass.rs +101 -87
  210. data/rust/build/src/main.rs +11 -2
  211. data/rust/build/src/pipeline.rs +80 -19
  212. data/rust/build/src/reserved_names.rs +6 -50
  213. data/rust/build/src/resolve.rs +82 -35
  214. data/rust/build/src/rustfmt.rs +100 -0
  215. data/rust/build/src/seams_pass.rs +85 -16
  216. data/rust/build/src/subprocess.rs +30 -6
  217. data/rust/build/src/tmp.rs +5 -2
  218. data/rust/build/src/translation_pass.rs +72 -22
  219. data/rust/build/src/world_pass.rs +109 -0
  220. data/rust/clippy.toml +4 -0
  221. data/rust/codegen/Cargo.toml +36 -0
  222. data/rust/codegen/clippy.toml +4 -0
  223. data/rust/codegen/src/asks.rs +40 -14
  224. data/rust/codegen/src/attr.rs +2 -2
  225. data/rust/codegen/src/bridging.rs +676 -117
  226. data/rust/codegen/src/commands.rs +979 -373
  227. data/rust/codegen/src/constraints.rs +88 -25
  228. data/rust/codegen/src/dependency_planning.rs +128 -34
  229. data/rust/codegen/src/domain_generator.rs +300 -170
  230. data/rust/codegen/src/exemplar.rs +87 -32
  231. data/rust/codegen/src/expr_emitter.rs +54 -6
  232. data/rust/codegen/src/fielded.rs +193 -41
  233. data/rust/codegen/src/hecks_naming.rs +124 -16
  234. data/rust/codegen/src/hecks_naming_tests.rs +81 -0
  235. data/rust/codegen/src/json.rs +80 -44
  236. data/rust/codegen/src/json_codec.rs +585 -149
  237. data/rust/codegen/src/literal.rs +104 -138
  238. data/rust/codegen/src/main.rs +145 -52
  239. data/rust/codegen/src/manifest.rs +76 -14
  240. data/rust/codegen/src/mutations.rs +1212 -239
  241. data/rust/codegen/src/naming.rs +111 -60
  242. data/rust/codegen/src/optional_pass.rs +90 -28
  243. data/rust/codegen/src/ports.rs +5 -4
  244. data/rust/codegen/src/prelude.rs +139 -49
  245. data/rust/codegen/src/queries.rs +601 -226
  246. data/rust/codegen/src/reactions.rs +268 -74
  247. data/rust/codegen/src/read_models.rs +606 -205
  248. data/rust/codegen/src/reference_specs.rs +16 -3
  249. data/rust/codegen/src/registry.rs +128 -79
  250. data/rust/codegen/src/reserved_names.rs +6 -50
  251. data/rust/codegen/src/shared.rs +71 -22
  252. data/rust/codegen/src/sidecars.rs +15 -5
  253. data/rust/codegen/src/skip_reason.rs +8 -2
  254. data/rust/codegen/src/types.rs +306 -108
  255. data/rust/host/Cargo.lock +1 -0
  256. data/rust/host/Cargo.toml +40 -4
  257. data/rust/host/HECKS_RELEASE +1 -1
  258. data/rust/host/clippy.toml +4 -0
  259. data/rust/host/src/api.rs +698 -218
  260. data/rust/host/src/auth.rs +491 -214
  261. data/rust/host/src/b64.rs +91 -0
  262. data/rust/host/src/bin/expr_harness.rs +6 -2
  263. data/rust/host/src/bin/lineage_harness.rs +47 -11
  264. data/rust/host/src/bin/mint_harness.rs +70 -37
  265. data/rust/host/src/bin/validate_harness.rs +16 -8
  266. data/rust/host/src/boot.rs +144 -68
  267. data/rust/host/src/boundary_fuzz/api.rs +130 -32
  268. data/rust/host/src/boundary_fuzz/auth.rs +108 -23
  269. data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
  270. data/rust/host/src/boundary_fuzz/ir.rs +164 -12
  271. data/rust/host/src/boundary_fuzz/server.rs +283 -36
  272. data/rust/host/src/boundary_fuzz/web.rs +56 -8
  273. data/rust/host/src/checkout.rs +78 -32
  274. data/rust/host/src/clock.rs +8 -0
  275. data/rust/host/src/commerce_ir.rs +333 -95
  276. data/rust/host/src/crypto.rs +33 -0
  277. data/rust/host/src/dispatch.rs +990 -331
  278. data/rust/host/src/expr_json.rs +1021 -273
  279. data/rust/host/src/extension.rs +24 -4
  280. data/rust/host/src/field_hints.rs +11 -7
  281. data/rust/host/src/fuzz_support.rs +85 -13
  282. data/rust/host/src/host.rs +100 -0
  283. data/rust/host/src/http.rs +22 -0
  284. data/rust/host/src/ir.rs +326 -16
  285. data/rust/host/src/journal.rs +277 -176
  286. data/rust/host/src/lambda_client.rs +219 -53
  287. data/rust/host/src/lib.rs +16 -4
  288. data/rust/host/src/log.rs +62 -11
  289. data/rust/host/src/mint.rs +1153 -326
  290. data/rust/host/src/needs.rs +185 -44
  291. data/rust/host/src/payments/keystore.rs +197 -45
  292. data/rust/host/src/payments/roles.rs +191 -0
  293. data/rust/host/src/payments/tests.rs +1445 -275
  294. data/rust/host/src/payments.rs +409 -121
  295. data/rust/host/src/platform_signin/attack_tests.rs +5 -5
  296. data/rust/host/src/platform_signin/config.rs +11 -11
  297. data/rust/host/src/platform_signin/cookie.rs +2 -2
  298. data/rust/host/src/platform_signin/extension.rs +2 -1
  299. data/rust/host/src/platform_signin/handler.rs +9 -8
  300. data/rust/host/src/platform_signin/jti.rs +4 -2
  301. data/rust/host/src/platform_signin/jwks.rs +2 -1
  302. data/rust/host/src/platform_signin/jws.rs +2 -37
  303. data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
  304. data/rust/host/src/platform_signin/mode_tests.rs +5 -3
  305. data/rust/host/src/platform_signin/pages.rs +20 -20
  306. data/rust/host/src/platform_signin/session.rs +8 -1
  307. data/rust/host/src/platform_signin/settings.rs +6 -6
  308. data/rust/host/src/platform_signin/strict_json.rs +12 -9
  309. data/rust/host/src/platform_signin/verify.rs +41 -41
  310. data/rust/host/src/platform_signin/wiring_support.rs +13 -18
  311. data/rust/host/src/presentation.rs +207 -63
  312. data/rust/host/src/presentation_write.rs +502 -187
  313. data/rust/host/src/query_step.rs +69 -18
  314. data/rust/host/src/rate_limit.rs +541 -142
  315. data/rust/host/src/reference_transform.rs +77 -25
  316. data/rust/host/src/reference_validate.rs +443 -104
  317. data/rust/host/src/resend.rs +190 -55
  318. data/rust/host/src/secrets.rs +116 -21
  319. data/rust/host/src/server.rs +483 -104
  320. data/rust/host/src/storage_shape.rs +88 -21
  321. data/rust/host/src/test_pg.rs +17 -7
  322. data/rust/host/src/ui_schema.rs +495 -207
  323. data/rust/host/src/wasm_runner.rs +21 -6
  324. data/rust/host/src/web/commerce.rs +101 -23
  325. data/rust/host/src/web/newsletter/cooldown.rs +10 -3
  326. data/rust/host/src/web/newsletter/template.rs +26 -7
  327. data/rust/host/src/web/newsletter/tests.rs +112 -23
  328. data/rust/host/src/web/newsletter.rs +209 -59
  329. data/rust/host/src/web/newsletter_send.rs +251 -79
  330. data/rust/host/src/web/registration_receipt/tests.rs +16 -4
  331. data/rust/host/src/web/registration_receipt.rs +100 -24
  332. data/rust/host/src/web/registrations.rs +1384 -332
  333. data/rust/host/src/web/resend_webhook.rs +138 -80
  334. data/rust/host/src/web.rs +2416 -508
  335. data/rust/lsp/Cargo.toml +36 -0
  336. data/rust/lsp/clippy.toml +4 -0
  337. data/rust/lsp/src/json.rs +29 -22
  338. data/rust/lsp/src/main.rs +81 -31
  339. data/rust/lsp/src/outline.rs +23 -7
  340. data/rust/lsp/src/rpc.rs +17 -4
  341. data/rust/parser/Cargo.toml +36 -0
  342. data/rust/parser/clippy.toml +4 -0
  343. data/rust/parser/src/build/closed_sets.rs +6 -1
  344. data/rust/parser/src/build/identity.rs +4 -3
  345. data/rust/parser/src/build/mod.rs +1 -0
  346. data/rust/parser/src/build/named_pattern.rs +26 -0
  347. data/rust/parser/src/build/naming.rs +2 -80
  348. data/rust/parser/src/build/pattern_subset.rs +6 -8
  349. data/rust/parser/src/build/query_derive.rs +3 -1
  350. data/rust/parser/src/build/query_inference.rs +14 -5
  351. data/rust/parser/src/build/read_model.rs +4 -1
  352. data/rust/parser/src/build/references.rs +4 -3
  353. data/rust/parser/src/canonical.rs +34 -15
  354. data/rust/parser/src/emit.rs +187 -105
  355. data/rust/parser/src/expr/ast_json.rs +134 -42
  356. data/rust/parser/src/expr/evaluator.rs +27 -9
  357. data/rust/parser/src/expr/mod.rs +54 -13
  358. data/rust/parser/src/expr/resolver.rs +299 -92
  359. data/rust/parser/src/ir.rs +35 -3
  360. data/rust/parser/src/keywords.rs +7168 -510
  361. data/rust/parser/src/lex.rs +11 -25
  362. data/rust/parser/src/lib.rs +1 -0
  363. data/rust/parser/src/main.rs +65 -6
  364. data/rust/parser/src/parse/adapter.rs +157 -0
  365. data/rust/parser/src/parse/aggregate.rs +19 -10
  366. data/rust/parser/src/parse/chapter.rs +16 -17
  367. data/rust/parser/src/parse/command.rs +35 -16
  368. data/rust/parser/src/parse/domain_port.rs +4 -7
  369. data/rust/parser/src/parse/entity.rs +21 -11
  370. data/rust/parser/src/parse/hecksagon.rs +10 -4
  371. data/rust/parser/src/parse/lifecycle.rs +9 -5
  372. data/rust/parser/src/parse/mod.rs +43 -91
  373. data/rust/parser/src/parse/needs.rs +21 -12
  374. data/rust/parser/src/parse/policy.rs +2 -2
  375. data/rust/parser/src/parse/process_manager.rs +9 -11
  376. data/rust/parser/src/parse/query.rs +8 -4
  377. data/rust/parser/src/parse/read_model.rs +57 -16
  378. data/rust/parser/src/parse/value_object.rs +7 -2
  379. data/rust/parser/src/parse/world.rs +322 -0
  380. data/rust/parser/src/ruby_value.rs +7 -6
  381. data/rust/src/exemplar/json.rs +4 -4
  382. data/rust/src/exemplar/mutations.rs +18 -0
  383. data/rust/src/exemplar/read_models.rs +1 -10
  384. data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
  385. data/rust/src/kernel/attribute_shapes/list.rs +2 -2
  386. data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
  387. data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
  388. data/rust/src/kernel/cli.rs +229 -112
  389. data/rust/src/kernel/dispatch.rs +323 -115
  390. data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
  391. data/rust/src/kernel/expr.rs +203 -86
  392. data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
  393. data/rust/src/kernel/expression_operators/comparison.rs +87 -2
  394. data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
  395. data/rust/src/kernel/expression_operators/logical.rs +9 -3
  396. data/rust/src/kernel/expression_operators/membership.rs +3 -1
  397. data/rust/src/kernel/expression_operators/mod.rs +34 -21
  398. data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
  399. data/rust/src/kernel/expression_operators/positional.rs +84 -30
  400. data/rust/src/kernel/expression_operators/presence.rs +52 -13
  401. data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
  402. data/rust/src/kernel/expression_operators/sized.rs +24 -8
  403. data/rust/src/kernel/expression_operators/text.rs +120 -27
  404. data/rust/src/kernel/expression_operators/to_string.rs +10 -4
  405. data/rust/src/kernel/json.rs +230 -148
  406. data/rust/src/kernel/mod.rs +46 -33
  407. data/rust/src/kernel/named_query.rs +63 -15
  408. data/rust/src/kernel/needs.rs +112 -25
  409. data/rust/src/kernel/orchestrate.rs +980 -581
  410. data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
  411. data/rust/src/kernel/pattern.rs +107 -34
  412. data/rust/src/kernel/query_comparators.rs +118 -41
  413. data/rust/src/kernel/query_ordering.rs +13 -5
  414. data/rust/src/kernel/read_model.rs +479 -181
  415. data/rust/src/kernel/reference_lookup.rs +122 -26
  416. data/rust/src/kernel/repository.rs +293 -66
  417. data/rust/src/kernel/routing.rs +58 -42
  418. data/rust/src/kernel/ruby_float.rs +139 -0
  419. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
  420. data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
  421. data/rust/src/kernel/vocab/field_hint.rs +19 -25
  422. data/rust/src/kernel/vocab/mod.rs +6 -6
  423. data/rust/src/kernel/vocab/query_comparator.rs +23 -24
  424. data/rust/src/kernel/vocab/refusal_template.rs +501 -297
  425. data/rust/src/lib.rs +50 -0
  426. data/rust/src/main.rs +4 -1
  427. data/rust/web/Cargo.toml +36 -0
  428. data/rust/web/clippy.toml +4 -0
  429. metadata +85 -15
  430. data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
  431. data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
  432. data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
  433. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
  434. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
  435. data/lib/hecks/ports/authentication.port +0 -6
  436. data/lib/hecks/ports/authentication.rb +0 -67
  437. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
  438. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
  439. data/prebuilt/verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json +0 -1
  440. data/rust/host/src/approval.rs +0 -652
@@ -1,4 +1,5 @@
1
1
  require "json"
2
+ require_relative "expression_sql"
2
3
 
3
4
  module Hecks
4
5
  module Translation
@@ -7,19 +8,34 @@ module Hecks
7
8
  module RuleCompiler
8
9
  module_function
9
10
 
10
- # Compiles one edge's rename/move/convert/drop/compute/backfill rules into a nested
11
+ # Compiles one edge's derive/rename/move/convert/drop/backfill rules into a nested
11
12
  # SQL expression over `state`, composed innermost-first in that phase order.
12
13
  def compile_rules(declared)
13
- expression = compile_renames("state", declared.renames)
14
+ expression = compile_derives("state", declared.derives)
15
+ expression = compile_renames(expression, declared.renames)
14
16
  expression = compile_moves(expression, declared.moves)
15
17
  expression = compile_converts(expression, declared.converts)
16
18
  expression = compile_drops(expression, declared.drops)
17
- expression = compile_computes(expression, declared.computes)
18
19
  # Backfilled last, same order as `Lineage#translate`'s in-process pass, so the
19
20
  # boot-time mint audit (which reads only this compiled SQL) sees the same result.
20
21
  compile_backfills(expression, declared.backfills)
21
22
  end
22
23
 
24
+ # Wraps `expression` in each derive, every one reading the record as it was stored.
25
+ def compile_derives(expression, derives)
26
+ derives.inject(expression) { |sql, derive| compile_derive(sql, derive) }
27
+ end
28
+
29
+ # Wraps `expression` so a derive's text lands at its destination when the expression has a
30
+ # value; the expression reads the stored `state`, never the rules composed before it.
31
+ def compile_derive(expression, derive)
32
+ value = ExpressionSql.compile(derive.tree, "state")
33
+ "(SELECT CASE WHEN __d IS NULL THEN __s " \
34
+ "ELSE hecks_tr_insert(__s, #{path_literal(derive.to)}, to_jsonb(__d), " \
35
+ "#{text_literal("derive #{derive.to}")}) END " \
36
+ "FROM (SELECT (#{expression}) AS __s) __outer, LATERAL (SELECT #{value} AS __d) __derived)"
37
+ end
38
+
23
39
  # Folds each rule of one kind into `expression`, innermost-first.
24
40
  def compile_renames(expression, renames)
25
41
  renames.inject(expression) { |sql, (old_name, new_name)| compile_rename(sql, old_name, new_name) }
@@ -31,8 +47,6 @@ module Hecks
31
47
 
32
48
  def compile_drops(expression, drops) = drops.inject(expression) { |sql, name| compile_drop(sql, name) }
33
49
 
34
- def compile_computes(expression, computes) = computes.inject(expression) { |sql, compute| compile_compute(sql, compute) }
35
-
36
50
  def compile_backfills(expression, backfills)
37
51
  backfills.inject(expression) { |sql, backfill| compile_backfill(sql, backfill) }
38
52
  end
@@ -71,25 +85,12 @@ module Hecks
71
85
  "CASE WHEN #{guard} THEN #{compile_id_expression(declared)} ELSE aggregate_id END AS aggregate_id"
72
86
  end
73
87
 
74
- # Evaluates a rekey's own SQL against the record's current `state`, read directly
75
- # rather than through `compile_rules`'s progressively-built expression chain.
88
+ # A `rekey_to` id: the value of the derive it names, read from the record's stored `state`,
89
+ # or the old id for a record where the derive has no value.
76
90
  def compile_id_expression(declared)
77
91
  rekey = declared.rekeys.first
78
- "(SELECT (#{rekey.sql}) FROM (SELECT (state) AS __s) __outer)"
79
- end
80
-
81
- # Wraps `expression` so a compute's SQL result lands at its declared destination
82
- # when the source path is present; the whole record stays readable as `__s`.
83
- def compile_compute(expression, compute)
84
- from = compute.from.to_s
85
- to = compute.to.to_s
86
- "(SELECT CASE WHEN (__x).present THEN " \
87
- "hecks_tr_insert((__x).remaining, #{path_literal(to)}, to_jsonb((#{compute.sql})), " \
88
- "#{text_literal("compute #{from} to: #{to}")}) " \
89
- "ELSE __s END " \
90
- "FROM (SELECT (#{expression}) AS __s) __outer, " \
91
- "LATERAL (SELECT hecks_tr_extract(__s, #{path_literal(from)}) AS __x) __extract, " \
92
- "LATERAL (SELECT (__s #>> #{path_literal(from)}) AS #{quote(from)}) __fields)"
92
+ derive = declared.derives.find { |candidate| candidate.to == rekey.to }
93
+ "COALESCE(#{ExpressionSql.compile(derive.tree, "state")}, aggregate_id)"
93
94
  end
94
95
 
95
96
  # Wraps `expression` so a backfill's default lands at its declared path only when
@@ -103,13 +104,6 @@ module Hecks
103
104
  "FROM (SELECT (#{expression}) AS __s) __outer)"
104
105
  end
105
106
 
106
- # Quotes a Postgres identifier; requires "pg" lazily so a non-Postgres-bound
107
- # domain never gains a hard dependency on the gem just by loading this file.
108
- def quote(name)
109
- require "pg"
110
- PG::Connection.quote_ident(name.to_s)
111
- end
112
-
113
107
  # Renders a Ruby value as a single-quoted SQL text literal, escaping embedded quotes.
114
108
  def text_literal(text) = "'#{text.to_s.gsub("'", "''")}'"
115
109
 
@@ -5,7 +5,7 @@ require_relative "scaffold/writer"
5
5
  module Hecks
6
6
  module Translation
7
7
  # Diffs the held era's storage shape against the current one and writes the edge file.
8
- # Ambiguities become parse-refusing `unresolved` constructs; it never proposes compute or drop.
8
+ # Ambiguities become parse-refusing `unresolved` constructs; it never proposes drop or derive.
9
9
  module Scaffold
10
10
  Edge = Struct.new(:domain, :from, :to, :ordinal, :label, :aggregates, :retired, keyword_init: true)
11
11
  ScaffoldedAggregate = Struct.new(:name, :was, :rules, keyword_init: true)
@@ -9,4 +9,3 @@ require_relative "translation/rule_compiler"
9
9
  require_relative "translation/scaffold"
10
10
  require_relative "translation/audit"
11
11
  require_relative "translation/reattest"
12
- require_relative "translation/approval_file"
@@ -18,20 +18,17 @@ module Hecks
18
18
 
19
19
  # Registers this plugin's era gates on one boot's gate list.
20
20
  #
21
- # Two `:pre_verify` gates: `era_compute_rules` always runs; `era_check` (ADR 0031)
22
- # runs only when the registry binds something lineage-capable.
21
+ # One `:pre_verify` gate, `era_check` (ADR 0031), registered only when the registry
22
+ # binds something lineage-capable.
23
23
  #
24
24
  # @param registry [Runtime::Registry] the registry being booted, asked whether any
25
25
  # bluebook's first aggregate is bound to a lineage-capable adapter
26
26
  # @param gates [Runtime::BootGates] this boot's gate list, registered onto in place
27
27
  # @return [Runtime::BootGates, nil] `gates` when `:era_check` was registered; nil when
28
- # the registry binds nothing lineage-capable, so only `:era_compute_rules` was added
28
+ # the registry binds nothing lineage-capable
29
29
  # @raise [Runtime::WiringError] if an aggregate's persistence binding is missing,
30
30
  # ambiguous, or carries an unsupported role
31
31
  def contribute_boot_gates(registry, gates)
32
- gates.register(:era_compute_rules, lambda { |reg, _dir|
33
- Runtime::EraCheck.check_compute_rules_for_registry!(reg)
34
- }, phase: :pre_verify)
35
32
  gates.register(:era_check, Runtime::EraCheck.method(:check_lineage!), phase: :pre_verify) if
36
33
  Runtime::EraCheck.lineage_capable_registry?(registry)
37
34
  end
data/lib/hecks/ports.rb CHANGED
@@ -17,7 +17,6 @@ require_relative "ports/identity_generation"
17
17
  require_relative "ports/key_vault"
18
18
  require_relative "ports/authorization"
19
19
  require_relative "ports/identity_resolution"
20
- require_relative "ports/authentication"
21
20
  require_relative "ports/access_control"
22
21
  require_relative "ports/identity_assignment"
23
22
  require_relative "ports/agent"
@@ -3,6 +3,7 @@
3
3
  require "fileutils"
4
4
  require "json"
5
5
  require "tempfile"
6
+ require_relative "rust_format"
6
7
  require_relative "projection_files/texts"
7
8
  require_relative "projection_files/expression_tables"
8
9
 
@@ -55,7 +56,8 @@ module Hecks
55
56
  def build(name, root: ROOT)
56
57
  raise ArgumentError, "no projection named #{name.inspect}" unless NAMES.include?(name.to_sym)
57
58
 
58
- public_send(name, root)
59
+ result = public_send(name, root)
60
+ Result.new(RustFormat.files(result.content), result.stale)
59
61
  end
60
62
 
61
63
  # Writes one projection, each file through a temporary file and a rename so a reader never sees
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # @@web_logical_id@@ is the ONE exception -- real Google OAuth token
3
- # exchange (hecks's own GoogleAuthentication adapter posting to
3
+ # exchange (the Rust web host's Google sign-in posting to
4
4
  # https://oauth2.googleapis.com/token) is a genuine third-party HTTPS
5
5
  # call with no AWS PrivateLink/VPC Endpoint option (unlike
6
6
  # lambda:InvokeFunction, above), and @@web_logical_id@@ is ALSO
@@ -77,20 +77,26 @@ module Hecks
77
77
  end
78
78
 
79
79
  def verb_entries(bluebook, homes)
80
- holders(bluebook).flat_map { |holder| command_entries(holder, homes) + query_entries(holder, homes) }
80
+ shapes = bluebook.aggregates.to_h { |aggregate| [aggregate.hecks_name, aggregate.value_objects] }
81
+ holders(bluebook).flat_map do |holder|
82
+ command_entries(holder, homes) + query_entries(holder, homes, shapes[homes[holder.hecks_name]])
83
+ end
81
84
  end
82
85
 
83
86
  def command_entries(holder, homes)
84
87
  holder.commands.map do |command|
85
88
  Entry.new(name: command.hecks_name, kind: :command, within: holder.hecks_name,
86
- section: homes[holder.hecks_name], facts: { command: command, holder: holder })
89
+ section: homes[holder.hecks_name],
90
+ facts: { command: command, holder: holder, within: homes[holder.hecks_name] })
87
91
  end
88
92
  end
89
93
 
90
- def query_entries(holder, homes)
94
+ # A query carries its holder and the aggregate's value objects, to speak its fields.
95
+ def query_entries(holder, homes, value_objects)
91
96
  holder.queries.map do |query|
92
97
  Entry.new(name: query.hecks_name, kind: :query, within: holder.hecks_name,
93
- section: homes[holder.hecks_name], facts: { query: query })
98
+ section: homes[holder.hecks_name],
99
+ facts: { query: query, holder: holder, value_objects: Array(value_objects) })
94
100
  end
95
101
  end
96
102
 
@@ -0,0 +1,42 @@
1
+ module Hecks
2
+ module Projections
3
+ module Glossary
4
+ # The groups an aggregate's terms are listed under, so a reader meets what can be done
5
+ # with a thing before the lists and the parts it is made of. Each group is a `###`
6
+ # heading in the Markdown, so GitHub and the page give the terms the same slugs.
7
+ module Groups
8
+ # Kind => the group's heading, in the order the groups are shown.
9
+ LABELS = {
10
+ command: "Actions", event: "What it records", policy: "What happens on its own",
11
+ saga: "What happens on its own", query: "Lists", entity: "What it is made of",
12
+ value_object: "What it is made of"
13
+ }.freeze
14
+
15
+ ORDER = LABELS.values.uniq.freeze
16
+
17
+ module_function
18
+
19
+ # @param entry [Glossary::Entry] a term
20
+ # @return [Integer] where its group sits; terms outside every group come first
21
+ def rank(entry) = ORDER.index(LABELS[entry.kind]) || -1
22
+
23
+ # A section's terms with a group heading before the first term of each group. Only an
24
+ # aggregate's section is grouped.
25
+ #
26
+ # @param section [Glossary::Section] the section, its terms already sorted
27
+ # @return [Array<Array(Symbol, Object)>] `[:group, label]` and `[:term, entry]` pairs
28
+ def walk(section)
29
+ return section.terms.map { |entry| [:term, entry] } unless section.aggregate
30
+
31
+ last = nil
32
+ section.terms.flat_map do |entry|
33
+ label = LABELS[entry.kind]
34
+ heading = label == last ? [] : [[:group, label]]
35
+ last = label
36
+ heading + [[:term, entry]]
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -41,6 +41,7 @@ module Hecks
41
41
  <p class="sub">Glossary</p>
42
42
  <input type="search" placeholder="Find a term" aria-label="Find a term" autocomplete="off">
43
43
  <p class="count"></p>
44
+ <p class="progress" hidden></p>
44
45
  <ol>
45
46
  @@ITEMS@@
46
47
  </ol>
@@ -69,7 +70,7 @@ module Hecks
69
70
  end
70
71
 
71
72
  def rail(title, sections, slugs)
72
- domain = title.split(" — ").first
73
+ domain = Page.domain(title)
73
74
  items = sections.map do |section|
74
75
  heading = section[:heading]
75
76
  %(<li><a href="##{slugs[heading]}">#{Html.inline(heading.text)}</a></li>)
@@ -77,13 +78,17 @@ module Hecks
77
78
  fill(RAIL, "DOMAIN" => Html.escape(domain), "ITEMS" => items.join("\n"))
78
79
  end
79
80
 
81
+ # @param title [String] the document's title, such as "Pizzas glossary"
82
+ # @return [String] the domain's name alone
83
+ def domain(title) = title.delete_suffix(" glossary")
84
+
80
85
  def front_matter(blocks)
81
86
  ["<header>", *blocks.filter_map { |block| front_part(block) }, "</header>"].join("\n")
82
87
  end
83
88
 
84
89
  def front_part(block)
85
90
  case block.type
86
- when :heading then "<h1>#{Html.inline(block.text.split(" — ").first)}</h1>"
91
+ when :heading then "<h1>#{Html.inline(Page.domain(block.text))}</h1>"
87
92
  when :quote then "<p class=\"vision\">#{Html.inline(block.text)}</p>"
88
93
  when :paragraph then "<p class=\"lede\">#{Html.inline(block.text)}</p>"
89
94
  when :mermaid then Html.figure(block, "How it all fits together")
@@ -25,7 +25,7 @@ module Hecks
25
25
 
26
26
  case line
27
27
  when /\A```mermaid/ then fence(lines, index)
28
- when /\A(\#{1,3}) (.+)\z/ then [heading(Regexp.last_match), index + 1]
28
+ when /\A(\#{1,4}) (.+)\z/ then [heading(Regexp.last_match), index + 1]
29
29
  when /\A> / then [Block.new(type: :quote, text: line[2..]), index + 1]
30
30
  when /\A- / then list(lines, index)
31
31
  else paragraph(lines, index)
@@ -4,6 +4,11 @@ module Hecks
4
4
  module Html
5
5
  # Groups parsed blocks into the page's sections and renders each section's terms.
6
6
  module Sections
7
+ # Paragraph openings that mark a rule, and the class that sets each apart on the page.
8
+ LEADS = {
9
+ "Always true:" => "rule", "Allowed only when:" => "rule", "Afterwards, always:" => "rule"
10
+ }.freeze
11
+
7
12
  module_function
8
13
 
9
14
  # GitHub's slugs for these headings, so links the Markdown carries land here too.
@@ -26,14 +31,14 @@ module Hecks
26
31
  sections
27
32
  end
28
33
 
29
- # Each `###` opens an article running to the next. A bold-only paragraph
30
- # captions whatever figure or list follows it.
34
+ # Each `###` opens a group of terms and each `####` a term running to the next. A
35
+ # bold-only paragraph captions whatever figure or list follows it.
31
36
  def section_html(section, slugs)
32
37
  heading = section[:heading]
33
38
  state = { parts: [%(<section id="#{slugs[heading]}">), "<h2>#{Html.inline(heading.text)}</h2>"],
34
- caption: nil, in_terms: false }
39
+ caption: nil, open: nil }
35
40
  section[:blocks].drop(1).each { |block| section_block(block, state, slugs) }
36
- state[:parts] << "</article></div>" if state[:in_terms]
41
+ close(state, nil)
37
42
  state[:parts] << "</section>"
38
43
  state[:parts].join("\n")
39
44
  end
@@ -41,7 +46,7 @@ module Hecks
41
46
  # Renders one block into `state[:parts]`, mutating `state`.
42
47
  def section_block(block, state, slugs)
43
48
  case block.type
44
- when :heading then open_term(block, state, slugs)
49
+ when :heading then block.level == 3 ? open_group(block, state, slugs) : open_term(block, state, slugs)
45
50
  when :mermaid then state[:parts] << Html.figure(block, state.delete(:caption))
46
51
  when :list then list(block, state)
47
52
  when :quote then state[:parts] << "<p class=\"about\">#{Html.inline(block.text)}</p>"
@@ -49,11 +54,26 @@ module Hecks
49
54
  end
50
55
  end
51
56
 
57
+ def open_group(block, state, slugs)
58
+ close(state, nil)
59
+ state[:parts] << %(<h3 class="group" id="#{slugs[block]}">#{Html.inline(block.text)}</h3>)
60
+ end
61
+
52
62
  def open_term(block, state, slugs)
53
- parts = state[:parts]
54
- parts << (state[:in_terms] ? "</article>" : "<div class=\"terms\">")
55
- parts << %(<article class="term" id="#{slugs[block]}">) << "<h3>#{Html.inline(block.text)}</h3>"
56
- state[:in_terms] = true
63
+ close(state, :terms)
64
+ state[:parts] << "<div class=\"terms\">" unless state[:open]
65
+ state[:parts] << %(<article class="term" id="#{slugs[block]}">) << "<h4>#{Html.inline(block.text)}</h4>"
66
+ state[:open] = :article
67
+ end
68
+
69
+ # Closes the open article, and the terms around it unless `keep` is `:terms`.
70
+ def close(state, keep)
71
+ state[:parts] << "</article>" if state[:open] == :article
72
+ state[:open] = :terms if state[:open]
73
+ return if keep == :terms
74
+
75
+ state[:parts] << "</div>" if state[:open]
76
+ state[:open] = nil
57
77
  end
58
78
 
59
79
  def list(block, state)
@@ -67,8 +87,8 @@ module Hecks
67
87
  if text.match?(/\A\*\*[^*]+\*\*\z/)
68
88
  state[:caption] = text.delete("*")
69
89
  else
70
- klass = text.start_with?("Always true:") ? " class=\"rule\"" : ""
71
- state[:parts] << "<p#{klass}>#{Html.inline(text)}</p>"
90
+ klass = LEADS.find { |lead, _class| text.start_with?(lead) }&.last
91
+ state[:parts] << "<p#{%( class="#{klass}") if klass}>#{Html.inline(text)}</p>"
72
92
  end
73
93
  end
74
94
  end
@@ -11,8 +11,7 @@ module Hecks
11
11
  module Html
12
12
  Block = Struct.new(:type, :text, :items, :level, keyword_init: true)
13
13
 
14
- FONTS = "https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;" \
15
- "0,6..72,600;1,6..72,400&family=Instrument+Sans:wght@400;500;600&display=swap".freeze
14
+ FONTS = "https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&display=swap".freeze
16
15
  MERMAID = "https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.1/mermaid.min.js".freeze
17
16
 
18
17
  module_function
@@ -10,21 +10,22 @@ module Hecks
10
10
  # Renders `glossary.md`, the source the glossary page is built from.
11
11
  # Uses only Markdown that GitHub renders as-is, so the file reads on its own.
12
12
  module Markdown
13
- LEDE = "Every term %<name>s uses, in the words of the people who work in it — grouped under the thing " \
14
- "each belongs to, and listed A to Z within it. This page is generated from the working " \
15
- "specification, so it says what the system does today, not what anyone hoped it would do. " \
16
- "If a sentence here reads wrong to you, the specification is wrong: say so.".freeze
13
+ LEDE = "The terms %<name>s uses, written the way the people who work in it would say them. " \
14
+ "Each term sits under the thing it belongs to: first what can be done with it, then " \
15
+ "what it records, the lists you can ask for, and what it is made of. The page is " \
16
+ "generated from the working specification, so it describes what the system does today. " \
17
+ "If a sentence reads wrong to you, the specification is wrong, and it is worth saying so.".freeze
17
18
 
18
19
  STANDING = {
19
- ROLES => "Who does what. A role is named once here rather than under every term it touches.",
20
- READ_MODELS => "Questions answered across more than one of the things above.",
21
- REACTIONS => "What happens on its own, in response to something this specification does not itself raise."
20
+ ROLES => "Who does what. Each role is listed once, with the actions it is responsible for.",
21
+ READ_MODELS => "Questions answered by reading more than one of the things above.",
22
+ REACTIONS => "What happens on its own when something arrives from outside this specification."
22
23
  }.freeze
23
24
 
24
25
  module_function
25
26
 
26
27
  # Titles the document after its chapter.
27
- def title(bluebook) = "#{bluebook.name} — Glossary"
28
+ def title(bluebook) = "#{bluebook.name} glossary"
28
29
 
29
30
  # Renders the full `glossary.md` document.
30
31
  #
@@ -41,13 +42,15 @@ module Hecks
41
42
  parts = ["# #{title(bluebook)}"]
42
43
  parts << "> #{bluebook.vision}" if bluebook.vision
43
44
  parts << format(LEDE, name: bluebook.name)
44
- parts << fence(Mermaid.map(bluebook))
45
+ parts << fence(Mermaid.map(bluebook)) if bluebook.aggregates.size > 1
45
46
  parts.join("\n\n")
46
47
  end
47
48
 
48
49
  # Renders one `##` section: its opening, then every term inside it.
49
50
  def section_text(section, document)
50
- terms = section.terms.map { |entry| term_text(entry, document.index) }
51
+ terms = Groups.walk(section).map do |kind, item|
52
+ kind == :group ? "### #{item}" : term_text(item, document.index)
53
+ end
51
54
  ["## #{section.title}", *section_opening(section, document), *terms].join("\n\n")
52
55
  end
53
56
 
@@ -67,11 +70,19 @@ module Hecks
67
70
  parts + diagrams(aggregate, bluebook) + facts(always_true(aggregate, bluebook), marked)
68
71
  end
69
72
 
70
- # The "How it fits" diagram, and "How it moves" when the aggregate has a lifecycle.
73
+ # The "How it fits" diagram when the aggregate has neighbours to draw, and "How it moves"
74
+ # with each move in words when it has a lifecycle.
71
75
  def diagrams(aggregate, bluebook)
72
- parts = ["**How it fits**", fence(Mermaid.context(aggregate, bluebook))]
73
- parts << "**How it moves**" << fence(Mermaid.lifecycle(aggregate)) if aggregate.lifecycle
74
- parts
76
+ parts = []
77
+ parts << "**How it fits**" << fence(Mermaid.context(aggregate, bluebook)) if Mermaid.neighbours?(aggregate, bluebook)
78
+ parts + moves(aggregate)
79
+ end
80
+
81
+ def moves(aggregate)
82
+ return [] unless aggregate.lifecycle
83
+
84
+ lines = Statements::Actions.lifecycle(aggregate).drop(1).map { |sentence| "- #{sentence}" }
85
+ ["**How it moves**", fence(Mermaid.lifecycle(aggregate)), lines.join("\n")]
75
86
  end
76
87
 
77
88
  # The "Always true" and "Handled as sensitive" lists, each only when it has lines.
@@ -85,25 +96,17 @@ module Hecks
85
96
  parts
86
97
  end
87
98
 
88
- # The statements projection's sentences for `aggregate` and its entities, spoken.
89
- # Invariants stay verbatim; construct names in other sentences are replaced with
90
- # how they are said, because this page never shows an identifier.
91
- def always_true(aggregate, bluebook)
92
- names = bluebook.aggregates.flat_map { |other| [other.hecks_name, *other.entities.map(&:hecks_name)] }
99
+ # The statements projection's relationship and invariant sentences for `aggregate` and
100
+ # its entities, each said once.
101
+ def always_true(aggregate, _bluebook)
93
102
  [aggregate, *aggregate.entities].flat_map do |holder|
94
- Statements.attribute_statements(holder).map { |sentence| spoken(sentence, names) } +
95
- Statements.invariant_statements(holder)
103
+ Statements.attribute_statements(holder) + Statements.invariant_statements(holder)
96
104
  end.uniq
97
105
  end
98
106
 
99
- # Replaces every whole-word construct name in `sentence` with how it is said.
100
- def spoken(sentence, names)
101
- names.reduce(sentence) { |text, name| text.gsub(/\b#{Regexp.escape(name)}\b/, Naming.words(name)) }
102
- end
103
-
104
- # Renders one term's `###` heading and its paragraphs.
107
+ # Renders one term's `####` heading and its paragraphs.
105
108
  def term_text(entry, index)
106
- ["### #{entry.headword}", *Sentences.paragraphs(entry, index)].join("\n\n")
109
+ ["#### #{entry.headword}", *Sentences.paragraphs(entry, index)].join("\n\n")
107
110
  end
108
111
 
109
112
  # Wraps a Mermaid diagram's source in a fenced code block.
@@ -35,6 +35,9 @@ module Hecks
35
35
  (lines << " classDef focus stroke-width:3px").uniq.join("\n")
36
36
  end
37
37
 
38
+ # @return [Boolean] whether the aggregate has anything around it worth drawing
39
+ def neighbours?(aggregate, bluebook) = !neighbours(aggregate, bluebook).empty?
40
+
38
41
  # The nodes and edges for the aggregate's entities, what it references, and what
39
42
  # references it.
40
43
  def neighbours(aggregate, bluebook)