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,24 +1,24 @@
1
1
  module Hecks
2
2
  module Bluebook
3
- # WHICH REGEXES A BLUEBOOK MAY SAY.
3
+ # **Which regexes a bluebook may say**.
4
4
  #
5
5
  # A `pattern:` is a fact about a value, carried in a bluebook — declared
6
6
  # data, not Ruby code, so it must not lean on what any one engine happens
7
7
  # to accept. Regex engines disagree in two different ways :
8
8
  #
9
- # ONLY A BACKTRACKING ENGINE CAN MATCH IT — lookahead, lookbehind,
9
+ # only a backtracking engine can match it — lookahead, lookbehind,
10
10
  # backreferences, atomic groups, possessive quantifiers. None of these
11
11
  # can be matched in linear time, and linear-time engines refuse them
12
12
  # outright. Refused here for the same reason.
13
13
  #
14
- # EVERY ENGINE PARSES IT AND THEY MEAN DIFFERENT THINGS — the dangerous
14
+ # every engine parses it and they mean different things — the dangerous
15
15
  # half, because nothing errors. `\d` `\w` `\s` are ASCII in some engines
16
16
  # and Unicode in others ; `[:digit:]` and friends flip the same way in
17
17
  # the other direction. Both families are refused, and a domain spells
18
18
  # the range it means.
19
19
  #
20
20
  # What remains — explicit ranges, alternation, quantifiers, anchors,
21
- # groups — reads identically everywhere, with `^` and `$` as LINE anchors
21
+ # groups — reads identically everywhere, with `^` and `$` as line anchors
22
22
  # (Ruby's reading). The evidence is spec/corpus/fixtures/patterns.json.
23
23
  module PatternSubset
24
24
  Rejection = Struct.new(:construct, :reason)
@@ -57,13 +57,13 @@ module Hecks
57
57
 
58
58
  # nil when the pattern is admitted, a Rejection when it is not.
59
59
  #
60
- # A CHARACTER WALK, deliberately plain : the subset is defined by this
60
+ # A character walk, deliberately plain : the subset is defined by this
61
61
  # walk, and a cleverer spelling would hide what it admits. An escaped
62
- # construct is a LITERAL, not a violation — `\(\?=` is the three
62
+ # construct is a literal, not a violation — `\(\?=` is the three
63
63
  # characters "(?=" and says nothing about lookahead — which is why this
64
64
  # steps over each backslash pair rather than matching the pattern as a
65
65
  # whole.
66
- # One character walk is the subset's DEFINITION (see the method
66
+ # One character walk is the subset's definition (see the method
67
67
  # comment above): each construct-check is a branch in a single
68
68
  # ordered pass sharing `index`/`in_class`/`class_start`. Splitting
69
69
  # the branches into separate methods would force those three cursor
@@ -72,10 +72,14 @@ module Hecks
72
72
  # methods agreeing about a shared cursor" — exactly what the walk's
73
73
  # own comment says a cleverer spelling would obscure.
74
74
  # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
75
+ #
76
+ # @param pattern [String, Symbol, #to_s] the declared `pattern:` regex source
77
+ # @return [Rejection, nil] the reason the pattern is refused, or `nil` if it is
78
+ # entirely within the portable subset
75
79
  def validate(pattern)
76
80
  chars = pattern.to_s.chars
77
81
  index = 0
78
- # A CHARACTER-CLASS INTERIOR IS A DIFFERENT ALPHABET : inside `[...]`,
82
+ # A character-class interior is a different alphabet : inside `[...]`,
79
83
  # `*`, `+`, `?`, `(`, `?` are literal characters, not quantifiers or
80
84
  # group syntax — `[*+]` means "a literal asterisk or plus". `]` is
81
85
  # only the class's close when it isn't the first character after `[`
@@ -132,7 +136,7 @@ module Hecks
132
136
  nil
133
137
  end
134
138
 
135
- # SPELLED OUT, not derived from the key : these strings are the refusal
139
+ # Spelled out, not derived from the key : these strings are the refusal
136
140
  # a caller reads.
137
141
  CONSTRUCTS = {
138
142
  backreference: "backreference",
@@ -145,8 +149,17 @@ module Hecks
145
149
  possessive: "possessive quantifier"
146
150
  }.freeze
147
151
 
