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,55 +1,77 @@
1
+ require_relative "bootstrap_table"
2
+
1
3
  module Hecks
2
4
  module Bluebook
3
5
  module DSL
4
- # THE THREE RESOLUTION PRIMITIVES the S10 given/invariant family's
6
+ # The three resolution primitives the S10 given/invariant family's
5
7
  # own "declared once, referenced by name" mechanism (ADR 0025)
6
8
  # reduces to, at every scope this language has grown one so far
7
9
  # (a command referencing its owner or a sibling piece's entity-wide
8
10
  # pool; an aggregate referencing another aggregate chapter-wide; a
9
11
  # value object referencing a sibling value object on the same
10
- # aggregate) — extracted here, once, so the NEXT scope this family
12
+ # aggregate) — extracted here, once, so the next scope this family
11
13
  # widens to (there will be one — see docs/resolution-rules/
12
14
  # chapter-given.md's own "Known limitations" for two already named)
13
15
  # reuses one of these three shapes instead of a fourth hand-written
14
16
  # near-duplicate resolver.
15
17
  #
16
- # NOT ONE UNIFIED ALGORITHM — a real design question this file
17
- # answers directly: the THREE existing resolvers are not
18
- # superficially different, they are STRUCTURALLY different (a
19
- # multi-pool fallback CHAIN; ONE pool keyed by declaring OWNER,
20
- # needing disambiguation; a LIVE SCAN over already-built sibling
18
+ # ## Not one unified algorithm
19
+ #
20
+ # A real design question this file answers directly: the three existing
21
+ # resolvers are not superficially different, they are structurally different (a
22
+ # multi-pool fallback chain; one pool keyed by declaring owner,
23
+ # needing disambiguation; a live scan over already-built sibling
21
24
  # objects with no separate pool at all) — forcing them into one
22
25
  # shape would be a real behavior change (see `#lookup`, below, for
23
26
  # which construct uses which), not the pure internal refactor this
24
- # module is. `build_rule` is the one piece that WAS genuinely
27
+ # module is. `build_rule` is the one piece that was genuinely
25
28
  # identical across all 7 declaring methods (`given`×3,
26
29
  # `invariant`×3, `ensures`×1) before this file existed — extract
27
30
  # predicate source, refuse if extraction failed, build the struct.
28
31
  #
29
- # `#lookup`/`#verify_resolves_via!` read WHICH construct uses which
32
+ # ## Resolution rules read off the grammar table, not a Ruby-only mirror
33
+ #
34
+ # `#lookup`/`#verify_resolves_via!` read which construct uses which
30
35
  # primitive off the self-hosted grammar table itself
31
- # (`Keyword#resolves_via`, `syntax.bluebook`) — not a Ruby-only
32
- # Hash cross-checked afterward (this file's OWN earlier shape, one
33
- # round ago) — so a real domain's own boot, not just `bundle exec
34
- # rspec`, fails loudly the moment the table and this file's own
36
+ # (`Keyword#resolves_via`, `syntax.bluebook`), so a real domain's own boot, not just
37
+ # `bundle exec rspec`, fails loudly the moment the table and this file's own
35
38
  # hand-written resolution methods disagree.
36
39
  module RuleReference
37
40
  module_function
38
41
 
42
+ # Extracts a predicate block's source and builds the rule struct that holds it,
43
+ # refusing when the source could not be read.
44
+ #
39
45
  # `struct_class` is `Given` or `Invariant` (both `Struct.new(
40
- # :description, :canonical, :predicate, keyword_init: true)` —
46
+ # :description, :canonical, :predicate, :ast, keyword_init: true)` —
41
47
  # `Given` lives in command.rb, `Invariant` in value_object.rb).
42
- # `owner_name`/`word` are ONLY for the refusal message's own
48
+ # `owner_name`/`word` are only for the refusal message's own
43
49
  # wording. `extraction_failure` is the tail of that same
44
- # message, and stays a REQUIRED parameter rather than one
50
+ # message, and stays a required parameter rather than one
45
51
  # hardcoded string on purpose — `given` ("its source could not
46
52
  # be read, so no other runtime could ever evaluate it"),
47
53
  # `invariant` ("it would be a rule the IR cannot carry"), and
48
54
  # `ensures` ("a postcondition is carried as text, and this one
