hecks 1.3.0 → 1.5.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 (373) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +72 -7
  3. data/lib/hecks/adapters/driven/d1.rb +187 -23
  4. data/lib/hecks/adapters/driven/folder.rb +83 -10
  5. data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
  6. data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
  7. data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
  8. data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
  9. data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
  10. data/lib/hecks/adapters/driven/heki.rb +106 -9
  11. data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
  12. data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
  13. data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
  15. data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
  16. data/lib/hecks/adapters/driven/lambda.rb +82 -35
  17. data/lib/hecks/adapters/driven/local_storage.rb +83 -10
  18. data/lib/hecks/adapters/driven/memory.rb +205 -9
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
  24. data/lib/hecks/adapters/driven/postgres.rb +175 -28
  25. data/lib/hecks/adapters/driven/prism.rb +50 -11
  26. data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  31. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  32. data/lib/hecks/adapters/driven/system_clock.rb +3 -0
  33. data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
  34. data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
  35. data/lib/hecks/adapters/driven.rb +6 -4
  36. data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
  37. data/lib/hecks/behaviors/dsl.rb +60 -2
  38. data/lib/hecks/behaviors/expectations.rb +190 -29
  39. data/lib/hecks/behaviors/ir.rb +12 -1
  40. data/lib/hecks/behaviors/rspec.rb +9 -1
  41. data/lib/hecks/behaviors/runner.rb +21 -2
  42. data/lib/hecks/behaviors.rb +9 -1
  43. data/lib/hecks/bluebook/aggregate.rb +43 -13
  44. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
  45. data/lib/hecks/bluebook/assembly/build.rb +26 -5
  46. data/lib/hecks/bluebook/assembly/contract.rb +98 -23
  47. data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
  48. data/lib/hecks/bluebook/assembly/marks.rb +159 -30
  49. data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
  50. data/lib/hecks/bluebook/assembly.rb +32 -14
  51. data/lib/hecks/bluebook/attribute.rb +26 -12
  52. data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
  53. data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
  54. data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
  55. data/lib/hecks/bluebook/behaviour/command.rb +55 -25
  56. data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
  57. data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
  58. data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
  59. data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
  60. data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
  61. data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
  62. data/lib/hecks/bluebook/behaviour/query.rb +6 -1
  63. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
  64. data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
  65. data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
  66. data/lib/hecks/bluebook/capabilities.rb +27 -0
  67. data/lib/hecks/bluebook/chapter.rb +51 -9
  68. data/lib/hecks/bluebook/command.rb +62 -17
  69. data/lib/hecks/bluebook/domain_port.rb +34 -9
  70. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  71. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  72. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
  73. data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
  74. data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
  75. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
  76. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
  77. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  78. data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
  79. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  80. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  81. data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
  82. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  83. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
  84. data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
  85. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  86. data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
  87. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  88. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  89. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
  90. data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
  91. data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
  92. data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
  93. data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
  94. data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
  95. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  96. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  97. data/lib/hecks/bluebook/entity.rb +40 -11
  98. data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
  99. data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
  100. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
  101. data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
  102. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
  103. data/lib/hecks/bluebook/expression/resolver.rb +369 -128
  104. data/lib/hecks/bluebook/hexagon.rb +35 -1
  105. data/lib/hecks/bluebook/lifecycle.rb +12 -1
  106. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
  107. data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
  108. data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
  109. data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
  110. data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
  111. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
  112. data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
  113. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
  114. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
  115. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
  116. data/lib/hecks/bluebook/meta_validator.rb +235 -139
  117. data/lib/hecks/bluebook/model_check.rb +434 -104
  118. data/lib/hecks/bluebook/pattern_subset.rb +32 -10
  119. data/lib/hecks/bluebook/policy.rb +15 -13
  120. data/lib/hecks/bluebook/process_manager.rb +27 -14
  121. data/lib/hecks/bluebook/project_discovery.rb +5 -0
  122. data/lib/hecks/bluebook/project_loader.rb +40 -0
  123. data/lib/hecks/bluebook/project_register.rb +50 -6
  124. data/lib/hecks/bluebook/query.rb +31 -4
  125. data/lib/hecks/bluebook/read_model.rb +35 -15
  126. data/lib/hecks/bluebook/reference.rb +26 -13
  127. data/lib/hecks/bluebook/smoke_test.rb +46 -23
  128. data/lib/hecks/bluebook/synthesizer.rb +46 -12
  129. data/lib/hecks/bluebook/translation.rb +34 -5
  130. data/lib/hecks/bluebook/value_object.rb +29 -11
  131. data/lib/hecks/bluebook.rb +5 -6
  132. data/lib/hecks/codemod.rb +138 -50
  133. data/lib/hecks/construct.rb +21 -7
  134. data/lib/hecks/corpus.rb +438 -0
  135. data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
  136. data/lib/hecks/doc/reference.rb +200 -31
  137. data/lib/hecks/embryonaut_bluebook.rb +38 -15
  138. data/lib/hecks/facade/cli_door.rb +69 -10
  139. data/lib/hecks/facade/cli_runner.rb +105 -24
  140. data/lib/hecks/facade/command_request.rb +23 -0
  141. data/lib/hecks/facade/handle.rb +155 -35
  142. data/lib/hecks/facade/json_door.rb +106 -25
  143. data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
  144. data/lib/hecks/facade/surface/chapter.rb +26 -17
  145. data/lib/hecks/facade/surface.rb +16 -3
  146. data/lib/hecks/facade.rb +15 -4
  147. data/lib/hecks/forms/app.rb +46 -30
  148. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  149. data/lib/hecks/forms/field_renderer.rb +142 -6
  150. data/lib/hecks/forms/field_shape.rb +183 -19
  151. data/lib/hecks/forms/html.rb +51 -7
  152. data/lib/hecks/forms/index_renderer.rb +14 -2
  153. data/lib/hecks/forms/page.rb +14 -0
  154. data/lib/hecks/forms/params.rb +120 -23
  155. data/lib/hecks/forms/port_argument.rb +14 -2
  156. data/lib/hecks/forms/query_form_renderer.rb +65 -2
  157. data/lib/hecks/forms/record_renderer.rb +60 -2
  158. data/lib/hecks/forms/record_table.rb +28 -1
  159. data/lib/hecks/forms/reference_options.rb +24 -0
  160. data/lib/hecks/forms/value_object_shape.rb +13 -3
  161. data/lib/hecks/forms.rb +24 -4
  162. data/lib/hecks/fqn.rb +59 -1
  163. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  164. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  165. data/lib/hecks/framework/oidc.json +15 -0
  166. data/lib/hecks/framework.rb +79 -25
  167. data/lib/hecks/freezer.rb +27 -11
  168. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
  169. data/lib/hecks/fuzzing/combination_miner.rb +178 -0
  170. data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
  171. data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
  172. data/lib/hecks/fuzzing/differential.rb +192 -0
  173. data/lib/hecks/fuzzing/domain_generator.rb +871 -0
  174. data/lib/hecks/fuzzing/era_boundary.rb +68 -19
  175. data/lib/hecks/fuzzing/form_census.rb +121 -20
  176. data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
  177. data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
  178. data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
  179. data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
  180. data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
  181. data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
  182. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
  183. data/lib/hecks/fuzzing/properties/guards.rb +86 -42
  184. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
  185. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
  186. data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
  187. data/lib/hecks/fuzzing/properties/querying.rb +82 -28
  188. data/lib/hecks/fuzzing/properties.rb +84 -51
  189. data/lib/hecks/fuzzing/qa_settings.rb +164 -0
  190. data/lib/hecks/fuzzing/replay.rb +241 -133
  191. data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
  192. data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
  193. data/lib/hecks/fuzzing/self_consistency.rb +315 -137
  194. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
  195. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
  196. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
  197. data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
  198. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
  199. data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
  200. data/lib/hecks/fuzzing/shrinker.rb +309 -0
  201. data/lib/hecks/fuzzing/structural_skips.rb +37 -130
  202. data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
  203. data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
  204. data/lib/hecks/fuzzing/value_generator.rb +110 -22
  205. data/lib/hecks/fuzzing.rb +5 -0
  206. data/lib/hecks/grammar/evolve.rb +188 -12
  207. data/lib/hecks/grammar.rb +53 -7
  208. data/lib/hecks/ir.rb +51 -20
  209. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  210. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  211. data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
  212. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  213. data/lib/hecks/language/oidc.json +5 -0
  214. data/lib/hecks/literal.rb +41 -9
  215. data/lib/hecks/naming.rb +112 -31
  216. data/lib/hecks/ports/access_control.rb +53 -2
  217. data/lib/hecks/ports/agent/answers.rb +83 -6
  218. data/lib/hecks/ports/agent.rb +119 -35
  219. data/lib/hecks/ports/authentication.rb +44 -4
  220. data/lib/hecks/ports/authorization.rb +50 -11
  221. data/lib/hecks/ports/clock.rb +42 -23
  222. data/lib/hecks/ports/extraction.rb +16 -0
  223. data/lib/hecks/ports/identity_assignment.rb +23 -2
  224. data/lib/hecks/ports/identity_generation.rb +17 -3
  225. data/lib/hecks/ports/identity_resolution.rb +17 -1
  226. data/lib/hecks/ports/key_vault.port +6 -0
  227. data/lib/hecks/ports/key_vault.rb +58 -0
  228. data/lib/hecks/ports/loading.rb +4 -0
  229. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  230. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  231. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  232. data/lib/hecks/ports/persistence/execution.rb +4 -0
  233. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  234. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  235. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  236. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  237. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  238. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  239. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  250. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
  251. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  252. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
  253. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  254. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
  255. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  256. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  257. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  258. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
  259. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  260. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  261. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  262. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
  263. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
  264. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  265. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
  266. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  267. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  268. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  269. data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
  270. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  271. data/lib/hecks/ports/persistence.rb +36 -1
  272. data/lib/hecks/ports/projection.rb +61 -7
  273. data/lib/hecks/ports/query/in_memory.rb +41 -8
  274. data/lib/hecks/ports/query/ordering.rb +21 -6
  275. data/lib/hecks/ports/query.rb +35 -0
  276. data/lib/hecks/ports.rb +1 -0
  277. data/lib/hecks/projections/bootstrap_table.rb +147 -0
  278. data/lib/hecks/projections/diagrams.rb +315 -79
  279. data/lib/hecks/projections/glossary/html.rb +91 -3
  280. data/lib/hecks/projections/glossary/markdown.rb +50 -3
  281. data/lib/hecks/projections/glossary/mermaid.rb +49 -1
  282. data/lib/hecks/projections/glossary/sections.rb +1 -1
  283. data/lib/hecks/projections/glossary/sentences.rb +110 -5
  284. data/lib/hecks/projections/glossary.rb +173 -31
  285. data/lib/hecks/projections/ir.rb +1 -1
  286. data/lib/hecks/projections/model/deviations.rb +62 -17
  287. data/lib/hecks/projections/model.rb +75 -21
  288. data/lib/hecks/projections/oidc.rb +45 -8
  289. data/lib/hecks/projections/parser_table.rb +53 -12
  290. data/lib/hecks/projections/reference.rb +15 -3
  291. data/lib/hecks/projections/rust_vocabulary.rb +646 -0
  292. data/lib/hecks/projections/shape.rb +10 -2
  293. data/lib/hecks/projections/statements.rb +69 -22
  294. data/lib/hecks/projections/vocabulary.rb +26 -9
  295. data/lib/hecks/projections.rb +5 -3
  296. data/lib/hecks/projector/cli_projector.rb +245 -37
  297. data/lib/hecks/projector/docs_projector.rb +154 -28
  298. data/lib/hecks/projector/exporter.rb +104 -29
  299. data/lib/hecks/projector/ir_projector.rb +7 -1
  300. data/lib/hecks/projector/narrate_projector.rb +144 -23
  301. data/lib/hecks/projector/target.rb +42 -18
  302. data/lib/hecks/projector.rb +86 -18
  303. data/lib/hecks/query_ir.rb +94 -47
  304. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  305. data/lib/hecks/query_specification/common/comparison.rb +109 -27
  306. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  307. data/lib/hecks/query_specification/common/null_policy.rb +61 -15
  308. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  309. data/lib/hecks/query_specification/common/options.rb +25 -0
  310. data/lib/hecks/query_specification/field_path.rb +69 -15
  311. data/lib/hecks/query_specification/hop_path.rb +57 -20
  312. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  313. data/lib/hecks/rendering.rb +9 -3
  314. data/lib/hecks/router/namespace_installer.rb +16 -3
  315. data/lib/hecks/router.rb +56 -1
  316. data/lib/hecks/runtime/aggregate_lock.rb +20 -11
  317. data/lib/hecks/runtime/boot_gates.rb +21 -3
  318. data/lib/hecks/runtime/caller.rb +40 -8
  319. data/lib/hecks/runtime/capability_graph.rb +13 -2
  320. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
  321. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
  322. data/lib/hecks/runtime/command_interpreter.rb +195 -113
  323. data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
  324. data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
  325. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  326. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  327. data/lib/hecks/runtime/command_rules/references.rb +145 -55
  328. data/lib/hecks/runtime/command_rules.rb +3 -0
  329. data/lib/hecks/runtime/dependency_planning.rb +56 -11
  330. data/lib/hecks/runtime/dispatcher.rb +263 -92
  331. data/lib/hecks/runtime/entity_element.rb +304 -67
  332. data/lib/hecks/runtime/entity_interpreter.rb +149 -88
  333. data/lib/hecks/runtime/errors.rb +37 -23
  334. data/lib/hecks/runtime/event.rb +15 -10
  335. data/lib/hecks/runtime/identity.rb +91 -23
  336. data/lib/hecks/runtime/instance.rb +103 -18
  337. data/lib/hecks/runtime/interpreting.rb +24 -16
  338. data/lib/hecks/runtime/invocation.rb +358 -0
  339. data/lib/hecks/runtime/loader.rb +105 -19
  340. data/lib/hecks/runtime/outbox.rb +164 -26
  341. data/lib/hecks/runtime/policy_interpreter.rb +73 -60
  342. data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
  343. data/lib/hecks/runtime/query_interpreter.rb +93 -74
  344. data/lib/hecks/runtime/reaction_invocation.rb +73 -28
  345. data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
  346. data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
  347. data/lib/hecks/runtime/reference_hop.rb +48 -6
  348. data/lib/hecks/runtime/refusal_wording.rb +142 -115
  349. data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
  350. data/lib/hecks/runtime/registry/verification.rb +153 -28
  351. data/lib/hecks/runtime/registry.rb +202 -32
  352. data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
  353. data/lib/hecks/runtime/routing.rb +36 -154
  354. data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
  355. data/lib/hecks/runtime/saga_interpreter.rb +90 -76
  356. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  357. data/lib/hecks/runtime/tenant_check.rb +33 -13
  358. data/lib/hecks/runtime/tenant_scope.rb +23 -5
  359. data/lib/hecks/runtime/value/admission.rb +75 -30
  360. data/lib/hecks/runtime/value/coercion.rb +555 -142
  361. data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
  362. data/lib/hecks/runtime/value.rb +71 -21
  363. data/lib/hecks/runtime.rb +39 -7
  364. data/lib/hecks/storehouse.rb +368 -72
  365. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  366. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  367. data/lib/hecks/version.rb +3 -3
  368. data/lib/hecks/vocabulary.rb +205 -4
  369. data/lib/hecks.rb +91 -11
  370. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
  371. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
  372. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
  373. metadata +28 -2