152
+ # Builds the rejection for one refused construct.
153
+ #
154
+ # @param key [Symbol] a key of `CONSTRUCTS`/`REASONS`, such as `:lookahead`
155
+ # @return [Rejection] the construct's name and the reason it is refused
148
156
  def refuse(key) = Rejection.new(CONSTRUCTS.fetch(key), REASONS.fetch(key))
149
157
 
158
+ # Says whether a POSIX bracket class (`[:digit:]` and friends) starts at `index`.
159
+ #
160
+ # @param chars [Array<String>] the pattern, split into characters
161
+ # @param index [Integer] the position to check
162
+ # @return [Boolean] whether a POSIX bracket class starts at `index`
150
163
  def posix_class_at?(chars, index)
151
164
  return false unless chars[index] == "[" && chars[index + 1] == ":"
152
165
 
@@ -156,9 +169,13 @@ module Hecks
156
169
  end
157
170
 
158
171
  # A possessive quantifier is `*+`, `++`, `?+`, or a bounded `{n}`/{n,m}`
159
- # immediately followed by `+` — only checked OUTSIDE a character class,
172
+ # immediately followed by `+` — only checked outside a character class,
160
173
  # where `*`, `+`, `?`, `{`, `}` are quantifier syntax rather than
161
174
  # literal characters.
175
+ #
176
+ # @param chars [Array<String>] the pattern, split into characters
177
+ # @param index [Integer] the position to check
178
+ # @return [Boolean] whether a possessive quantifier starts at `index`
162
179
  def possessive_at?(chars, index)
163
180
  return true if %w[* + ?].include?(chars[index]) && chars[index + 1] == "+"
164
181
  return false unless chars[index] == "{"
@@ -169,6 +186,11 @@ module Hecks
169
186
 
170
187
  # Length of a `{n}` / `{n,}` / `{n,m}` bound starting at `index`, or nil
171
188
  # if what's there isn't one.
189
+ #
190
+ # @param chars [Array<String>] the pattern, split into characters
191
+ # @param index [Integer] the position the bound is expected to start at
192
+ # @return [Integer, nil] the bound's length in characters, or `nil` if `index`
193
+ # does not start a `{n}`/`{n,}`/`{n,m}` bound
172
194
  def bounded_quantifier_length(chars, index)
173
195
  cursor = index + 1
174
196
  digit_seen = false
@@ -1,5 +1,5 @@
1
- # GENERATED — projected from the language's own Policy aggregate.
2
- # DO NOT EDIT: the holding half is rendered, and Behaviour::Policy
1
+ # Generated — projected from the language's own Policy aggregate.
2
+ # Do not edit: the holding half is rendered, and Behaviour::Policy
3
3
  # is where anything hand-written belongs.
4
4
  require_relative "behaviour/policy"
5
5
 
@@ -10,28 +10,30 @@ module Hecks
10
10
  include Behaviour::Policy
11
11
 
12
12
  emits_ir(
13
- name: :name,
14
- on_event: :on_event,
15
- trigger_command: :trigger_command,
16
- target_domain: :target_domain,
17
- where: :where,
18
- for_each: :for_each,
19
- with_spec: -> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } },
20
- where_ast: -> { where_ast }
13
+ name: :name,
14
+ on_event: :on_event,
15
+ trigger_command: :trigger_command,
16
+ target_domain: :target_domain,
17
+ expect_undelivered: :expect_undelivered,
18
+ where: :where,
19
+ for_each: :for_each,
20
+ with_spec: -> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } },
21
+ where_ast: -> { where_ast }
21
22
  )
22
23
 
23
- attr_reader :name, :on_event, :trigger_command, :target_domain, :where, :for_each, :with_spec
24
+ attr_reader :name, :on_event, :trigger_command, :target_domain, :expect_undelivered, :where, :for_each, :with_spec
24
25
 
25
- # AGGREGATE, DECLARED AND DELIBERATELY OFF THE WIRE
26
+ # Aggregate, declared and deliberately off the wire
26
27
  # the wire format is a pinned contract, and it does not carry
27
28
  # where a policy was written before the builder hoisted it
28
29
  attr_accessor :aggregate
29
30
 
30
- def initialize(name:, on_event: nil, trigger_command: nil, target_domain: nil, where: nil, for_each: nil, with_spec: [], aggregate: nil)
31
+ def initialize(name:, on_event: nil, trigger_command: nil, target_domain: nil, expect_undelivered: false, where: nil, for_each: nil, with_spec: [], aggregate: nil)
31
32
  @name = name.to_s