49
- # has none") each already had their OWN exact wording before
55
+ # has none") each already had their own exact wording before
50
56
  # this method existed; unifying them into one generic sentence
51
57
  # would be a real (if small) behavior change this refactor is
52
58
  # not making.
59
+ #
60
+ # @param struct_class [Class] the rule struct to build — `Bluebook::Given` or
61
+ # `Bluebook::Invariant`
62
+ # @param description [String] the rule's own description, as declared by the caller's
63
+ # own `given`/`invariant`/`ensures` word
64
+ # @param predicate [Proc] the rule's own body block, never called here — only its
65
+ # extracted source is used
66
+ # @param owner_name [String] the declaring construct's own name, for the refusal message
67
+ # @param word [String] the declaring word (`"given"`, `"invariant"`, or `"ensures"`),
68
+ # for the refusal message
69
+ # @param extraction_failure [String] the refusal message's own tail, naming what an
70
+ # unreadable predicate would mean for this particular word
71
+ # @return [Bluebook::Given, Bluebook::Invariant] the built rule struct, an instance of
72
+ # `struct_class`
73
+ # @raise [Bluebook::DSL::Malformed] if `predicate`'s source could not be extracted, or
74
+ # it matches against a pattern construct `Expression::AstJson::PatternSubset` refuses
53
75
  def build_rule(struct_class, description, predicate, owner_name:, word:, extraction_failure:)
54
76
  canonical = Ports::Extraction.canonical(predicate)
55
77
 
@@ -64,35 +86,45 @@ module Hecks
64
86
  struct_class.new(description: description, canonical: canonical, predicate: predicate, ast: ast)
65
87
  end
66
88
 
67
- # PRIMITIVE 1 — an ORDERED CHAIN of flat `Hash[description] =>
89
+ # Primitive 1 — an ordered chain of flat `Hash[description] =>
68
90
  # Rule` pools, first match wins. `CommandBuilder#given`'s own
69
- # two-pool shape (its OWN owner's `named_givens`, then a sibling
91
+ # two-pool shape (its own owner's `named_givens`, then a sibling
70
92
  # piece's entity-wide pool) is this with a 2-element chain — a
71
93
  # future single-pool bare reference is the same primitive with a
72
94
  # 1-element chain, not a separate "just look in one hash" method.
95
+ # @param pools [Array<Hash{String => Bluebook::Given, Bluebook::Invariant}>] pools to
96
+ # search in order; the first pool holding `description` wins
97
+ # @param description [String] the rule's own description to find
98
+ # @return [Bluebook::Given, Bluebook::Invariant, nil] the matching rule, or `nil` if no
99
+ # pool has one
73
100
  def resolve_hash_chain(pools, description)
74
101
  pools.each { |pool| return pool[description] if pool.key?(description) }
75
102
  nil
76
103
  end
77
104
 
78
- # PRIMITIVE 2 — ONE pool keyed BY DECLARING OWNER,
105
+ # Primitive 2 — one pool keyed by declaring owner,
79
106
  # `Hash[description][owner] => Rule` — `AggregateBuilder#given`'s
80
107
  # own chapter-wide shape, the only construct so far where the
81
- # SAME description can mean two genuinely different predicates
108
+ # same description can mean two genuinely different predicates
82
109
  # (docs/implemented/resolution-rules/chapter-given.md). Returns the full
83
- # candidates Hash (0, 1, or many entries) — deliberately NOT
110
+ # candidates Hash (0, 1, or many entries) — deliberately not
84
111
  # raising here, so each caller keeps its own exact refusal
85
112
  # wording for "none," "ambiguous," and "declared_by: named the
86
113
  # wrong owner" rather than one generic message papering over all
87
114
  # three.
115
+ # @param pool [Hash{String => Hash{String => Bluebook::Given, Bluebook::Invariant}}]
116
+ # descriptions mapped to their own candidates, each keyed by declaring owner
117
+ # @param description [String] the rule's own description to find
118
+ # @return [Hash{String => Bluebook::Given, Bluebook::Invariant}] the candidates for
119
+ # `description`, keyed by declaring owner; empty when none exist
88
120
  def resolve_owner_keyed(pool, description)
89
121
  pool[description] || {}
90
122
  end
91
123
 
92
- # PRIMITIVE 3 — a LIVE SCAN over already-built SIBLING OBJECTS'
124
+ # Primitive 3 — a live scan over already-built sibling objects'
93
125
  # own collections, not a separately-maintained pool at all —