@@ -1,7 +1,7 @@
1
1
  module Hecks
2
2
  module Grammar
3
3
  # The file surgery under bin/evolve: reading and rewriting the
4
- # aggregate-local KeywordSeed/ArgumentSeed rows as TEXT, so a
4
+ # aggregate-local KeywordSeed/ArgumentSeed rows as text, so a
5
5
  # proposed word enters the table exactly as a hand would write it
6
6
  # and an admitted one loses its ceremony (an absent status reads as
7
7
  # admitted — the grown-column convention).
@@ -18,11 +18,15 @@ module Hecks
18
18
  module_function
19
19
 
20
20
  # bin/evolve's own `--name value` flag reader. Only consumes the
21
- # NEXT argv element as the value when that element doesn't itself
21
+ # next argv element as the value when that element doesn't itself
22
22
  # look like a flag — otherwise `--foo --bar` would swallow `--bar`
23
23
  # as `--foo`'s value (and `--bar` would then never be seen at
24
24
  # all), and a value-less `--foo` at the end of argv would bypass
25
25
  # whatever default `foo` promised instead of falling back to it.
26
+ # @param argv [Array<String>] the command-line argument list to scan
27
+ # @param name [String] the flag's name, without its leading `--`
28
+ # @param default [String, nil] value to use when the flag is absent or has no value
29
+ # @return [String, nil] the flag's value, or `default`
26
30
  def option(argv, name, default = nil)