32
33
  @on_event = on_event
33
34
  @trigger_command = trigger_command
34
35
  @target_domain = target_domain
36
+ @expect_undelivered = expect_undelivered.to_s == "true"
35
37
  @where = where
36
38
  @for_each = for_each
37
39
  @with_spec = with_spec
@@ -3,8 +3,8 @@ require_relative "../ir"
3
3
 
4
4
  module Hecks
5
5
  module Bluebook
6
- # `compensates` — a SECOND `DispatchSpec`, shape-identical to this
7
- # one, naming the command that undoes THIS dispatch specifically
6
+ # `compensates` — a second `DispatchSpec`, shape-identical to this
7
+ # one, naming the command that undoes this dispatch specifically
8
8
  # (see `ProcessManagerBuilder::HandlerBuilder#dispatch_impl`'s own
9
9
  # comment). `nil` for a dispatch with nothing to undo (a pure
10
10
  # bookkeeping mark, or one whose own effect is superseded by a later
@@ -14,7 +14,7 @@ module Hecks
14
14
  # used" bar would refuse a second level speculatively.
15
15
  DispatchSpec = Struct.new(:command_name, :with_spec, :compensates, keyword_init: true) do
16
16
  # A Struct already answers to_h; including the mixin puts the
17
- # DECLARED emission ahead of Struct's own in the ancestry, which
17
+ # declared emission ahead of Struct's own in the ancestry, which
18
18
  # is what makes the shape data rather than a method body.
19
19
  include Hecks::IR
20
20
 
@@ -39,36 +39,40 @@ module Hecks
39
39
 
40
40
  # The compensation half of a procedure, as its own thing.
41
41
  #
42
- # A PROCESS MANAGER coordinates: legs, states, an opinion about who goes
43
- # next. A SAGA undoes: what makes the world good again when a leg it
42
+ # A process manager coordinates: legs, states, an opinion about who goes
43
+ # next. A saga undoes: what makes the world good again when a leg it
44
44
  # dispatched is refused. Two concepts, and the industry slurs them into one
45
45
  # word — so here they are two objects, and a procedure either has a saga or
46
46
  # does not.
47
47
  #
48
48
  # `undoes` is the ordered list of commands the compensation sends — a
49
- # STATIC PREVIEW, declaration order (`Behaviour::ProcessManager#saga`),
49
+ # static preview, declaration order (`Behaviour::ProcessManager#saga`),
50
50
  # not one instance's own runtime history. Per-dispatch compensation
51
51
  # (`compensates`, on the step it compensates for) moved most of what
52
52
  # a saga undoes off this leg's own hand-written body and onto
53
53
  # whichever forward dispatch each one undoes — this reads every
54
- # declared `compensates` across the WHOLE saga first, then whatever
54
+ # declared `compensates` across the whole saga first, then whatever
55
55
  # this leg's own hand-written body still lists, for compensation
56
- # that isn't expressible as "undo command X." WHICH of a declared
56
+ # that isn't expressible as "undo command X." which of a declared
57
57
  # `compensates` actually fires for one instance, and in what order
58
58
  # (newest-first, completed-legs-only), is `SagaInterpreter`'s own
59
59
  # dynamic `completed_compensations` — a per-instance runtime fact
60
60
  # this declaration-only object could never hold.
61
61
  #
62
- # NAMING COLLISION, ONCE FLAGGED, NOW RESOLVED — `command`'s own
62
+ # **Naming collision, once flagged, now resolved** — `command`'s own
63
63
  # `corrects event, reverses: true` (docs/implemented/decisions/0036-
64
64
  # corrects-is-an-appended-fact-not-a-rewrite.md) already claimed
65
- # `reverses` for a different meaning: auto-deriving a command's OWN
66
- # corrective mutation from a past EVENT, not a saga's own
67
- # compensating leg from a past DISPATCH. This feature keeps
65
+ # `reverses` for a different meaning: auto-deriving a command's own
66
+ # corrective mutation from a past event, not a saga's own
67
+ # compensating leg from a past dispatch. This feature keeps
68
68
  # `reverses` reserved for `corrects` and uses `compensates` for
69
69
  # per-dispatch saga compensation instead — a deliberate choice, not
