hecks 1.3.0 → 1.5.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 (373) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +72 -7
  3. data/lib/hecks/adapters/driven/d1.rb +187 -23
  4. data/lib/hecks/adapters/driven/folder.rb +83 -10
  5. data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
  6. data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
  7. data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
  8. data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
  9. data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
  10. data/lib/hecks/adapters/driven/heki.rb +106 -9
  11. data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
  12. data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
  13. data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
  15. data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
  16. data/lib/hecks/adapters/driven/lambda.rb +82 -35
  17. data/lib/hecks/adapters/driven/local_storage.rb +83 -10
  18. data/lib/hecks/adapters/driven/memory.rb +205 -9
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
  24. data/lib/hecks/adapters/driven/postgres.rb +175 -28
  25. data/lib/hecks/adapters/driven/prism.rb +50 -11
  26. data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  31. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  32. data/lib/hecks/adapters/driven/system_clock.rb +3 -0
  33. data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
  34. data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
  35. data/lib/hecks/adapters/driven.rb +6 -4
  36. data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
  37. data/lib/hecks/behaviors/dsl.rb +60 -2
  38. data/lib/hecks/behaviors/expectations.rb +190 -29
  39. data/lib/hecks/behaviors/ir.rb +12 -1
  40. data/lib/hecks/behaviors/rspec.rb +9 -1
  41. data/lib/hecks/behaviors/runner.rb +21 -2
  42. data/lib/hecks/behaviors.rb +9 -1
  43. data/lib/hecks/bluebook/aggregate.rb +43 -13
  44. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
  45. data/lib/hecks/bluebook/assembly/build.rb +26 -5
  46. data/lib/hecks/bluebook/assembly/contract.rb +98 -23
  47. data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
  48. data/lib/hecks/bluebook/assembly/marks.rb +159 -30
  49. data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
  50. data/lib/hecks/bluebook/assembly.rb +32 -14
  51. data/lib/hecks/bluebook/attribute.rb +26 -12
  52. data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
  53. data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
  54. data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
  55. data/lib/hecks/bluebook/behaviour/command.rb +55 -25
  56. data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
  57. data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
  58. data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
  59. data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
  60. data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
  61. data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
  62. data/lib/hecks/bluebook/behaviour/query.rb +6 -1
  63. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
  64. data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
  65. data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
  66. data/lib/hecks/bluebook/capabilities.rb +27 -0
  67. data/lib/hecks/bluebook/chapter.rb +51 -9
  68. data/lib/hecks/bluebook/command.rb +62 -17
  69. data/lib/hecks/bluebook/domain_port.rb +34 -9
  70. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  71. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  72. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
  73. data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
  74. data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
  75. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
  76. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
  77. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  78. data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
  79. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  80. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  81. data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
  82. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  83. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
  84. data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
  85. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  86. data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
  87. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  88. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  89. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
  90. data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
  91. data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
  92. data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
  93. data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
  94. data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
  95. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  96. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  97. data/lib/hecks/bluebook/entity.rb +40 -11
  98. data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
  99. data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
  100. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
  101. data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
  102. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
  103. data/lib/hecks/bluebook/expression/resolver.rb +369 -128
  104. data/lib/hecks/bluebook/hexagon.rb +35 -1
  105. data/lib/hecks/bluebook/lifecycle.rb +12 -1
  106. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
  107. data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
  108. data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
  109. data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
  110. data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
  111. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
  112. data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
  113. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
  114. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
  115. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
  116. data/lib/hecks/bluebook/meta_validator.rb +235 -139
  117. data/lib/hecks/bluebook/model_check.rb +434 -104
  118. data/lib/hecks/bluebook/pattern_subset.rb +32 -10
  119. data/lib/hecks/bluebook/policy.rb +15 -13
  120. data/lib/hecks/bluebook/process_manager.rb +27 -14
  121. data/lib/hecks/bluebook/project_discovery.rb +5 -0
  122. data/lib/hecks/bluebook/project_loader.rb +40 -0
  123. data/lib/hecks/bluebook/project_register.rb +50 -6
  124. data/lib/hecks/bluebook/query.rb +31 -4
  125. data/lib/hecks/bluebook/read_model.rb +35 -15
  126. data/lib/hecks/bluebook/reference.rb +26 -13
  127. data/lib/hecks/bluebook/smoke_test.rb +46 -23
  128. data/lib/hecks/bluebook/synthesizer.rb +46 -12
  129. data/lib/hecks/bluebook/translation.rb +34 -5
  130. data/lib/hecks/bluebook/value_object.rb +29 -11
  131. data/lib/hecks/bluebook.rb +5 -6
  132. data/lib/hecks/codemod.rb +138 -50
  133. data/lib/hecks/construct.rb +21 -7
  134. data/lib/hecks/corpus.rb +438 -0
  135. data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
  136. data/lib/hecks/doc/reference.rb +200 -31
  137. data/lib/hecks/embryonaut_bluebook.rb +38 -15
  138. data/lib/hecks/facade/cli_door.rb +69 -10
  139. data/lib/hecks/facade/cli_runner.rb +105 -24
  140. data/lib/hecks/facade/command_request.rb +23 -0
  141. data/lib/hecks/facade/handle.rb +155 -35
  142. data/lib/hecks/facade/json_door.rb +106 -25
  143. data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
  144. data/lib/hecks/facade/surface/chapter.rb +26 -17
  145. data/lib/hecks/facade/surface.rb +16 -3
  146. data/lib/hecks/facade.rb +15 -4
  147. data/lib/hecks/forms/app.rb +46 -30
  148. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  149. data/lib/hecks/forms/field_renderer.rb +142 -6
  150. data/lib/hecks/forms/field_shape.rb +183 -19
  151. data/lib/hecks/forms/html.rb +51 -7
  152. data/lib/hecks/forms/index_renderer.rb +14 -2
  153. data/lib/hecks/forms/page.rb +14 -0
  154. data/lib/hecks/forms/params.rb +120 -23
  155. data/lib/hecks/forms/port_argument.rb +14 -2
  156. data/lib/hecks/forms/query_form_renderer.rb +65 -2
  157. data/lib/hecks/forms/record_renderer.rb +60 -2
  158. data/lib/hecks/forms/record_table.rb +28 -1
  159. data/lib/hecks/forms/reference_options.rb +24 -0
  160. data/lib/hecks/forms/value_object_shape.rb +13 -3
  161. data/lib/hecks/forms.rb +24 -4
  162. data/lib/hecks/fqn.rb +59 -1
  163. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  164. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  165. data/lib/hecks/framework/oidc.json +15 -0
  166. data/lib/hecks/framework.rb +79 -25
  167. data/lib/hecks/freezer.rb +27 -11
  168. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
  169. data/lib/hecks/fuzzing/combination_miner.rb +178 -0
  170. data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
  171. data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
  172. data/lib/hecks/fuzzing/differential.rb +192 -0
  173. data/lib/hecks/fuzzing/domain_generator.rb +871 -0
  174. data/lib/hecks/fuzzing/era_boundary.rb +68 -19
  175. data/lib/hecks/fuzzing/form_census.rb +121 -20
  176. data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
  177. data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
  178. data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
  179. data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
  180. data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
  181. data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
  182. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
  183. data/lib/hecks/fuzzing/properties/guards.rb +86 -42
  184. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
  185. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
  186. data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
  187. data/lib/hecks/fuzzing/properties/querying.rb +82 -28
  188. data/lib/hecks/fuzzing/properties.rb +84 -51
  189. data/lib/hecks/fuzzing/qa_settings.rb +164 -0
  190. data/lib/hecks/fuzzing/replay.rb +241 -133
  191. data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
  192. data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
  193. data/lib/hecks/fuzzing/self_consistency.rb +315 -137
  194. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
  195. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
  196. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
  197. data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
  198. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
  199. data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
  200. data/lib/hecks/fuzzing/shrinker.rb +309 -0
  201. data/lib/hecks/fuzzing/structural_skips.rb +37 -130
  202. data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
  203. data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
  204. data/lib/hecks/fuzzing/value_generator.rb +110 -22
  205. data/lib/hecks/fuzzing.rb +5 -0
  206. data/lib/hecks/grammar/evolve.rb +188 -12
  207. data/lib/hecks/grammar.rb +53 -7
  208. data/lib/hecks/ir.rb +51 -20
  209. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  210. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  211. data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
  212. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  213. data/lib/hecks/language/oidc.json +5 -0
  214. data/lib/hecks/literal.rb +41 -9
  215. data/lib/hecks/naming.rb +112 -31
  216. data/lib/hecks/ports/access_control.rb +53 -2
  217. data/lib/hecks/ports/agent/answers.rb +83 -6
  218. data/lib/hecks/ports/agent.rb +119 -35
  219. data/lib/hecks/ports/authentication.rb +44 -4
  220. data/lib/hecks/ports/authorization.rb +50 -11
  221. data/lib/hecks/ports/clock.rb +42 -23
  222. data/lib/hecks/ports/extraction.rb +16 -0
  223. data/lib/hecks/ports/identity_assignment.rb +23 -2
  224. data/lib/hecks/ports/identity_generation.rb +17 -3
  225. data/lib/hecks/ports/identity_resolution.rb +17 -1
  226. data/lib/hecks/ports/key_vault.port +6 -0
  227. data/lib/hecks/ports/key_vault.rb +58 -0
  228. data/lib/hecks/ports/loading.rb +4 -0
  229. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  230. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  231. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  232. data/lib/hecks/ports/persistence/execution.rb +4 -0
  233. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  234. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  235. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  236. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  237. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  238. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  239. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  250. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
  251. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  252. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
  253. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  254. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
  255. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  256. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  257. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  258. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
  259. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  260. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  261. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  262. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
  263. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
  264. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  265. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
  266. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  267. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  268. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  269. data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
  270. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  271. data/lib/hecks/ports/persistence.rb +36 -1
  272. data/lib/hecks/ports/projection.rb +61 -7
  273. data/lib/hecks/ports/query/in_memory.rb +41 -8
  274. data/lib/hecks/ports/query/ordering.rb +21 -6
  275. data/lib/hecks/ports/query.rb +35 -0
  276. data/lib/hecks/ports.rb +1 -0
  277. data/lib/hecks/projections/bootstrap_table.rb +147 -0
  278. data/lib/hecks/projections/diagrams.rb +315 -79
  279. data/lib/hecks/projections/glossary/html.rb +91 -3
  280. data/lib/hecks/projections/glossary/markdown.rb +50 -3
  281. data/lib/hecks/projections/glossary/mermaid.rb +49 -1
  282. data/lib/hecks/projections/glossary/sections.rb +1 -1
  283. data/lib/hecks/projections/glossary/sentences.rb +110 -5
  284. data/lib/hecks/projections/glossary.rb +173 -31
  285. data/lib/hecks/projections/ir.rb +1 -1
  286. data/lib/hecks/projections/model/deviations.rb +62 -17
  287. data/lib/hecks/projections/model.rb +75 -21
  288. data/lib/hecks/projections/oidc.rb +45 -8
  289. data/lib/hecks/projections/parser_table.rb +53 -12
  290. data/lib/hecks/projections/reference.rb +15 -3
  291. data/lib/hecks/projections/rust_vocabulary.rb +646 -0
  292. data/lib/hecks/projections/shape.rb +10 -2
  293. data/lib/hecks/projections/statements.rb +69 -22
  294. data/lib/hecks/projections/vocabulary.rb +26 -9
  295. data/lib/hecks/projections.rb +5 -3
  296. data/lib/hecks/projector/cli_projector.rb +245 -37
  297. data/lib/hecks/projector/docs_projector.rb +154 -28
  298. data/lib/hecks/projector/exporter.rb +104 -29
  299. data/lib/hecks/projector/ir_projector.rb +7 -1
  300. data/lib/hecks/projector/narrate_projector.rb +144 -23
  301. data/lib/hecks/projector/target.rb +42 -18
  302. data/lib/hecks/projector.rb +86 -18
  303. data/lib/hecks/query_ir.rb +94 -47
  304. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  305. data/lib/hecks/query_specification/common/comparison.rb +109 -27
  306. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  307. data/lib/hecks/query_specification/common/null_policy.rb +61 -15
  308. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  309. data/lib/hecks/query_specification/common/options.rb +25 -0
  310. data/lib/hecks/query_specification/field_path.rb +69 -15
  311. data/lib/hecks/query_specification/hop_path.rb +57 -20
  312. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  313. data/lib/hecks/rendering.rb +9 -3
  314. data/lib/hecks/router/namespace_installer.rb +16 -3
  315. data/lib/hecks/router.rb +56 -1
  316. data/lib/hecks/runtime/aggregate_lock.rb +20 -11
  317. data/lib/hecks/runtime/boot_gates.rb +21 -3
  318. data/lib/hecks/runtime/caller.rb +40 -8
  319. data/lib/hecks/runtime/capability_graph.rb +13 -2
  320. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
  321. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
  322. data/lib/hecks/runtime/command_interpreter.rb +195 -113
  323. data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
  324. data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
  325. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  326. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  327. data/lib/hecks/runtime/command_rules/references.rb +145 -55
  328. data/lib/hecks/runtime/command_rules.rb +3 -0
  329. data/lib/hecks/runtime/dependency_planning.rb +56 -11
  330. data/lib/hecks/runtime/dispatcher.rb +263 -92
  331. data/lib/hecks/runtime/entity_element.rb +304 -67
  332. data/lib/hecks/runtime/entity_interpreter.rb +149 -88
  333. data/lib/hecks/runtime/errors.rb +37 -23
  334. data/lib/hecks/runtime/event.rb +15 -10
  335. data/lib/hecks/runtime/identity.rb +91 -23
  336. data/lib/hecks/runtime/instance.rb +103 -18
  337. data/lib/hecks/runtime/interpreting.rb +24 -16
  338. data/lib/hecks/runtime/invocation.rb +358 -0
  339. data/lib/hecks/runtime/loader.rb +105 -19
  340. data/lib/hecks/runtime/outbox.rb +164 -26
  341. data/lib/hecks/runtime/policy_interpreter.rb +73 -60
  342. data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
  343. data/lib/hecks/runtime/query_interpreter.rb +93 -74
  344. data/lib/hecks/runtime/reaction_invocation.rb +73 -28
  345. data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
  346. data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
  347. data/lib/hecks/runtime/reference_hop.rb +48 -6
  348. data/lib/hecks/runtime/refusal_wording.rb +142 -115
  349. data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
  350. data/lib/hecks/runtime/registry/verification.rb +153 -28
  351. data/lib/hecks/runtime/registry.rb +202 -32
  352. data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
  353. data/lib/hecks/runtime/routing.rb +36 -154
  354. data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
  355. data/lib/hecks/runtime/saga_interpreter.rb +90 -76
  356. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  357. data/lib/hecks/runtime/tenant_check.rb +33 -13
  358. data/lib/hecks/runtime/tenant_scope.rb +23 -5
  359. data/lib/hecks/runtime/value/admission.rb +75 -30
  360. data/lib/hecks/runtime/value/coercion.rb +555 -142
  361. data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
  362. data/lib/hecks/runtime/value.rb +71 -21
  363. data/lib/hecks/runtime.rb +39 -7
  364. data/lib/hecks/storehouse.rb +368 -72
  365. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  366. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  367. data/lib/hecks/version.rb +3 -3
  368. data/lib/hecks/vocabulary.rb +205 -4
  369. data/lib/hecks.rb +91 -11
  370. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
  371. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
  372. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
  373. metadata +28 -2