27
31
  index = argv.index("--#{name}")
28
32
  return default unless index
@@ -42,6 +46,12 @@ module Hecks
42
46
  # the gate specs, not on anything this method knows about). A
43
47
  # clean return leaves the snapshots unused; the caller decides
44
48
  # from there whether the tree stands.
49
+ #
50
+ # @param paths [Array<String>] files to snapshot before running the block
51
+ # @yield the mutating work to run, restored from snapshot if it raises
52
+ # @return [Object] the block's result
53
+ # @raise [StandardError] re-raises whatever the block raised, after restoring
54
+ # every snapshotted file
45
55
  def restore_on_raise(paths)
46
56
  snapshots = paths.to_h { |path| [path, File.read(path)] }
47
57
  yield
@@ -50,6 +60,10 @@ module Hecks
50
60
  raise
51
61
  end
52
62
 
63
+ # Every syntax-table file declaring a `KeywordSeed` or `ArgumentSeed`
64
+ # value object.
65
+ #
66
+ # @return [Array<String>] matching `.bluebook` file paths
53
67
  def syntax_paths
54
68
  Dir.glob(File.expand_path("../language/**/*.bluebook", __dir__)).select do |path|
55
69
  source = File.read(path)
@@ -60,13 +74,25 @@ module Hecks
60
74
  # Kept as a narrow compatibility door for callers deliberately doing