94
126
  # `ValueObjectBuilder#invariant`'s own shape: every sibling value
95
- # object on the same aggregate has ALREADY been built by the time
127
+ # object on the same aggregate has already been built by the time
96
128
  # a later one references back (declaration order, the same
97
129
  # constraint every scope in this family carries), so there is
98
130
  # nothing to write through — just read their own already-declared
@@ -100,42 +132,57 @@ module Hecks
100
132
  # sibling (`:invariants` today; kept a parameter, not hardcoded,
101
133
  # since a future sibling-scan scope might reference a different
102
134
  # collection).
135
+ # @param siblings [Array<Object>] the already-built sibling objects to scan; each must
136
+ # respond to `reader`
137
+ # @param description [String] the rule's own description to find
138
+ # @param reader [Symbol] the method to call on each sibling to read its own rule
139
+ # collection, such as `:invariants`
140
+ # @return [Bluebook::Given, Bluebook::Invariant, nil] the matching rule, or `nil` if no
141
+ # sibling declares one
103
142
  def resolve_sibling_scan(siblings, description, reader:)
104
143
  siblings.flat_map { |sibling| sibling.public_send(reader) }
105
144
  .find { |rule| rule.description == description }
106
145
  end
107
146
 
108
- # WHICH CONSTRUCT USES WHICH PRIMITIVE — no longer a Ruby-only
109
- # Hash (that WAS this constant's own shape, one round ago): the
147
+ # **Which construct uses which primitive** — no longer a Ruby-only
148
+ # Hash (that was this constant's own shape, one round ago): the
110
149
  # user's own correction — "my goal is that if they read the same
111
150
  # table they behave identically" — means a table only Ruby ever
112
151
  # reads cannot deliver that, no matter how faithfully it is
113
152
  # cross-checked afterward. `Keyword#resolves_via`/`#disambiguator`
114
- # (self-hosted, `syntax.bluebook`) is the REAL table now — the
115
- # SAME generated data `rust/parser/src/keywords.rs` is generated
153
+ # (self-hosted, `syntax.bluebook`) is the real table now — the
154
+ # same generated data `rust/parser/src/keywords.rs` is generated
116
155
  # from (`bin/project_parser_table`). `#lookup` reads it live.
117
156
  #
118
- # THE ONE UNAVOIDABLE EXCEPTION: the meta-domain's own bootstrap
157
+ # **The one unavoidable exception**: the meta-domain's own bootstrap
119
158
  # (`MetaValidator.load_grammar_into`) dispatches `given`/
120
- # `invariant` on ITSELF 61 times while building the very grammar
159
+ # `invariant` on itself 61 times while building the very grammar
121
160
  # table that would answer "how does given/Aggregate resolve" —
122
161
  # `MetaValidator.grammar_registry`/`SyntaxBoot.call` are not
123
- # ready yet, and cannot be made ready without ALREADY having
162
+ # ready yet, and cannot be made ready without already having
124
163
  # resolved a `given` somewhere upstream. `MetaValidator.
125
- # bootstrapping?` is the SAME guard `MetaValidator.call` (the
164
+ # bootstrapping?` is the same guard `MetaValidator.call` (the
126
165
  # judge) already uses to skip self-judging during this exact
127
166
  # window — `#lookup` uses it too, falling back to
128
- # `BOOTSTRAP_FALLBACK` (below) ONLY while it's true. Every REAL
167
+ # `BOOTSTRAP_FALLBACK` (below) only while it's true. Every real
129
168
  # domain (banking, pizzas, compliance, any future one) boots
130
- # AFTER `grammar_registry` is fully built and memoized, so reads
169
+ # after `grammar_registry` is fully built and memoized, so reads
131
170
  # the real table, every time, no exception.
132
- BOOTSTRAP_FALLBACK = {
133
- %w[given Aggregate] => { resolves_via: "owner_keyed", disambiguator: "declared_by" },
134
- %w[given Entity] => { resolves_via: "owner_keyed", disambiguator: "declared_by" },
135
- %w[given Command] => { resolves_via: "hash_chain" },
136
- %w[invariant ValueObject] => { resolves_via: "sibling_scan" }
137
- }.freeze
171
+ #
172
+ # No longer kept in sync by hand — the same `resolves_via`/
173
+ # `disambiguator` columns, projected ahead of time into the
174
+ # committed lib/hecks/bluebook/dsl/bootstrap_table.rb
175
+ # (bin/project_bootstrap_table, pinned by spec/bootstrap_table_spec.rb).
176
+ BOOTSTRAP_FALLBACK = BootstrapTable::RESOLVES
138
177
 
