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
@@ -35,16 +35,20 @@ module Hecks
35
35
  attr_accessor :last_suite
36
36
 
37
37
  # `Kernel.load`s one `.behaviors` file and returns its suite, with
38
- # NO test actually executed yet — the cheap half, split out so a
38
+ # no test actually executed yet — the cheap half, split out so a
39
39
  # caller that only needs to know what tests exist (the rspec shim,
40
40
  # naming its `it`s at collection time) doesn't pay for running them
41
41
  # until it actually wants to. `Behaviors.loading_path` is bound only
42
42
  # for the duration of the load, and `last_suite` is reset to nil
43
- # BEFORE it — a file that loads without ever calling
43
+ # before it — a file that loads without ever calling
44
44
  # `Hecks.behaviors` is unambiguously a parse error, never a stale
45
45
  # suite from whatever loaded before it in a sweep (a real bug in a
46
46
  # prior port of this idea: compared only against nil, so after the
47
47
  # first successful file in a sweep it stayed non-nil forever).
48
+ # @param path [String] the `.behaviors` file's path
49
+ # @return [Behaviors::ParseResult] `suite` holding the built `BehaviorsSuite` and
50
+ # `parse_error` nil on success; `suite` nil and `parse_error` a String describing
51
+ # a raised exception, or the file loading without calling `Hecks.behaviors`
48
52
  def parse(path)
49
53
  path = File.expand_path(path)
50
54
  previous_path = loading_path
@@ -66,6 +70,11 @@ module Hecks
66
70
  end
67
71
 
68
72
  # One `.behaviors` file → `FileResult`, every test actually run.
73
+ #
74
+ # @param path [String] the `.behaviors` file's path
75
+ # @return [Behaviors::FileResult] `parse_error` and empty `runs` on a parse
76
+ # failure; otherwise `parse_error` nil and `runs` one `Expectations::Result`
77
+ # per test
69
78
  def run(path)
70
79
  parsed = parse(path)
71
80
  return FileResult.new(path: parsed.path, parse_error: parsed.parse_error, runs: []) if parsed.parse_error
@@ -78,12 +87,22 @@ module Hecks
78
87
  # it actually found — a sweep that goes green without saying how
79
88
  # much it looked at is indistinguishable from one that found
80
89
  # nothing to look at.
90
+ #
91
+ # @param dir [String] the directory to search, recursively, for `.behaviors` files
92
+ # @return [Behaviors::SweepResult] `files_swept` the count found, `files` one
93
+ # `FileResult` per file, and `summary` the aggregate counts `summarize` returns
81
94
  def run_all(dir)
82
95
  files = Dir.glob(File.join(dir, "**", "*.behaviors"))
83
96
  results = files.map { |path| run(path) }
84
97
  SweepResult.new(root: dir, files_swept: files.size, files: results, summary: summarize(results))
85
98
  end
86
99
 
100
+ # Tallies a sweep's results into counts by outcome.
101
+ #
102
+ # @param results [Array<Behaviors::FileResult>] the swept files' results
103
+ # @return [Hash{Symbol => Integer}] `:files` the file count, `:parse_errors` files
104
+ # that failed to parse, `:total` tests actually run across every file, `:passed`,
105
+ # `:failed` and `:errored` counting each `Expectations::Result#status`
87
106
  def summarize(results)
88
107
  runs = results.flat_map(&:runs)
