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
@@ -6,16 +6,32 @@ module Hecks
6
6
  # from the IR it is handed, which is why the Postgres mint path can
7
7
  # call it too.
8
8
  module ShapeDiff
9
+ # Computes an aggregate's structural signature, for comparing two eras of it.
10
+ #
11
+ # @param aggregate [Bluebook::Aggregate] the aggregate to describe
12
+ # @return [Array<Array>] one `[name, signature]` pair per attribute, sorted by name;
13
+ # `name` is a Symbol and `signature` is whatever `attribute_signature` returns
9
14
  def shape(aggregate)
10
15
  aggregate.attributes.map do |attribute|
11
16
  [attribute.name, attribute_signature(aggregate, attribute.type)]
12
17
  end.sort_by(&:first)
13
18
  end
14
19
 
20
+ # Expands a declared type into a signature that exposes its members.
21
+ #
15
22
  # A plain type name for a primitive; `[type_name, member_signatures]`
16
23
  # for a value object or entity, walked recursively — so two
17
24
  # attributes with the same declared type name but a different
18
25
  # internal shape are never mistaken for unchanged.
26
+ #
27
+ # @param aggregate [Bluebook::Aggregate] the aggregate whose value objects and entities
28
+ # are searched for `type_name`
29
+ # @param type_name [String, Bluebook::Reference] the attribute's declared type
30
+ # @param seen [Array<String, Bluebook::Reference>] types already being expanded, which
31
+ # stops a self-referencing type from recursing forever
32
+ # @return [String, Bluebook::Reference, Array] `type_name` itself for a primitive, a
33
+ # reference, or a type already in `seen`; otherwise `[type_name, members]`, where
34
+ # `members` is an Array of `[member_name, signature]` pairs sorted by member name
19
35
  def attribute_signature(aggregate, type_name, seen = [])
20
36
  container = nested_type(aggregate, type_name)
21
37
  return type_name if container.nil? || seen.include?(type_name)
@@ -26,13 +42,21 @@ module Hecks
26
42
  [type_name, members]
27
43
  end
28
44
 
45
+ # Looks a type name up among an aggregate's value objects, then its entities.
46
+ #
47
+ # @param aggregate [Bluebook::Aggregate] the aggregate to search
48
+ # @param type_name [String, Bluebook::Reference] the declared type to find
49
+ # @return [Bluebook::ValueObject, Bluebook::Entity, nil] the matching declaration; nil
50
+ # when the type is a primitive, a reference, or nothing this aggregate declares
29
51
  def nested_type(aggregate, type_name)
30
52
  aggregate.value_object(type_name) || aggregate.entities.find { |entity| entity.name == type_name }
31
53
  end
32
54
 
55
+ # Lists the new attributes that would leave an existing record with a required gap.
56
+ #
33
57
  # Attributes present now that the held shape never had — the
34
- # addition side of drift, which `uncovered_attributes` above never
35
- # looks at, since it only walks the HELD shape (vanish/retype).
58
+ # addition side of drift, which `uncovered_attributes` below never
59
+ # looks at, since it only walks the held shape (vanish/retype).
36
60
  # Most additions are free (ADR 0025, "Added attributes and
37
61
  # absence"): a default:, a list_of (frozen []), or a value object
38
62
  # whose fields all default fill an existing record automatically
@@ -40,11 +64,20 @@ module Hecks
40
64
  # non-optional attribute with no way to fill itself — can leave an
41
65
  # existing record with the field genuinely absent, and that is
42
66
  # what this reports: unfilled by a declared translation's own
43
- # `backfill`, OR by a move/convert that lands an OLD field inside
67
+ # `backfill`, or by a move/convert that lands an old field inside
44
68
  # this brand-new attribute (`Lineage#fills?` — the destination-
45
69
  # side question, never `explains?`'s source-side one, since a
46
70
  # rename from Crate to Bin can introduce a top-level attribute
47
71
  # name that never existed to have "vanished").
72
+ #
73
+ # @param aggregate [Bluebook::Aggregate] the aggregate as currently declared
74
+ # @param held_aggregate [Bluebook::Aggregate] the same aggregate as the held era's text
75
+ # declares it
76
+ # @param lineage [Ports::Persistence::Lineage, nil] the edge's rules for this aggregate; nil
77
+ # when the edge declares none, so nothing can fill an addition
78
+ # @return [Array<Symbol>] names of new, non-optional attributes with no default, no
79
+ # list cardinality, no fully-defaulted value object and no rule filling them; `[]`
80
+ # when every addition is safe
48
81
  def unsafe_additions(aggregate, held_aggregate, lineage)
