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
@@ -1,32 +1,19 @@
1
1
  module Hecks
2
2
  module Projections
3
3
  module Model
4
- # HOW THE MODEL'S SHAPE DIFFERS FROM THE LANGUAGE'S, and why.
4
+ # How the model's shape differs from the language's, and why.
5
5
  #
6
6
  # Every construct's `emits_ir` restates what the grammar declares —
7
7
  # and the two legitimately differ, in seven ways. These lived only
8
8
  # as prose in Ruby comments until spec/model_shape_conformance_spec
9
- # made them checkable; they live HERE so the generator and the gate
9
+ # made them checkable; they live here so the generator and the gate
10
10
  # read one source rather than two that must agree.
11
11
  #
12
12
  # A reason is carried beside each entry because the generated file
13
- # will CARRY it: a deviation is exactly the kind of thing whose
13
+ # will carry it: a deviation is exactly the kind of thing whose
14
14
  # explanation must survive regeneration, and the only way it can is
15
15
  # to be emitted rather than typed into the output.
16
16
  module Deviations
17
- # The grammar is relational — a Command points UP at its
18
- # Aggregate — where the model composes. An explicit `as:` still
19
- # keeps its `_id` (Command's own `entity_id`, kept as data); the
20
- # parent link itself mints bare now (ADR 0025) — `aggregate` or
21
- # `bluebook`, whichever this category's creating command declares
22
- # first — so both spellings are checked. Entity spells its own
23
- # (separate, non-colliding) text twin of the parent link `owner`.
24
- PARENT_REF = ->(field) { field.to_s.match?(/_id\z/) || %i[owner aggregate bluebook].include?(field) }
25
-
26
- # The judge's own field, never the model's — contracts.rb already
27
- # says so with `derived: { position: :walk }`.
28
- JUDGE_ONLY = %i[position].freeze
29
-
30
17
  # What the model holds that the grammar declares elsewhere: the
31
18
  # containment edges, stated in syntax.bluebook's Keyword rows as
32
19
  # `context` -> `opens`.
@@ -64,7 +51,7 @@ module Hecks
64
51
  "Policy" => { where_ast: "the structured form of `where`, derived from the same text at emission" }
65
52
  }.freeze
66
53
 
67
- # DECLARED, AND DELIBERATELY NOT EMITTED. The category that had no
54
+ # **Declared, and deliberately not emitted**. The category that had no
68
55
  # home anywhere before — each of these was a comment and nothing
69
56
  # more.
