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
@@ -10,12 +10,26 @@ module Hecks
10
10
  module LineageManager
11
11
  # What a mint must prove before it may happen: the edge covers
12
12
  # the whole diff, no identity path was re-keyed, and the audit's
13
- # first two layers pass over the LIVE compiled chain.
13
+ # first two layers pass over the live compiled chain.
14
14
  module CoverageCheck
15
- # Layer 1 against THIS edge specifically: every vanished or
15
+ # Refuses a mint whose translation edge leaves any part of the shape change
16
+ # unexplained.
17
+ #
18
+ # Layer 1 against this edge specifically: every vanished or
16
19
  # retyped path in the held→current diff must be explained, and
17
20
  # every held aggregate must still be claimed. The refusal is
18
21
  # EraGuard's own, byte for byte.
22
+ #
23
+ # @param registry [Runtime::Registry] the registry whose loaded translations may
24
+ # claim a held aggregate through `was:` or `retired`
25
+ # @param bluebook [Bluebook::Chapter] the domain as currently declared
26
+ # @param held_bluebook [Bluebook::Chapter] the domain as the latest held era's
27
+ # text declares it (a shadow parse)
28
+ # @param edge [Bluebook::Translation] the one edge leaving the latest held era
29
+ # @return [void]
30
+ # @raise [Runtime::WiringError] if an identity path changed without a `rekey`
31
+ # rule, a vanished or retyped path has no rule, a new required attribute has
32
+ # nothing to fill it, or a held aggregate is neither renamed nor retired
19
33
  def check_coverage!(registry, bluebook, held_bluebook, edge)
20
34
  bluebook.aggregates.each do |aggregate|
21
35
  rules = Ports::Persistence::Lineage.from_declared(edge.for_aggregate(aggregate.name), aggregate.name)
@@ -32,15 +46,28 @@ module Hecks
32
46
  Runtime::EraGuard.check_vanished_aggregates!(registry, bluebook, held_bluebook)
33
47
  end
34
48
 
35
- # An identity-path change is a RE-KEYING, not an ordinary
49
+ # Refuses a mint that changes an aggregate's identity paths without declaring
50
+ # a `rekey`.
51
+ #
52
+ # An identity-path change is a re-keying, not an ordinary
36
53
  # translation — stored ids were fixed at write time under the old
37
- # key, so this refuses UNLESS the edge declares a `rekey` for
54
+ # key, so this refuses unless the edge declares a `rekey` for
38
55
  # this aggregate covering exactly that. `rules.rekey?` (see
39
56
  # `Ports::Persistence::Lineage`'s own comment) is the single
40
57
  # source of truth every consumer of this fact asks — this is not
41
58
  # a second, independent check of `declared.rekeys`.
59
+ #
60
+ # @param bluebook [Bluebook::Chapter] the domain, named in the refusal
61
+ # @param aggregate [Bluebook::Aggregate] the aggregate as currently declared
62
+ # @param held_aggregate [Bluebook::Aggregate] the same aggregate as the held
63
+ # era's text declares it
64
+ # @param rules [Ports::Persistence::Lineage, nil] the edge's rules for this
65
+ # aggregate; nil when the edge declares none, which cannot excuse a change
66
+ # @return [nil] when the identity paths are unchanged or a `rekey` covers them
67
+ # @raise [Runtime::WiringError] if the identity paths differ and no `rekey` rule
68
+ # is declared
42
69
  def check_identity_unchanged!(bluebook, aggregate, held_aggregate, rules)
43
- # The FULL declared path lists, in declaration order — never the
70
+ # The full declared path lists, in declaration order — never the
44
71
  # single-head shortcut, which is nil for every composite identity
45
72
  # and so would let two different composites compare as unchanged.
46
73
  return if held_aggregate.identity_paths == aggregate.identity_paths
@@ -56,10 +83,28 @@ module Hecks
56
83
  "data explicitly"
57
84
  end
58
85
 
59
- # Layers 1 and 2 of the audit, over the LIVE compiled chain —
86
+ # Previews every aggregate's translated head through the pending chain and
87
+ # refuses the mint if the audit finds a violation.
88
+ #
89
+ # Layers 1 and 2 of the audit, over the live compiled chain —
60
90
  # before anything is minted, so a refusal leaves no half-born
61
91
  # era. (A convert meeting an unmapped value raises inside the