70
70
  # an accidental collision.
71
71
  Saga = Struct.new(:trigger, :from_state, :to_state, :compensations, keyword_init: true) do
72
+ # Lists the commands this saga sends to undo a refused leg.
73
+ #
74
+ # @return [Array<String>] the name of each compensating command, in the order
75
+ # `compensations` lists them
72
76
  def undoes = compensations.map(&:command_name)
73
77
 
74
78
  def to_s = "#{trigger} → #{to_state} (#{undoes.join(', ')})"
@@ -78,10 +82,10 @@ module Hecks
78
82
  # produced by `DSL::ProcessManagerBuilder` — its start/end events,
79
83
  # correlation field, derived states, and handler rows. Its
80
84
  # compensation half (`saga`, `Behaviour::ProcessManager#saga`) is
81
- # DERIVED from the handler answering `REFUSED`, below, not declared as
85
+ # derived from the handler answering `REFUSED`, below, not declared as
82
86
  # its own construct.
83
87
  class ProcessManager
84
- # The BLUEBOOK's name for this construct, asked the same way of a class
88
+ # The bluebook's name for this construct, asked the same way of a class
85
89
  # that has crossed over and of an IR object that has not. Collapses into
86
90
  # Construct when this one crosses.
87
91
  # The trigger of a compensating leg. Not an event name — no aggregate
@@ -120,6 +124,15 @@ module Hecks
120
124
 
121
125
  attr_reader :name, :correlates_by, :starts_on, :ends_on, :states, :handlers
122
126
 
127
+ # @param name [String, Symbol] the process manager's declared name
128
+ # @param correlates_by [Symbol, nil] the payload field a triggering event's
129
+ # instances are correlated by
130
+ # @param starts_on [String, nil] the event that starts a new instance
131
+ # @param ends_on [String, nil] the event that ends an instance
132
+ # @param states [Array<String>] the declared states this procedure's instances pass
133
+ # through
134
+ # @param handlers [Array<Bluebook::ProcessManagerHandler>] the declared handler rows,
135
+ # one per (event, state) leg
123
136
  def initialize(name:, correlates_by: nil, starts_on: nil, ends_on: nil,
124
137
  states: [], handlers: [])
125
138
  @name = name.to_s
@@ -8,10 +8,15 @@ module Hecks
8
8
 
9
9
  attr_reader :root
10
10
 
11
+ # @param root [String] the directory to search under
11
12
  def initialize(root)
12
13
  @root = File.expand_path(root)
13
14
  end
14
15
 
16
+ # Walks `root` for every directory that holds `.bluebook` files.
17
+ #
18
+ # @return [Array<String>] the absolute path of each `bluebook` directory found,
19
+ # sorted; a directory named in `SKIPPED_DIRECTORIES` is pruned from the walk
15
20
  def bluebook_directories
16
21
  found = []
17
22
  Find.find(root) do |path|
@@ -14,14 +14,31 @@ module Hecks
14
14
 
15
15
  attr_reader :root, :discovery, :register
16
16
 
17
+ # Discovers and loads every domain under `root` in one call.
18
+ #
19
+ # @param root [String] the directory to search under
20
+ # @return [Bluebook::ProjectLoader] the loader, with every discovered domain
21
+ # booted and registered
17
22
  def self.load(root) = new(root).load
18
23
 
24
+ # @param root [String] the directory to search under
25
+ # @param discovery [Bluebook::ProjectDiscovery] the discovery this loader walks
26
+ # `root` with
27
+ # @param register [Bluebook::ProjectRegister] the register booted domains are
28
+ # fed into
19
29
  def initialize(root, discovery: ProjectDiscovery.new(root), register: ProjectRegister.new)
20
30
  @root = File.expand_path(root)
21
31
  @discovery = discovery
22
32
  @register = register
23
33
  end
24
34
 
35
+ # Boots every domain `discovery` finds under `root` and registers its declarations.
36
+ #
37
+ # @return [Bluebook::ProjectLoader] self
38
+ # @raise [Bluebook::ProjectRegister::MissingRealm] see `ProjectRegister#register`
39
+ # @raise [Bluebook::ProjectRegister::LatestMismatch] see `ProjectRegister#register`
40
+ # @raise [Bluebook::ProjectRegister::DuplicateFqn] see `ProjectRegister#register`
41
+ # @raise [Runtime::WiringError] see `ProjectRegister#register`
25
42
  def load
