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,110 @@
1
+ require_relative "../naming"
2
+ require_relative "../projector"
3
+
4
+ module Hecks
5
+ module Projections
6
+ # AN OIDC CLIENT/SCOPE MANIFEST derived from a domain's own IR: what
7
+ # an identity provider has to know about this domain before it can
8
+ # issue a token that means anything here.
9
+ #
10
+ # THE ARTIFACT HALF OF SOMETHING ALREADY HALF-BUILT.
11
+ # `spec/oidc_projection_spec.rb` covers the INTEGRATION half — verified
12
+ # claims in, `IdentityResolution.resolve` → `Authorization.holds_role?`
13
+ # → a dispatch scoped by `Hecks.as_caller(role:)`. That half enforces
14
+ # a role per command. It just had no way to say, up front and as data,
15
+ # WHICH role each command wants — every answer came from asking the
16
+ # live runtime one dispatch at a time.
17
+ #
18
+ # This is that catalogue, and the two are checked against each other
19
+ # rather than merely coexisting: `verb` here is spelled exactly as
20
+ # `Runtime::Dispatcher#dispatch` takes it and exactly as
21
+ # `Bluebook#verbs` lists it, so a scope can never name a command
22
+ # the domain does not have (spec/projections/oidc_projection_spec.rb
23
+ # holds the two lists equal), and `role` is the same string
24
+ # `Ports::Authorization.holds_role?` compares against a real
25
+ # `Governance::RoleAssignment`.
26
+ #
27
+ # ROLES COME FROM THE COMMANDS, NOT FROM GOVERNANCE. A command's own
28
+ # `role "Compliance officer"` is in the bluebook IR
29
+ # (`Command#role`), whereas `uses_framework "Governance"` is
30
+ # declared in the `.hecksagon` — which `call(bluebook:, options:)`
31
+ # cannot see at all. Reading the commands is both the only thing
32
+ # available here AND the more accurate source: it says what each
33
+ # command actually demands, not merely which role vocabulary the
34
+ # application happened to mount.
35
+ #
36
+ # A command with no declared role projects `"role" => nil` rather
37
+ # than being dropped — an unguarded command is a real fact about the
38
+ # domain, and silently omitting it would make the manifest read as
39
+ # though the command did not exist.
40
+ module OIDC
41
+ extend Projector::Target
42
+
43
+ projects_as :oidc
44
+
45
+ module_function
46
+
47
+ # `audience:` overrides the domain name, for the ordinary case
48
+ # where the IdP's registered audience is a URL rather than a bare
49
+ # chapter name.
50
+ def call(bluebook:, options: {})
51
+ scopes = scopes_for(bluebook)
52
+
53
+ {
54
+ "audience" => (options[:audience] || bluebook.name).to_s,
55
+ "scopes" => scopes,
56
+ "roles" => scopes.map { |scope| scope["role"] }.compact.uniq.sort
57
+ }
58
+ end
59
+
60
+ # Sorted by scope, not left in declaration order — a manifest is
61
+ # something two versions of get diffed, and the same precedent
62
+ # `StorageShape.project` sets for its own aggregate list.
63
+ #
64
+ # Recurses into entities, not just an aggregate's own direct
65
+ # commands — the exact same gap `Bluebook#verbs` closed for the
66
+ # same reason (`chapter.rb`'s own comment): a command reached
67
+ # through `Dispatcher#dispatch`'s dotted `Entity.Command` routing
68
+ # is real and callable, so a manifest that never names it can
69
+ # never grant a client a scope for it either.
70
+ def scopes_for(bluebook)
71
+ bluebook.aggregates.flat_map { |aggregate| aggregate_scopes(bluebook, aggregate) }
72
+ .sort_by { |scope| scope["scope"] }
73
+ end
74
+
75
+ def aggregate_scopes(bluebook, aggregate)
76
+ verb_prefix = "#{bluebook.name}::#{aggregate.hecks_name}"
77
+ scope_prefix = "#{Naming.snake(bluebook.name)}:#{Naming.snake(aggregate.hecks_name)}"
78
+
79
+ command_scopes(aggregate.commands, verb_prefix, scope_prefix) +
80
+ aggregate.entities.flat_map { |entity| entity_scopes(entity, verb_prefix, scope_prefix) }
81
+ end
82
+
83
+ # S17, ADR 0026 — an entity can nest further entities (`Dispatch`,
84
+ # inside `Handler`), so this recurses the same way `Chapter#verbs`
85
+ # now does. The verb and scope prefixes grow in lockstep, each
86
+ # `.`-joined the same way its own kind already was.
87
+ def entity_scopes(entity, verb_prefix, scope_prefix)
88
+ verb_prefix = "#{verb_prefix}.#{entity.hecks_name}"
89
+ scope_prefix = "#{scope_prefix}.#{Naming.snake(entity.hecks_name)}"
90
+
91
+ command_scopes(entity.commands, verb_prefix, scope_prefix) +
92
+ entity.entities.flat_map { |piece| entity_scopes(piece, verb_prefix, scope_prefix) }
93
+ end
94
+
95
+ # `banking:account.open` — the shape an OIDC scope is conventionally
96
+ # spelled in, and snake_cased through the SAME `Naming.snake` the
97
+ # facade uses to name a command's own door method, so a scope and
98
+ # the Ruby call that satisfies it cannot drift apart.
99
+ def command_scopes(commands, verb_prefix, scope_prefix)
100
+ commands.map do |command|
101
+ {
102
+ "scope" => "#{scope_prefix}.#{Naming.snake(command.hecks_name)}",
103
+ "verb" => "#{verb_prefix}.#{command.hecks_name}",
104
+ "role" => command.role
105
+ }
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,159 @@
1
+ require_relative "../projector"
2
+
3
+ module Hecks
4
+ module Projections
5
+ # THE RUST PARSER'S KEYWORD TABLE, projected from the chapter's own
6
+ # Syntax aggregate — "the parser's grammar knowledge is DERIVED from
7
+ # hecks's self-description, not hand-typed a second time", which
8
+ # is the anti-drift idea the whole Rust-parser plan rests on.
9
+ #
10
+ # A REGISTERED TARGET NOW, rather than a module living inside its own
11
+ # bin/ script. It was already a projection in everything but call
12
+ # shape; this only stops it being a fifth way of spelling one.
13
+ module ParserTable
14
+ extend Projector::Target
15
+ projects_as :parser_table, declares: "Syntax"
16
+
17
+ module_function
18
+
19
+ def call(bluebook:, options: {}) = render(bluebook)
20
+
21
+ module_function
22
+
23
+ # THE CHAPTER IS HANDED OVER, not reached for. This used to open the
24
+ # grammar registry itself, which meant the projection could only ever
25
+ # project one chapter — the language's own. Taking it as an argument is
26
+ # what the projector protocol asks for, and it costs nothing.
27
+ def syntax(bluebook) = bluebook.aggregate("Syntax")
28
+
29
+ # Every cell as text — exactly spec/syntax_conformance_spec.rb's own
30
+ # `rows` helper, reused rather than re-derived: a member's fields decode
31
+ # back through typed literal decoding on the way out of reconstruction,
32
+ # and this reads it back as what was written.
33
+ def rows(bluebook, name)
34
+ syntax(bluebook).value_objects.find { |vo| vo.hecks_name == name }
35
+ .members.map { |row| row.to_h.transform_values(&:to_s) }
36
+ end
37
+
38
+ def closed_set_members(bluebook, name) = rows(bluebook, name).map { |row| row[:name] }
39
+
40
+ KEYWORD_FIELDS = %i[word context body inner opens fills status was resolves_via disambiguator].freeze
41
+ ARGUMENT_FIELDS = %i[keyword context at named kind required fills selects
42
+ pair_key_fills pair_value_fills pairs_shape status variadic minimum].freeze
43
+
44
+ # A Rust string literal for one field's value — every field here is
45
+ # plain ASCII (a word, a context name, a digit, "true"/"false"), so this
46
+ # only has to be safe against the two characters Rust string literals
47
+ # themselves reserve.
48
+ def rust_string(value) = "\"#{value.to_s.gsub('\\', '\\\\\\\\').gsub('"', '\\"')}\""
49
+
50
+ def keyword_row(row)
51
+ fields = KEYWORD_FIELDS.map { |field| rust_string(row[field]) }
52
+ " KeywordRow { #{KEYWORD_FIELDS.zip(fields).map { |name, value| "#{name}: #{value}" }.join(', ')} },"
53
+ end
54
+
55
+ def argument_row(row)
56
+ fields = ARGUMENT_FIELDS.map { |field| rust_string(row[field]) }
57
+ " ArgumentRow { #{ARGUMENT_FIELDS.zip(fields).map { |name, value| "#{name}: #{value}" }.join(', ')} },"
58
+ end
59
+
60
+ def const_str_array(name, values)
61
+ lines = values.map { |value| " #{rust_string(value)}," }
62
+ "pub static #{name}: &[&str] = &[\n#{lines.join("\n")}\n];\n"
63
+ end
64
+
65
+ # S14, ADR 0026 — Keyword/Argument are genuine entities of Syntax
66
+ # now, dispatched (not merely declared) so their own `status`
67
+ # really is a lifecycle. `SyntaxBoot.call` reads the STILL-STATIC
68
+ # seed rows (`KeywordSeed`/`ArgumentSeed`), dispatches each one
69
+ # through the real admission/lifecycle door, and hands back the
70
+ # exact same shape `rows` used to read straight off the closed set
71
+ # — symbol keys, string values, `status` included — so nothing
72
+ # else in this file needed to change.
73
+ def render(bluebook)
74
+ table = Hecks::Bluebook::MetaValidator::SyntaxBoot.call
75
+ keywords = table[:keywords]
76
+ arguments = table[:arguments]
77
+
78
+ <<~RUST
79
+ // GENERATED by bin/project_parser_table from
80
+ // aggregate-local syntax tables under lib/hecks/language/ — DO NOT EDIT BY HAND.
81
+ //
82
+ // Re-run `bin/project_parser_table` after any syntax-table change.
83
+ // spec/parser_table_spec.rb regenerates this into memory and fails the
84
+ // normal `bundle exec rspec` suite the moment this file drifts from what
85
+ // the language currently declares — see that spec and
86
+ // bin/project_parser_table's own header for why this file is never
87
+ // hand-edited.
88
+
89
+ #[derive(Debug, Clone, Copy)]
90
+ pub struct KeywordRow {
91
+ pub word: &'static str,
92
+ pub context: &'static str,
93
+ pub body: &'static str,
94
+ pub inner: &'static str,
95
+ pub opens: &'static str,
96
+ pub fills: &'static str,
97
+ pub status: &'static str,
98
+ pub was: &'static str,
99
+ /// Which shared Hecks::Bluebook::DSL::RuleReference
100
+ /// primitive this word's own bare form resolves through,
101
+ /// once it has one ("hash_chain" / "owner_keyed" /
102
+ /// "sibling_scan") — "" for every word that only ever
103
+ /// declares.
104
+ pub resolves_via: &'static str,
105
+ /// Which disambiguator keyword argument a bare reference
106
+ /// may supply — "declared_by" today, "" otherwise.
107
+ pub disambiguator: &'static str,
108
+ }
109
+
110
+ #[derive(Debug, Clone, Copy)]
111
+ pub struct ArgumentRow {
112
+ pub keyword: &'static str,
113
+ pub context: &'static str,
114
+ pub at: &'static str,
115
+ pub named: &'static str,
116
+ pub kind: &'static str,
117
+ pub required: &'static str,
118
+ pub fills: &'static str,
119
+ pub selects: &'static str,
120
+ pub pair_key_fills: &'static str,
121
+ pub pair_value_fills: &'static str,
122
+ pub pairs_shape: &'static str,
123
+ pub status: &'static str,
124
+ pub variadic: &'static str,
125
+ pub minimum: &'static str,
126
+ }
127
+
128
+ impl KeywordRow {
129
+ /// An absent status reads as admitted — the grown-column convention
130
+ /// syntax_conformance_spec.rb's own `status_of`/`live?` already use.
131
+ pub fn live(&self) -> bool {
132
+ matches!(self.status, "" | "admitted" | "deprecated")
133
+ }
134
+ }
135
+
136
+ impl ArgumentRow {
137
+ pub fn live(&self) -> bool {
138
+ matches!(self.status, "" | "admitted" | "deprecated")
139
+ }
140
+ }
141
+
142
+ pub static KEYWORDS: &[KeywordRow] = &[
143
+ #{keywords.map { |row| keyword_row(row) }.join("\n")}
144
+ ];
145
+
146
+ pub static ARGUMENTS: &[ArgumentRow] = &[
147
+ #{arguments.map { |row| argument_row(row) }.join("\n")}
148
+ ];
149
+
150
+ #{const_str_array('CONTEXTS', closed_set_members(bluebook, 'Context'))}
151
+ #{const_str_array('BODIES', closed_set_members(bluebook, 'Body'))}
152
+ #{const_str_array('ARGUMENT_KINDS', closed_set_members(bluebook, 'ArgumentKind'))}
153
+ #{const_str_array('PAIRS_SHAPES', closed_set_members(bluebook, 'PairsShape'))}
154
+ #{const_str_array('STATUSES', closed_set_members(bluebook, 'Status'))}
155
+ RUST
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,38 @@
1
+ require_relative "../doc/reference"
2
+ require_relative "../projector"
3
+
4
+ module Hecks
5
+ module Projections
6
+ # THE DSL REFERENCE PAGES, projected from the chapter's own Syntax
7
+ # aggregate — the tables come from the declaration, the prose is
8
+ # preserved from whatever is already committed.
9
+ #
10
+ # THE FIRST PROJECTION THAT EMITS A TREE. `emits: :files` says so,
11
+ # and the framework writes the map rather than inferring one: a Hash
12
+ # of path => contents and a Hash that merely holds strings are the
13
+ # same object to Ruby, so only the projection can know which it
14
+ # meant. This is the case the output contract was designed for and
15
+ # deliberately left unimplemented until something real needed it.
16
+ #
17
+ # NOT PURE, and it cannot be. `Doc::Reference.pages` READS the
18
+ # committed pages to harvest their prose, so the existing directory
19
+ # is an input — passed as `from:` rather than assumed, so the
20
+ # projection never reaches for a path of its own choosing.
21
+ module Reference
22
+ extend Projector::Target
23
+
24
+ projects_as :reference, declares: "Syntax", emits: :files
25
+
26
+ module_function
27
+
28
+ def call(bluebook:, options: {})
29
+ from = options[:from] or
30
+ raise ArgumentError,
31
+ ":reference harvests prose from the committed pages, so it needs " \
32
+ "`from:` — the directory they live in"
33
+
34
+ Doc::Reference.pages(from)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,31 @@
1
+ require_relative "../runtime/storage_shape"
2
+ require_relative "../projector"
3
+
4
+ module Hecks
5
+ module Projections
6
+ # THE STORAGE SHAPE of one bluebook — the same structural form
7
+ # `StorageShape.mint_hash` hashes to name an era, which is what makes
8
+ # a bump/no-bump question answerable by diffing two of these.
9
+ #
10
+ # THE RETROFIT, and the point of it: `Runtime::StorageShape.project`
11
+ # was written long before this framework existed, and it already
12
+ # takes exactly one bluebook, already returns a plain Hash, already
13
+ # touches no disk and no live runtime — it even already uses the
14
+ # word. Nothing about it was shaped to fit `call(bluebook:, options:)`.
15
+ #
16
+ # That it fits anyway, by a two-line keyword adapter and no change to
17
+ # `StorageShape` itself, is the evidence that the protocol is a real
18
+ # description of what projections do here rather than a shape fitted
19
+ # around its own first example (`:ir`, whose `call` is `to_h`, proves
20
+ # much less on its own).
21
+ module Shape
22
+ extend Projector::Target
23
+
24
+ projects_as :shape
25
+
26
+ module_function
27
+
28
+ def call(bluebook:, options: {}) = Runtime::StorageShape.project(bluebook)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,110 @@
1
+ require_relative "../projector"
2
+
3
+ module Hecks
4
+ module Projections
5
+ # A DOMAIN'S OWN DECLARED FACTS, PROJECTED AS PLAIN ENGLISH SENTENCES
6
+ # — one atomic, independently-checkable statement per fact, not a
7
+ # flowing document (`Projections::Reference`/`DocsProjector` already
8
+ # do that job well; this is a flat list a caller can iterate, diff,
9
+ # or hand to something else entirely — a review, a test-plan seed,
10
+ # a sanity check that the domain still says what someone thinks it
11
+ # says).
12
+ #
13
+ # NEVER INVENTS A SENTENCE FROM NOTHING — `DocsProjector`'s own
14
+ # discipline, held here too: a relationship's sentence is built
15
+ # MECHANICALLY from its own declared shape (holder, target,
16
+ # relationship kind — the same facts `Projections::Diagrams`'s own
17
+ # relationship_edge already reads), and an invariant's sentence IS
18
+ # the domain author's own `description`, capitalized and
19
+ # punctuated, never paraphrased. If a fact has no author-written
20
+ # description and no unambiguous mechanical phrasing, it doesn't
21
+ # get a sentence here — a wrong sentence is worse than a missing
22
+ # one.
23
+ #
24
+ # REACHABLE THE SAME WAY EVERY PROJECTION ALREADY IS, no new facade
25
+ # wiring needed: `Pizzas.project(Projections::Statements)` —
26
+ # `Facade::Surface::Chapter#project`'s own comment already settled
27
+ # this ("anything genuinely needing the graph is a projector, and a
28
+ # projector is given it"). `bin/statements` is a thin, optional
29
+ # convenience for reaching the same call from a shell.
30
+ #
31
+ # MVP SCOPE: "has many"/"has a"/"belongs to"/"references" sentences
32
+ # for every list or relationship attribute, and every invariant's
33
+ # own description (aggregate-level and every nested value object's),
34
+ # verbatim. Lifecycle transitions and command given/ensures read the
35
+ # same way and are the natural next sentences — not built yet.
36
+ module Statements
37
+ extend Hecks::Projector::Target
38
+
39
+ projects_as :statements
40
+
41
+ module_function
42
+
43
+ def call(bluebook:, options: {})
44
+ holders(bluebook).flat_map { |holder| statements_for(holder) }
45
+ end
46
+
47
+ def holders(bluebook) = bluebook.aggregates.flat_map { |aggregate| [aggregate, *aggregate.entities] }
48
+
49
+ def statements_for(holder)
50
+ attribute_statements(holder) + invariant_statements(holder)
51
+ end
52
+
53
+ # "MANY" READS THE SAME WAY REGARDLESS OF WHAT'S BEHIND IT — a
54
+ # `has_many` relationship to another aggregate and a plain
55
+ # `list_of(Topping)` value-object attribute are the same idea to
56
+ # someone reading the domain in English ("an Order has many
57
+ # toppings" is true either way), even though they're two
58
+ # different IR shapes (a `Reference` versus an ordinary type).
59
+ # `attribute.name` carries the noun, not the target's own class
60
+ # name, because the field's own name is what the domain author
61
+ # actually chose to call the collection — "toppings", not
62
+ # "Topping".
63
+ def attribute_statements(holder)
64
+ holder.attributes.filter_map { |attribute| attribute_statement(holder, attribute) }
65
+ end
66
+
67
+ def attribute_statement(holder, attribute)
68
+ subject = "#{article(holder.hecks_name)} #{holder.hecks_name}"
69
+ return "#{subject} has many #{attribute.name}." if attribute.list?
70
+
71
+ target = attribute.relationship && attribute.type.target_name
72
+ target_phrase = target && "#{article(target).downcase} #{target}"
73
+ case attribute.relationship
74
+ when "has_one" then "#{subject} has #{target_phrase}."
75
+ when "belongs_to" then "#{subject} belongs to #{target_phrase}."
76
+ when "reference_to" then "#{subject} references #{target_phrase}."
77
+ end
78
+ end
79
+
80
+ # "AN ACCOUNT", NOT "A ACCOUNT" — every subject/object noun here is
81
+ # a bare construct name (`ATMCard`, `ExternalTransfer`, `Account`),
82
+ # never free text, so the ordinary "starts with a vowel LETTER"
83
+ # heuristic is safe: this language's own naming never produces the
84
+ # English exceptions that heuristic gets wrong (an "hour", a
85
+ # "university") because a construct name is always spelled as a
86
+ # plain word, never an abbreviation read letter-by-letter or a
87
+ # word starting with a consonant LETTER but a vowel SOUND.
88
+ def article(word) = word.to_s.match?(/\A[AEIOUaeiou]/) ? "An" : "A"
89
+
90
+ # INVARIANTS LIVE IN TWO PLACES — directly on the holder (an
91
+ # aggregate-level rule, checked after every command) and on every
92
+ # value object nested inside it — `DocsProjector#rules_of`'s own
93
+ # `value_object_for` lookup is the precedent for walking both.
94
+ def invariant_statements(holder)
95
+ own = Array(holder.respond_to?(:invariants) ? holder.invariants : [])
96
+ nested = Array(holder.respond_to?(:value_objects) ? holder.value_objects : []).flat_map(&:invariants)
97
+ (own + nested).map { |invariant| invariant_statement(invariant) }
98
+ end
99
+
100
+ # THE DOMAIN AUTHOR'S OWN WORDS, CAPITALIZED AND PUNCTUATED —
101
+ # nothing else. `invariant("a pizza is named")` already reads as
102
+ # a sentence; this is the entire transformation.
103
+ def invariant_statement(invariant)
104
+ text = invariant.description.to_s.strip
105
+ text = "#{text[0].upcase}#{text[1..]}" if text[0]
106
+ text.end_with?(".", "!", "?") ? text : "#{text}."
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,100 @@
1
+ require_relative "../projector"
2
+
3
+ module Hecks
4
+ module Projections
5
+ # THE CLOSED SETS, PROJECTED — lib/hecks/vocabulary.rb rendered
6
+ # from whichever chapter declares a `Vocabulary` aggregate, which in
7
+ # practice is the language projecting its own tables.
8
+ #
9
+ # Projector.call(:vocabulary, bluebook: <the Bluebook chapter>)
10
+ #
11
+ # A PROJECTION RATHER THAN A BIN/ SCRIPT, and that correction is the
12
+ # point of it. This was first written as its own program with its own
13
+ # call shape, beside `bin/project_parser_table`, `bin/reference` and
14
+ # `bin/expression_projection` — four tools doing "canonical IR in,
15
+ # external artifact out" four different ways, which is the exact
16
+ # thing the projector registry exists to stop.
17
+ #
18
+ # Reads the chapter's JUDGED IR, so what is written out is what the
19
+ # language actually holds rather than what a builder happened to
20
+ # produce.
21
+ module Vocabulary
22
+ extend Projector::Target
23
+
24
+ projects_as :vocabulary, declares: "Vocabulary"
25
+
26
+ HEADER = <<~RUBY.freeze
27
+ # GENERATED — projected from the language's own Vocabulary aggregate
28
+ # (lib/hecks/language/bluebook/vocabulary.bluebook).
29
+ #
30
+ # DO NOT EDIT. spec/vocabulary_table_spec.rb re-projects this in memory
31
+ # and refuses a diff, so an edit here fails the ordinary suite.
32
+ #
33
+ # Plain data on purpose — no requires, no dependency on the model —
34
+ # because several of these sets are read while a bluebook is still
35
+ # being parsed. A table that needed the framework to load could not
36
+ # be the table the framework loads with.
37
+ RUBY
38
+
39
+ module_function
40
+
41
+ # The projector protocol. `options` is unused: a vocabulary table
42
+ # has nothing to vary.
43
+ def call(bluebook:, options: {}) = render(bluebook)
44
+
45
+ # FULL ROWS, not just the first field of each.
46
+ #
47
+ # Most vocabularies ARE one-field lists and the field is the term.
48
+ # Several are not: `Comparison` declares the algebra each operator
49
+ # computes with, `RefusalTemplate` an error plus its key and text.
50
+ # Taking the first field of those produced a list of thirty-nine
51
+ # duplicated error names — well-formed and meaningless. So rows are
52
+ # carried whole, and the terms are derived from them.
53
+ def tables(bluebook)
54
+ bluebook.aggregate("Vocabulary").value_objects.to_h do |vo|
55
+ [vo.hecks_name, vo.members.map { |row| row.to_h.transform_keys(&:to_s).transform_values(&:to_s) }]
56
+ end
57
+ end
58
+
59
+ def render(bluebook)
60
+ rows = tables(bluebook).sort_by(&:first).map do |name, members|
61
+ " #{name.inspect} => [\n" +
62
+ members.map { |row| " #{row.inspect}.freeze" }.join(",\n") +
63
+ "\n ].freeze"
64
+ end
65
+
66
+ <<~RUBY
67
+ #{HEADER}
68
+ module Hecks
69
+ module Vocabulary
70
+ TABLES = {
71
+ #{rows.join(",\n")}
72
+ }.freeze
73
+
74
+ # THE TERMS — the first field of each row, which for a
75
+ # one-field vocabulary is the whole of it. Derived ONCE and
76
+ # frozen rather than mapped per call: these are constant
77
+ # tables, and a constant that allocates a new array every
78
+ # time it is read is not one.
79
+ TERMS = TABLES.transform_values { |rows| rows.map { |row| row.values.first }.freeze }.freeze
80
+
81
+ module_function
82
+
83
+ # Refuses an unknown name rather than answering nil: a set
84
+ # the language does not declare is a typo, not an empty set.
85
+ def fetch(name) = TERMS.fetch(name)
86
+
87
+ # The rows whole, for the vocabularies that carry more than
88
+ # a term — Comparison's own algebra, RefusalTemplate's text.
89
+ def rows(name) = TABLES.fetch(name)
90
+
91
+ def symbols(name) = fetch(name).map(&:to_sym)
92
+
93
+ def names = TABLES.keys
94
+ end
95
+ end
96
+ RUBY
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,32 @@
1
+ require_relative "projector"
2
+
3
+ module Hecks
4
+ # THE TARGETS A DOMAIN CAN BE PROJECTED INTO, as constants rather than
5
+ # bare symbols — `Pizzas.project(Projections::OIDC)`.
6
+ #
7
+ # A constant is worth the namespace for two reasons a Symbol cannot
8
+ # give: a typo raises `NameError` at the call site instead of
9
+ # `UnknownProjector` at dispatch time, and each target has somewhere to
10
+ # carry its own documentation and defaults.
11
+ #
12
+ # Namespaced rather than top-level because `IR` is already taken —
13
+ # `Hecks::Bluebook` is the MODEL (`Bluebook::Command`, and the
14
+ # chapter class itself).
15
+ # `Projections::IR` is the projection OF that model, a different thing
16
+ # that would be genuinely confusing under the same bare name.
17
+ #
18
+ # `include Hecks::Projections` gets the short spelling where the
19
+ # extra qualification is noise (`bin/` scripts, a console session).
20
+ module Projections
21
+ end
22
+ end
23
+
24
+ require_relative "projections/ir"
25
+ require_relative "projections/shape"
26
+ require_relative "projections/oidc"
27
+ require_relative "projections/vocabulary"
28
+ require_relative "projections/parser_table"
29
+ require_relative "projections/reference"
30
+ require_relative "projections/model"
31
+ require_relative "projections/diagrams"
32
+ require_relative "projections/statements"