89
108
  {
@@ -8,11 +8,19 @@ require_relative "behaviors/runner"
8
8
  # lib/hecks/fuzzing.rb, the shape this file mirrors) is opt-in too.
9
9
  module Hecks
10
10
  class << self
11
- # `Hecks.behaviors "Name" do ... end` — deliberately NOT routed
11
+ # `Hecks.behaviors "Name" do ... end` — deliberately not routed
12
12
  # through `collect` the way `bluebook`/`hecksagon`/`world` are: a
13
13
  # behaviors suite is a test artifact a runner reads on demand, never
14
14
  # a thing a live domain boot needs, so it has no business landing in
15
15
  # `Runtime.current_registry`.
16
+ #
17
+ # @param name [String] the suite's declared name
18
+ # @yield the suite's body, evaluated against a `Behaviors::BehaviorsBuilder`
19
+ # @return [Behaviors::BehaviorsSuite] the built suite, also stashed as
20
+ # `Behaviors.last_suite`
21
+ # @raise [Behaviors::LoadOutsideRunner] if called outside `Behaviors.run(path)`
22
+ # @raise [Behaviors::Malformed] if the suite declares no `vision` or `loads`, or
23
+ # any of its `test` blocks is malformed
16
24
  def behaviors(name, &)
17
25
  path = Behaviors.loading_path or
18
26
  raise Behaviors::LoadOutsideRunner,
@@ -3,15 +3,15 @@ require_relative "expression/ast_json"
3
3
 
4
4
  module Hecks
5
5
  module Bluebook
6
- # A FIELD READ THROUGH A `reference_to`, HELD LOCALLY (S12, ADR 0025
6
+ # A field read through a `reference_to`, held locally (S12, ADR 0025
7
7
  # — "Consistency across aggregate boundaries") — `projects
8
8
  # :customer_status, from: :"customer.status"` declares that this
9
- # aggregate's own `:customer_status` is a COPY of the target's own
9
+ # aggregate's own `:customer_status` is a copy of the target's own
10
10
  # `:status`, kept fresh by a rebuild sweep rather than read live at
11
- # rule-evaluation time. `reference` names the LOCAL reference
11
+ # rule-evaluation time. `reference` names the local reference
12
12
  # attribute to walk through (`:customer`, minted by this
13
13
  # aggregate's own `reference_to Customer`); `remote_field` names
14
- # the SCALAR on the target aggregate to copy.
14
+ # the scalar on the target aggregate to copy.
15
15
  ProjectedField = Struct.new(:name, :reference, :remote_field, keyword_init: true)
16
16
 
17
17
  # A construct like every other: the aggregate carries its own identity
@@ -19,14 +19,14 @@ module Hecks
19
19
  # everything declared on it. The chain is model objects end to end —
20
20
  # reference resolution and hecks_fqn both walk it.
21
21
  #
22
- # THE HOLDING HALF, and nothing else. Every line below restates what
22
+ # The holding half, and nothing else. Every line below restates what
23
23
  # `language/bluebook/aggregate.bluebook` already declares — the field
24
24
  # list, said again as readers, again as constructor keywords, and
25
25
  # again as an emission. That triplication is what a generator removes;
26
- # `Behaviour::Aggregate` carries everything that is NOT derivable from
26
+ # `Behaviour::Aggregate` carries everything that is not derivable from
27
27
  # the declaration, so regenerating this file can never be lossy.
28
28
  #
29
- # PROTOTYPE: hand-written in the shape a generator would emit, to
29
+ # Prototype: hand-written in the shape a generator would emit, to
30
30
  # prove the seam before the generator exists. `bin/project_model`
31
31
  # would own this file; behaviour/aggregate.rb stays hand-written.
32
32
  class Aggregate
@@ -42,16 +42,16 @@ module Hecks
42
42
  value_objects: many(:value_objects),
43
43
  commands: many(:commands),
44
44
  invariants: -> { invariants.map { |rule| Expression::AstJson.rule_row(rule) } },
45
- # A PRECONDITION SHARED ACROSS COMMANDS, DECLARED ONCE (S10, ADR
46
- # 0025) — the aggregate's OWN named `given`s, the declaration a
45
+ # A precondition shared across commands, declared once (S10, ADR
46
+ # 0025) — the aggregate's own named `given`s, the declaration a
47
47
  # referencing command's own (already-resolved) `givens` entry
48
48
  # came from. Both sides of "declared once, referenced many"
49
- # are real IR, the same shape a value object's TYPE and an
49
+ # are real IR, the same shape a value object's type and an
50
50
  # attribute's own reference to it both are.
51
51
  preconditions: -> { preconditions.map { |rule| Expression::AstJson.rule_row(rule) } },
52
- # S12, ADR 0025 — deliberately NOT folded into `attributes`:
52
+ # S12, ADR 0025 — deliberately not folded into `attributes`:
53
53
  # `EraGuard::ShapeDiff` only ever walks `attributes` to decide
54
- # whether a NEW field leaves an existing record with something
54
+ # whether a new field leaves an existing record with something
55
55
  # genuinely absent, and a projected field's own absence story
56
56
  # is different — a record predating the `projects` declaration
57
57
  # is expected to be missing it until the rebuild sweep runs,
@@ -64,7 +64,7 @@ module Hecks
64
64
  lifecycle: one(:lifecycle),
65
65
  entities: many(:entities),
66
66
  queries: many(:queries),
67
- # ADDITIVE — every domain that declares no port emits `ports: []`,
67
+ # Additive — every domain that declares no port emits `ports: []`,
68
68
  # the same "regenerate deliberately" wire-format change
69
69
  # ir_golden_spec.rb's own header describes; no existing key
70
70
  # moves. See docs/decisions (rust/project/ports.rb) for the
@@ -80,6 +80,36 @@ module Hecks
80
80
  # Assigns what the language declares, then hands off to the
81
81
  # behaviour's own `settle` — derived identity, name indexes and
82
82
  # owner stamping, none of which the declaration states.
83
+ #
84
+ # @param name [String, Symbol] the aggregate's declared name
85
+ # @param description [String, nil] the aggregate's declared prose description
86
+ # @param attributes [Array<Bluebook::Attribute>] the aggregate's declared fields
87
+ # @param value_objects [Array<Class>] the value object classes (`Bluebook::ValueObject`
88
+ # subclasses) declared on this aggregate
89
+ # @param commands [Array<Class>] the command classes (`Bluebook::Command` subclasses)
90
+ # declared on this aggregate
91
+ # @param invariants [Array<Bluebook::Invariant>] the aggregate-level rules that must
92
+ # always hold
93
+ # @param preconditions [Array<Bluebook::Given>] the aggregate's own named `given`s, a
94
+ # referencing command's own resolved `givens` entry can point back at
95
+ # @param projected_fields [Array<Bluebook::ProjectedField>] the declared `projects`
96
+ # fields, copied from another aggregate's own state by the rebuild sweep
97
+ # @param identified_by [String, Symbol, Array<String, Symbol>] the identity path(s)
98
+ # this aggregate is addressed by
99
+ # @param lifecycle [Bluebook::Lifecycle, nil] the aggregate's declared state machine,
100
+ # or `nil` if it declares none
101
+ # @param entities [Array<Class>] the entity classes (`Bluebook::Entity` subclasses)
102
+ # nested directly under this aggregate
103
+ # @param queries [Array<Bluebook::Query>] the queries declared directly on this
104
+ # aggregate
105
+ # @param policies [Array<Bluebook::Policy>] the reactions hoisted onto this aggregate
106
+ # from the chapter that assembled it
107
+ # @param ports [Array<Bluebook::DomainPort>] the aggregate-scoped ports attached after
108
+ # this aggregate was built
109
+ # @param reference_targets [Array<String>] the name of every aggregate this one points
110
+ # at with an aggregate-level `reference_to`
111
+ # @param provenance [Object, nil] the aggregate's declared canonical source, captured
112
+ # exactly as written, or `nil` if it declares none
83
113
  def initialize(name:, description: nil, attributes: [], value_objects: [],
84
114
  commands: [], invariants: [], preconditions: [], projected_fields: [], identified_by: [], lifecycle: nil,
85
115
  entities: [], queries: [], policies: [], ports: [], reference_targets: [],
@@ -7,13 +7,20 @@ module Hecks
7
7
  # is the part no field table can say: which construct holds which, and how a
8
8
  # head's references become able to resolve.
9
9
  #
10
- # It DECIDES NOTHING. Whether a declaration is admissible was settled on the
10
+ # It decides nothing. Whether a declaration is admissible was settled on the
11
11
  # way in, by the language.
12
12
  class AggregateAssembly
13
+ # @param row [Hash{Symbol => Object}] one declared aggregate's raw contract data,
14
+ # as `@declaration[:aggregates]` carries it
13
15
  def initialize(row)
14
16
  @row = row
15
17
  end
16
18
 
19
+ # Builds the aggregate's whole owner-chain graph — its value objects, commands,
20
+ # entities, queries and lifecycle — from its raw declaration.
21
+ #
22
+ # @return [Bluebook::Aggregate] the built aggregate, with every reference stamped
23
+ # to resolve against it
17
24
  def aggregate
18
25
  shapes = Array(@row[:value_objects]).map { |shape| value_object(shape) }
19
26
  commands = Array(@row[:commands]).map { |verb| Build.call("Command", verb) }
@@ -29,7 +36,7 @@ module Hecks
29
36
  entities: entities,
30
37
  queries: asks,
31
38
  lifecycle: lifecycle_of(@row),
32
- # A policy declared inside a head is HOISTED onto the chapter by the
39
+ # A policy declared inside a head is hoisted onto the chapter by the
33
40
  # builder, and `Aggregate#to_h` never carried it — so the language does
34
41
  # not record which head a chapter-level policy was written in. The
35
42
  # chapter holds them all, which is what `PolicyInterpreter` reads.
@@ -43,11 +50,11 @@ module Hecks
43
50
 
44
51
  private
45
52
 
46
- # What this head points at with an aggregate-level `reference_to`. NOT the
53
+ # What this head points at with an aggregate-level `reference_to`. Not the
47
54
  # self-references its verbs carry — the first version read
48
55
  # `commands.filter_map(&:references)` and gave Pizza two targets of "Pizza",
49
56
  # its own verbs pointing at itself, where the builder recorded none. An
50
- # aggregate-level `reference_to X` leaves a reference ATTRIBUTE behind, so
57
+ # aggregate-level `reference_to X` leaves a reference attribute behind, so
51
58
  # the attributes are where the answer is.
52
59
  def reference_targets(fields)
53
60
  fields.select(&:reference?).map { |field| field.type.target_name }
@@ -57,7 +64,7 @@ module Hecks
57
64
  Build.call("ValueObject", row)
58
65
  end
59
66
 
60
- # S17, ADR 0026 — RECURSES. "That is what `entity` is for, and
67
+ # S17, ADR 0026 — recurses. "That is what `entity` is for, and
61
68
  # `entity` is declared by the language and used zero times in it"
62
69
  # — Dispatch, inside Handler, is the first use, so `row[:entities]`
63
70
  # is built the same way `row` itself was reached: through this
@@ -75,7 +82,7 @@ module Hecks
75
82
 
76
83
  # Every reference learns which head declares it, so it can reach the chapter
77
84
  # and resolve. Deliberately across every list that can carry one — a
78
- # reference the walk misses resolves to nil, and a nil target is SKIPPED
85
+ # reference the walk misses resolves to nil, and a nil target is skipped
79
86
  # rather than refused, so the guarantee would go quiet instead of red.
80
87
  def stamp_references(aggregate)
81
88
  lists = [aggregate.attributes, *aggregate.commands.map(&:attributes), *aggregate.queries.map(&:attributes)]
@@ -84,7 +91,7 @@ module Hecks
84
91
  lists.flatten.select(&:reference?).each { |field| field.type.declared_in = aggregate }
85
92
  end
86
93
 
87
- # S17, ADR 0026 — walks NESTED entities too (Dispatch, inside
94
+ # S17, ADR 0026 — walks nested entities too (Dispatch, inside
88
95
  # Handler), not only an aggregate's own direct ones.
89
96
  def entity_reference_lists(entities, lists)
90
97
  entities.each do |piece|
@@ -95,7 +102,7 @@ module Hecks
95
102
  end
96
103
  end
97
104
 
98
- # THREE LANGUAGE FIELDS, ONE IR OBJECT. `state_field`, `state_start` and
105
+ # Three language fields, one IR object. `state_field`, `state_start` and
99
106
  # `transitions` are separate declarations; the IR keeps one Lifecycle. The
100
107
  # contract names them derived, and this is what derives them.
101
108
  def lifecycle_of(row)
@@ -110,8 +117,8 @@ module Hecks
110
117
  end
111
118
 
112
119
  # A lifecycle holds [command, StateTransition] pairs and `to_h` expands one
113
- # pair whose `from` is a list into several rows. Grouped back by command AND
114
- # TARGET, because one declared pair has exactly one target and may have many
120
+ # pair whose `from` is a list into several rows. Grouped back by command and
121
+ # target, because one declared pair has exactly one target and may have many
115
122
  # froms — grouping by command alone would fuse two declarations that move the
116
123
  # same verb to different states.
117
124
  def transitions(rows)
@@ -1,12 +1,12 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  class Assembly
4
- # ONE WAY TO BUILD A CONSTRUCT, for every construct.
4
+ # One way to build a construct, for every construct.
5
5
  #
6
- # There used to be a method per category here — `value_object(row)`,
7
- # `command(row)`, `policy(row)` — each one gathering the same keywords the
8
- # contract already names. This reads the contract instead, so adding a field
9
- # to the language and forgetting to assemble it is caught by the coverage gate
6
+ # Not a method per category — `value_object(row)`, `command(row)`,
7
+ # `policy(row)`, each gathering the same keywords the contract already
8
+ # names. This reads the contract instead, so adding a field to the
9
+ # language and forgetting to assemble it is caught by the coverage gate
10
10
  # rather than by nobody.
11
11
  #
12
12
  # `make` is the only branch, and it is a real one: a construct that became a
@@ -16,6 +16,16 @@ module Hecks
16
16
  module Build
17
17
  module_function
18
18
 
19
+ # Builds one construct from its declared row, reading which keywords it takes
20
+ # and how to read each one off `Assembly.contract(category)`.
21
+ #
22
+ # @param category [String] the contract's category name, such as `"Aggregate"`
23
+ # or `"Command"`
24
+ # @param row [Hash{Symbol => Object}] the construct's own declared row
25
+ # @param extra [Hash{Symbol => Object}] keywords the caller supplies directly,
26
+ # such as already-built children the contract itself cannot derive
27
+ # @return [Object] the built construct: an instance for a category whose
28
+ # `make` is `:new`, or a class for one whose `make` is `:declare`
19
29
  def call(category, row, extra = {})
20
30
  contract = Assembly.contract(category)
21
31
  keywords = contract.fields.to_h { |keyword, (key, reader)| [keyword, read(reader, row[key])] }
@@ -23,12 +33,23 @@ module Hecks
23
33
  holder(contract).public_send(contract.make, **keywords, **extra)
24
34
  end
25
35
 
36
+ # Resolves the class or module a contract's construct is built through.
37
+ #
38
+ # @param contract [Bluebook::Assembly::Contract] the category's field contract
39
+ # @return [Module] the holder that answers `contract.make` (`.new` or `.declare`)
40
+ # @raise [ArgumentError] if the contract names no holder to build through
26
41
  def holder(contract)
27
42
  contract.holder or raise ArgumentError, "#{contract} holds nothing that can be built"
28
43
  end
29
44
 
30
45
  # A reader is a Marks method, a list of them, or one of three spellings that
31
46
  # need no decoding at all.
47
+ #
48
+ # @param reader [Symbol, Array, nil] the contract field's reader: `:plain`,
49
+ # `:identity`, `:flag`, `[:each, marks_method]`, `[:option, name]`, or a bare
50
+ # `Marks` method name
51
+ # @param value [Object] the raw declared value to read
52
+ # @return [Object] the value read through `reader`
32
53
  def read(reader, value)
33
54
  case reader
34
55
  when :plain then value
@@ -5,14 +5,14 @@ module Hecks
5
5
  # struct format itself (this file) plus, reopened in contracts.rb, the
6
6
  # `CONTRACTS` table of one `Contract` per construct category.
7
7
  class Assembly
8
- # WHAT A CONSTRUCT NEEDS THAT THE LANGUAGE CANNOT SAY, and how a claim that a
9
- # field needs no assembling is CHECKED.
8
+ # What a construct needs that the language cannot say, and how a claim that a
9
+ # field needs no assembling is checked.
10
10
  #
11
- # `derived:` used to be a list of names, and a list of names is a promise with
12
- # nobody holding it. The coverage gate only asked whether a field was accounted
13
- # for — so writing `derived: %i[version]` would have satisfied it while dropping
14
- # a chapter's version in silence, which is the exact shape of every defect this
15
- # arc has found. Naming a field derived is a CLAIM, and a claim needs a kind:
11
+ # Naming a field derived is a claim, and a claim needs a kind, not just a name
12
+ # in a list. A bare list of names is a promise with nobody holding it: the
13
+ # coverage gate only asks whether a field is accounted for, so `derived:
14
+ # %i[version]` would satisfy it while dropping a chapter's version in
15
+ # silence — the exact shape of every defect this arc has found.
16
16
  #
17
17
  # :parent the containment tree supplies it — a `*_id`, or one of
18
18
  # the named pointers below. Checked against the name.
@@ -25,38 +25,70 @@ module Hecks
25
25
  # :elsewhere not a fact about this construct at all. Allow-listed one
26
26
  # by one, because it is the kind with no other check.
27
27
  #
28
- # Every one of those can FAIL. That is the whole difference.
28
+ # Every one of those can fail. That is the whole difference.
29
29
  Contract = Struct.new(:holder, :make, :fields, :derived, :rows, :reads, keyword_init: true) do
30
- # How a declaration key is read back off a ROW. Absent means the default —
30
+ # How a declaration key is read back off a row. Absent means the default —
31
31
  # `text(row[key])`, a single cell — which is most of them ; present names the
32
32
  # shape, because a list needs a reader per element and a folded field is
33
33
  # gathered rather than fetched. Same pattern as `rows`, in the other
34
34
  # direction: declare the exceptions, default the rest.
35
+ #
36
+ # @param key [String, Symbol] the field's declaration key, as `fields` names it
37
+ # @return [Symbol, Array, nil] the reader named in `reads` for `key`, or `nil`
38
+ # when the field reads through the default single-cell reader
35
39
  def reader(key) = Hash(reads)[key.to_sym]
36
40
 
37
- # How an appendable LIST becomes rows the walk can offer. A list absent from
41
+ # How an appendable list becomes rows the walk can offer. A list absent from
38
42
  # here reads straight off the node ; one that is present names the shaper,
39
43
  # because the IR keeps a shape the language does not — a transition whose
40
44
  # `from` is a list is several rows, an append binds several fields at once,
41
45
  # an open map is one row per entry.
46
+ #
47
+ # @param list [String, Symbol] the appendable list's field name
48
+ # @return [Symbol, nil] the shaper method named in `rows` for `list`, or `nil`
49
+ # when the list reads straight off the node instead
42
50
  def shaper(list) = Hash(rows)[list.to_sym]
43
51
 
52
+ # Whether `field` is one this contract accounts for, stored or derived.
53
+ #
54
+ # @param field [Symbol] the field name to check
55
+ # @return [Boolean] whether `field` is a key of `fields` or of `derived`
44
56
  def declares?(field) = fields.key?(field) || derived.key?(field)
45
57
 
58
+ # Names the kind of derived claim `field` makes, if any.
59
+ #
60
+ # @param field [Symbol] the derived field name to check
61
+ # @return [Symbol, Array, nil] the kind `derived` names for `field` — `:parent`,
62
+ # `:children`, `:elsewhere`, `:walk`, an `[:computed, method]` or `[:folded,
63
+ # object, member]` pair — or `nil` when `field` is not derived
46
64
  def kind_of(field) = derived[field]
47
65
 
48
- # WHERE A FOLDED FIELD ACTUALLY LIVES, as [object, member].
66
+ # The fields the walk supplies — every `derived: { field => :walk }` claim.
67
+ # The language declares them (`attribute :position, Position`) so the
68
+ # judge can order siblings, but no constructor takes one. This is the one
69
+ # place that fact is stated ; `Specializer` and `Model::Deviations` read it
70
+ # here rather than each keeping their own `%i[position]`.
71
+ #
72
+ # @return [Array<Symbol>] every field name whose derived kind is `:walk`
73
+ def walked = derived.select { |_field, kind| kind == :walk }.keys
74
+
75
+ # Where a folded field actually lives, as [object, member].
49
76
  #
50
77
  # `[:folded, :lifecycle, :field]` says the language's `state_field` is the
51
- # `field` of the IR's one Lifecycle. That is the same fact `Readings` used to
52
- # state a second time as `node.lifecycle&.field` — so saying it once here
53
- # drives BOTH directions: the walk reads the member on the way in, and the
54
- # reconstruction gathers the members back into the object on the way out.
78
+ # `field` of the IR's one Lifecycle. `Readings` would otherwise have to state
79
+ # that fact a second time as `node.lifecycle&.field` — saying it once here
80
+ # drives both directions instead: the walk reads the member on the way in, and
81
+ # the reconstruction gathers the members back into the object on the way out.
55
82
  #
56
83
  # A nil member means the fold has no single member to name — `rows` is a
57
84
  # count of what `closed_set` and `members` hold between them, and `options`
58
85
  # spreads across eight keys. Those keep their own code, and the gate still
59
86
  # checks the object they name is real.
87
+ #
88
+ # @param field [Symbol] the derived field name to check
89
+ # @return [Array(Symbol, Symbol), Array(Array<Symbol>, nil), nil] the `[object,
90
+ # member]` pair the field folds into — `member` is `nil` when the fold has
91
+ # no single member to name — or `nil` when `field` does not fold at all
60
92
  def folded(field)
61
93
  kind = derived[field]
62
94
  return nil unless kind.is_a?(Array) && kind.first == :folded
@@ -64,16 +96,20 @@ module Hecks
64
96
  [kind[1], kind[2]]
65
97
  end
66
98
 
67
- # COMPUTED means worked out, not merely answerable.
99
+ # Computed means worked out, not merely answerable.
68
100
  #
69
101
  # Asking only whether the holder responds was too weak, and measurably so:
70
102
  # `[:computed, :version]` passed, because `Bluebook` does answer to
71
103
  # `version` — it just answers with what the constructor was handed. A field
72
- # the constructor TAKES is stored, and calling it computed is how a chapter's
104
+ # the constructor takes is stored, and calling it computed is how a chapter's
73
105
  # version would have gone missing while the gate said yes.
74
106
  #
75
- # So a computed field is one the holder answers AND the constructor does not
107
+ # So a computed field is one the holder answers and the constructor does not
76
108
  # accept. `query_name` qualifies (`Naming.snake(name)`) ; `version` cannot.
109
+ #
110
+ # @param method [Symbol] the field name to check
111
+ # @return [Boolean] whether `holder` answers `method` but its constructor does
112
+ # not accept it as a keyword
77
113
  def computes?(method)
78
114
  return false unless holder
79
115
  return false unless answers?(method)
@@ -81,10 +117,22 @@ module Hecks
81
117
  !accepts?(method)
82
118
  end
83
119
 
120
+ # Whether `holder` answers `method`, checked the way that fits how it is
121
+ # built: `respond_to?` for a `:declare` holder (a class, queried live) and
122
+ # `method_defined?` for a `:new` holder (an instance method, checked without
123
+ # building one).
124
+ #
125
+ # @param method [Symbol] the method name to check
126
+ # @return [Boolean] whether `holder` answers `method`
84
127
  def answers?(method)
85
128
  make == :declare ? holder.respond_to?(method) : holder.method_defined?(method)
86
129
  end
87
130
 
131
+ # Whether the holder's constructor takes `keyword` as a keyword argument.
132
+ #
133
+ # @param keyword [Symbol] the keyword to check
134
+ # @return [Boolean] whether the holder's constructor (`.declare` or
135
+ # `#initialize`) accepts `keyword` as an optional or required keyword
88
136
  def accepts?(keyword)
89
137
  builder = make == :declare ? holder.method(:declare) : holder.instance_method(:initialize)
90
138
 
@@ -92,22 +140,49 @@ module Hecks
92
140
  end
93
141
  end
94
142
 
95
- # The fields that hold a parent's id without being spelled `*_id`. An entity is
96
- # declared in an aggregate, a member on a value object, a dispatch inside a
97
- # handler, and each names its parent with a word rather than a suffix.
98
- PARENT_POINTERS = %i[owner shape handler].freeze
143
+ # The fields that point at a parent without being spelled `*_id` — the one list,
144
+ # read by the assembly gate (a `:parent` claim), the model generator and
145
+ # QueryIR (a declared field the model composes instead of storing).
146
+ #
147
+ # aggregate, bluebook the bare parent link a creating command mints
148
+ # (ADR 0025) — exactly the `parent_key`s `Plan` reads
149
+ # off the language
150
+ # owner Entity's own text twin of that link, which is why
151
+ # Entity's contract claims `owner: :parent`
152
+ #
153
+ # `shape` and `handler` are not on this list — S17 (ADR 0026) made Member and
154
+ # Dispatch nested entities, so neither is a parent field any more the way
155
+ # `owner` is. `spec/assembly_spec` derives this set from `Plan` and the
156
+ # contracts directly and fails on any drift, rather than trusting this list to
157
+ # stay in sync with a second hand-written copy kept by the model generator.
158
+ PARENT_POINTERS = %i[aggregate bluebook owner].freeze
159
+
160
+ # Whether `field` is a parent pointer that does not end in `_id`.
161
+ #
162
+ # @param field [String, Symbol] the field name to check
163
+ # @return [Boolean] whether `field` ends with `_id`, or is one of
164
+ # `PARENT_POINTERS`
165
+ def self.parent_pointer?(field)
166
+ field.to_s.end_with?("_id") || PARENT_POINTERS.include?(field.to_sym)
167
+ end
99
168
 
100
169
  # The only fields allowed to claim they describe something other than the
101
170
  # construct they hang off, each with the reason spelled out.
102
171
  #
103
172
  # normalisations the canonical-form table belongs to the expression grammar.
104
173
  # A chapter's rules are canonicalised on the way in, so the
105
- # language models the table — but no chapter STORES one, and
174
+ # language models the table — but no chapter stores one, and
106
175
  # `Bluebook#to_h` splices it in from Expression.
107
176
  ELSEWHERE = {
108
177
  Bluebook: %i[normalisations]
109
178
  }.freeze
110
179
 
180
+ # Whether `field` is allow-listed as describing something other than
181
+ # `category`'s own construct — see `ELSEWHERE`.
182
+ #
183
+ # @param category [String, Symbol] the construct category name
184
+ # @param field [Symbol] the field name to check
185
+ # @return [Boolean] whether `field` is on `category`'s `ELSEWHERE` entry
111
186
  def self.elsewhere?(category, field)
112
187
  Array(ELSEWHERE[category.to_sym]).include?(field)
113
188
  end