178
+ # Reads how a (word, context) pair resolves its rule references, off the self-hosted
179
+ # grammar table itself (or, while that table is still booting, the projected fallback).
180
+ #
181
+ # @param word [String] the DSL word, such as `"given"` or `"invariant"`
182
+ # @param context [String] the grammar context, such as `"Aggregate"`
183
+ # @return [Hash{Symbol => String, nil}] `:resolves_via` and `:disambiguator` for this
184
+ # (word, context) pair, `nil`-valued when the row leaves either blank, or `{}` if no row
185
+ # matches at all
139
186
  def lookup(word, context)
140
187
  if MetaValidator.bootstrapping?
141
188
  BOOTSTRAP_FALLBACK[[word, context]] || {}
@@ -149,17 +196,24 @@ module Hecks
149
196
  end
150
197
  end
151
198
 
152
- # A LIVE CROSS-CHECK, not a spec-only one — every REAL domain's
199
+ # A live cross-check, not a spec-only one — every real domain's
153
200
  # own boot (not just `bundle exec rspec`) now genuinely fails
154
201
  # loudly if a construct's own hand-written resolution method
155
202
  # ever disagrees with what the self-hosted grammar table claims
156
203
  # for it. Each of the three `reference_named_*` methods below
157
- # calls this FIRST, naming the primitive it is ABOUT to use —
204
+ # calls this first, naming the primitive it is about to use —
158
205
  # if `syntax.bluebook`'s own `resolves_via` for this exact
159
206
  # (word, context) pair ever names something else, this is a
160
207
  # real drift between the language's own self-description and
161
- # its own implementation, caught at the next boot of ANYTHING,
208
+ # its own implementation, caught at the next boot of anything,
162
209
  # not just the next `rspec` run.
210
+ # @param word [String] the DSL word, such as `"given"` or `"invariant"`
211
+ # @param context [String] the grammar context, such as `"Aggregate"`
212
+ # @param expected_primitive [String] the resolution primitive's own name the caller is
213
+ # about to use, such as `"hash_chain"`, `"owner_keyed"`, or `"sibling_scan"`
214
+ # @return [void]
215
+ # @raise [RuntimeError] if `syntax.bluebook`'s own `resolves_via` for this (word, context)
216
+ # pair names something other than `expected_primitive`
163
217
  def verify_resolves_via!(word, context, expected_primitive)
164
218
  actual = lookup(word, context)[:resolves_via]
165
219
  return if actual == expected_primitive
@@ -13,6 +13,9 @@ module Hecks
13
13
 
14
14
  include WordGate
15
15
 
16
+ # @param name [String, Symbol] the aggregate's name in the destination era
17
+ # @param was [String, Symbol, nil] the aggregate's earlier name, when renamed
18
+ # @raise [Bluebook::DSL::Malformed] if `name` is empty
16
19
  def initialize(name, was: nil)
17
20
  raise Malformed, "an aggregate translation needs a name" if name.to_s.empty?
18
21
 
@@ -28,14 +31,23 @@ module Hecks
28
31
  @backfills = []
29
32
  end
30
33
 
31
- # RENAMED FROM `rename`/`move`/`convert`/`retype`/`compute`/
32
- # `rekey`/`backfill` (all seven below) — item #13's full
33
- # metaprogrammed dispatch (slice 4c). Not bootstrap-reachable
34
- # (translation.bluebook describes ITS OWN structure with
35
- # aggregate/entity/attribute, never with these — they're
36
- # words for real, user-authored `.translation` files only,
37
- # loaded after the grammar table exists), so none need a
38
- # BOOTSTRAP_CALLS_FALLBACK entry.
34
+ # Declares a field rename with no other change: same path, new name.
35
+ #
36
+ # Answers the `rename` word (and, via the same table rows, its
37
+ # siblings `move`/`convert`/`retype`/`compute`/`rekey`/`backfill`
38
+ # below) through the table's `calls:` column — item #13's full
39
+ # metaprogrammed dispatch (slice 4c). Each is carried in
40
+ # `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` like every other
41
+ # `calls:`-routed word, but none of their rows are ever consulted
42
+ # during real bootstrap: `translation.bluebook` describes its own
43
+ # structure with aggregate/entity/attribute, never with these —
44
+ # they're words for real, user-authored `.translation` files
45
+ # only, loaded after the grammar table already exists.
46
+ #
47
+ # @param old_name [Symbol, String] the field's name in the held era
48
+ # @param to [Symbol, String] the field's name in the destination era
49
+ # @return [void]
50
+ # @raise [Bluebook::DSL::Malformed] if `old_name` or `to` is empty
39
51
  def rename_impl(old_name, to:)