26
43
  discovery.bluebook_directories.each do |directory|
27
44
  runtime = Runtime.boot(directory)
@@ -30,10 +47,33 @@ module Hecks
30
47
  self
31
48
  end
32
49
 
50
+ # Lists every registered entry.
51
+ #
52
+ # @return [Hash{String => Bluebook::ProjectRegister::Entry}] every registered
53
+ # entry, keyed by its FQN
33
54
  def entries = register.entries
55
+
56
+ # Finds a registered entry by its fully-qualified verb.
57
+ #
58
+ # @param address [String, #to_s] the FQN to look up
59
+ # @return [Bluebook::ProjectRegister::Entry] the entry registered under `address`
60
+ # @raise [KeyError] if no entry is registered under `address`
34
61
  def fetch(address) = register.fetch(address)
62
+
63
+ # Says whether an entry is registered under an address.
64
+ #
65
+ # @param address [String, #to_s] the FQN to check
66
+ # @return [Boolean] whether an entry is registered under `address`
35
67
  def include?(address) = register.include?(address)
68
+
69
+ # Lists every registered command entry.
70
+ #
71
+ # @return [Array<Bluebook::ProjectRegister::Entry>] every registered command entry
36
72
  def commands = register.commands
73
+
74
+ # Lists every registered query entry.
75
+ #
76
+ # @return [Array<Bluebook::ProjectRegister::Entry>] every registered query entry
37
77
  def queries = register.queries
38
78
  end
39
79
  end
@@ -4,7 +4,14 @@ module Hecks
4
4
  # declarations become public FQNs, but never searches or boots folders.
5
5
  class ProjectRegister
6
6
  Entry = Struct.new(:fqn, :source_directory, :dispatcher, :declared_verb, :domain_version, keyword_init: true) do
7
+ # Says whether this entry addresses a command.
8
+ #
9
+ # @return [Boolean] whether this entry's `fqn` addresses a command
7
10
  def command? = fqn.command?
11
+
12
+ # Says whether this entry addresses a query.
13
+ #
14
+ # @return [Boolean] whether this entry's `fqn` addresses a query
8
15
  def query? = fqn.query?
9
16
  end
10
17
 
@@ -14,16 +21,53 @@ module Hecks
14
21
 
15
22
  attr_reader :entries
16
23
 
24
+ # Starts an empty register, with no domains loaded yet.
17
25
  def initialize
18
26
  @entries = {}
19
27
  @tenant_directories = Hash.new { |hash, key| hash[key] = [] }
20
28
  end
21
29
 
30
+ # Finds a registered entry by its fully-qualified verb.
31
+ #
32
+ # @param address [String, #to_s] the FQN to look up, such as `"Pizzas.Pizza.Order"`
33
+ # @return [Bluebook::ProjectRegister::Entry] the entry registered under `address`
34
+ # @raise [KeyError] if no entry is registered under `address`
22
35
  def fetch(address) = entries.fetch(address.to_s)
36
+
37
+ # Says whether an entry is registered under an address.
38
+ #
39
+ # @param address [String, #to_s] the FQN to check
40
+ # @return [Boolean] whether an entry is registered under `address`
23
41
  def include?(address) = entries.key?(address.to_s)
42
+
43
+ # Lists every registered command entry.
44
+ #
45
+ # @return [Array<Bluebook::ProjectRegister::Entry>] every registered command entry
24
46
  def commands = entries.values.select(&:command?)
47
+
48
+ # Lists every registered query entry.
49
+ #
50
+ # @return [Array<Bluebook::ProjectRegister::Entry>] every registered query entry
25
51
  def queries = entries.values.select(&:query?)
26
52
 
