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,280 @@
1
+ require_relative "ir"
2
+ require_relative "../runtime/loader"
3
+ require_relative "../runtime/errors"
4
+ require_relative "../runtime/value"
5
+ require_relative "../runtime/reaction_invocation"
6
+
7
+ # Hecks::Behaviors::Expectations
8
+ #
9
+ # One test case, start to finish: take the suite's own runtime (booted
10
+ # once for exactly what its `loads` names, reset to nothing between
11
+ # tests — `runtime_for`), replay `setup` dispatches, dispatch (or query)
12
+ # the command under test, check `expect`. Split out of
13
+ # runner.rb the same way the file-count/sweep concern is split from a
14
+ # single test's own execution.
15
+ #
16
+ # TWO DELIBERATE DEVIATIONS from a prior port of this same idea (read
17
+ # before writing this, not reinvented):
18
+ #
19
+ # 1. A `setup` refusal and a refusal from the command under test are
20
+ # caught in SEPARATE rescue scopes. The prior port wrapped the whole
21
+ # test body — setups included — in one `rescue *REFUSAL_CLASSES`, so a
22
+ # broken setup could spuriously satisfy `expect refused: "..."` if its
23
+ # own refusal happened to match the expected substring. Here, any
24
+ # domain refusal during `setup` is unconditionally an ERROR — the
25
+ # example never got to the situation it claims to test.
26
+ #
27
+ # 2. `emits:` is read off a `registry.event_log` DIFF around the tested
28
+ # dispatch, not off `Result#events` alone. `Result#events` only holds
29
+ # the events the OUTERMOST dispatch announced; a policy's own cascade
30
+ # reenters through the same `Dispatcher#dispatch` (`PolicyInterpreter
31
+ # #deliver` → `door.reenter` → `dispatch`), and every dispatch's
32
+ # events — outer and reentrant alike — land in the one shared
33
+ # `registry.event_log`, in order (`CommandRules::Emission#emit`,
34
+ # `PortOperationInterpreter#emit`/`#call`). Diffing that log's length
35
+ # before/after the tested dispatch is the one read that actually sees
36
+ # a cascade — this DSL has no `kind: :cascade` because cascades are
37
+ # always on and `emits:` is expected to see them.
38
+ module Hecks
39
+ module Behaviors
40
+ module Expectations
41
+ module_function
42
+
43
+ REFUSAL_CLASSES = Hecks::Runtime::DOMAIN_REFUSALS
44
+ SPECIAL_KEYS = %i[ok refused emits count].freeze
45
+
46
+ def run_one(test, suite, runtime: nil)
47
+ runtime ||= runtime_for(suite)
48
+ runtime.registry.reset_runtime_state!
49
+ bluebooks = runtime.registry.bluebooks.values
50
+
51
+ current_setup = nil
52
+ begin
53
+ test.setups.each do |setup|
54
+ current_setup = setup
55
+ dispatch_command(runtime, qualify(setup.command, nil, bluebooks, kind: :command), setup.args)
56
+ end
57
+ rescue *REFUSAL_CLASSES => e
58
+ return error_result(test, "setup #{current_setup&.command.inspect} refused: #{e.message}")
59
+ end
60
+
61
+ run_tested(test, runtime, bluebooks)
62
+ rescue StandardError => e
63
+ error_result(test, "#{e.class}: #{e.message}")
64
+ end
65
+
66
+ # ONE BOOT PER SUITE, NOT PER TEST. The isolation a test needs is a
67
+ # runtime with nothing in it — and a boot of the same files gives
68
+ # exactly that back for the price of `Registry#reset_runtime_state!`
69
+ # instead of ~2s of loading, verifying and era-checking the same
70
+ # bluebook again (chess: 76 behaviours, 155s of which was booting
71
+ # `chess.bluebook` 76 times). Keyed by the suite's own `loads` AND
72
+ # their mtimes, so an edited bluebook boots fresh on the next test
73
+ # rather than running against a stale one — the property a watch
74
+ # loop or a long rspec session actually relies on. `runtime:` lets
75
+ # a caller that already holds a booted runtime (a spec, a REPL)
76
+ # hand it in; it is reset the same way.
77
+ RUNTIMES = {}
78
+ RUNTIMES_LOCK = Mutex.new
79
+ private_constant :RUNTIMES, :RUNTIMES_LOCK
80
+
81
+ def runtime_for(suite)
82
+ files = Array(suite.loads).map { |path| File.expand_path(path) }
83
+ key = files.map { |file| [file, File.exist?(file) ? File.mtime(file).to_f : nil] }
84
+
85
+ RUNTIMES_LOCK.synchronize do
86
+ RUNTIMES[key] ||= Hecks::Runtime::Loader.boot_files(files, install_facade: false)
87
+ end
88
+ end
89
+
90
+ def run_tested(test, runtime, bluebooks)
91
+ verb = qualify(test.tests_command, test.on_aggregate, bluebooks, kind: test.kind)
92
+
93
+ if test.query?
94
+ run_query(test, runtime, verb)
95
+ else
96
+ run_command(test, runtime, verb)
97
+ end
98
+ rescue *REFUSAL_CLASSES => e
99
+ check_refusal(test, e)
100
+ end
101
+
102
+ def run_command(test, runtime, verb)
103
+ before = runtime.registry.event_log.length
104
+ result = dispatch_command(runtime, verb, test.input)
105
+
106
+ return fail_result(test, "expected refused: #{test.expect[:refused].inspect} but dispatch succeeded") if test.expect.key?(:refused)
107
+
108
+ if (expected_emits = test.expect[:emits])
109
+ actual = runtime.registry.event_log[before..].map(&:name)
110
+ return fail_result(test, "expected emits: #{expected_emits.inspect}, got #{actual.inspect}") unless actual == expected_emits
111
+ end
112
+
113
+ check_ok(test) || check_fields(test, settled_state(runtime, verb, result)) || pass_result(test)
114
+ end
115
+
116
+ # A field expectation reads the aggregate AS IT STANDS once the
117
+ # dispatch and its whole cascade have run — the same "cascades are
118
+ # always on" reading `emits:` already commits to. `Result#state` is
119
+ # the wrong source for that: it snapshots the instance the OUTER
120
+ # dispatch saved, and a policy's own reentrant dispatch (a ply
121
+ # advancing off a Moved event, a move count bumping) hydrates and
122
+ # saves a FRESH record afterward — so a field the cascade wrote
123
+ # read back stale (found live: `expect move_count: 1` got 0 while
124
+ # `emits:` saw MoveCountBumped in the same test). The repository
125
+ # holds the settled record; read it back by the id the dispatch
126
+ # itself answered with.
127
+ def settled_state(runtime, verb, result)
128
+ return result.state || {} unless result.respond_to?(:id) && result.id
129
+
130
+ domain, rest = verb.split("::", 2)
131
+ aggregate_name = rest.to_s.split(".", 2).first
132
+ aggregate = runtime.registry.bluebook(domain)&.aggregate(aggregate_name)
133
+ return result.state || {} unless aggregate
134
+
135
+ record = runtime.registry.repository(domain, aggregate).find(result.id)
136
+ record ? record.state : (result.state || {})
137
+ end
138
+
139
+ # A behaviors test writes a dispatch the way the guide's own chess
140
+ # examples do — receiver identity and command facts side by side
141
+ # (`label: "g", id: "wn", to: { file: 2, rank: 2 }`) — and since
142
+ # #335 the dispatcher's own `to:` keyword is the ROUTING envelope,
143
+ # so forwarding those kwargs loose collides the moment a domain
144
+ # declares a command fact named `to` (chess does: every Move's own
145
+ # destination). Found live: every such test failed with "to: does
146
+ # not recognize file, rank" while this guide promised the spelling
147
+ # works. `ReactionInvocation.build` is #335's own seam for turning
148
+ # mixed facts into the strict envelope — identities lifted into
149
+ # `to:`, declared facts into `with:` — so a behaviors dispatch now
150
+ # goes through the exact same separation a policy's projection
151
+ # does. A verb that resolves to no command (a port operation —
152
+ # "Pizzas::Order.PaymentGateway.Receive") keeps the loose
153
+ # passthrough: its own input already spells the port form's
154
+ # `to:`/`with:`, which the dispatcher's port branch reads directly.
155
+ def dispatch_command(runtime, verb, args)
156
+ invocation = begin
157
+ Runtime::ReactionInvocation.build(registry: runtime.registry, verb: verb,
158
+ projected: args, explicit: true)
159
+ rescue Runtime::UnknownVerb
160
+ nil
161
+ end
162
+ return runtime.dispatch(verb, **args) unless invocation
163
+
164
+ if invocation.key?(:to)
165
+ runtime.dispatch(verb, to: invocation[:to], with: invocation[:with])
166
+ else
167
+ runtime.dispatch(verb, with: invocation[:with])
168
+ end
169
+ end
170
+
171
+ def run_query(test, runtime, verb)
172
+ rows = runtime.query(verb, **test.input)
173
+
174
+ return fail_result(test, "expected refused: #{test.expect[:refused].inspect} but the query succeeded") if test.expect.key?(:refused)
175
+
176
+ if (expected = test.expect[:count])
177
+ count = if rows.is_a?(Array)
178
+ rows.size
179
+ else
180
+ (rows.nil? ? 0 : 1)
181
+ end
182
+ return fail_result(test, "expected count: #{expected}, got #{count}") if count != expected
183
+ end
184
+
185
+ check_ok(test) || pass_result(test)
186
+ end
187
+
188
+ def check_ok(test)
189
+ return unless test.expect.key?(:ok)
190
+
191
+ expected = test.expect[:ok]
192
+ return if expected == true || expected.nil?
193
+
194
+ fail_result(test, "expect ok: only accepts true — got #{expected.inspect}")
195
+ end
196
+
197
+ def check_fields(test, state)
198
+ test.expect.each do |key, expected|
199
+ next if SPECIAL_KEYS.include?(key)
200
+
201
+ unless state.key?(key) || state.key?(key.to_s)
202
+ return fail_result(test, "expect #{key}: names no field on the tested aggregate — " \
203
+ "valid expect keys are ok:, refused:, emits:, count: (queries only), " \
204
+ "or a real field name")
205
+ end
206
+
207
+ actual = normalize(state.key?(key) ? state[key] : state[key.to_s])
208
+ exp = normalize(expected)
209
+ return fail_result(test, "expected #{key}: #{expected.inspect}, got #{actual.inspect}") unless actual == exp
210
+ end
211
+ nil
212
+ end
213
+
214
+ def check_refusal(test, error)
215
+ expected = test.expect[:refused]
216
+ return error_result(test, "unexpected refusal (#{error.class}): #{error.message}") unless expected
217
+
218
+ msg = error.message.to_s
219
+ # hecks's given/ensures refusals render "Command refused —
220
+ # <description>" (RefusalWording) ; a behaviors file names just
221
+ # the description — end_with?/include? bridges the prefix.
222
+ return pass_result(test) if msg == expected || msg.end_with?(expected) || msg.include?(expected)
223
+
224
+ fail_result(test, "expected refused: #{expected.inspect}, got #{msg.inspect}")
225
+ end
226
+
227
+ # The real corpus this DSL is proven against writes VO-typed
228
+ # `expect` values both ways — bare (`expect kind: "bishop"`) and
229
+ # wrapped (`expect kind: { value: "bishop" }`). A live record's
230
+ # field always comes back as a `Hecks::Runtime::Value`;
231
+ # normalizing BOTH sides to the same bare-scalar-or-plain-hash
232
+ # shape is the one comparison that accepts either spelling.
233
+ def normalize(value)
234
+ return Hecks::Runtime::Value.materialize_unwrapped(value) if value.is_a?(Hecks::Runtime::Value)
235
+ return normalize(value[:value]) if value.is_a?(Hash) && value.keys == [:value]
236
+
237
+ value
238
+ end
239
+
240
+ # A bare `tests`/`setup` command name carries no domain — `loads`
241
+ # can name more than one bluebook, so resolution searches every
242
+ # aggregate across every bluebook the suite booted for the one that
243
+ # actually declares the command. `on:` (when given, only ever on
244
+ # the TESTED command — `setup` never receives it, see the DSL
245
+ # contract) narrows the search to one aggregate by name instead of
246
+ # searching all of them.
247
+ def qualify(command, on_aggregate, bluebooks, kind:)
248
+ return command.to_s if command.to_s.include?(".")
249
+
250
+ members = kind == :query ? :queries : :commands
251
+ pairs =
252
+ if on_aggregate
253
+ bluebooks.filter_map { |bb| (agg = bb.aggregate(on_aggregate)) && [bb, agg] }
254
+ else
255
+ bluebooks.flat_map { |bb| bb.aggregates.map { |agg| [bb, agg] } }
256
+ end
257
+ candidates = pairs.select { |_, agg| agg.public_send(members).any? { |m| m.hecks_name == command.to_s } }
258
+
259
+ case candidates.size
260
+ when 0
261
+ raise ArgumentError, "no aggregate among #{bluebooks.map(&:name).inspect} declares a #{kind} " \
262
+ "named #{command.inspect} — say `on:` if it's ambiguous, or check the spelling"
263
+ when 1
264
+ bluebook, aggregate = candidates.first
265
+ "#{bluebook.name}::#{aggregate.name}.#{command}"
266
+ else
267
+ owners = candidates.map { |bb, agg| "#{bb.name}::#{agg.name}" }
268
+ raise ArgumentError, "#{command.inspect} is declared on more than one aggregate (#{owners.join(', ')}) " \
269
+ "— say `on:` to disambiguate, or use the dotted FQN"
270
+ end
271
+ end
272
+
273
+ def pass_result(test) = Result.new(description: test.description, status: :pass, message: nil)
274
+ def fail_result(test, message) = Result.new(description: test.description, status: :fail, message: message)
275
+ def error_result(test, message) = Result.new(description: test.description, status: :error, message: message)
276
+
277
+ Result = Struct.new(:description, :status, :message, keyword_init: true)
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,31 @@
1
+ # Hecks::Behaviors::{TestSetup,TestCase,BehaviorsSuite}
2
+ #
3
+ # The `.behaviors` authoring surface's own IR — plain Structs, holding
4
+ # exactly what `Hecks.behaviors "Name" do ... end` collected. Deliberately
5
+ # NOT part of `Hecks::IR`/`emits_ir` — a behaviors suite is never
6
+ # collected into a domain Registry (see `Hecks.behaviors`), never
7
+ # dispatched through MetaValidator, and carries none of the self-hosted
8
+ # round-trip machinery a real bluebook construct does. It is a test
9
+ # artifact a runner reads on demand, not a domain a boot needs.
10
+ module Hecks
11
+ module Behaviors
12
+ TestSetup = Struct.new(:command, :args, keyword_init: true)
13
+
14
+ TestCase = Struct.new(:description, :tests_command, :on_aggregate, :kind,
15
+ :setups, :input, :expect, keyword_init: true) do
16
+ # An already-dotted tests_command is a literal FQN; otherwise `on:`
17
+ # composes with the domain name resolved once setups/the tested
18
+ # dispatch actually run (see Expectations — the domain isn't known
19
+ # until `loads` is booted, so it's a runtime concern, not a field
20
+ # here).
21
+ def dotted? = tests_command.to_s.include?(".")
22
+
23
+ def query? = kind == :query
24
+ end
25
+
26
+ # `loads` — absolute paths, already resolved against the `.behaviors`
27
+ # file's own directory by the DSL (`BehaviorsBuilder#loads`). `path` is
28
+ # the `.behaviors` file itself, kept for error messages.
29
+ BehaviorsSuite = Struct.new(:name, :vision, :loads, :tests, :path, keyword_init: true)
30
+ end
31
+ end
@@ -0,0 +1,42 @@
1
+ require_relative "../behaviors"
2
+
3
+ # Hecks::Behaviors::RSpec.describe_file(path) — the shim a consumer's
4
+ # `bundle exec rspec` uses to run `.behaviors` files as ordinary examples,
5
+ # one `it` per test, named by the test's own description string. Same
6
+ # shape `spec/guides_spec.rb` uses for doctested guides: the file is
7
+ # PARSED at collection time (cheap — `Behaviors.parse`, no test actually
8
+ # run yet, just enough to know the `it` names), and each test's own
9
+ # `Expectations.run_one` runs lazily inside its own `it`, exactly when
10
+ # rspec actually executes it.
11
+ #
12
+ # require "hecks/behaviors/rspec"
13
+ #
14
+ # Dir.glob("bluebook/**/*.behaviors").each do |path|
15
+ # Hecks::Behaviors::RSpec.describe_file(path)
16
+ # end
17
+ module Hecks
18
+ module Behaviors
19
+ module RSpec
20
+ module_function
21
+
22
+ def describe_file(path)
23
+ parsed = Behaviors.parse(path)
24
+
25
+ ::RSpec.describe(File.basename(path)) do
26
+ if parsed.parse_error
27
+ it "loads without a parse error" do
28
+ raise parsed.parse_error
29
+ end
30
+ else
31
+ parsed.suite.tests.each do |test|
32
+ it(test.description) do
33
+ run = Expectations.run_one(test, parsed.suite)
34
+ raise run.message if run.status != :pass
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,96 @@
1
+ require_relative "expectations"
2
+
3
+ # Hecks::Behaviors.run(path) / .run_all(dir)
4
+ #
5
+ # Discovery + aggregation. `run_one` (expectations.rb) is the impure edge
6
+ # that actually touches a live runtime; this file finds `.behaviors`
7
+ # files, loads them, and maps their tests through it.
8
+ module Hecks
9
+ module Behaviors
10
+ FileResult = Struct.new(:path, :parse_error, :runs, keyword_init: true)
11
+ SweepResult = Struct.new(:root, :files_swept, :files, :summary, keyword_init: true)
12
+ ParseResult = Struct.new(:path, :suite, :parse_error, keyword_init: true)
13
+
14
+ class LoadOutsideRunner < StandardError; end
15
+
16
+ class << self
17
+ # The `.behaviors` file currently being `Kernel.load`ed, if any —
18
+ # bound only for the duration of that load (`run`'s own `ensure`),
19
+ # never left set. `Hecks.behaviors` (lib/hecks/behaviors.rb)
20
+ # reads this to resolve its own `loads` line relative to the right
21
+ # file, and to refuse a `.behaviors` file loaded any other way.
22
+ attr_accessor :loading_path
23
+
24
+ # The suite the most recent `Hecks.behaviors` call built — read
25
+ # once, right after `Kernel.load`, by `run` below, which resets it
26
+ # to nil before every load so a file that loads without calling
27
+ # `Hecks.behaviors` at all is never confused for a stale previous
28
+ # suite (a real bug in a prior port of this idea: compared only
29
+ # against nil, so after the first successful file in a sweep it
30
+ # stayed non-nil forever).
31
+ attr_accessor :last_suite
32
+
33
+ # `Kernel.load`s one `.behaviors` file and returns its suite, with
34
+ # NO test actually executed yet — the cheap half, split out so a
35
+ # caller that only needs to know what tests exist (the rspec shim,
36
+ # naming its `it`s at collection time) doesn't pay for running them
37
+ # until it actually wants to. `Behaviors.loading_path` is bound only
38
+ # for the duration of the load, and `last_suite` is reset to nil
39
+ # BEFORE it — a file that loads without ever calling
40
+ # `Hecks.behaviors` is unambiguously a parse error, never a stale
41
+ # suite from whatever loaded before it in a sweep (a real bug in a
42
+ # prior port of this idea: compared only against nil, so after the
43
+ # first successful file in a sweep it stayed non-nil forever).
44
+ def parse(path)
45
+ path = File.expand_path(path)
46
+ previous_path = loading_path
47
+ self.loading_path = path
48
+ self.last_suite = nil
49
+
50
+ begin
51
+ Kernel.load(path)
52
+ rescue StandardError, ScriptError => e
53
+ return ParseResult.new(path: path, suite: nil, parse_error: "#{e.class}: #{e.message}")
54
+ ensure
55
+ self.loading_path = previous_path
56
+ end
57
+
58
+ suite = last_suite
59
+ return ParseResult.new(path: path, suite: nil, parse_error: "file loaded but called no Hecks.behaviors") unless suite
60
+
61
+ ParseResult.new(path: path, suite: suite, parse_error: nil)
62
+ end
63
+
64
+ # One `.behaviors` file → `FileResult`, every test actually run.
65
+ def run(path)
66
+ parsed = parse(path)
67
+ return FileResult.new(path: parsed.path, parse_error: parsed.parse_error, runs: []) if parsed.parse_error
68
+
69
+ runs = parsed.suite.tests.map { |test| Expectations.run_one(test, parsed.suite) }
70
+ FileResult.new(path: parsed.path, parse_error: nil, runs: runs)
71
+ end
72
+
73
+ # Sweeps every `.behaviors` file under `dir`. Reports the file count
74
+ # it actually found — a sweep that goes green without saying how
75
+ # much it looked at is indistinguishable from one that found
76
+ # nothing to look at.
77
+ def run_all(dir)
78
+ files = Dir.glob(File.join(dir, "**", "*.behaviors"))
79
+ results = files.map { |path| run(path) }
80
+ SweepResult.new(root: dir, files_swept: files.size, files: results, summary: summarize(results))
81
+ end
82
+
83
+ def summarize(results)
84
+ runs = results.flat_map(&:runs)
85
+ {
86
+ files: results.size,
87
+ parse_errors: results.count(&:parse_error),
88
+ total: runs.size,
89
+ passed: runs.count { |r| r.status == :pass },
90
+ failed: runs.count { |r| r.status == :fail },
91
+ errored: runs.count { |r| r.status == :error }
92
+ }
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,25 @@
1
+ require_relative "behaviors/dsl"
2
+ require_relative "behaviors/expectations"
3
+ require_relative "behaviors/runner"
4
+
5
+ # The `.behaviors` toolkit `bin/behaviors` and a consumer's own rspec shim
6
+ # (`hecks/behaviors/rspec`) drive. Not required by lib/hecks.rb on
7
+ # purpose — a booted domain never needs it, the same reason `Fuzzing` (see
8
+ # lib/hecks/fuzzing.rb, the shape this file mirrors) is opt-in too.
9
+ module Hecks
10
+ class << self
11
+ # `Hecks.behaviors "Name" do ... end` — deliberately NOT routed
12
+ # through `collect` the way `bluebook`/`hecksagon`/`world` are: a
13
+ # behaviors suite is a test artifact a runner reads on demand, never
14
+ # a thing a live domain boot needs, so it has no business landing in
15
+ # `Runtime.current_registry`.
16
+ def behaviors(name, &)
17
+ path = Behaviors.loading_path or
18
+ raise Behaviors::LoadOutsideRunner,
19
+ "Hecks.behaviors called outside Hecks::Behaviors.run(path) — " \
20
+ "a .behaviors file is only ever loaded by the behaviors runner"
21
+
22
+ Behaviors.last_suite = Behaviors::BehaviorsBuilder.build(name, source_path: path, &)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,108 @@
1
+ require_relative "behaviour/aggregate"
2
+
3
+ module Hecks
4
+ module Bluebook
5
+ # A FIELD READ THROUGH A `reference_to`, HELD LOCALLY (S12, ADR 0025
6
+ # — "Consistency across aggregate boundaries") — `projects
7
+ # :customer_status, from: :"customer.status"` declares that this
8
+ # aggregate's own `:customer_status` is a COPY of the target's own
9
+ # `:status`, kept fresh by a rebuild sweep rather than read live at
10
+ # rule-evaluation time. `reference` names the LOCAL reference
11
+ # attribute to walk through (`:customer`, minted by this
12
+ # aggregate's own `reference_to Customer`); `remote_field` names
13
+ # the SCALAR on the target aggregate to copy.
14
+ ProjectedField = Struct.new(:name, :reference, :remote_field, keyword_init: true)
15
+
16
+ # A construct like every other: the aggregate carries its own identity
17
+ # and sits in the owner chain between its chapter (a Bluebook) and
18
+ # everything declared on it. The chain is model objects end to end —
19
+ # reference resolution and hecks_fqn both walk it.
20
+ #
21
+ # THE HOLDING HALF, and nothing else. Every line below restates what
22
+ # `language/bluebook/aggregate.bluebook` already declares — the field
23
+ # list, said again as readers, again as constructor keywords, and
24
+ # again as an emission. That triplication is what a generator removes;
25
+ # `Behaviour::Aggregate` carries everything that is NOT derivable from
26
+ # the declaration, so regenerating this file can never be lossy.
27
+ #
28
+ # PROTOTYPE: hand-written in the shape a generator would emit, to
29
+ # prove the seam before the generator exists. `bin/project_model`
30
+ # would own this file; behaviour/aggregate.rb stays hand-written.
31
+ class Aggregate
32
+ include Construct
33
+ include Hecks::IR
34
+ include Behaviour::Aggregate
35
+
36
+ emits_ir(
37
+ name: :name,
38
+ description: :description,
39
+ identified_by: :identity_paths,
40
+ attributes: many(:attributes),
41
+ value_objects: many(:value_objects),
42
+ commands: many(:commands),
43
+ invariants: -> { invariants.map { |rule| { description: rule.description, canonical: rule.canonical } } },
44
+ # A PRECONDITION SHARED ACROSS COMMANDS, DECLARED ONCE (S10, ADR
45
+ # 0025) — the aggregate's OWN named `given`s, the declaration a
46
+ # referencing command's own (already-resolved) `givens` entry
47
+ # came from. Both sides of "declared once, referenced many"
48
+ # are real IR, the same shape a value object's TYPE and an
49
+ # attribute's own reference to it both are.
50
+ preconditions: -> { preconditions.map { |rule| { description: rule.description, canonical: rule.canonical } } },
51
+ # S12, ADR 0025 — deliberately NOT folded into `attributes`:
52
+ # `EraGuard::ShapeDiff` only ever walks `attributes` to decide
53
+ # whether a NEW field leaves an existing record with something
54
+ # genuinely absent, and a projected field's own absence story
55
+ # is different — a record predating the `projects` declaration
56
+ # is expected to be missing it until the rebuild sweep runs,
57
+ # not a shape drift a translation needs to explain.
58
+ projected_fields: -> {
59
+ projected_fields.map { |field|
60
+ { name: field.name.to_s, reference: field.reference.to_s, remote_field: field.remote_field.to_s }
61
+ }
62
+ },
63
+ lifecycle: one(:lifecycle),
64
+ entities: many(:entities),
65
+ queries: many(:queries),
66
+ # ADDITIVE — every domain that declares no port emits `ports: []`,
67
+ # the same "regenerate deliberately" wire-format change
68
+ # ir_golden_spec.rb's own header describes; no existing key
69
+ # moves. See docs/decisions (rust/project/ports.rb) for the
70
+ # first reader of this.
71
+ ports: many(:ports),
72
+ provenance: :provenance
73
+ )
74
+
75
+ attr_reader :name, :description, :attributes, :value_objects, :commands, :invariants, :preconditions,
76
+ :projected_fields, :identified_by, :identity_paths, :identity_heads, :lifecycle,
77
+ :entities, :queries, :policies, :ports, :reference_targets, :provenance
78
+
79
+ # Assigns what the language declares, then hands off to the
80
+ # behaviour's own `settle` — derived identity, name indexes and
81
+ # owner stamping, none of which the declaration states.
82
+ def initialize(name:, description: nil, attributes: [], value_objects: [],
83
+ commands: [], invariants: [], preconditions: [], projected_fields: [], identified_by: [], lifecycle: nil,
84
+ entities: [], queries: [], policies: [], ports: [], reference_targets: [],
85
+ provenance: nil)
86
+ @name = name.to_s
87
+ @hecks_name = @name
88
+ @description = description
89
+ @attributes = attributes
90
+ @value_objects = value_objects
91
+ @commands = commands
92
+ @invariants = invariants
93
+ @preconditions = preconditions
94
+ @projected_fields = projected_fields
95
+ @identified_by = identified_by
96
+ @lifecycle = lifecycle
97
+ @entities = entities
98
+ @queries = queries
99
+ @policies = policies
100
+ @ports = ports
101
+ @reference_targets = reference_targets
102
+ @provenance = provenance
103
+
104
+ settle
105
+ end
106
+ end
107
+ end
108
+ end