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,114 @@
1
+ require "json"
2
+ require "uri"
3
+ require_relative "html"
4
+ require_relative "field_shape"
5
+ require_relative "field_renderer"
6
+ require_relative "reference_options"
7
+ require_relative "params"
8
+ require_relative "record_table"
9
+
10
+ module Hecks
11
+ module Forms
12
+ # Query -> the page body for its GET view — the working half of what
13
+ # `query_form.bluebook` names (see docs/command-form-and-query-form-
14
+ # bluebook.md): a canonical, shareable link (queries are GETs — every
15
+ # one of them is already a URL, so this says so up front rather than
16
+ # hiding that behind a form); a row of ready-made links for any
17
+ # closed-set parameter, since a caller filtering by an enum should not
18
+ # have to fill in a form to get a link they could just click; a filter
19
+ # FORM for everything else, which — being a GET form — produces
20
+ # exactly the same kind of link on submit; and the results table once
21
+ # a request actually supplies parameters.
22
+ module QueryFormRenderer
23
+ def self.render(registry:, domain:, aggregate:, query:, action:, params: {}, results: nil, error: nil)
24
+ fields = query.attributes.map { |a| FieldShape.resolve(a, aggregate: aggregate) }
25
+ reference_options = ReferenceOptions.collect(registry, domain, fields)
26
+
27
+ <<~HTML
28
+ #{header(domain, aggregate, query)}
29
+ #{canonical_link(action, fields)}
30
+ #{quick_links(action, fields)}
31
+ #{filter_form(action, fields, params, reference_options)}
32
+ #{error_banner(error)}
33
+ #{results_section(aggregate, results, domain)}
34
+ #{inspect_panel(domain, aggregate, query, fields)}
35
+ HTML
36
+ end
37
+
38
+ def self.header(domain, aggregate, query)
39
+ <<~HTML
40
+ <h1>#{Escape.html("#{domain}::#{aggregate.hecks_name}.#{query.hecks_name}")}</h1>
41
+ #{query.description ? %(<p class="goal">#{Escape.html(query.description)}</p>) : ''}
42
+ #{badges(query)}
43
+ HTML
44
+ end
45
+
46
+ def self.badges(query)
47
+ parts = []
48
+ parts << %(<span class="badge">limit #{Escape.html(query.limit.to_h[:value])}</span>) if query.limit
49
+ parts.join
50
+ end
51
+
52
+ def self.canonical_link(action, fields)
53
+ paths = Params.paths(fields)
54
+ template = paths.empty? ? action : "#{action}?#{paths.map { |path| "#{path}={#{path}}" }.join('&')}"
55
+ <<~HTML
56
+ <p class="help">Every query is a plain GET — this exact URL is bookmarkable, linkable from a dashboard, curlable, whatever a feature needs:</p>
57
+ <div class="link-row"><code id="canonical-link">#{Escape.html(template)}</code><button type="button" class="copy" data-copy="#canonical-link">copy</button></div>
58
+ HTML
59
+ end
60
+
61
+ # The one enum-shaped parameter (if there is one) rendered as literal,
62
+ # clickable links — no form to fill in for "show me the suspended
63
+ # ones". Capped at the FIRST closed-set field on purpose: a second one
64
+ # would mean a cross product of links, which reads as noise rather
65
+ # than help. A query with more than one is still fully reachable
66
+ # through the filter form below.
67
+ def self.quick_links(action, fields)
68
+ field = fields.find { |f| %i[select radio].include?(f.kind) }
69
+ return "" unless field
70
+
71
+ links = field.options.map do |value, label|
72
+ href = "#{action}?#{field.path}=#{URI.encode_www_form_component(value)}"
73
+ %(<a href="#{Escape.attr(href)}">#{Escape.html(field.label)}: #{Escape.html(label)}</a>)
74
+ end
75
+ %(<div class="example-links">#{links.join}</div>)
76
+ end
77
+
78
+ def self.filter_form(action, fields, params, reference_options)
79
+ return "" if fields.empty?
80
+
81
+ <<~HTML
82
+ <form method="get" action="#{Escape.attr(action)}">
83
+ #{fields.map { |f| FieldRenderer.render(f, values: params, reference_options: reference_options) }.join}
84
+ <div class="actions"><button type="submit">Run query</button></div>
85
+ </form>
86
+ HTML
87
+ end
88
+
89
+ def self.error_banner(error)
90
+ return "" unless error
91
+
92
+ %(<div class="error-banner" role="alert"><p><strong>#{Escape.html(error.class.name.split('::').last)}</strong> — #{Escape.html(error.message)}</p></div>)
93
+ end
94
+
95
+ def self.results_section(aggregate, results, domain)
96
+ return "" unless results
97
+
98
+ "<h2>Results (#{results.size})</h2>#{RecordTable.render(aggregate, results, domain: domain)}"
99
+ end
100
+
101
+ def self.inspect_panel(domain, aggregate, query, fields)
102
+ verb = "#{domain}::#{aggregate.hecks_name}.#{query.hecks_name}"
103
+ paths = Params.paths(fields)
104
+ <<~HTML
105
+ <details class="inspect">
106
+ <summary>Inspect — #{Escape.html(verb)}</summary>
107
+ <p>Parameters: #{paths.empty? ? '<em>none</em>' : paths.map { |p| "<code>#{Escape.html(p)}</code>" }.join(', ')}</p>
108
+ <pre>#{Escape.html(JSON.pretty_generate(query.to_h))}</pre>
109
+ </details>
110
+ HTML
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,115 @@
1
+ require_relative "html"
2
+ require_relative "field_shape"
3
+ require_relative "record_table"
4
+
5
+ module Hecks
6
+ module Forms
7
+ # The two pages a `.bluebook` doesn't declare but a browsing developer
8
+ # always wants: every record of an aggregate, and one record's own
9
+ # state plus what it can legally do next. Neither is a `query` — they
10
+ # read the repository directly, the same way `AggregateDoor#all`/`#find`
11
+ # do (facade/surface/aggregate_door.rb) — so they exist for every
12
+ # aggregate whether or not its bluebook declared a query at all.
13
+ module RecordRenderer
14
+ def self.index(registry:, domain:, aggregate:)
15
+ instances = registry.repository(domain, aggregate).all
16
+ <<~HTML
17
+ <h1>#{Escape.html("#{domain}::#{aggregate.hecks_name}")}</h1>
18
+ #{aggregate.description ? %(<p class="goal">#{Escape.html(aggregate.description)}</p>) : ''}
19
+ #{creating_links(domain, aggregate)}
20
+ <h2>Records (#{instances.size})</h2>
21
+ #{RecordTable.render(aggregate, instances, domain: domain)}
22
+ #{verb_list(domain, aggregate)}
23
+ HTML
24
+ end
25
+
26
+ def self.creating_links(domain, aggregate)
27
+ creators = aggregate.commands.select(&:creates?)
28
+ return "" if creators.empty?
29
+
30
+ links = creators.map { |cmd|
31
+ %(<a class="button" href="/#{domain}/#{aggregate.hecks_name}/#{cmd.hecks_name}.html">+ #{Escape.html(cmd.hecks_name)}</a>)
32
+ }
33
+ %(<div class="actions">#{links.join}</div>)
34
+ end
35
+
36
+ def self.show(registry:, domain:, aggregate:, id:)
37
+ instance = registry.repository(domain, aggregate).find(id)
38
+ return nil unless instance
39
+
40
+ state = instance.state
41
+ current = aggregate.lifecycle && state[aggregate.lifecycle.field]
42
+ <<~HTML
43
+ <h1>#{Escape.html("#{domain}::#{aggregate.hecks_name}")} <span class="mono">#{Escape.html(id)}</span></h1>
44
+ #{current ? %(<span class="badge role">#{Escape.html(aggregate.lifecycle.field)}: #{Escape.html(current)}</span>) : ''}
45
+ #{state_table(state)}
46
+ <h2>Commands</h2>
47
+ #{command_links(domain, aggregate, id, current)}
48
+ #{query_links(domain, aggregate)}
49
+ HTML
50
+ end
51
+
52
+ def self.state_table(state)
53
+ rows = state.map { |key, value|
54
+ "<tr><th>#{Escape.html(Humanize.label(key.to_s))}</th><td>#{Escape.html(render_value(value))}</td></tr>"
55
+ }
56
+ %(<div class="table-scroll"><table><tbody>#{rows.join}</tbody></table></div>)
57
+ end
58
+
59
+ # `Value.materialize` first — a stored field is a `Runtime::Value`
60
+ # wherever its attribute is a value object, not a plain Hash (see
61
+ # record_table.rb's own note on the same read).
62
+ def self.render_value(value)
63
+ case (value = Runtime::Value.materialize(value))
64
+ when Hash then value.map { |k, v| "#{k}: #{render_value(v)}" }.join(", ")
65
+ when Array then value.map { |v| render_value(v) }.join("; ")
66
+ else value.to_s
67
+ end
68
+ end
69
+
70
+ # Every non-creating command, EXCEPT a lifecycle transition that does
71
+ # not apply from the record's current state — the same rule
72
+ # `Rules#admissible_transition` enforces at dispatch, read here so a
73
+ # link that would only refuse is never offered in the first place.
74
+ def self.command_links(domain, aggregate, id, current_state)
75
+ commands = aggregate.commands.reject(&:creates?).select { |cmd| applies?(aggregate, cmd, current_state) }
76
+ return "<p><em>No commands act on an existing #{Escape.html(aggregate.hecks_name)}.</em></p>" if commands.empty?
77
+
78
+ items = commands.map do |cmd|
79
+ href = "/#{domain}/#{aggregate.hecks_name}/#{cmd.hecks_name}.html?to=#{Escape.attr(id)}"
80
+ %(<li><a href="#{href}"><span>#{Escape.html(cmd.hecks_name)}</span><span class="kind">#{Escape.html(cmd.goal.to_s)}</span></a></li>)
81
+ end
82
+ %(<ul class="verb-list">#{items.join}</ul>)
83
+ end
84
+
85
+ def self.applies?(aggregate, command, current_state)
86
+ transitions = aggregate.lifecycle&.transitions_for(command.hecks_name) || []
87
+ return true if transitions.empty?
88
+
89
+ transitions.any? { |t| t.from.nil? || Array(t.from).map(&:to_s).include?(current_state.to_s) }
90
+ end
91
+
92
+ def self.query_links(domain, aggregate)
93
+ return "" if aggregate.queries.empty?
94
+
95
+ items = aggregate.queries.map do |query|
96
+ %(<li><a href="/#{domain}/#{aggregate.hecks_name}/#{query.hecks_name}.html"><span>#{Escape.html(query.hecks_name)}</span><span class="kind">query</span></a></li>)
97
+ end
98
+ %(<h2>Queries</h2><ul class="verb-list">#{items.join}</ul>)
99
+ end
100
+
101
+ def self.verb_list(domain, aggregate)
102
+ commands = aggregate.commands.reject(&:creates?)
103
+ return "" if commands.empty? && aggregate.queries.empty?
104
+
105
+ cmd_items = commands.map { |c|
106
+ %(<li><a href="/#{domain}/#{aggregate.hecks_name}/#{c.hecks_name}.html"><span>#{Escape.html(c.hecks_name)}</span><span class="kind">command</span></a></li>)
107
+ }
108
+ query_items = aggregate.queries.map { |q|
109
+ %(<li><a href="/#{domain}/#{aggregate.hecks_name}/#{q.hecks_name}.html"><span>#{Escape.html(q.hecks_name)}</span><span class="kind">query</span></a></li>)
110
+ }
111
+ %(<h2>Every command &amp; query on #{Escape.html(aggregate.hecks_name)}</h2><ul class="verb-list">#{(cmd_items + query_items).join}</ul>)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,63 @@
1
+ require_relative "html"
2
+ require_relative "field_shape"
3
+
4
+ module Hecks
5
+ module Forms
6
+ # The one shape every renderer in this directory reads a record as —
7
+ # `id` plus its state hash. `Runtime::Instance` already answers both,
8
+ # so `repository.all`'s own records pass straight through; a QUERY's
9
+ # answer does not (`QueryInterpreter#call` flattens `{id:}.merge(state)`
10
+ # into one hash with no method to call — see
11
+ # docs/command-form-and-query-form-bluebook.md's note on why), so
12
+ # app.rb wraps those results in one of these before they ever reach a
13
+ # renderer, and every renderer here only has to know one shape.
14
+ Record = Struct.new(:id, :state)
15
+
16
+ # An array of Record (or anything answering `#id`/`#state` — a
17
+ # Runtime::Instance qualifies without wrapping) -> an HTML table.
18
+ # Shared by the aggregate index page (record_renderer.rb) and a
19
+ # query's own results (query_form_renderer.rb) — the same records
20
+ # read the same columns either way.
21
+ module RecordTable
22
+ def self.columns(aggregate)
23
+ lifecycle = aggregate.lifecycle&.field
24
+ scalars = aggregate.attributes.reject { |a| a.reference? || a.list? }.map(&:name)
25
+ [*aggregate.identity_heads, lifecycle, *scalars].compact.uniq.first(7)
26
+ end
27
+
28
+ def self.render(aggregate, instances, domain:)
29
+ cols = columns(aggregate)
30
+ head = (["id"] + cols).map { |name| "<th>#{Escape.html(Humanize.label(name.to_s))}</th>" }.join
31
+ rows = instances.map { |instance| row(instance, aggregate, cols, domain) }
32
+ return "<p><em>No records.</em></p>" if rows.empty?
33
+
34
+ <<~HTML
35
+ <div class="table-scroll"><table>
36
+ <thead><tr>#{head}</tr></thead>
37
+ <tbody>#{rows.join}</tbody>
38
+ </table></div>
39
+ HTML
40
+ end
41
+
42
+ def self.row(instance, aggregate, cols, domain)
43
+ cells = cols.map { |name| "<td>#{Escape.html(cell(instance, name))}</td>" }.join
44
+ href = "/#{domain}/#{aggregate.hecks_name}/#{instance.id}.html"
45
+ "<tr><td><a href=\"#{Escape.attr(href)}\">#{Escape.html(instance.id)}</a></td>#{cells}</tr>"
46
+ end
47
+
48
+ def self.cell(instance, name)
49
+ # `state` holds `Runtime::Value` wherever an attribute is a value
50
+ # object, not a plain Hash — `.materialize` is the runtime's own
51
+ # unwrap (value.rb), the same one a `to_h` read anywhere else in
52
+ # this codebase goes through, so a table cell agrees with every
53
+ # other reader about what a stored field actually contains.
54
+ value = Runtime::Value.materialize(instance.state[name])
55
+ case value
56
+ when Hash then value.values.first
57
+ when nil then ""
58
+ else value
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,30 @@
1
+ module Hecks
2
+ module Forms
3
+ # A Field tree (field_shape.rb) -> `{path => [[id, label], ...]}` for
4
+ # every `:reference` field in it — the options a `<select>` needs.
5
+ # Shared by CommandFormRenderer and QueryFormRenderer alike: a
6
+ # command's own reference field and a query's own reference parameter
7
+ # resolve against the same repository the same way, and neither word
8
+ # owns this more than the other.
9
+ module ReferenceOptions
10
+ def self.collect(registry, domain, fields)
11
+ targets = {}
12
+ walk(fields) { |field| targets[field.path] = field.target_aggregate if field.kind == :reference }
13
+ targets.transform_values { |aggregate| aggregate && options_for(registry, domain, aggregate) }
14
+ end
15
+
16
+ def self.walk(fields, &block)
17
+ fields.each do |field|
18
+ block.call(field)
19
+ walk(field.children, &block) if field.children
20
+ end
21
+ end
22
+
23
+ def self.options_for(registry, domain, aggregate)
24
+ registry.repository(domain, aggregate).all.first(200).map { |instance| [instance.id, instance.id] }
25
+ rescue StandardError
26
+ nil # a wiring gap (no repository bound) degrades to a plain text id input, not a 500
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,46 @@
1
+ module Hecks
2
+ module Forms
3
+ # THE VALUE-OBJECT CLASSIFICATION every consumer of a resolved VO
4
+ # shape needs, spelled once — `field_shape.rb` (a command's own
5
+ # form), `ui_schema.rb` (embryonaut_console's table/detail-panel
6
+ # renderer, a separate app, not this repo), and
7
+ # `adapters/driven/sql_query_builder.rb` (a query's own ORDER BY/
8
+ # WHERE compiler) each grew their own copy of "is this VO money-
9
+ # shaped," "does it have exactly one attribute, worth unwrapping,"
10
+ # and "which member of it is numeric" — the same three questions,
11
+ # answered identically by definition (a VO's shape doesn't change
12
+ # depending on who's asking), so a fix to one copy was never
13
+ # guaranteed to reach the others.
14
+ module ValueObjectShape
15
+ module_function
16
+
17
+ # `Price{cents:, currency:}`/`ContractValue{cents:, currency:}` —
18
+ # every money-shaped VO in the corpus spells it exactly this way,
19
+ # the two-attribute convention the language itself never enforces
20
+ # but every real chapter follows.
21
+ def money?(value_object)
22
+ value_object.attributes.map { |a| a.name.to_s }.sort == %w[cents currency]
23
+ end
24
+
25
+ # A VO with exactly one attribute is a NAME for a scalar, not a
26
+ # genuine group (EmailAddress{address}, CustomerNumber{value}) —
27
+ # [[feedback_name_the_scalar_field]]'s own reasoning, shared here
28
+ # rather than re-decided per caller. Returns the sole attribute,
29
+ # or nil for anything else.
30
+ def sole_attribute(value_object)
31
+ return nil unless value_object.attributes.size == 1
32
+
33
+ value_object.attributes.first
34
+ end
35
+
36
+ # The first Integer/Float member, or nil — what a numeric
37
+ # comparison or an ORDER BY compiles against when the VO isn't
38
+ # money-shaped (money's own two members are handled by `money?`
39
+ # instead, since which one governs ordering is a money-specific
40
+ # decision, not a general "pick the first number" one).
41
+ def numeric_member(value_object)
42
+ value_object.attributes.find { |a| %w[Integer Float].include?(a.type.to_s) }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,54 @@
1
+ # The forms surface: `expose` (the DSL a `command_form.bluebook`/
2
+ # `query_form.bluebook` will eventually become — see docs/
3
+ # command-form-and-query-form-bluebook.md), the IR->HTML renderers, and
4
+ # the Rack app that content-negotiates between them and a plain JSON
5
+ # reading of the same dispatch. NOT required by `require "hecks"`
6
+ # itself — a project that never boots this file never pays for `rack`,
7
+ # the same lazy-dependency discipline the Gemfile's own comment already
8
+ # holds `pg`/`oauth2`/`aws-sdk-lambda` to.
9
+ #
10
+ # See docs/command-form-and-query-form-bluebook.md for the design this
11
+ # implements and what it deliberately leaves for later.
12
+ require "hecks"
13
+ require_relative "forms/app"
14
+
15
+ module Hecks
16
+ module Forms
17
+ # `expose`'s own declaration — "which chapters does this app expose" —
18
+ # kept OUTSIDE the `Hecks.*` collector convention (`Hecks.bluebook`,
19
+ # `Hecks.hecksagon`, ...) and outside `Runtime::Registry` entirely, on
20
+ # purpose: a real language word goes through `syntax.bluebook` and
21
+ # `MetaValidator` (see docs/implemented/guides/extending-hecks.md, "a new word is a
22
+ # declared row before it is a line of Ruby") and is judged by
23
+ # spec/syntax_conformance_spec.rb + spec/dsl_coverage_spec.rb — gates
24
+ # this construct has not earned yet. An ordinary Ruby DSL, one level of
25
+ # module state, no different in kind from a project's own initializer —
26
+ # see docs/command-form-and-query-form-bluebook.md, "why this isn't
27
+ # syntax.bluebook yet".
28
+ #
29
+ # ONE `expose` GRANTS A WHOLE CHAPTER, not a command or a query
30
+ # individually — the future `command_form.bluebook`/`query_form.bluebook`
31
+ # words are PER-DECLARATION (one command, one query, its own form/view,
32
+ # possibly its own overrides), which `expose` doesn't do today and was
33
+ # never trying to; it's the coarse "turn this chapter's forms on at
34
+ # all" switch those finer words will eventually sit inside.
35
+ class Config
36
+ attr_reader :name, :exposes
37
+
38
+ def initialize(name)
39
+ @name = name.to_s
40
+ @exposes = []
41
+ end
42
+
43
+ def expose(chapter_name) = @exposes << chapter_name.to_s
44
+ end
45
+
46
+ def self.configure(name, &block)
47
+ config = Config.new(name)
48
+ config.instance_eval(&block) if block
49
+ (@configs ||= {})[config.name] = config
50
+ end
51
+
52
+ def self.config(name) = (@configs || {})[name.to_s]
53
+ end
54
+ end
data/lib/hecks/fqn.rb ADDED
@@ -0,0 +1,94 @@
1
+ module Hecks
2
+ # A public Bluebook command/query address.
3
+ #
4
+ # Realm is deployment identity from a world. Domain@version pins a domain
5
+ # contract; an unpinned domain is the world's configured latest alias.
6
+ class Fqn
7
+ class Invalid < ArgumentError; end
8
+
9
+ attr_reader :realm, :domain, :version, :aggregate, :verb
10
+
11
+ def self.command(realm:, domain:, aggregate:, command:, version: nil)
12
+ new(realm: realm, domain: domain, version: version, aggregate: aggregate, verb: command, kind: :command)
13
+ end
14
+
15
+ def self.query(realm:, domain:, aggregate: nil, query:, version: nil)
16
+ new(realm: realm, domain: domain, version: version, aggregate: aggregate, verb: query, kind: :query)
17
+ end
18
+
19
+ def self.parse(text)
20
+ head, separator, verb = text.to_s.rpartition(".")
21
+ raise Invalid, "FQN must be Realm::Domain::Aggregate.verb — got #{text.inspect}" if separator.empty?
22
+
23
+ segments = head.split("::", -1)
24
+ raise Invalid, "FQN must be Realm::Domain::Aggregate.verb — got #{text.inspect}" unless [1, 2, 3].include?(segments.length) && segments.none?(&:empty?) && !verb.empty?
25
+
26
+ realm, domain_spec, aggregate = case segments.length
27
+ when 3 then segments
28
+ # A two-segment lowercase address is the
29
+ # public realm/domain form for a
30
+ # domain-level read model.
31
+ when 2 then query_name?(verb) ? [segments[0], segments[1], nil] : [nil, *segments]
32
+ else [nil, segments.first, nil]
33
+ end
34
+ domain, version = split_domain(domain_spec)
35
+ kind = if command_name?(verb)
36
+ :command
37
+ elsif query_name?(verb)
38
+ :query
39
+ else
40
+ raise Invalid, "FQN verb must be PascalCase command or snake_case query — got #{text.inspect}"
41
+ end
42
+
43
+ raise Invalid, "domain-level FQNs are query-only — got #{text.inspect}" if aggregate.nil? && kind == :command
44
+
45
+ new(realm: realm, domain: domain, version: version, aggregate: aggregate, verb: verb, kind: kind)
46
+ end
47
+
48
+ def self.command_name?(name) = /\A[A-Z][A-Za-z0-9]*\z/.match?(name.to_s)
49
+ def self.query_name?(name) = /\A[a-z][a-z0-9_]*\z/.match?(name.to_s)
50
+
51
+ def initialize(realm:, domain:, version: nil, aggregate:, verb:, kind:)
52
+ @realm = realm && segment(realm, "realm")
53
+ @domain = segment(domain, "domain")
54
+ @version = version && segment(version, "version")
55
+ @aggregate = aggregate && segment(aggregate, "aggregate")
56
+ @verb = segment(verb, "verb")
57
+ @kind = kind.to_sym
58
+
59
+ valid = command? ? self.class.command_name?(@verb) : self.class.query_name?(@verb)
60
+ raise Invalid, "#{@kind} FQN has an invalid verb #{@verb.inspect}" unless valid
61
+ end
62
+
63
+ def command? = @kind == :command
64
+ def query? = @kind == :query
65
+ def kind = @kind
66
+
67
+ def to_s
68
+ domain = @version ? "#{@domain}@#{@version}" : @domain
69
+ [[@realm, domain, @aggregate].compact.join("::"), @verb].join(".")
70
+ end
71
+
72
+ def ==(other) = other.is_a?(Fqn) && to_s == other.to_s
73
+ alias eql? ==
74
+ def hash = to_s.hash
75
+
76
+ private
77
+
78
+ def self.split_domain(value)
79
+ domain, version, extra = value.to_s.split("@", 3)
80
+ raise Invalid,
81
+ "FQN domain version is malformed: #{value.inspect}" if domain.empty? || extra || (value.include?("@") && version.to_s.empty?)
82
+
83
+ [domain, version]
84
+ end
85
+
86
+ def segment(value, label)
87
+ text = value.to_s
88
+ raise Invalid,
89
+ "FQN #{label} cannot be empty or contain a separator" if text.empty? || text.include?("::") || text.include?(".") || text.include?("@")
90
+
91
+ text
92
+ end
93
+ end
94
+ end
@@ -0,0 +1 @@
1
+ ../../../../examples/compliance/bluebook/compliance.bluebook