53
+ # Registers every aggregate command/query and read model query a booted chapter
54
+ # declares, under its versioned and (if current) unversioned FQNs.
55
+ #
56
+ # @param bluebooks [Array<Bluebook::Chapter>] the booted chapters to register
57
+ # @param registry [Runtime::Registry] the registry `bluebooks` booted into
58
+ # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
59
+ # dispatcher entries dispatch through
60
+ # @param directory [String] the source directory `bluebooks` was discovered under
61
+ # @return [Bluebook::ProjectRegister] self
62
+ # @raise [Bluebook::ProjectRegister::MissingRealm] if a bluebook's world declares no
63
+ # realm
64
+ # @raise [Bluebook::ProjectRegister::LatestMismatch] if a world's `latest` disagrees
65
+ # with its bluebook's own version
66
+ # @raise [Bluebook::ProjectRegister::DuplicateFqn] if an FQN this call would register
67
+ # is already registered
68
+ # @raise [Runtime::WiringError] if a second tenant registers the same on-disk
69
+ # directory while any of its aggregates binds to an adapter that is not
70
+ # `tenant_capable?`
27
71
  def register(bluebooks, registry, dispatcher, directory)
28
72
  bluebooks.each do |bluebook|
29
73
  refuse_unless_safe_for_second_tenant!(bluebook, registry, directory)
@@ -86,15 +130,15 @@ module Hecks
86
130
  end
87
131
  end
88
132
 
89
- # THE ACTUAL "more than one tenant" MOMENT — Runtime::TenantCheck's
133
+ # The actual "more than one tenant" moment — Runtime::TenantCheck's
90
134
  # own header names this table as the one place real multitenancy
91
- # happens: the SAME on-disk directory (one domain, one
92
- # `persisted_by` binding) registering a SECOND time, under a
93
- # different realm, into this SAME shared route table. A directory's
94
- # FIRST registration is never refused here — nothing shares its
135
+ # happens: the same on-disk directory (one domain, one
136
+ # `persisted_by` binding) registering a second time, under a
137
+ # different realm, into this same shared route table. A directory's
138
+ # first registration is never refused here — nothing shares its
95
139
  # data yet, so a plain single-tenant deployment on an ordinary
96
140
  # adapter (Postgres, no schema story) still boots exactly as
97
- # before. Only the SECOND (and any later) registration of that
141
+ # before. Only the second (and any later) registration of that
98
142
  # same directory is refused, and refused before this call adds its
99
143
  # routes to the table — so a leaking tenant's requests never
100
144
  # become reachable through `Router#resolve` in the first place.
@@ -6,16 +6,21 @@ module Hecks
6
6
  # throughout this chapter reach for. See lib/hecks/bluebook.rb's own
7
7
  # header for what `Bluebook` is as a whole.
8
8
  module Bluebook
9
+ # Renders a captured Ruby value to its self-describing wire spelling.
10
+ #
11
+ # @param value [Object] any value an `emits_ir` field carries, such as a Symbol,
12
+ # String, Hash, Array, or literal
13
+ # @return [String] the value rendered through `Hecks::Literal.render`
9
14
  def self.render_value(value) = Literal.render(value)
10
15
 
11
- # A query — an ASK, declared on an aggregate or on one of its entities.
16
+ # A query — an ask, declared on an aggregate or on one of its entities.
12
17
  #
13
- # It crosses over as an INSTANCE rather than a class, and the reason is worth
18
+ # It crosses over as an instance rather than a class, and the reason is worth
14
19
  # stating because it is the boundary of the pattern. A query inherits its
15
20
  # whole body from `QuerySpecification::Common::Options` — `wheres`,
16
21
  # `order_by`, `limit`, `offset`, `cursor`,
17
22
  # `authorization`, `null_semantics`, `inspection` — and those
18
- # are INSTANCE methods that the runtime and the SQLite adapter both read.
23
+ # are instance methods that the runtime and the SQLite adapter both read.
19
24
  # Hoisting the declaration onto a metaclass would put the identity and the
20
25
  # specification on opposite sides of the object.
21
26
  #
@@ -40,6 +45,25 @@ module Hecks
40
45
 
41
46
  attr_reader :name, :description, :attributes
42
47
 