62
92
  # preview query itself: same rollback-shaped outcome.)
93
+ #
94
+ # @param bluebook [Bluebook::Chapter] the domain as currently declared
95
+ # @param lineage [Adapters::PostgresEra::Lineage] the domain's lineage, on an open
96
+ # connection
97
+ # @param chain [Array<Hash{Symbol => Bluebook::Translation}>] the full edge chain
98
+ # ending in `edge`, as `edge_chain` returns it
99
+ # @param ordinal [Integer] the ordinal of the era about to be minted
100
+ # @param edge [Bluebook::Translation] the pending edge, whose per-aggregate rules
101
+ # the audit checks
102
+ # @return [nil] when no aggregate reports a violation
103
+ # @raise [Runtime::WiringError] if Postgres refuses the translated preview query
104
+ # (a `convert` meeting an unmapped value, for one), or the audit reports any
105
+ # violation; the message lists them all
106
+ # @raise [PG::Error] if Postgres refuses the "before" query; only the translated
107
+ # preview is rescued into a `Runtime::WiringError`
63
108
  def audit!(bluebook, lineage, chain, ordinal, edge)
64
109
  violations = []
65
110
  bluebook.aggregates.each do |aggregate|
@@ -5,12 +5,15 @@ module Hecks
5
5
  module Adapters
6
6
  class PostgresEra
7
7
  module LineageManager
8
- # The boot-time resolution: which era IS this checkout? First
8
+ # The boot-time resolution: which era is this checkout? First
9
9
  # boot holds era 1; a quiet reboot changes nothing; a
10
10
  # held-but-superseded shape boots read-only-toward-the-fence; an
11
11
  # unheld shape goes to the minter.
12
12
  module EraResolver
13
13
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
14
+ # Resolves which era this boot is and records it on the registry, holding era 1
15
+ # on a first boot and minting the next era when the shape is one no era holds.
16
+ #
14
17
  # A single order-dependent dispatch over a closed set of four
15
18
  # boot scenarios (first boot / quiet reboot / held-but-superseded
16
19
  # / unheld-shape-goes-to-minter), each already explained by its
@@ -18,11 +21,32 @@ module Hecks
18
21
  # `ensure`. Splitting the branches into separate methods would
19
22
  # turn each `return` (which exits `check!`, closing `db`) into a
20
23
  # sentinel value threaded back up, obscuring the mutual
21
- # exclusivity that IS the method's whole point.
24
+ # exclusivity that is the method's whole point.
25
+ #
26
+ # @param registry [Runtime::Registry] the booting registry; `resolved_eras` gets
27
+ # this domain's ordinal on every path but a first boot (where the adapter
28
+ # self-resolves to era 1), and `superseded_eras` gets the newest ordinal when
29
+ # this checkout's shape is held but superseded
30
+ # @param bluebook [Bluebook::Chapter] the domain being booted
31
+ # @param current_text [String] the domain's bluebook source as it stands on disk,
32
+ # held verbatim as the new era's text on a first boot or a mint
33
+ # @param settings [Hash{Symbol, String => Object}] the world's persistence settings
34
+ # for this binding; `database` is required, `schema`, `role` (a Postgres role to
35
+ # grant this era's privileges to) and `allow_superuser` are honored
36
+ # @param directory [String, nil] the domain's bluebook directory, where
37
+ # `HECKS_SCAFFOLD=1` writes a translation edge; nil disables scaffolding
38
+ # @return [Integer, nil] the ordinal of the era this boot minted, or nil when it
39
+ # minted nothing (first boot, quiet reboot, or a held-but-superseded shape)
40
+ # @raise [Runtime::WiringError] if the database cannot be reached, the connection
41
+ # is a superuser or has BYPASSRLS without `allow_superuser`, a declared
42
+ # `formerly_known_as` rename cannot be applied, a held text fails its integrity
43
+ # check, or the shape is unheld and `mint!` refuses
44
+ # @raise [Bluebook::DSL::Malformed] if a held era's text parses under neither the
45
+ # current nor the legacy grammar
22
46
  def check!(registry:, bluebook:, current_text:, settings:, directory: nil)
23
47
  db = PostgresEra.connect_for(bluebook.name, settings)
24
48
  lineage = Lineage.new(db, bluebook.name, formerly_known_as: bluebook.formerly_known_as)