@@ -6,7 +6,7 @@ module Hecks
6
6
  # The DSL reference, projected from the language's own Syntax chapter
7
7
  # — the same Keyword/Argument rows the conformance specs hold equal
8
8
  # to the live builders. Nothing here is described twice: the tables
9
- # come from the declaration, the PROSE is hand-written between
9
+ # come from the declaration, the prose is hand-written between
10
10
  # markers the generator preserves, and the golden spec refuses a
11
11
  # tree where the two have drifted.
12
12
  #
@@ -19,9 +19,9 @@ module Hecks
19
19
  GENERATED_END = "<!-- generated:end -->".freeze
20
20
  TODO_SENTINEL = "<!-- TODO: document this word -->".freeze
21
21
 
22
- # A PAGE'S OWN HAND-WRITTEN OPENING, harvested under a key no word
22
+ # A page's own hand-written opening, harvested under a key no word
23
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
24
+ # this is a Symbol). It exists so a page can boot once — load a real
25
25
  # corpus chapter, wire its hexagon — and have every word's example
26
26
  # below run against that single boot, the way a guide's opening
27
27
  # `ruby boot` block already does. Without it each word would have to
@@ -31,18 +31,36 @@ module Hecks
31
31
 
32
32
  module_function
33
33
 
34
+ # The marker opening one word's generated region.
35
+ #
36
+ # @param word [String, Symbol, #to_s] the word this section documents
37
+ # @return [String] the HTML comment marking that word's generated region open
34
38
  def generated_begin(word) = "<!-- generated:begin word=#{word} -->"