49
82
  held_names = held_aggregate.attributes.map(&:name)
50
83
 
@@ -55,9 +88,17 @@ module Hecks
55
88
  .map(&:name)
56
89
  end
57
90
 
58
- # THE FOUR-ROW TABLE, as a predicate over the fourth row only — the
91
+ # Decides whether a new attribute could be genuinely absent from an existing record.
92
+ #
93
+ # The four-row table, as a predicate over the fourth row only — the
59
94
  # other three (default:, list_of, a fully-defaulted value object)
60
95
  # all fill an existing record for free and never reach here.
96
+ #
97
+ # @param aggregate [Bluebook::Aggregate] the aggregate declaring the attribute, searched
98
+ # for the value object its type may name
99
+ # @param attribute [Bluebook::Attribute] the newly added attribute
100
+ # @return [Boolean] false when the attribute is optional, has a default, is a list, or
101
+ # is a value object whose every field has a default; true otherwise
61
102
  def possibly_absent?(aggregate, attribute)
62
103
  return false if attribute.optional?
63
104
  return false unless attribute.default.nil?
@@ -69,14 +110,24 @@ module Hecks
69
110
  true
70
111
  end
71
112
 
113
+ # Lists the held paths that vanished or changed type with no rule explaining them.
114
+ #
72
115
  # Paths the translation needs to explain: attributes that vanished
73
116
  # by name, attributes that kept their name but changed type (a
74
117
  # `convert` is what lets that be declared at all), and — recursing
75
- # into a same-named, same-typed value object — its OWN members
118
+ # into a same-named, same-typed value object — its own members
76
119
  # vanishing or changing type one level down, reported as a dotted
77
120
  # path ("price.currency"). A pure addition, at any depth, never
78
121
  # needs covering; only vanish-or-retype does, matching the
79
122
  # top-level rule at every depth.
123
+ #
124
+ # @param aggregate [Bluebook::Aggregate] the aggregate as currently declared
125
+ # @param held_aggregate [Bluebook::Aggregate] the same aggregate as the held era's text
126
+ # declares it
127
+ # @param lineage [Ports::Persistence::Lineage, nil] the edge's rules for this aggregate; nil
128
+ # when the edge declares none, so every vanished or retyped path is reported
129
+ # @return [Array<String>] bare attribute names and dotted member paths such as
130
+ # `"price.currency"`; `[]` when the whole diff is explained
80
131
  def uncovered_attributes(aggregate, held_aggregate, lineage)
81
132
  paths = held_aggregate.attributes.flat_map do |held_attribute|
82
133
  current_attribute = aggregate.attribute(held_attribute.name)
@@ -90,20 +141,37 @@ module Hecks
90
141
  paths.reject { |path| lineage.explains?(path) }
91
142
  end
92
143
 
93
- # `held_type`/`current_type` are type NAMES, resolved against each
94
- # side's OWN value_object AND entity declarations — neither is ever
144
+ # Compares one path's held and current types, recursing into shared members.
145
+ #
146
+ # `held_type`/`current_type` are type names, resolved against each
147
+ # side's own value_object and entity declarations — neither is ever
95
148
  # nested in the DSL, only in the type graph, so both are always
96
149
  # looked up flat off their respective aggregate. A `list_of` entity
97
- # is only reached here to DETECT a member vanish-or-retype; there is
150
+ # is only reached here to detect a member vanish-or-retype; there is
98
151
  # no per-element translation machinery yet (`move`/`convert`/`drop`
99
152
  # only reach into a single nested hash, not each element of an
100
153
  # array) — the only way to satisfy a refusal on an entity path
101
154
  # today is a top-level `drop` of the whole list attribute, which
102
155
  # `explains?` already recognizes as covering everything nested
103
156
  # under it. Blunt, but loud beats silent.