48
+ # @param name [String, Symbol] the query's declared name
49
+ # @param description [String, nil] the query's declared prose description
50
+ # @param attributes [Array<Bluebook::Attribute>] the query's declared result fields
51
+ # @param wheres [Array<QuerySpecification::Common::WhereClause>] the declared filter
52
+ # conditions
53
+ # @param order_by [QuerySpecification::Common::OrderBy, nil] the declared sort field
54
+ # and direction, or `nil` for no explicit ordering
55
+ # @param limit [QuerySpecification::Common::LimitSpec, nil] the declared row limit,
56
+ # or `nil` for none
57
+ # @param offset [QuerySpecification::Common::OffsetSpec, nil] the declared row offset,
58
+ # or `nil` for none
59
+ # @param cursor [QuerySpecification::Common::CursorSpec, nil] the declared pagination
60
+ # cursor, or `nil` for none
61
+ # @param authorization [QuerySpecification::Common::AuthorizationSpec, nil] the declared
62
+ # authorization policy, or `nil` for none
63
+ # @param null_semantics [QuerySpecification::Common::NullSemantics, nil] how this query
64
+ # orders `nil` values; defaults to `NullSemantics.default` when omitted
65
+ # @param inspection [QuerySpecification::Common::InspectionSpec, nil] the declared
66
+ # inspection mode, or `nil` for none
43
67
  def initialize(name:, description: nil, attributes: [], wheres: [],
44
68
  order_by: nil, limit: nil, offset: nil, cursor: nil,
45
69
  authorization: nil, null_semantics: nil,
@@ -54,11 +78,14 @@ module Hecks
54
78
  @attributes = attributes
55
79
  end
56
80
 
57
- # A query's shape is NOT fixed — `extra_options_to_h` carries
81
+ # A query's shape is not fixed — `extra_options_to_h` carries
58
82
  # whatever options the specification layer grew (count, median,
59
83
  # group_by, scope_to). Declared emission covers the settled part
60
84
  # and `super` hands it over; the tail stays dynamic, which is the
61
85
  # honest description of it.
86
+ #
87
+ # @return [Hash] the declared emission, merged with whatever `extra_options_to_h`
88
+ # the specification layer currently carries
62
89
  def to_h = super.merge(extra_options_to_h)
63
90
  end
64
91
  end
@@ -4,11 +4,11 @@ module Hecks
4
4
  module Bluebook
5
5
  # A read model — an ask that gathers heads from more than one aggregate.
6
6
  #
7
- # Like a query it crosses over as an INSTANCE, and for the same reason: its
7
+ # Like a query it crosses over as an instance, and for the same reason: its
8
8
  # body is inherited from `QuerySpecification::ReadModel::Specification`, whose
9
9
  # readers the runtime and the SQLite adapter both call on the object. It gains
10
10
  # an identity and an owner ; it keeps the name it always had, because only a
11
- # CLASS ever had a competing answer for `name`.
11
+ # class ever had a competing answer for `name`.
12
12
  class ReadModel < QuerySpecification::ReadModel::Specification
13
13
  include Construct
14
14
 
@@ -29,11 +29,27 @@ module Hecks
29
29
  attr_reader :name, :description, :reference_name, :reference_target, :aggregate_heads, :group_by,
30
30
  :count, :median_field
31
31
 
32
- # `reference_name:`/`reference_target:` are nil for a ROOTLESS read
32
+ # `reference_name:`/`reference_target:` are nil for a rootless read
33
33
  # model (no `reference_to` declared) — `&.` throughout, rather than
34
- # the `.to_s`/`.to_sym` this used to require unconditionally, so
34
+ # calling `.to_s`/`.to_sym` unconditionally, so
35
35
  # `reference_target.nil?` stays a real, checkable fact for the
36
36
  # interpreter instead of silently becoming `""`.
37
+ #
38
+ # @param name [String, Symbol] the read model's declared name
39
+ # @param description [String, nil] the read model's declared prose description
40
+ # @param reference_name [Symbol, String, nil] the local reference attribute this
41
+ # read model roots at, or `nil` for a rootless read model
42
+ # @param reference_target [String, Symbol, nil] the rooted aggregate's name, or
43
+ # `nil` for a rootless read model
44
+ # @param aggregate_heads [Array<Hash{Symbol => Object}>] the included aggregates, each
45
+ # a row with `:aggregate` (the aggregate's name), `:as` (the output field name) and
46
+ # `:many` (whether this head is a many-side join)
47
+ # @param group_by [Array<Hash{field: Symbol}>] the declared group-by fields, one row
48
+ # per field
49
+ # @param count [Boolean, nil] whether this read model reduces to a row count; `nil`
50
+ # when undeclared
51
+ # @param median_field [Symbol, nil] the field this read model reduces to the median
52
+ # of, or `nil` when undeclared
37
53
  def initialize(name:, description: nil, reference_name: nil, reference_target: nil, aggregate_heads: [],
38
54
  group_by: [], count: nil, median_field: nil, **)
39
55
  super(joins: aggregate_heads, **)
@@ -47,11 +63,11 @@ module Hecks
47
63
  # `group_by_fields` is the convenience reader everything but
48
64
  # `to_h`/the Judge's own generic walk actually wants.
49
65
  @group_by = group_by
50
- # ABSENT IS NOT FALSE. `@count` stays nil rather than becoming
66
+ # **Absent is not false**. `@count` stays nil rather than becoming
51
67
  # `false` when undeclared — the same "if you declare it, declare
52
68
  # something" reading `Lifecycle`'s own optional fields rely on
53
69
  # (MetaValidator::Judge#setters skips a setter whose every
54
- # source is `nil`, so an ALWAYS-false `@count` would dispatch
70
+ # source is `nil`, so an always-false `@count` would dispatch
55
71
  # `ReadModel.Count` on every read model that never wrote the
56
72
  # word at all). `!!count` collapses a truthy DSL `true` (or a
57
73
  # reconstructed `true`) to the same `true`/`nil` pair either path
@@ -60,15 +76,15 @@ module Hecks
60
76
  @median_field = median_field&.to_sym
61
77
  end
62
78
 
63
- # `.to_sym` regardless of source — the DIRECT builder path stores
79
+ # `.to_sym` regardless of source — the direct builder path stores
64
80
  # symbols, but `Reconstruction::Shapes#group_by_field` (the
65
- # replayed-from-the-meta-domain path every REAL boot actually
81
+ # replayed-from-the-meta-domain path every real boot actually
66
82
  # goes through) reads the field back as a String, the same way
67
- # `aggregate_heads`' own `:as` does. Row hash KEYS built by
83
+ # `aggregate_heads`' own `:as` does. Row hash keys built by
68
84
  # `Value.materialize_unwrapped` are symbols (`attr.name`), so
69
85
  # this has to be too, or `row[field]` in `nest` silently misses.
70
86
 
71
- # `wheres`/`order_by`/`limit` are spelled explicitly here, the SAME
87
+ # `wheres`/`order_by`/`limit` are spelled explicitly here, the same
72
88
  # mechanism `Query#to_h` already uses (query.rb, read directly
73
89
  # before this was written) — always present, `wheres` a (possibly
74
90
  # empty) array and `order_by`/`limit` nil when undeclared, exactly
@@ -78,22 +94,26 @@ module Hecks
78
94
  # than a reader needing to learn a second one. See this file's own
79
95
  # `filtered_head_name` and language/bluebook/syntax.bluebook's
80
96
  # `ReadModel` `where`/`order_by`/`limit` member rows for the history
81
- # of why this WAS narrower, and 2026-08-11's read-model where/
97
+ # of why this was narrower, and 2026-08-11's read-model where/
82
98
  # order_by/limit task for why it stopped being.
83
99
  # Same dynamic tail as a Query's, plus two collections whose
84
- # ROWS are plain hashes rather than constructs — they are
100
+ # rows are plain hashes rather than constructs — they are
85
101
  # normalised here rather than by `many`, which recurses through
86
102
  # `to_h` and would have nothing to call.
87
- # `count`/`median_field` are ABSENT (no key at all), not `nil`,
103
+ # `count`/`median_field` are absent (no key at all), not `nil`,
88
104
  # when undeclared — unlike `group_by` (always a `[]`, never
89
- # missing) they are the FIRST fields this construct has ever
90
- # added that no OLDER real corpus member could possibly carry, so
105
+ # missing) they are the first fields this construct has ever
106
+ # added that no older real corpus member could possibly carry, so
91
107
  # merging them unconditionally would have put a `null` onto every
92
108
  # existing read model's own wire shape (banking's
93
109
  # `ComplianceDashboard`/`CustomerPortfolio`, the language's own
94
110
  # `WholeBluebook`, ...) for a fact nothing about them changed —
95
111
  # the exact `extra_options_to_h` reads for `cursor`/`offset`/etc,
96
112
  # applied here for the same reason.
113
+ #
114
+ # @return [Hash] the declared emission, with `aggregate_heads`/`group_by` rows
115
+ # stringified, `count`/`median_field` merged in only when declared, and
116
+ # `extra_options_to_h`'s own dynamic tail merged last
97
117
  def to_h
98
118
  reductions = {}
99
119
  reductions[:count] = true if @count