hecks 0.2.0 → 1.0.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 (419) 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 +427 -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 +101 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +179 -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 +145 -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 +88 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +438 -0
  31. data/lib/hecks/adapters/driven/postgres_era.adapter +17 -0
  32. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  33. data/lib/hecks/adapters/driven/prism.rb +80 -0
  34. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  35. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  36. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  37. data/lib/hecks/adapters/driven/sqlite/codec.rb +80 -0
  38. data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
  39. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
  40. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  41. data/lib/hecks/adapters/driven/sqlite.rb +306 -0
  42. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  43. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  44. data/lib/hecks/adapters/driven.rb +54 -0
  45. data/lib/hecks/adapters.rb +6 -0
  46. data/lib/hecks/behaviors/dsl.rb +125 -0
  47. data/lib/hecks/behaviors/expectations.rb +340 -0
  48. data/lib/hecks/behaviors/ir.rb +31 -0
  49. data/lib/hecks/behaviors/rspec.rb +42 -0
  50. data/lib/hecks/behaviors/runner.rb +96 -0
  51. data/lib/hecks/behaviors.rb +25 -0
  52. data/lib/hecks/bluebook/aggregate.rb +108 -0
  53. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  54. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  55. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  56. data/lib/hecks/bluebook/assembly/contracts.rb +438 -0
  57. data/lib/hecks/bluebook/assembly/marks.rb +228 -0
  58. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  59. data/lib/hecks/bluebook/assembly.rb +91 -0
  60. data/lib/hecks/bluebook/attribute.rb +96 -0
  61. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  62. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  63. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  64. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  65. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  66. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  67. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  68. data/lib/hecks/bluebook/behaviour/lifecycle.rb +68 -0
  69. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  70. data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -0
  71. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  73. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  75. data/lib/hecks/bluebook/chapter.rb +78 -0
  76. data/lib/hecks/bluebook/command.rb +124 -0
  77. data/lib/hecks/bluebook/domain_port.rb +102 -0
  78. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  79. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +1018 -0
  80. data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
  81. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  82. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
  83. data/lib/hecks/bluebook/dsl/command_builder.rb +714 -0
  84. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  85. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  86. data/lib/hecks/bluebook/dsl/entity_builder.rb +430 -0
  87. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  88. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  89. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  90. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  91. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  92. data/lib/hecks/bluebook/dsl/policy_builder.rb +135 -0
  93. data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
  94. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  95. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -0
  96. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  97. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  98. data/lib/hecks/bluebook/dsl/rule_reference.rb +173 -0
  99. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  100. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  101. data/lib/hecks/bluebook/dsl/word_gate.rb +228 -0
  102. data/lib/hecks/bluebook/dsl/world_builder.rb +117 -0
  103. data/lib/hecks/bluebook/dsl.rb +45 -0
  104. data/lib/hecks/bluebook/entity.rb +103 -0
  105. data/lib/hecks/bluebook/expression/canonical_form.rb +123 -0
  106. data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
  107. data/lib/hecks/bluebook/expression/projection.json +218 -0
  108. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  109. data/lib/hecks/bluebook/expression/resolver.rb +781 -0
  110. data/lib/hecks/bluebook/expression.rb +13 -0
  111. data/lib/hecks/bluebook/hexagon.rb +60 -0
  112. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  113. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  114. data/lib/hecks/bluebook/meta_validator/judge.rb +621 -0
  115. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  116. data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
  117. data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
  118. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
  119. data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -0
  120. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  121. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  122. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  123. data/lib/hecks/bluebook/meta_validator.rb +542 -0
  124. data/lib/hecks/bluebook/model_check.rb +445 -0
  125. data/lib/hecks/bluebook/pattern_subset.rb +184 -0
  126. data/lib/hecks/bluebook/policy.rb +41 -0
  127. data/lib/hecks/bluebook/process_manager.rb +128 -0
  128. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  129. data/lib/hecks/bluebook/project_loader.rb +40 -0
  130. data/lib/hecks/bluebook/project_register.rb +82 -0
  131. data/lib/hecks/bluebook/query.rb +61 -0
  132. data/lib/hecks/bluebook/read_model.rb +109 -0
  133. data/lib/hecks/bluebook/reference.rb +74 -0
  134. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  135. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  136. data/lib/hecks/bluebook/translation.rb +92 -0
  137. data/lib/hecks/bluebook/value_object.rb +66 -0
  138. data/lib/hecks/bluebook.rb +74 -0
  139. data/lib/hecks/codemod.rb +342 -0
  140. data/lib/hecks/construct.rb +71 -0
  141. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  142. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  143. data/lib/hecks/deploy/oidc.json +18 -0
  144. data/lib/hecks/doc/reference.rb +410 -0
  145. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  146. data/lib/hecks/facade/cli_door.rb +119 -0
  147. data/lib/hecks/facade/cli_runner.rb +190 -0
  148. data/lib/hecks/facade/command_request.rb +105 -0
  149. data/lib/hecks/facade/handle.rb +173 -0
  150. data/lib/hecks/facade/json_door.rb +166 -0
  151. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  152. data/lib/hecks/facade/surface/chapter.rb +107 -0
  153. data/lib/hecks/facade/surface.rb +48 -0
  154. data/lib/hecks/facade.rb +44 -0
  155. data/lib/hecks/forms/app.rb +341 -0
  156. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  157. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  158. data/lib/hecks/forms/field_renderer.rb +177 -0
  159. data/lib/hecks/forms/field_shape.rb +232 -0
  160. data/lib/hecks/forms/html.rb +84 -0
  161. data/lib/hecks/forms/index_renderer.rb +35 -0
  162. data/lib/hecks/forms/page.rb +157 -0
  163. data/lib/hecks/forms/params.rb +161 -0
  164. data/lib/hecks/forms/port_argument.rb +46 -0
  165. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  166. data/lib/hecks/forms/record_renderer.rb +119 -0
  167. data/lib/hecks/forms/record_table.rb +68 -0
  168. data/lib/hecks/forms/reference_options.rb +30 -0
  169. data/lib/hecks/forms/value_object_shape.rb +46 -0
  170. data/lib/hecks/forms.rb +54 -0
  171. data/lib/hecks/fqn.rb +94 -0
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  175. data/lib/hecks/framework/bluebook/governance.bluebook +145 -0
  176. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  177. data/lib/hecks/framework/oidc.json +39 -0
  178. data/lib/hecks/framework.rb +90 -0
  179. data/lib/hecks/freezer.rb +67 -0
  180. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +527 -0
  181. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  182. data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
  183. data/lib/hecks/fuzzing/properties.rb +1192 -0
  184. data/lib/hecks/fuzzing/replay.rb +668 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
  189. data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
  190. data/lib/hecks/fuzzing/value_generator.rb +200 -0
  191. data/lib/hecks/fuzzing.rb +12 -0
  192. data/lib/hecks/grammar/evolve.rb +324 -0
  193. data/lib/hecks/grammar/expression.bluebook +420 -0
  194. data/lib/hecks/grammar/expression_operators.json +1648 -0
  195. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  196. data/lib/hecks/grammar/oidc.json +69 -0
  197. data/lib/hecks/grammar/translation.bluebook +203 -0
  198. data/lib/hecks/grammar.rb +178 -0
  199. data/lib/hecks/ir.rb +126 -0
  200. data/lib/hecks/language/adapter.bluebook +116 -0
  201. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  202. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  203. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  204. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  205. data/lib/hecks/language/bluebook/command.bluebook +471 -0
  206. data/lib/hecks/language/bluebook/entity.bluebook +392 -0
  207. data/lib/hecks/language/bluebook/policy.bluebook +189 -0
  208. data/lib/hecks/language/bluebook/process_manager.bluebook +381 -0
  209. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  210. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  211. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  212. data/lib/hecks/language/bluebook/syntax.bluebook +448 -0
  213. data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -0
  214. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  215. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  216. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  217. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  218. data/lib/hecks/language/oidc.json +333 -0
  219. data/lib/hecks/language/port.bluebook +120 -0
  220. data/lib/hecks/language/translation/translation.bluebook +110 -0
  221. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  222. data/lib/hecks/language/world/wiring.bluebook +62 -0
  223. data/lib/hecks/language/world/world.bluebook +84 -0
  224. data/lib/hecks/literal.rb +125 -0
  225. data/lib/hecks/naming.rb +174 -0
  226. data/lib/hecks/ports/access_control.port +9 -0
  227. data/lib/hecks/ports/access_control.rb +62 -0
  228. data/lib/hecks/ports/agent/answers.rb +104 -0
  229. data/lib/hecks/ports/agent.port +8 -0
  230. data/lib/hecks/ports/agent.rb +167 -0
  231. data/lib/hecks/ports/authentication.port +6 -0
  232. data/lib/hecks/ports/authentication.rb +50 -0
  233. data/lib/hecks/ports/authorization.port +7 -0
  234. data/lib/hecks/ports/authorization.rb +62 -0
  235. data/lib/hecks/ports/clock.port +5 -0
  236. data/lib/hecks/ports/clock.rb +62 -0
  237. data/lib/hecks/ports/extraction.port +5 -0
  238. data/lib/hecks/ports/extraction.rb +37 -0
  239. data/lib/hecks/ports/identity_assignment.port +5 -0
  240. data/lib/hecks/ports/identity_assignment.rb +45 -0
  241. data/lib/hecks/ports/identity_generation.port +5 -0
  242. data/lib/hecks/ports/identity_generation.rb +49 -0
  243. data/lib/hecks/ports/identity_resolution.port +5 -0
  244. data/lib/hecks/ports/identity_resolution.rb +40 -0
  245. data/lib/hecks/ports/loading.port +4 -0
  246. data/lib/hecks/ports/loading.rb +13 -0
  247. data/lib/hecks/ports/persistence/append_only.rb +138 -0
  248. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  249. data/lib/hecks/ports/persistence/execution.rb +23 -0
  250. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  251. data/lib/hecks/ports/persistence/plugin.rb +54 -0
  252. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
  253. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
  254. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
  255. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
  256. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
  257. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
  258. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
  259. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
  260. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
  261. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
  262. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
  263. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
  264. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
  265. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
  266. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
  267. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
  268. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  269. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
  270. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
  271. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
  272. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
  273. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
  274. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
  275. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
  276. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
  277. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
  278. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
  279. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
  280. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
  281. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
  282. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
  283. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
  284. data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
  285. data/lib/hecks/ports/persistence/plugins/era.rb +48 -0
  286. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  287. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  288. data/lib/hecks/ports/persistence.port +4 -0
  289. data/lib/hecks/ports/persistence.rb +39 -0
  290. data/lib/hecks/ports/projection.port +4 -0
  291. data/lib/hecks/ports/projection.rb +96 -0
  292. data/lib/hecks/ports/query/in_memory.rb +60 -0
  293. data/lib/hecks/ports/query/ordering.rb +41 -0
  294. data/lib/hecks/ports/query.rb +36 -0
  295. data/lib/hecks/ports.rb +27 -0
  296. data/lib/hecks/projections/diagrams.rb +642 -0
  297. data/lib/hecks/projections/ir.rb +18 -0
  298. data/lib/hecks/projections/model/deviations.rb +98 -0
  299. data/lib/hecks/projections/model.rb +145 -0
  300. data/lib/hecks/projections/oidc.rb +110 -0
  301. data/lib/hecks/projections/parser_table.rb +159 -0
  302. data/lib/hecks/projections/reference.rb +38 -0
  303. data/lib/hecks/projections/shape.rb +44 -0
  304. data/lib/hecks/projections/statements.rb +110 -0
  305. data/lib/hecks/projections/vocabulary.rb +100 -0
  306. data/lib/hecks/projections.rb +32 -0
  307. data/lib/hecks/projector/cli_projector.rb +446 -0
  308. data/lib/hecks/projector/docs_projector.rb +321 -0
  309. data/lib/hecks/projector/exporter.rb +158 -0
  310. data/lib/hecks/projector/ir_projector.rb +18 -0
  311. data/lib/hecks/projector/narrate_projector.rb +243 -0
  312. data/lib/hecks/projector/target.rb +97 -0
  313. data/lib/hecks/projector.rb +186 -0
  314. data/lib/hecks/query_ir.rb +411 -0
  315. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  316. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  317. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  318. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  319. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  320. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  321. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  322. data/lib/hecks/query_specification/common/null_policy.rb +92 -0
  323. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  324. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  325. data/lib/hecks/query_specification/common/options.rb +32 -0
  326. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  327. data/lib/hecks/query_specification/common/specification.rb +10 -0
  328. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  329. data/lib/hecks/query_specification/field_path.rb +107 -0
  330. data/lib/hecks/query_specification/hop_path.rb +132 -0
  331. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  332. data/lib/hecks/query_specification.rb +14 -0
  333. data/lib/hecks/rendering.rb +48 -0
  334. data/lib/hecks/router/namespace_installer.rb +157 -0
  335. data/lib/hecks/router.rb +70 -0
  336. data/lib/hecks/runtime/aggregate_lock.rb +45 -0
  337. data/lib/hecks/runtime/boot_gates.rb +41 -0
  338. data/lib/hecks/runtime/caller.rb +66 -0
  339. data/lib/hecks/runtime/capability_graph.rb +44 -0
  340. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
  341. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
  342. data/lib/hecks/runtime/command_interpreter.rb +497 -0
  343. data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
  344. data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
  345. data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
  346. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  347. data/lib/hecks/runtime/command_rules/references.rb +189 -0
  348. data/lib/hecks/runtime/command_rules.rb +28 -0
  349. data/lib/hecks/runtime/dependency_planning.rb +245 -0
  350. data/lib/hecks/runtime/dispatcher.rb +301 -0
  351. data/lib/hecks/runtime/entity_element.rb +253 -0
  352. data/lib/hecks/runtime/entity_interpreter.rb +299 -0
  353. data/lib/hecks/runtime/errors.rb +122 -0
  354. data/lib/hecks/runtime/event.rb +51 -0
  355. data/lib/hecks/runtime/identity.rb +156 -0
  356. data/lib/hecks/runtime/instance.rb +175 -0
  357. data/lib/hecks/runtime/interpreting.rb +90 -0
  358. data/lib/hecks/runtime/loader.rb +184 -0
  359. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  360. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  361. data/lib/hecks/runtime/query_interpreter.rb +286 -0
  362. data/lib/hecks/runtime/reaction_invocation.rb +253 -0
  363. data/lib/hecks/runtime/read_model_interpreter.rb +341 -0
  364. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  365. data/lib/hecks/runtime/reference_hop.rb +99 -0
  366. data/lib/hecks/runtime/refusal_wording.rb +126 -0
  367. data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
  368. data/lib/hecks/runtime/registry/verification.rb +246 -0
  369. data/lib/hecks/runtime/registry.rb +279 -0
  370. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  371. data/lib/hecks/runtime/routing.rb +96 -0
  372. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  373. data/lib/hecks/runtime/saga_interpreter.rb +503 -0
  374. data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -0
  375. data/lib/hecks/runtime/tenant_check.rb +84 -0
  376. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  377. data/lib/hecks/runtime/value/admission.rb +130 -0
  378. data/lib/hecks/runtime/value/coercion.rb +565 -0
  379. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  380. data/lib/hecks/runtime/value.rb +125 -0
  381. data/lib/hecks/runtime.rb +107 -0
  382. data/lib/hecks/storehouse.rb +632 -0
  383. data/lib/hecks/version.rb +16 -0
  384. data/lib/hecks/vocabulary.rb +218 -0
  385. data/lib/hecks.rb +123 -6
  386. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
  387. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
  388. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
  389. metadata +412 -222
  390. data/bin/hecks +0 -7
  391. data/bin/hecks-package +0 -65
  392. data/bin/hecks_console +0 -12
  393. data/bin/hecks_serverless +0 -6
  394. data/lib/cli/build.rb +0 -14
  395. data/lib/cli/command_runner.rb +0 -28
  396. data/lib/cli/console.rb +0 -10
  397. data/lib/cli/generate.rb +0 -37
  398. data/lib/cli/hecks-cli.rb +0 -27
  399. data/lib/cli/test.rb +0 -57
  400. data/lib/console/commands.rb +0 -8
  401. data/lib/console/hecks-console.rb +0 -1
  402. data/lib/packager/README.md +0 -0
  403. data/lib/packager/app_runner.rb +0 -21
  404. data/lib/packager/args.rb +0 -26
  405. data/lib/packager/compatibility/fixnum.rb +0 -6
  406. data/lib/packager/hecks.rb +0 -39
  407. data/lib/packager/query_runner.rb +0 -21
  408. data/lib/packager/resources/Dockerfile +0 -11
  409. data/lib/packager/resources/app_binary +0 -7
  410. data/lib/packager/resources/bundle_config +0 -3
  411. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  412. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  413. data/lib/serverless/Domain +0 -32
  414. data/lib/serverless/cli.rb +0 -75
  415. data/lib/serverless/resources/command_name.js +0 -5
  416. data/lib/serverless/resources/environment.js +0 -7
  417. data/lib/serverless/resources/handler.js.tt +0 -28
  418. data/lib/serverless/resources/run_binary.js +0 -22
  419. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,173 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module DSL