157
+ #
158
+ # @param path [String] the bare or dotted path being compared, reported on a mismatch
159
+ # @param held_type [String, Bluebook::Reference] the type the held era declares there
160
+ # @param current_type [String, Bluebook::Reference] the type declared there now
161
+ # @param held_aggregate [Bluebook::Aggregate] the held aggregate, searched for
162
+ # `held_type`'s declaration
163
+ # @param aggregate [Bluebook::Aggregate] the current aggregate, searched for
164
+ # `current_type`'s declaration
165
+ # @param lineage [Ports::Persistence::Lineage, nil] the edge's rules, asked whether a retype
166
+ # pairs the two type names; nil means no retype is accepted
167
+ # @param seen [Array<String, Bluebook::Reference>] current types already being
168
+ # expanded, which stops a self-referencing type from recursing forever
169
+ # @return [Array<String>] `[path]` when the types differ with no retype declared; the
170
+ # dotted paths of members that vanished or changed type beneath it; `[]` when
171
+ # nothing differs or either type is not a value object or entity
104
172
  def diff_type(path, held_type, current_type, held_aggregate, aggregate, lineage, seen = [])
105
173
  if held_type != current_type
106
- # A declared retype says the two TYPE names mean the same shape
174
+ # A declared retype says the two type names mean the same shape
107
175
  # — accept the pair, but still recurse into the members so a
108
176
  # member drift hiding beneath the rename is caught by name.
109
177
  return [path] unless lineage&.retype?(held_type, current_type)
@@ -16,24 +16,34 @@ module Hecks
16
16
  # historical bluebook text under old grammar defaults. Both are real,
17
17
  # independent, currently-shipped consumers.
18
18
  #
19
- # A prior version of this module ALSO drove its own top-level check —
20
- # `check!`/`check_bluebook!`, walking a registry and reading/writing a
21
- # held snapshot under `data/eras/*.bluebook` — duplicating, on its own,
22
- # the same per-aggregate walk `CoverageCheck` already performs against
23
- # `PostgresEra`'s own DB-held shapes. Nothing in production ever called
24
- # it (only a direct unit spec did); deleted rather than kept unwired,
25
- # per ADR 0032. Wanted again, it's rebuilt informed by `CoverageCheck`'s
26
- # real orchestration, not resurrected from here.
19
+ # There is deliberately no top-level `check!`/`check_bluebook!` here
20
+ # walking a registry and reading/writing a held snapshot under
21
+ # `data/eras/*.bluebook`: it would duplicate, on its own, the same
22
+ # per-aggregate walk `CoverageCheck` already performs against
23
+ # `PostgresEra`'s own DB-held shapes, and nothing in production would
24
+ # call it — an unwired driver is not kept, per ADR 0032. If one is
25
+ # wanted, it is built informed by `CoverageCheck`'s real orchestration.
27
26
  module EraGuard
28
27
  extend ShapeDiff
29
28
 
30
29
  module_function
31
30
 
31
+ # Refuses the boot when a held aggregate is gone and no translation says where it went.
32
+ #
32
33
  # An aggregate that existed in the held text and answers to no
33
34
  # current name — renamed silently, with nothing declaring `was:` to
34
35
  # explain where its data went — is exactly the disease this guards
35
36
  # against, and a plain per-aggregate diff would never see it: the
36
37
  # current aggregate simply has no held counterpart to compare to.
38
+ #
39
+ # @param registry [Runtime::Registry] the registry whose declared translations are
40
+ # searched for a `was:` or a `retired` entry naming the held aggregate
41
+ # @param bluebook [Bluebook::Chapter] the bluebook booting now
42
+ # @param held_bluebook [Bluebook::Chapter] the bluebook parsed from the held era's text
43
+ # @return [void]
44
+ # @raise [Runtime::WiringError] if a held aggregate matches no current aggregate by
45
+ # name, no current aggregate's translation declares it as `was:`, and no translation
46
+ # for this domain retires it
37
47
  def check_vanished_aggregates!(registry, bluebook, held_bluebook)
38
48
  held_bluebook.aggregates.each do |held_aggregate|
39
49
  claimed = bluebook.aggregates.any? do |aggregate|
@@ -54,9 +64,18 @@ module Hecks
54
64
  end
55
65
  end
56
66
 
67
+ # Raises the refusal naming every changed path no translation rule explains.
68
+ #
57
69
  # The Layer-1 coverage refusal — one wording, shared with whoever
58
70
  # calls it (today, `PostgresEra::LineageManager::CoverageCheck`'s
59
71
  # own mint-time coverage check).
