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
@@ -2,23 +2,22 @@ require "json"
2
2
 
3
3
  module Hecks
4
4
  module Translation
5
- # The closed, PURE half of Postgres's own SQL compiler
6
- # (adapters/driven/postgres_era/lineage/head_compiler.rb) — the part
7
- # that turns one TranslationAggregate's declared rules into a
8
- # jsonb-transforming SQL expression. No database connection, no
9
- # watermark, no era chain, no catalog lookup: those stay exactly
10
- # where they were, in head_compiler.rb's own per-mint assembly,
11
- # which calls into this module instead of defining these methods
12
- # itself.
5
+ # The closed, pure half of Postgres's own SQL compiler
6
+ # (ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb)
7
+ # — the part that turns one TranslationAggregate's declared rules into
8
+ # a jsonb-transforming SQL expression. No database connection, no
9
+ # watermark, no era chain, no catalog lookup: those live in
10
+ # head_compiler.rb's own per-mint assembly, which calls into this
11
+ # module instead of defining these methods itself.
13
12
  #
14
- # Extracted here — not left as private methods on
15
- # Adapters::PostgresEra::Lineage — so a SECOND, adapter-agnostic
13
+ # A module of its own — not private methods on
14
+ # Adapters::PostgresEra::Lineage — so a second, adapter-agnostic
16
15
  # caller (Exporter.translation_aggregate's build-time SQL export,
17
16
  # feeding rust/host's own future boot-time mint) can call the exact
18
- # SAME code Ruby's own mint path runs, not a hand-ported duplicate
19
- # that could silently drift the way `Exporter.translation_hash`
20
- # drifted from `hecks_eras`/`hecks_approvals`' real schema before
21
- # this file existed (rekeys/backfills were missing for years).
17
+ # same code Ruby's own mint path runs, not a hand-ported duplicate
18
+ # that could silently drift the way a hand-kept
19
+ # `Exporter.translation_hash` can drift from `hecks_eras`/
20
+ # `hecks_approvals`' real schema (leaving out rekeys and backfills).
22
21
  module RuleCompiler
23
22
  module_function
24
23
 
@@ -27,6 +26,13 @@ module Hecks
27
26
  # in the reference transform's phase order (renames, moves,
28
27
  # converts, drops), computes last. `retype` compiles to nothing:
29
28
  # stored state never carries a type name.
29
+ # Compiles a declared edge's rename/move/convert/drop/compute rules into one nested SQL
30
+ # expression over the `state` jsonb column.
31
+ #
32
+ # @param declared [Bluebook::TranslationAggregate] this edge's declared rules for one
33
+ # aggregate
34
+ # @return [String] a SQL expression, `"state"` unchanged when `declared` declares none
35
+ # of the five rule kinds
30
36
  def compile_rules(declared)
31
37
  expression = "state"
32
38
  declared.renames.each do |old_name, new_name|
@@ -51,31 +57,47 @@ module Hecks
51
57
  expression
52
58
  end
53
59
 
54
- # Whether THIS edge's declared rules for this aggregate include a
60
+ # Whether this edge's declared rules for this aggregate include a
55
61
  # rekey — checked directly off the raw IR object, the same way
56
62
  # every other rule kind is already read in `compile_rules`
57
63
  # (`declared.computes`, `declared.moves`, ...), not through the
58
64
  # `Ports::Persistence::Lineage` wrapper the app-level consumers
59
65
  # (coverage_check.rb, minter.rb, layer_two.rb) go through — this
60
66
  # module builds SQL straight off the IR either way.
67
+ # Reports whether an edge's declared rules for an aggregate include a rekey.
68
+ #
69
+ # @param declared [Bluebook::TranslationAggregate, nil] this edge's declared rules for
70
+ # one aggregate; nil for an aggregate the edge declares nothing about
71
+ # @return [Boolean] true when `declared` is present and its `rekeys` is non-empty
61
72
  def rekeyed?(declared) = declared && !declared.rekeys.empty?