35
39
 
36
- # Keyed by REGION rather than by word — the same marker convention,
40
+ # Keyed by region rather than by word — the same marker convention,
37
41
  # used for the parts of a page that are not about one word: a
38
42
  # page's generated lede here, README's generated indexes below.
43
+ #
44
+ # @param id [String, Symbol, #to_s] the region's id, such as `"page"` or `"tools"`
45
+ # @return [String] the HTML comment marking that region's generated region open
39
46
  def region_begin(id) = "<!-- generated:begin id=#{id} -->"
40
47
 
48
+ # The language's own Syntax aggregate, read off the judged grammar chapter.
49
+ #
50
+ # @return [Bluebook::Aggregate, nil] the Syntax aggregate, or nil if the
51
+ # grammar's Bluebook chapter declares none by that name
41
52
  def syntax
42
53
  meta = Bluebook::MetaValidator.grammar_registry.bluebook("Bluebook")
43
54
  meta.aggregates.find { |aggregate| aggregate.hecks_name == "Syntax" }
44
55
  end
45
56
 
57
+ # Reads one closed-set value object's declared members off the Syntax
58
+ # aggregate, as string-valued Hashes.
59
+ #
60
+ # @param name [String, Symbol, #to_s] the value object's `hecks_name`,
61
+ # such as `"Keyword"`
62
+ # @return [Array<Hash{Symbol => String}>] each declared member's fields,
63
+ # values stringified
46
64
  def rows(name)