72
+ #
73
+ # @param bluebook [Bluebook::Chapter] the bluebook booting now, named in the message
74
+ # @param aggregate [Bluebook::Aggregate] the aggregate whose shape changed
75
+ # @param uncovered [Array<String>] the unexplained paths, as `uncovered_attributes`
76
+ # returns them; must not be empty, since the first one seeds the suggested rule
77
+ # @return [void] never returns; always raises
78
+ # @raise [Runtime::WiringError] always, carrying the refusal wording
60
79
  def refuse_uncovered!(bluebook, aggregate, uncovered)
61
80
  raise WiringError,
62
81
  "cannot boot #{bluebook.name}::#{aggregate.name}: its shape changed and " \
@@ -65,9 +84,18 @@ module Hecks
65
84
  "#{suggestion(uncovered.first)}."
66
85
  end
67
86
 
87
+ # Raises the refusal naming every new required attribute an existing record cannot fill.
88
+ #
68
89
  # The addition-side sibling of refuse_uncovered! above — same
69
90
  # wording shape, different cause: nothing vanished or changed type,
70
91
  # something new arrived that an existing record has no way to hold.
92
+ #
93
+ # @param bluebook [Bluebook::Chapter] the bluebook booting now, named in the message
94
+ # @param aggregate [Bluebook::Aggregate] the aggregate that gained the attributes
95
+ # @param unsafe [Array<Symbol>] the attribute names, as `unsafe_additions` returns them;
96
+ # must not be empty, since the first one seeds the suggested `backfill`
97
+ # @return [void] never returns; always raises
98
+ # @raise [Runtime::WiringError] always, carrying the refusal wording
71
99
  def refuse_unsafe_addition!(bluebook, aggregate, unsafe)
72
100
  raise WiringError,
73
101
  "cannot boot #{bluebook.name}::#{aggregate.name}: #{unsafe.map { |name| ":#{name}" }.join(', ')} " \
@@ -77,8 +105,18 @@ module Hecks
77
105
  "`backfill :#{unsafe.first}, default: ...`."
78
106
  end
79
107
 
108
+ # Renders a path the way a translation file spells it.
109
+ #
110
+ # @param path [String] a bare attribute name or a dotted value-object member path
111
+ # @return [String] the path quoted (`"price.currency"`) when dotted, otherwise as a
112
+ # Symbol literal (`:cost`)
80
113
  def render_path(path) = path.include?(".") ? path.inspect : ":#{path}"
81
114
 
115
+ # Proposes the translation rules that would explain one uncovered path.
116
+ #
117
+ # @param path [String] a bare attribute name or a dotted value-object member path
118
+ # @return [String] backticked example rules: `move`/`drop` for a dotted path,
119
+ # `rename`/`drop` for a bare name
82
120
  def suggestion(path)
83
121
  if path.include?(".")
84
122
  "`move #{path.inspect}, to: #{path.inspect}` or `drop #{path.inspect}`"
@@ -90,60 +128,79 @@ module Hecks
90
128
  # Parses held source into its own IR, in a scratch registry so a past
91
129
  # era's text never touches the one actually booting.
92
130
  #
93
- # NORMAL PARSE FIRST, shadow only as a FALLBACK — not shadow-parsing
94
- # unconditionally, which is what this used to do. A handful of DSL
131
+ # Normal parse first, shadow only as a fallback — never shadow-parsing
132
+ # unconditionally. A handful of DSL
95
133
  # defaults fork on `MetaValidator.shadow_parsing?` for a reason
96
- # that has NOTHING to do with syntax the live grammar can no longer
134
+ # that has nothing to do with syntax the live grammar can no longer
97
135
  # read at all (`identified_by { }`, `belongs_to`, `has_one`,
98
136
  # `has_many` — genuinely removed spellings, exactly what shadow-
99
137
  # parsing exists to keep readable): `reference_to`'s own default
100
138
  # mint name (`default_reference_name`, attribute_collector.rb)
101
- # changed from `_id`-suffixed to bare under ADR 0025, and THAT fork
102
- # applies even to text using nothing but current, live syntax.
103
- # Held text minted under the CURRENT grammar — every real era in
104
- # this corpus today, since nothing has ever minted a second one —
105
- # parses fine normally; only the reference-naming DEFAULT differed
106
- # once shadow mode engaged unconditionally, so it silently
107
- # reconstructed a DIFFERENT shape (and hash) than a fresh parse of
139
+ # is bare under ADR 0025 and `_id`-suffixed under shadow mode, and
140
+ # that fork applies even to text using nothing but current, live
141
+ # syntax. Held text minted under the current grammar — every real
142
+ # era in this corpus today, since nothing has ever minted a second
143
+ # one — parses fine normally; under unconditional shadow mode only
144
+ # the reference-naming default differs, so it silently
145
+ # reconstructs a different shape (and hash) than a fresh parse of
108
146
  # the identical text — the same text hashing two different ways