25
- # FIRST, before ensure_base! provisions or verifies anything —
49
+ # First, before ensure_base! provisions or verifies anything —
26
50
  # the one point every PostgresEra boot passes through, and the
27
51
  # earliest at which the connection's own role is known. See
28
52
  # check_fence_applies!'s own header (BUG#24) for why a
@@ -39,7 +63,7 @@ module Hecks
39
63
  lineage.hold_first!(current_text, projection: Runtime::StorageShape.project(bluebook))
40
64
  bluebook.aggregates.each { |aggregate| lineage.ensure_first_head!(aggregate.storage_name) }
41
65
  # hold_first! already established era 1 as current for
42
- # EVERY role; this one just needs its own privileges.
66
+ # every role; this one just needs its own privileges.
43
67
  lineage.grant_role!(role, aggregates: bluebook.aggregates, era: 1) if role
44
68
  return
45
69
  end
@@ -52,7 +76,7 @@ module Hecks
52
76
  # is permanently the old one — the frozen text is authentic
53
77
  # record ("this domain really was called that, at the
54
78
  # time") and must never be rewritten. Only the in-memory
55
- # shape used for THIS comparison is normalized, and only
79
+ # shape used for this comparison is normalized, and only
56
80
  # where it exactly matches the declared old name, so an
57
81
  # unrelated domain that happens to shape-match some other
58
82
  # domain's history still shows up as a real mismatch.
@@ -74,9 +98,9 @@ module Hecks
74
98
  matched, = shapes.find { |_, shape| shape == current_shape }
75
99
  if matched
76
100
  # A held-but-superseded era — an old checkout still running.
77
- # It may keep BOOTING and READING (PostgresEra is the one
101
+ # It may keep booting and reading (PostgresEra is the one
78
102
  # adapter that recognizes this rather than refusing), but it
79
- # may not keep WRITING: the shared era fence was already
103
+ # may not keep writing: the shared era fence was already
80
104
  # advanced past this ordinal by whichever mint superseded
81
105
  # it, and nothing here may roll that back. Granting only
82
106
  # this role's privileges, never advance_era!, is what keeps
@@ -84,11 +108,11 @@ module Hecks
84
108
  # called with a superseded ordinal.
85
109
  lineage.grant_role!(role, aggregates: bluebook.aggregates, era: matched[:ordinal]) if role
86
110
  registry.resolved_eras[bluebook.name] = matched[:ordinal]
87
- # ...and the boot REMEMBERS that it is superseded, not only
111
+ # ...and the boot remembers that it is superseded, not only
88
112
  # which era it is: `RepositoryFactory.build` hands this to
89
113
  # the adapter as `superseded_by:`, and `PostgresEra#append`
90
114
  # refuses on it before issuing the INSERT. That is the
91
- # in-process half of "may not keep WRITING" — the half that
115
+ # in-process half of "may not keep writing" — the half that
92
116
  # holds even for a connection the RLS fence cannot bite
93
117
  # (a superuser under allow_superuser; BUG#24).
94
118
  registry.superseded_eras[bluebook.name] = latest[:ordinal]
@@ -10,6 +10,22 @@ module Hecks
10
10
  # world's post-cut writes into the head by their recorded global
11
11
  # ordinals, under the full audit, in one transaction.
12
12
  module MergeCoordinator
13
+ # Merges the writes old checkouts made after the last mint into the current
14
+ # era's head, on a connection of its own that it closes afterwards.
15
+ #
16
+ # @param registry [Runtime::Registry] the loaded registry; supplies the
17
+ # translation edges the chain is built from
18
+ # @param bluebook [Bluebook::Chapter] the domain whose tail is merged
19
+ # @param settings [Hash{Symbol, String => Object}] the world's persistence
20
+ # settings for this binding; `database` is required
21
+ # @param winners [Hash{String => String}] record id to `"old"` or `"new"`, naming
22
+ # which world's whole record wins for each id both worlds touched since the cut
23
+ # @return [true] when the merge committed
24
+ # @raise [Runtime::WiringError] if the database cannot be reached, the domain
25
+ # stands at era 1, the edge chain is broken, a record touched by both worlds
26
+ # has no named winner, the post-merge audit reports violations, another mint
27
+ # or merge holds the domain lock for 10 seconds, or Postgres refuses a
28
+ # statement; every refusal rolls the merge back
13
29
  def merge!(registry:, bluebook:, settings:, winners: {})