62
73
 
63
- # THE ONLY TWO PLACES `aggregate_id` NEEDS TO CHANGE — guarded so
74
+ # The only two places `aggregate_id` needs to change — guarded so
64
75
  # the generated SQL for the overwhelming common case (no rekey
65
76
  # declared) stays the bare `aggregate_id` passthrough it always
66
- # was — this CASE only appears in an edge that actually declares
77
+ # was — this case only appears in an edge that actually declares
67
78
  # one.
79
+ #
80
+ # @param guard [String] a SQL boolean expression gating when the rekey applies, such as
81
+ # `"operation = 'save'"`
82
+ # @param declared [Bluebook::TranslationAggregate] this edge's declared rules; must
83
+ # declare a rekey (`rekeyed?(declared)` true)
84
+ # @return [String] a `"CASE WHEN ... END AS aggregate_id"` SQL expression
68
85
  def id_case(guard, declared)
69
86
  "CASE WHEN #{guard} THEN #{compile_id_expression(declared)} ELSE aggregate_id END AS aggregate_id"
70
87
  end
71
88
 
72
- # THE REKEY'S OWN SQL — reading `state` directly, not the
89
+ # The rekey's own SQL — reading `state` directly, not the
73
90
  # progressively-built `expression` chain `compile_compute` reads
74
91
  # from. A rekey doesn't consume or move any field the way a move
75
92
  # or compute does, so there is no same-edge rename/move ordering
76
93
  # it needs to see first — it reads the record's stored fields
77
94
  # exactly as they already are, the same `__s` convention
78
95
  # `compile_compute` exposes.
96
+ #
97
+ # @param declared [Bluebook::TranslationAggregate] this edge's declared rules; its
98
+ # first `rekeys` entry supplies the SQL
99
+ # @return [String] a SQL expression evaluating the rekey's own SQL against the record's
100
+ # current `state`
79
101
  def compile_id_expression(declared)
80
102
  rekey = declared.rekeys.first
81
103
  "(SELECT (#{rekey.sql}) FROM (SELECT (state) AS __s) __outer)"
@@ -85,6 +107,12 @@ module Hecks
85
107
  # — evaluated exclusively inside the compiled head, never
86
108
  # in-process. The old field is exposed under its own name (as
87
109
  # text, exactly as the author's expression expects to cast it).
110
+ #
111
+ # @param expression [String] the SQL expression built so far by `compile_rules`, read as
112
+ # `__s` inside the compute's own SQL
113
+ # @param compute [Bluebook::TranslationCompute] the declared compute rule
114
+ # @return [String] `expression` wrapped so the compute's field lands at its declared
115
+ # destination when the source field is present, unchanged otherwise
88
116
  def compile_compute(expression, compute)
89
117
  from = compute.from.to_s
90
118
  to = compute.to.to_s
@@ -96,7 +124,7 @@ module Hecks
96
124
  "LATERAL (SELECT (__s ->> #{text_literal(from)}) AS #{quote(from)}) __fields)"
97
125
  end
98
126
 
99
- # `PG::Connection.quote_ident` needs the `pg` gem LOADED, not
127
+ # `PG::Connection.quote_ident` needs the `pg` gem loaded, not
100
128
  # connected — required here, lazily, the same "a domain that
101
129
  # never wires PostgresEra should never need the gem" reasoning
102
130
  # `PostgresEra.connect_for`'s own `require "pg"` already holds
@@ -104,13 +132,24 @@ module Hecks
104
132
  # non-Postgres-bound domain (there are none today, but nothing
105
133
  # here should assume there never will be) doesn't gain a hard
106
134
  # dependency on `pg` just by loading this file.
135
+ #
136
+ # @param name [String, Symbol] the identifier to quote
137
+ # @return [String] `name` as a double-quoted Postgres identifier
107
138
  def quote(name)
108
139
  require "pg"
109
140
  PG::Connection.quote_ident(name.to_s)
110
141
  end
111
142
 