70
57
  OFF_THE_WIRE = {
@@ -85,13 +72,71 @@ module Hecks
85
72
 
86
73
  module_function
87
74
 
75
+ # The grammar is relational — a Command points up at its
76
+ # Aggregate — where the model composes. An explicit `as:` still
77
+ # keeps its `_id` (Command's own `entity_id`, kept as data); the
78
+ # parent link itself mints bare now (ADR 0025) — `aggregate` or
79
+ # `bluebook`, whichever this category's creating command declares
80
+ # first. Entity spells its own (separate, non-colliding) text twin
81
+ # of the parent link `owner`. Not restated here: the one list is
82
+ # `Assembly::PARENT_POINTERS`, which the assembly gate reads too.
83
+ # Tells whether `field` is a parent pointer, minted bare rather than
84
+ # declared like an ordinary field.
85
+ #
86
+ # @param field [String, Symbol] the field name to check
87
+ # @return [Boolean] true if `field` is a parent pointer (`Assembly::PARENT_POINTERS`,
88
+ # or any name ending `_id`)
89
+ def parent_ref?(field) = Hecks::Bluebook::Assembly.parent_pointer?(field)
90
+
91
+ # The judge's own fields, never the model's — read off the
92
+ # category's contract (`derived: { position: :walk }`), not restated.
93
+ #
94
+ # @param name [String] the construct's name, an `Assembly::CONTRACTS` key
95
+ # @return [Array<Symbol>] fields the assembly judge derives by walking, for this construct
96
+ # @raise [KeyError] if `name` has no assembly contract
97
+ def judge_only(name) = Hecks::Bluebook::Assembly.contract(name).walked
98
+
88
99
  # The tables that carry a reason answer with names only when the
89
100
  # caller wants the set rather than the explanations.
101
+ #
102
+ # @param name [String] the construct's name, an `OFF_THE_WIRE` key
103
+ # @return [Array<Symbol>] fields declared but deliberately not emitted for `name`;
104
+ # empty if `name` has none
90
105
  def off_the_wire(name) = OFF_THE_WIRE.fetch(name, {}).keys
106
+
107
+ # Names `name`'s model-only fields.
108
+ #
109
+ # @param name [String] the construct's name, a `COMPUTED` key
110
+ # @return [Array<Symbol>] model-only fields computed rather than declared for
111
+ # `name`; empty if `name` has none
91
112
  def computed(name) = COMPUTED.fetch(name, {}).keys
113
+
114
+ # Names `name`'s containment edges.
115
+ #
116
+ # @param name [String] the construct's name, a `CONTAINED` key
117
+ # @return [Array<Symbol>] fields the model holds that the grammar declares as
118
+ # containment edges instead, for `name`; empty if `name` has none
92
119
  def contained(name) = CONTAINED.fetch(name, [])
120
+
121
+ # Names `name`'s folded fields and what each gathers.
122
+ #
123
+ # @param name [String] the construct's name, a `FOLDED` key
124
+ # @return [Hash{Symbol => Array<Symbol>}] each folded field for `name`, mapped to
125
+ # the declared fields it gathers; empty if `name` has none
93
126
  def folded(name) = FOLDED.fetch(name, {})
127
+
128
+ # Names `name`'s unpacked fields and what each opens into.
129
+ #
130
+ # @param name [String] the construct's name, an `UNPACKED` key
131
+ # @return [Hash{Symbol => Array<Symbol>}] each declared field for `name` that opens
132
+ # into several model fields, mapped to those fields; empty if `name` has none
94
133
  def unpacked(name) = UNPACKED.fetch(name, {})
134
+
135
+ # Names `name`'s dynamic-tail fields.
136
+ #
137
+ # @param name [String] the construct's name, a `DYNAMIC_TAIL` key
138
+ # @return [Array<Symbol>] fields for `name` emitted by `to_h`'s own merge rather
139
+ # than by `emits_ir`; empty if `name` has none
95
140
  def dynamic_tail(name) = DYNAMIC_TAIL.fetch(name, [])
96
141
  end
97
142
  end
@@ -3,22 +3,22 @@ require_relative "../projector"
3
3
 
4
4
  module Hecks
5
5
  module Projections
6
- # THE MODEL CLASSES, PROJECTED FROM THE LANGUAGE THAT DECLARES THEM.
6
+ # The model classes, projected from the language that declares them.
7
7
  #
8
- # A construct's HOLDING half — its readers, its emission, a
8
+ # A construct's holding half — its readers, its emission, a
9
9
  # constructor that assigns declared fields and hands off — restates
10
10
  # what `bluebook.bluebook` already says, three times over in Ruby.
11
11
  # This renders it instead.
12
12
  #
13
- # ONLY THE HOLDING HALF. `Behaviour::X` is hand-written and permanent,
13
+ # **Only the holding half**. `Behaviour::X` is hand-written and permanent,
14
14
  # and `settle` is the seam: everything a declaration cannot state
15
15
  # lives behind it, so regenerating can never be lossy. That property
16
16
  # was established construct by construct before any of this was
17
17
  # written — the chapter looked generatable and was not, until its
18
18
  # `@hecks_root`, ports table and child stamping moved behind `settle`.
19
19
  #
20
- # THE HOST MANIFEST IS THE HONEST PART. The grammar states the fields;
21
- # it does not state which constructs are Ruby CLASSES rather than
20
+ # The `HOST` manifest is the honest part. The grammar states the fields;
21
+ # it does not state which constructs are Ruby classes rather than
22
22
  # instances, what a constructor's defaults are, or how a value is
23
23
  # coerced on the way in. Those are facts about Ruby, not about
24
24
  # bluebooks, so they are declared here rather than pretended into the
@@ -29,7 +29,7 @@ module Hecks
29
29
 
30
30
  projects_as :model, declares: "Bluebook", emits: :files
31
31
 
32
- # PER-CONSTRUCT RUBY FACTS. `coerce` is the only fiddly column: a
32
+ # **Per-construct Ruby facts**. `coerce` is the only fiddly column: a
33
33
  # declared field arrives as whatever the builder handed over, and
34
34
  # each construct has always normalised its own on the way in.
35
35
  HOST = {
@@ -37,21 +37,25 @@ module Hecks
37
37
  construct: "Policy",
38
38
  file: "policy.rb",
39
39
  behaviour: "Behaviour::Policy",
40
- readers: %i[name on_event trigger_command target_domain where for_each with_spec],
40
+ readers: %i[name on_event trigger_command target_domain expect_undelivered where for_each with_spec],
41
41
  accessors: %i[aggregate],
42
42
  defaults: { name: nil, on_event: "nil", trigger_command: "nil",
43
- target_domain: "nil", where: "nil", for_each: "nil",
43
+ target_domain: "nil", expect_undelivered: "false", where: "nil", for_each: "nil",
44
44
  with_spec: "[]", aggregate: "nil" },
45
- coerce: { name: ".to_s", aggregate: "&.to_s" },
46
- # A LIST OF BINDINGS IS NOT A SCALAR ON THE WIRE. Every other
45
+ # A flag on the wire is a boolean — the language holds it as
46
+ # text ("true"), the builder hands over `true`, and a
47
+ # reconstruction hands back whichever it read; all three land
48
+ # as the same `true`/`false`.
49
+ coerce: { name: ".to_s", aggregate: "&.to_s", expect_undelivered: ".to_s == \"true\"" },
50
+ # A list of bindings is not a scalar on the wire. Every other
47
51
  # field emits as itself; this one has to render the way
48
52
  # `DispatchSpec`'s own `with_spec` does — keys to strings, and
49
- # `render_value` KEEPING the leading colon on a Symbol, because
53
+ # `render_value` keeping the leading colon on a Symbol, because
50
54
  # a binding that reads an event field and one that supplies a
51
55
  # literal string are otherwise indistinguishable once written
52
56
  # down (see `MetaValidator::Readings`' own note on exactly that).
53
57
  renders: { with_spec: "-> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } }",
54
- # COMPUTED (Deviations::COMPUTED["Policy"]): the structured
58
+ # Computed (Deviations::COMPUTED["Policy"]): the structured
55
59
  # form of `where`, a pure function of that text, the same
56
60
  # `ast` every rule row carries beside its `canonical`.
57
61
  where_ast: "-> { where_ast }" },
@@ -61,14 +65,30 @@ module Hecks
61
65
 
62
66
  module_function
63
67
 
68
+ # Renders every `HOST`-listed construct's holding half.
69
+ #
70
+ # @param bluebook [Bluebook::Chapter] the "Bluebook" chapter itself (the language
71
+ # describing its own constructs), not a domain being projected
72
+ # @param options [Hash{Symbol => Object}] ignored; present to satisfy the
73
+ # `Projector::Target` calling convention
74
+ # @return [Hash{String => String}] each construct's output filename mapped to its
75
+ # rendered Ruby source
64
76
  def call(bluebook:, options: {})
65
77
  HOST.to_h { |name, host| [host.fetch(:file), render(bluebook, name, host)] }
66
78
  end
67
79
 
80
+ # Renders one construct's class: emission, readers and constructor, wrapped in
81
+ # the generated-file header and namespace.
82
+ #
83
+ # @param bluebook [Bluebook::Chapter] the "Bluebook" chapter the construct's own
84
+ # attributes are read from
85
+ # @param name [String] the construct's name, a key into `HOST` (`"Policy"`, ...)
86
+ # @param host [Hash{Symbol => Object}] `name`'s `HOST` entry
87
+ # @return [String] the rendered class source, ready to write to `host[:file]`
68
88
  def render(bluebook, name, host)
69
89
  <<~RUBY
70
- # GENERATED — projected from the language's own #{name} aggregate.
71
- # DO NOT EDIT: the holding half is rendered, and #{host.fetch(:behaviour)}
90
+ # Generated — projected from the language's own #{name} aggregate.
91
+ # Do not edit: the holding half is rendered, and #{host.fetch(:behaviour)}
72
92
  # is where anything hand-written belongs.
73
93
  require_relative "behaviour/#{File.basename(host.fetch(:file), '.rb')}"
74
94
 
@@ -91,9 +111,14 @@ module Hecks
91
111
 
92
112
  # The emission, keyed as the model spells it and sourced as the
93
113
  # language declares it.
94
- # A COMPUTED field (Deviations::COMPUTED) is emitted too — it is
114
+ # A computed field (Deviations::COMPUTED) is emitted too — it is
95
115
  # model-only by definition, so it rides after the declared fields
96
- # and MUST have a `renders` entry, there being nothing to `send`.
116
+ # and must have a `renders` entry, there being nothing to `send`.
117
+ #
118
+ # @param bluebook [Bluebook::Chapter] the "Bluebook" chapter the construct's own
119
+ # attributes are read from
120
+ # @param name [String] the construct's name, a key into `HOST`
121
+ # @return [String] the rendered `emits_ir(...)` call, one field per line
97
122
  def emits(bluebook, name)
98
123
  fields = emitted_fields(bluebook, name) + Deviations.computed(name)
99
124
  renders = HOST.fetch(name).fetch(:renders, {})
@@ -102,37 +127,54 @@ module Hecks
102
127
  "emits_ir(\n#{fields.map { |f| " #{"#{f}:".ljust(width + 1)} #{renders.fetch(f, ":#{f}")}" }.join(",\n")}\n)"
103
128
  end
104
129
 
105
- # WHAT THE CONSTRUCT EMITS: what the language declares, less every
130
+ # What the construct emits: what the language declares, less every
106
131
  # deviation the tables account for. The generator and
107
132
  # spec/model_shape_conformance_spec compute this the same way, from
108
133
  # the same tables, which is the point of the tables being in lib.
134
+ #
135
+ # @param bluebook [Bluebook::Chapter] the "Bluebook" chapter the construct's own
136
+ # attributes are read from
137
+ # @param name [String] the construct's name, an aggregate on `bluebook`
138
+ # @return [Array<Symbol>] the declared attribute names, minus every field
139
+ # `Deviations` marks as parent-ref, judge-only, off-the-wire, dynamic-tail,
140
+ # folded, or unpacked
109
141
  def emitted_fields(bluebook, name)
110
142
  bluebook.aggregate(name).attributes.map(&:name)
111
- .reject { |f| Deviations::PARENT_REF.call(f) } -
112
- Deviations::JUDGE_ONLY -
143
+ .reject { |f| Deviations.parent_ref?(f) } -
144
+ Deviations.judge_only(name) -
113
145
  Deviations.off_the_wire(name) -
114
146
  Deviations.dynamic_tail(name) -
115
147
  Deviations.folded(name).values.flatten -
116
148
  Deviations.unpacked(name).keys
117
149
  end
118
150
 
151
+ # Renders the `attr_reader` line for every emitted field, plus an `attr_accessor`
152
+ # for each field the model deliberately keeps off the wire.
153
+ #
154
+ # @param host [Hash{Symbol => Object}] the construct's `HOST` entry
155
+ # @return [String] the rendered reader/accessor lines, one construct's worth
119
156
  def readers(host)
120
157
  lines = ["attr_reader #{host.fetch(:readers).map { |r| ":#{r}" }.join(', ')}"]
121
158
  accessors = host.fetch(:accessors, [])
122
159
  return lines.join("\n") if accessors.empty?
123
160
 
124
161
  # A declared field the model deliberately does not emit still
125
- # needs a reader, and the REASON it is off the wire is carried
162
+ # needs a reader, and the reason it is off the wire is carried
126
163
  # here rather than typed in — a comment that survives
127
164
  # regeneration is one the generator writes.
128
165
  reasons = Deviations::OFF_THE_WIRE.fetch(host.fetch(:construct, ""), {})
129
166
  (lines + accessors.map do |a|
130
167
  why = reasons[a]
131
- (why ? "\n# #{a.upcase}, DECLARED AND DELIBERATELY OFF THE WIRE\n# #{wrap(why)}\n" : "") +
168
+ (why ? "\n# #{a.to_s.capitalize}, declared and deliberately off the wire\n# #{wrap(why)}\n" : "") +
132
169
  "attr_accessor :#{a}"
133
170
  end).join("\n")
134
171
  end
135
172
 
173
+ # Renders the `initialize` that assigns every declared field, coerced as `HOST`
174
+ # states, and calls `settle` unless the construct opts out.
175
+ #
176
+ # @param host [Hash{Symbol => Object}] the construct's `HOST` entry
177
+ # @return [String] the rendered `def initialize ... end` block
136
178
  def constructor(host)
137
179
  args = host.fetch(:defaults).map { |f, d| d ? "#{f}: #{d}" : "#{f}:" }.join(", ")
138
180
  body = host.fetch(:defaults).keys.map do |f|
@@ -143,7 +185,19 @@ module Hecks
143
185
  "def initialize(#{args})\n#{body.join("\n")}\nend"
144
186
  end
145
187
 
188
+ # Indents every non-blank line of a rendered block, for nesting it inside the
189
+ # class body.
190
+ #
191
+ # @param text [String] the block to indent
192
+ # @param by [Integer] how many spaces to prefix each non-blank line with
193
+ # @return [String] the indented text
146
194
  def indent(text, by) = text.lines.map { |l| l.strip.empty? ? l : (" " * by) + l }.join
195
+
196
+ # Wraps prose into `# `-prefixed comment lines, for a reader-declared reason
197
+ # rendered back into the generated file.
198
+ #
199
+ # @param text [String] the prose to wrap
200
+ # @return [String] the wrapped text, its lines joined by `"\n# "`
147
201
  def wrap(text) = text.scan(/.{1,62}(?:\s|$)/).map(&:strip).join("\n# ")
148
202
  end
149
203
  end
@@ -3,16 +3,17 @@ require_relative "../projector"
3
3
 
4
4
  module Hecks
5
5
  module Projections
6
- # AN OIDC CLIENT/SCOPE MANIFEST derived from a domain's own IR: what
6
+ # An OIDC client/scope manifest derived from a domain's own IR: what
7
7
  # an identity provider has to know about this domain before it can
8
8
  # issue a token that means anything here.
9
9
  #
10
- # THE ARTIFACT HALF OF SOMETHING ALREADY HALF-BUILT.
11
- # `spec/oidc_projection_spec.rb` covers the INTEGRATION half — verified
10
+ # ## The artifact half of something already half-built
11
+ #
12
+ # `spec/oidc_projection_spec.rb` covers the integration half — verified
12
13
  # claims in, `IdentityResolution.resolve` → `Authorization.holds_role?`
13
14
  # → a dispatch scoped by `Hecks.as_caller(role:)`. That half enforces
14
15
  # a role per command. It just had no way to say, up front and as data,
15
- # WHICH role each command wants — every answer came from asking the
16
+ # which role each command wants — every answer came from asking the
16
17
  # live runtime one dispatch at a time.
17
18
  #
18
19
  # This is that catalogue, and the two are checked against each other
@@ -24,12 +25,13 @@ module Hecks
24
25
  # `Ports::Authorization.holds_role?` compares against a real
25
26
  # `Governance::RoleAssignment`.
26
27
  #
27
- # ROLES COME FROM THE COMMANDS, NOT FROM GOVERNANCE. A command's own
28
- # `role "Compliance officer"` is in the bluebook IR
28
+ # ## Roles come from the commands, not from governance
29
+ #
30
+ # A command's own `role "Compliance officer"` is in the bluebook IR
29
31
  # (`Command#role`), whereas `uses_framework "Governance"` is
30
32
  # declared in the `.hecksagon` — which `call(bluebook:, options:)`
31
33
  # cannot see at all. Reading the commands is both the only thing
32
- # available here AND the more accurate source: it says what each
34
+ # available here and the more accurate source: it says what each
33
35
  # command actually demands, not merely which role vocabulary the
34
36
  # application happened to mount.
35
37
  #
@@ -47,6 +49,13 @@ module Hecks
47
49
  # `audience:` overrides the domain name, for the ordinary case
48
50
  # where the IdP's registered audience is a URL rather than a bare
49
51
  # chapter name.
52
+ #
53
+ # @param bluebook [Bluebook::Chapter] the chapter being projected
54
+ # @param options [Hash{Symbol => Object}] `:audience` (String, Symbol, nil)
55
+ # overrides the manifest's `"audience"` value
56
+ # @return [Hash{String => Object}] `"audience"` (String), `"scopes"` (see
57
+ # `scopes_for`) and `"roles"` (`Array<String>`, every distinct declared role,
58
+ # sorted)
50
59
  def call(bluebook:, options: {})
51
60
  scopes = scopes_for(bluebook)
52
61
 
@@ -67,11 +76,22 @@ module Hecks
67
76
  # through `Dispatcher#dispatch`'s dotted `Entity.Command` routing
68
77
  # is real and callable, so a manifest that never names it can
69
78
  # never grant a client a scope for it either.
79
+ #
80
+ # @param bluebook [Bluebook::Chapter] the chapter being projected
81
+ # @return [Array<Hash{String => Object}>] every command's scope entry (see
82
+ # `command_scopes`), sorted by `"scope"`
70
83
  def scopes_for(bluebook)
71
84
  bluebook.aggregates.flat_map { |aggregate| aggregate_scopes(bluebook, aggregate) }
72
85
  .sort_by { |scope| scope["scope"] }
73
86
  end
74
87
 
88
+ # Projects one aggregate's own commands and every entity nested inside it.
89
+ #
90
+ # @param bluebook [Bluebook::Chapter] the aggregate's owning chapter, for the
91
+ # verb and scope prefixes
92
+ # @param aggregate [Bluebook::Aggregate] the aggregate being projected
93
+ # @return [Array<Hash{String => Object}>] the aggregate's and its entities'
94
+ # scope entries (see `command_scopes`)
75
95
  def aggregate_scopes(bluebook, aggregate)
76
96
  verb_prefix = "#{bluebook.name}::#{aggregate.hecks_name}"
77
97
  scope_prefix = "#{Naming.snake(bluebook.name)}:#{Naming.snake(aggregate.hecks_name)}"
@@ -84,6 +104,14 @@ module Hecks
84
104
  # inside `Handler`), so this recurses the same way `Chapter#verbs`
85
105
  # now does. The verb and scope prefixes grow in lockstep, each
86
106
  # `.`-joined the same way its own kind already was.
107
+ #
108
+ # @param entity [Bluebook::Entity] the entity being projected
109
+ # @param verb_prefix [String] the enclosing aggregate or entity's own verb
110
+ # prefix, extended with this entity's name
111
+ # @param scope_prefix [String] the enclosing aggregate or entity's own scope
112
+ # prefix, extended with this entity's snake-cased name
113
+ # @return [Array<Hash{String => Object}>] this entity's and its nested entities'
114
+ # scope entries (see `command_scopes`)
87
115
  def entity_scopes(entity, verb_prefix, scope_prefix)
88
116
  verb_prefix = "#{verb_prefix}.#{entity.hecks_name}"
89
117
  scope_prefix = "#{scope_prefix}.#{Naming.snake(entity.hecks_name)}"
@@ -93,9 +121,18 @@ module Hecks
93
121
  end
94
122
 
95
123
  # `banking:account.open` — the shape an OIDC scope is conventionally
96
- # spelled in, and snake_cased through the SAME `Naming.snake` the
124
+ # spelled in, and snake_cased through the same `Naming.snake` the
97
125
  # facade uses to name a command's own door method, so a scope and
98
126
  # the Ruby call that satisfies it cannot drift apart.
127
+ #
128
+ # @param commands [Array<Bluebook::Command>] the commands to project
129
+ # @param verb_prefix [String] the owning aggregate or entity's fully-qualified
130
+ # verb prefix
131
+ # @param scope_prefix [String] the owning aggregate or entity's snake-cased
132
+ # scope prefix
133
+ # @return [Array<Hash{String => Object}>] one entry per command: `"scope"`
134
+ # (String), `"verb"` (String), `"role"` (String, `nil` if the command
135
+ # declares none)
99
136
  def command_scopes(commands, verb_prefix, scope_prefix)
100
137
  commands.map do |command|
101
138
  {
@@ -2,12 +2,12 @@ require_relative "../projector"
2
2
 
3
3
  module Hecks
4
4
  module Projections
5
- # THE RUST PARSER'S KEYWORD TABLE, projected from the chapter's own
6
- # Syntax aggregate — "the parser's grammar knowledge is DERIVED from
5
+ # The Rust parser's keyword table, projected from the chapter's own
6
+ # Syntax aggregate — "the parser's grammar knowledge is derived from
7
7
  # hecks's self-description, not hand-typed a second time", which
8
8
  # is the anti-drift idea the whole Rust-parser plan rests on.
9
9
  #
10
- # A REGISTERED TARGET NOW, rather than a module living inside its own
10
+ # A registered target now, rather than a module living inside its own
11
11
  # bin/ script. It was already a projection in everything but call
12
12
  # shape; this only stops it being a fifth way of spelling one.
13
13
  module ParserTable
@@ -16,25 +16,47 @@ module Hecks
16
16
 
17
17
  module_function
18
18
 
19
+ # Projects the Rust parser's keyword and closed-set tables for one chapter.
20
+ #
21
+ # @param bluebook [Bluebook::Chapter] the chapter to project; the language's own
22
+ # for the ordinary `bin/project_parser_table` run
23
+ # @param options [Hash{Symbol => Object}] ignored; present to satisfy the
24
+ # `Projector::Target` calling convention
25
+ # @return [String] the rendered Rust source
19
26
  def call(bluebook:, options: {}) = render(bluebook)
20
27
 
21
28
  module_function
22
29
 
23
- # THE CHAPTER IS HANDED OVER, not reached for. This used to open the
24
- # grammar registry itself, which meant the projection could only ever
25
- # project one chapter — the language's own. Taking it as an argument is
26
- # what the projector protocol asks for, and it costs nothing.
30
+ # The chapter is handed over, not reached for — reaching for the grammar
31
+ # registry directly would pin the projection to one chapter, the language's
32
+ # own, and the projector protocol already hands over the chapter as an
33
+ # argument, at no cost.
34
+ #
35
+ # @param bluebook [Bluebook::Chapter] the chapter to read the Syntax aggregate
36
+ # from
37
+ # @return [Bluebook::Aggregate, nil] the chapter's `"Syntax"` aggregate, `nil` if
38
+ # it declares none
27
39
  def syntax(bluebook) = bluebook.aggregate("Syntax")
28
40
 
29
41
  # Every cell as text — exactly spec/syntax_conformance_spec.rb's own
30
42
  # `rows` helper, reused rather than re-derived: a member's fields decode
31
43
  # back through typed literal decoding on the way out of reconstruction,
32
44
  # and this reads it back as what was written.
45
+ #
46
+ # @param bluebook [Bluebook::Chapter] the chapter to read from
47
+ # @param name [String] the closed-set value object's name, such as `"Context"`
48
+ # @return [Array<Hash{Symbol => String}>] one row per declared member, every
49
+ # field stringified
33
50
  def rows(bluebook, name)
34
51
  syntax(bluebook).value_objects.find { |vo| vo.hecks_name == name }
35
52
  .members.map { |row| row.to_h.transform_values(&:to_s) }
36
53
  end
37
54
 
55
+ # Reads one closed set's member names, for a Rust `&[&str]` constant.
56
+ #
57
+ # @param bluebook [Bluebook::Chapter] the chapter to read from
58
+ # @param name [String] the closed-set value object's name
59
+ # @return [Array<String>] every declared member's `:name` field
38
60
  def closed_set_members(bluebook, name) = rows(bluebook, name).map { |row| row[:name] }
39
61
 
40
62
  KEYWORD_FIELDS = %i[word context body inner opens fills status was resolves_via disambiguator].freeze
@@ -45,18 +67,34 @@ module Hecks
45
67
  # plain ASCII (a word, a context name, a digit, "true"/"false"), so this
46
68
  # only has to be safe against the two characters Rust string literals
47
69
  # themselves reserve.
70
+ #
71
+ # @param value [Object] the field value to render; stringified before escaping
72
+ # @return [String] a quoted Rust string literal
48
73
  def rust_string(value) = "\"#{value.to_s.gsub('\\', '\\\\\\\\').gsub('"', '\\"')}\""
49
74
 
75
+ # Renders one `KEYWORDS` table entry.
76
+ #
77
+ # @param row [Hash{Symbol => String}] one keyword row, keyed by `KEYWORD_FIELDS`
78
+ # @return [String] one `KeywordRow { ... },` line
50
79
  def keyword_row(row)
51
80
  fields = KEYWORD_FIELDS.map { |field| rust_string(row[field]) }
52
81
  " KeywordRow { #{KEYWORD_FIELDS.zip(fields).map { |name, value| "#{name}: #{value}" }.join(', ')} },"
53
82
  end
54
83
 
84
+ # Renders one `ARGUMENTS` table entry.
85
+ #
86
+ # @param row [Hash{Symbol => String}] one argument row, keyed by `ARGUMENT_FIELDS`
87
+ # @return [String] one `ArgumentRow { ... },` line
55
88
  def argument_row(row)
56
89
  fields = ARGUMENT_FIELDS.map { |field| rust_string(row[field]) }
57
90
  " ArgumentRow { #{ARGUMENT_FIELDS.zip(fields).map { |name, value| "#{name}: #{value}" }.join(', ')} },"
58
91
  end
59
92
 
93
+ # Renders one `pub static NAME: &[&str]` constant.
94
+ #
95
+ # @param name [String] the Rust constant's name
96
+ # @param values [Array<String>] the constant's member strings
97
+ # @return [String] the rendered `pub static` declaration
60
98
  def const_str_array(name, values)
61
99
  lines = values.map { |value| " #{rust_string(value)}," }
62
100
  "pub static #{name}: &[&str] = &[\n#{lines.join("\n")}\n];\n"
@@ -64,12 +102,15 @@ module Hecks
64
102
 
65
103
  # S14, ADR 0026 — Keyword/Argument are genuine entities of Syntax
66
104
  # now, dispatched (not merely declared) so their own `status`
67
- # really is a lifecycle. `SyntaxBoot.call` reads the STILL-STATIC
105
+ # really is a lifecycle. `SyntaxBoot.call` reads the still-static
68
106
  # seed rows (`KeywordSeed`/`ArgumentSeed`), dispatches each one
69
- # through the real admission/lifecycle door, and hands back the
70
- # exact same shape `rows` used to read straight off the closed set
71
- # — symbol keys, string values, `status` included — so nothing
72
- # else in this file needed to change.
107
+ # through the real admission/lifecycle door, and hands back rows in
108
+ # the same shape `rows` reads for every other closed set here —
109
+ # symbol keys, string values, `status` included — so nothing else
110
+ # in this file needs to change.
111
+ #
112
+ # @param bluebook [Bluebook::Chapter] the chapter to project
113
+ # @return [String] the rendered `KEYWORDS`/`ARGUMENTS`/closed-set Rust source
73
114
  def render(bluebook)
74
115
  table = Hecks::Bluebook::MetaValidator::SyntaxBoot.call
75
116
  keywords = table[:keywords]
@@ -3,18 +3,18 @@ require_relative "../projector"
3
3
 
4
4
  module Hecks
5
5
  module Projections
6
- # THE DSL REFERENCE PAGES, projected from the chapter's own Syntax
6
+ # The DSL reference pages, projected from the chapter's own Syntax
7
7
  # aggregate — the tables come from the declaration, the prose is
8
8
  # preserved from whatever is already committed.
9
9
  #
10
- # THE FIRST PROJECTION THAT EMITS A TREE. `emits: :files` says so,
10
+ # The first projection that emits a tree. `emits: :files` says so,
11
11
  # and the framework writes the map rather than inferring one: a Hash
12
12
  # of path => contents and a Hash that merely holds strings are the
13
13
  # same object to Ruby, so only the projection can know which it
14
14
  # meant. This is the case the output contract was designed for and
15
15
  # deliberately left unimplemented until something real needed it.
16
16
  #
17
- # NOT PURE, and it cannot be. `Doc::Reference.pages` READS the
17
+ # Not pure, and it cannot be. `Doc::Reference.pages` reads the
18
18
  # committed pages to harvest their prose, so the existing directory
19
19
  # is an input — passed as `from:` rather than assumed, so the
20
20
  # projection never reaches for a path of its own choosing.
@@ -25,6 +25,18 @@ module Hecks
25
25
 
26
26
  module_function
27
27
 
28
+ # Renders one reference page per DSL keyword context, carrying prose
29
+ # over from whatever is already committed under `options[:from]`.
30
+ #
31
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
32
+ # Syntax aggregate to render pages from; unused beyond admission, since the
33
+ # keyword table this reads comes from the global `Syntax` grammar
34
+ # @param options [Hash] must include `:from`
35
+ # @option options [String] :from directory holding the already-committed
36
+ # reference pages, read to harvest their prose
37
+ # @return [Hash{String => String}] each page's filename => its rendered
38
+ # Markdown, including `"index.md"`
39
+ # @raise [ArgumentError] if `options[:from]` is missing or falsy
28
40
  def call(bluebook:, options: {})
29
41
  from = options[:from] or
30
42
  raise ArgumentError,