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,154 @@
1
+ require "json"
2
+ require_relative "handle"
3
+ require_relative "command_request"
4
+ require_relative "../naming"
5
+ require_relative "../runtime/errors"
6
+ require_relative "../runtime/value"
7
+
8
+ module Hecks
9
+ module Facade
10
+ # THE JSON DOOR — WHERE Facade MEETS BODY-IN/BODY-OUT CALLERS.
11
+ #
12
+ # `Handle`/`Surface` are Ruby sugar over the dispatcher for a Ruby caller
13
+ # holding real objects — a symbol verb name, a `**kwargs` payload, a
14
+ # `Handle` back in hand. A REST-ish JSON API is a caller holding STRINGS
15
+ # instead: a URL segment naming a collection, a URL segment naming a
16
+ # record or a verb, a parsed request body whose every key arrived as a
17
+ # String because that is all JSON ever gives. Every app that wants to put
18
+ # a JSON API in front of a booted domain has to do that translation —
19
+ # name to class, string to symbol, nested `Runtime::Value` back to plain
20
+ # data — and one sibling app had already hand-written it once, bespoke,
21
+ # against its own routes, before this existed. This is that translation
22
+ # pulled out, generic, reading the SAME IR the rest of the facade already
23
+ # reads rather than re-deriving "how do I find an aggregate by name".
24
+ #
25
+ # NO HTTP LIVES HERE. Same discipline `Router` and `Surface` already
26
+ # hold: this module never sees a request object, never picks a status
27
+ # code, never calls `halt`. Every method here takes plain Ruby values in
28
+ # — a raw JSON String is the one exception, see `.parse` below, every
29
+ # other input is already-parsed data — and returns plain Ruby values
30
+ # out, or raises. Turning a raw request body into that input, and
31
+ # turning a raised exception into an HTTP status, both stay the calling
32
+ # app's job, exactly the way they already are for `Router#dispatch`.
33
+ #
34
+ # EVERY "THAT DOESN'T EXIST" CASE RAISES `Runtime::NotFound` — THE SAME
35
+ # CLASS EVERY TIME, NOT A NEW ONE PER CALLER. `Runtime::NotFound` already
36
+ # sits in `Runtime::DOMAIN_REFUSALS` (runtime/errors.rb) — the family a
37
+ # booted app already has, or trivially can have, ONE generic `error`
38
+ # handler for, mapping the whole family to a status code without a
39
+ # special case per refusal. A bespoke `JsonDoor::CollectionNotFound` (or
40
+ # three of those, one per flavor of "not found") would just force every
41
+ # app that adopts this door to widen its rescue clause to match it — the
42
+ # opposite of what a shared refusal family is for. So "no such
43
+ # aggregate", "no creating command", "no such command", and "no record
44
+ # with that id" all raise the one class, distinguished only by message.
45
+ module JsonDoor
46
+ module_function
47
+
48
+ # "Banking", "Customer" -> the `Banking::Customer` class `.find` /
49
+ # `.create_...` / etc already answer for — the same class
50
+ # `Facade::Handle`'s own reference accessors reach with
51
+ # `Object.const_get` (see handle.rb's `define_reference_accessors`).
52
+ #
53
+ # Checked against the CURRENT boot's IR first, not against Ruby's
54
+ # constant table directly — a name that names nothing in this
55
+ # registry should refuse before ever asking Ruby whether some
56
+ # same-named constant happens to exist (possibly a stale one, left
57
+ # over from an earlier boot in this same process — the exact hazard
58
+ # `AggregateDoor#port`'s own comment describes at length). Only once
59
+ # the IR confirms the aggregate is real does this read the constant
60
+ # the current boot's `Surface.install` actually minted for it.
61
+ def aggregate(dispatcher, domain, name)
62
+ ir = dispatcher.registry.bluebook(domain)&.aggregate(name)
63
+ raise Runtime::NotFound, "#{domain} declares no aggregate named #{name.inspect}" unless ir
64
+
65
+ Object.const_get("#{domain}::#{ir.hecks_name}")
66
+ end
67
+
68
+ # The one command a POST to a bare collection URL means — "make one of
69
+ # these". `AggregateDoor` already enforces exactly one creating
70
+ # command per aggregate (`Command#creates?`, true exactly when the
71
+ # command declares no `references`); this just names it the same
72
+ # snake_case-plus-bang a Ruby caller would already ask the door for
73
+ # (`Naming.snake`, the identical call `AggregateDoor` itself makes
74
+ # when it defines that singleton method in the first place — `!`
75
+ # because every command does now, door and Handle alike).
76
+ def creating_command(klass)
77
+ creating = klass.ir.commands.find(&:creates?)
78
+ raise Runtime::NotFound, "#{klass.ir.hecks_name} declares no creating command" unless creating
79
+
80
+ "#{Naming.snake(creating.hecks_name)}!"
81
+ end
82
+
83
+ # A URL segment or a JSON body's "command" field, checked against what
84
+ # the aggregate actually declares. `klass.commands` is the same
85
+ # sorted, snake_cased list `AggregateDoor` built for its own door, so
86
+ # a name this accepts is a name a `Handle` can actually dispatch.
87
+ def validate_command!(klass, name)
88
+ wanted = name.to_s
89
+ return wanted if klass.commands.include?(wanted)
90
+
91
+ raise Runtime::NotFound, "#{klass.ir.hecks_name} declares no command named #{wanted.inspect}"
92
+ end
93
+
94
+ # `klass.find` already answers nil-on-miss — the right shape for a
95
+ # Ruby caller that means to check for itself. A JSON caller asking for
96
+ # one record by id off a URL means to HAVE it, or answer 404 — this is
97
+ # that stricter wrapper, raising the same `Runtime::NotFound` the rest
98
+ # of this door raises rather than handing back nil for the caller to
99
+ # remember to check.
100
+ def find!(klass, id)
101
+ klass.find(id) or raise Runtime::NotFound, "no #{klass.ir.hecks_name} found for id #{id.inspect}"
102
+ end
103
+
104
+ # JSON only ever hands back String keys. A command's args, and every
105
+ # nested value-object literal inside them, need symbol keys before
106
+ # `Handle`/`Dispatcher` will accept them at all — this is that
107
+ # recursive conversion, blind to how deep a body nests.
108
+ def deep_symbolize(value)
109
+ case value
110
+ when Hash then value.to_h { |k, v| [k.to_sym, deep_symbolize(v)] }
111
+ when Array then value.map { |item| deep_symbolize(item) }
112
+ else value
113
+ end
114
+ end
115
+
116
+ # The other direction: a `Handle`, or a query row's plain state hash,
117
+ # carrying a `Runtime::Value` at every level a value object sits at —
118
+ # down to plain Ruby a JSON encoder can walk without knowing what a
119
+ # `Runtime::Value` is.
120
+ #
121
+ # `Runtime::Value.materialize` (runtime/value.rb) already does the
122
+ # actual recursion — its `Hash` branch calls itself on every value,
123
+ # its `Value` branch unwraps through `#to_h`, which itself calls
124
+ # `materialize` on every field, so a value object three levels deep
125
+ # unwraps three levels deep for free. This is not a second unwrapper
126
+ # sitting next to it: `materialize` covers `Hash`/`Array`/`Value`
127
+ # already, and a `Handle` is none of those three, so the one thing
128
+ # this adds is `#to_h`'ing a `Handle` first so `materialize`'s own
129
+ # `Hash` case can take it from there.
130
+ def materialize(value)
131
+ value = value.to_h if value.is_a?(Handle)
132
+ Runtime::Value.materialize(value)
133
+ end
134
+
135
+ # Parsed JSON and raw JSON text cross the same receiver/payload boundary
136
+ # as CLI and forms. The result is ready to splat into Dispatcher#dispatch
137
+ # and contains no loose routing fields.
138
+ def command_request(body, receiver:, legacy_receiver: nil)
139
+ input = body.is_a?(String) ? parse(body) : body
140
+ CommandRequest.normalize(input, receiver: receiver, legacy_receiver: legacy_receiver)
141
+ end
142
+
143
+ # The one place a raw JSON string is legitimate input for this door —
144
+ # a POST body, still text at the point a generic, HTTP-blind layer can
145
+ # see it. `JSON::ParserError` already names "this wasn't JSON" exactly
146
+ # right ; wrapping it in a bespoke `JsonDoor`-specific class would
147
+ # only be a second name for the same fact, so it propagates exactly as
148
+ # `JSON.parse` raises it — a calling app catches it the same standard
149
+ # way it would catch any other malformed-input error, no new class to
150
+ # learn.
151
+ def parse(raw_json) = JSON.parse(raw_json)
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,185 @@
1
+ require_relative "../../bluebook/dsl/hecksagon_builder"
2
+ require_relative "../../bluebook/dsl/domain_port_builder"
3
+ require_relative "../../bluebook/dsl/const_shim"
4
+ require_relative "../../bluebook/hexagon"
5
+ require_relative "../handle"
6
+ require_relative "../../naming"
7
+
8
+ module Hecks
9
+ module Facade
10
+ module Surface
11
+ # One aggregate's door: creating verbs as module methods returning
12
+ # the record in hand, CRUD delegation to the repository, the
13
+ # `persisted_by`-style binding collector a `.hecksagon` lands on, and
14
+ # the aggregate-scoped `port` a `.hecksagon` lands on beside it.
15
+ module AggregateDoor
16
+ def aggregate_module(dispatcher, domain, ir)
17
+ fqn = "#{domain}::#{ir.hecks_name}"
18
+ door = Module.new
19
+
20
+ ir.attributes.each do |attribute|
21
+ next unless RESERVED.include?(attribute.name.to_sym)
22
+
23
+ warn "[hecks] #{ir.hecks_name}##{attribute.name} shadows a built-in — no reader defined"
24
+ end
25
+
26
+ # A creating verb is a MODULE method returning the new record in hand ;
27
+ # a verb that reaches an existing record lives on the Handle. `!` —
28
+ # a command DOES something (mutates, may refuse), Ruby's own
29
+ # convention for that ; `Naming.snake` alone used to leave a
30
+ # creating command's bare name claiming the exact spelling a
31
+ # QUERY of the SAME business name also wants (`Account`'s own
32
+ # "Open" — the creating command AND a query listing open
33
+ # accounts, a real same-aggregate collision this corpus already
34
+ # has) — the suffix is what makes both nameable at all, not
35
+ # merely a style choice.
36
+ ir.commands.select(&:creates?).each do |command|
37
+ door.define_singleton_method("#{Naming.snake(command.hecks_name)}!") do |**args|
38
+ Handle.new(dispatcher: dispatcher, domain: domain, ir: ir,
39
+ instance: dispatcher.dispatch("#{fqn}.#{command.hecks_name}", with: args).instance)
40
+ end
41
+ end
42
+
43
+ # A query is a MODULE method too — same level as a creating
44
+ # command, since neither needs an existing record in hand — but
45
+ # bare: a query reads and returns, nothing to warn a caller
46
+ # about the way `!` does for a command. Answers the raw row
47
+ # array `dispatcher.query` itself answers, the same shape
48
+ # `runtime.query("#{fqn}.Name")` already gave.
49
+ ir.queries.each do |query|
50
+ door.define_singleton_method(Naming.snake(query.hecks_name)) do |**args|
51
+ dispatcher.query("#{fqn}.#{query.hecks_name}", **args)
52
+ end
53
+ end
54
+
55
+ door.define_singleton_method(:fqn) { fqn }
56
+ door.define_singleton_method(:ir) { ir }
57
+
58
+ # THE SAME VERB THE CHAPTER ANSWERS, one level down. Every
59
+ # construct emits its own IR (Hecks::IR), so an aggregate
60
+ # is a legitimate thing to project — `Pizzas::Order.project(
61
+ # Projections::IR)` is this aggregate's IR, not the chapter's.
62
+ #
63
+ # A chapter-scoped target refuses here rather than inventing an
64
+ # answer: `Projector.admits!` is what tells `Projections::IR`
65
+ # (`from: :any`) apart from `Projections::Shape`
66
+ # (`from: :chapter`), which used to return a confidently empty
67
+ # `{"aggregates" => []}` for an aggregate.
68
+ door.define_singleton_method(:project) do |target, out: nil, **options|
69
+ key = Projector.key_for(target)
70
+ artifact = Projector.call(key, bluebook: ir, options: options)
71
+ return artifact unless out
72
+
73
+ Projector.write(artifact, out, as: Projector.emits_for(key))
74
+ end
75
+ door.define_singleton_method(:repository) { dispatcher.registry.repository(domain, ir) }
76
+ door.define_singleton_method(:commands) { ir.commands.map { |c| "#{Naming.snake(c.hecks_name)}!" }.sort }
77
+ door.define_singleton_method(:queries) { ir.queries.map { |q| Naming.snake(q.hecks_name) }.sort }
78
+ # ONE AGGREGATE'S USAGE DOCUMENT — the same projection the chapter
79
+ # answers with, narrowed to this head. `commands` above already
80
+ # answers "what can I call"; this answers "and what does each one
81
+ # want, refuse, and guarantee", which is the rest of the question.
82
+ door.define_singleton_method(:docs) do |**options|
83
+ Projector.call(:docs, bluebook: dispatcher.registry.bluebook(domain),
84
+ options: options.merge(aggregate: ir.hecks_name))
85
+ end
86
+ # ONE AGGREGATE, READ BACK IN ENGLISH — the same narrowing `:docs`
87
+ # takes, aimed at `:narrate` instead.
88
+ door.define_singleton_method(:narrate) do |**options|
89
+ Projector.call(:narrate, bluebook: dispatcher.registry.bluebook(domain),
90
+ options: options.merge(aggregate: ir.hecks_name))
91
+ end
92
+ door.define_singleton_method(:count) { dispatcher.registry.repository(domain, ir).count }
93
+ door.define_singleton_method(:events) { dispatcher.events.select { |event| event.aggregate == fqn } }
94
+
95
+ door.define_singleton_method(:find) do |id|
96
+ found = dispatcher.registry.repository(domain, ir).find(id)
97
+ found && Handle.new(dispatcher: dispatcher, domain: domain, ir: ir, instance: found)
98
+ end
99
+
100
+ door.define_singleton_method(:all) do |**opts|
101
+ dispatcher.registry.repository(domain, ir).all(**opts).map do |instance|
102
+ Handle.new(dispatcher: dispatcher, domain: domain, ir: ir, instance: instance)
103
+ end
104
+ end
105
+
106
+ # THE SAME REASON `method_missing` BELOW EXISTS AT ALL — a facade
107
+ # left over from a PREVIOUS boot in this process shadows the fresh
108
+ # `BindingProxy` a `.hecksagon` would otherwise reach through
109
+ # `ConstShim`/`const_missing`, so `Pizzas::Pizza.port(...)` lands
110
+ # HERE instead once any boot has run before.
111
+ #
112
+ # RE-RESOLVED, NOT THE CLOSED-OVER `ir` — this door can be a STALE
113
+ # one, built by a boot from earlier in this same process, sitting
114
+ # on the `Pizzas`/`Pizza` constants only because nothing has
115
+ # re-installed them since. Attaching to this door's OWN `ir` would
116
+ # attach the port to a discarded aggregate from that old boot,
117
+ # invisible to the CURRENT one actually being loaded — silently,
118
+ # the exact way `method_missing` below already has to avoid it for
119
+ # a plain bind, via `HecksagonBuilder.collector` rather than
120
+ # anything this door closes over. `Hecks.current_registry` is
121
+ # the same "whichever boot is actually in progress" indirection,
122
+ # and `BindingProxy#port` already re-resolves through it the same
123
+ # way — this is that door's fallback twin, not a shortcut past it.
124
+ door.define_singleton_method(:port) do |name, &block|
125
+ current = Hecks.current_registry&.bluebook(domain)&.aggregate(ir.hecks_name) or
126
+ raise Bluebook::DSL::Malformed, "#{fqn}.port(#{name.inspect}) called outside a boot"
127
+
128
+ domain_port = Bluebook::DSL::ConstShim.with(->(const) { const }) do
129
+ Bluebook::DSL::DomainPortBuilder.build(name, owner: current.hecks_name, &block)
130
+ end
131
+ domain_port.operations.each do |operation|
132
+ operation.attributes.select(&:reference?).each { |attribute| attribute.type.declared_in = current }
133
+ end
134
+ current.add_port(domain_port)
135
+ self
136
+ end
137
+
138
+ door.define_singleton_method(:method_missing) do |verb, *args, **kwargs, &block|
139
+ collector = Bluebook::DSL::HecksagonBuilder.collector
140
+ return super(verb, *args, **kwargs, &block) unless collector
141
+
142
+ collector << Bluebook::Bind.new(
143
+ aggregate: fqn,
144
+ verb: verb.to_s,
145
+ adapter: args.first.to_s,
146
+ role: kwargs[:role]&.to_s
147
+ )
148
+ block&.call
149
+ self
150
+ end
151
+
152
+ door.define_singleton_method(:respond_to_missing?) do |name, include_private = false|
153
+ !Bluebook::DSL::HecksagonBuilder.collector.nil? || super(name, include_private)
154
+ end
155
+
156
+ # THE SAME STALE-FACADE HAZARD `method_missing`/`port` ABOVE ALREADY
157
+ # DOCUMENT, one door lower — `Surface.install` installs an aggregate's
158
+ # OWN name as a bare TOP-LEVEL constant too (`Namespace.install(Object,
159
+ # aggregate.hecks_name, ...)`, surface.rb's own `install`), not only
160
+ # nested under its chapter. So once ANY domain has booted once in this
161
+ # process, `Account::Debit` written while declaring some OTHER
162
+ # bluebook — same domain or a different one — reaches THIS door's
163
+ # const_missing directly, never `Object.const_missing`/`ConstShim::
164
+ # Hook` at all: real modules resolve without ever calling that.
165
+ #
166
+ # `ir.hecks_name`, NOT the qualified `fqn` — a scoped reference has to
167
+ # read the SAME either way, whether or not a stale door happens to be
168
+ # sitting on this process from an earlier boot; qualifying it here
169
+ # would make `Account::Debit`'s own meaning depend on incidental
170
+ # process history, which is the exact instability S0b exists to
171
+ # remove (docs/dsl-work-slices.md — "two domains in one registry" is
172
+ # this file's own reason for being owned by that slice).
173
+ door.define_singleton_method(:const_missing) do |name|
174
+ resolver = Bluebook::DSL::ConstShim.resolver
175
+ return resolver.call("#{ir.hecks_name}::#{name}") if resolver
176
+
177
+ super(name)
178
+ end
179
+
180
+ door
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,107 @@
1
+ require_relative "../../bluebook/dsl/binding_proxy"
2
+ require_relative "../../bluebook/dsl/const_shim"
3
+ require_relative "../../bluebook/dsl/hecksagon_builder"
4
+
5
+ module Hecks
6
+ module Facade
7
+ module Surface
8
+ # One chapter's module: vision and aggregate roll-call on the
9
+ # singleton, one aggregate door per declared head, and the
10
+ # declaration hook for names the door does not carry.
11
+ module Chapter
12
+ def chapter_module(dispatcher, bluebook)
13
+ chapter = Module.new
14
+ chapter.define_singleton_method(:vision) { bluebook.vision }
15
+ chapter.define_singleton_method(:aggregates) { bluebook.aggregates.map(&:name).sort }
16
+
17
+ # THE CHAPTER, EXPLAINING ITSELF. `Projector::DocsProjector` reads
18
+ # nothing but this bluebook's own IR, so the document cannot drift
19
+ # from the domain — the same guarantee `bin/reference` gives the DSL
20
+ # reference by generating it from the Syntax chapter.
21
+ #
22
+ # A METHOD RATHER THAN ONLY A SCRIPT because that is what gets it
23
+ # read: `QualityControl.docs` in a console, beside `vision` and
24
+ # `aggregates`, at the moment somebody is wondering what a verb
25
+ # wants. Projected on each call rather than memoised — it is a pure
26
+ # read of the IR, and a stale document is the entire thing this
27
+ # exists to prevent.
28
+ chapter.define_singleton_method(:docs) do |**options|
29
+ Projector.call(:docs, bluebook: bluebook, options: options)
30
+ end
31
+
32
+ # THE CHAPTER, READ BACK IN ENGLISH — `Projector::NarrateProjector`
33
+ # beside `:docs`, for the reader who needs to confirm the domain is
34
+ # right rather than call it: `QualityControl.narrate` in a console.
35
+ chapter.define_singleton_method(:narrate) do |**options|
36
+ Projector.call(:narrate, bluebook: bluebook, options: options)
37
+ end
38
+
39
+ # THE DOMAIN'S OWN IR, PROJECTED. `Projector` has taken
40
+ # `call(name, bluebook:, options:)` since §30, but nothing could
41
+ # reach it from a booted domain — this module already closes
42
+ # over the one `Bluebook` every projector wants and simply
43
+ # never handed it out, so every bin/ projector rebuilt a
44
+ # Registry and reloaded the files to recover what was already
45
+ # sitting here.
46
+ #
47
+ # Pizzas.project(Projections::OIDC)
48
+ # Pizzas.project(Projections::Shape, out: "shape.json")
49
+ #
50
+ # No `to_ir` companion: `project(Projections::IR)` already
51
+ # answers with the canonical serialized form, and handing out
52
+ # the live graph as well would be a second way to say the same
53
+ # thing. Anything genuinely needing the graph is a projector,
54
+ # and a projector is given it.
55
+ #
56
+ # `out:` writes and everything else is passed through to the
57
+ # target — so `options` stays the projector's own vocabulary
58
+ # (`audience:` for OIDC) rather than a fixed set this method
59
+ # would have to know about.
60
+ chapter.define_singleton_method(:project) do |target, out: nil, **options|
61
+ key = Projector.key_for(target)
62
+ artifact = Projector.call(key, bluebook: bluebook, options: options)
63
+ return artifact unless out
64
+
65
+ Projector.write(artifact, out, as: Projector.emits_for(key))
66
+ end
67
+
68
+ bluebook.aggregates.each do |aggregate|
69
+ chapter.const_set(aggregate.hecks_name, aggregate_module(dispatcher, bluebook.name, aggregate))
70
+ end
71
+
72
+ # INSIDE A HECKSAGON, A NAME IS A DECLARATION, NOT A LOOKUP. A
73
+ # `.hecksagon` may name an aggregate this door does not carry — a STALE
74
+ # door from an earlier boot resolving another registry's chapter, the
75
+ # exact hazard the constant tree used to hide by reinstalling on every
76
+ # load. With a collector open, the name becomes a `BindingProxy`
77
+ # recording the same bind the aggregate module would ; without one, it
78
+ # is a genuine NameError, exactly as before.
79
+ #
80
+ # THE OTHER READER OF A MISS is S0b's own bridge (docs/dsl-work-
81
+ # slices.md, const_shim.rb's `ScopedConstant`): a bluebook still being
82
+ # DECLARED that names `#{bluebook.name}::Something` — an event, a
83
+ # command reference, an `admits:` — reaches HERE, not
84
+ # `Object.const_missing`, the moment this chapter's own facade already
85
+ # exists (a previous boot in the same process, or this same chapter
86
+ # re-entering its own name). `ConstShim.active?` is exactly as true
87
+ # here as it is at the top level, mid-declaration, and consulting the
88
+ # SAME resolver is what makes a scoped reference resolve identically
89
+ # whether or not a facade happens to be built yet — the earlier
90
+ # attempt's own failure mode (this file's own history) was a bridge
91
+ # that worked only BEFORE any facade existed.
92
+ chapter.define_singleton_method(:const_missing) do |name|
93
+ collector = Bluebook::DSL::HecksagonBuilder.collector
94
+ return Bluebook::DSL::BindingProxy.new("#{bluebook.name}::#{name}", collector) if collector
95
+
96
+ resolver = Bluebook::DSL::ConstShim.resolver
97
+ return resolver.call("#{bluebook.name}::#{name}") if resolver
98
+
99
+ super(name)
100
+ end
101
+
102
+ chapter
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,48 @@
1
+ require_relative "surface/chapter"
2
+ require_relative "surface/aggregate_door"
3
+
4
+ module Hecks
5
+ module Facade
6
+ # THE DOOR, WITHOUT THE CLASSES.
7
+ #
8
+ # `Pizzas::Pizza.create_pizza(...)` is the public surface (HANDOVER rule 3),
9
+ # and this is what serves it now : anonymous per-boot modules whose
10
+ # singleton methods close over the dispatcher and dispatch by FQN — the
11
+ # same shape `Router::NamespaceInstaller` proved. Nothing here is a domain
12
+ # class ; the IR is the only graph, and the door is a projection of it,
13
+ # installed by `Loader.bind_runtime` and re-installed whole on the next
14
+ # boot. How a chapter's module is built is surface/chapter.rb; how one
15
+ # aggregate's door is built is surface/aggregate_door.rb.
16
+ #
17
+ # The hexagon-binding door rides along : `Pizzas::Pizza.persisted_by("Heki")`
18
+ # in a `.hecksagon` file lands on the module's `method_missing`, which
19
+ # records an `Bind` into whatever `HecksagonBuilder.collector` is open
20
+ # AT CALL TIME — so even a facade left over from a previous boot records
21
+ # into the current builder, and a chapter with no constant at all falls
22
+ # through to `ConstShim` → `BindingProxy`, which mints byte-identical binds.
23
+ module Surface
24
+ # These would shadow the machinery a Handle runs on, so a field named one
25
+ # of them gets no reader and says so — the same warning the class door gave.
26
+ RESERVED = %i[id state events reload inspect to_h hash class].freeze
27
+
28
+ extend Chapter
29
+ extend AggregateDoor
30
+
31
+ module_function
32
+
33
+ def install(dispatcher)
34
+ dispatcher.registry.bluebooks.each_value do |bluebook|
35
+ chapter = chapter_module(dispatcher, bluebook)
36
+ Namespace.install(Object, bluebook.name, chapter)
37
+
38
+ bluebook.aggregates.each do |aggregate|
39
+ next if aggregate.hecks_name == bluebook.name
40
+
41
+ Namespace.install(Object, aggregate.hecks_name, chapter.const_get(aggregate.hecks_name, false))
42
+ end
43
+ end
44
+ dispatcher
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,44 @@
1
+ module Hecks
2
+ # The class-free public surface — see facade/surface.rb for the door and
3
+ # facade/handle.rb for the record in hand. Installed per boot by
4
+ # `Runtime::Loader.bind_runtime`.
5
+ module Facade
6
+ end
7
+
8
+ # The one careful way to put something at top level.
9
+ #
10
+ # Overwrites only what it itself installed (tracked in GENERATED), refuses —
11
+ # with a warning, never a raise — to clobber a constant belonging to user
12
+ # code or the stdlib. Which is why a chapter named `Set` never becomes a
13
+ # constant, and why `Registry` keeps its own table of chapters rather than
14
+ # trusting Ruby's. Each re-install REPLACES the previous boot's entry, so
15
+ # what GENERATED retains is one small facade module per name, not a graph
16
+ # per boot.
17
+ module Namespace
18
+ GENERATED = {}
19
+
20
+ module_function
21
+
22
+ def install(container, name, value)
23
+ name = name.to_s
24
+
25
+ if container.const_defined?(name, false)
26
+ current = container.const_get(name)
27
+ unless GENERATED[[container, name]].equal?(current)
28
+ warn "[hecks] #{name} is already defined — leaving it alone"
29
+ return current
30
+ end
31
+ container.send(:remove_const, name)
32
+ end
33
+
34
+ container.const_set(name, value)
35
+ GENERATED[[container, name]] = value
36
+ value
37
+ end
38
+ end
39
+ end
40
+
41
+ require_relative "facade/handle"
42
+ require_relative "facade/surface"
43
+ require_relative "facade/command_request"
44
+ require_relative "facade/json_door"