143
+ # Renders a Ruby value as a single-quoted SQL text literal, escaping embedded quotes.
144
+ #
145
+ # @param text [String, Symbol, Object] the value to render; converted with `to_s`
146
+ # @return [String] a single-quoted SQL literal
112
147
  def text_literal(text) = "'#{text.to_s.gsub("'", "''")}'"
113
148
 
149
+ # Renders a dotted path as a SQL `text[]` array literal, one element per segment.
150
+ #
151
+ # @param path [String, Symbol] a bare or dotted path, such as `"price.cents"`
152
+ # @return [String] a SQL `ARRAY[...]::text[]` expression
114
153
  def path_literal(path)
115
154
  segments = path.to_s.split(".").map { |segment| text_literal(segment) }
116
155
  "ARRAY[#{segments.join(', ')}]::text[]"
@@ -8,6 +8,13 @@ module Hecks
8
8
  # everything ambiguous left unresolved for a human.
9
9
  module Differ
10
10
  # Diff two bluebook IRs into the edge's declarations.
11
+ #
12
+ # @param held_bluebook [Bluebook::Chapter] the era being translated from
13
+ # @param current_bluebook [Bluebook::Chapter] the era being translated to
14
+ # @return [Hash{Symbol => Object}] `:aggregates` (Array<Scaffold::ScaffoldedAggregate>,
15
+ # one per matched aggregate carrying a rename or a rule), `:retired` (Array<String>,
16
+ # vanished aggregate names with no successor), `:unclaimed` (Array<String>, vanished
17
+ # aggregate names left ambiguous)
11
18
  def diff(held_bluebook, current_bluebook)
12
19
  held_shapes = projections(held_bluebook)
13
20
  current_shapes = projections(current_bluebook)
@@ -27,17 +34,31 @@ module Hecks
27
34
  { aggregates: aggregates, retired: matched[:retired], unclaimed: matched[:unclaimed] }
28
35
  end
29
36
 
37
+ # Projects a bluebook's storage shape and indexes its aggregates by name.
38
+ #
39
+ # @param bluebook [Bluebook::Chapter] the bluebook to project
40
+ # @return [Hash{String => Hash}] the bluebook's storage-shape aggregates (see
41
+ # `Runtime::StorageShape.project`), keyed by aggregate name
30
42
  def projections(bluebook)
31
43
  Runtime::StorageShape.project(bluebook)["aggregates"].to_h { |shape| [shape["name"], shape] }
32
44
  end
33
45
 
34
46
  # A vanished aggregate whose full shape reappears under exactly one
35
- # new name was renamed. `retired` is only confident when NOTHING
47
+ # new name counts as a rename. `retired` is only confident when nothing
36
48
  # remains it could plausibly have become — a vanished aggregate
37
49
  # beside an unmatched new one might be a rename-plus-reshape, and
38
50
  # writing `retired` there would be a guess that strands data.
39
51
  # Anything ambiguous stays unclaimed, and the coverage gate names
40
52
  # it until a human decides.
53
+ #
54
+ # @param held_shapes [Hash{String => Hash}] the held era's aggregate shapes, keyed by
55
+ # name (see `projections`)
56
+ # @param current_shapes [Hash{String => Hash}] the current era's aggregate shapes,
57
+ # keyed by name
58
+ # @return [Hash{Symbol => Object}] `:pairs` (Hash{String => String, nil}, each current
59
+ # name to its matched held name, or nil when unmatched), `:retired` (Array<String>,
60
+ # vanished names with no successor), `:unclaimed` (Array<String>, vanished names
61
+ # left ambiguous)
41
62
  def match_aggregates(held_shapes, current_shapes)
42
63
  pairs = {}
43
64
  current_shapes.each_key { |name| pairs[name] = held_shapes.key?(name) ? name : nil }
@@ -61,12 +82,19 @@ module Hecks
61
82
  end
62
83
 
63
84
  # Every changed path in one aggregate, resolved into rules by