40
52
  raise Malformed, "a rename needs a source name" if old_name.to_s.empty?
41
53
  raise Malformed, "a rename needs a destination name (to:)" if to.to_s.empty?
@@ -43,6 +55,13 @@ module Hecks
43
55
  @renames[old_name.to_sym] = to.to_sym
44
56
  end
45
57
 
58
+ # Declares a field moved to a different path, name unchanged.
59
+ #
60
+ # @param old_path [String, Symbol] the field's path in the held era; dotted reaches a
61
+ # value-object member
62
+ # @param to [String, Symbol] the field's path in the destination era
63
+ # @return [Array<Bluebook::TranslationMove>] every move declared so far, this one last
64
+ # @raise [Bluebook::DSL::Malformed] if `old_path` or `to` is empty
46
65
  def move_impl(old_path, to:)
47
66
  raise Malformed, "a move needs a destination path (to:)" if to.to_s.empty?
48
67
  raise Malformed, "a move needs a source path" if old_path.to_s.empty?
@@ -50,10 +69,21 @@ module Hecks
50
69
  @moves << TranslationMove.new(old_path.to_s, to.to_s)
51
70
  end
52
71
 
72
+ # Declares an exhaustive value-to-value mapping for a field with nothing structural in
73
+ # common with its replacement.
74
+ #
53
75
  # A value with nothing structural in common with its replacement
54
76
  # — declared as an exhaustive table, not computed, so every value
55
77
  # that can appear in old data has a named destination. Paths
56
78
  # follow `move`'s convention: dotted reaches a value-object member.
79
+ #
80
+ # @param old_path [String, Symbol] the field's path in the held era
81
+ # @param to [String, Symbol] the field's path in the destination era
82
+ # @param values [Hash] every old value mapped to its destination value
83
+ # @return [Array<Bluebook::TranslationConvert>] every convert declared so far, this one
84
+ # last
85
+ # @raise [Bluebook::DSL::Malformed] if `old_path` or `to` is empty, or `values` is nil
86
+ # or empty
57
87
  def convert_impl(old_path, to:, values:)
58
88
  raise Malformed, "a convert needs a destination path (to:)" if to.to_s.empty?
59
89
  raise Malformed, "a convert needs a source path" if old_path.to_s.empty?
@@ -70,10 +100,18 @@ module Hecks
70
100
  # coerce-and-append with nothing else, now executed by
71
101
  # `GenericDispatch`.
72
102
 
103
+ # Declares that a type's name changed while its member structure stayed the same.
104
+ #
73
105
  # A value object's or entity's own type name changed, member
74
106
  # structure unchanged. The stored data never carries the type
75
107
  # name, so nothing moves — this declares that the pair of names
76
108
  # means the same shape, which is what lets the era diff accept it.
109
+ #
110
+ # @param old_type [String, Symbol] the type's name in the held era
111
+ # @param to [String, Symbol] the type's name in the destination era
112
+ # @return [Array<Bluebook::TranslationRetype>] every retype declared so far, this one
113
+ # last
114
+ # @raise [Bluebook::DSL::Malformed] if `old_type` or `to` is empty
77
115
  def retype_impl(old_type, to:)
78
116
  raise Malformed, "a retype needs a source type name" if old_type.to_s.empty?
79
117
  raise Malformed, "a retype needs a destination type name (to:)" if to.to_s.empty?
@@ -81,10 +119,19 @@ module Hecks
81
119
  @retypes << TranslationRetype.new(old_type.to_s, to.to_s)
82
120
  end
83
121
 
122
+ # Declares a field computed by a hand-written Postgres SQL expression.
123
+ #
84
124
  # A computed transform whose only implementation is the SQL