47
65
  syntax.value_objects.find { |vo| vo.hecks_name == name }
48
66
  .members.map { |row| row.to_h.transform_values(&:to_s) }
@@ -53,31 +71,60 @@ module Hecks
53
71
  # really is a lifecycle. `SyntaxBoot.call` discovers the static
54
72
  # aggregate-local seed rows (`KeywordSeed`/`ArgumentSeed`), dispatches each
55
73
  # 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.
74
+ # same shape `rows`, above, already produces — nothing below this
75
+ # needed to change.
76
+ #
77
+ # This module holds no separate `@keywords ||=` memo of its own —
78
+ # delegates straight through to `SyntaxBoot.call`'s own cache
79
+ # (keyed on the grammar registry's own chapter set — see its
80
+ # comment) rather than shadowing it. A second, local cache on top
81
+ # of that one would double-cache with no way to invalidate either
82
+ # half: whichever call happened to land first would lock in
83
+ # forever, even one caught mid-build missing every Paging-attached
84
+ # word (limit/offset/cursor/nulls).
85
+ #
86
+ # @return [Array<Hash{Symbol => String}>] every declared Keyword row
68
87
  def keywords = Bluebook::MetaValidator::SyntaxBoot.call[:keywords]
88
+
89
+ # Every declared Argument row.
90
+ #
91
+ # @return [Array<Hash{Symbol => String}>] every declared Argument row
69
92
  def arguments = Bluebook::MetaValidator::SyntaxBoot.call[:arguments]
70
93
 
94
+ # Reads a row's declared status, defaulting when it declared none.
95
+ #
96
+ # @param row [Hash{Symbol => String}] a Keyword or Argument row
97
+ # @return [String] the row's declared status, defaulting to `"admitted"`
98
+ # when it declared none
71
99
  def status_of(row) = row[:status].to_s.empty? ? "admitted" : row[:status].to_s
100
+
101
+ # Whether a row is still current enough to appear in the reference.
102
+ #
103
+ # @param row [Hash{Symbol => String}] a Keyword or Argument row
104
+ # @return [Boolean] true if the row's status is `"admitted"` or `"deprecated"`
72
105
  def live?(row) = %w[admitted deprecated].include?(status_of(row))
73
106
 
107
+ # Every distinct context a keyword is declared in.
108
+ #
109
+ # @return [Array<String>] every distinct context a keyword is declared in
74
110
  def contexts = keywords.map { |row| row[:context] }.uniq
75
111
 
112
+ # Derives a context's reference page filename.
113
+ #
114
+ # @param context [String, Symbol, #to_s] a context name, such as `"File"`
115
+ # @return [String] the reference page's filename for that context
76
116
  def page_name(context) = "#{Naming.snake(context)}.md"
77
117
 
78
118
  # Every reference page, rendered fresh — prose carried over from
79
119
  # the committed pages, new words seeded with the sentinel, orphaned
80
120
  # prose refused.
121
+ #
122
+ # @param directory [String] path to the directory holding the committed
123
+ # reference pages, read for their hand-written prose
124
+ # @return [Hash{String => String}] every page's filename (plus `"index.md"`)
125
+ # mapped to its freshly rendered Markdown content
126
+ # @raise [RuntimeError] if a committed page carries prose for a word the
127
+ # language no longer declares in that context
81
128
  def pages(directory)
82
129
  contexts.each_with_object({}) do |context, pages|
83
130
  path = File.join(directory, page_name(context))
@@ -86,13 +133,22 @@ module Hecks
86
133
  end.merge("index.md" => render_index)
87
134
  end
88
135
 
