hecks 0.2.0 → 1.0.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 (419) 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 +427 -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 +101 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +179 -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 +145 -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 +88 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +438 -0
  31. data/lib/hecks/adapters/driven/postgres_era.adapter +17 -0
  32. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  33. data/lib/hecks/adapters/driven/prism.rb +80 -0
  34. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  35. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  36. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  37. data/lib/hecks/adapters/driven/sqlite/codec.rb +80 -0
  38. data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
  39. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
  40. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  41. data/lib/hecks/adapters/driven/sqlite.rb +306 -0
  42. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  43. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  44. data/lib/hecks/adapters/driven.rb +54 -0
  45. data/lib/hecks/adapters.rb +6 -0
  46. data/lib/hecks/behaviors/dsl.rb +125 -0
  47. data/lib/hecks/behaviors/expectations.rb +340 -0
  48. data/lib/hecks/behaviors/ir.rb +31 -0
  49. data/lib/hecks/behaviors/rspec.rb +42 -0
  50. data/lib/hecks/behaviors/runner.rb +96 -0
  51. data/lib/hecks/behaviors.rb +25 -0
  52. data/lib/hecks/bluebook/aggregate.rb +108 -0
  53. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  54. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  55. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  56. data/lib/hecks/bluebook/assembly/contracts.rb +438 -0
  57. data/lib/hecks/bluebook/assembly/marks.rb +228 -0
  58. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  59. data/lib/hecks/bluebook/assembly.rb +91 -0
  60. data/lib/hecks/bluebook/attribute.rb +96 -0
  61. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  62. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  63. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  64. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  65. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  66. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  67. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  68. data/lib/hecks/bluebook/behaviour/lifecycle.rb +68 -0
  69. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  70. data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -0
  71. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  73. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  75. data/lib/hecks/bluebook/chapter.rb +78 -0
  76. data/lib/hecks/bluebook/command.rb +124 -0
  77. data/lib/hecks/bluebook/domain_port.rb +102 -0
  78. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  79. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +1018 -0
  80. data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
  81. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  82. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
  83. data/lib/hecks/bluebook/dsl/command_builder.rb +714 -0
  84. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  85. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  86. data/lib/hecks/bluebook/dsl/entity_builder.rb +430 -0
  87. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  88. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  89. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  90. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  91. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  92. data/lib/hecks/bluebook/dsl/policy_builder.rb +135 -0
  93. data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
  94. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  95. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -0
  96. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  97. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  98. data/lib/hecks/bluebook/dsl/rule_reference.rb +173 -0
  99. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  100. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  101. data/lib/hecks/bluebook/dsl/word_gate.rb +228 -0
  102. data/lib/hecks/bluebook/dsl/world_builder.rb +117 -0
  103. data/lib/hecks/bluebook/dsl.rb +45 -0
  104. data/lib/hecks/bluebook/entity.rb +103 -0
  105. data/lib/hecks/bluebook/expression/canonical_form.rb +123 -0
  106. data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
  107. data/lib/hecks/bluebook/expression/projection.json +218 -0
  108. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  109. data/lib/hecks/bluebook/expression/resolver.rb +781 -0
  110. data/lib/hecks/bluebook/expression.rb +13 -0
  111. data/lib/hecks/bluebook/hexagon.rb +60 -0
  112. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  113. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  114. data/lib/hecks/bluebook/meta_validator/judge.rb +621 -0
  115. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  116. data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
  117. data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
  118. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
  119. data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -0
  120. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  121. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  122. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  123. data/lib/hecks/bluebook/meta_validator.rb +542 -0
  124. data/lib/hecks/bluebook/model_check.rb +445 -0
  125. data/lib/hecks/bluebook/pattern_subset.rb +184 -0
  126. data/lib/hecks/bluebook/policy.rb +41 -0
  127. data/lib/hecks/bluebook/process_manager.rb +128 -0
  128. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  129. data/lib/hecks/bluebook/project_loader.rb +40 -0
  130. data/lib/hecks/bluebook/project_register.rb +82 -0
  131. data/lib/hecks/bluebook/query.rb +61 -0
  132. data/lib/hecks/bluebook/read_model.rb +109 -0
  133. data/lib/hecks/bluebook/reference.rb +74 -0
  134. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  135. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  136. data/lib/hecks/bluebook/translation.rb +92 -0
  137. data/lib/hecks/bluebook/value_object.rb +66 -0
  138. data/lib/hecks/bluebook.rb +74 -0
  139. data/lib/hecks/codemod.rb +342 -0
  140. data/lib/hecks/construct.rb +71 -0
  141. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  142. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  143. data/lib/hecks/deploy/oidc.json +18 -0
  144. data/lib/hecks/doc/reference.rb +410 -0
  145. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  146. data/lib/hecks/facade/cli_door.rb +119 -0
  147. data/lib/hecks/facade/cli_runner.rb +190 -0
  148. data/lib/hecks/facade/command_request.rb +105 -0
  149. data/lib/hecks/facade/handle.rb +173 -0
  150. data/lib/hecks/facade/json_door.rb +166 -0
  151. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  152. data/lib/hecks/facade/surface/chapter.rb +107 -0
  153. data/lib/hecks/facade/surface.rb +48 -0
  154. data/lib/hecks/facade.rb +44 -0
  155. data/lib/hecks/forms/app.rb +341 -0
  156. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  157. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  158. data/lib/hecks/forms/field_renderer.rb +177 -0
  159. data/lib/hecks/forms/field_shape.rb +232 -0
  160. data/lib/hecks/forms/html.rb +84 -0
  161. data/lib/hecks/forms/index_renderer.rb +35 -0
  162. data/lib/hecks/forms/page.rb +157 -0
  163. data/lib/hecks/forms/params.rb +161 -0
  164. data/lib/hecks/forms/port_argument.rb +46 -0
  165. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  166. data/lib/hecks/forms/record_renderer.rb +119 -0
  167. data/lib/hecks/forms/record_table.rb +68 -0
  168. data/lib/hecks/forms/reference_options.rb +30 -0
  169. data/lib/hecks/forms/value_object_shape.rb +46 -0
  170. data/lib/hecks/forms.rb +54 -0
  171. data/lib/hecks/fqn.rb +94 -0
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  175. data/lib/hecks/framework/bluebook/governance.bluebook +145 -0
  176. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  177. data/lib/hecks/framework/oidc.json +39 -0
  178. data/lib/hecks/framework.rb +90 -0
  179. data/lib/hecks/freezer.rb +67 -0
  180. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +527 -0
  181. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  182. data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
  183. data/lib/hecks/fuzzing/properties.rb +1192 -0
  184. data/lib/hecks/fuzzing/replay.rb +668 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
  189. data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
  190. data/lib/hecks/fuzzing/value_generator.rb +200 -0
  191. data/lib/hecks/fuzzing.rb +12 -0
  192. data/lib/hecks/grammar/evolve.rb +324 -0
  193. data/lib/hecks/grammar/expression.bluebook +420 -0
  194. data/lib/hecks/grammar/expression_operators.json +1648 -0
  195. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  196. data/lib/hecks/grammar/oidc.json +69 -0
  197. data/lib/hecks/grammar/translation.bluebook +203 -0
  198. data/lib/hecks/grammar.rb +178 -0
  199. data/lib/hecks/ir.rb +126 -0
  200. data/lib/hecks/language/adapter.bluebook +116 -0
  201. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  202. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  203. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  204. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  205. data/lib/hecks/language/bluebook/command.bluebook +471 -0
  206. data/lib/hecks/language/bluebook/entity.bluebook +392 -0
  207. data/lib/hecks/language/bluebook/policy.bluebook +189 -0
  208. data/lib/hecks/language/bluebook/process_manager.bluebook +381 -0
  209. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  210. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  211. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  212. data/lib/hecks/language/bluebook/syntax.bluebook +448 -0
  213. data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -0
  214. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  215. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  216. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  217. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  218. data/lib/hecks/language/oidc.json +333 -0
  219. data/lib/hecks/language/port.bluebook +120 -0
  220. data/lib/hecks/language/translation/translation.bluebook +110 -0
  221. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  222. data/lib/hecks/language/world/wiring.bluebook +62 -0
  223. data/lib/hecks/language/world/world.bluebook +84 -0
  224. data/lib/hecks/literal.rb +125 -0
  225. data/lib/hecks/naming.rb +174 -0
  226. data/lib/hecks/ports/access_control.port +9 -0
  227. data/lib/hecks/ports/access_control.rb +62 -0
  228. data/lib/hecks/ports/agent/answers.rb +104 -0
  229. data/lib/hecks/ports/agent.port +8 -0
  230. data/lib/hecks/ports/agent.rb +167 -0
  231. data/lib/hecks/ports/authentication.port +6 -0
  232. data/lib/hecks/ports/authentication.rb +50 -0
  233. data/lib/hecks/ports/authorization.port +7 -0
  234. data/lib/hecks/ports/authorization.rb +62 -0
  235. data/lib/hecks/ports/clock.port +5 -0
  236. data/lib/hecks/ports/clock.rb +62 -0
  237. data/lib/hecks/ports/extraction.port +5 -0
  238. data/lib/hecks/ports/extraction.rb +37 -0
  239. data/lib/hecks/ports/identity_assignment.port +5 -0
  240. data/lib/hecks/ports/identity_assignment.rb +45 -0
  241. data/lib/hecks/ports/identity_generation.port +5 -0
  242. data/lib/hecks/ports/identity_generation.rb +49 -0
  243. data/lib/hecks/ports/identity_resolution.port +5 -0
  244. data/lib/hecks/ports/identity_resolution.rb +40 -0
  245. data/lib/hecks/ports/loading.port +4 -0
  246. data/lib/hecks/ports/loading.rb +13 -0
  247. data/lib/hecks/ports/persistence/append_only.rb +138 -0
  248. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  249. data/lib/hecks/ports/persistence/execution.rb +23 -0
  250. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  251. data/lib/hecks/ports/persistence/plugin.rb +54 -0
  252. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
  253. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
  254. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
  255. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
  256. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
  257. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
  258. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
  259. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
  260. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
  261. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
  262. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
  263. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
  264. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
  265. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
  266. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
  267. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
  268. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  269. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
  270. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
  271. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
  272. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
  273. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
  274. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
  275. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
  276. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
  277. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
  278. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
  279. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
  280. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
  281. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
  282. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
  283. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
  284. data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
  285. data/lib/hecks/ports/persistence/plugins/era.rb +48 -0
  286. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  287. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  288. data/lib/hecks/ports/persistence.port +4 -0
  289. data/lib/hecks/ports/persistence.rb +39 -0
  290. data/lib/hecks/ports/projection.port +4 -0
  291. data/lib/hecks/ports/projection.rb +96 -0
  292. data/lib/hecks/ports/query/in_memory.rb +60 -0
  293. data/lib/hecks/ports/query/ordering.rb +41 -0
  294. data/lib/hecks/ports/query.rb +36 -0
  295. data/lib/hecks/ports.rb +27 -0
  296. data/lib/hecks/projections/diagrams.rb +642 -0
  297. data/lib/hecks/projections/ir.rb +18 -0
  298. data/lib/hecks/projections/model/deviations.rb +98 -0
  299. data/lib/hecks/projections/model.rb +145 -0
  300. data/lib/hecks/projections/oidc.rb +110 -0
  301. data/lib/hecks/projections/parser_table.rb +159 -0
  302. data/lib/hecks/projections/reference.rb +38 -0
  303. data/lib/hecks/projections/shape.rb +44 -0
  304. data/lib/hecks/projections/statements.rb +110 -0
  305. data/lib/hecks/projections/vocabulary.rb +100 -0
  306. data/lib/hecks/projections.rb +32 -0
  307. data/lib/hecks/projector/cli_projector.rb +446 -0
  308. data/lib/hecks/projector/docs_projector.rb +321 -0
  309. data/lib/hecks/projector/exporter.rb +158 -0
  310. data/lib/hecks/projector/ir_projector.rb +18 -0
  311. data/lib/hecks/projector/narrate_projector.rb +243 -0
  312. data/lib/hecks/projector/target.rb +97 -0
  313. data/lib/hecks/projector.rb +186 -0
  314. data/lib/hecks/query_ir.rb +411 -0
  315. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  316. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  317. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  318. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  319. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  320. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  321. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  322. data/lib/hecks/query_specification/common/null_policy.rb +92 -0
  323. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  324. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  325. data/lib/hecks/query_specification/common/options.rb +32 -0
  326. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  327. data/lib/hecks/query_specification/common/specification.rb +10 -0
  328. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  329. data/lib/hecks/query_specification/field_path.rb +107 -0
  330. data/lib/hecks/query_specification/hop_path.rb +132 -0
  331. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  332. data/lib/hecks/query_specification.rb +14 -0
  333. data/lib/hecks/rendering.rb +48 -0
  334. data/lib/hecks/router/namespace_installer.rb +157 -0
  335. data/lib/hecks/router.rb +70 -0
  336. data/lib/hecks/runtime/aggregate_lock.rb +45 -0
  337. data/lib/hecks/runtime/boot_gates.rb +41 -0
  338. data/lib/hecks/runtime/caller.rb +66 -0
  339. data/lib/hecks/runtime/capability_graph.rb +44 -0
  340. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
  341. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
  342. data/lib/hecks/runtime/command_interpreter.rb +497 -0
  343. data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
  344. data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
  345. data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
  346. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  347. data/lib/hecks/runtime/command_rules/references.rb +189 -0
  348. data/lib/hecks/runtime/command_rules.rb +28 -0
  349. data/lib/hecks/runtime/dependency_planning.rb +245 -0
  350. data/lib/hecks/runtime/dispatcher.rb +301 -0
  351. data/lib/hecks/runtime/entity_element.rb +253 -0
  352. data/lib/hecks/runtime/entity_interpreter.rb +299 -0
  353. data/lib/hecks/runtime/errors.rb +122 -0
  354. data/lib/hecks/runtime/event.rb +51 -0
  355. data/lib/hecks/runtime/identity.rb +156 -0
  356. data/lib/hecks/runtime/instance.rb +175 -0
  357. data/lib/hecks/runtime/interpreting.rb +90 -0
  358. data/lib/hecks/runtime/loader.rb +184 -0
  359. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  360. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  361. data/lib/hecks/runtime/query_interpreter.rb +286 -0
  362. data/lib/hecks/runtime/reaction_invocation.rb +253 -0
  363. data/lib/hecks/runtime/read_model_interpreter.rb +341 -0
  364. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  365. data/lib/hecks/runtime/reference_hop.rb +99 -0
  366. data/lib/hecks/runtime/refusal_wording.rb +126 -0
  367. data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
  368. data/lib/hecks/runtime/registry/verification.rb +246 -0
  369. data/lib/hecks/runtime/registry.rb +279 -0
  370. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  371. data/lib/hecks/runtime/routing.rb +96 -0
  372. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  373. data/lib/hecks/runtime/saga_interpreter.rb +503 -0
  374. data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -0
  375. data/lib/hecks/runtime/tenant_check.rb +84 -0
  376. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  377. data/lib/hecks/runtime/value/admission.rb +130 -0
  378. data/lib/hecks/runtime/value/coercion.rb +565 -0
  379. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  380. data/lib/hecks/runtime/value.rb +125 -0
  381. data/lib/hecks/runtime.rb +107 -0
  382. data/lib/hecks/storehouse.rb +632 -0
  383. data/lib/hecks/version.rb +16 -0
  384. data/lib/hecks/vocabulary.rb +218 -0
  385. data/lib/hecks.rb +123 -6
  386. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
  387. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
  388. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
  389. metadata +412 -222
  390. data/bin/hecks +0 -7
  391. data/bin/hecks-package +0 -65
  392. data/bin/hecks_console +0 -12
  393. data/bin/hecks_serverless +0 -6
  394. data/lib/cli/build.rb +0 -14
  395. data/lib/cli/command_runner.rb +0 -28
  396. data/lib/cli/console.rb +0 -10
  397. data/lib/cli/generate.rb +0 -37
  398. data/lib/cli/hecks-cli.rb +0 -27
  399. data/lib/cli/test.rb +0 -57
  400. data/lib/console/commands.rb +0 -8
  401. data/lib/console/hecks-console.rb +0 -1
  402. data/lib/packager/README.md +0 -0
  403. data/lib/packager/app_runner.rb +0 -21
  404. data/lib/packager/args.rb +0 -26
  405. data/lib/packager/compatibility/fixnum.rb +0 -6
  406. data/lib/packager/hecks.rb +0 -39
  407. data/lib/packager/query_runner.rb +0 -21
  408. data/lib/packager/resources/Dockerfile +0 -11
  409. data/lib/packager/resources/app_binary +0 -7
  410. data/lib/packager/resources/bundle_config +0 -3
  411. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  412. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  413. data/lib/serverless/Domain +0 -32
  414. data/lib/serverless/cli.rb +0 -75
  415. data/lib/serverless/resources/command_name.js +0 -5
  416. data/lib/serverless/resources/environment.js +0 -7
  417. data/lib/serverless/resources/handler.js.tt +0 -28
  418. data/lib/serverless/resources/run_binary.js +0 -22
  419. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,410 @@