64
- # signature matching over the FULL path set — top-level names and
85
+ # signature matching over the full path set — top-level names and
65
86
  # dotted members alike. Unique signature pair: a rename (both
66
87
  # top-level) or a move (any dotted end). Same path, same members,
67
88
  # new type name: a retype. Anything else: unresolved, carrying its
68
89
  # type-compatible candidates (an empty list is the arrow toward
69
90
  # compute, or drop).
91
+ #
92
+ # @param held_shape [Hash{String => Object}] the held era's aggregate shape (see
93
+ # `projections`)
94
+ # @param current_shape [Hash{String => Object}] the current era's aggregate shape
95
+ # @return [Array<Hash{Symbol => Object}>] one rule Hash per changed path — `:kind`
96
+ # plus `:from`/`:to` for `:rename`/`:move`/`:retype`, or `:from`/`:candidates` for
97
+ # `:unresolved` (see `Renderer#render_rule`); `[]` when nothing changed
70
98
  def attribute_rules(held_shape, current_shape)
71
99
  rules = []
72
100
  rules << identity_hint(held_shape, current_shape)
@@ -81,7 +109,7 @@ module Hecks
81
109
  appeared = appeared_paths(held_attrs, current_attrs, retyped)
82
110
 
83
111
  # Mutates `rules` in place (not build-and-concat, like the two
84
- # passes above) because it must READ its own earlier writes:
112
+ # passes above) because it must read its own earlier writes:
85
113
  # `taken`, below, is recomputed from `rules` at the top of every
86
114
  # iteration, so a target this same loop already claimed for an
87
115
  # earlier vanished path is excluded from a later one. Passing a
@@ -92,7 +120,14 @@ module Hecks
92
120
  end
93
121
 
94
122
  # retype pass: same attribute name, same member structure, the
95
- # TYPE's own name changed
123
+ # type's own name changed
124
+ #
125
+ # @param held_attrs [Hash{String => Hash}] the held era's attributes, keyed by name
126
+ # @param current_attrs [Hash{String => Hash}] the current era's attributes, keyed by
127
+ # name
128
+ # @return [Array<Hash{Symbol => String}>] one `{kind: :retype, from:, to:}` rule per
129
+ # attribute whose members are unchanged but whose type name changed; `[]` when none
130
+ # match
96
131
  def retype_rules(held_attrs, current_attrs)
97
132
  (held_attrs.keys & current_attrs.keys).filter_map do |name|
98
133
  held = held_attrs[name]
@@ -105,6 +140,16 @@ module Hecks
105
140
  end
106
141
  end
107
142
 
143
+ # Resolves each vanished path into a rename, move or unresolved rule, appended to
144
+ # `rules` in place.
145
+ #
146
+ # @param rules [Array<Hash{Symbol => Object}>] the rule list to append to; also read
147
+ # for `:rename`/`:move` targets an earlier iteration already claimed
148
+ # @param vanished [Hash{String => Object}] vanished paths to type signature (see
149
+ # `vanished_paths`)
150
+ # @param appeared [Hash{String => Object}] appeared paths to type signature (see
151
+ # `appeared_paths`)
152
+ # @return [void]
108
153
  def resolve_vanished_rules!(rules, vanished, appeared)
109
154
  vanished.each do |path, signature|
110
155
  matches = appeared.select { |_, candidate| candidate == signature }.keys
@@ -125,6 +170,14 @@ module Hecks
125
170
  # and members that vanished (or changed type) inside a kept
126
171
  # attribute — mirroring exactly what EraGuard will demand coverage
127
172
  # for.
173
+ #
174
+ # @param held_attrs [Hash{String => Hash}] the held era's attributes, keyed by name
175
+ # @param current_attrs [Hash{String => Hash}] the current era's attributes, keyed by
176
+ # name
177
+ # @param retyped [Array<String>] type names already claimed by a `:retype` rule,
178
+ # excluded here
179
+ # @return [Hash{String => Object}] each vanished or changed path, dotted for a member,
180
+ # valued by that path's type signature
128
181
  def vanished_paths(held_attrs, current_attrs, retyped)