109
147
  # depending on which code path read it, breaking `ensure_named!`'s
110
148
  # own from/to edge lookup with a spurious "no translation edge
111
149
  # covers it" refusal that has nothing to do with any real
112
150
  # translation gap.
113
151
  #
114
- # A normal parse can only ever SUCCEED on text the live grammar
152
+ # A normal parse can only ever succeed on text the live grammar
115
153
  # fully understands — there is no way for it to silently produce a
116
154
  # wrong-but-plausible answer for genuinely legacy text, since every
117
155
  # removed spelling refuses loudly (`Malformed`) rather than
118
156
  # degrading. So: try normal first — if the ordinary grammar reads
119
- # this text without complaint, that IS the canonical, unambiguous
157
+ # this text without complaint, that is the canonical, unambiguous
120
158
  # interpretation, the same one `label_of`/`mint_hash` on the same
121
159
  # source text always computes, whoever's asking. Only on a
122
160
  # `Malformed` refusal — the one signal that actually means "this
123
161
  # spelling doesn't exist anymore" — fall back to the legacy
124
- # grammar, exactly as before this change. Any OTHER exception (a
162
+ # grammar. Any other exception (a
125
163
  # genuine syntax error, an unrelated validation refusal) propagates
126
164
  # unchanged; swallowing it here to retry under shadow mode would
127
165
  # risk masking a real defect in the held text behind a confusing
128
166
  # second failure instead of the original, more specific one.
129
167
  #
130
168
  # `MetaValidator.while_shadow_parsing` (ADR 0025, docs/dsl-work-
131
- # slices.md's S0a) is what makes the fallback a LEGACY grammar
169
+ # slices.md's S0a) is what makes the fallback a legacy grammar
132
170
  # rather than just a second copy of today's: it stops
133
171
  # `BluebookBuilder.build` from judging this text against the
134
- # grammar as it stands NOW, which is the one thing that would make
135
- # a removed spelling refuse HISTORY the day it is removed from
172
+ # grammar as it stands now, which is the one thing that would make
173
+ # a removed spelling refuse history the day it is removed from
136
174
  # live source. The scratch registry is throwaway either way —
137
175
  # nothing here is dispatched against or exposed to the real one —
138
176
  # so skipping the judge/assemble round-trip changes nothing this
139
177
  # method reads: `shape`, `uncovered_attributes`, and friends only
140
178
  # ever ask the built IR for its own structure.
179
+ #
180
+ # @param source [String] the held bluebook text to evaluate
181
+ # @param path [String] the file path the text is evaluated as, which the predicate
182
+ # extractor reads from disk; callers pass a tempfile holding the same text
183
+ # @return [Bluebook::Chapter, nil] the first bluebook the text declares; nil when it
184
+ # declares none
185
+ # @raise [Bluebook::DSL::Malformed] if the text parses under neither the live grammar
186
+ # nor the legacy one
187
+ # @raise [SyntaxError] if the text is not valid Ruby
141
188
  def shadow_parse(source, path)
142
189
  parse_bluebook(source, path, shadow: false)
143
190
  rescue Hecks::Bluebook::DSL::Malformed
144
191
  parse_bluebook(source, path, shadow: true)
145
192
  end
146
193
 
194
+ # Evaluates bluebook text once, in a throwaway registry, under one chosen grammar.
195
+ #
196
+ # @param source [String] the bluebook text to evaluate
197
+ # @param path [String] the file path reported to `Kernel.eval` as the text's origin
198
+ # @param shadow [Boolean] true evaluates inside `MetaValidator.while_shadow_parsing`
199
+ # (the legacy grammar); false evaluates under the live grammar
200
+ # @return [Bluebook::Chapter, nil] the first bluebook the text registered in the scratch
201
+ # registry; nil when it declares none
202
+ # @raise [Bluebook::DSL::Malformed] if the chosen grammar refuses the text
203
+ # @raise [SyntaxError] if the text is not valid Ruby
147
204
  def parse_bluebook(source, path, shadow:)
