hecks 0.2.0 → 0.3.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 (408) hide show
  1. checksums.yaml +5 -5
  2. data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +127 -0
  4. data/lib/hecks/adapters/driven/d1.adapter +12 -0
  5. data/lib/hecks/adapters/driven/d1.rb +403 -0
  6. data/lib/hecks/adapters/driven/folder.adapter +3 -0
  7. data/lib/hecks/adapters/driven/folder.rb +199 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
  11. data/lib/hecks/adapters/driven/governance_authorization.rb +62 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +148 -0
  17. data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
  18. data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
  19. data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
  20. data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
  21. data/lib/hecks/adapters/driven/lambda.adapter +4 -0
  22. data/lib/hecks/adapters/driven/lambda.rb +131 -0
  23. data/lib/hecks/adapters/driven/memory.adapter +3 -0
  24. data/lib/hecks/adapters/driven/memory.rb +103 -0
  25. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
  26. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
  27. data/lib/hecks/adapters/driven/postgres/codec.rb +80 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +391 -0
  31. data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
  32. data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
  33. data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
  34. data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
  35. data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
  36. data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
  37. data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
  38. data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
  39. data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
  40. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
  41. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
  42. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  43. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
  44. data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
  45. data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
  46. data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
  47. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  48. data/lib/hecks/adapters/driven/prism.rb +80 -0
  49. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  50. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  51. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  52. data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
  53. data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
  54. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
  55. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  56. data/lib/hecks/adapters/driven/sqlite.rb +285 -0
  57. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  58. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  59. data/lib/hecks/adapters/driven.rb +36 -0
  60. data/lib/hecks/adapters.rb +6 -0
  61. data/lib/hecks/behaviors/dsl.rb +96 -0
  62. data/lib/hecks/behaviors/expectations.rb +280 -0
  63. data/lib/hecks/behaviors/ir.rb +31 -0
  64. data/lib/hecks/behaviors/rspec.rb +42 -0
  65. data/lib/hecks/behaviors/runner.rb +96 -0
  66. data/lib/hecks/behaviors.rb +25 -0
  67. data/lib/hecks/bluebook/aggregate.rb +108 -0
  68. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  69. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  70. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  71. data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
  72. data/lib/hecks/bluebook/assembly/marks.rb +227 -0
  73. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  74. data/lib/hecks/bluebook/assembly.rb +78 -0
  75. data/lib/hecks/bluebook/attribute.rb +96 -0
  76. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  77. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  78. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  79. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  80. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  81. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  82. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  83. data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
  84. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  85. data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
  86. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  87. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  88. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  89. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  90. data/lib/hecks/bluebook/chapter.rb +68 -0
  91. data/lib/hecks/bluebook/command.rb +124 -0
  92. data/lib/hecks/bluebook/domain_port.rb +102 -0
  93. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  94. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
  95. data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
  96. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  97. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
  98. data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
  99. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  100. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  101. data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
  102. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  103. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  104. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  105. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  106. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  107. data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
  108. data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
  109. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  110. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
  111. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  112. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  113. data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
  114. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  115. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  116. data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
  117. data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
  118. data/lib/hecks/bluebook/dsl.rb +45 -0
  119. data/lib/hecks/bluebook/entity.rb +103 -0
  120. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
  121. data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
  122. data/lib/hecks/bluebook/expression/projection.json +170 -0
  123. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  124. data/lib/hecks/bluebook/expression/resolver.rb +630 -0
  125. data/lib/hecks/bluebook/expression.rb +13 -0
  126. data/lib/hecks/bluebook/hexagon.rb +60 -0
  127. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  128. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  129. data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
  130. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  131. data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
  132. data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
  133. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
  134. data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
  135. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  136. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  137. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  138. data/lib/hecks/bluebook/meta_validator.rb +452 -0
  139. data/lib/hecks/bluebook/model_check.rb +318 -0
  140. data/lib/hecks/bluebook/pattern_subset.rb +120 -0
  141. data/lib/hecks/bluebook/policy.rb +41 -0
  142. data/lib/hecks/bluebook/process_manager.rb +86 -0
  143. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  144. data/lib/hecks/bluebook/project_loader.rb +40 -0
  145. data/lib/hecks/bluebook/project_register.rb +82 -0
  146. data/lib/hecks/bluebook/query.rb +61 -0
  147. data/lib/hecks/bluebook/read_model.rb +109 -0
  148. data/lib/hecks/bluebook/reference.rb +74 -0
  149. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  150. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  151. data/lib/hecks/bluebook/translation.rb +92 -0
  152. data/lib/hecks/bluebook/value_object.rb +58 -0
  153. data/lib/hecks/bluebook.rb +74 -0
  154. data/lib/hecks/codemod.rb +342 -0
  155. data/lib/hecks/construct.rb +71 -0
  156. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  157. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  158. data/lib/hecks/deploy/oidc.json +18 -0
  159. data/lib/hecks/doc/reference.rb +389 -0
  160. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  161. data/lib/hecks/facade/cli_door.rb +116 -0
  162. data/lib/hecks/facade/cli_runner.rb +190 -0
  163. data/lib/hecks/facade/command_request.rb +105 -0
  164. data/lib/hecks/facade/handle.rb +173 -0
  165. data/lib/hecks/facade/json_door.rb +154 -0
  166. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  167. data/lib/hecks/facade/surface/chapter.rb +107 -0
  168. data/lib/hecks/facade/surface.rb +48 -0
  169. data/lib/hecks/facade.rb +44 -0
  170. data/lib/hecks/forms/app.rb +300 -0
  171. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  172. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  173. data/lib/hecks/forms/field_renderer.rb +170 -0
  174. data/lib/hecks/forms/field_shape.rb +232 -0
  175. data/lib/hecks/forms/html.rb +53 -0
  176. data/lib/hecks/forms/index_renderer.rb +35 -0
  177. data/lib/hecks/forms/page.rb +157 -0
  178. data/lib/hecks/forms/params.rb +132 -0
  179. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  180. data/lib/hecks/forms/record_renderer.rb +115 -0
  181. data/lib/hecks/forms/record_table.rb +63 -0
  182. data/lib/hecks/forms/reference_options.rb +30 -0
  183. data/lib/hecks/forms/value_object_shape.rb +46 -0
  184. data/lib/hecks/forms.rb +54 -0
  185. data/lib/hecks/fqn.rb +94 -0
  186. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  187. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  188. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  189. data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
  190. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  191. data/lib/hecks/framework/oidc.json +39 -0
  192. data/lib/hecks/framework.rb +90 -0
  193. data/lib/hecks/freezer.rb +67 -0
  194. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  195. data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
  196. data/lib/hecks/fuzzing/properties.rb +1146 -0
  197. data/lib/hecks/fuzzing/replay.rb +635 -0
  198. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  199. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
  200. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  201. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
  202. data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
  203. data/lib/hecks/fuzzing/value_generator.rb +177 -0
  204. data/lib/hecks/fuzzing.rb +12 -0
  205. data/lib/hecks/grammar/evolve.rb +291 -0
  206. data/lib/hecks/grammar/expression.bluebook +417 -0
  207. data/lib/hecks/grammar/expression_operators.json +1152 -0
  208. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  209. data/lib/hecks/grammar/oidc.json +69 -0
  210. data/lib/hecks/grammar/translation.bluebook +203 -0
  211. data/lib/hecks/grammar.rb +178 -0
  212. data/lib/hecks/ir.rb +126 -0
  213. data/lib/hecks/language/adapter.bluebook +116 -0
  214. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  215. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  216. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  217. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  218. data/lib/hecks/language/bluebook/command.bluebook +437 -0
  219. data/lib/hecks/language/bluebook/entity.bluebook +316 -0
  220. data/lib/hecks/language/bluebook/policy.bluebook +172 -0
  221. data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
  222. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  223. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  224. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  225. data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
  226. data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
  227. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  228. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  229. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  230. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  231. data/lib/hecks/language/oidc.json +313 -0
  232. data/lib/hecks/language/port.bluebook +92 -0
  233. data/lib/hecks/language/translation/translation.bluebook +110 -0
  234. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  235. data/lib/hecks/language/world/wiring.bluebook +62 -0
  236. data/lib/hecks/language/world/world.bluebook +84 -0
  237. data/lib/hecks/literal.rb +125 -0
  238. data/lib/hecks/naming.rb +121 -0
  239. data/lib/hecks/ports/access_control.port +4 -0
  240. data/lib/hecks/ports/access_control.rb +62 -0
  241. data/lib/hecks/ports/agent/answers.rb +104 -0
  242. data/lib/hecks/ports/agent.port +4 -0
  243. data/lib/hecks/ports/agent.rb +167 -0
  244. data/lib/hecks/ports/authentication.port +4 -0
  245. data/lib/hecks/ports/authentication.rb +50 -0
  246. data/lib/hecks/ports/authorization.port +4 -0
  247. data/lib/hecks/ports/authorization.rb +59 -0
  248. data/lib/hecks/ports/clock.port +4 -0
  249. data/lib/hecks/ports/clock.rb +62 -0
  250. data/lib/hecks/ports/extraction.port +4 -0
  251. data/lib/hecks/ports/extraction.rb +37 -0
  252. data/lib/hecks/ports/identity_assignment.port +4 -0
  253. data/lib/hecks/ports/identity_assignment.rb +45 -0
  254. data/lib/hecks/ports/identity_generation.port +4 -0
  255. data/lib/hecks/ports/identity_generation.rb +49 -0
  256. data/lib/hecks/ports/identity_resolution.port +4 -0
  257. data/lib/hecks/ports/identity_resolution.rb +40 -0
  258. data/lib/hecks/ports/loading.port +4 -0
  259. data/lib/hecks/ports/loading.rb +13 -0
  260. data/lib/hecks/ports/persistence/append_only.rb +102 -0
  261. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  262. data/lib/hecks/ports/persistence/execution.rb +18 -0
  263. data/lib/hecks/ports/persistence/lineage.rb +277 -0
  264. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  266. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  267. data/lib/hecks/ports/persistence.port +4 -0
  268. data/lib/hecks/ports/persistence.rb +39 -0
  269. data/lib/hecks/ports/projection.port +4 -0
  270. data/lib/hecks/ports/projection.rb +76 -0
  271. data/lib/hecks/ports/query/in_memory.rb +60 -0
  272. data/lib/hecks/ports/query/ordering.rb +41 -0
  273. data/lib/hecks/ports/query.rb +36 -0
  274. data/lib/hecks/ports.rb +27 -0
  275. data/lib/hecks/projections/diagrams.rb +413 -0
  276. data/lib/hecks/projections/ir.rb +18 -0
  277. data/lib/hecks/projections/model/deviations.rb +98 -0
  278. data/lib/hecks/projections/model.rb +145 -0
  279. data/lib/hecks/projections/oidc.rb +110 -0
  280. data/lib/hecks/projections/parser_table.rb +159 -0
  281. data/lib/hecks/projections/reference.rb +38 -0
  282. data/lib/hecks/projections/shape.rb +31 -0
  283. data/lib/hecks/projections/statements.rb +110 -0
  284. data/lib/hecks/projections/vocabulary.rb +100 -0
  285. data/lib/hecks/projections.rb +32 -0
  286. data/lib/hecks/projector/cli_projector.rb +437 -0
  287. data/lib/hecks/projector/docs_projector.rb +321 -0
  288. data/lib/hecks/projector/exporter.rb +141 -0
  289. data/lib/hecks/projector/ir_projector.rb +18 -0
  290. data/lib/hecks/projector/narrate_projector.rb +243 -0
  291. data/lib/hecks/projector/target.rb +97 -0
  292. data/lib/hecks/projector.rb +186 -0
  293. data/lib/hecks/query_ir.rb +392 -0
  294. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  295. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  296. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  297. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  298. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  299. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  300. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  301. data/lib/hecks/query_specification/common/null_policy.rb +80 -0
  302. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  303. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  304. data/lib/hecks/query_specification/common/options.rb +32 -0
  305. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  306. data/lib/hecks/query_specification/common/specification.rb +10 -0
  307. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  308. data/lib/hecks/query_specification/field_path.rb +89 -0
  309. data/lib/hecks/query_specification/hop_path.rb +130 -0
  310. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  311. data/lib/hecks/query_specification.rb +14 -0
  312. data/lib/hecks/rendering.rb +48 -0
  313. data/lib/hecks/router/namespace_installer.rb +157 -0
  314. data/lib/hecks/router.rb +70 -0
  315. data/lib/hecks/runtime/caller.rb +50 -0
  316. data/lib/hecks/runtime/capability_graph.rb +44 -0
  317. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
  318. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
  319. data/lib/hecks/runtime/command_interpreter.rb +413 -0
  320. data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
  321. data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
  322. data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
  323. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  324. data/lib/hecks/runtime/command_rules/references.rb +181 -0
  325. data/lib/hecks/runtime/command_rules.rb +28 -0
  326. data/lib/hecks/runtime/dependency_planning.rb +231 -0
  327. data/lib/hecks/runtime/dispatcher.rb +286 -0
  328. data/lib/hecks/runtime/entity_element.rb +253 -0
  329. data/lib/hecks/runtime/entity_interpreter.rb +228 -0
  330. data/lib/hecks/runtime/era_check.rb +136 -0
  331. data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
  332. data/lib/hecks/runtime/era_guard.rb +193 -0
  333. data/lib/hecks/runtime/era_tamper.rb +61 -0
  334. data/lib/hecks/runtime/errors.rb +100 -0
  335. data/lib/hecks/runtime/event.rb +51 -0
  336. data/lib/hecks/runtime/identity.rb +128 -0
  337. data/lib/hecks/runtime/instance.rb +120 -0
  338. data/lib/hecks/runtime/interpreting.rb +69 -0
  339. data/lib/hecks/runtime/loader.rb +143 -0
  340. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  341. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  342. data/lib/hecks/runtime/query_interpreter.rb +254 -0
  343. data/lib/hecks/runtime/reaction_invocation.rb +245 -0
  344. data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
  345. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  346. data/lib/hecks/runtime/reference_hop.rb +99 -0
  347. data/lib/hecks/runtime/refusal_wording.rb +124 -0
  348. data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
  349. data/lib/hecks/runtime/registry/verification.rb +158 -0
  350. data/lib/hecks/runtime/registry.rb +218 -0
  351. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  352. data/lib/hecks/runtime/routing.rb +96 -0
  353. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  354. data/lib/hecks/runtime/saga_interpreter.rb +301 -0
  355. data/lib/hecks/runtime/storage_shape.rb +120 -0
  356. data/lib/hecks/runtime/tenant_check.rb +84 -0
  357. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  358. data/lib/hecks/runtime/value/admission.rb +112 -0
  359. data/lib/hecks/runtime/value/coercion.rb +500 -0
  360. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  361. data/lib/hecks/runtime/value.rb +125 -0
  362. data/lib/hecks/runtime.rb +95 -0
  363. data/lib/hecks/translation/audit/approval_digest.rb +31 -0
  364. data/lib/hecks/translation/audit/layer_one.rb +37 -0
  365. data/lib/hecks/translation/audit/layer_two.rb +77 -0
  366. data/lib/hecks/translation/audit/unfed_report.rb +42 -0
  367. data/lib/hecks/translation/audit.rb +70 -0
  368. data/lib/hecks/translation/reattest.rb +72 -0
  369. data/lib/hecks/translation/rule_compiler.rb +120 -0
  370. data/lib/hecks/translation/scaffold/differ.rb +183 -0
  371. data/lib/hecks/translation/scaffold/renderer.rb +41 -0
  372. data/lib/hecks/translation/scaffold/writer.rb +28 -0
  373. data/lib/hecks/translation/scaffold.rb +30 -0
  374. data/lib/hecks/translation.rb +11 -0
  375. data/lib/hecks/version.rb +16 -0
  376. data/lib/hecks/vocabulary.rb +213 -0
  377. data/lib/hecks.rb +118 -6
  378. metadata +401 -222
  379. data/bin/hecks +0 -7
  380. data/bin/hecks-package +0 -65
  381. data/bin/hecks_console +0 -12
  382. data/bin/hecks_serverless +0 -6
  383. data/lib/cli/build.rb +0 -14
  384. data/lib/cli/command_runner.rb +0 -28
  385. data/lib/cli/console.rb +0 -10
  386. data/lib/cli/generate.rb +0 -37
  387. data/lib/cli/hecks-cli.rb +0 -27
  388. data/lib/cli/test.rb +0 -57
  389. data/lib/console/commands.rb +0 -8
  390. data/lib/console/hecks-console.rb +0 -1
  391. data/lib/packager/README.md +0 -0
  392. data/lib/packager/app_runner.rb +0 -21
  393. data/lib/packager/args.rb +0 -26
  394. data/lib/packager/compatibility/fixnum.rb +0 -6
  395. data/lib/packager/hecks.rb +0 -39
  396. data/lib/packager/query_runner.rb +0 -21
  397. data/lib/packager/resources/Dockerfile +0 -11
  398. data/lib/packager/resources/app_binary +0 -7
  399. data/lib/packager/resources/bundle_config +0 -3
  400. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  401. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  402. data/lib/serverless/Domain +0 -32
  403. data/lib/serverless/cli.rb +0 -75
  404. data/lib/serverless/resources/command_name.js +0 -5
  405. data/lib/serverless/resources/environment.js +0 -7
  406. data/lib/serverless/resources/handler.js.tt +0 -28
  407. data/lib/serverless/resources/run_binary.js +0 -22
  408. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,92 @@