129
182
  paths = {}
130
183
  held_attrs.each do |name, held|
@@ -146,6 +199,17 @@ module Hecks
146
199
  paths
147
200
  end
148
201
 
202
+ # Current paths that need explaining: whole attributes that appeared, and members
203
+ # that appeared (or changed type) inside a kept attribute — the mirror of
204
+ # `vanished_paths`.
205
+ #
206
+ # @param held_attrs [Hash{String => Hash}] the held era's attributes, keyed by name
207
+ # @param current_attrs [Hash{String => Hash}] the current era's attributes, keyed by
208
+ # name
209
+ # @param retyped [Array<String>] type names already claimed by a `:retype` rule,
210
+ # excluded here
211
+ # @return [Hash{String => Object}] each appeared or changed path, dotted for a
212
+ # member, valued by that path's type signature
149
213
  def appeared_paths(held_attrs, current_attrs, retyped)
150
214
  paths = {}
151
215
  current_attrs.each do |name, current|
@@ -166,31 +230,58 @@ module Hecks
166
230
  paths
167
231
  end
168
232
 
169
- # THE ONE THING `attribute_rules` COULD NOT SEE BEFORE — the shape
233
+ # The one thing `attribute_rules` could not see before — the shape
170
234
  # projection already carries `"identity"` (`StorageShape
171
235
  # .project_aggregate`), it was simply never read here. An
172
236
  # unresolved placeholder, not a guess: `coverage_check.rb`'s own
173
237
  # `check_identity_unchanged!` is the real gate this only hints
174
238
  # toward, the same "tool proactively guides you" pattern the
175
239
  # generic unresolved message already gives unfed fields.
240
+ #
241
+ # @param held_shape [Hash{String => Object}] the held era's aggregate shape
242
+ # @param current_shape [Hash{String => Object}] the current era's aggregate shape
243
+ # @return [Hash{Symbol => Object}, nil] `{kind: :unresolved, from: :identity,
244
+ # candidates: []}` when the identity paths differ; nil when they match
176
245
  def identity_hint(held_shape, current_shape)
177
246
  return if held_shape["identity"] == current_shape["identity"]
178
247
 
179
248
  { kind: :unresolved, from: :identity, candidates: [] }
180
249
  end
181
250
 
251
+ # Finds appeared paths type-compatible with a vanished path's own signature.
252
+ #
253
+ # @param appeared [Hash{String => Object}] appeared paths to type signature (see
254
+ # `appeared_paths`)
255
+ # @param signature [Object] the vanished path's own type signature to match against
256
+ # @return [Array<String>] appeared paths whose scalar signature (see `scalar_of`)
257
+ # matches `signature`'s
182
258
  def compatible_candidates(appeared, signature)
183
259
  appeared.select { |_, candidate| scalar_of(candidate) == scalar_of(signature) }.keys
184
260
  end
185
261
 
262
+ # Reduces a type signature to the shape `compatible_candidates` compares by.
263
+ #
264
+ # @param signature [Object, Hash] a path's type signature
265
+ # @return [Object] `signature["members"]` for a container Hash, else `signature`
266
+ # unchanged
186
267
  def scalar_of(signature) = signature.is_a?(Hash) ? signature["members"] : signature
187
268
 
269
+ # Reads a container attribute's own members, keyed by name.
270
+ #
271
+ # @param attribute [Hash{String => Object}] one path's type signature
272
+ # @return [Hash{String => Object}] each member's name to its type; `{}` when
273
+ # `attribute` is not a container
188
274
  def members_of(attribute)
189
275
  return {} unless container?(attribute)
190
276
 
191
277
  attribute["type"]["members"].to_h { |member| [member["name"], member["type"]] }
192
278
  end
193
279
 