4
+ # THE THREE RESOLUTION PRIMITIVES the S10 given/invariant family's
5
+ # own "declared once, referenced by name" mechanism (ADR 0025)
6
+ # reduces to, at every scope this language has grown one so far
7
+ # (a command referencing its owner or a sibling piece's entity-wide
8
+ # pool; an aggregate referencing another aggregate chapter-wide; a
9
+ # value object referencing a sibling value object on the same
10
+ # aggregate) — extracted here, once, so the NEXT scope this family
11
+ # widens to (there will be one — see docs/resolution-rules/
12
+ # chapter-given.md's own "Known limitations" for two already named)
13
+ # reuses one of these three shapes instead of a fourth hand-written
14
+ # near-duplicate resolver.
15
+ #
16
+ # NOT ONE UNIFIED ALGORITHM — a real design question this file
17
+ # answers directly: the THREE existing resolvers are not
18
+ # superficially different, they are STRUCTURALLY different (a
19
+ # multi-pool fallback CHAIN; ONE pool keyed by declaring OWNER,
20
+ # needing disambiguation; a LIVE SCAN over already-built sibling
21
+ # objects with no separate pool at all) — forcing them into one
22
+ # shape would be a real behavior change (see `#lookup`, below, for
23
+ # which construct uses which), not the pure internal refactor this
24
+ # module is. `build_rule` is the one piece that WAS genuinely
25
+ # identical across all 7 declaring methods (`given`×3,
26
+ # `invariant`×3, `ensures`×1) before this file existed — extract
27
+ # predicate source, refuse if extraction failed, build the struct.
28
+ #
29
+ # `#lookup`/`#verify_resolves_via!` read WHICH construct uses which
30
+ # primitive off the self-hosted grammar table itself
31
+ # (`Keyword#resolves_via`, `syntax.bluebook`) — not a Ruby-only
32
+ # Hash cross-checked afterward (this file's OWN earlier shape, one
33
+ # round ago) — so a real domain's own boot, not just `bundle exec
34
+ # rspec`, fails loudly the moment the table and this file's own
35
+ # hand-written resolution methods disagree.
36
+ module RuleReference
37
+ module_function
38
+
39
+ # `struct_class` is `Given` or `Invariant` (both `Struct.new(
40
+ # :description, :canonical, :predicate, keyword_init: true)` —
41
+ # `Given` lives in command.rb, `Invariant` in value_object.rb).
42
+ # `owner_name`/`word` are ONLY for the refusal message's own
43
+ # wording. `extraction_failure` is the tail of that same
44
+ # message, and stays a REQUIRED parameter rather than one
45
+ # hardcoded string on purpose — `given` ("its source could not
46
+ # be read, so no other runtime could ever evaluate it"),
47
+ # `invariant` ("it would be a rule the IR cannot carry"), and
48
+ # `ensures` ("a postcondition is carried as text, and this one
49
+ # has none") each already had their OWN exact wording before
50
+ # this method existed; unifying them into one generic sentence
51
+ # would be a real (if small) behavior change this refactor is
52
+ # not making.
53
+ def build_rule(struct_class, description, predicate, owner_name:, word:, extraction_failure:)
54
+ canonical = Ports::Extraction.canonical(predicate)
55
+
56
+ if canonical.to_s.empty?
57
+ raise Malformed,
58
+ "#{owner_name}'s #{word} #{description.inspect} did not survive " \
59
+ "extraction — #{extraction_failure}"
60
+ end
61
+
62
+ struct_class.new(description: description, canonical: canonical, predicate: predicate)
63
+ end
64
+
65
+ # PRIMITIVE 1 — an ORDERED CHAIN of flat `Hash[description] =>
66
+ # Rule` pools, first match wins. `CommandBuilder#given`'s own
67
+ # two-pool shape (its OWN owner's `named_givens`, then a sibling
68
+ # piece's entity-wide pool) is this with a 2-element chain — a
69
+ # future single-pool bare reference is the same primitive with a
70
+ # 1-element chain, not a separate "just look in one hash" method.
71
+ def resolve_hash_chain(pools, description)
72
+ pools.each { |pool| return pool[description] if pool.key?(description) }
73
+ nil
74
+ end
75
+
76
+ # PRIMITIVE 2 — ONE pool keyed BY DECLARING OWNER,
77
+ # `Hash[description][owner] => Rule` — `AggregateBuilder#given`'s
78
+ # own chapter-wide shape, the only construct so far where the
79
+ # SAME description can mean two genuinely different predicates
80
+ # (docs/implemented/resolution-rules/chapter-given.md). Returns the full
81
+ # candidates Hash (0, 1, or many entries) — deliberately NOT
82
+ # raising here, so each caller keeps its own exact refusal
83
+ # wording for "none," "ambiguous," and "declared_by: named the
84
+ # wrong owner" rather than one generic message papering over all
85
+ # three.
86
+ def resolve_owner_keyed(pool, description)
87
+ pool[description] || {}
88
+ end
89
+
90
+ # PRIMITIVE 3 — a LIVE SCAN over already-built SIBLING OBJECTS'
91
+ # own collections, not a separately-maintained pool at all —
92
+ # `ValueObjectBuilder#invariant`'s own shape: every sibling value
93
+ # object on the same aggregate has ALREADY been built by the time
94
+ # a later one references back (declaration order, the same
95
+ # constraint every scope in this family carries), so there is
96
+ # nothing to write through — just read their own already-declared
97
+ # rules directly. `reader` is the method name to call on each
98
+ # sibling (`:invariants` today; kept a parameter, not hardcoded,
99
+ # since a future sibling-scan scope might reference a different
100
+ # collection).
101
+ def resolve_sibling_scan(siblings, description, reader:)
102
+ siblings.flat_map { |sibling| sibling.public_send(reader) }
103
+ .find { |rule| rule.description == description }
104
+ end
105
+
106
+ # WHICH CONSTRUCT USES WHICH PRIMITIVE — no longer a Ruby-only
107
+ # Hash (that WAS this constant's own shape, one round ago): the
108
+ # user's own correction — "my goal is that if they read the same
109
+ # table they behave identically" — means a table only Ruby ever
110
+ # reads cannot deliver that, no matter how faithfully it is
111
+ # cross-checked afterward. `Keyword#resolves_via`/`#disambiguator`
112
+ # (self-hosted, `syntax.bluebook`) is the REAL table now — the
113
+ # SAME generated data `rust/parser/src/keywords.rs` is generated
114
+ # from (`bin/project_parser_table`). `#lookup` reads it live.
115
+ #
116
+ # THE ONE UNAVOIDABLE EXCEPTION: the meta-domain's own bootstrap
117
+ # (`MetaValidator.load_grammar_into`) dispatches `given`/
118
+ # `invariant` on ITSELF 61 times while building the very grammar
119
+ # table that would answer "how does given/Aggregate resolve" —
120
+ # `MetaValidator.grammar_registry`/`SyntaxBoot.call` are not
121
+ # ready yet, and cannot be made ready without ALREADY having
122
+ # resolved a `given` somewhere upstream. `MetaValidator.
123
+ # bootstrapping?` is the SAME guard `MetaValidator.call` (the
124
+ # judge) already uses to skip self-judging during this exact
125
+ # window — `#lookup` uses it too, falling back to
126
+ # `BOOTSTRAP_FALLBACK` (below) ONLY while it's true. Every REAL
127
+ # domain (banking, pizzas, compliance, any future one) boots
128
+ # AFTER `grammar_registry` is fully built and memoized, so reads
129
+ # the real table, every time, no exception.
130
+ BOOTSTRAP_FALLBACK = {
131
+ %w[given Aggregate] => { resolves_via: "owner_keyed", disambiguator: "declared_by" },
132
+ %w[given Entity] => { resolves_via: "owner_keyed", disambiguator: "declared_by" },
133
+ %w[given Command] => { resolves_via: "hash_chain" },
134
+ %w[invariant ValueObject] => { resolves_via: "sibling_scan" }
135
+ }.freeze
136
+
137
+ def lookup(word, context)
138
+ if MetaValidator.bootstrapping?
139
+ BOOTSTRAP_FALLBACK[[word, context]] || {}
140
+ else
141
+ row = MetaValidator::SyntaxBoot.call[:keywords]
142
+ .find { |r| r[:word] == word && r[:context] == context }
143
+ return {} unless row
144
+
145
+ { resolves_via: row[:resolves_via], disambiguator: row[:disambiguator] }
146
+ .transform_values { |value| value.to_s.empty? ? nil : value }
147
+ end
148
+ end
149
+
150
+ # A LIVE CROSS-CHECK, not a spec-only one — every REAL domain's
151
+ # own boot (not just `bundle exec rspec`) now genuinely fails
152
+ # loudly if a construct's own hand-written resolution method
153
+ # ever disagrees with what the self-hosted grammar table claims
154
+ # for it. Each of the three `reference_named_*` methods below
155
+ # calls this FIRST, naming the primitive it is ABOUT to use —
156
+ # if `syntax.bluebook`'s own `resolves_via` for this exact
157
+ # (word, context) pair ever names something else, this is a
158
+ # real drift between the language's own self-description and
159
+ # its own implementation, caught at the next boot of ANYTHING,
160
+ # not just the next `rspec` run.
161
+ def verify_resolves_via!(word, context, expected_primitive)
162
+ actual = lookup(word, context)[:resolves_via]
163
+ return if actual == expected_primitive
164
+
165
+ raise "internal: syntax.bluebook says #{word}/#{context} resolves via " \
166
+ "#{actual.inspect}, but #{word}'s own Ruby builder is about to use " \
167
+ "#{expected_primitive.inspect} — the grammar table and the " \
168
+ "implementation have drifted"
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,243 @@
1
+ require_relative "word_gate"
2
+ module Hecks
3
+ module Bluebook
4
+ module DSL
5
+ class TranslationAggregateBuilder
6
+ GRAMMAR_CONTEXT = "TranslationAggregate"
7
+
8
+ include WordGate
9
+
10
+ def initialize(name, was: nil)
11
+ raise Malformed, "an aggregate translation needs a name" if name.to_s.empty?
12
+
13
+ @name = name
14
+ @was = was
15
+ @renames = {}
16
+ @moves = []
17
+ @converts = []
18
+ @drops = []
19
+ @retypes = []
20
+ @computes = []
21
+ @rekeys = []
22
+ @backfills = []
23
+ end
24
+
25
+ # RENAMED FROM `rename`/`move`/`convert`/`retype`/`compute`/
26
+ # `rekey`/`backfill` (all seven below) — item #13's full
27
+ # metaprogrammed dispatch (slice 4c). Not bootstrap-reachable
28
+ # (translation.bluebook describes ITS OWN structure with
29
+ # aggregate/entity/attribute, never with these — they're
30
+ # words for real, user-authored `.translation` files only,
31
+ # loaded after the grammar table exists), so none need a
32
+ # BOOTSTRAP_CALLS_FALLBACK entry.
33
+ def rename_impl(old_name, to:)
34
+ raise Malformed, "a rename needs a source name" if old_name.to_s.empty?
35
+ raise Malformed, "a rename needs a destination name (to:)" if to.to_s.empty?
36
+
37
+ @renames[old_name.to_sym] = to.to_sym
38
+ end
39
+
40
+ def move_impl(old_path, to:)
41
+ raise Malformed, "a move needs a destination path (to:)" if to.to_s.empty?
42
+ raise Malformed, "a move needs a source path" if old_path.to_s.empty?
43
+
44
+ @moves << TranslationMove.new(old_path.to_s, to.to_s)
45
+ end
46
+
47
+ # A value with nothing structural in common with its replacement
48
+ # — declared as an exhaustive table, not computed, so every value
49
+ # that can appear in old data has a named destination. Paths
50
+ # follow `move`'s convention: dotted reaches a value-object member.
51
+ def convert_impl(old_path, to:, values:)
52
+ raise Malformed, "a convert needs a destination path (to:)" if to.to_s.empty?
53
+ raise Malformed, "a convert needs a source path" if old_path.to_s.empty?
54
+ raise Malformed, "a convert needs a values: table" if values.nil? || values.empty?
55
+
56
+ @converts << TranslationConvert.new(old_path.to_s, to.to_s, values)
57
+ end
58
+
59
+ # `drop` — item #13's full metaprogrammed dispatch, slice 2
60
+ # (whole-project table-unification survey): a declared,
61
+ # deliberate acknowledgment that an attribute's data does not
62
+ # survive the rename — the honest alternative to letting it
63
+ # vanish because nothing named it. A blank-guarded, kind-driven
64
+ # coerce-and-append with nothing else, now executed by
65
+ # `GenericDispatch`.
66
+
67
+ # A value object's or entity's own type name changed, member
68
+ # structure unchanged. The stored data never carries the type
69
+ # name, so nothing moves — this declares that the pair of names
70
+ # means the same shape, which is what lets the era diff accept it.
71
+ def retype_impl(old_type, to:)
72
+ raise Malformed, "a retype needs a source type name" if old_type.to_s.empty?
73
+ raise Malformed, "a retype needs a destination type name (to:)" if to.to_s.empty?
74
+
75
+ @retypes << TranslationRetype.new(old_type.to_s, to.to_s)
76
+ end
77
+
78
+ # A computed transform whose only implementation is the SQL
79
+ # expression itself — Postgres-only by construction. The scaffold
80
+ # never proposes one; a human writes it, and the audit's
81
+ # human-sampled review is its only verification.
82
+ def compute_impl(old_path, to:, sql:)
83
+ raise Malformed, "a compute needs a destination path (to:)" if to.to_s.empty?
84
+ raise Malformed, "a compute needs a source path" if old_path.to_s.empty?
85
+ raise Malformed, "a compute needs its sql: expression" if sql.to_s.empty?
86
+
87
+ @computes << TranslationCompute.new(old_path.to_s, to.to_s, sql.to_s)
88
+ end
89
+
90
+ # THE AGGREGATE'S OWN IDENTITY, changing what it's computed from —
91
+ # not a field crossing a boundary (`move`), not a value's own
92
+ # transform (`compute`): the record's key. No path arguments,
93
+ # unlike every rule above — nothing is consumed from or moved into
94
+ # `state`, only what identifies the record is recomputed. Same
95
+ # SQL-only, Postgres-only, human-reviewed-sample-is-the-only-
96
+ # verification shape `compute` already has, and for the same
97
+ # reason: there is nothing in-process to check this against.
98
+ def rekey_impl(sql:)
99
+ raise Malformed, "a rekey needs its sql: expression" if sql.to_s.empty?
100
+
101
+ @rekeys << TranslationRekey.new(sql.to_s)
102
+ end
103
+
104
+ # A NEWLY ADDED, required attribute — the addition-side sibling of
105
+ # `drop`. Nothing to rename, move, or convert FROM, since old data
106
+ # never held this field at all; `default` is what an existing
107
+ # record reads until the next command against it writes a real
108
+ # value. Adapter-agnostic, unlike `compute` — applied the same
109
+ # in-process way rename/move/drop already are
110
+ # (`Lineage#translate`), because there is nothing to compute here,
111
+ # only a value to declare. This is what
112
+ # `EraGuard.refuse_unsafe_addition!` asks for when a non-optional
113
+ # attribute with no default: could leave an existing record with
114
+ # the field genuinely absent.
115
+ def backfill_impl(name, default:)
116
+ raise Malformed, "a backfill needs a name" if name.to_s.empty?
117
+ raise Malformed, "a backfill needs a default: value" if default.nil?
118
+
119
+ @backfills << TranslationBackfill.new(name.to_sym, default)
120
+ end
121
+
122
+ # The scaffold writes this where it cannot decide; a file carrying
123
+ # one can only boot into this refusal — never a guess.
124
+ #
125
+ # RENAMED FROM `unresolved` — item #13's full metaprogrammed
126
+ # dispatch (slice 4). Builds its own message with real branching
127
+ # (empty vs. named candidates, a special :identity case), not a
128
+ # fixed string a boolean `refuses:` flag could express — reached
129
+ # through `calls:` instead, like `attribute`/`role`. NOT
130
+ # bootstrap-reachable: translation.bluebook (loaded during
131
+ # bootstrap, to describe the translation DSL itself) never
132
+ # writes `unresolved` — that word is only ever used by real,
133
+ # user-authored `.translation` files, loaded well after the
134
+ # grammar table exists — so no BOOTSTRAP_CALLS_FALLBACK entry is
135
+ # needed here (checked directly, not assumed).
136
+ def unresolved_impl(name, candidates: [])
137
+ raise Malformed, unresolved_message(name, candidates)
138
+ end
139
+
140
+ # `method_missing`/`respond_to_missing?` used to be hand-written
141
+ # here, giving a hand-typed "must be rename, move, convert, ..."
142
+ # list on every genuinely undefined call — WordGate (`include`d
143
+ # above) now answers the same question off the self-hosted
144
+ # grammar table instead, the exact "hardcoded legal-word list"
145
+ # this whole arc's item #13 exists to close. A word admitted
146
+ # elsewhere in the grammar but not in this context still gets a
147
+ # richer, table-driven refusal than the old generic one did.
148
+
149
+ def build
150
+ TranslationAggregate.new(
151
+ name: @name, was: @was, renames: @renames, moves: @moves, converts: @converts,
152
+ drops: @drops, retypes: @retypes, computes: @computes, rekeys: @rekeys, backfills: @backfills
153
+ )
154
+ end
155
+
156
+ private
157
+
158
+ def unresolved_message(name, candidates)
159
+ return identity_unresolved_message if name.to_sym == :identity
160
+
161
+ rendered = Array(candidates).map { |candidate| render_path(candidate) }
162
+ if rendered.empty?
163
+ "#{@name}'s translation leaves #{render_path(name)} unresolved (no candidate matched — " \
164
+ "consider drop, or compute on Postgres) — replace unresolved with a real rule before booting."
165
+ else
166
+ "#{@name}'s translation leaves #{render_path(name)} unresolved (candidates: #{rendered.join(', ')}) — " \
167
+ "replace unresolved with a rename, move, convert, or drop before booting."
168
+ end
169
+ end
170
+
171
+ def render_path(path)
172
+ path.to_s.include?(".") ? path.to_s.inspect : ":#{path}"
173
+ end
174
+
175
+ # THE SCAFFOLD'S OWN HINT for the one drift it can detect but never
176
+ # resolve on its own — an aggregate's `identified_by` changed. Not
177
+ # a field to rename/move/drop, so none of the ordinary hints fit;
178
+ # `coverage_check.rb#check_identity_unchanged!` is the real gate,
179
+ # this only names the one rule that gets a legitimate mint through it.
180
+ def identity_unresolved_message
181
+ "#{@name}'s translation leaves its identity unresolved — identified_by changed since the held era. " \
182
+ "Declare a rekey rule (a rename/move/drop cannot fix this) before booting."
183
+ end
184
+ end
185
+
186
+ class TranslationBuilder
187
+ GRAMMAR_CONTEXT = "Translation"
188
+
189
+ include WordGate
190
+
191
+ def initialize(domain, from:, to:)
192
+ raise Malformed, "a translation names no domain" if domain.to_s.empty?
193
+ raise Malformed, "#{domain}'s translation says nothing about its origin era (from:)" if from.to_s.empty?
194
+ raise Malformed, "#{domain}'s translation says nothing about its destination era (to:)" if to.to_s.empty?
195
+
196
+ @domain = domain
197
+ @from = from
198
+ @to = to
199
+ @aggregates = []
200
+ @retired = []
201
+ end
202
+
203
+ # RENAMED FROM `aggregate` — item #13's full metaprogrammed
204
+ # dispatch (slice 4c). Not bootstrap-reachable — this "Translation"
205
+ # -context `aggregate` (opens a TranslationAggregateBuilder) is a
206
+ # different (context, word) pair than "Bluebook"-context
207
+ # `aggregate` (the one translation.bluebook itself is described
208
+ # with), so it's never used to describe the language's own
209
+ # translation chapter.
210
+ def aggregate_impl(name, was: nil, &block)
211
+ builder = TranslationAggregateBuilder.new(name, was: was)
212
+ builder.instance_eval(&block) if block
213
+ @aggregates << builder.build
214
+ end
215
+
216
+ # `retired` — item #13's full metaprogrammed dispatch, slice 2
217
+ # (whole-project table-unification survey): an aggregate that is
218
+ # gone outright — not renamed. The deliberate alternative to a
219
+ # bogus `was:` claim on an unrelated aggregate. Same shape
220
+ # `TranslationAggregateBuilder#drop` is, now executed by
221
+ # `GenericDispatch`.
222
+
223
+ # `method_missing`/`respond_to_missing?` — same removal as
224
+ # TranslationAggregateBuilder's own, one level up: WordGate
225
+ # (`include`d above) answers off the self-hosted grammar table
226
+ # now instead of a hand-typed "it declares aggregate blocks and
227
+ # retired aggregates" message.
228
+
229
+ def build
230
+ MetaValidator.call_translation(
231
+ Translation.new(domain: @domain, from: @from, to: @to, aggregates: @aggregates, retired: @retired)
232
+ )
233
+ end
234
+
235
+ def self.build(domain, from:, to:, &block)
236
+ builder = new(domain, from: from, to: to)
237
+ builder.instance_eval(&block) if block
238
+ builder.build
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,178 @@
1
+ require_relative "word_gate"
2
+ module Hecks
3
+ module Bluebook
4
+ module DSL
5
+ class ValueObjectBuilder
6
+ GRAMMAR_CONTEXT = "ValueObject"
7
+
8
+ include AttributeCollector
9
+ include RuleReference
10
+ include WordGate
11
+
12
+ def initialize(name, owner_value_objects: [])
13
+ @name = name
14
+ @invariants = []
15
+ @members = []
16
+ @owner_value_objects = owner_value_objects
17
+ end
18
+
19
+ # THE WRAPPER BLOCK IS GONE (ADR 0025, "Attributes" — "closed sets
20
+ # lose the wrapper block"). `member` lines are bare now, written
21
+ # directly in the `value_object` body with no `one_of do ... end`
22
+ # around them — `build`'s own `closed_set: @closed_set ||
23
+ # !@members.empty?` already treats a non-empty `@members` as closed,
24
+ # so nothing else has to change for that to work. A single-field set
25
+ # has an even shorter spelling: `attribute`'s own `one_of:` keyword
26
+ # (`AttributeCollector#install_inline_closed_set`, overridden below).
27
+ #
28
+ # This also removes a real, documented landmine: the block form
29
+ # collided with `AttributeCollector#one_of`'s own type-position
30
+ # method (`one_of("a", "b")`, different arity) the moment both were
31
+ # mixed into the same builder — a value_object could never actually
32
+ # use the inline form on one of its own attributes. One `one_of`
33
+ # method now, not two.
34
+ #
35
+ # LEGACY UNDER SHADOW-PARSING (S0a's own bridge) — frozen era text
36
+ # still writes the block form (2 locations in
37
+ # `examples/banking/data/eras/banking/1.bluebook`, duplicated once
38
+ # more in that era's own archive copy), so `EraGuard.shadow_parse`
39
+ # still needs to read it. `block_given?` is what tells the two
40
+ # calling shapes apart: the type-position form
41
+ # (`one_of("a", "b")`) never passes a block, only the wrapper does.
42
+ # RENAMED FROM `one_of` — item #13's full metaprogrammed dispatch
43
+ # (slice 5). SAME NAME as `AttributeCollector#one_of_impl`'s own
44
+ # — required for the `super(*values)` call below to keep
45
+ # resolving; see that method's own comment. Reached directly
46
+ # through its own "ValueObject"-context Keyword row (the
47
+ # block-wrapper form has its own row, distinct from "Type"'s),
48
+ # not through the Type-position fallback this word's OTHER
49
+ # context uses.
50
+ def one_of_impl(*values, &block)
51
+ unless block
52
+ # NO VALUES, NO BLOCK is the SCALAR spelling — nonsensical, not
53
+ # merely inert: it names a closed set with nothing in it. The
54
+ # original block form caught this by a side effect (`@closed_set
55
+ # = true` ran unconditionally, before the `if block`), and this
56
+ # keeps the same refusal rather than letting the call fall
57
+ # through and silently do nothing.
58
+ if values.empty?
59
+ raise Malformed,
60
+ "#{@name}'s one_of names no values — one_of(\"a\", \"b\") takes at least one, or " \
61
+ "give the attribute its own one_of: [...] for a named closed set"
62
+ end
63
+
64
+ # AttributeCollector's own `one_of(*values)` — Ruby's normal
65
+ # `super` reaches the included module's method from here.
66
+ return super(*values)
67
+ end
68
+
69
+ unless MetaValidator.shadow_parsing?
70
+ raise Malformed,
71
+ "#{@name}'s one_of do ... end wrapper is gone — give the single attribute its " \
72
+ "own one_of: [...], or write bare member lines with no wrapper for a multi-field set"
73
+ end
74
+
75
+ @closed_set = true
76
+ instance_eval(&block)
77
+ end
78
+
79
+ # RENAMED FROM `member` — item #13's full metaprogrammed dispatch
80
+ # (slice 4c). Bootstrap-reachable, in
81
+ # GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
82
+ def member_impl(**fields)
83
+ raise Malformed, "#{@name} declared an empty member" if fields.empty?
84
+
85
+ @members << fields
86
+ end
87
+
88
+ # NO BLOCK is a REFERENCE, not a fresh declaration — the same
89
+ # move S10 already made for `CommandBuilder#given` (ADR 0025,
90
+ # "a precondition shared across commands is declared once... a
91
+ # command references it by name"), one level over: a rule
92
+ # shared across SIBLING value objects on the same aggregate,
93
+ # declared once, on the first one to need it. Real, live
94
+ # redundancy this closes: `Account`'s own `Money`/`PositiveMoney`
95
+ # both declared `invariant("a currency is a three-letter code")
96
+ # { currency.to_s.size == 3 }`, byte for byte. Declare it before
97
+ # the value objects that reference it — resolution happens at
98
+ # the referencing value object's own build time, against
99
+ # whatever sibling value objects the aggregate has already
100
+ # built, the same ordering rule `given` carries.
101
+ # RENAMED FROM `invariant` — item #13's full metaprogrammed
102
+ # dispatch (slice 4b). Bootstrap-reachable, in
103
+ # GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
104
+ def invariant_impl(description, &predicate)
105
+ return reference_named_invariant(description) unless predicate
106
+
107
+ # moved to the language: given "a rule says what it means", on Shape.Assert
108
+
109
+ @invariants << build_rule(Invariant, description, predicate, owner_name: @name, word: "invariant",
110
+ extraction_failure: "it would be a rule the IR cannot carry")
111
+ end
112
+
113
+ private
114
+
115
+ # PRIMITIVE 3 (RuleReference#resolve_sibling_scan) — see that
116
+ # method's own comment for why this is a live scan, not a pool.
117
+ def reference_named_invariant(description)
118
+ verify_resolves_via!("invariant", "ValueObject", "sibling_scan")
119
+ named = resolve_sibling_scan(@owner_value_objects, description, reader: :invariants) ||
120
+ raise(Malformed,
121
+ "#{@name}'s invariant #{description.inspect} names no rule a sibling value " \
122
+ "object on this aggregate declares — declare it once with a block, on the " \
123
+ "value object that needs it first, before the ones that reference it back")
124
+
125
+ @invariants << named
126
+ end
127
+
128
+ public
129
+
130
+ def build
131
+ if @inline_closed_set_field && attributes.size > 1
132
+ raise Malformed,
133
+ "#{@name}'s one_of: on :#{@inline_closed_set_field} only works when it is the " \
134
+ "value object's only attribute — #{attributes.size} declared here; write bare " \
135
+ "member lines instead for a multi-field set"
136
+ end
137
+
138
+ ValueObject.declare(
139
+ name: @name, attributes: attributes,
140
+ invariants: @invariants, members: @members,
141
+ closed_set: @closed_set || !@members.empty?
142
+ )
143
+ end
144
+
145
+ def self.build(name, owner_value_objects: [], &block)
146
+ builder = new(name, owner_value_objects: owner_value_objects)
147
+ builder.instance_eval(&block) if block
148
+ builder.build
149
+ end
150
+
151
+ private
152
+
153
+ # THE NEW SPELLING — `attribute :name, String, one_of: %w[...]`,
154
+ # overriding `AttributeCollector`'s own refusal (every OTHER
155
+ # includer has no meaningful use for this). PRIVATE, like the
156
+ # module's own version it overrides — it is a callback `attribute`
157
+ # invokes on itself, never a word a bluebook author calls by name.
158
+ # Refuses a SECOND attribute naming one_of: on the same value
159
+ # object outright — a single-field set names exactly one field, by
160
+ # construction; two would be structurally ambiguous about which
161
+ # field each member line belongs to. `build` refuses the OTHER
162
+ # half of that same rule (this attribute coexisting with unrelated
163
+ # ones on a multi-field object).
164
+ def install_inline_closed_set(field, values)
165
+ if @inline_closed_set_field && @inline_closed_set_field != field
166
+ raise Malformed,
167
+ "#{@name} declares one_of: on more than one attribute (:#{@inline_closed_set_field} " \
168
+ "and :#{field}) — a single-field closed set names exactly one"
169
+ end
170
+
171
+ @inline_closed_set_field = field
172
+ @closed_set = true
173
+ values.each { |value| member_impl(field => value.to_s) }
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end