89
- # A WORD ADMITTING TWO FORMS HAS TWO ROWS — syntax.bluebook's own
136
+ # A word admitting two forms has two rows — syntax.bluebook's own
90
137
  # 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
138
+ # none) is the case that made it real again. One section per word all
92
139
  # the same: the prose is the word's rather than the form's, and the
93
140
  # argument rows join by (word, context) and so already cover every
94
141
  # form. Grouped rather than rendered per row, or a reader would meet
95
142
  # the same heading and the same paragraph twice.
143
+ #
144
+ # @param context [String] the context name, such as `"File"`
145
+ # @param prose [Hash{String, Symbol => String}] hand-written prose harvested
146
+ # from the committed page, keyed by word (or `PREAMBLE` for the page's lede)
147
+ # @param path [String] the page's file path, used only in the orphan-refusal
148
+ # message below
149
+ # @return [String] the page's full rendered Markdown
150
+ # @raise [RuntimeError] if `prose` carries a key for a word the language no
151
+ # longer declares in `context`
96
152
  def render_page(context, prose, path)
97
153
  words = keywords.select { |row| row[:context] == context }.group_by { |row| row[:word] }
98
154
  orphans = prose.keys - words.keys - [PREAMBLE]
@@ -119,6 +175,10 @@ module Hecks
119
175
  PAGE
120
176
  end
121
177
 
178
+ # The one-line description of where a context's words are typed.
179
+ #
180
+ # @param context [String] the context name, such as `"File"` or `"Command"`
181
+ # @return [String] a sentence naming where words in this context are typed
122
182
  def context_lede(context)
123
183
  openers = keywords.select { |row| row[:opens] == context }
124
184
  return "Words available at the top of a file." if context == "File"
@@ -128,9 +188,15 @@ module Hecks
128
188
  inside.empty? ? "Words available in the #{context} body." : "Words available inside #{inside}."
129
189
  end
130
190
 
131
- # One SPELLING per form, everything else off the first row — the
191
+ # One spelling per form, everything else off the first row — the
132
192
  # columns that differ between two forms of one word are `body` (which
133
193
  # is what the spelling shows) and nothing else.
194
+ #
195
+ # @param forms [Array<Hash{Symbol => String}>] one word's Keyword rows, one
196
+ # per admitted form
197
+ # @param prose [String, nil] the word's hand-written prose, or nil if none
198
+ # was harvested
199
+ # @return [String] the word's rendered section, generated table plus prose
134
200
  def render_word(forms, prose)
135
201
  row = forms.first
136
202
  table = argument_table(row)
@@ -152,15 +218,30 @@ module Hecks
152
218
  WORD
153
219
  end
154
220
 
221
+ # Falls back to the TODO sentinel when a word has no prose yet.
222
+ #
223
+ # @param prose [String, nil] a word's hand-written prose, or nil if none
224
+ # was harvested
225
+ # @return [String] `prose` stripped, or the TODO sentinel if it is blank
155
226
  def prose_or_sentinel(prose)
156
227
  text = prose.to_s.strip
157
228
  text.empty? ? TODO_SENTINEL : text
158
229
  end
159
230
 
231
+ # Finds the Argument rows declared for one Keyword row's word.
232
+ #
233
+ # @param row [Hash{Symbol => String}] a Keyword row
234
+ # @return [Array<Hash{Symbol => String}>] the Argument rows declared for
235
+ # this row's word, in this row's context
160
236
  def word_arguments(row)
161
237
  arguments.select { |arg| arg[:keyword] == row[:word] && arg[:context] == row[:context] }
162
238
  end
163
239
 
240
+ # Builds the call spelling shown for one word.
241
+ #
242
+ # @param row [Hash{Symbol => String}] a Keyword row
243
+ # @return [String] the word's call spelling, positional arguments then
244
+ # named ones, with a trailing `do ... end` unless its body is `"none"`
164
245
  def signature(row)
165
246
  positional = word_arguments(row).reject { |arg| arg[:at].to_s.empty? }
166
247
  .sort_by { |arg| arg[:at].to_i }
@@ -172,6 +253,11 @@ module Hecks
172
253
  row[:body].to_s == "none" ? base : "#{base} do ... end"
173
254
  end
174
255
 
256
+ # Renders one word's arguments as a Markdown table.
257
+ #
258
+ # @param row [Hash{Symbol => String}] a Keyword row
259
+ # @return [String] a Markdown table of the row's arguments, or `""` if it
260
+ # declares none
175
261
  def argument_table(row)
176
262
  args = word_arguments(row)
177
263
  return "" if args.empty?
@@ -184,6 +270,10 @@ module Hecks
184
270
  "#{lines.join("\n")}\n"
185
271
  end
186
272
 
273
+ # Renders the reference index page.
274
+ #
275
+ # @return [String] the reference index page's rendered Markdown, one
276
+ # linked entry per context
187
277
  def render_index
188
278
  listed = contexts.map do |context|
189
279
  count = keywords.select { |row| row[:context] == context }.map { |row| row[:word] }.uniq.size
@@ -204,7 +294,7 @@ module Hecks
204
294
  # Prose keyed by word: everything between a section's generated
205
295
  # region and the next `## ` heading (or end of file).
206
296
  #
207
- # Starts on PREAMBLE rather than nil so the text between the PAGE's
297
+ # Starts on `PREAMBLE` rather than nil so the text between the page's
208
298
  # own generated lede and its first word heading is carried over too
209
299
  # instead of being silently dropped. A page written before that
210
300
  # region existed has no generated marker ahead of its first `## `,
@@ -212,8 +302,13 @@ module Hecks
212
302
  # preamble is invented — the older shape reads back unchanged.