280
+ # Reports whether a type signature is a container (value object or entity), not a
281
+ # scalar.
282
+ #
283
+ # @param attribute [Hash{String => Object}] one path's type signature
284
+ # @return [Boolean] true if `attribute["type"]` is a Hash, false for a scalar
194
285
  def container?(attribute) = attribute["type"].is_a?(Hash)
195
286
  end
196
287
  end
@@ -5,6 +5,10 @@ module Hecks
5
5
  # ambiguities as parse-refusing `unresolved` constructs, never
6
6
  # comments, so an unresolved file can only boot into a refusal.
7
7
  module Renderer
8
+ # Renders a scaffolded edge as loadable `.bluebook` translation source.
9
+ #
10
+ # @param edge [Scaffold::Edge] the edge to render
11
+ # @return [String] the edge's `.bluebook` text, ending in a newline
8
12
  def render(edge)
9
13
  lines = ["Hecks.data_translation #{edge.domain.inspect}, from: #{edge.from.inspect}, to: #{edge.to.inspect} do"]
10
14
  edge.aggregates.each do |aggregate|
@@ -19,6 +23,12 @@ module Hecks
19
23
  "#{lines.join("\n")}\n"
20
24
  end
21
25
 
26
+ # Renders one scaffolded rule as a line of `.bluebook` source.
27
+ #
28
+ # @param rule [Hash{Symbol => Object}] a rule Hash as `Differ#attribute_rules` builds
29
+ # it: `:kind` plus `:from`/`:to`, or `:from`/`:candidates` for `:unresolved`
30
+ # @return [String, nil] the rendered line; nil if `rule[:kind]` is none of `:rename`,
31
+ # `:move`, `:retype` or `:unresolved`
22
32
  def render_rule(rule)
23
33
  case rule[:kind]
24
34
  when :rename then "rename :#{rule[:from]}, to: :#{rule[:to]}"
@@ -30,6 +40,11 @@ module Hecks
30
40
  end
31
41
  end
32
42
 
43
+ # Renders a path as bluebook source: a Symbol literal when bare, a String literal
44
+ # when dotted.
45
+ #
46
+ # @param path [String, Symbol] a bare or dotted path
47
+ # @return [String] `":name"` for a bare path, `path.inspect` for a dotted one
33
48
  def render_path(path) = path.to_s.include?(".") ? path.to_s.inspect : ":#{path}"
34
49
  end
35
50
  end
@@ -6,10 +6,18 @@ module Hecks
6
6
  # Put the rendered edge on disk, regenerating in place when a file
7
7
  # for the same shape pair already exists.
8
8
  module Writer
9
+ # Renders an edge and writes it under `directory/translations`, regenerating in
10
+ # place when a file for the same shape pair already exists.
11
+ #
9
12
  # The edge file, regenerated in place when one for the same shape
10
13
  # pair already exists (matched textually — an unresolved file
11
- # cannot be LOADED to ask, that being the whole point of
14
+ # cannot be loaded to ask, that being the whole point of
12
15
  # unresolved).
16
+ #
17
+ # @param directory [String] the domain's root directory
18
+ # @param edge [Scaffold::Edge] the edge to render and write
19
+ # @return [String] the path written, either the matched existing file or a new
20
+ # `<ordinal>-<label>.bluebook`
13
21
  def write!(directory, edge)
14
22
  translations_dir = File.join(directory, "translations")
15
23
  FileUtils.mkdir_p(translations_dir)
@@ -6,13 +6,13 @@ module Hecks
6
6
  module Translation
7
7
  # The scaffold writes translations; humans resolve ambiguity. It
8
8
  # diffs the held era's storage-shape projection against the current
9
- # one (scaffold/differ.rb) and WRITES the edge file
9
+ # one (scaffold/differ.rb) and writes the edge file
10
10
  # (scaffold/renderer.rb, scaffold/writer.rb): confident rules inline
11
11
  # (unique signature pairs → rename/move, aggregate renames → `was:`,
12
12
  # type renames with identical members → retype, aggregates gone