85
125
  # expression itself — Postgres-only by construction. The scaffold
86
126
  # never proposes one; a human writes it, and the audit's
87
127
  # human-sampled review is its only verification.
128
+ #
129
+ # @param old_path [String, Symbol] the source field's path in the held era
130
+ # @param to [String, Symbol] the field's path in the destination era
131
+ # @param sql [String] the Postgres SQL expression computing the destination value
132
+ # @return [Array<Bluebook::TranslationCompute>] every compute declared so far, this one
133
+ # last
134
+ # @raise [Bluebook::DSL::Malformed] if `old_path`, `to`, or `sql` is empty
88
135
  def compute_impl(old_path, to:, sql:)
89
136
  raise Malformed, "a compute needs a destination path (to:)" if to.to_s.empty?
90
137
  raise Malformed, "a compute needs a source path" if old_path.to_s.empty?
@@ -93,7 +140,10 @@ module Hecks
93
140
  @computes << TranslationCompute.new(old_path.to_s, to.to_s, sql.to_s)
94
141
  end
95
142
 
96
- # THE AGGREGATE'S OWN IDENTITY, changing what it's computed from —
143
+ # Declares a hand-written Postgres SQL expression that recomputes the aggregate's own
144
+ # identity.
145
+ #
146
+ # The aggregate's own identity, changing what it's computed from —
97
147
  # not a field crossing a boundary (`move`), not a value's own
98
148
  # transform (`compute`): the record's key. No path arguments,
99
149
  # unlike every rule above — nothing is consumed from or moved into
@@ -101,14 +151,21 @@ module Hecks
101
151
  # SQL-only, Postgres-only, human-reviewed-sample-is-the-only-
102
152
  # verification shape `compute` already has, and for the same
103
153
  # reason: there is nothing in-process to check this against.
154
+ #
155
+ # @param sql [String] the Postgres SQL expression computing the destination identity
156
+ # @return [Array<Bluebook::TranslationRekey>] every rekey declared so far, this one last
157
+ # @raise [Bluebook::DSL::Malformed] if `sql` is empty
104
158
  def rekey_impl(sql:)
105
159
  raise Malformed, "a rekey needs its sql: expression" if sql.to_s.empty?
106
160
 
107
161
  @rekeys << TranslationRekey.new(sql.to_s)
108
162
  end
109
163
 
110
- # A NEWLY ADDED, required attribute — the addition-side sibling of
111
- # `drop`. Nothing to rename, move, or convert FROM, since old data
164
+ # Declares a newly added, required attribute and the default existing records read
165
+ # until a real value is written.
166
+ #
167
+ # A newly added, required attribute — the addition-side sibling of
168
+ # `drop`. Nothing to rename, move, or convert from, since old data
112
169
  # never held this field at all; `default` is what an existing
113
170
  # record reads until the next command against it writes a real
114
171
  # value. Adapter-agnostic, unlike `compute` — applied the same
@@ -118,6 +175,12 @@ module Hecks
118
175
  # `EraGuard.refuse_unsafe_addition!` asks for when a non-optional
119
176
  # attribute with no default: could leave an existing record with
120
177
  # the field genuinely absent.
178
+ #
179
+ # @param name [String, Symbol] the new attribute's name
180
+ # @param default [Object] the value an existing record reads until it is written for real
181
+ # @return [Array<Bluebook::TranslationBackfill>] every backfill declared so far, this
182
+ # one last
183
+ # @raise [Bluebook::DSL::Malformed] if `name` is empty or `default` is nil
121
184
  def backfill_impl(name, default:)
122
185
  raise Malformed, "a backfill needs a name" if name.to_s.empty?
123
186
  raise Malformed, "a backfill needs a default: value" if default.nil?
@@ -125,33 +188,46 @@ module Hecks
125
188
  @backfills << TranslationBackfill.new(name.to_sym, default)
126
189
  end
127
190
 
191
+ # Always refuses to boot: marks a field the scaffold could not decide a rule for.
192
+ #
128
193
  # The scaffold writes this where it cannot decide; a file carrying
129
194
  # one can only boot into this refusal — never a guess.
130
195
  #