14
30
  db = PostgresEra.connect_for(bluebook.name, settings)
15
31
  lineage = Lineage.new(db, bluebook.name, formerly_known_as: bluebook.formerly_known_as)
@@ -12,6 +12,29 @@ module Hecks
12
12
  # reviewed, check coverage, audit the live chain, and mint — or
13
13
  # refuse toward the authoring loop by name.
14
14
  module Minter
15
+ # Mints the next era for a drifted shape in one transaction, after proving the
16
+ # edge is present, current, approved where it must be, covering, and audited.
17
+ #
18
+ # @param registry [Runtime::Registry] the booting registry; supplies the loaded
19
+ # translation edges
20
+ # @param bluebook [Bluebook::Chapter] the domain as currently declared
21
+ # @param current_text [String] the domain's bluebook source, held verbatim as the
22
+ # new era's text
23
+ # @param lineage [Adapters::PostgresEra::Lineage] the domain's lineage, on an open
24
+ # connection
25
+ # @param latest [Hash{Symbol => Object}] the newest held era, as `Lineage#eras`
26
+ # returns it (`:ordinal`, `:hash`, `:label`, `:held_text`, `:watermark`)
27
+ # @param role [String, nil] a Postgres role to grant the new era's privileges to;
28
+ # nil grants nothing
29
+ # @param directory [String, nil] the domain's bluebook directory, where
30
+ # `HECKS_SCAFFOLD=1` writes a missing edge; nil disables scaffolding
31
+ # @return [Integer] the ordinal of the era just minted
32
+ # @raise [Runtime::WiringError] if no edge leaves the latest era, more than one
33
+ # does, the edge targets another shape, a compute or rekey edge lacks a current
34
+ # approval, the edge leaves drift uncovered, the chain is broken, the audit
35
+ # reports violations, or the mint transaction itself fails
36
+ # @raise [Bluebook::DSL::Malformed] if the latest held text parses under neither
37
+ # the current nor the legacy grammar
15
38
  def mint!(registry, bluebook, current_text, lineage, latest, role: nil, directory: nil)
16
39
  ensure_named!(lineage, latest)
17
40
  latest = lineage.eras.last
@@ -34,12 +57,28 @@ module Hecks
34
57
  ordinal
35
58
  end
36
59
 
37
- # Find the one translation edge that leaves the held era, and
38
- # validate it: exactly one edge must leave (eras fork
60
+ # Finds the one translation edge that leaves the held era, and
61
+ # validates it: exactly one edge must leave (eras fork
39
62
  # mechanically — a second edge from the same source is a wiring
40
63
  # mistake, not a merge to resolve automatically), and it must
41
64
  # target the current shape's label (otherwise the edge is stale
42
65
  # and boot must not silently mint past it).
66
+ #
67
+ # @param registry [Runtime::Registry] the registry whose loaded `translations`
68
+ # are searched
69
+ # @param bluebook [Bluebook::Chapter] the domain as currently declared
70
+ # @param lineage [Adapters::PostgresEra::Lineage] the domain's lineage, used only
71
+ # when scaffolding a missing edge
72
+ # @param latest [Hash{Symbol => Object}] the newest held era, already named;
73
+ # `:label` is the edge's required source and `:ordinal` appears in refusals
74
+ # @param label [String] the current shape's label, the edge's required target
75
+ # @param ordinal [Integer] the ordinal of the era about to be minted
76
+ # @param directory [String, nil] where `HECKS_SCAFFOLD=1` writes a missing edge;
77
+ # nil disables scaffolding
78
+ # @return [Bluebook::Translation] the single edge from `latest[:label]` to `label`
79
+ # @raise [Runtime::WiringError] if no edge leaves the latest era (naming the
80
+ # scaffold it wrote, when it wrote one), more than one does, or the one edge
81
+ # targets a label other than `label`
43
82
  def resolve_edge!(registry, bluebook, lineage, latest, label, ordinal, directory)
44
83
  edges = registry.translations.select { |t| t.domain == bluebook.name && t.from == latest[:label] }
45
84
  refuse_toward_the_scaffold!(registry, bluebook, lineage, latest, ordinal, directory) if edges.empty?
@@ -58,15 +97,29 @@ module Hecks
58
97
  edge
59
98
  end