148
205
  scratch = Registry.new
149
206
  loading = Ports::Loading.bootstrap
@@ -6,41 +6,52 @@ require_relative "storage_shape"
6
6
  module Hecks
7
7
  module Runtime
8
8
  # The refusal wording for a held era whose digest no longer matches its
9
- # frozen text — the digest mismatch alone is what DETECTS tampering (a
9
+ # frozen text — the digest mismatch alone is what detects tampering (a
10
10
  # plain SHA256 comparison over raw bytes, unrelated to any of this);
11
11
  # this only supplies the wording once that's already fired.
12
12
  #
13
- # LIVES HERE, NOT UNDER `Ports::Persistence` — `project` below directly
13
+ # Lives here, not under `Ports::Persistence` — `project` below directly
14
14
  # calls `Runtime::EraGuard.shadow_parse`/`Runtime::StorageShape.project`
15
15
  # (DSL-execution machinery), the "an adapter/port reaches into the
16
16
  # runtime instead of being handed already-computed data" shape that
17
17
  # has caused trouble here before. A capability this dependent on the
18
- # runtime is a runtime-owned one that the Postgres adapter CALLS, not
18
+ # runtime is a runtime-owned one that the Postgres adapter calls, not
19
19
  # a ports-level module that happens to reach sideways into it.
20
20
  #
21
- # Used to also distinguish a cosmetic edit from a real shape change
22
- # here, by re-parsing the edited text — a pure quality-of-message
23
- # nicety, not a safety property, since dropped: every tamper refusal
24
- # reaches the same generic wording now, the one this already fell to
25
- # whenever it couldn't classify an edit anyway. An operator judges
26
- # "did this matter" themselves,
27
- # reading the still-archived original — an anomalous recovery moment
28
- # already, not a normal boot path.
21
+ # Every tamper refusal reaches the same generic wording, cosmetic edit
22
+ # or real shape change alike: telling the two apart by re-parsing the
23
+ # edited text is a pure quality-of-message nicety, not a safety
24
+ # property, and an edit that cannot be classified has to fall to the
25
+ # generic wording anyway. An operator judges "did this matter"
26
+ # themselves, reading the still-archived original — an anomalous
27
+ # recovery moment already, not a normal boot path.
29
28
  module EraTamper
30
29
  module_function
31
30
 
31
+ # Words the boot refusal for a held era text whose digest no longer matches.
32
+ #
33
+ # @param domain [String] name of the domain whose era was edited
34
+ # @param ordinal [Integer] the edited era's ordinal in `hecks_eras`
35
+ # @return [String] the refusal message, ready to raise as a `Runtime::WiringError`
32
36
  def refusal(domain:, ordinal:)
33
37
  "cannot boot #{domain}: the held text of era #{ordinal} was edited after it was frozen — " \
34
38
  "held era texts are storage facts; restore the original text, or reset the data"
35
39
  end
36
40
 
37
- # The storage-shape projection of a bluebook text, JSON-normalized
38
- # for structural comparison against a stored projection; nil when
39
- # the text does not load. ALWAYS parsed through a fresh tempfile,
40
- # never the held file's own path: the predicate extractor caches
41
- # source by path, and a held path whose content has changed (the
42
- # very situation this module exists for) would hand it stale
43
- # lines.
41
+ # Parses a bluebook text and returns its storage-shape projection.
42
+ #
43
+ # The projection is JSON-normalized for structural comparison
44
+ # against a stored projection; nil when the text does not load.
45
+ # Always parsed through a fresh tempfile, never the held file's own
46
+ # path: the predicate extractor caches source by path, and a held
47
+ # path whose content has changed (the very situation this module
48
+ # exists for) would hand it stale lines.
49
+ #
50
+ # @param text [String] the bluebook source to parse
51
+ # @param _source_path [String, nil] ignored; the text is always parsed from a tempfile
52
+ # @return [Hash{String => Object}, nil] `StorageShape.project`'s Hash after a JSON
53
+ # round-trip; nil when the text declares no bluebook, or when parsing or projecting
54
+ # raises any `StandardError` or `SyntaxError`
44
55
  def project(text, _source_path = nil)
45
56
  file = Tempfile.new(["hecks-tamper-", ".bluebook"])
46
57
  begin