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,82 @@
1
+ module Hecks
2
+ module Bluebook
3
+ # The callable catalogue for a discovered project. It knows how Bluebook
4
+ # declarations become public FQNs, but never searches or boots folders.
5
+ class ProjectRegister
6
+ Entry = Struct.new(:fqn, :source_directory, :dispatcher, :declared_verb, :domain_version, keyword_init: true) do
7
+ def command? = fqn.command?
8
+ def query? = fqn.query?
9
+ end
10
+
11
+ class DuplicateFqn < StandardError; end
12
+ class MissingRealm < StandardError; end
13
+ class LatestMismatch < StandardError; end
14
+
15
+ attr_reader :entries
16
+
17
+ def initialize
18
+ @entries = {}
19
+ end
20
+
21
+ def fetch(address) = entries.fetch(address.to_s)
22
+ def include?(address) = entries.key?(address.to_s)
23
+ def commands = entries.values.select(&:command?)
24
+ def queries = entries.values.select(&:query?)
25
+
26
+ def register(bluebooks, registry, dispatcher, directory)
27
+ bluebooks.each do |bluebook|
28
+ world = registry.world(bluebook.name)
29
+ realm = world&.realm
30
+ raise MissingRealm, "#{bluebook.name} in #{directory} has no world realm" if realm.to_s.empty?
31
+ if world.latest && bluebook.version && world.latest != bluebook.version
32
+ raise LatestMismatch,
33
+ "#{bluebook.name} in #{directory} declares latest #{world.latest.inspect}, not #{bluebook.version.inspect}"
34
+ end
35
+
36
+ bluebook.aggregates.each do |aggregate|
37
+ aggregate.commands.each do |command|
38
+ add(Fqn.command(realm: realm, domain: bluebook.name, version: bluebook.version,
39
+ aggregate: aggregate.hecks_name, command: command.hecks_name), directory, dispatcher, command.hecks_name, bluebook.version) if bluebook.version
40
+ add(Fqn.command(realm: realm, domain: bluebook.name, aggregate: aggregate.hecks_name,
41
+ command: command.hecks_name), directory, dispatcher, command.hecks_name, bluebook.version) if current?(
42
+ bluebook, world
43
+ )
44
+ end
45
+ aggregate.queries.each do |query|
46
+ name = Naming.snake(query.name)
47
+ add(Fqn.query(realm: realm, domain: bluebook.name, version: bluebook.version,
48
+ aggregate: aggregate.hecks_name, query: name), directory, dispatcher, query.name, bluebook.version) if bluebook.version
49
+ add(Fqn.query(realm: realm, domain: bluebook.name, aggregate: aggregate.hecks_name,
50
+ query: name), directory, dispatcher, query.name, bluebook.version) if current?(bluebook, world)
51
+ end
52
+ end
53
+ bluebook.read_models.each do |model|
54
+ add(Fqn.query(realm: realm, domain: bluebook.name, version: bluebook.version,
55
+ query: model.query_name), directory, dispatcher, model.query_name, bluebook.version) if bluebook.version
56
+ add(Fqn.query(realm: realm, domain: bluebook.name, query: model.query_name),
57
+ directory, dispatcher, model.query_name, bluebook.version) if current?(bluebook, world)
58
+ end
59
+ end
60
+ self
61
+ end
62
+
63
+ private
64
+
65
+ def current?(bluebook, world)
66
+ bluebook.version.nil? || world.latest == bluebook.version
67
+ end
68
+
69
+ def add(fqn, directory, dispatcher, declared_verb, domain_version)
70
+ existing = entries[fqn.to_s]
71
+ if existing
72
+ raise DuplicateFqn, "#{fqn} is declared in both #{existing.source_directory} and #{directory}"
73
+ end
74
+
75
+ entries[fqn.to_s] = Entry.new(
76
+ fqn: fqn, source_directory: directory, dispatcher: dispatcher,
77
+ declared_verb: declared_verb, domain_version: domain_version
78
+ )
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,61 @@
1
+ require_relative "behaviour/query"
2
+
3
+ module Hecks
4
+ module Bluebook
5
+ def self.render_value(value) = Literal.render(value)
6
+
7
+ # A query — an ASK, declared on an aggregate or on one of its entities.
8
+ #
9
+ # It crosses over as an INSTANCE rather than a class, and the reason is worth
10
+ # stating because it is the boundary of the pattern. A query inherits its
11
+ # whole body from `QuerySpecification::Common::Options` — `wheres`,
12
+ # `order_by`, `limit`, `offset`, `cursor`,
13
+ # `authorization`, `null_semantics`, `inspection` — and those
14
+ # are INSTANCE methods that the runtime and the SQLite adapter both read.
15
+ # Hoisting the declaration onto a metaclass would put the identity and the
16
+ # specification on opposite sides of the object.
17
+ #
18
+ # And nothing is lost, because `Class#name` was the only reason a construct
19
+ # ever needed a second word for its own name. An instance answers `name`
20
+ # truthfully. So a query gains an owner and an identity — which is what the
21
+ # graph is for — and keeps the name it always had.
22
+ class Query < QuerySpecification::Common::Options
23
+ include Construct
24
+
25
+ include Hecks::IR
26
+ include Behaviour::Query
27
+
28
+ emits_ir(
29
+ name: :name,
30
+ description: :description,
31
+ attributes: many(:attributes),
32
+ wheres: many(:wheres),
33
+ order_by: one(:order_by),
34
+ limit: one(:limit)
35
+ )
36
+
37
+ attr_reader :name, :description, :attributes
38
+
39
+ def initialize(name:, description: nil, attributes: [], wheres: [],
40
+ order_by: nil, limit: nil, offset: nil, cursor: nil,
41
+ authorization: nil, null_semantics: nil,
42
+ inspection: nil)
43
+ null_semantics ||= QuerySpecification::Common::NullSemantics.default
44
+ super(wheres: wheres, order_by: order_by, limit: limit, offset: offset, cursor: cursor,
45
+ authorization: authorization,
46
+ null_semantics: null_semantics, inspection: inspection)
47
+ @name = name.to_s
48
+ @hecks_name = @name
49
+ @description = description
50
+ @attributes = attributes
51
+ end
52
+
53
+ # A query's shape is NOT fixed — `extra_options_to_h` carries
54
+ # whatever options the specification layer grew (count, median,
55
+ # group_by, scope_to). Declared emission covers the settled part
56
+ # and `super` hands it over; the tail stays dynamic, which is the
57
+ # honest description of it.
58
+ def to_h = super.merge(extra_options_to_h)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,109 @@
1
+ require_relative "behaviour/read_model"
2
+
3
+ module Hecks
4
+ module Bluebook
5
+ # A read model — an ask that gathers heads from more than one aggregate.
6
+ #
7
+ # Like a query it crosses over as an INSTANCE, and for the same reason: its
8
+ # body is inherited from `QuerySpecification::ReadModel::Specification`, whose
9
+ # readers the runtime and the SQLite adapter both call on the object. It gains
10
+ # an identity and an owner ; it keeps the name it always had, because only a
11
+ # CLASS ever had a competing answer for `name`.
12
+ class ReadModel < QuerySpecification::ReadModel::Specification
13
+ include Construct
14
+
15
+ include Hecks::IR
16
+ include Behaviour::ReadModel
17
+
18
+ emits_ir(
19
+ name: :name,
20
+ description: :description,
21
+ reference_name: :reference_name,
22
+ reference_target: :reference_target,
23
+ query_name: :query_name,
24
+ wheres: many(:wheres),
25
+ order_by: one(:order_by),
26
+ limit: one(:limit)
27
+ )
28
+
29
+ attr_reader :name, :description, :reference_name, :reference_target, :aggregate_heads, :group_by,
30
+ :count, :median_field
31
+
32
+ # `reference_name:`/`reference_target:` are nil for a ROOTLESS read
33
+ # model (no `reference_to` declared) — `&.` throughout, rather than
34
+ # the `.to_s`/`.to_sym` this used to require unconditionally, so
35
+ # `reference_target.nil?` stays a real, checkable fact for the
36
+ # interpreter instead of silently becoming `""`.
37
+ def initialize(name:, description: nil, reference_name: nil, reference_target: nil, aggregate_heads: [],
38
+ group_by: [], count: nil, median_field: nil, **options)
39
+ super(joins: aggregate_heads, **options)
40
+ @name = name.to_s
41
+ @hecks_name = @name
42
+ @description = description
43
+ @reference_name = reference_name&.to_sym
44
+ @reference_target = reference_target&.to_s
45
+ @aggregate_heads = aggregate_heads
46
+ # Hash rows (`{field: :agg}`), same shape as `aggregate_heads` —
47
+ # `group_by_fields` is the convenience reader everything but
48
+ # `to_h`/the Judge's own generic walk actually wants.
49
+ @group_by = group_by
50
+ # ABSENT IS NOT FALSE. `@count` stays nil rather than becoming
51
+ # `false` when undeclared — the same "if you declare it, declare
52
+ # something" reading `Lifecycle`'s own optional fields rely on
53
+ # (MetaValidator::Judge#setters skips a setter whose every
54
+ # source is `nil`, so an ALWAYS-false `@count` would dispatch
55
+ # `ReadModel.Count` on every read model that never wrote the
56
+ # word at all). `!!count` collapses a truthy DSL `true` (or a
57
+ # reconstructed `true`) to the same `true`/`nil` pair either path
58
+ # produces — never `false`.
59
+ @count = count ? true : nil
60
+ @median_field = median_field&.to_sym
61
+ end
62
+
63
+ # `.to_sym` regardless of source — the DIRECT builder path stores
64
+ # symbols, but `Reconstruction::Shapes#group_by_field` (the
65
+ # replayed-from-the-meta-domain path every REAL boot actually
66
+ # goes through) reads the field back as a String, the same way
67
+ # `aggregate_heads`' own `:as` does. Row hash KEYS built by
68
+ # `Value.materialize_unwrapped` are symbols (`attr.name`), so
69
+ # this has to be too, or `row[field]` in `nest` silently misses.
70
+
71
+ # `wheres`/`order_by`/`limit` are spelled explicitly here, the SAME
72
+ # mechanism `Query#to_h` already uses (query.rb, read directly
73
+ # before this was written) — always present, `wheres` a (possibly
74
+ # empty) array and `order_by`/`limit` nil when undeclared, exactly
75
+ # like a Query's. `extra_options_to_h` still excludes these three by
76
+ # name (options.rb), so nothing here double-spells them; the two
77
+ # constructs now share one encoding for the same three words rather
78
+ # than a reader needing to learn a second one. See this file's own
79
+ # `filtered_head_name` and language/bluebook/syntax.bluebook's
80
+ # `ReadModel` `where`/`order_by`/`limit` member rows for the history
81
+ # of why this WAS narrower, and 2026-08-11's read-model where/
82
+ # order_by/limit task for why it stopped being.
83
+ # Same dynamic tail as a Query's, plus two collections whose
84
+ # ROWS are plain hashes rather than constructs — they are
85
+ # normalised here rather than by `many`, which recurses through
86
+ # `to_h` and would have nothing to call.
87
+ # `count`/`median_field` are ABSENT (no key at all), not `nil`,
88
+ # when undeclared — unlike `group_by` (always a `[]`, never
89
+ # missing) they are the FIRST fields this construct has ever
90
+ # added that no OLDER real corpus member could possibly carry, so
91
+ # merging them unconditionally would have put a `null` onto every
92
+ # existing read model's own wire shape (banking's
93
+ # `ComplianceDashboard`/`CustomerPortfolio`, the language's own
94
+ # `WholeBluebook`, ...) for a fact nothing about them changed —
95
+ # the exact `extra_options_to_h` reads for `cursor`/`offset`/etc,
96
+ # applied here for the same reason.
97
+ def to_h
98
+ reductions = {}
99
+ reductions[:count] = true if @count
100
+ reductions[:median_field] = @median_field.to_s if @median_field
101
+ super
102
+ .merge(aggregate_heads: @aggregate_heads.map { |head| head.merge(as: head[:as].to_s) })
103
+ .merge(group_by: @group_by.map { |row| row.merge(field: row[:field].to_s) })
104
+ .merge(reductions)
105
+ .merge(extra_options_to_h)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,74 @@
1
+ module Hecks
2
+ module Bluebook
3
+ # An attribute that points at another aggregate's head.
4
+ #
5
+ # This used to be the STRING `"Reference<Customer>"`, minted by
6
+ # `AggregateBuilder#reference_to` and `CommandBuilder#cross_reference` from
7
+ # a real constant that had just been handed in, and then parsed back apart
8
+ # by a regex in the command interpreter, by string equality in the read-model
9
+ # interpreter and the SQLite adapter, and by `delete_prefix` in the bluebook
10
+ # builder. Five readers of a spelling one writer invented.
11
+ #
12
+ # It holds the TARGET instead, and answers `resolve` with the target's
13
+ # Aggregate. Resolution is LAZY and deliberately so: `reference_to
14
+ # Customer` may name an aggregate declared lower in the file — banking's
15
+ # Account points at Customer and survives only because Customer happens to
16
+ # be written above — so the edge cannot be resolved at declaration time.
17
+ # It resolves through the chapter's own IR (`Bluebook#aggregate`),
18
+ # which is scoped by construction : the lookup cannot walk anywhere but
19
+ # this chapter's declared heads, so a same-named aggregate in another
20
+ # loaded domain is unreachable rather than defended against.
21
+ #
22
+ # `to_s` still spells `"Reference<Customer>"`, because `Attribute#to_h` is
23
+ # part of the pinned byte-for-byte export contract. IR objects in
24
+ # the graph, strings in the export.
25
+ class Reference
26
+ attr_reader :target_name
27
+
28
+ # The Aggregate whose declaration carries this reference — the way
29
+ # up to the chapter, stamped once every sibling has been read.
30
+ attr_accessor :declared_in
31
+
32
+ def initialize(target_name)
33
+ @target_name = Naming.demodulise(target_name).to_s
34
+ end
35
+
36
+ # The Aggregate this points at, or nil when the target belongs to
37
+ # ANOTHER domain — a cross-domain target may legitimately not be loaded,
38
+ # the same reading `across` policies get.
39
+ def resolve
40
+ unless declared_in
41
+ raise DSL::Malformed,
42
+ "#{self} cannot say which aggregate declares it, so it cannot " \
43
+ "resolve — a reference that resolves to nothing is checked " \
44
+ "against nothing"
45
+ end
46
+
47
+ declared_in.hecks_owner&.aggregate(@target_name)
48
+ end
49
+
50
+ # The IR spelling, the one the export carries.
51
+ def to_s = "Reference<#{@target_name}>"
52
+ def inspect = "#<Reference #{@target_name}>"
53
+
54
+ # VALUE EQUALITY, not identity — without this, two references to
55
+ # the SAME target, parsed from two SEPARATE bluebook reads (era
56
+ # N's own boot and a held era's own shadow reconstruction,
57
+ # coverage_check.rb's own comparison), are different objects and
58
+ # compare unequal by Ruby's default `==`. `EraGuard::ShapeDiff
59
+ # #diff_type`'s `held_type != current_type` check then reads as
60
+ # true for EVERY reference_to attribute on EVERY mint, regardless
61
+ # of whether the reference actually changed — a real refusal for
62
+ # attributes nothing about. `declared_in` (which aggregate carries
63
+ # this reference) is deliberately excluded: the same reference
64
+ # attribute exists once, but its `held_type`/`current_type` come
65
+ # from separately-parsed bluebooks with structurally different
66
+ # (if same-shaped) owning aggregates, and `declared_in` is a
67
+ # cross-reference for `resolve`, not part of what this attribute
68
+ # itself IS.
69
+ def ==(other) = other.is_a?(Reference) && target_name == other.target_name
70
+ alias eql? ==
71
+ def hash = [self.class, target_name].hash
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,166 @@
1
+ require_relative "synthesizer"
2
+ require "tmpdir"
3
+ require "fileutils"
4
+
5
+ module Hecks
6
+ module Bluebook
7
+ # BOOTS A REAL BLUEBOOK AND ACTUALLY DISPATCHES AGAINST IT — the
8
+ # sibling `bin/model_check` never had: that tool proves a bluebook
9
+ # is STRUCTURALLY sound (no dead states, no unreachable transitions)
10
+ # without ever running a single command; this proves it WORKS,
11
+ # catching exactly the class of bug static analysis structurally
12
+ # cannot see. Built and proved inside `bin/interview`'s own `smoke`
13
+ # command, where it first found two real bugs — a lifecycle field
14
+ # colliding with an explicit attribute, and a non-creating command
15
+ # missing `Command.ActsOn` — that reloaded clean, checked clean,
16
+ # and only broke once something actually dispatched against them.
17
+ #
18
+ # SYNTHESIZED ARGS, NEVER RANDOM (`Synthesizer`) — a String becomes
19
+ # a fixed marker, a closed set uses its own first admitted member, a
20
+ # reference reuses whatever this same run already minted for that
21
+ # target. ONE call per declared command, ONE call per declared
22
+ # report, per created root; every failure is collected and reported
23
+ # together rather than stopping at the first.
24
+ #
25
+ # WHAT THIS CANNOT CATCH: a call that dispatches cleanly but answers
26
+ # WRONG — the read-model join bug this exact tool's own first user
27
+ # hit is the textbook case, a query that silently returned an empty
28
+ # array rather than raising. Smoke-testing proves nothing crashes;
29
+ # it does not prove the answer is the right one.
30
+ #
31
+ # A FAILURE HERE IS NOT NECESSARILY A DOMAIN BUG, either — it may
32
+ # just be `Synthesizer`'s own naive values (0, an empty list,
33
+ # "smoke-test") not satisfying a real invariant this simple
34
+ # generator was never built to reason about (a positive amount, a
35
+ # non-empty topping list, an email pattern). Confirmed against
36
+ # Banking and Pizzas: both real domains, both showing exactly that
37
+ # class of failure, neither one an actual bug. Building a
38
+ # synthesizer that respects arbitrary declared invariants is real
39
+ # constraint-aware generation — a separate, larger tool than this.
40
+ module SmokeTest
41
+ Failure = Struct.new(:domain, :aggregate, :command, :error, keyword_init: true) do
42
+ def to_s = "#{domain}::#{aggregate}.#{command}: #{error}"
43
+ end
44
+
45
+ module_function
46
+
47
+ # `dir` is any real, bootable hecks app directory — a saved
48
+ # domain, or a throwaway one a caller rendered into a temp dir
49
+ # for exactly this purpose. Plain `Hecks.boot`, not
50
+ # `Router.boot` — proven all night, and it works against a
51
+ # domain with framework-attached chapters (`uses_framework
52
+ # "Governance"`, say) that `Router.boot` refuses outright: those
53
+ # chapters carry no `.world` of their own, which `Router`'s
54
+ # per-domain realm resolution requires and a plain boot never
55
+ # did. Reports are reachable the identical way regardless of
56
+ # boot path — `dispatcher.query("Domain.report_name", **args)`
57
+ # is the SAME `Dispatcher#query` either boot hands back; Router
58
+ # was never actually required for this, only its OWN namespace
59
+ # SUGAR (`Domain.report_name(...)` as a bare method call) was.
60
+ #
61
+ # NEVER BOOTS `dir`'S OWN REAL BINDINGS — measured, not a
62
+ # precaution taken on spec: pointed at `examples/pizzas` (a real,
63
+ # persistent store carrying real accumulated records), a
64
+ # synthesized `CreatePizza` collided with an actual pre-existing
65
+ # record literally named "smoke-test" — a coincidence this time,
66
+ # data pollution the next. `isolate!` copies only the `.bluebook`
67
+ # (and `.world`, for its realm) into a throwaway directory and
68
+ # deliberately leaves the real `.hecksagon` behind — with none
69
+ # present, `Ports::Persistence::BindingPolicy.default_binding`
70
+ # binds every aggregate to Memory on its own, no settings needed.
71
+ # Whatever `dir` is really bound to in production is never
72
+ # touched.
73
+ # `install_facade: false` — this only ever dispatches by FQN string
74
+ # (below), so it never needs the `Widget::Item.Add(...)` Ruby
75
+ # sugar `Hecks.boot` installs by default. Skipping it matters
76
+ # here specifically: that sugar lands as a BARE global constant on
77
+ # `Object` per domain and per aggregate name, with no scoping and
78
+ # no cleanup — and this tool boots throwaway domains under
79
+ # whatever generic names the caller's `.bluebook` happens to use
80
+ # ("Widget", "Item", "Tag" in this tool's own specs). Installing
81
+ # the facade would leak those names into the rest of the process —
82
+ # measured, not hypothetical: it once left a stale `Widget` constant
83
+ # that corrupted an unrelated spec's own unrelated use of the same
84
+ # bare name.
85
+ def call(dir)
86
+ Dir.mktmpdir("hecks-smoke-") do |scratch|
87
+ isolate!(dir, scratch)
88
+ dispatcher = Hecks.boot(scratch, install_facade: false)
89
+ domain = dispatcher.registry.bluebooks.keys.first
90
+ next [] unless domain
91
+
92
+ smoke_domain(dispatcher, domain)
93
+ end
94
+ end
95
+
96
+ # ONLY THE `.bluebook`, NEVER `.world` EITHER — a real world's own
97
+ # settings (`persisted_by("Heki") { dir "..." }`) are keyed to the
98
+ # REAL adapter it names, not to Memory; copied verbatim, they
99
+ # apply to the wrong binding and refuse with a `WiringError`
100
+ # ("Memory does not declare :dir") — measured, not assumed, the
101
+ # first time this ran against Banking. A plain `Hecks.boot`
102
+ # needs no realm to address anything (unlike `Router.boot`), so
103
+ # leaving the world out entirely is the correct minimal isolation,
104
+ # not a workaround. `translations/*.bluebook` also deliberately
105
+ # excluded — those are earlier, superseded versions of the same
106
+ # domain; smoke-testing the current declaration is the point, not
107
+ # its whole history.
108
+ def isolate!(dir, scratch)
109
+ source = Adapters::Folder.new.bluebook_directory(dir)
110
+ target = File.join(scratch, "bluebook")
111
+ FileUtils.mkdir_p(target)
112
+ Dir.glob(File.join(source, "*.bluebook")).each { |file| FileUtils.cp(file, target) }
113
+ end
114
+
115
+ # AGGREGATES WALKED IN DECLARATION ORDER, on purpose — so whatever
116
+ # a LATER aggregate's own shape needs from an EARLIER one (a
117
+ # `TripItem` needing a real `PackingItem`, say) already exists by
118
+ # the time that aggregate's own turn comes, the same reason
119
+ # `Synthesizer#args_for`'s `created` map is threaded through in
120
+ # this same order.
121
+ def smoke_domain(dispatcher, domain)
122
+ chapter = dispatcher.registry.bluebook(domain)
123
+ return [] unless chapter
124
+
125
+ created = {}
126
+ failures = []
127
+
128
+ chapter.aggregates.each do |aggregate|
129
+ creating, noncreating = aggregate.commands.partition(&:creates?)
130
+
131
+ creating.each do |command|
132
+ args = Synthesizer.args_for(chapter, aggregate, command, created)
133
+ begin
134
+ result = dispatcher.dispatch("#{domain}::#{aggregate.name}.#{command.hecks_name}", **args)
135
+ created[aggregate.name] = result.instance.id
136
+ rescue StandardError => e
137
+ failures << Failure.new(domain: domain, aggregate: aggregate.name, command: command.hecks_name,
138
+ error: "#{e.class}: #{e.message}")
139
+ next
140
+ end
141
+
142
+ noncreating.each do |nc_command|
143
+ nc_args = Synthesizer.args_for(chapter, aggregate, nc_command, created).merge(id: created[aggregate.name])
144
+ dispatcher.dispatch("#{domain}::#{aggregate.name}.#{nc_command.hecks_name}", **nc_args)
145
+ rescue StandardError => e
146
+ failures << Failure.new(domain: domain, aggregate: aggregate.name, command: nc_command.hecks_name,
147
+ error: "#{e.class}: #{e.message}")
148
+ end
149
+ end
150
+ end
151
+
152
+ chapter.read_models.each do |model|
153
+ root_id = created[model.reference_target]
154
+ next unless root_id # nothing was created for this root — nothing to smoke-test yet, not a failure
155
+
156
+ dispatcher.query("#{domain}.#{model.query_name}", model.reference_name => root_id)
157
+ rescue StandardError => e
158
+ failures << Failure.new(domain: domain, aggregate: model.name, command: "report(#{model.name})",
159
+ error: "#{e.class}: #{e.message}")
160
+ end
161
+
162
+ failures
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,95 @@
1
+ module Hecks
2
+ module Bluebook
3
+ # ONE SYNTHESIZED ARGUMENT PER DECLARED ATTRIBUTE — never a random
4
+ # guess. Reads only real `IR` (`Aggregate`, `Command`,
5
+ # `Attribute`, `ValueObject`), so it works against ANY
6
+ # loaded bluebook, not one particular domain: a String becomes a
7
+ # fixed marker; an Integer becomes 0; a closed set uses ITS OWN
8
+ # first admitted member (never a value a real `one_of` would
9
+ # legitimately refuse); a reference uses whatever id the CALLER
10
+ # already knows for that target (`created`), so a command whose
11
+ # own shape depends on an earlier-created record — a `TripItem`
12
+ # needing a real `PackingItem`, say — gets something real to work
13
+ # with instead of a guess that would only ever fail.
14
+ #
15
+ # Extracted from `bin/interview`'s own smoke test, where this first
16
+ # got built and proved — the logic never actually knew anything
17
+ # about the Interview domain or any one app; it only ever read IR.
18
+ module Synthesizer
19
+ # THE ONLY TYPE NAMES THAT ARE EVER TRULY SCALAR — anything else
20
+ # a value object's own field names is ANOTHER value object,
21
+ # nested (`Pizza.price_cents: Price`, itself wrapping `cents:
22
+ # Integer` — a real, ordinary shape, not an edge case). Missing
23
+ # this the first time round meant a nested field silently
24
+ # synthesized as the bare string `"smoke-test"` instead of the
25
+ # real nested structure a command actually declares — caught
26
+ # only by testing against Pizzas instead of re-testing against
27
+ # the one simpler domain this was first built for, where every
28
+ # value object happened to be single-field and primitive-typed.
29
+ # THE SAME LIST `Attribute::PRIMITIVES` holds, referenced rather
30
+ # than repeated. It was a second, byte-identical copy — and only
31
+ # the Attribute one is held to `vocabulary.bluebook` by
32
+ # spec/vocabulary_conformance_spec, so this copy could drift from
33
+ # the language and nothing would say so.
34
+ PRIMITIVES = Attribute::PRIMITIVES
35
+
36
+ module_function
37
+
38
+ # `chapter`/`aggregate` are the real `Bluebook`/`Aggregate`
39
+ # the command belongs to — needed to resolve a value object type
40
+ # name back to its own declared shape. `created` maps an
41
+ # aggregate NAME to a real id already minted for it earlier in
42
+ # the same run; a reference whose target isn't in there yet gets
43
+ # a placeholder instead of failing outright, since the caller may
44
+ # not care about that particular argument's real value.
45
+ def args_for(chapter, aggregate, command, created = {})
46
+ command.attributes.to_h do |attribute|
47
+ if attribute.reference?
48
+ [attribute.name, created.fetch(attribute.type.target_name, "smoke-test-id")]
49
+ else
50
+ [attribute.name, value_for(chapter, aggregate, attribute.type)]
51
+ end
52
+ end
53
+ end
54
+
55
+ # A value object's OWN synthesized value — its first admitted
56
+ # member if it's a closed set (the one value guaranteed not to be
57
+ # refused), otherwise one synthesized scalar per declared field.
58
+ # Falls back to searching every aggregate in the chapter, the
59
+ # same tolerance `bin/interview shape`'s own lookup already
60
+ # needed: a value object referenced by name doesn't have to be
61
+ # declared on the SAME aggregate using it.
62
+ def value_for(chapter, aggregate, type_name)
63
+ value_object = aggregate.value_object(type_name) ||
64
+ chapter.aggregates.filter_map { |a| a.value_object(type_name) }.first
65
+ return "smoke-test" unless value_object
66
+
67
+ return value_object.members.first.to_h if value_object.respond_to?(:closed_set?) && value_object.closed_set? && value_object.members.any?
68
+
69
+ value_object.attributes.to_h { |field| [field.name, field_value_for(chapter, aggregate, field.type)] }
70
+ end
71
+
72
+ # A single field's own synthesized value — a plain scalar if its
73
+ # type is one of the true primitives, otherwise that type names
74
+ # ANOTHER value object, resolved the identical way `value_for`
75
+ # resolves any other one (its own closed set or its own nested
76
+ # fields, however deep that nesting actually goes).
77
+ def field_value_for(chapter, aggregate, type_name)
78
+ PRIMITIVES.include?(type_name.to_s) ? scalar_for(type_name) : value_for(chapter, aggregate, type_name)
79
+ end
80
+
81
+ # A bare primitive's own synthesized value. Never used for a
82
+ # closed set's OWN field (that's `value_for`'s job, reading the
83
+ # set's real first member) — only for a plain scalar field with
84
+ # no declared vocabulary to respect.
85
+ def scalar_for(primitive)
86
+ case primitive.to_s
87
+ when "Integer" then 0
88
+ when "Float" then 0.0
89
+ when "TrueClass", "FalseClass" then false
90
+ else "smoke-test"
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end