60
99
 
100
+ # Refuses a mint whose edge carries a compute or rekey rule without a recorded
101
+ # approval that still matches the edge and the journal.
102
+ #
61
103
  # A compute's (and, the same way, a rekey's) only verification
62
104
  # is the audit's human-approved sample — mint stays
63
105
  # non-interactive by requiring the approval to already exist,
64
- # recorded IN THIS DATABASE by `bin/translation_audit …
106
+ # recorded in this database by `bin/translation_audit …
65
107
  # --approve` and bound to what was actually reviewed: this
66
108
  # edge's parsed content, and the journal as it stood when the
67
109
  # samples were read. A journal that has advanced past the
68
110
  # review invalidates it — the approved samples no longer cover
69
111
  # the data.
112
+ #
113
+ # @param bluebook [Bluebook::Chapter] the domain, named in the refusal
114
+ # @param lineage [Adapters::PostgresEra::Lineage] the domain's lineage, which holds
115
+ # the recorded approvals and the journal's last ordinal
116
+ # @param edge [Bluebook::Translation] the edge about to be minted through
117
+ # @param ordinal [Integer] the ordinal of the era about to be minted
118
+ # @return [nil] when the edge has no compute or rekey rule, or its approval is
119
+ # current
120
+ # @raise [Runtime::WiringError] if no approval is recorded for this shape pair, the
121
+ # approval's edge digest differs from this edge's, or the journal has advanced
122
+ # past the reviewed ordinal
70
123
  def ensure_compute_rekey_approved!(bluebook, lineage, edge, ordinal)
71
124
  return unless edge.aggregates.any? { |declared| !declared.computes.empty? || !declared.rekeys.empty? }
72
125
 
@@ -86,10 +139,28 @@ module Hecks
86
139
  "human approved no longer cover the data; re-run bin/translation_audit with --approve"
87
140
  end
88
141
 
142
+ # Refuses a mint that found no translation edge, naming the authoring tools —
143
+ # or, with scaffolding on, the file it just wrote.
144
+ #
89
145
  # No edge yet: the boot refuses toward the authoring loop —
90
146
  # naming both tools and the era ordinal. With HECKS_SCAFFOLD=1
91
- # the boot RUNS the scaffold first (an explicit flag, never a
147
+ # the boot runs the scaffold first (an explicit flag, never a
92
148
  # silent side-effect) and the refusal names the file it wrote.
149
+ #
150
+ # @param registry [Runtime::Registry] the registry, forwarded to `scaffold!` when
151
+ # scaffolding is on
152
+ # @param bluebook [Bluebook::Chapter] the domain, named in the refusal
153
+ # @param lineage [Adapters::PostgresEra::Lineage] the domain's lineage, forwarded
154
+ # to `scaffold!` when scaffolding is on
155
+ # @param latest [Hash{Symbol => Object}] the newest held era, forwarded to
156
+ # `scaffold!` when scaffolding is on
157
+ # @param ordinal [Integer] the ordinal of the era about to be minted, named in
158
+ # the refusal
159
+ # @param directory [String, nil] the domain's bluebook directory; scaffolding
160
+ # requires this to be present as well as `HECKS_SCAFFOLD=1`
161
+ # @return [void] never returns
162
+ # @raise [Runtime::WiringError] always; the message names the scaffolded file
163
+ # when `HECKS_SCAFFOLD=1` and `directory` wrote one
93
164
  def refuse_toward_the_scaffold!(registry, bluebook, lineage, latest, ordinal, directory)
94
165
  if ENV["HECKS_SCAFFOLD"] == "1" && directory
95
166
  path = scaffold!(registry, bluebook, lineage, latest, directory)
@@ -104,9 +175,19 @@ module Hecks
104
175
  "check it with bin/translation_audit, then boot again"
105
176
  end
106
177
 
107
- # Diff the held era against the current shape and write the edge
178
+ # Diffs the held era against the current shape and writes the edge
108
179
  # file — confident rules inline, ambiguities as parse-refusing