213
303
  # A single-pass line-scanning state machine (current/collecting/
214
304
  # buffer/in_fence) — each branch mutates shared local state that
215
- # carries into the NEXT iteration, so splitting per branch would
305
+ # carries into the next iteration, so splitting per branch would
216
306
  # mean passing all four back and forth by reference every line.
307
+ #
308
+ # @param text [String] a committed reference page's full Markdown source
309
+ # @return [Hash{String, Symbol => String}] hand-written prose, keyed by
310
+ # word (or `PREAMBLE` for the page's lede); empty and TODO-sentinel-only
311
+ # entries are dropped
217
312
  # rubocop:disable-next Metrics/PerceivedComplexity
218
313
  def harvest(text)
219
314
  prose = {}
@@ -221,7 +316,7 @@ module Hecks
221
316
  collecting = false
222
317
  buffer = []
223
318
 
224
- # A HEADING INSIDE A FENCE IS NOT A HEADING. `## something` is an
319
+ # A heading inside a fence is not a heading. `## something` is an
225
320
  # ordinary Ruby comment, and now that every word's section carries
226
321
  # runnable code, one written at the left margin would otherwise
227
322
  # end that section mid-example and orphan the rest of it under a
@@ -246,6 +341,13 @@ module Hecks
246
341
  prose.reject { |_word, text_| text_.empty? || text_ == TODO_SENTINEL }
247
342
  end
248
343
 
344
+ # Renders every reference page and writes each to `directory`.
345
+ #
346
+ # @param directory [String] path to the directory to write pages into,
347
+ # created if it does not exist
348
+ # @return [void]
349
+ # @raise [RuntimeError] if a committed page carries prose for a word the
350
+ # language no longer declares in that context
249
351
  def write!(directory)
250
352
  FileUtils.mkdir_p(directory)
251
353
  pages(directory).each do |name, content|
@@ -257,6 +359,10 @@ module Hecks
257
359
  # reference page, keyed by region id instead of a word, so the
258
360
  # index a reader lands on first can't drift from what actually
259
361
  # exists on disk either.
362
+ #
363
+ # @param root [String] the repository root
364
+ # @return [Hash{String => String}] each region id mapped to its freshly
365
+ # rendered content
260
366
  def readme_regions(root)
261
367
  {
262
368
  "guides" => guide_index(root),
@@ -267,6 +373,11 @@ module Hecks
267
373
  }
268
374
  end
269
375
 
376
+ # Lists every committed guide, linked and titled by its own heading.
377
+ #
378
+ # @param root [String] the repository root
379
+ # @return [String] a Markdown list linking every committed guide, titled
380
+ # by its own `# ` heading
270
381
  def guide_index(root)
271
382
  paths = Dir.glob(File.join(root, "docs/implemented/guides/*.md"))
272
383
  .reject { |p| %w[AUTHORING.md].include?(File.basename(p)) }
@@ -278,6 +389,11 @@ module Hecks
278
389
  lines.join("\n")
279
390
  end
280
391
 
392
+ # Links the reference index, with its context count.
393
+ #
394
+ # @param _root [String] unused; kept for the shape `readme_regions` calls
395
+ # every region renderer with
396
+ # @return [String] one line linking the reference index, with its context count
281
397
  def reference_index(_root)
282
398
  count = contexts.size
283
399
  "[The DSL reference](docs/implemented/reference/index.md) — #{count} contexts, generated from " \
@@ -285,6 +401,11 @@ module Hecks
285
401
  "`spec/reference_golden_spec.rb`."
286
402
  end
287
403
 
404
+ # Lists every `bin/` script that opens with a comment, one row each.
405
+ #
406
+ # @param root [String] the repository root
407
+ # @return [String] a Markdown table of every `bin/` script with an opening
408
+ # comment, one row each
288
409
  def tool_table(root)
289
410
  scripts = Dir.glob(File.join(root, "bin/*")).select { |p| File.file?(p) }.sort
290
411
  rows = scripts.filter_map { |path| [path, tool_summary(path)] }.select { |_, desc| desc }
@@ -293,11 +414,15 @@ module Hecks
293
414
  lines.join("\n")
294
415
  end
295
416
 
296
- # The opening comment PARAGRAPH, not just the first line — a table
417
+ # The opening comment paragraph, not just the first line — a table
297
418
  # cell that trails off mid-clause reads worse than one that runs a
298
419
  # little long and says "...". A code-bearing comment (`field.name`,
299
420
  # `pattern:`) makes naive sentence-splitting on "." or ":" cut in
300
- # the wrong place, so this truncates on LENGTH alone.
421
+ # the wrong place, so this truncates on length alone.
422
+ #
423
+ # @param path [String] a `bin/` script's path
424
+ # @return [String, nil] its opening comment paragraph, truncated to 140
425
+ # characters, or nil if the script opens with no comment
301
426
  def tool_summary(path)
302
427
  comment_lines = []
303
428
  started = false
@@ -317,13 +442,17 @@ module Hecks
317
442
  text.length > 140 ? "#{text[0, 137]}..." : text
318
443
  end
319
444
 
320
- # ONE REAL, COMMITTED FILE, READ FRESH — not re-derived from a boot
445
+ # One real, committed file, read fresh — not re-derived from a boot
321
446
  # (this module never requires `hecks/projections/diagrams`, and
322
447
  # shouldn't just to draw one example). `docs/generated/diagrams/`
323
448
  # is already held to the declaration by `spec/diagrams_spec.rb`'s
324
449
  # own drift check; this just quotes its own output, so the two
325
450
  # can't independently drift from each other either — a stale