1
+ require_relative "../bluebook/meta_validator"
2
+ require_relative "../naming"
3
+
4
+ module Hecks
5
+ module Doc
6
+ # The DSL reference, projected from the language's own Syntax chapter
7
+ # — the same Keyword/Argument rows the conformance specs hold equal
8
+ # to the live builders. Nothing here is described twice: the tables
9
+ # come from the declaration, the PROSE is hand-written between
10
+ # markers the generator preserves, and the golden spec refuses a
11
+ # tree where the two have drifted.
12
+ #
13
+ # Regenerate with bin/reference. A new word arrives with a TODO
14
+ # sentinel; the coverage gate refuses to let an admitted word ship
15
+ # undocumented; prose for a word the language no longer declares is
16
+ # a hard error naming its orphans — deleting someone's writing is a
17
+ # human's decision.
18
+ module Reference
19
+ GENERATED_END = "<!-- generated:end -->".freeze
20
+ TODO_SENTINEL = "<!-- TODO: document this word -->".freeze
21
+
22
+ # A PAGE'S OWN HAND-WRITTEN OPENING, harvested under a key no word
23
+ # can ever collide with (words are strings off the Syntax chapter;
24
+ # this is a Symbol). It exists so a page can boot ONCE — load a real
25
+ # corpus chapter, wire its hexagon — and have every word's example
26
+ # below run against that single boot, the way a guide's opening
27
+ # `ruby boot` block already does. Without it each word would have to
28
+ # stand up its own domain, and 105 invented chapters would collide
29
+ # on the facade constants `Surface.install` never uninstalls.
30
+ PREAMBLE = :preamble
31
+
32
+ module_function
33
+
34
+ def generated_begin(word) = "<!-- generated:begin word=#{word} -->"
35
+
36
+ # Keyed by REGION rather than by word — the same marker convention,
37
+ # used for the parts of a page that are not about one word: a
38
+ # page's generated lede here, README's generated indexes below.
39
+ def region_begin(id) = "<!-- generated:begin id=#{id} -->"
40
+
41
+ def syntax
42
+ meta = Bluebook::MetaValidator.grammar_registry.bluebook("Bluebook")
43
+ meta.aggregates.find { |aggregate| aggregate.hecks_name == "Syntax" }
44
+ end
45
+
46
+ def rows(name)
47
+ syntax.value_objects.find { |vo| vo.hecks_name == name }
48
+ .members.map { |row| row.to_h.transform_values(&:to_s) }
49
+ end
50
+
51
+ # S14, ADR 0026 — Keyword/Argument are genuine entities of Syntax
52
+ # now, dispatched (not merely declared) so their own `status`
53
+ # really is a lifecycle. `SyntaxBoot.call` discovers the static
54
+ # aggregate-local seed rows (`KeywordSeed`/`ArgumentSeed`), dispatches each
55
+ # one through the real admission/lifecycle door, and hands back the
56
+ # same shape `rows` used to produce — nothing below this needed to
57
+ # change.
58
+ #
59
+ # NO SEPARATE `@keywords ||=` HERE ANYMORE. This module used to
60
+ # memoize its own copy on top of `SyntaxBoot.call`'s own memo — a
61
+ # double cache with no way to invalidate either half, and a real
62
+ # bug: whichever call in the whole process happened to land first
63
+ # got locked in forever, even one caught mid-build missing every
64
+ # Paging-attached word (limit/offset/cursor/nulls). `SyntaxBoot.call`
65
+ # now carries the one cache that matters (keyed on the grammar
66
+ # registry's own chapter set — see its comment) ; this delegates
67
+ # straight through instead of shadowing it.
68
+ def keywords = Bluebook::MetaValidator::SyntaxBoot.call[:keywords]
69
+ def arguments = Bluebook::MetaValidator::SyntaxBoot.call[:arguments]
70
+
71
+ def status_of(row) = row[:status].to_s.empty? ? "admitted" : row[:status].to_s
72
+ def live?(row) = %w[admitted deprecated].include?(status_of(row))
73
+
74
+ def contexts = keywords.map { |row| row[:context] }.uniq
75
+
76
+ def page_name(context) = "#{Naming.snake(context)}.md"
77
+
78
+ # Every reference page, rendered fresh — prose carried over from
79
+ # the committed pages, new words seeded with the sentinel, orphaned
80
+ # prose refused.
81
+ def pages(directory)
82
+ contexts.each_with_object({}) do |context, pages|
83
+ path = File.join(directory, page_name(context))
84
+ prose = File.exist?(path) ? harvest(File.read(path)) : {}
85
+ pages[page_name(context)] = render_page(context, prose, path)
86
+ end.merge("index.md" => render_index)
87
+ end
88
+
89
+ # A WORD ADMITTING TWO FORMS HAS TWO ROWS — syntax.bluebook's own
90
+ # stated rule, and `identified_by` (a block, or a bare argument and
91
+ # none) is the case that made it real again. One SECTION per word all
92
+ # the same: the prose is the word's rather than the form's, and the
93
+ # argument rows join by (word, context) and so already cover every
94
+ # form. Grouped rather than rendered per row, or a reader would meet
95
+ # the same heading and the same paragraph twice.
96
+ def render_page(context, prose, path)
97
+ words = keywords.select { |row| row[:context] == context }.group_by { |row| row[:word] }
98
+ orphans = prose.keys - words.keys - [PREAMBLE]
99
+ unless orphans.empty?
100
+ raise "#{path} carries prose for #{orphans.join(', ')}, which the language no longer " \
101
+ "declares in #{context} — deleting writing is a human's decision, so decide"
102
+ end
103
+
104
+ sections = words.map { |word, forms| render_word(forms, prose[word]) }
105
+ preamble = prose[PREAMBLE].to_s.strip
106
+ <<~PAGE
107
+ # #{context}
108
+
109
+ #{region_begin('page')}
110
+ #{context_lede(context)}
111
+
112
+ *The tables on this page are generated from the language's own
113
+ aggregate-local syntax tables (`lib/hecks/language/**/*.bluebook`)
114
+ by `bin/reference` — do not edit inside the markers. The prose
115
+ between them is hand-written and survives regeneration.*
116
+ #{GENERATED_END}
117
+ #{preamble.empty? ? '' : "\n#{preamble}\n"}
118
+ #{sections.join("\n")}
119
+ PAGE
120
+ end
121
+
122
+ def context_lede(context)
123
+ openers = keywords.select { |row| row[:opens] == context }
124
+ return "Words available at the top of a file." if context == "File"
125
+ return "Words available in the type position of an `attribute`." if context == "Type"
126
+
127
+ inside = openers.map { |row| "`#{row[:word]} do ... end`" }.uniq.join(" / ")
128
+ inside.empty? ? "Words available in the #{context} body." : "Words available inside #{inside}."
129
+ end
130
+
131
+ # One SPELLING per form, everything else off the first row — the
132
+ # columns that differ between two forms of one word are `body` (which
133
+ # is what the spelling shows) and nothing else.
134
+ def render_word(forms, prose)
135
+ row = forms.first
136
+ table = argument_table(row)
137
+ facts = []
138
+ facts << "opens a `#{row[:opens]}` body" unless row[:opens].to_s.empty?
139
+ facts << "fills `#{row[:fills]}`" unless row[:fills].to_s.empty?
140
+ facts << "**status: #{status_of(row)}**" unless status_of(row) == "admitted"
141
+ facts << "was `#{row[:was]}`" unless row[:was].to_s.empty?
142
+ spellings = forms.map { |form| "`#{signature(form)}`" }.join(" / ")
143
+
144
+ <<~WORD
145
+ ## #{row[:word]}
146
+
147
+ #{generated_begin(row[:word])}
148
+ #{spellings}#{facts.empty? ? '' : " — #{facts.join(', ')}"}
149
+ #{table}#{GENERATED_END}
150
+
151
+ #{prose_or_sentinel(prose)}
152
+ WORD
153
+ end
154
+
155
+ def prose_or_sentinel(prose)
156
+ text = prose.to_s.strip
157
+ text.empty? ? TODO_SENTINEL : text
158
+ end
159
+
160
+ def word_arguments(row)
161
+ arguments.select { |arg| arg[:keyword] == row[:word] && arg[:context] == row[:context] }
162
+ end
163
+
164
+ def signature(row)
165
+ positional = word_arguments(row).reject { |arg| arg[:at].to_s.empty? }
166
+ .sort_by { |arg| arg[:at].to_i }
167
+ .map { |arg| arg[:fills].to_s.empty? ? arg[:kind] : arg[:fills] }
168
+ named = word_arguments(row).select { |arg| arg[:at].to_s.empty? }
169
+ .map { |arg| "#{arg[:named]}:" }
170
+ parts = positional + named
171
+ base = parts.empty? ? row[:word] : "#{row[:word]} #{parts.join(', ')}"
172
+ row[:body].to_s == "none" ? base : "#{base} do ... end"
173
+ end
174
+
175
+ def argument_table(row)
176
+ args = word_arguments(row)
177
+ return "" if args.empty?
178
+
179
+ lines = ["", "| argument | kind | required | fills |", "|---|---|---|---|"]
180
+ args.each do |arg|
181
+ name = arg[:at].to_s.empty? ? "`#{arg[:named]}:`" : "positional #{arg[:at]}"
182
+ lines << "| #{name} | #{arg[:kind]} | #{arg[:required]} | #{arg[:fills]} |"
183
+ end
184
+ lines.join("\n") + "\n"
185
+ end
186
+
187
+ def render_index
188
+ listed = contexts.map do |context|
189
+ count = keywords.select { |row| row[:context] == context }.map { |row| row[:word] }.uniq.size
190
+ "- [#{context}](#{page_name(context)}) — #{count} #{count == 1 ? 'word' : 'words'}"
191
+ end
192
+ <<~INDEX
193
+ # The DSL reference
194
+
195
+ One page per context — the place in a file where a word may be
196
+ typed. Generated by `bin/reference` from the Syntax chapter;
197
+ the prose between the generated markers is hand-written and
198
+ survives regeneration.
199
+
200
+ #{listed.join("\n")}
201
+ INDEX
202
+ end
203
+
204
+ # Prose keyed by word: everything between a section's generated
205
+ # region and the next `## ` heading (or end of file).
206
+ #
207
+ # Starts on PREAMBLE rather than nil so the text between the PAGE's
208
+ # own generated lede and its first word heading is carried over too
209
+ # instead of being silently dropped. A page written before that
210
+ # region existed has no generated marker ahead of its first `## `,
211
+ # so nothing is collecting when that heading arrives and no empty
212
+ # preamble is invented — the older shape reads back unchanged.
213
+ def harvest(text)
214
+ prose = {}
215
+ current = PREAMBLE
216
+ collecting = false
217
+ buffer = []
218
+
219
+ # A HEADING INSIDE A FENCE IS NOT A HEADING. `## something` is an
220
+ # ordinary Ruby comment, and now that every word's section carries
221
+ # runnable code, one written at the left margin would otherwise
222
+ # end that section mid-example and orphan the rest of it under a
223
+ # word the language never declared.
224
+ in_fence = false
225
+
226
+ text.each_line do |line|
227
+ in_fence = !in_fence if line.start_with?("```")
228
+
229
+ if !in_fence && (match = line.match(/\A## (\S+)\s*\z/))
230
+ prose[current] = buffer.join.strip if current && collecting
231
+ current = match[1]
232
+ collecting = false
233
+ buffer = []
234
+ elsif line.include?(GENERATED_END)
235
+ collecting = true
236
+ elsif collecting
237
+ buffer << line
238
+ end
239
+ end
240
+ prose[current] = buffer.join.strip if current && collecting
241
+ prose.reject { |_word, text_| text_.empty? || text_ == TODO_SENTINEL }
242
+ end
243
+
244
+ def write!(directory)
245
+ FileUtils.mkdir_p(directory)
246
+ pages(directory).each do |name, content|
247
+ File.write(File.join(directory, name), content)
248
+ end
249
+ end
250
+
251
+ # README's own generated regions — the same marker convention as a
252
+ # reference page, keyed by region id instead of a word, so the
253
+ # index a reader lands on first can't drift from what actually
254
+ # exists on disk either.
255
+ def readme_regions(root)
256
+ {
257
+ "guides" => guide_index(root),
258
+ "reference" => reference_index(root),
259
+ "tools" => tool_table(root),
260
+ "corpus" => corpus_roster(root),
261
+ "diagrams" => diagram_showcase(root)
262
+ }
263
+ end
264
+
265
+ def guide_index(root)
266
+ paths = Dir.glob(File.join(root, "docs/implemented/guides/*.md")).sort
267
+ .reject { |p| %w[AUTHORING.md].include?(File.basename(p)) }
268
+ lines = paths.map do |path|
269
+ heading = File.foreach(path).find { |line| line.start_with?("# ") }
270
+ title = heading ? heading.sub(/\A#\s*/, "").strip : File.basename(path)
271
+ "- [#{title}](docs/implemented/guides/#{File.basename(path)})"
272
+ end
273
+ lines.join("\n")
274
+ end
275
+
276
+ def reference_index(root)
277
+ count = contexts.size
278
+ "[The DSL reference](docs/implemented/reference/index.md) — #{count} contexts, generated from " \
279
+ "the aggregate-local tables under `lib/hecks/language/` and held to them by " \
280
+ "`spec/reference_golden_spec.rb`."
281
+ end
282
+
283
+ def tool_table(root)
284
+ scripts = Dir.glob(File.join(root, "bin/*")).select { |p| File.file?(p) }.sort
285
+ rows = scripts.filter_map { |path| [path, tool_summary(path)] }.select { |_, desc| desc }
286
+ lines = ["| tool | |", "|---|---|"]
287
+ rows.each { |path, desc| lines << "| `bin/#{File.basename(path)}` | #{desc} |" }
288
+ lines.join("\n")
289
+ end
290
+
291
+ # The opening comment PARAGRAPH, not just the first line — a table
292
+ # cell that trails off mid-clause reads worse than one that runs a
293
+ # little long and says "...". A code-bearing comment (`field.name`,
294
+ # `pattern:`) makes naive sentence-splitting on "." or ":" cut in
295
+ # the wrong place, so this truncates on LENGTH alone.
296
+ def tool_summary(path)
297
+ comment_lines = []
298
+ started = false
299
+ File.foreach(path).first(10).each do |line|
300
+ if line.start_with?("#") && !line.start_with?("#!")
301
+ next if !started && line.strip == "#"
302
+
303
+ started = true
304
+ comment_lines << line.sub(/\A#\s?/, "").rstrip
305
+ elsif started
306
+ break
307
+ end
308
+ end
309
+ return nil if comment_lines.empty?
310
+
311
+ text = comment_lines.join(" ").squeeze(" ")
312
+ text.length > 140 ? "#{text[0, 137]}..." : text
313
+ end
314
+
315
+ # ONE REAL, COMMITTED FILE, READ FRESH — not re-derived from a boot
316
+ # (this module never requires `hecks/projections/diagrams`, and
317
+ # shouldn't just to draw one example). `docs/generated/diagrams/`
318
+ # is already held to the declaration by `spec/diagrams_spec.rb`'s
319
+ # own drift check; this just quotes its own output, so the two
320
+ # can't independently drift from each other either — a stale
321
+ # Order_lifecycle.mmd fails THAT spec long before this one runs.
322
+ def diagram_showcase(root)
323
+ lifecycle = File.read(File.join(root, "docs/generated/diagrams/pizzas/Order_lifecycle.mmd")).strip
324
+ <<~MARKDOWN.strip
325
+ `bin/project_diagrams` reads a booted domain's own declaration and draws it as Mermaid — nine kinds so far: `<Name>_lifecycle.mmd`, `relationships.mmd`, `dispatch.mmd`, `roles.mmd`, `ports.mmd`, `read_models.mmd`, `<Name>_surface.mmd` (what a command does, and what it writes), `<Name>_saga.mmd`, and `frameworks.mmd`. Nothing hand-drawn — the same reason a domain is data at all. Order's own lifecycle, straight off the bluebook above:
326
+
327
+ ```mermaid
328
+ #{lifecycle}
329
+ ```
330
+
331
+ The full set for every domain in this checkout — `examples/pizzas`, `examples/banking` — lives in [`docs/generated/diagrams/`](docs/generated/diagrams/), held to the declaration by `spec/diagrams_spec.rb` the same drift-refusing way this page is held to its own source.
332
+ MARKDOWN
333
+ end
334
+
335
+ def corpus_roster(root)
336
+ dirs = Dir.glob(File.join(root, "examples/*/")).sort
337
+ lines = dirs.filter_map do |dir|
338
+ name = File.basename(dir.chomp("/"))
339
+ bluebooks = Dir.glob(File.join(dir, "bluebook/*.bluebook")).sort
340
+ bluebooks = Dir.glob(File.join(dir, "*.bluebook")).sort if bluebooks.empty?
341
+ next if bluebooks.empty?
342
+
343
+ vision = bluebooks.filter_map { |bluebook| File.read(bluebook)[/vision\s+"([^"]*)"/, 1] }.first
344
+ "- **#{name}** — #{vision}"
345
+ end
346
+ lines.join("\n")
347
+ end
348
+
349
+ def render_readme(root, text)
350
+ readme_regions(root).reduce(text) do |current, (id, content)|
351
+ pattern = /#{Regexp.escape(region_begin(id))}.*?#{Regexp.escape(GENERATED_END)}/m
352
+ current.sub(pattern) { "#{region_begin(id)}\n#{content}\n#{GENERATED_END}" }
353
+ end
354
+ end
355
+
356
+ def write_readme!(root)
357
+ path = File.join(root, "README.md")
358
+ File.write(path, render_readme(root, File.read(path)))
359
+ end
360
+
361
+ # An example A READER CAN SEE and the harness will actually run.
362
+ # `ruby skip` is display-only by the doctest harness's own rule, and
363
+ # a hidden `<!-- doctest:boot -->` block is setup rather than an
364
+ # example — a word whose only "example" is invisible or inert is a
365
+ # word still shipping on its prose alone, which is the thing this
366
+ # gate exists to refuse.
367
+ EXAMPLE_FENCE = /^```ruby(?: bluebook| boot)?[ \t]*$/.freeze
368
+
369
+ def exemplified?(prose) = prose.to_s.match?(EXAMPLE_FENCE)
370
+
371
+ # EVERY LIVE WORD, PAIRED WITH ITS PROSE. Both coverage gates ask a
372
+ # question about this same walk and differ only in what they ask of
373
+ # the prose, so they share it rather than each re-deriving the page
374
+ # set — the two are meant to move together, and one drifting past
375
+ # the other is how a word ends up counted documented by one and
376
+ # missing to the other.
377
+ #
378
+ # `harvest` already rejects empty prose and the TODO sentinel, so a
379
+ # word with nothing written for it arrives here with a nil.
380
+ def live_words(directory)
381
+ contexts.flat_map { |context|
382
+ path = File.join(directory, page_name(context))
383
+ prose = File.exist?(path) ? harvest(File.read(path)) : {}
384
+ keywords.select { |row| row[:context] == context && live?(row) }
385
+ .map { |row| [row[:word], context, prose[row[:word]]] }
386
+ }.uniq { |word, context, _| [word, context] }
387
+ end
388
+
389
+ def name_of(word, context) = "#{word} (#{context})"
390
+
391
+ # The coverage gate's question: every LIVE word with no prose yet.
392
+ def undocumented(directory)
393
+ live_words(directory).reject { |_word, _context, prose| prose }
394
+ .map { |word, context, _| name_of(word, context) }
395
+ end
396
+
397
+ # The SECOND coverage gate: prose is a declaration, and a
398
+ # declaration nothing runs cannot disagree with anything. A word
399
+ # documented only in sentences can go stale — or describe a word
400
+ # the runtime never wired at all, which this repository has already
401
+ # shipped twice (`read_model`'s where/order_by/limit/offset, and
402
+ # `role`/`goal` on a command). An example that RUNS is the only
403
+ # documentation that can go red.
404
+ def unexemplified(directory)
405
+ live_words(directory).reject { |_word, _context, prose| exemplified?(prose) }
406
+ .map { |word, context, _| name_of(word, context) }
407
+ end
408
+ end
409
+ end
410
+ end
@@ -0,0 +1,75 @@
1
+ module Hecks
2
+ # A VENDORED, EXTERNAL BLUEBOOK — same shape as Framework (framework.rb),
3
+ # for members that don't ship inside hecks's own lib/ at all: a
4
+ # separate, independently-versioned package
5
+ # (github.com/chrisyoung/embryonaut_bluebooks) that a consuming project
6
+ # vendors into its OWN checkout, the same way a project already vendors
7
+ # hecks itself (bin/vendor_hecks, vendor/hecks/).
8
+ #
9
+ # RECOVERED, NOT REBUILT — this module and its `uses_embryonaut_bluebook`
10
+ # DSL word (hecksagon_builder.rb) were built on a prior commit of this
11
+ # repo (933d1dd), vendored out to a real consumer (lifeadelics/domain,
12
+ # for embryonaut_bluebooks/payments), and then lost from this repo's own
13
+ # reachable history — a hard reset or rebase left no branch containing
14
+ # that commit. The lifeadelics vendor snapshot (a `git archive` of that
15
+ # commit, committed into their repo) was the only surviving copy; this
16
+ # file is ported forward from it, checked against current `main`'s own
17
+ # conventions rather than copied wholesale, since the two trees had
18
+ # otherwise diverged for weeks in both directions.
19
+ #
20
+ # RESOLVED FROM THE CONSUMING REGISTRY'S OWN ROOT, not this gem's
21
+ # __dir__ — Framework::ROOT can be a fixed, `__dir__`-relative constant
22
+ # because framework members ship inside this gem; an embryonaut bluebook
23
+ # ships inside the CONSUMER's own checkout instead, at
24
+ # `<registry.root>/vendor/embryonaut_bluebooks/<name>/bluebook/`. There
25
+ # is no fixed answer until a registry (and its root) actually exists, so
26
+ # this resolves lazily, per call — the same reason `uses_framework`
27
+ # itself only runs at hecksagon-build time, when a real registry is
28
+ # current.
29
+ #
30
+ # EVERY `.bluebook` FILE IN THE PACKAGE, SORTED — not just one. Unlike a
31
+ # framework member (one file, named by its own stem), a vendored package
32
+ # can span several bluebook files that reopen the SAME `Hecks.bluebook`
33
+ # (embryonaut_bluebooks/payments/bluebook/{payment,payments,policies}
34
+ # .bluebook all reopen "Payments"). Load order matters — policies
35
+ # .bluebook names `Payment::Succeed` and needs the aggregate already
36
+ # built — and a plain alphabetical sort already gives the right order:
37
+ # payment < payments < policies, the same reason that package's own
38
+ # files are named to fall in that order in the first place.
39
+ #
40
+ # ONLY THE BLUEBOOK FILES — same restriction Framework draws, same
41
+ # reason: a `.hecksagon`/`.port`/`.adapter` is a WIRING decision
42
+ # (persistence, which processor adapter is bound) that belongs to
43
+ # whoever is deploying, never baked into the vendored package itself.
44
+ # embryonaut_bluebooks/payments ships its own mock `.hecksagon` for its
45
+ # own spec suite; a consumer declares its OWN separate
46
+ # `Hecks.hecksagon "Payments" do ... end` to bind real storage/adapters
47
+ # — see Framework's own comment for the fuller reasoning, identical here.
48
+ #
49
+ # IDEMPOTENT THE SAME WAY Framework.load! IS — checked against the
50
+ # bluebook this package actually declares (`Naming.pascal("payments")`
51
+ # => "Payments"), not a separate ledger. A vendored package's directory
52
+ # name and its declared `Hecks.bluebook` name are the one convention
53
+ # this reuses from Framework rather than reinventing.
54
+ module EmbryonautBluebook
55
+ def self.load!(name, registry: Hecks.current_registry)
56
+ unless registry&.root
57
+ raise Runtime::WiringError,
58
+ "uses_embryonaut_bluebook(#{name.inspect}) needs a registry with a root to vendor from"
59
+ end
60
+
61
+ return if registry.bluebook(Naming.pascal(name.to_s))
62
+
63
+ dir = File.join(registry.root, "vendor", "embryonaut_bluebooks", name.to_s, "bluebook")
64
+ files = Dir.glob(File.join(dir, "*.bluebook"))
65
+
66
+ if files.empty?
67
+ raise Runtime::WiringError,
68
+ "no vendored embryonaut bluebook named #{name.inspect} at #{dir} — " \
69
+ "run bin/vendor_embryonaut_bluebooks #{name}"
70
+ end
71
+
72
+ files.each { |file| Kernel.load(file) }
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,119 @@
1
+ require_relative "../runtime/errors"
2
+
3
+ module Hecks
4
+ module Facade
5
+ # THE CLI DOOR — WHERE Facade MEETS A CALLER HOLDING FLAT STRINGS.
6
+ #
7
+ # `JsonDoor` beside this one translates for a caller holding parsed JSON:
8
+ # String keys, already-nested objects, real Integers. A command line has
9
+ # neither of those. It has `sequence.value=99` — one flat string, with the
10
+ # nesting spelled as a path and the type not spelled at all.
11
+ #
12
+ # So this does the two things that turns into: rebuild the nesting, and
13
+ # give every leaf the type the chapter declared for it.
14
+ #
15
+ # THE TYPE COMES FROM THE PROJECTION, NEVER FROM THE VALUE. A door that
16
+ # guessed — "99 looks like a number" — would send the Integer 99 for a
17
+ # version string of "99", and be wrong in a way nothing downstream could
18
+ # detect, because both are perfectly good arguments. `Projector::CliProjector`
19
+ # already read the declared field type out of the value object; this only
20
+ # applies it.
21
+ module CliDoor
22
+ module_function
23
+
24
+ # `["reference.value=BUG#1", "sequence.value=99"]` against a projected
25
+ # verb spec -> `{ reference: { value: "BUG#1" }, sequence: { value: 99 } }`
26
+ def arguments(spec, pairs)
27
+ # Legacy options are accepted but not printed in help. This lets an
28
+ # existing id=... aggregate invocation cross the new receiver boundary
29
+ # while the projected surface teaches to=... exclusively.
30
+ options = (spec[:arguments] + Array(spec[:legacy_arguments])).to_h do |argument|
31
+ [argument[:path], argument]
32
+ end
33
+
34
+ pairs.each_with_object({}) do |pair, args|
35
+ path, value = split(pair)
36
+ # key? first, never `||` — full names whichever spelling (the
37
+ # bare path, or its one-argument expansion) actually declares
38
+ # this option, and the lookup below must hold to that same
39
+ # decision rather than re-guessing which one exists.
40
+ full = options.key?(path) ? path : expand(path, options)
41
+ argument = options.key?(full) ? options[full] : raise(Runtime::NotFound, unknown(path, options.keys))
42
+
43
+ next append(args, full.split("."), cast(value, argument[:type])) if argument[:list]
44
+
45
+ bury(args, full.split("."), cast(value, argument[:type]))
46
+ end
47
+ end
48
+
49
+ def split(pair)
50
+ name, value = pair.split("=", 2)
51
+ raise Runtime::NotFound, "#{pair.inspect} is not name=value" if value.nil?
52
+
53
+ [name, value]
54
+ end
55
+
56
+ # THE SHORT FORM, FOR THE COMMON CASE. Almost every value object in this
57
+ # corpus has exactly one field, so `reference=BUG#1` is unambiguous and
58
+ # is what anybody types. Expanded only when precisely one option starts
59
+ # with that prefix — two would be a guess, and a guess about which field
60
+ # a caller meant is worse than asking them to say.
61
+ def expand(path, options)
62
+ candidates = options.keys.select { |key| key.start_with?("#{path}.") }
63
+ candidates.length == 1 ? candidates.first : path
64
+ end
65
+
66
+ def cast(value, type)
67
+ case type
68
+ when "Integer" then Integer(value)
69
+ when "Float" then Float(value)
70
+ when "Boolean" then %w[true yes 1].include?(value.downcase)
71
+ else value
72
+ end
73
+ rescue ArgumentError
74
+ raise Runtime::TypeMismatch, "#{value.inspect} is not #{type} — the chapter declares this field as #{type}"
75
+ end
76
+
77
+ # A LIST GROWS RATHER THAN OVERWRITES, and getting this wrong is silent.
78
+ #
79
+ # `tags.value=framework tags.value=model-checker` used to reach `bury`
80
+ # twice and store the second one alone — no refusal, no warning, one tag
81
+ # simply gone. That is the failure the interview named first: not the
82
+ # loud kind, the kind where a value is forgotten and the caller has no
83
+ # way to notice.
84
+ #
85
+ # A LIST OF ONE IS STILL A LIST. `tags.value=flaky` produces
86
+ # `[{ value: "flaky" }]`, not `{ value: "flaky" }`, because the chapter
87
+ # declared a collection and a caller who sent one element did not
88
+ # thereby declare a different shape. The old behaviour handed a bare
89
+ # object to a `list_of` attribute, and everything downstream that walks
90
+ # it — a query's `contains`, a projection, the Postgres adapter's own
91
+ # array handling — is entitled to assume it can iterate.
92
+ #
93
+ # MULTI-FIELD ELEMENTS ARE NOT SUPPORTED HERE, deliberately. A flat
94
+ # command line has no way to say which `a.x=` goes with which `a.y=`,
95
+ # and inventing an index syntax would be a language nobody asked for.
96
+ # Every list in this corpus is a list of single-field value objects; a
97
+ # richer one is a job for `JsonDoor`, which has real nesting.
98
+ def append(hash, path, value)
99
+ *branches, leaf = path.map(&:to_sym)
100
+ holder = branches[0..-2].reduce(hash) { |node, key| node[key] ||= {} }
101
+ list = holder[branches.last] ||= []
102
+
103
+ list << { leaf => value }
104
+ hash
105
+ end
106
+
107
+ def bury(hash, path, value)
108
+ *branches, leaf = path.map(&:to_sym)
109
+ target = branches.reduce(hash) { |node, key| node[key] ||= {} }
110
+ target[leaf] = value
111
+ hash
112
+ end
113
+
114
+ def unknown(path, known)
115
+ "no argument #{path.inspect} — this verb takes #{known.sort.join(', ')}"
116
+ end
117
+ end
118
+ end
119
+ end