61
75
  # single-file surgery. Normal operation uses `syntax_paths` and discovers
62
76
  # the owning concept from the row itself.
77
+ #
78
+ # @return [String, nil] the first syntax-table path, or nil when there are none
63
79
  def syntax_path = syntax_paths.first
64
80
 
81
+ # Resolves the file(s) a call should search or write.
82
+ #
83
+ # @param path [String, nil] an explicit single file, or nil for every syntax path
84
+ # @return [Array<String>] `[path]` when given, else `syntax_paths`
65
85
  def paths_for(path) = path ? Array(path) : syntax_paths
66
86
 
67
87
  # The Keyword one_of's member rows, parsed leniently off the text —
68
88
  # enough to know each row's (word, context, status), which is all
69
89
  # the tool ever asks.
90
+ #
91
+ # @param path [String, nil] an explicit single file to read, or nil for every
92
+ # syntax path
93
+ # @return [Array<Hash>] one Hash per member row, with `:word`, `:context`,
94
+ # `:status` (`"admitted"` when unspelled), and `:was` (nil unless renamed)
95
+ # @raise [Refusal] if `path` is given and declares no `KeywordSeed` value object
70
96
  def keyword_rows(path = nil)
71
97
  paths_for(path).flat_map do |candidate|
72
98
  blocks = seed_blocks(File.read(candidate), "KeywordSeed")
@@ -82,6 +108,20 @@ module Hecks
82
108
  end
83
109
  end
84
110
 
111
+ # Declares a new, proposed keyword row in the syntax table that owns
112
+ # `context` (or `opens`'s own aggregate, for a `File`-context word).
113
+ #
114
+ # @param word [String] the keyword's spelling
115
+ # @param context [String] the grammar context the word is declared in
116
+ # @param body [String] the keyword's body shape; `"none"` by default
117
+ # @param inner [String] the keyword's inner shape, if any
118
+ # @param opens [String] the aggregate concept a `File`-context word opens
119
+ # @param fills [String] the field the keyword fills, if any
120
+ # @param path [String, nil] an explicit single file to search/write, or nil to
121
+ # search every syntax path
122
+ # @return [void]
123
+ # @raise [Refusal] if `context`.`word` is already declared, or no syntax table
124
+ # owns `context`
85
125
  def propose(word:, context:, body: "none", inner: "", opens: "", fills: "", path: nil)
86
126
  if keyword_rows(path).any? do |row|
87
127
  row[:word] == word && row[:context] == context