326
- # Order_lifecycle.mmd fails THAT spec long before this one runs.
451
+ # Order_lifecycle.mmd fails that spec long before this one runs.
452
+ #
453
+ # @param root [String] the repository root
454
+ # @return [String] a Markdown section showcasing the generated diagram
455
+ # tooling, quoting `examples/pizzas`'s own committed Order lifecycle diagram
327
456
  def diagram_showcase(root)
328
457
  lifecycle = File.read(File.join(root, "docs/generated/diagrams/pizzas/Order_lifecycle.mmd")).strip
329
458
  <<~MARKDOWN.strip
@@ -337,6 +466,11 @@ module Hecks
337
466
  MARKDOWN
338
467
  end
339
468
 
469
+ # Lists every example domain, with its own declared vision.
470
+ #
471
+ # @param root [String] the repository root
472
+ # @return [String] a Markdown list of every example domain with a
473
+ # `.bluebook` file, each with its own declared `vision` text
340
474
  def corpus_roster(root)
341
475
  dirs = Dir.glob(File.join(root, "examples/*/"))
342
476
  lines = dirs.filter_map do |dir|
@@ -351,6 +485,12 @@ module Hecks
351
485
  lines.join("\n")
352
486
  end
353
487
 
488
+ # Replaces every generated region inside `text` with its freshly rendered
489
+ # content, leaving the hand-written parts of README untouched.
490
+ #
491
+ # @param root [String] the repository root
492
+ # @param text [String] the README's current full text
493
+ # @return [String] the README's text, with each generated region refreshed
354
494
  def render_readme(root, text)
355
495
  readme_regions(root).reduce(text) do |current, (id, content)|
356
496
  pattern = /#{Regexp.escape(region_begin(id))}.*?#{Regexp.escape(GENERATED_END)}/m
@@ -358,12 +498,16 @@ module Hecks
358
498
  end
359
499
  end
360
500
 
501
+ # Regenerates README's generated regions in place.
502
+ #
503
+ # @param root [String] the repository root
504
+ # @return [void]
361
505
  def write_readme!(root)
362
506
  path = File.join(root, "README.md")
363
507
  File.write(path, render_readme(root, File.read(path)))
364
508
  end
365
509
 
366
- # An example A READER CAN SEE and the harness will actually run.
510
+ # An example a reader can see and the harness will actually run.
367
511
  # `ruby skip` is display-only by the doctest harness's own rule, and
368
512
  # a hidden `<!-- doctest:boot -->` block is setup rather than an
369
513
  # example — a word whose only "example" is invisible or inert is a
@@ -371,9 +515,14 @@ module Hecks
371
515
  # gate exists to refuse.