13
13
  # without a successor → retired), ambiguities as parse-refusing
14
14
  # `unresolved` constructs — never comments, so an unresolved file can
15
- # only boot into a refusal, never a guess. It NEVER proposes a
15
+ # only boot into a refusal, never a guess. It never proposes a
16
16
  # `compute` (a computation takes domain judgment no mechanical diff
17
17
  # can infer) — an empty-candidate `unresolved` is what points the
18
18
  # author there. It never proposes a `drop` either: data loss is a
@@ -11,7 +11,7 @@ module Hecks
11
11
  module Ports
12
12
  module Persistence
13
13
  module Plugins
14
- # ADR 0033 — requiring THIS FILE is installing the plugin. Nothing in
14
+ # ADR 0033 — requiring this file is installing the plugin. Nothing in
15
15
  # Hecks core requires it; an app that binds `PostgresEra`, or wants
16
16
  # schema-translation support at all, requires it explicitly — the
17
17
  # same shape every adapter-specific spec fixture already uses to
@@ -19,6 +19,8 @@ module Hecks
19
19
  module Era
20
20
  module_function
21
21
 
22
+ # Registers this plugin's era gates on one boot's gate list.
23
+ #
22
24
  # `Runtime::Loader.run_boot_gates!` asks every loaded persistence
23
25
  # plugin to contribute here, generically — it never mentions
24
26
  # `EraCheck` or "era" by name. Two gates, both `:pre_verify`:
@@ -29,11 +31,19 @@ module Hecks
29
31
  # unchanged) — this is the rich, adapter-aware version of that
30
32
  # check; `Runtime::Loader`'s own structural backstop (plain
31
33
  # `Bluebook::Translation` data, no plugin-specific class) only
32
- # ever fires when NO persistence plugin is loaded at all.
34
+ # ever fires when no persistence plugin is loaded at all.
33
35
  #
34
36
  # `:era_check` — conditional, exactly ADR 0031's own gate,
35
37
  # unchanged: registered only when this registry has an aggregate
36
38
  # actually bound to a lineage-capable adapter.
39
+ #
40
+ # @param registry [Runtime::Registry] the registry being booted, asked whether any
41
+ # bluebook's first aggregate is bound to a lineage-capable adapter
42
+ # @param gates [Runtime::BootGates] this boot's gate list, registered onto in place
43
+ # @return [Runtime::BootGates, nil] `gates` when `:era_check` was registered; nil when
44
+ # the registry binds nothing lineage-capable, so only `:era_compute_rules` was added
45
+ # @raise [Runtime::WiringError] if an aggregate's persistence binding is missing,
46
+ # ambiguous, or carries an unsupported role
37
47
  def contribute_boot_gates(registry, gates)