1
+ module Hecks
2
+ module Bluebook
3
+ # One field crossing a value-object boundary: a scalar becoming a VO
4
+ # member, a VO member becoming a scalar, or a rename across the move.
5
+ # Paths are dotted ("price.cents") for a VO member, bare otherwise.
6
+ TranslationMove = Struct.new(:from, :to)
7
+
8
+ # One field whose VALUE, not just its name or position, changed —
9
+ # an old value has nothing in common with a new one, so the only
10
+ # honest way to bridge it is a declared, exhaustive lookup table.
11
+ # Paths follow `TranslationMove`'s convention (dotted reaches a VO
12
+ # member); `values` maps every old-era value that can appear to its
13
+ # new-era replacement.
14
+ TranslationConvert = Struct.new(:from, :to, :values)
15
+
16
+ # A value object's or entity's own TYPE NAME changed with its member
17
+ # structure unchanged — the one drift `rename`/`move` cannot express,
18
+ # because the attribute kept its name and only the type it points at
19
+ # was renamed. Mirrors `was:` one level deeper.
20
+ TranslationRetype = Struct.new(:from, :to)
21
+
22
+ # A computed transform — rescale, reformat, split, merge — whose only
23
+ # implementation is the SQL expression itself, evaluated exclusively
24
+ # inside the compiled Postgres head. No in-process reference exists
25
+ # to check it against; any aggregate carrying one refuses to boot on
26
+ # every other adapter.
27
+ TranslationCompute = Struct.new(:from, :to, :sql)
28
+
29
+ # The aggregate's own IDENTITY changed what it's computed from — not
30
+ # a field crossing a boundary (that's `move`), a value objects's type
31
+ # name (`retype`), or a value transform (`compute`): the record's own
32
+ # key. No `from:`/`to:` path, unlike every other rule here, because
33
+ # nothing is being consumed from or moved into `state` — the state
34
+ # a record already holds is untouched; only what identifies it is
35
+ # recomputed. Same SQL-only, Postgres-only, no-in-process-reference
36
+ # shape `compute` already has, and for the same reason: there is
37
+ # nothing to check this against outside the compiled head.
38
+ TranslationRekey = Struct.new(:sql)
39
+
40
+ # A newly added, required attribute with no source in old data at
41
+ # all — not a rename, move, or convert, all of which need a FROM
42
+ # path in the old shape. `default` is the value an existing record
43
+ # reads until the next command against it writes a real one; unlike
44
+ # `compute`, this is adapter-agnostic — applied in-process by
45
+ # `Lineage#translate`, the same as rename/move/drop, because there is
46
+ # nothing to compute, only a value to declare.
47
+ TranslationBackfill = Struct.new(:name, :default)
48
+
49
+ # One aggregate's part of a translation: which attributes were
50
+ # renamed, moved, converted, or deliberately dropped on the way from
51
+ # the old era to the new one. `drops` exists so data loss is a
52
+ # declared decision, not a field that quietly stopped being
53
+ # explained.
54
+ class TranslationAggregate
55
+ attr_reader :name, :was, :renames, :moves, :converts, :drops, :retypes, :computes, :rekeys, :backfills
56
+
57
+ def initialize(name:, was: nil, renames: {}, moves: [], converts: [], drops: [], retypes: [],
58
+ computes: [], rekeys: [], backfills: [])
59
+ @name = name.to_s
60
+ @was = was&.to_s
61
+ @renames = renames
62
+ @moves = moves
63
+ @converts = converts
64
+ @drops = drops
65
+ @retypes = retypes
66
+ @computes = computes
67
+ @rekeys = rekeys
68
+ @backfills = backfills
69
+ end
70
+ end
71
+
72
+ # A declared map from one era of a domain's storage shape to the
73
+ # next. Renames and moves — the smallest slice that proves the
74
+ # concept: old records are translated when they are replayed, never
75
+ # rewritten. `retired` acknowledges aggregates that are gone
76
+ # outright — not renamed — so their disappearance is a decision,
77
+ # not an accident.
78
+ class Translation
79
+ attr_reader :domain, :from, :to, :aggregates, :retired
80
+
81
+ def initialize(domain:, from:, to:, aggregates: [], retired: [])
82
+ @domain = domain.to_s
83
+ @from = from
84
+ @to = to
85
+ @aggregates = aggregates
86
+ @retired = retired
87
+ end
88
+
89
+ def for_aggregate(name) = @aggregates.find { |aggregate| aggregate.name == name.to_s }
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,58 @@
1
+ require_relative "behaviour/value_object"
2
+
3
+ module Hecks
4
+ module Bluebook
5
+ Invariant = Struct.new(:description, :canonical, :predicate, keyword_init: true)
6
+
7
+ # A value object — a DECLARATION HOLDER, never instantiated.
8
+ #
9
+ # `ValueObjectBuilder` returns an anonymous subclass whose singleton
10
+ # carries the declaration : attributes, invariants, members. Nothing ever
11
+ # calls `.new` on one — coercion builds `Runtime::Value` wrappers, and
12
+ # invariants run as canonical text through the Evaluator. The runtime
13
+ # reaches a shape through `aggregate.value_object(name)`, the IR's own
14
+ # finder ; there is no constant nesting and no second index.
15
+ #
16
+ # `to_h` does not move. It is a byte-for-byte contract pinned by the
17
+ # golden fixtures, so it keeps spelling the short declared name, which
18
+ # is what `hecks_name` carries. DECLARATIONS IN THE GRAPH, STRINGS IN THE
19
+ # EXPORT.
20
+ class ValueObject
21
+ extend Construct
22
+ # EXTENDED, not included — this construct is a class, so its
23
+ # emission is a class method. See Hecks::IR's own note on
24
+ # the two shapes.
25
+ extend Hecks::IR
26
+ extend Behaviour::ValueObject
27
+
28
+ emits_ir(
29
+ name: :hecks_name,
30
+ attributes: many(:attributes),
31
+ invariants: -> { invariants.map { |rule| { description: rule.description, canonical: rule.canonical } } },
32
+ closed_set: :closed_set?,
33
+ members: -> { members.map { |member| member.map { |field, value| [field.to_s, value.to_s] } } }
34
+ )
35
+
36
+ class << self
37
+ attr_reader :attributes, :invariants, :members
38
+
39
+ # One declared shape — a subclass rather than an instance, so the thing
40
+ # the bluebook declares and the thing Ruby holds are one object.
41
+ def declare(name:, attributes: [], invariants: [], members: [], closed_set: !members.empty?)
42
+ shape = Class.new(self)
43
+ shape.hecks_name = name.to_s
44
+ shape.absorb(attributes: attributes, invariants: invariants,
45
+ members: members, closed_set: closed_set)
46
+ shape
47
+ end
48
+
49
+ def absorb(attributes:, invariants:, members:, closed_set:)
50
+ @attributes = attributes
51
+ @invariants = invariants
52
+ @members = members
53
+ @closed_set = closed_set
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,74 @@
1
+ # Hecks::Bluebook
2
+ #
3
+ # Everything a .bluebook file becomes on its way to running: the expression
4
+ # language (expression/), the model itself (the chapter class plus
5
+ # everything it declares), the assembly that turns
6
+ # declarations back into that graph (assembly.rb + assembly/), the authoring
7
+ # DSL (dsl/), and the meta-validator that judges a chapter against the
8
+ # language's own grammar (meta_validator.rb + meta_validator/).
9
+ #
10
+ # The require order below preserves the boot order the flat list in
11
+ # lib/hecks.rb used to spell: expression and IR first (pure
12
+ # declarations), assembly's collaborators before its face, the DSL before
13
+ # the meta-validator that its builders call at build time.
14
+
15
+ module Hecks
16
+ # Declared as a CLASS, not a module — `Hecks::Bluebook` IS a
17
+ # chapter (bluebook/chapter.rb carries its body). Everything a chapter
18
+ # declares nests under it, as do the ways to build one (`DSL`) and to
19
+ # judge one (`MetaValidator`). Reopening this anywhere must say `class`.
20
+ module Bluebook
21
+ end
22
+ end
23
+
24
+ # What the chapter's own files lean on at class-body level (`extend
25
+ # Construct`, the assembly's QuerySpecification marks) — required here
26
+ # because those files' contents are frozen and cannot say so themselves.
27
+ require_relative "construct"
28
+ require_relative "literal"
29
+ require_relative "query_specification"
30
+
31
+ require_relative "bluebook/expression"
32
+ # THE MODEL ITSELF — the chapter class's own body first, then everything
33
+ # a chapter declares. Order matters only for reading: each is a bag of
34
+ # declarations with no load-time cross-references.
35
+ require_relative "bluebook/chapter"
36
+ require_relative "bluebook/reference"
37
+ require_relative "bluebook/attribute"
38
+ require_relative "bluebook/value_object"
39
+ require_relative "bluebook/command"
40
+ require_relative "bluebook/lifecycle"
41
+ require_relative "bluebook/query"
42
+ require_relative "bluebook/read_model"
43
+ require_relative "bluebook/entity"
44
+ require_relative "bluebook/domain_port"
45
+ require_relative "bluebook/policy"
46
+ require_relative "bluebook/process_manager"
47
+ require_relative "bluebook/aggregate"
48
+ require_relative "bluebook/hexagon"
49
+ require_relative "bluebook/translation"
50
+
51
+ require_relative "bluebook/assembly/contract"
52
+ require_relative "bluebook/assembly/contracts"
53
+ require_relative "bluebook/assembly/specializer"
54
+ require_relative "bluebook/assembly/build"
55
+ require_relative "bluebook/assembly/marks"
56
+ require_relative "bluebook/assembly/aggregate_assembly"
57
+ require_relative "bluebook/assembly"
58
+
59
+ require_relative "bluebook/project_loader"
60
+
61
+ require_relative "bluebook/dsl"
62
+ require_relative "bluebook/pattern_subset"
63
+
64
+ require_relative "bluebook/meta_validator"
65
+ require_relative "bluebook/meta_validator/plan"
66
+ require_relative "bluebook/meta_validator/readings"
67
+ require_relative "bluebook/meta_validator/judge"
68
+ require_relative "bluebook/meta_validator/shapes"
69
+ require_relative "bluebook/meta_validator/reconstruction"
70
+ require_relative "bluebook/meta_validator/world_judge"
71
+ require_relative "bluebook/meta_validator/port_judge"
72
+ require_relative "bluebook/meta_validator/adapter_judge"
73
+ require_relative "bluebook/meta_validator/translation_judge"
74
+ require_relative "bluebook/meta_validator/syntax_boot"
@@ -0,0 +1,342 @@
1
+ require_relative "bluebook/meta_validator"
2
+
3
+ module Hecks
4
+ # SHARED MACHINERY for a codemod that migrates real `.bluebook` source
5
+ # once a DSL builder change makes some previously-required declaration
6
+ # optional/redundant — pulled out of `bin/codemod_implicit_append_fields`
7
+ # (the first one built), which needed three real, hard-won fixes before
8
+ # it could be trusted: a process-lifetime AST cache with no
9
+ # invalidation, a batch-revert granularity that let one unsafe
10
+ # candidate sink every other safe one sharing its boot, and (in the
11
+ # SPINE the codemod migrates FOR, not here) an append-at-end insertion
12
+ # that only round-tripped correctly for whichever field happened to be
13
+ # last. None of those are guessable in advance; they only surface by
14
+ # actually running a real edit against real self-hosted code. This
15
+ # module is that lesson, kept — the NEXT codemod plugs in two rule-
16
+ # specific procs (`find_candidates`, `apply_candidate`) and inherits
17
+ # the boot/safety-net machinery rather than rediscovering it.
18
+ #
19
+ # A CODEMOD IS NOT PATTERN-MATCHING ALONE. Deciding "is this line safe
20
+ # to delete" means knowing what the RUNTIME would resolve it to — so
21
+ # every codemod built on this module follows the same three steps:
22
+ # 1. Boot the real domain (or the self-hosted meta-domain) and read
23
+ # its IR to find CANDIDATES — provided by the caller's own
24
+ # `find_candidates`, since the actual redundancy rule is specific
25
+ # to whichever spine change this migration serves.
26
+ # 2. Locate and remove each candidate's own source text — the
27
+ # caller's own `apply_candidate`.
28
+ # 3. Re-boot from the edited text and diff the full IR export
29
+ # against the pre-edit export. Byte-identical -> keep. Anything
30
+ # else (including a RAISED exception — the self-hosted
31
+ # meta-domain DISPATCHES itself into being, S14, so a bad edit
32
+ # can surface as a runtime refusal, not just a differing export)
33
+ # -> revert and report SKIPPED, never silently guessed past.
34
+ #
35
+ # DESIGN-TIME CHECKLIST for the SPINE change a future codemod migrates
36
+ # corpus text for — both items below are real bugs THIS module's own
37
+ # first use found, not hypothetical:
38
+ # - Does the resolved value get inserted into an ORDER-SENSITIVE
39
+ # list (the exported IR is array-order-sensitive throughout)? If
40
+ # so, the spine's own insertion must preserve the ORIGINAL
41
+ # position, not just append — an append-at-end insertion only
42
+ # round-trips correctly for a value that already happened to be
43
+ # last.
44
+ # - Does resolution depend on ANOTHER construct already being
45
+ # declared (the aggregate/entity a `sets`/`append:` field resolves
46
+ # against)? If a creator command can be declared BEFORE the
47
+ # construct it creates (real, live: `command "Handler"` before
48
+ # `entity "Handler"`, one file down), one-pass resolution
49
+ # genuinely cannot see it yet — not a codemod bug, a structural
50
+ # limit worth naming rather than working around.
51
+ module Codemod
52
+ ROOT = File.expand_path("../..", __dir__)
53
+
54
+ EXAMPLE_ROOTS = Dir.glob(File.join(ROOT, "examples", "*")).select { |p| File.directory?(p) }.sort
55
+ META_FILES = (Dir.glob(File.join(ROOT, "lib/hecks/grammar/*.bluebook")) +
56
+ Dir.glob(File.join(ROOT, "lib/hecks/framework/bluebook/*.bluebook")) +
57
+ Dir.glob(File.join(ROOT, "lib/hecks/language/bluebook/**/*.bluebook"))).sort
58
+
59
+ # THE SAME LIGHTWEIGHT PATH `spec/spec_helper.rb`'s own
60
+ # `boot_in_memory` uses — `Hecks.with_registry` satisfies
61
+ # `Hecks.bluebook`'s own `collect`'s "loaded outside a boot" check
62
+ # without `Hecks.boot`'s full era-check/adapter-wiring path, which
63
+ # needs a LIVE Postgres connection for `compliance` and would make
64
+ # every codemod depend on a database it has no reason to touch — a
65
+ # codemod only ever reads a chapter's own declared IR, never a
66
+ # stored record.
67
+ PERSISTENCE_PORT = File.join(ROOT, "lib/hecks/ports/persistence.port")
68
+ EXTRACTION_PORT = File.join(ROOT, "lib/hecks/ports/extraction.port")
69
+ MEMORY_ADAPTER = File.join(ROOT, "lib/hecks/adapters/driven/memory.adapter")
70
+ PRISM_ADAPTER = File.join(ROOT, "lib/hecks/adapters/driven/prism.adapter")
71
+
72
+ def self.export_json(registry) = Hecks::Projector::Exporter.json(registry)
73
+
74
+ # `Hecks::Adapters::Prism` caches a file's parsed AST for the
75
+ # life of the PROCESS, keyed by path — fine for every existing
76
+ # caller (a file loads once per process: one `bin/ir` run, one
77
+ # rspec worker), but a codemod legitimately reloads the SAME path
78
+ # after editing it, and a stale cached tree reports a
79
+ # `given`/`ensures` block at its OLD line number, which no longer
80
+ # matches the freshly re-executed file's own `block.source_location`
81
+ # — surfacing as "did not survive extraction" on a perfectly valid
82
+ # file. `Prism.forget` is the real invalidation API this module's
83
+ # own first use motivated (found here, fixed at the source rather
84
+ # than left as a private `TREES.clear` poke from outside).
85
+ def self.load_bluebook(path)
86
+ paths = if path.is_a?(Array)
87
+ path
88
+ elsif File.directory?(path)
89
+ Dir.glob(File.join(path, "*.bluebook"))
90
+ else
91
+ [path]
92
+ end
93
+ paths.each { |file| Hecks::Adapters::Prism.forget(file) }
94
+
95
+ registry = Hecks::Runtime::Registry.new
96
+ Hecks.with_registry(registry) do
97
+ Kernel.load(PERSISTENCE_PORT)
98
+ Kernel.load(EXTRACTION_PORT)
99
+ Kernel.load(MEMORY_ADAPTER)
100
+ Kernel.load(PRISM_ADAPTER)
101
+ Hecks::Bluebook::MetaValidator.defer { paths.each { |file| Kernel.load(file) } }
102
+ Hecks::Bluebook::MetaValidator.judge_deferred!(registry)
103
+ end
104
+ registry
105
+ end
106
+
107
+ # `forget_all`, not a single `forget` — the meta-domain is NINE
108
+ # files (`MetaValidator::GRAMMAR_FILES`) merged into one registry,
109
+ # and a caller here (the codemod runner) doesn't generally know in
110
+ # advance which ONE it just edited.
111
+ def self.boot_meta
112
+ Hecks::Adapters::Prism.forget_all
113
+ Hecks::Bluebook::MetaValidator.instance_variable_set(:@grammar_registry, nil)
114
+ export_json(Hecks::Bluebook::MetaValidator.grammar_registry)
115
+ end
116
+
117
+ def self.meta_registry
118
+ Hecks::Adapters::Prism.forget_all
119
+ Hecks::Bluebook::MetaValidator.instance_variable_set(:@grammar_registry, nil)
120
+ Hecks::Bluebook::MetaValidator.grammar_registry
121
+ end
122
+
123
+ # Walks every aggregate (and every nested entity, recursively)
124
+ # across every chapter in a booted registry, yielding [owning
125
+ # construct, command] pairs — `construct` is whichever
126
+ # Aggregate/Entity actually OWNS the command, the same distinction
127
+ # `AggregateBuilder#command` vs `EntityBuilder#command` already
128
+ # draws. Generic enough for any rule that needs to walk real
129
+ # commands, not specific to the attribute-redundancy rule.
130
+ def self.each_command(registry)
131
+ registry.bluebooks.each_value do |chapter|
132
+ chapter.aggregates.each do |aggregate|
133
+ walk = lambda do |construct|
134
+ construct.commands.each { |command| yield construct, command }
135
+ construct.entities.each(&walk) if construct.respond_to?(:entities)
136
+ end
137
+ walk.call(aggregate)
138
+ end
139
+ end
140
+ end
141
+
142
+ def self.owner_attribute(construct, name)
143
+ construct.attributes.find { |attr| attr.name.to_s == name.to_s }
144
+ end
145
+
146
+ # A LIST attribute's own element construct — the value object or
147
+ # entity `list_of(...)` names, resolved by `hecks_name` the same way
148
+ # `AttributeCollector#resolve_identity_field!` already does. Shared
149
+ # because "what does this list actually hold" is a question any
150
+ # append-shaped rule needs answered, not just this one.
151
+ def self.element_construct_for(construct, list_field)
152
+ list_attr = owner_attribute(construct, list_field)
153
+ return nil unless list_attr&.list?
154
+
155
+ pool = construct.respond_to?(:value_objects) ? construct.value_objects.dup : []
156
+ pool.concat(construct.entities) if construct.respond_to?(:entities)
157
+ pool.find { |c| c.hecks_name.to_s == list_attr.type.to_s }
158
+ end
159
+
160
+ # Either a raised exception OR a differing export counts as unsafe
161
+ # — see the module header on why the meta-domain specifically can
162
+ # raise. Returns [value_or_nil, error_message_or_nil].
163
+ def self.safely
164
+ [yield, nil]
165
+ rescue StandardError => e
166
+ [nil, "#{e.class}: #{e.message}"]
167
+ end
168
+
169
+ # THE REUSABLE RUNNER — every real bug fix this module carries lives
170
+ # here, not in a caller's own script. A caller supplies:
171
+ #
172
+ # find_candidates: ->(registry) { [...] }
173
+ # Given a booted registry, return every CANDIDATE this rule
174
+ # could migrate. A candidate is whatever shape the caller wants
175
+ # — `apply_candidate` is the only other thing that reads it.
176
+ #
177
+ # apply_candidate: ->(text, candidate) { [new_text, changed_bool] }
178
+ # Given one file's current text and one candidate, return the
179
+ # edited text and whether a match was actually found/removed —
180
+ # `false` (text unchanged) when the candidate doesn't apply to
181
+ # THIS file, which is how the meta-domain's multi-file search
182
+ # below finds the right one without the caller needing to know
183
+ # which file a candidate lives in ahead of time.
184
+ #
185
+ # label: ->(candidate) { "..." }
186
+ # One-line description for the results report.
187
+ class Runner
188
+ def initialize(find_candidates:, apply_candidate:, label:)
189
+ @find_candidates = find_candidates
190
+ @apply_candidate = apply_candidate
191
+ @label = label
192
+ end
193
+
194
+ def run(dry_run: false)
195
+ results = { applied: [], skipped: [], clean: [] }
196
+ run_example_domains(results, dry_run)
197
+ run_meta_domain(results, dry_run)
198
+ results
199
+ end
200
+
201
+ def report(results, dry_run:)
202
+ puts "== results (#{dry_run ? 'DRY RUN — nothing written' : 'applied'}) =="
203
+ puts "clean (no candidates): #{results[:clean].join(', ')}" unless results[:clean].empty?
204
+ results[:applied].each { |r| puts "APPLIED #{r[:file]}: #{r[:candidates].join(', ')}" }
205
+ results[:skipped].each { |r| puts "SKIPPED #{r[:file]} (#{r[:reason]}): #{Array(r[:candidates]).join(', ')}" }
206
+ end
207
+
208
+ private
209
+
210
+ def apply_many(text, candidates)
211
+ applied = []
212
+ candidates.each do |c|
213
+ text, changed = @apply_candidate.call(text, c)
214
+ applied << c if changed
215
+ end
216
+ [text, applied]
217
+ end
218
+
219
+ # The per-candidate write/verify/revert sequence below is one
220
+ # coherent unit (see the comment ahead of the candidates.each
221
+ # loop for why it can't batch) — splitting it across methods just
222
+ # to satisfy the line count would scatter state four ways for no
223
+ # readability gain.
224
+ # rubocop:disable-next Metrics/BlockLength
225
+ def run_example_domains(results, dry_run)
226
+ Codemod::EXAMPLE_ROOTS.each do |domain_dir|
227
+ bluebook_files = Dir.glob(File.join(domain_dir, "bluebook", "*.bluebook"))
228
+ next if bluebook_files.empty?
229
+
230
+ before_json = Codemod.export_json(Codemod.load_bluebook(bluebook_files))
231
+ candidates = @find_candidates.call(Codemod.load_bluebook(bluebook_files))
232
+
233
+ if candidates.empty?
234
+ results[:clean] << domain_dir
235
+ next
236
+ end
237
+
238
+ live = bluebook_files.to_h { |file| [file, File.read(file)] }
239
+ applied_by_file = Hash.new { |hash, file| hash[file] = [] }
240
+
241
+ candidates.each do |candidate|
242
+ target_file = bluebook_files.find { |file| apply_many(live[file], [candidate]).last.any? }
243
+ unless target_file
244
+ results[:skipped] << { file: domain_dir, reason: "no matching source line", candidates: [@label.call(candidate)] }
245
+ next
246
+ end
247
+
248
+ original_text = live[target_file]
249
+ text, = apply_many(original_text, [candidate])
250
+ live[target_file] = text
251
+ File.write(target_file, text)
252
+ after_json, error = Codemod.safely do
253
+ Codemod.export_json(Codemod.load_bluebook(bluebook_files))
254
+ end
255
+
256
+ if after_json == before_json && !dry_run
257
+ applied_by_file[target_file] << candidate
258
+ elsif after_json == before_json
259
+ live[target_file] = original_text
260
+ File.write(target_file, original_text)
261
+ Codemod.safely { Codemod.load_bluebook(bluebook_files) }
262
+ applied_by_file[target_file] << candidate
263
+ else
264
+ live[target_file] = original_text
265
+ File.write(target_file, original_text)
266
+ Codemod.safely { Codemod.load_bluebook(bluebook_files) }
267
+ reason = error ? "reboot raised after edit (#{error}) — reverted" : "IR changed after edit — reverted"
268
+ results[:skipped] << { file: target_file, reason: reason, candidates: [@label.call(candidate)] }
269
+ end
270
+ end
271
+
272
+ applied_by_file.each do |file, applied|
273
+ results[:applied] << { file: file, candidates: applied.map(&@label) }
274
+ end
275
+ end
276
+ end
277
+
278
+ # PER-CANDIDATE, not one batched write-then-verify — the
279
+ # meta-domain is ONE shared registry (SyntaxBoot DISPATCHES it
280
+ # into itself, S14), so a single unsafe candidate among many would
281
+ # otherwise sink every OTHER, genuinely safe candidate in the same
282
+ # run: this module's first real run found exactly that (25
283
+ # candidates, one dispatch-time break, all 25 reverted as a batch
284
+ # before this per-candidate loop existed). `before_meta` stays the
285
+ # fixed reference throughout — a truly safe candidate never
286
+ # changes the IR, so the running state's own export should always
287
+ # still equal the pristine original after each kept edit, by
288
+ # definition, no moving target needed.
289
+ def run_meta_domain(results, dry_run)
290
+ before_meta = Codemod.boot_meta
291
+ meta_candidates = @find_candidates.call(Codemod.meta_registry)
292
+
293
+ if meta_candidates.empty?
294
+ results[:clean] << "meta-domain"
295
+ return
296
+ end
297
+
298
+ live_meta = Codemod::META_FILES.to_h { |f| [f, File.read(f)] }
299
+ applied_by_file = Hash.new { |h, k| h[k] = [] }
300
+
301
+ meta_candidates.each do |candidate|
302
+ target_file = Codemod::META_FILES.find { |f| apply_many(live_meta[f], [candidate]).last.any? }
303
+ unless target_file
304
+ results[:skipped] << { file: "meta-domain", reason: "no matching source line", candidates: [@label.call(candidate)] }
305
+ next
306
+ end
307
+
308
+ text, = apply_many(live_meta[target_file], [candidate])
309
+ original_text = live_meta[target_file]
310
+ live_meta[target_file] = text
311
+ Codemod::META_FILES.each { |f| File.write(f, live_meta[f]) }
312
+
313
+ # DRY RUN STILL VERIFIES — see run_example_domains' own
314
+ # comment; the edit is always written and rebooted for real,
315
+ # then always reverted afterward when dry-run (whether or not
316
+ # it was safe) so the next candidate is judged against the
317
+ # true original state, never an accumulated hypothetical.
318
+ after_meta, error = Codemod.safely { Codemod.boot_meta }
319
+
320
+ if after_meta == before_meta && !dry_run
321
+ applied_by_file[target_file] << candidate
322
+ elsif after_meta == before_meta # dry-run, safe — revert, but count as applied
323
+ live_meta[target_file] = original_text
324
+ File.write(target_file, original_text)
325
+ Codemod.safely { Codemod.boot_meta }
326
+ applied_by_file[target_file] << candidate
327
+ else
328
+ live_meta[target_file] = original_text
329
+ File.write(target_file, original_text)
330
+ Codemod.safely { Codemod.boot_meta } # restore cache/memoized state to the reverted text
331
+ reason = error ? "reboot raised (#{error})" : "IR changed"
332
+ results[:skipped] << { file: "meta-domain", reason: reason, candidates: [@label.call(candidate)] }
333
+ end
334
+ end
335
+
336
+ applied_by_file.each do |file, candidates|
337
+ results[:applied] << { file: file, candidates: candidates.map(&@label) }
338
+ end
339
+ end
340
+ end
341
+ end
342
+ end
@@ -0,0 +1,71 @@
1
+ module Hecks
2
+ # The invisible field a built construct carries.
3
+ #
4
+ # A construct is a RECORD WITH AN OWNER CHAIN — the chapter (Bluebook)
5
+ # owns its aggregates, an aggregate owns everything declared on it — and the
6
+ # bluebook identity is carried in its own field, under a `hecks_` prefix that
7
+ # no domain attribute can collide with. Invisible means exactly that: not an
8
+ # attribute, not a key in `to_h`, not a reader on instances. Framework
9
+ # metadata about the construct, not part of the domain it describes.
10
+ #
11
+ # The identity is COMPUTED by walking owners rather than stamped, so nothing
12
+ # has to be re-stamped when a chapter is assembled after its aggregates:
13
+ #
14
+ # Pizzas the chapter — no owner
15
+ # Pizzas::Pizza an aggregate joins its chapter with ::
16
+ # Pizzas::Pizza.Price everything else joins its owner with .
17
+ #
18
+ # That spelling is not invented here. It is the id `MetaValidator::Judge`
19
+ # already mints in `#identify`, so a construct and the meta-domain's
20
+ # record OF that construct carry the same identity, and there is no
21
+ # translation table between them to be quietly wrong in.
22
+ #
23
+ # Usage:
24
+ #
25
+ # price = Class.new(ValueObject) # a declaration holder
26
+ # price.hecks_name = "Price"
27
+ # price.hecks_owner = pizza_ir # the Aggregate that declares it
28
+ # price.hecks_fqn # => "Pizzas::Pizza.Price"
29
+ module Construct
30
+ # A construct asked for an identity it cannot compute.
31
+ class Unowned < StandardError; end
32
+
33
+ # What declares this one — the chapter above an aggregate, the aggregate
34
+ # above a value object. nil for a chapter, which is the top.
35
+ attr_accessor :hecks_owner
36
+
37
+ attr_writer :hecks_name
38
+
39
+ # A chapter is the only construct that legitimately has no owner. Everything
40
+ # else is DECLARED IN something, so a missing owner is an unstamped construct
41
+ # rather than a top — see hecks_fqn.
42
+ attr_writer :hecks_root
43
+
44
+ def hecks_root? = @hecks_root ? true : false
45
+
46
+ # The name as the bluebook declares it, never the constant path.
47
+ def hecks_name = @hecks_name
48
+
49
+ # How this construct joins its owner. An aggregate is a member of its
50
+ # chapter's namespace (`::`) ; everything else is declared ON its owner
51
+ # (`.`). Overridden by Aggregate, defaulted here for every other construct.
52
+ def hecks_separator = "."
53
+
54
+ # REFUSES rather than guesses. A construct with no owner and no claim to be a
55
+ # chapter has simply not been stamped yet — entity commands are in that state
56
+ # while entities are still IR objects — and answering the bare name would be a
57
+ # plausible half-truth that no test would notice. That shape of falsehood is
58
+ # what this repo keeps finding, so it goes red instead.
59
+ def hecks_fqn
60
+ return hecks_name.to_s if hecks_root?
61
+
62
+ unless hecks_owner
63
+ raise Construct::Unowned,
64
+ "#{hecks_name.inspect} cannot say what declares it, so it has no " \
65
+ "identity — it was never stamped with an owner"
66
+ end
67
+
68
+ "#{hecks_owner.hecks_fqn}#{hecks_separator}#{hecks_name}"
69
+ end
70
+ end
71
+ end