131
- # RENAMED FROM `unresolved` — item #13's full metaprogrammed
196
+ # Answers the `unresolved` word through the table's `calls:`
197
+ # column — item #13's full metaprogrammed
132
198
  # dispatch (slice 4). Builds its own message with real branching
133
199
  # (empty vs. named candidates, a special :identity case), not a
134
- # fixed string a boolean `refuses:` flag could express — reached
135
- # through `calls:` instead, like `attribute`/`role`. NOT
136
- # bootstrap-reachable: translation.bluebook (loaded during
137
- # bootstrap, to describe the translation DSL itself) never
138
- # writes `unresolved` — that word is only ever used by real,
139
- # user-authored `.translation` files, loaded well after the
140
- # grammar table exists — so no BOOTSTRAP_CALLS_FALLBACK entry is
141
- # needed here (checked directly, not assumed).
200
+ # fixed string a boolean `refuses:` flag could express. Carried in
201
+ # `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` like every other
202
+ # `calls:`-routed word, but that row is never consulted during
203
+ # real bootstrap (checked directly, not assumed):
204
+ # translation.bluebook (loaded during bootstrap, to describe the
205
+ # translation DSL itself) never writes `unresolved` — that word is
206
+ # only ever used by real, user-authored `.translation` files,
207
+ # loaded well after the grammar table already exists.
208
+ #
209
+ # @param name [String, Symbol] the unresolved field's name, or `:identity` for an
210
+ # unresolved identity change
211
+ # @param candidates [Array<String, Symbol>] paths the scaffold considered but could not
212
+ # choose between; empty when it found none
213
+ # @return [void]
214
+ # @raise [Bluebook::DSL::Malformed] always
142
215
  def unresolved_impl(name, candidates: [])
143
216
  raise Malformed, unresolved_message(name, candidates)
144
217
  end
145
218
 
146
- # `method_missing`/`respond_to_missing?` used to be hand-written
147
- # here, giving a hand-typed "must be rename, move, convert, ..."
148
- # list on every genuinely undefined call — WordGate (`include`d
149
- # above) now answers the same question off the self-hosted
150
- # grammar table instead, the exact "hardcoded legal-word list"
151
- # this whole arc's item #13 exists to close. A word admitted
152
- # elsewhere in the grammar but not in this context still gets a
153
- # richer, table-driven refusal than the old generic one did.
219
+ # `method_missing`/`respond_to_missing?` answer off the self-hosted
220
+ # grammar table (via the `include`d `WordGate`, above), giving a
221
+ # richer, table-driven "must be rename, move, convert, ..." refusal
222
+ # on a genuinely undefined call than a hand-typed list could —
223
+ # the exact "hardcoded legal-word list" this whole arc's item #13
224
+ # exists to close. A word admitted
225
+ # elsewhere in the grammar but not in this context still gets that
226
+ # richer refusal.
154
227
 
228
+ # Assembles the declared rules into a `TranslationAggregate`.
229
+ #
230
+ # @return [Bluebook::TranslationAggregate] the built per-aggregate translation
155
231
  def build