109
- # `unresolved` lines. Returns the file path.
180
+ # `unresolved` lines.
181
+ #
182
+ # @param _registry [Runtime::Registry] unused; kept so the signature matches its
183
+ # caller's own arguments
184
+ # @param bluebook [Bluebook::Chapter] the domain as currently declared
185
+ # @param lineage [Adapters::PostgresEra::Lineage] the domain's lineage; named so
186
+ # `ensure_named!` can mint a name for `latest` first if it has none
187
+ # @param latest [Hash{Symbol => Object}] the newest held era, as `Lineage#eras`
188
+ # returns it
189
+ # @param directory [String] the domain's bluebook directory to write the edge under
190
+ # @return [String] the path `Translation::Scaffold::Writer#write!` wrote
110
191
  def scaffold!(_registry, bluebook, lineage, latest, directory)
111
192
  ensure_named!(lineage, latest)
112
193
  latest = lineage.eras.last
@@ -126,9 +207,17 @@ module Hecks
126
207
  Translation::Scaffold.write!(directory, edge)
127
208
  end
128
209
 
210
+ # Mints a name for an era that has none yet, leaving an already-named era untouched.
211
+ #
129
212
  # Era names are minted once. An era held before any drift was
130
213
  # seen has no name yet; it gets one the moment an edge needs to
131
214
  # leave it.
215
+ #
216
+ # @param lineage [Adapters::PostgresEra::Lineage] the domain's lineage, which
217
+ # records the minted name
218
+ # @param era [Hash{Symbol => Object}] the era to name, as `Lineage#eras` returns it;
219
+ # `:hash` present means it is already named
220
+ # @return [void]
132
221
  def ensure_named!(lineage, era)
133
222
  return if era[:hash]
134
223
 
@@ -12,7 +12,7 @@ module Hecks
12
12
  class PostgresEra
13
13
  # The PostgresEra side of the boot-time era gate. Where every other
14
14
  # adapter's era check can only hold texts and refuse, this one
15
- # ACTS: a drifted shape whose translation edge exists, matches by
15
+ # acts: a drifted shape whose translation edge exists, matches by
16
16
  # shape label, covers the whole diff, and passes the audit mints
17
17
  # the next era — one transaction — and boots into it. Everything
18
18
  # else refuses, as loudly and as specifically as the situation
@@ -38,10 +38,24 @@ module Hecks
38
38
 
39
39
  module_function
40
40
 
41
- # The FULL chain, one edge per step in original mint order —
41
+ # Collects the translation edge for every step from era 1 to the current shape.
42
+ #
43
+ # The full chain, one edge per step in original mint order —
42
44
  # never flattened. Each step is found by its stored labels;
43
45
  # every mint required its own edge, so a break in the chain is a
44
46
  # deleted file, and deserves its own refusal.
47
+ #
48
+ # @param registry [Runtime::Registry] the registry whose loaded `translations`
49
+ # are searched
50
+ # @param bluebook [Bluebook::Chapter] the domain; only edges declared for its
51
+ # name are considered
52
+ # @param eras [Array<Hash{Symbol => Object}>] the ancestor eras in ordinal order,
53
+ # as `Lineage#eras` returns them; only `:label` is read
54
+ # @param current_label [String] the shape label the last step must lead to
55
+ # @return [Array<Hash{Symbol => Bluebook::Translation}>] one `{ translation: edge }`
56
+ # per step, in mint order; `[]` when `eras` is empty
57
+ # @raise [Runtime::WiringError] if no loaded translation leads one label to the
58
+ # next
45
59
  def edge_chain(registry, bluebook, eras, current_label)
46
60
  labels = eras.map { |era| era[:label] } + [current_label]
47
61
  (0...(labels.size - 1)).map do |index|
@@ -57,9 +71,18 @@ module Hecks
57
71
  end
58
72
  end
59
73
 
74
+ # Parses a held era's bluebook text into a throwaway registry, leaving the live
75
+ # one untouched.
76
+ #
60
77
  # Held texts live in rows, not files, but the predicate
61
78
  # extractor reads source from disk at the eval path — so a
62
79
  # shadow parse writes the text to a scratch file first.
80
+ #
81
+ # @param source [String] bluebook source text, as held in `hecks_eras.held_text`
82
+ # @return [Bluebook::Chapter, nil] the first bluebook the text declares, or nil
83
+ # when it declares none
84
+ # @raise [Bluebook::DSL::Malformed] if the text parses under neither the current
85
+ # grammar nor the legacy shadow-parse fallback
63
86
  def shadow(source)
64
87
  file = Tempfile.new(["hecks-era-", ".bluebook"])
65
88
  file.write(source)