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,90 @@
1
+ Hecks.bluebook "Identity" do
2
+ vision "Authentication proves an identifier; the organization decides what that identity means."
3
+ # S13, ADR 0025 (coverage standard) — GENERIC, not `supporting`: an
4
+ # OIDC-shaped external identifier is the textbook generic subdomain
5
+ # (Vernon's own example of one), off-the-shelf and buyable —
6
+ # `Governance` (role/scope RBAC beside it) stays `supporting`
7
+ # instead, since ITS OWN model is bespoke to this codebase's actor/
8
+ # role/scope shape, not a drop-in replaceable concept the way an
9
+ # identity provider is.
10
+ generic
11
+
12
+ aggregate "Identity" do
13
+ description "A stable organizational identity, independent of how it was authenticated."
14
+
15
+ attribute :identity_id, IdentityId
16
+
17
+ identified_by :identity_id
18
+
19
+ value_object "IdentityId" do
20
+ attribute :value, String
21
+ invariant("an identity id is present") { !value.to_s.empty? }
22
+ end
23
+
24
+ # No natural key exists for "a newly recognized identity" — the
25
+ # caller mints identity_id via Ports::IdentityGeneration.uuid
26
+ # before dispatching (see spec/ports/identity_generation_spec.rb),
27
+ # the same pattern proven there against Pizzas.
28
+ command "Register" do
29
+ role "Identity registrar"
30
+ goal "Recognize an identity"
31
+
32
+ attribute :identity_id, IdentityId
33
+ emits "IdentityRegistered"
34
+ end
35
+ end
36
+
37
+ aggregate "ExternalIdentifier" do
38
+ description "One authenticated (issuer, subject) pair, linked to the identity it proves control of."
39
+
40
+ reference_to Identity
41
+ attribute :key, ExternalIdentifierKey
42
+ attribute :issuer, Issuer
43
+ attribute :subject, Subject
44
+
45
+ identified_by :key
46
+
47
+ value_object "ExternalIdentifierKey" do
48
+ attribute :value, String
49
+ invariant("a key is present") { !value.to_s.empty? }
50
+ end
51
+
52
+ value_object "Issuer" do
53
+ attribute :value, String
54
+ invariant("an issuer is present") { !value.to_s.empty? }
55
+ end
56
+
57
+ value_object "Subject" do
58
+ attribute :value, String
59
+ invariant("a subject is present") { !value.to_s.empty? }
60
+ end
61
+
62
+ # `key` is deliberately not derived here — a caller-side concern
63
+ # (e.g. SHA256 of issuer+subject), same as any other identity
64
+ # field this domain never mints itself.
65
+ command "Link" do
66
+ role "Identity registrar"
67
+ goal "Associate an external authenticated subject with an identity"
68
+
69
+ reference_to Identity
70
+ attribute :key, ExternalIdentifierKey
71
+ attribute :issuer, Issuer
72
+ attribute :subject, Subject
73
+
74
+ emits "ExternalIdentifierLinked"
75
+ end
76
+
77
+ # THE OIDC-SHAPED LOOKUP — a token hands over `(issuer, subject)`,
78
+ # never the `key` this aggregate is actually identified by (`key`
79
+ # is a caller-side derivation, deliberately not reproduced here —
80
+ # see `Link`'s own comment). `identity` rides the row already,
81
+ # from `reference_to Identity` above, so a caller reads it straight
82
+ # off the match rather than needing a second query.
83
+ query "ResolvedBy" do
84
+ description "The external identifier linked to this exact (issuer, subject) pair, if any."
85
+ attribute :issuer, Issuer
86
+ attribute :subject, Subject
87
+ where(issuer: :issuer, subject: :subject)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,39 @@
1
+ {
2
+ "audience": "Compliance",
3
+ "scopes": [
4
+ {
5
+ "scope": "compliance:account_freeze_review.clear",
6
+ "verb": "Compliance::AccountFreezeReview.Clear",
7
+ "role": "Compliance officer"
8
+ },
9
+ {
10
+ "scope": "compliance:account_freeze_review.escalate",
11
+ "verb": "Compliance::AccountFreezeReview.Escalate",
12
+ "role": "Compliance officer"
13
+ },
14
+ {
15
+ "scope": "compliance:account_freeze_review.open",
16
+ "verb": "Compliance::AccountFreezeReview.Open",
17
+ "role": "System"
18
+ },
19
+ {
20
+ "scope": "compliance:box_surrender_review.clear",
21
+ "verb": "Compliance::BoxSurrenderReview.Clear",
22
+ "role": "Compliance officer"
23
+ },
24
+ {
25
+ "scope": "compliance:box_surrender_review.escalate",
26
+ "verb": "Compliance::BoxSurrenderReview.Escalate",
27
+ "role": "Compliance officer"
28
+ },
29
+ {
30
+ "scope": "compliance:box_surrender_review.open",
31
+ "verb": "Compliance::BoxSurrenderReview.Open",
32
+ "role": "System"
33
+ }
34
+ ],
35
+ "roles": [
36
+ "Compliance officer",
37
+ "System"
38
+ ]
39
+ }
@@ -0,0 +1,90 @@
1
+ require_relative "runtime/registry"
2
+
3
+ module Hecks
4
+ # THE REGISTRY OF FRAMEWORK BLUEBOOKS — Governance, Identity, and
5
+ # whatever lands beside them in `lib/hecks/framework/bluebook/`:
6
+ # domain-agnostic chapters no single example owns, shared by reference
7
+ # rather than copied into every domain that wants one.
8
+ #
9
+ # UNDER `lib/`, not a top-level sibling — a real consumer (embryonaut)
10
+ # vendors ONLY `lib/` (`bin/vendor-hecks`, and this gem's own
11
+ # `hecks.gemspec`, both glob `lib/**/*`), so a `framework/`
12
+ # sitting beside `lib/` rather than inside it packaged and vendored
13
+ # cleanly but left `uses_framework` finding nothing at runtime —
14
+ # `Framework::ROOT` pointed at a directory that simply never shipped.
15
+ # Nothing in THIS repo's own suite caught it, since every spec here
16
+ # reads the working tree directly; only vendoring into a real consumer
17
+ # did. Same reasoning `lib/hecks/language/bluebook/` already
18
+ # settled for the self-hosted grammar's own `.bluebook` files —
19
+ # non-Ruby data a module owns lives inside `lib/` with the code that
20
+ # reads it, not beside it.
21
+ #
22
+ # DERIVED FROM THE DIRECTORY, not hand-listed a second time — the same
23
+ # reasoning `Assembly::CONTRACTS` gives for reading the language's own
24
+ # fields from a table instead of restating them: a member added here
25
+ # and forgotten in a list would be a member `uses_framework` could
26
+ # never find, and a hand-kept list is exactly the shape that goes
27
+ # stale in silence. `spec/corpus_spec.rb`'s own `FRAMEWORK_MEMBERS`
28
+ # glob is the precedent this mirrors.
29
+ #
30
+ # NAMED BY FILE STEM, capitalized — `governance.bluebook` holds
31
+ # `Hecks.bluebook "Governance"`, the same one-to-one spelling every
32
+ # other chapter in this codebase already keeps between its filename
33
+ # and its declared name.
34
+ module Framework
35
+ ROOT = File.expand_path("framework/bluebook", __dir__).freeze
36
+
37
+ def self.members
38
+ Dir.glob(File.join(ROOT, "*.bluebook")).to_h do |path|
39
+ [Naming.pascal(File.basename(path, ".bluebook")), path]
40
+ end
41
+ end
42
+
43
+ # LOADED FROM ITS OWN REAL PATH, always — never a copy. A domain
44
+ # booted through `Fuzzing::IsolatedBoot`'s tmp-directory copy still
45
+ # reaches the SAME `framework/bluebook/governance.bluebook` this
46
+ # constant points at, because `uses_framework` runs `Kernel.load`
47
+ # against `ROOT`, not against anything inside the copied domain
48
+ # directory — there is nothing here for a relocated copy to break,
49
+ # the way a symlink carried along with the copy would.
50
+ #
51
+ # ONLY THE BLUEBOOK — a framework member's own `.hecksagon`, if it
52
+ # has one, is NOT auto-loaded. Persistence is a WIRING decision, the
53
+ # same as any other aggregate's, and belongs to whoever is doing the
54
+ # deploying, not to a default baked into the framework member
55
+ # itself: a consuming app needs `Governance::RoleAssignment` durable
56
+ # (Postgres, say), and a framework member hard-coding Memory for its
57
+ # own specs would silently make that decision FOR every app that
58
+ # attaches it. The consuming app declares its OWN, SEPARATE
59
+ # `Hecks.hecksagon "Governance" do ... end` block for that — not
60
+ # binds folded into its own hecksagon, which would build but never
61
+ # resolve: `Ports::Persistence::BindingPolicy.resolve` looks up
62
+ # `registry.hecksagon` by the AGGREGATE's own domain name, not the
63
+ # domain doing the binding, the one invariant this runtime holds
64
+ # everywhere else too. See `examples/banking/bluebook/banking.hecksagon`
65
+ # for the pattern — a real `.hecksagon` file can hold more than one
66
+ # `Hecks.hecksagon` call, one per domain it wires.
67
+ # IDEMPOTENT, PER REGISTRY — `uses_framework` is now called more than
68
+ # once for the SAME member within a single boot (S8: a domain
69
+ # attaching Governance for its own role check, plus a framework
70
+ # sibling attaching it too, e.g. `banking.hecksagon`'s Identity
71
+ # block). `Kernel.load` always re-executes the file, unlike
72
+ # `require`, so a second call would re-run `Hecks.bluebook
73
+ # "Governance"` a second time — and the self-hosting meta-domain
74
+ # records every declaration as a real dispatched command against its
75
+ # own ledger, so a second `Declare` for the SAME aggregate is a real
76
+ # `AlreadyExists`, not a no-op. Skipped once the member's bluebook is
77
+ # already registered in THIS registry — the same chapter, not merely
78
+ # a same-named one from a stale prior boot.
79
+ def self.load!(name)
80
+ path = members.fetch(name.to_s) do
81
+ raise Runtime::WiringError,
82
+ "no framework member named #{name.inspect} — known: #{members.keys.sort.join(', ')}"
83
+ end
84
+
85
+ return if Hecks.current_registry.bluebook(name.to_s)
86
+
87
+ Kernel.load(path)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,67 @@
1
+ module Hecks
2
+ # THE ONE PLACE THAT KNOWS HOW TO FREEZE A DOMAIN VALUE.
3
+ #
4
+ # Freezing here has been fixed four times in four places, each time by
5
+ # topping the container and leaving the contents: list attributes, the
6
+ # event log, query rows, and value objects. `.freeze` on a Hash stops a
7
+ # key being added or removed and NOTHING ELSE — the String, Array or
8
+ # Hash a field holds stays mutable, so a caller reaches straight
9
+ # through and edits in place. Each fix looked complete and none was.
10
+ #
11
+ # WHAT SHOULD BE FROZEN, and why it is not everything:
12
+ #
13
+ # A VALUE OBJECT has no identity to change over. `with` already
14
+ # answers a new one rather than mutating, so freezing it through is
15
+ # what it always claimed to be.
16
+ #
17
+ # AN EMITTED EVENT is a record of something that happened. A mutable
18
+ # audit trail is not one.
19
+ #
20
+ # A QUERY ROW is an answer, not a handle — mutating one edits nobody's
21
+ # state and silently disagrees with the store.
22
+ #
23
+ # AN INSTANCE'S STATE is NOT frozen, deliberately: the interpreter
24
+ # builds it up across a dispatch, and a command's whole job is to
25
+ # change it. Its VALUES are frozen; the holder is not.
26
+ #
27
+ # A COMMAND'S ARGUMENTS are not frozen either. They arrive from
28
+ # outside, are normalised and coerced on the way in, and the coerced
29
+ # result is what becomes a frozen value.
30
+ module Freezer
31
+ module_function
32
+
33
+ # Numbers, symbols, nil and booleans are already immediate or frozen;
34
+ # a Value froze itself when it was built. What is left is the mutable
35
+ # trio, and each has to be WALKED rather than topped.
36
+ def deep(held)
37
+ case held
38
+ when Hash then held.each_value { |inner| deep(inner) }.freeze
39
+ when Array then held.each { |inner| deep(inner) }.freeze
40
+ when String then held.freeze
41
+ else held
42
+ end
43
+ end
44
+
45
+ # The question a gate asks, rather than the act. Answers false for the
46
+ # FIRST thing that is reachable and mutable, which is what makes a
47
+ # failure message worth reading.
48
+ def deeply_frozen?(held) = unfrozen_within(held).nil?
49
+
50
+ # The path to the first mutable thing reachable from `held`, or nil.
51
+ # A path rather than a boolean because "something in this event is
52
+ # mutable" is not an actionable sentence.
53
+ def unfrozen_within(held, path = [])
54
+ return (path.empty? ? "(the value itself)" : path.join(".")) unless immune?(held) || held.frozen?
55
+
56
+ case held
57
+ when Hash then held.lazy.filter_map { |key, inner| unfrozen_within(inner, path + [key.to_s]) }.first
58
+ when Array then held.each_with_index.lazy.filter_map { |inner, i| unfrozen_within(inner, path + [i.to_s]) }.first
59
+ end
60
+ end
61
+
62
+ # Immediates are frozen in every Ruby that matters, but asking
63
+ # `frozen?` of them and trusting the answer has bitten enough people
64
+ # that it is worth being explicit.
65
+ def immune?(held) = held.nil? || held == true || held == false || held.is_a?(Numeric) || held.is_a?(Symbol)
66
+ end
67
+ end
@@ -0,0 +1,86 @@
1
+ module Hecks
2
+ module Fuzzing
3
+ # A value that is DELIBERATELY THE WRONG SHAPE for the attribute it is handed
4
+ # to — the sibling of ValueGenerator, which only ever builds values that satisfy
5
+ # a declared type.
6
+ #
7
+ # That limit is why the fuzzer could not reach the space a hand-written
8
+ # adversarial corpus covers. spec/corpus/banking.json got there one step at a
9
+ # time, by hand : a value object where an Integer is declared, a Float where an
10
+ # Integer is declared, a numeral wearing quotes, an attribute missing entirely,
11
+ # an attribute the command never declared. One of those — a hash reaching a
12
+ # refusal message — is the exact shape that exposed refusal wording drift
13
+ # (a composite rendered with `inspect` at one site and JSON at another —
14
+ # Rendering's whole story), and no generated value
15
+ # could ever have produced it.
16
+ #
17
+ # These are expected to be REFUSED, and that is the point : a refusal is an
18
+ # answer, and its wording is pinned byte-for-byte. The bugs live in
19
+ # the sentence, not in the happy path.
20
+ module InvalidValueGenerator
21
+ module_function
22
+
23
+ # Each kind names a real confusion, not random noise. Ordered roughly by how
24
+ # often this codebase has actually been bitten by it.
25
+ KINDS = %i[
26
+ object_for_scalar
27
+ scalar_for_object
28
+ float_for_integer
29
+ numeral_string
30
+ array_for_scalar
31
+ boolean_for_string
32
+ null
33
+ ].freeze
34
+
35
+ def corrupt(attribute, aggregate, random:)
36
+ kind = kinds_for(attribute, aggregate).sample(random: random)
37
+ build(kind, attribute, aggregate, random: random)
38
+ end
39
+
40
+ # Only the confusions that MEAN anything for this attribute. Offering
41
+ # `scalar_for_object` for a plain Integer would just be a second spelling of
42
+ # `numeral_string`, and a kind that cannot be wrong for the attribute it is
43
+ # handed teaches the corpus nothing.
44
+ def kinds_for(attribute, aggregate)
45
+ value_object = aggregate.value_object(attribute.type.to_s)
46
+ return %i[scalar_for_object array_for_scalar null] if value_object
47
+ return %i[object_for_scalar float_for_integer numeral_string array_for_scalar null] if attribute.type.to_s == "Integer"
48
+
49
+ %i[object_for_scalar array_for_scalar boolean_for_string null]
50
+ end
51
+
52
+ def build(kind, attribute, aggregate, random:)
53
+ case kind
54
+ when :object_for_scalar then { "cents" => random.rand(1..1000) }
55
+ when :scalar_for_object then scalar_for(attribute, aggregate, random: random)
56
+ when :float_for_integer then random.rand(1.0..100.0).round(2)
57
+ when :numeral_string then random.rand(1..1000).to_s
58
+ when :array_for_scalar then [random.rand(1..10), random.rand(1..10)]
59
+ when :boolean_for_string then random.rand(2).zero?
60
+ else nil
61
+ end
62
+ end
63
+
64
+ # A bare scalar where a value object is declared. A SINGLE-FIELD value object
65
+ # legitimately accepts one (that is the standing-in rule every domain relies
66
+ # on), so the interesting case is a value object with SEVERAL fields, where a
67
+ # scalar cannot stand for anything and the refusal has to say so.
68
+ def scalar_for(attribute, aggregate, random:)
69
+ value_object = aggregate.value_object(attribute.type.to_s)
70
+ return "a bare scalar" unless value_object && value_object.attributes.size == 1
71
+
72
+ # One field, so a scalar is legal — corrupt the FIELD's own type instead,
73
+ # which is still a shape the attribute cannot accept.
74
+ { value_object.attributes.first.name.to_s => ["nested", "array"] }
75
+ end
76
+
77
+ # An attribute the command never declared. `refuse_unknown_arguments` is a
78
+ # real dispatch step (Vocabulary::AggregateDispatchOrder), and nothing
79
+ # generated had ever exercised it.
80
+ def undeclared_argument(random:)
81
+ name = %w[colour flavour rank note].sample(random: random)
82
+ [name, %w[red loud third scribbled].sample(random: random)]
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,92 @@
1
+ require "fileutils"
2
+ require "tmpdir"
3
+
4
+ module Hecks
5
+ module Fuzzing
6
+ # A FRESH, IN-PROCESS ADAPTER FOR EVERY EPHEMERAL BOOT.
7
+ #
8
+ # Fuzzing/replay copies a domain to a tmpdir and boots from there
9
+ # specifically to get zero-history state — `rm_rf`ing the copy's own
10
+ # `data/` achieves that for a file-based adapter (Memory,
11
+ # SqlitePersistence, Heki), because copying the DIRECTORY copies the
12
+ # store. It achieves nothing for an adapter that lives outside the
13
+ # copied directory entirely — Postgres, named by a fixed connection
14
+ # string in `.world` (examples/pizzas/bluebook/pizzas.world, for
15
+ # instance). Copying the directory does not copy or isolate the
16
+ # DATABASE, so a "fresh" boot against a Postgres-bound domain would
17
+ # still see every record any other run, ever, wrote to it.
18
+ #
19
+ # So every `.hecksagon` in the copy gets its persistence binding
20
+ # rewritten to Memory before booting, and any `projected_by` bind
21
+ # dropped outright (optional — `Registry#read_repository` already
22
+ # falls back to the authoritative repository when none exists). The
23
+ # domain's own rules and shape are untouched ; only WHICH adapter this
24
+ # one ephemeral copy answers through changes. What the domain is
25
+ # bound to for real deployment is never touched — only this tmp copy.
26
+ module IsolatedBoot
27
+ module_function
28
+
29
+ def call(domain_path)
30
+ Dir.mktmpdir("hecks-fuzz") do |tmp|
31
+ copy = File.join(tmp, File.basename(domain_path))
32
+ copy_dereferencing(domain_path, copy)
33
+ FileUtils.rm_rf(File.join(copy, "data"))
34
+ rebind_to_memory!(copy)
35
+ yield copy
36
+ end
37
+ end
38
+
39
+ # SYMLINKS ARE FOLLOWED, NOT COPIED. `FileUtils.cp_r` reproduces a
40
+ # symlink AS a symlink, and a RELATIVE one then points at nothing
41
+ # from a tmpdir — `lib/hecks/framework/bluebook/compliance
42
+ # .bluebook` is exactly that, a link to
43
+ # `examples/compliance/bluebook/compliance.bluebook`, so the whole
44
+ # framework domain failed to boot here with a LoadError naming a
45
+ # path under /var/folders that had never existed. Nothing about the
46
+ # domain was wrong; the copy was.
47
+ #
48
+ # `FileUtils.cp` follows a symlink and copies its CONTENT, which is
49
+ # what an isolated boot wants: the copy has to stand alone, since
50
+ # rebind_to_memory! rewrites files in it and must not reach back
51
+ # through a link into the real tree.
52
+ def copy_dereferencing(source, destination)
53
+ FileUtils.mkdir_p(destination)
54
+ Dir.glob(File.join(source, "**", "*"), File::FNM_DOTMATCH).each do |path|
55
+ next if [".", ".."].include?(File.basename(path))
56
+
57
+ target = File.join(destination, path.delete_prefix("#{source}/"))
58
+ if File.directory?(path)
59
+ FileUtils.mkdir_p(target)
60
+ else
61
+ FileUtils.mkdir_p(File.dirname(target))
62
+ FileUtils.cp(path, target)
63
+ end
64
+ end
65
+ end
66
+
67
+ def rebind_to_memory!(copy)
68
+ Dir.glob(File.join(copy, "**", "*.hecksagon")).each do |path|
69
+ # `persisted_by`/`projected_by` can be spelled two ways now —
70
+ # aggregate-scoped (`Banking::Customer.persisted_by("Heki")`,
71
+ # always parenthesised) and, since §0's domain-level default
72
+ # binds, a bare call at the hecksagon's own root
73
+ # (`persisted_by "Heki"`, no parens, no receiver). Both must
74
+ # be caught here or a domain that only declares the bare form
75
+ # keeps its real adapter under an "isolated" fuzz boot.
76
+ lines = File.readlines(path).reject { |line| line.match?(/\bprojected_by\s*\(?\s*"/) }
77
+ File.write(path, lines.join.gsub(/persisted_by\s*\(?\s*"[^"]+"\s*\)?/, 'persisted_by("Memory")'))
78
+ end
79
+
80
+ # THE SETTINGS, NOT JUST THE BIND — `WorldBuilder#method_missing`
81
+ # stores a settings block under BOTH "verb:adapter" and the bare
82
+ # "verb" (world_builder.rb:32-33), so a bind rewritten to Memory
83
+ # still falls back to whatever adapter's settings were declared
84
+ # bare — Postgres's `database:`, which Memory does not take and
85
+ # WiringError refuses on sight. Memory needs no settings at all
86
+ # (a domain with no .world boots it as the default), so the
87
+ # simplest correct fix is dropping .world from the copy entirely.
88
+ Dir.glob(File.join(copy, "**", "*.world")).each { |path| File.delete(path) }
89
+ end
90
+ end
91
+ end
92
+ end