156
232
  TranslationAggregate.new(
157
233
  name: @name, was: @was, renames: @renames, moves: @moves, converts: @converts,
@@ -178,7 +254,7 @@ module Hecks
178
254
  path.to_s.include?(".") ? path.to_s.inspect : ":#{path}"
179
255
  end
180
256
 
181
- # THE SCAFFOLD'S OWN HINT for the one drift it can detect but never
257
+ # The scaffold's own hint for the one drift it can detect but never
182
258
  # resolve on its own — an aggregate's `identified_by` changed. Not
183
259
  # a field to rename/move/drop, so none of the ordinary hints fit;
184
260
  # `coverage_check.rb#check_identity_unchanged!` is the real gate,
@@ -199,6 +275,10 @@ module Hecks
199
275
 
200
276
  include WordGate
201
277
 
278
+ # @param domain [String, Symbol] the domain this translation carries forward
279
+ # @param from [String, Symbol] the origin era
280
+ # @param to [String, Symbol] the destination era
281
+ # @raise [Bluebook::DSL::Malformed] if `domain`, `from`, or `to` is empty
202
282
  def initialize(domain, from:, to:)
203
283
  raise Malformed, "a translation names no domain" if domain.to_s.empty?
204
284
  raise Malformed, "#{domain}'s translation says nothing about its origin era (from:)" if from.to_s.empty?
@@ -211,38 +291,64 @@ module Hecks
211
291
  @retired = []
212
292
  end
213
293
 
214
- # RENAMED FROM `aggregate` — item #13's full metaprogrammed
294
+ # Declares one aggregate's own translation rules.
295
+ #
296
+ # Answers the `aggregate` word through the table's `calls:`
297
+ # column — item #13's full metaprogrammed
215
298
  # dispatch (slice 4c). Not bootstrap-reachable — this "Translation"
216
299
  # -context `aggregate` (opens a TranslationAggregateBuilder) is a
217
300
  # different (context, word) pair than "Bluebook"-context
218
301
  # `aggregate` (the one translation.bluebook itself is described
219
- # with), so it's never used to describe the language's own
302
+ # with), so it never describes the language's own
220
303
  # translation chapter.
304
+ #
305
+ # @param name [String, Symbol] the aggregate's name in the destination era
306
+ # @param was [String, Symbol, nil] the aggregate's earlier name, when renamed
307
+ # @yield the aggregate's own translation body, evaluated against a
308
+ # `TranslationAggregateBuilder`
309
+ # @return [Array<Bluebook::TranslationAggregate>] every aggregate translation declared
310
+ # so far, this one last
311
+ # @raise [Bluebook::DSL::Malformed] if `name` is empty, or any rule in the body fails
312
+ # its own checks
221
313
  def aggregate_impl(name, was: nil, &block)
222
314
  builder = TranslationAggregateBuilder.new(name, was: was)
223
315
  builder.instance_eval(&block) if block
224
316
  @aggregates << builder.build
225
317
  end
226
318
 
227
- # `retired` — item #13's full metaprogrammed dispatch, slice 2
228
- # (whole-project table-unification survey): an aggregate that is
229
- # gone outright — not renamed. The deliberate alternative to a
230
- # bogus `was:` claim on an unrelated aggregate. Same shape
231
- # `TranslationAggregateBuilder#drop` is, now executed by
232
- # `GenericDispatch`.
233
-
234
- # `method_missing`/`respond_to_missing?` — same removal as
235
- # TranslationAggregateBuilder's own, one level up: WordGate
236
- # (`include`d above) answers off the self-hosted grammar table
237
- # now instead of a hand-typed "it declares aggregate blocks and
238
- # retired aggregates" message.
239
-
319
+ # `retired` (an aggregate that is gone outright, not renamed — the
320
+ # deliberate alternative to a bogus `was:` claim on an unrelated
321
+ # aggregate, the same shape `TranslationAggregateBuilder#drop` is)
322
+ # is executed straight off the grammar table by `GenericDispatch` —
323
+ # item #13's full metaprogrammed dispatch, slice 2 (whole-project
324
+ # table-unification survey) — so no hand-written method answers it here.
325
+
326
+ # `method_missing`/`respond_to_missing?` answer off the self-hosted
327
+ # grammar table (via the `include`d `WordGate`, above, the same
328
+ # mechanism `TranslationAggregateBuilder`'s own comment describes
329
+ # one level up) instead of a hand-typed "it declares aggregate
330
+ # blocks and retired aggregates" message.
331
+
332
+ # Assembles the declared era pair, aggregates and retirements, judged by the translation
333
+ # language.
334
+ #
335
+ # @return [Bluebook::Translation] the translation, returned once the language accepts it
336
+ # @raise [Bluebook::DSL::Malformed] if the translation language refuses the declaration
240
337
  def build
241
338
  MetaValidator.call_translation(
242
339
  Translation.new(domain: @domain, from: @from, to: @to, aggregates: @aggregates, retired: @retired)
243
340
  )
244
341
  end
245
342
 
343
+ # Evaluates a `.translation` file's top-level block against a fresh builder.
344
+ #
345
+ # @param domain [String, Symbol] the domain this translation carries forward
346
+ # @param from [String, Symbol] the origin era
347
+ # @param to [String, Symbol] the destination era
348
+ # @yield the translation body, evaluated with the builder as `self`; may be omitted
349
+ # @return [Bluebook::Translation] the judged translation
350
+ # @raise [Bluebook::DSL::Malformed] if the body fails any check, or the translation
351
+ # language refuses the declaration
246
352
  def self.build(domain, from:, to:, &block)
247
353
  builder = new(domain, from: from, to: to)
248
354
  builder.instance_eval(&block) if block