372
516
  EXAMPLE_FENCE = /^```ruby(?: bluebook| boot)?[ \t]*$/
373
517
 
518
+ # Whether a word's prose carries a runnable example.
519
+ #
520
+ # @param prose [String, nil] a word's hand-written prose, or nil
521
+ # @return [Boolean] true if `prose` contains a runnable `ruby` or
522
+ # `ruby bluebook`/`ruby boot` fenced example
374
523
  def exemplified?(prose) = prose.to_s.match?(EXAMPLE_FENCE)
375
524
 
376
- # EVERY LIVE WORD, PAIRED WITH ITS PROSE. Both coverage gates ask a
525
+ # Every live word, paired with its prose. Both coverage gates ask a
377
526
  # question about this same walk and differ only in what they ask of
378
527
  # the prose, so they share it rather than each re-deriving the page
379
528
  # set — the two are meant to move together, and one drifting past
@@ -382,6 +531,12 @@ module Hecks
382
531
  #
383
532
  # `harvest` already rejects empty prose and the TODO sentinel, so a
384
533
  # word with nothing written for it arrives here with a nil.
534
+ #
535
+ # @param directory [String] path to the directory holding the committed
536
+ # reference pages
537
+ # @return [Array(String, String, String), Array(String, String, nil)]
538
+ # `[word, context, prose]` for every live (admitted or deprecated) word,
539
+ # `prose` nil if none was harvested
385
540
  def live_words(directory)
386
541
  rows = contexts.flat_map do |context|
387
542
  path = File.join(directory, page_name(context))
@@ -392,21 +547,35 @@ module Hecks
392
547
  rows.uniq { |word, context, _| [word, context] }
393
548
  end
394
549
 
550
+ # Disambiguates a word by the context it is declared in.
551
+ #
552
+ # @param word [String] a word
553
+ # @param context [String] the context it is declared in
554
+ # @return [String] the word, disambiguated by its context
395
555
  def name_of(word, context) = "#{word} (#{context})"
396
556
 
397
- # The coverage gate's question: every LIVE word with no prose yet.
557
+ # The coverage gate's question: every live word with no prose yet.
558
+ #
559
+ # @param directory [String] path to the directory holding the committed
560
+ # reference pages
561
+ # @return [Array<String>] each undocumented live word, named by `name_of`
398
562
  def undocumented(directory)
399
563
  live_words(directory).reject { |_word, _context, prose| prose }
400
564
  .map { |word, context, _| name_of(word, context) }
401
565
  end
402
566
 
403
- # The SECOND coverage gate: prose is a declaration, and a
567
+ # The second coverage gate: prose is a declaration, and a
404
568
  # declaration nothing runs cannot disagree with anything. A word
405
569
  # documented only in sentences can go stale — or describe a word
406
570
  # the runtime never wired at all, which this repository has already
407
571
  # shipped twice (`read_model`'s where/order_by/limit/offset, and
408
- # `role`/`goal` on a command). An example that RUNS is the only
572
+ # `role`/`goal` on a command). An example that runs is the only
409
573
  # documentation that can go red.
574
+ #
575
+ # @param directory [String] path to the directory holding the committed
576
+ # reference pages
577
+ # @return [Array<String>] each live word with prose but no runnable
578
+ # example, named by `name_of`
410
579
  def unexemplified(directory)
411
580
  live_words(directory).reject { |_word, _context, prose| exemplified?(prose) }
412
581
  .map { |word, context, _| name_of(word, context) }
@@ -1,12 +1,17 @@
1
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/).
2
+ # A vendored, external bluebook.
8
3
  #
9
- # RECOVERED, NOT REBUILT — this module and its `uses_embryonaut_bluebook`
4
+ # ## What it is
5
+ #
6
+ # Same shape as Framework (framework.rb), for members that don't ship
7
+ # inside hecks's own lib/ at all: a separate, independently-versioned
8
+ # package (github.com/chrisyoung/embryonaut_bluebooks) that a consuming
9
+ # project vendors into its own checkout, the same way a project already
10
+ # vendors hecks itself (bin/vendor_hecks, vendor/hecks/).
11
+ #
12
+ # ## Recovered, not rebuilt
13
+ #
14
+ # This module and its `uses_embryonaut_bluebook`
10
15
  # DSL word (hecksagon_builder.rb) were built on a prior commit of this
11
16
  # repo (933d1dd), vendored out to a real consumer (lifeadelics/domain,
12
17
  # for embryonaut_bluebooks/payments), and then lost from this repo's own
@@ -17,19 +22,23 @@ module Hecks
17
22
  # conventions rather than copied wholesale, since the two trees had
18
23
  # otherwise diverged for weeks in both directions.
19
24
  #
20
- # RESOLVED FROM THE CONSUMING REGISTRY'S OWN ROOT, not this gem's
25
+ # ## Resolution
26
+ #
27
+ # Resolved from the consuming registry's own root, not this gem's
21
28
  # __dir__ — Framework::ROOT can be a fixed, `__dir__`-relative constant
22
29
  # because framework members ship inside this gem; an embryonaut bluebook
23
- # ships inside the CONSUMER's own checkout instead, at
30
+ # ships inside the consumer's own checkout instead, at
24
31
  # `<registry.root>/vendor/embryonaut_bluebooks/<name>/bluebook/`. There
25
32
  # is no fixed answer until a registry (and its root) actually exists, so
26
33
  # this resolves lazily, per call — the same reason `uses_framework`
27
34
  # itself only runs at hecksagon-build time, when a real registry is
28
35
  # current.
29
36
  #
30
- # EVERY `.bluebook` FILE IN THE PACKAGE, SORTED — not just one. Unlike a
37
+ # ## Load order
38
+ #
39
+ # Every `.bluebook` file in the package, sorted — not just one. Unlike a
31
40
  # framework member (one file, named by its own stem), a vendored package
32
- # can span several bluebook files that reopen the SAME `Hecks.bluebook`
41
+ # can span several bluebook files that reopen the same `Hecks.bluebook`
33
42
  # (embryonaut_bluebooks/payments/bluebook/{payment,payments,policies}
34
43
  # .bluebook all reopen "Payments"). Load order matters — policies
35
44
  # .bluebook names `Payment::Succeed` and needs the aggregate already
@@ -37,21 +46,35 @@ module Hecks
37
46
  # payment < payments < policies, the same reason that package's own
38
47
  # files are named to fall in that order in the first place.
39
48
  #
40
- # ONLY THE BLUEBOOK FILES — same restriction Framework draws, same
41
- # reason: a `.hecksagon`/`.port`/`.adapter` is a WIRING decision
49
+ # ## Scope
50
+ #
51
+ # Only the bluebook files — same restriction Framework draws, same
52
+ # reason: a `.hecksagon`/`.port`/`.adapter` is a wiring decision
42
53
  # (persistence, which processor adapter is bound) that belongs to
43
54
  # whoever is deploying, never baked into the vendored package itself.
44
55
  # embryonaut_bluebooks/payments ships its own mock `.hecksagon` for its
45
- # own spec suite; a consumer declares its OWN separate
56
+ # own spec suite; a consumer declares its own separate
46
57
  # `Hecks.hecksagon "Payments" do ... end` to bind real storage/adapters
47
58
  # — see Framework's own comment for the fuller reasoning, identical here.
48
59
  #
49
- # IDEMPOTENT THE SAME WAY Framework.load! IS — checked against the
60
+ # ## Idempotency
61
+ #
62
+ # Idempotent the same way `Framework.load!` is — checked against the
50
63
  # bluebook this package actually declares (`Naming.pascal("payments")`
51
64
  # => "Payments"), not a separate ledger. A vendored package's directory
52
65
  # name and its declared `Hecks.bluebook` name are the one convention
53
66
  # this reuses from Framework rather than reinventing.
54
67
  module EmbryonautBluebook
68
+ # Loads a vendored embryonaut bluebook package's `.bluebook` files, once
69
+ # per registry.
70
+ #
71
+ # @param name [String, Symbol] the vendored package's directory name, such
72
+ # as `"payments"`
73
+ # @param registry [Runtime::Registry, nil] the registry to vendor into and
74
+ # check for an existing load; defaults to the current boot registry
75
+ # @return [void]
76
+ # @raise [Runtime::WiringError] if `registry` has no root, or no vendored
77
+ # package named `name` is checked out
55
78
  def self.load!(name, registry: Hecks.current_registry)
56
79
  unless registry&.root
57
80
  raise Runtime::WiringError,