@@ -97,7 +137,7 @@ module Hecks
97
137
  row = %(#{indent}member word: "#{word}", context: "#{context}", body: "#{body}", ) +
98
138
  %(inner: "#{inner}", opens: "#{opens}", fills: "#{fills}", status: "proposed"\n)
99
139
 
100
- # At the END of the one_of — grouping by context is a courtesy of
140
+ # At the end of the one_of — grouping by context is a courtesy of
101
141
  # the hand; a proposed row sits at the bottom until admission,
102
142
  # when whoever admits it may move it home.
103
143
  closing = block.rindex(/^\s*end\s*$/)
@@ -105,6 +145,17 @@ module Hecks
105
145
  File.write(path, source.sub(block, updated))
106
146
  end
107
147
 
148
+ # Rewrites a declared keyword row's `status:` cell in place.
149
+ #
150
+ # @param word [String] the keyword's spelling
151
+ # @param context [String] the grammar context the word is declared in
152
+ # @param to [String] the new status: `"proposed"`, `"admitted"`, `"deprecated"`,
153
+ # or `"retired"`
154
+ # @param path [String, nil] an explicit single file to search/write, or nil to
155
+ # search every syntax path
156
+ # @return [void]
157
+ # @raise [Refusal] if `to` is not one of the four stations, or `context`.`word`
158
+ # is not declared
108
159
  def set_status(word:, context:, to:, path: nil)
109
160
  raise Refusal, "#{to.inspect} is not a station a word's life admits" unless %w[proposed admitted deprecated
110
161
  retired].include?(to)
@@ -119,7 +170,7 @@ module Hecks
119
170
  next line unless member_row?(line, word, context)
120
171
 
121
172
  stripped = line.sub(/,\s*status: "[^"]*"/, "")
122
- # Admitted is the default and stays UNSPELLED — only a word
173
+ # Admitted is the default and stays unspelled — only a word
123
174
  # entering or leaving the language carries its status.
124
175
  to == "admitted" ? stripped : stripped.sub(/\n\z/, %(, status: "#{to}"\n))
125
176
  end.join
@@ -131,8 +182,17 @@ module Hecks
131
182
  # `was:` — one hop only. Renaming an already-renamed word refuses
132
183
  # until the language grows real eras for its own words; renaming
133
184
  # onto a spelling the context already declares refuses too. The
134
- # word's Argument rows follow it — row-aware now, not the blind
135
- # substitution this used to be (see `cascade_argument_rename`).
185
+ # word's Argument rows follow it — row-aware, not a blind
186
+ # substitution (see `cascade_argument_rename`).
187
+ #
188
+ # @param word [String] the keyword's current spelling
189
+ # @param context [String] the grammar context the word is declared in
190
+ # @param to [String] the keyword's new spelling
191
+ # @param path [String, nil] an explicit single file to search/write, or nil to
192
+ # search every syntax path
193
+ # @return [void]
194
+ # @raise [Refusal] if `context`.`word` is not declared, was already renamed once,
195
+ # or `to` is already declared in `context`
136
196
  def rename(word:, context:, to:, path: nil)
137
197
  row = keyword_rows(path).find { |r| r[:word] == word && r[:context] == context }
138
198
  raise Refusal, "#{context}.#{word} is not declared" unless row
@@ -160,16 +220,38 @@ module Hecks
160
220
  cascade_argument_rename(keyword: word, context: context, to: to, path: paths)
161
221
  end
162
222
 
223
+ # Tells whether `line` is a KeywordSeed member row for `(word, context)`.
224
+ #
225
+ # @param line [String] one raw source line
226
+ # @param word [String] the keyword's spelling
227
+ # @param context [String] the grammar context
228
+ # @return [Boolean]
163
229
  def member_row?(line, word, context)
164
230
  line =~ /^\s*member / && line.include?(%(word: "#{word}")) && line.include?(%(context: "#{context}"))
165
231
  end
166
232
 
233
+ # Finds which syntax-table path declares a keyword row.
234
+ #
235
+ # @param word [String] the keyword's spelling
236
+ # @param context [String] the grammar context
237
+ # @param paths [Array<String>] candidate syntax-table paths to search
238
+ # @return [String] the path whose KeywordSeed declares `(word, context)`
239
+ # @raise [Refusal] if no path in `paths` declares that row
167
240
  def path_holding_keyword(word, context, paths = syntax_paths)
168
241
  paths.find do |candidate|
169
242
  keyword_blocks(File.read(candidate)).any? { |block| block.lines.any? { |line| member_row?(line, word, context) } }
170
243
  end || raise(Refusal, "#{context}.#{word} is not declared")
171
244
  end
172
245
 
246
+ # Finds which syntax-table path declares an argument row.
247
+ #
248
+ # @param keyword [String] the argument's owning keyword
249
+ # @param context [String] the grammar context
250
+ # @param at [String] the argument's positional slot, `""` for a named-only argument
251
+ # @param named [String] the argument's keyword name, `""` for a positional-only argument
252
+ # @param paths [Array<String>] candidate syntax-table paths to search
253
+ # @return [String] the path whose ArgumentSeed declares this row
254
+ # @raise [Refusal] if no path in `paths` declares that row
173
255
  def path_holding_argument(keyword, context, at, named, paths = syntax_paths)
174
256
  paths.find do |candidate|
175
257
  argument_blocks(File.read(candidate)).any? do |block|
@@ -183,6 +265,14 @@ module Hecks
183
265
  # A new row belongs wherever that context's existing rows live. File is
184
266
  # intentionally wider than one aggregate; for a new entry point, `opens`
185
267
  # identifies the aggregate concept whose file should own it.
268
+ #
269
+ # @param context [String] the grammar context a new row is being added to
270
+ # @param word [String] the word being added, used only in the refusal message
271
+ # @param opens [String] the aggregate concept a `File`-context word opens
272
+ # @param paths [Array<String>] candidate syntax-table paths to search
273
+ # @return [String] the path that should own the new row
274
+ # @raise [Refusal] if no candidate path declares an existing row for `context`
275
+ # (and, for a `File` context with `opens` given, no path declares that aggregate)
186
276
  def owner_path(context:, word:, opens: "", paths: syntax_paths)
187
277
  if context == "File" && !opens.to_s.empty?
188
278
  aggregate_path = paths.find { |candidate| File.read(candidate).match?(/^\s*aggregate "#{Regexp.escape(opens)}" do$/) }
@@ -197,6 +287,13 @@ module Hecks
197
287
  end || raise(Refusal, "no aggregate-local syntax table owns context #{context.inspect} for #{word}")
198
288
  end
199
289
 
290
+ # Every `value_object "<name>"` block's full source text, from its
291
+ # opener to its closing `end`.
292
+ #
293
+ # @param source [String] a `.bluebook` file's source text
294
+ # @param name [String] the value object's name, such as `"KeywordSeed"`
295
+ # @return [Array<String>] each matching block's raw source, including the
296
+ # opener and closing `end` lines
200
297
  def seed_blocks(source, name)
201
298
  opener = /^([ \t]*)value_object "#{Regexp.escape(name)}" do$/
202
299
  source.to_enum(:scan, opener).map do
@@ -209,6 +306,15 @@ module Hecks
209
306
  end
210
307
  end
211
308
 
309
+ # The first `value_object "<name>"` block's source text.
310
+ #
311
+ # @param source [String] a `.bluebook` file's source text
312
+ # @param name [String] the value object's name, such as `"KeywordSeed"`
313
+ # @param required [Boolean] whether a missing block should raise instead of
314
+ # returning nil
315
+ # @return [String, nil] the block's raw source, or nil when absent and not
316
+ # `required`
317
+ # @raise [Refusal] if `required` and `source` declares no such value object
212
318
  def seed_block(source, name, required: false)
213
319
  block = seed_blocks(source, name).first
214
320
  unless block
@@ -219,21 +325,37 @@ module Hecks
219
325
  block
220
326
  end
221
327
 
222
- # From `value_object "KeywordSeed"` to ITS OWN closing `end` — `member`
328
+ # From `value_object "KeywordSeed"` to its own closing `end` — `member`
223
329
  # rows sit bare now (S3, ADR 0025 — the `one_of do ... end` wrapper
224
330
  # is gone), so the first bare `end` line after the opener already
225
- # IS the value object's own, the same fact the original one_of-
331
+ # is the value object's own, the same fact the original one_of-
226
332
  # nested version of this method leaned on (nothing else nested
227
333
  # inside it either, before or after).
334
+ # @param source [String] a `.bluebook` file's source text
335
+ # @return [Array<String>] each KeywordSeed block's raw source
228
336
  def keyword_blocks(source) = seed_blocks(source, "KeywordSeed")
337
+
338
+ # The first KeywordSeed block's source text.
339
+ #
340
+ # @param source [String] a `.bluebook` file's source text
341
+ # @return [String] the first KeywordSeed block's raw source
342
+ # @raise [Refusal] if `source` declares no KeywordSeed value object
229
343
  def keyword_block(source) = seed_block(source, "KeywordSeed", required: true)
230
344
 
231
345
  # ── the Argument rows — a word's own arguments, at last with tooling
232
346
  # of their own rather than the rename-only cascade above. A word may
233
- # carry SEVERAL argument rows (one per position, one per named
347
+ # carry several argument rows (one per position, one per named
234
348
  # kwarg), so identity here is the full (keyword, context, at, named)
235
349
  # tuple, not the two-field key a Keyword row answers to.
236
350
 
351
+ # The ArgumentSeed's member rows, parsed leniently off the text.
352
+ #
353
+ # @param path [String, nil] an explicit single file to read, or nil for every
354
+ # syntax path
355
+ # @return [Array<Hash>] one Hash per member row, with `:keyword`, `:context`,
356
+ # `:at`, `:named`, `:kind`, `:required`, `:fills`, and `:status`
357
+ # (`"admitted"` when unspelled)
358
+ # @raise [Refusal] if `path` is given and declares no `ArgumentSeed` value object
237
359
  def argument_rows(path = nil)
238
360
  paths_for(path).flat_map do |candidate|
239
361
  blocks = seed_blocks(File.read(candidate), "ArgumentSeed")
@@ -250,13 +372,27 @@ module Hecks
250
372
  end
251
373
  end
252
374
 
253
- # `pairs_shape` — for a `pairs` argument that fills ONE field with a
375
+ # `pairs_shape` — for a `pairs` argument that fills one field with a
254
376
  # whole key/value list rather than naming a field per pair (the
255
377
  # shape `Handler.dispatch`'s own `with:` already carries). Without
256
378
  # it, `spec/syntax_conformance_spec.rb` reads a pairs argument
257
379
  # naming a single field as a row that "names a single field, which
258
380
  # it cannot fill" — correctly, since the two shapes are genuinely
259
381
  # different and only one of them can be checked the same way.
382
+ # @param keyword [String] the argument's owning keyword
383
+ # @param context [String] the grammar context
384
+ # @param kind [String] the argument's value kind
385
+ # @param required [String] `"true"` or `"false"`, as text like every other cell
386
+ # @param at [String] the argument's positional slot, `""` for a named-only argument
387
+ # @param named [String] the argument's keyword name, `""` for a positional-only argument
388
+ # @param fills [String] the field the argument fills, if any
389
+ # @param pairs_shape [String, nil] the shape a `pairs` argument's key/value list
390
+ # fills, or nil when this argument is not a `pairs` argument
391
+ # @param path [String, nil] an explicit single file to search/write, or nil to
392
+ # search every syntax path
393
+ # @return [void]
394
+ # @raise [Refusal] if this (keyword, context, at, named) row is already declared,
395
+ # or no syntax table owns `context`
260
396
  def propose_argument(keyword:, context:, kind:, required: "false", at: "", named: "", fills: "",
261
397
  pairs_shape: nil, path: nil)
262
398
  if argument_rows(path).any? { |r| argument_identity(r) == [keyword, context, at, named] }
@@ -280,6 +416,18 @@ module Hecks
280
416
  File.write(path, source.sub(block, updated))
281
417
  end
282
418
 
419
+ # Rewrites a declared argument row's `status:` cell in place.
420
+ #
421
+ # @param keyword [String] the argument's owning keyword
422
+ # @param context [String] the grammar context
423
+ # @param to [String] the new status: `"proposed"`, `"admitted"`, `"deprecated"`,
424
+ # or `"retired"`
425
+ # @param at [String] the argument's positional slot, `""` for a named-only argument
426
+ # @param named [String] the argument's keyword name, `""` for a positional-only argument
427
+ # @param path [String, nil] an explicit single file to search/write, or nil to
428
+ # search every syntax path
429
+ # @return [void]
430
+ # @raise [Refusal] if `to` is not one of the four stations, or the row is not declared
283
431
  def set_argument_status(keyword:, context:, to:, at: "", named: "", path: nil)
284
432
  raise Refusal, "#{to.inspect} is not a station an argument's life admits" unless %w[proposed admitted deprecated
285
433
  retired].include?(to)
@@ -307,19 +455,39 @@ module Hecks
307
455
  File.write(path, source.sub(block, updated))
308
456
  end
309
457
 
458
+ # Tells whether `line` is an ArgumentSeed member row for this
459
+ # (keyword, context, at, named) tuple.
460
+ #
461
+ # @param line [String] one raw source line
462
+ # @param keyword [String] the argument's owning keyword
463
+ # @param context [String] the grammar context
464
+ # @param at [String] the argument's positional slot
465
+ # @param named [String] the argument's keyword name
466
+ # @return [Boolean]
310
467
  def argument_row?(line, keyword, context, at, named)
311
468
  line =~ /^\s*member / &&
312
469
  line.include?(%(keyword: "#{keyword}")) && line.include?(%(context: "#{context}")) &&
313
470
  line.include?(%(at: "#{at}")) && line.include?(%(named: "#{named}"))
314
471
  end
315
472
 
473
+ # The identity tuple an argument row is keyed by.
474
+ #
475
+ # @param row [Hash] an argument row, as `argument_rows` returns one
476
+ # @return [Array(String, String, String, String)] the row's `(keyword, context,
477
+ # at, named)` identity tuple
316
478
  def argument_identity(row) = [row[:keyword], row[:context], row[:at], row[:named]]
317
479
 
318
- # The rename cascade, ROW-AWARE — only the rows that actually belong
480
+ # The rename cascade, row-aware — only the rows that actually belong
319
481
  # to the renamed word, spelling updated in place, rather than a
320
482
  # blind `gsub` on every `keyword: "word",` substring in the file
321
483
  # (which a coincidentally-matching row elsewhere could have
322
484
  # corrupted, and which read nothing before writing).
485
+ # @param keyword [String] the keyword whose argument rows follow its rename
486
+ # @param context [String] the grammar context
487
+ # @param to [String] the keyword's new spelling
488
+ # @param path [String, nil] an explicit single file to search/write, or nil to
489
+ # search every syntax path
490
+ # @return [void]
323
491
  def cascade_argument_rename(keyword:, context:, to:, path: nil)
324
492
  paths_for(path).each do |candidate|
325
493
  source = File.read(candidate)
@@ -337,11 +505,19 @@ module Hecks
337
505
  end
338
506
  end
339
507
 
340
- # From `value_object "ArgumentSeed"` to ITS OWN closing `end` — see
508
+ # From `value_object "ArgumentSeed"` to its own closing `end` — see
341
509
  # `keyword_block`'s own comment for why the first bare `end` after
342
510
  # the opener is already the right one, now that `member` rows sit
343
511
  # bare (S3, ADR 0025).
512
+ # @param source [String] a `.bluebook` file's source text
513
+ # @return [Array<String>] each ArgumentSeed block's raw source
344
514
  def argument_blocks(source) = seed_blocks(source, "ArgumentSeed")
515
+
516
+ # The first ArgumentSeed block's source text.
517
+ #
518
+ # @param source [String] a `.bluebook` file's source text
519
+ # @return [String] the first ArgumentSeed block's raw source
520
+ # @raise [Refusal] if `source` declares no ArgumentSeed value object
345
521
  def argument_block(source) = seed_block(source, "ArgumentSeed", required: true)
346
522
  end
347
523
  end
data/lib/hecks/grammar.rb CHANGED
@@ -7,12 +7,12 @@ require_relative "../hecks"
7
7
 
8
8
  module Hecks
9
9
  # The sublanguage grammar domains (grammar/*.bluebook) and the one boot
10
- # path for reading them as DATA — the expression chapter replayed
10
+ # path for reading them as data — the expression chapter replayed
11
11
  # through its own admission ledger, so anything derived from it (the
12
12
  # operator projections, the conformance specs) reads the set that
13
13
  # actually survived the Admit gates, never a hand-copied list.
14
14
  #
15
- # Booted on CALL, never at require: the Prism adapter normalises every
15
+ # Booted on call, never at require: the Prism adapter normalises every
16
16
  # predicate through CanonicalForm while a bluebook loads, so the
17
17
  # expression machinery cannot boot the chapter that configures it —
18
18
  # this module exists precisely so generators and specs boot it in a
@@ -27,6 +27,11 @@ module Hecks
27
27
  # its real commands. A refused step raises — a generator running off
28
28
  # a half-admitted ledger would project a table the gates never
29
29
  # accepted.
30
+ #
31
+ # @return [Runtime::Dispatcher] the dispatcher bound to the booted, replayed
32
+ # expression chapter
33
+ # @raise [Runtime::WiringError] if a ledger step is refused by the chapter's
34
+ # own domain rules
30
35
  def expression
31
36
  registry = Runtime::Registry.new
32
37
  root = File.expand_path("../..", __dir__)
@@ -42,7 +47,7 @@ module Hecks
42
47
  JSON.parse(File.read(LEDGER)).fetch("steps").each do |step|
43
48
  args = symbolize(step.fetch("args"))
44
49
  begin
45
- dispatcher.dispatch(step.fetch("verb"), **args)
50
+ dispatcher.dispatch_flat(step.fetch("verb"), args)
46
51
  rescue *Runtime::DOMAIN_REFUSALS => e
47
52
  raise Runtime::WiringError,
48
53
  "the admission ledger refused at #{step['verb']} #{step['args']} — #{e.message}"
@@ -52,6 +57,13 @@ module Hecks
52
57
  dispatcher
53
58
  end
54
59
 
60
+ # Reads every admitted operator from the ledger's replayed chapter.
61
+ #
62
+ # @param dispatcher [Runtime::Dispatcher] a dispatcher bound to the booted
63
+ # expression chapter; defaults to booting and replaying a fresh one
64
+ # @return [Array<Hash>] one Hash per admitted operator, with `:symbol`,
65
+ # `:category`, `:precedence`, `:arity`, and `:renderings` (an Array of
66
+ # `{target:, form:}` Hashes)
55
67
  def admitted_operators(dispatcher = expression)
56
68
  records(dispatcher, "Operator").select { |op| op[:status] == "admitted" }.map do |op|
57
69
  { symbol: op[:symbol].value, category: op[:category].value,
@@ -60,6 +72,13 @@ module Hecks
60
72
  end
61
73
  end
62
74
 
75
+ # Reads every admitted normalisation rule from the ledger's replayed
76
+ # chapter, in position order.
77
+ #
78
+ # @param dispatcher [Runtime::Dispatcher] a dispatcher bound to the booted
79
+ # expression chapter; defaults to booting and replaying a fresh one
80
+ # @return [Array<Hash>] one Hash per admitted rule, with `:strategy`,
81
+ # `:source_token`, `:replacement`, `:boundary`, and `:position`
63
82
  def admitted_normalisations(dispatcher = expression)
64
83
  records(dispatcher, "Normalisation")
65
84
  .select { |rule| rule[:status] == "admitted" }
@@ -71,22 +90,34 @@ module Hecks
71
90
  end
72
91
  end
73
92
 
93
+ # Reads every record of one aggregate from the expression chapter's own
94
+ # repository.
95
+ #
96
+ # @param dispatcher [Runtime::Dispatcher] a dispatcher bound to the booted
97
+ # expression chapter
98
+ # @param aggregate_name [String] the aggregate's declared name, such as
99
+ # `"Operator"`
100
+ # @return [Array<Runtime::Instance>] every stored instance of the aggregate
74
101
  def records(dispatcher, aggregate_name)
75
102
  registry = dispatcher.registry
76
103
  aggregate = registry.bluebook("Expression").aggregate(aggregate_name)
77
104
  registry.repository("Expression", aggregate).all
78
105
  end
79
106
 
80
- # THE OPERATORS THE LANGUAGE STANDS ON. Every guard and invariant in
107
+ # The operators the language stands on. Every guard and invariant in
81
108
  # the language's own chapters — the meta-domain (Bluebook, World) and
82
109
  # the grammar chapters beside this file — evaluates through the very
83
110
  # operator table the ledger admits. An operator one of those
84
- # predicates uses is SELF-BEARING: retire it and the language can no
111
+ # predicates uses is self-bearing: retire it and the language can no
85
112
  # longer read its own rules — found the hard way, as a projection
86
113
  # missing `!=` that could not boot the chapter to fix itself. This
87
114
  # derives the set, with a usage site per operator, so the generator
88
- # and the conformance spec can refuse the retirement BY NAME instead
115
+ # and the conformance spec can refuse the retirement by name instead
89
116
  # of wedging.
117
+ #
118
+ # @return [Hash{String => Array<String>}] each self-bearing operator symbol
119
+ # mapped to the `"Chapter Aggregate.command"`/`"Chapter Aggregate::ValueObject"`
120
+ # sites that use it
90
121
  def self_bearing_operators
91
122
  sites = Hash.new { |h, k| h[k] = [] }
92
123
 
@@ -118,6 +149,9 @@ module Hecks
118
149
 
119
150
  # Every grammar/*.bluebook chapter, booted the same way the corpus
120
151
  # boots them — each alone, in a scratch registry.
152
+ #
153
+ # @return [Array<Class>] each grammar chapter, booted alone in its own scratch
154
+ # registry
121
155
  def grammar_chapters
122
156
  Dir[File.join(DIR, "*.bluebook")].map do |chapter|
123
157
  registry = Runtime::Registry.new
@@ -136,6 +170,10 @@ module Hecks
136
170
  # Which admitted operators one canonical text evaluates through —
137
171
  # the evaluator's own parse, walked for its operator nodes, leaves
138
172
  # walked for the resolver's arithmetic.
173
+ #
174
+ # @param canonical [String] canonical expression text to parse
175
+ # @return [Array<String>] operator symbols the expression evaluates through, or
176
+ # `[]` when `canonical` fails to parse
139
177
  def operators_in(canonical)
140
178
  evaluator = Bluebook::Expression::Evaluator
141
179
  begin
@@ -146,7 +184,7 @@ module Hecks
146
184
  walk_operators(node, evaluator).uniq
147
185
  end
148
186
 
149
- # A recursive descent over a CLOSED, declared set of AST node types
187
+ # A recursive descent over a closed, declared set of AST node types
150
188
  # (Evaluator's boolean/compare/include nodes, Resolver's arithmetic
151
189
  # nodes, and the generic Struct fallback) — each branch does the
152
190
  # same one thing (name the node's own operator, recurse into its
@@ -155,6 +193,10 @@ module Hecks
155
193
  # would trade one place that shows the whole operator vocabulary for
156
194
  # several that each show a fragment, with no reduction in real
157
195
  # complexity.
196
+ # @param node [Object] an evaluator/resolver AST node, or a Struct fallback
197
+ # @param evaluator [Module] `Bluebook::Expression::Evaluator`, passed through
198
+ # so nested calls don't re-resolve the constant
199
+ # @return [Array<String>] operator symbols found in `node` and its children
158
200
  # rubocop:disable-next Metrics/AbcSize
159
201
  def walk_operators(node, evaluator)
160
202
  resolver = Bluebook::Expression::Resolver
@@ -177,6 +219,10 @@ module Hecks
177
219
  end
178
220
  end
179
221
 
222
+ # Deep-symbolizes a JSON-decoded value's Hash keys.
223
+ #
224
+ # @param value [Object] a Hash, Array, or scalar decoded from JSON
225
+ # @return [Object] `value` with every Hash key (recursively) converted to a Symbol
180
226
  def symbolize(value)
181
227
  case value
182
228
  when Hash then value.to_h { |k, v| [k.to_sym, symbolize(v)] }