38
48
  gates.register(:era_compute_rules, lambda { |reg, _dir|
39
49
  Runtime::EraCheck.check_compute_rules_for_registry!(reg)
@@ -3,12 +3,12 @@ require_relative "../../runtime/registry"
3
3
  module Hecks
4
4
  module Ports
5
5
  module Persistence
6
- # THE OTHER SHAPE AN ADAPTER CAN BE. `AppendOnly` names one shape
6
+ # The other shape an adapter can be. `AppendOnly` names one shape
7
7
  # already — something that stores bytes, locally (Postgres, SQLite,
8
8
  # Heki) or over the network (D1), doesn't matter, the point is it
9
9
  # does real local interpretation and has real entries to replay.
10
10
  # This names the second, different shape: not "storage reached
11
- # remotely," but the real INTERPRETER itself living behind a call
11
+ # remotely," but the real interpreter itself living behind a call
12
12
  # boundary — `append`/`project` don't do partial local work and
13
13
  # then fail, they raise unconditionally, because there is no local
14
14
  # write-ahead log to have; `entries` is always `[]` for the same
@@ -28,6 +28,10 @@ module Hecks
28
28
  # `Runtime::RemoteDispatcher`'s own use) instead of comparing
29
29
  # adapter names by string.
30
30
  module RemoteRuntime
31
+ # Refuses every local write; `project` is an alias and refuses the same way.
32
+ #
33
+ # @return [void] never returns
34
+ # @raise [Runtime::WiringError] always, pointing the caller at `Runtime::RemoteDispatcher`
31
35
  def append(*)
32
36
  raise Runtime::WiringError,
33
37
  "#{self.class.name} is a remote-runtime delegate — dispatch through " \
@@ -35,6 +39,9 @@ module Hecks
35
39
  end
36
40
  alias project append
37
41
 
42
+ # Reports an empty journal, since a remote-runtime delegate keeps no local log.
43
+ #
44
+ # @return [Array] always `[]`
38
45
  def entries = []
39
46
  end
40
47
  end
@@ -1,13 +1,33 @@
1
1
  require_relative "append_only"
2
+ require_relative "codec_boundary"
2
3
 
3
4
  module Hecks
4
5
  module Ports
5
6
  module Persistence
6
7
  # Turns a declared adapter binding plus its world configuration into a
7
8
  # concrete repository. Selection policy stays out of adapter creation.
9
+ #
10
+ # Every adapter it builds is guarded (`CodecBoundary.guard!`) before
11
+ # anything else touches it — `recover!` included — so no adapter a
12
+ # runtime reaches can build an `Instance` from undecoded state.
8
13
  module RepositoryFactory
9
14
  module_function
10
15
 
16
+ # Instantiates the adapter a bind names, guards it, and wraps it as a repository.
17
+ #
18
+ # @param registry [Runtime::Registry] the registry supplying the adapter class, the
19
+ # domain's world settings, its resolved eras and the root path
20
+ # @param domain [String, Symbol] name of the domain the aggregate belongs to
21
+ # @param aggregate [Bluebook::Aggregate] the aggregate to persist
22
+ # @param bind [Bluebook::Bind] the bind naming the adapter, as `BindingPolicy.resolve`
23
+ # or the projection port chose it
24
+ # @param recover [Boolean] true to replay the journal through `project` before returning
25
+ # @param settings_verb [String] the verb whose world settings configure the adapter;
26
+ # `"persisted_by"` by default, `Ports::Projection::VERB` for a projection
27
+ # @return [Persistence::AppendOnly] the repository over the guarded adapter
28
+ # @raise [Runtime::WiringError] if the bind's verb or settings fail the registry's
29
+ # checks, no `Hecks::Adapters` constant matches the adapter name, or the adapter
30
+ # lacks `append`, `project` or `entries`
11
31
  def build(registry, domain, aggregate, bind, recover: true, settings_verb: VERB)
12
32
  registry.check_verb(bind)
13
33
  settings = (registry.world(domain)&.for_binding(settings_verb, bind.adapter) || {})
@@ -15,8 +35,8 @@ module Hecks
15
35
  registry.check_settings(bind, settings)
16
36
  # The domain, the resolved era, and (for an old checkout) the era
17
37
  # that superseded it ride along after the declared-settings
18
- # check: a lineage adapter journals per DOMAIN, writes into its
19
- # own ERA's partition, and refuses to write at all once that era
38
+ # check: a lineage adapter journals per domain, writes into its
39
+ # own era's partition, and refuses to write at all once that era
20
40
  # is superseded (`PostgresEra#append`, BUG#24) — none of which a
21
41
  # world's settings carry.
22
42
  adapter = registry.adapter_class(bind.adapter)
@@ -24,7 +44,7 @@ module Hecks
24
44
  settings: settings.merge(domain: domain.to_s, era: registry.resolved_eras[domain.to_s],
25
45
  superseded_by: registry.superseded_eras[domain.to_s]),
26
46
  root: registry.root)
27
- repository = AppendOnly.new(adapter)
47
+ repository = AppendOnly.new(CodecBoundary.guard!(adapter))
28
48
  recover ? repository.recover! : repository
29
49
  end
30
50
  end