hecks 1.2.0 → 1.4.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 (358) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +7 -7
  3. data/lib/hecks/adapters/driven/d1.rb +187 -23
  4. data/lib/hecks/adapters/driven/folder.rb +10 -10
  5. data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
  6. data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
  7. data/lib/hecks/adapters/driven/heki/journal.rb +60 -2
  8. data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
  9. data/lib/hecks/adapters/driven/heki.rb +13 -7
  10. data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
  11. data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
  12. data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
  13. data/lib/hecks/adapters/driven/lambda.rb +39 -33
  14. data/lib/hecks/adapters/driven/local_storage.rb +17 -10
  15. data/lib/hecks/adapters/driven/memory.rb +205 -9
  16. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
  17. data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
  18. data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
  19. data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
  20. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
  21. data/lib/hecks/adapters/driven/postgres.rb +175 -28
  22. data/lib/hecks/adapters/driven/postgres_era.adapter +5 -0
  23. data/lib/hecks/adapters/driven/prism.rb +4 -4
  24. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  25. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  26. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  27. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  28. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  29. data/lib/hecks/adapters/driven.rb +4 -4
  30. data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
  31. data/lib/hecks/behaviors/dsl.rb +2 -2
  32. data/lib/hecks/behaviors/expectations.rb +51 -23
  33. data/lib/hecks/behaviors/ir.rb +1 -1
  34. data/lib/hecks/behaviors/rspec.rb +1 -1
  35. data/lib/hecks/behaviors/runner.rb +2 -2
  36. data/lib/hecks/behaviors.rb +1 -1
  37. data/lib/hecks/bluebook/aggregate.rb +13 -13
  38. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
  39. data/lib/hecks/bluebook/assembly/build.rb +1 -1
  40. data/lib/hecks/bluebook/assembly/contract.rb +39 -16
  41. data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
  42. data/lib/hecks/bluebook/assembly/marks.rb +18 -18
  43. data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
  44. data/lib/hecks/bluebook/assembly.rb +9 -9
  45. data/lib/hecks/bluebook/attribute.rb +9 -9
  46. data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
  47. data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
  48. data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
  49. data/lib/hecks/bluebook/behaviour/command.rb +23 -23
  50. data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -3
  51. data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
  52. data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
  53. data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
  54. data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
  55. data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
  56. data/lib/hecks/bluebook/behaviour/query.rb +1 -1
  57. data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
  58. data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
  59. data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
  60. data/lib/hecks/bluebook/capabilities.rb +27 -0
  61. data/lib/hecks/bluebook/chapter.rb +28 -9
  62. data/lib/hecks/bluebook/command.rb +12 -12
  63. data/lib/hecks/bluebook/domain_port.rb +9 -9
  64. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  65. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  66. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
  67. data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
  68. data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
  69. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
  70. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
  71. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  72. data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
  73. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  74. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  75. data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
  76. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  77. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
  78. data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
  79. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  80. data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
  81. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  82. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  83. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
  84. data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
  85. data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
  86. data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
  87. data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
  88. data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
  89. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  90. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  91. data/lib/hecks/bluebook/entity.rb +11 -11
  92. data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
  93. data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
  94. data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
  95. data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
  96. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
  97. data/lib/hecks/bluebook/expression/resolver.rb +60 -62
  98. data/lib/hecks/bluebook/hexagon.rb +1 -1
  99. data/lib/hecks/bluebook/lifecycle.rb +1 -1
  100. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
  101. data/lib/hecks/bluebook/meta_validator/judge.rb +122 -86
  102. data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
  103. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
  104. data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
  105. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
  106. data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
  107. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
  108. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
  109. data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
  110. data/lib/hecks/bluebook/meta_validator.rb +70 -70
  111. data/lib/hecks/bluebook/model_check.rb +301 -84
  112. data/lib/hecks/bluebook/pattern_subset.rb +9 -9
  113. data/lib/hecks/bluebook/policy.rb +15 -13
  114. data/lib/hecks/bluebook/process_manager.rb +14 -14
  115. data/lib/hecks/bluebook/project_register.rb +6 -6
  116. data/lib/hecks/bluebook/query.rb +4 -4
  117. data/lib/hecks/bluebook/read_model.rb +14 -14
  118. data/lib/hecks/bluebook/reference.rb +8 -8
  119. data/lib/hecks/bluebook/smoke_test.rb +19 -19
  120. data/lib/hecks/bluebook/synthesizer.rb +12 -12
  121. data/lib/hecks/bluebook/translation.rb +4 -4
  122. data/lib/hecks/bluebook/value_object.rb +6 -6
  123. data/lib/hecks/bluebook.rb +2 -2
  124. data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
  125. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
  126. data/lib/hecks/codemod.rb +36 -35
  127. data/lib/hecks/construct.rb +6 -6
  128. data/lib/hecks/corpus.rb +317 -0
  129. data/lib/hecks/deprecation.rb +95 -0
  130. data/lib/hecks/doc/reference.rb +19 -19
  131. data/lib/hecks/embryonaut_bluebook.rb +11 -11
  132. data/lib/hecks/facade/cli_door.rb +69 -10
  133. data/lib/hecks/facade/cli_runner.rb +105 -24
  134. data/lib/hecks/facade/command_request.rb +23 -0
  135. data/lib/hecks/facade/handle.rb +79 -32
  136. data/lib/hecks/facade/json_door.rb +106 -25
  137. data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
  138. data/lib/hecks/facade/surface/chapter.rb +26 -17
  139. data/lib/hecks/facade/surface.rb +16 -3
  140. data/lib/hecks/facade.rb +15 -4
  141. data/lib/hecks/forms/app.rb +46 -30
  142. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  143. data/lib/hecks/forms/field_renderer.rb +142 -6
  144. data/lib/hecks/forms/field_shape.rb +183 -20
  145. data/lib/hecks/forms/html.rb +51 -7
  146. data/lib/hecks/forms/index_renderer.rb +14 -2
  147. data/lib/hecks/forms/params.rb +120 -23
  148. data/lib/hecks/forms/port_argument.rb +2 -2
  149. data/lib/hecks/forms/query_form_renderer.rb +2 -2
  150. data/lib/hecks/forms/record_renderer.rb +2 -2
  151. data/lib/hecks/forms/record_table.rb +1 -1
  152. data/lib/hecks/forms/value_object_shape.rb +3 -3
  153. data/lib/hecks/forms.rb +24 -4
  154. data/lib/hecks/fqn.rb +1 -1
  155. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  156. data/lib/hecks/framework.rb +48 -17
  157. data/lib/hecks/freezer.rb +11 -11
  158. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
  159. data/lib/hecks/fuzzing/combination_miner.rb +119 -0
  160. data/lib/hecks/fuzzing/concurrent_dispatch.rb +361 -0
  161. data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
  162. data/lib/hecks/fuzzing/differential.rb +158 -0
  163. data/lib/hecks/fuzzing/domain_generator.rb +694 -0
  164. data/lib/hecks/fuzzing/era_boundary.rb +124 -0
  165. data/lib/hecks/fuzzing/form_census.rb +199 -0
  166. data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
  167. data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
  168. data/lib/hecks/fuzzing/isolated_boot.rb +226 -38
  169. data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
  170. data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
  171. data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
  172. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
  173. data/lib/hecks/fuzzing/properties/guards.rb +129 -26
  174. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
  175. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
  176. data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
  177. data/lib/hecks/fuzzing/properties/querying.rb +17 -17
  178. data/lib/hecks/fuzzing/properties.rb +84 -39
  179. data/lib/hecks/fuzzing/qa_settings.rb +152 -0
  180. data/lib/hecks/fuzzing/replay.rb +224 -114
  181. data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
  182. data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
  183. data/lib/hecks/fuzzing/self_consistency.rb +676 -0
  184. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
  189. data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
  190. data/lib/hecks/fuzzing/shrinker.rb +197 -0
  191. data/lib/hecks/fuzzing/structural_skips.rb +39 -0
  192. data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
  193. data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
  194. data/lib/hecks/fuzzing/value_generator.rb +66 -14
  195. data/lib/hecks/fuzzing.rb +11 -0
  196. data/lib/hecks/grammar/evolve.rb +10 -10
  197. data/lib/hecks/grammar.rb +7 -7
  198. data/lib/hecks/ir.rb +13 -13
  199. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  200. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  201. data/lib/hecks/language/bluebook/vocabulary.bluebook +365 -15
  202. data/lib/hecks/language/oidc.json +5 -0
  203. data/lib/hecks/literal.rb +9 -9
  204. data/lib/hecks/naming.rb +89 -21
  205. data/lib/hecks/ports/access_control.rb +58 -2
  206. data/lib/hecks/ports/agent/answers.rb +83 -6
  207. data/lib/hecks/ports/agent.rb +119 -35
  208. data/lib/hecks/ports/authentication.rb +44 -4
  209. data/lib/hecks/ports/authorization.rb +53 -11
  210. data/lib/hecks/ports/clock.rb +42 -23
  211. data/lib/hecks/ports/extraction.rb +16 -0
  212. data/lib/hecks/ports/identity_assignment.rb +24 -2
  213. data/lib/hecks/ports/identity_generation.rb +17 -3
  214. data/lib/hecks/ports/identity_resolution.rb +18 -1
  215. data/lib/hecks/ports/loading.rb +4 -0
  216. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  217. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  218. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  219. data/lib/hecks/ports/persistence/execution.rb +4 -0
  220. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  221. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  222. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  223. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  224. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  225. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  226. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  227. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  228. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  229. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  230. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  231. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +160 -58
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +44 -6
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +338 -83
  242. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  243. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  244. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  245. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
  246. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  247. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  248. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  249. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
  250. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
  251. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
  252. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  253. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  254. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  255. data/lib/hecks/ports/persistence/repository_factory.rb +29 -6
  256. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  257. data/lib/hecks/ports/persistence.rb +36 -1
  258. data/lib/hecks/ports/projection.rb +61 -7
  259. data/lib/hecks/ports/query/in_memory.rb +3 -3
  260. data/lib/hecks/ports/query/ordering.rb +6 -6
  261. data/lib/hecks/ports/query.rb +35 -0
  262. data/lib/hecks/projections/bootstrap_table.rb +112 -0
  263. data/lib/hecks/projections/diagrams.rb +75 -75
  264. data/lib/hecks/projections/glossary/html.rb +250 -0
  265. data/lib/hecks/projections/glossary/markdown.rb +105 -0
  266. data/lib/hecks/projections/glossary/mermaid.rb +110 -0
  267. data/lib/hecks/projections/glossary/page.css +271 -0
  268. data/lib/hecks/projections/glossary/page.js +72 -0
  269. data/lib/hecks/projections/glossary/sections.rb +17 -0
  270. data/lib/hecks/projections/glossary/sentences.rb +205 -0
  271. data/lib/hecks/projections/glossary.rb +214 -286
  272. data/lib/hecks/projections/ir.rb +1 -1
  273. data/lib/hecks/projections/model/deviations.rb +18 -17
  274. data/lib/hecks/projections/model.rb +25 -21
  275. data/lib/hecks/projections/oidc.rb +7 -7
  276. data/lib/hecks/projections/parser_table.rb +5 -5
  277. data/lib/hecks/projections/reference.rb +3 -3
  278. data/lib/hecks/projections/rust_vocabulary.rb +443 -0
  279. data/lib/hecks/projections/shape.rb +2 -2
  280. data/lib/hecks/projections/statements.rb +11 -11
  281. data/lib/hecks/projections/vocabulary.rb +9 -9
  282. data/lib/hecks/projections.rb +5 -3
  283. data/lib/hecks/projector/cli_projector.rb +29 -29
  284. data/lib/hecks/projector/docs_projector.rb +13 -13
  285. data/lib/hecks/projector/exporter.rb +42 -21
  286. data/lib/hecks/projector/ir_projector.rb +1 -1
  287. data/lib/hecks/projector/narrate_projector.rb +15 -22
  288. data/lib/hecks/projector/target.rb +13 -13
  289. data/lib/hecks/projector.rb +15 -15
  290. data/lib/hecks/query_ir.rb +47 -47
  291. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  292. data/lib/hecks/query_specification/common/comparison.rb +132 -24
  293. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  294. data/lib/hecks/query_specification/common/null_policy.rb +57 -13
  295. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  296. data/lib/hecks/query_specification/common/options.rb +25 -0
  297. data/lib/hecks/query_specification/field_path.rb +69 -15
  298. data/lib/hecks/query_specification/hop_path.rb +57 -20
  299. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  300. data/lib/hecks/rendering.rb +3 -3
  301. data/lib/hecks/router/namespace_installer.rb +3 -3
  302. data/lib/hecks/router.rb +1 -1
  303. data/lib/hecks/runtime/aggregate_lock.rb +11 -11
  304. data/lib/hecks/runtime/boot_gates.rb +3 -3
  305. data/lib/hecks/runtime/caller.rb +8 -8
  306. data/lib/hecks/runtime/capability_graph.rb +2 -2
  307. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
  308. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +64 -67
  309. data/lib/hecks/runtime/command_interpreter.rb +159 -102
  310. data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
  311. data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
  312. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  313. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  314. data/lib/hecks/runtime/command_rules/references.rb +94 -19
  315. data/lib/hecks/runtime/command_rules.rb +3 -0
  316. data/lib/hecks/runtime/dependency_planning.rb +11 -11
  317. data/lib/hecks/runtime/dispatcher.rb +283 -90
  318. data/lib/hecks/runtime/entity_element.rb +282 -46
  319. data/lib/hecks/runtime/entity_interpreter.rb +160 -69
  320. data/lib/hecks/runtime/errors.rb +19 -19
  321. data/lib/hecks/runtime/event.rb +6 -6
  322. data/lib/hecks/runtime/identity.rb +22 -22
  323. data/lib/hecks/runtime/instance.rb +39 -14
  324. data/lib/hecks/runtime/interpreting.rb +12 -12
  325. data/lib/hecks/runtime/invocation.rb +276 -0
  326. data/lib/hecks/runtime/loader.rb +14 -14
  327. data/lib/hecks/runtime/outbox.rb +23 -23
  328. data/lib/hecks/runtime/policy_interpreter.rb +54 -54
  329. data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
  330. data/lib/hecks/runtime/query_interpreter.rb +111 -56
  331. data/lib/hecks/runtime/reaction_invocation.rb +76 -9
  332. data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
  333. data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
  334. data/lib/hecks/runtime/reference_hop.rb +6 -6
  335. data/lib/hecks/runtime/refusal_wording.rb +92 -112
  336. data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
  337. data/lib/hecks/runtime/registry/verification.rb +36 -26
  338. data/lib/hecks/runtime/registry.rb +56 -27
  339. data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
  340. data/lib/hecks/runtime/routing.rb +10 -88
  341. data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
  342. data/lib/hecks/runtime/saga_interpreter.rb +93 -56
  343. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  344. data/lib/hecks/runtime/tenant_check.rb +9 -9
  345. data/lib/hecks/runtime/tenant_scope.rb +5 -5
  346. data/lib/hecks/runtime/value/admission.rb +75 -30
  347. data/lib/hecks/runtime/value/coercion.rb +379 -226
  348. data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
  349. data/lib/hecks/runtime/value.rb +28 -23
  350. data/lib/hecks/runtime.rb +7 -7
  351. data/lib/hecks/storehouse.rb +64 -64
  352. data/lib/hecks/version.rb +3 -3
  353. data/lib/hecks/vocabulary.rb +207 -5
  354. data/lib/hecks.rb +13 -11
  355. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
  356. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
  357. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
  358. metadata +43 -2
@@ -14,20 +14,20 @@ require_relative "../ports/persistence/binding_policy"
14
14
  # runner.rb the same way the file-count/sweep concern is split from a
15
15
  # single test's own execution.
16
16
  #
17
- # TWO DELIBERATE DEVIATIONS from a prior port of this same idea (read
17
+ # Two deliberate deviations from a prior port of this same idea (read
18
18
  # before writing this, not reinvented):
19
19
  #
20
20
  # 1. A `setup` refusal and a refusal from the command under test are
21
- # caught in SEPARATE rescue scopes. The prior port wrapped the whole
21
+ # caught in separate rescue scopes. The prior port wrapped the whole
22
22
  # test body — setups included — in one `rescue *REFUSAL_CLASSES`, so a
23
23
  # broken setup could spuriously satisfy `expect refused: "..."` if its
24
24
  # own refusal happened to match the expected substring. Here, any
25
- # domain refusal during `setup` is unconditionally an ERROR — the
25
+ # domain refusal during `setup` is unconditionally an error — the
26
26
  # example never got to the situation it claims to test.
27
27
  #
28
- # 2. `emits:` is read off a `registry.event_log` DIFF around the tested
28
+ # 2. `emits:` is read off a `registry.event_log` diff around the tested
29
29
  # dispatch, not off `Result#events` alone. `Result#events` only holds
30
- # the events the OUTERMOST dispatch announced; a policy's own cascade
30
+ # the events the outermost dispatch announced; a policy's own cascade
31
31
  # reenters through the same `Dispatcher#dispatch` (`PolicyInterpreter
32
32
  # #deliver` → `door.reenter` → `dispatch`), and every dispatch's
33
33
  # events — outer and reentrant alike — land in the one shared
@@ -66,18 +66,18 @@ module Hecks
66
66
  error_result(test, "#{e.class}: #{e.message}")
67
67
  end
68
68
 
69
- # ONE BOOT PER SUITE, NOT PER TEST. The isolation a test needs is a
69
+ # One boot per suite, not per test. The isolation a test needs is a
70
70
  # runtime with nothing in it — and a boot of the same files gives
71
71
  # exactly that back for the price of `Registry#reset_runtime_state!`
72
72
  # instead of ~2s of loading, verifying and era-checking the same
73
73
  # bluebook again (chess: 76 behaviours, 155s of which was booting
74
- # `chess.bluebook` 76 times). Keyed by the suite's own `loads` AND
74
+ # `chess.bluebook` 76 times). Keyed by the suite's own `loads` and
75
75
  # their mtimes, so an edited bluebook boots fresh on the next test
76
76
  # rather than running against a stale one — the property a watch
77
77
  # loop or a long rspec session actually relies on. `runtime:` lets
78
78
  # a caller that already holds a booted runtime (a spec, a REPL)
79
79
  # hand it in; it is reset the same way.
80
- # NOT frozen — a real cache, mutated below (`RUNTIMES[key] ||=
80
+ # Not frozen — a real cache, mutated below (`RUNTIMES[key] ||=
81
81
  # boot_and_guard(files)`) and by #reset!. False positive for
82
82
  # Style/MutableConstant.
83
83
  # rubocop:disable-next Style/MutableConstant
@@ -94,12 +94,12 @@ module Hecks
94
94
  end
95
95
  end
96
96
 
97
- # `reset_runtime_state!` only drops repository OBJECTS between
97
+ # `reset_runtime_state!` only drops repository objects between
98
98
  # tests (registry.rb) — sufficient isolation for `Memory`, whose
99
99
  # `@records` is a plain per-instance ivar, so a fresh object really
100
100
  # is a fresh store. Against anything else (Sqlite, Postgres) the
101
101
  # rows themselves stay put: tests leak into each other, and a
102
- # suite booted against a domain's REAL hecksagon writes to a real
102
+ # suite booted against a domain's real hecksagon writes to a real
103
103
  # database. Refusing that wiring here, at boot, is the same shape
104
104
  # of guard `BindingPolicy` already applies to a missing bind — the
105
105
  # project's identity is refusing bad wiring up front, not
@@ -159,13 +159,13 @@ module Hecks
159
159
  check_ok(test) || check_fields(test, settled_state(runtime, verb, result)) || pass_result(test)
160
160
  end
161
161
 
162
- # A field expectation reads the aggregate AS IT STANDS once the
162
+ # A field expectation reads the aggregate as it stands once the
163
163
  # dispatch and its whole cascade have run — the same "cascades are
164
164
  # always on" reading `emits:` already commits to. `Result#state` is
165
- # the wrong source for that: it snapshots the instance the OUTER
165
+ # the wrong source for that: it snapshots the instance the outer
166
166
  # dispatch saved, and a policy's own reentrant dispatch (a ply
167
167
  # advancing off a Moved event, a move count bumping) hydrates and
168
- # saves a FRESH record afterward — so a field the cascade wrote
168
+ # saves a fresh record afterward — so a field the cascade wrote
169
169
  # read back stale (found live: `expect move_count: 1` got 0 while
170
170
  # `emits:` saw MoveCountBumped in the same test). The repository
171
171
  # holds the settled record; read it back by the id the dispatch
@@ -185,7 +185,7 @@ module Hecks
185
185
  # A behaviors test writes a dispatch the way the guide's own chess
186
186
  # examples do — receiver identity and command facts side by side
187
187
  # (`label: "g", id: "wn", to: { file: 2, rank: 2 }`) — and since
188
- # #335 the dispatcher's own `to:` keyword is the ROUTING envelope,
188
+ # #335 the dispatcher's own `to:` keyword is the routing envelope,
189
189
  # so forwarding those kwargs loose collides the moment a domain
190
190
  # declares a command fact named `to` (chess does: every Move's own
191
191
  # destination). Found live: every such test failed with "to: does
@@ -194,18 +194,31 @@ module Hecks
194
194
  # mixed facts into the strict envelope — identities lifted into
195
195
  # `to:`, declared facts into `with:` — so a behaviors dispatch now
196
196
  # goes through the exact same separation a policy's projection
197
- # does. A verb that resolves to no command (a port operation
198
- # "Pizzas::Order.PaymentGateway.Receive") keeps the loose
199
- # passthrough: its own input already spells the port form's
200
- # `to:`/`with:`, which the dispatcher's port branch reads directly.
197
+ # does. A verb that names a port operation (checked explicitly,
198
+ # below — "Pizzas::Order.PaymentGateway.Receive") keeps the loose
199
+ # passthrough instead: its own input already spells the port
200
+ # form's `to:`/`with:`, which the dispatcher's port branch reads
201
+ # directly, and `ReactionInvocation.build`'s explicit envelope
202
+ # expects a command's own declared attributes at the top level,
203
+ # not a port operation's already-wrapped `to:`/`with:` shape.
204
+ #
205
+ # This used to rely on `resolve_target` raising `UnknownVerb` for
206
+ # any port-operation verb — true only so long as nothing else ever
207
+ # asked it to resolve one. Now that a `policy` can legitimately
208
+ # `trigger` a port operation (`ReactionInvocation#resolve_target`'s
209
+ # own port-operation branch), that raise is gone, so this checks
210
+ # for a port operation directly instead of leaning on a refusal
211
+ # that no longer happens.
201
212
  def dispatch_command(runtime, verb, args)
213
+ return runtime.dispatch_flat(verb, args) if port_operation?(runtime, verb)
214
+
202
215
  invocation = begin
203
216
  Runtime::ReactionInvocation.build(registry: runtime.registry, verb: verb,
204
217
  projected: args, explicit: true)
205
218
  rescue Runtime::UnknownVerb
206
219
  nil
207
220
  end
208
- return runtime.dispatch(verb, **args) unless invocation
221
+ return runtime.dispatch_flat(verb, args) unless invocation
209
222
 
210
223
  if invocation.key?(:to)
211
224
  runtime.dispatch(verb, to: invocation[:to], with: invocation[:with])
@@ -214,6 +227,21 @@ module Hecks
214
227
  end
215
228
  end
216
229
 
230
+ # The same "Head.Rest" shape `Dispatcher#dispatch` and
231
+ # `ReactionInvocation#resolve_target` both already check — a bare
232
+ # domain/aggregate lookup plus a port-name lookup, no command
233
+ # resolution needed since all this asks is whether one exists.
234
+ def port_operation?(runtime, verb)
235
+ domain, aggregate_name, command_path = Naming.split_verb(verb)
236
+ return false unless command_path
237
+
238
+ aggregate = runtime.registry.bluebook(domain)&.aggregate(aggregate_name)
239
+ return false unless aggregate
240
+
241
+ head, rest = command_path.split(".", 2)
242
+ rest && !!aggregate.port(head)
243
+ end
244
+
217
245
  def run_query(test, runtime, verb)
218
246
  rows = runtime.query(verb, **test.input)
219
247
 
@@ -303,7 +331,7 @@ module Hecks
303
331
  # `expect` values both ways — bare (`expect kind: "bishop"`) and
304
332
  # wrapped (`expect kind: { value: "bishop" }`). A live record's
305
333
  # field always comes back as a `Hecks::Runtime::Value`;
306
- # normalizing BOTH sides to the same bare-scalar-or-plain-hash
334
+ # normalizing both sides to the same bare-scalar-or-plain-hash
307
335
  # shape is the one comparison that accepts either spelling.
308
336
  def normalize(value)
309
337
  return Hecks::Runtime::Value.materialize_unwrapped(value) if value.is_a?(Hecks::Runtime::Value)
@@ -316,7 +344,7 @@ module Hecks
316
344
  # can name more than one bluebook, so resolution searches every
317
345
  # aggregate across every bluebook the suite booted for the one that
318
346
  # actually declares the command. `on:` (when given, only ever on
319
- # the TESTED command — `setup` never receives it, see the DSL
347
+ # the tested command — `setup` never receives it, see the DSL
320
348
  # contract) narrows the search to one aggregate by name instead of
321
349
  # searching all of them.
322
350
  def qualify(command, on_aggregate, bluebooks, kind:)
@@ -326,7 +354,7 @@ module Hecks
326
354
  disambiguate_qualified_name(candidates, command, kind, bluebooks)
327
355
  end
328
356
 
329
- # THE SEARCH — every (bluebook, aggregate) pair that declares a
357
+ # **The search** — every (bluebook, aggregate) pair that declares a
330
358
  # command/query named `command`, narrowed to `on_aggregate` by name
331
359
  # when given.
332
360
  def qualify_candidates(command, on_aggregate, bluebooks, kind)
@@ -340,7 +368,7 @@ module Hecks
340
368
  pairs.select { |_, agg| agg.public_send(members).any? { |m| m.hecks_name == command.to_s } }
341
369
  end
342
370
 
343
- # THE REPORT — zero candidates and more-than-one candidates both
371
+ # **The report** — zero candidates and more-than-one candidates both
344
372
  # refuse (with a different message); exactly one resolves to its
345
373
  # dotted FQN.
346
374
  def disambiguate_qualified_name(candidates, command, kind, bluebooks)
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # The `.behaviors` authoring surface's own IR — plain Structs, holding
4
4
  # exactly what `Hecks.behaviors "Name" do ... end` collected. Deliberately
5
- # NOT part of `Hecks::IR`/`emits_ir` — a behaviors suite is never
5
+ # not part of `Hecks::IR`/`emits_ir` — a behaviors suite is never
6
6
  # collected into a domain Registry (see `Hecks.behaviors`), never
7
7
  # dispatched through MetaValidator, and carries none of the self-hosted
8
8
  # round-trip machinery a real bluebook construct does. It is a test
@@ -4,7 +4,7 @@ require_relative "../behaviors"
4
4
  # `bundle exec rspec` uses to run `.behaviors` files as ordinary examples,
5
5
  # one `it` per test, named by the test's own description string. Same
6
6
  # shape `spec/guides_spec.rb` uses for doctested guides: the file is
7
- # PARSED at collection time (cheap — `Behaviors.parse`, no test actually
7
+ # parsed at collection time (cheap — `Behaviors.parse`, no test actually
8
8
  # run yet, just enough to know the `it` names), and each test's own
9
9
  # `Expectations.run_one` runs lazily inside its own `it`, exactly when
10
10
  # rspec actually executes it.
@@ -35,12 +35,12 @@ 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
@@ -8,7 +8,7 @@ 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
@@ -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
@@ -7,7 +7,7 @@ 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
13
  def initialize(row)
@@ -29,7 +29,7 @@ module Hecks
29
29
  entities: entities,
30
30
  queries: asks,
31
31
  lifecycle: lifecycle_of(@row),
32
- # A policy declared inside a head is HOISTED onto the chapter by the
32
+ # A policy declared inside a head is hoisted onto the chapter by the
33
33
  # builder, and `Aggregate#to_h` never carried it — so the language does
34
34
  # not record which head a chapter-level policy was written in. The
35
35
  # chapter holds them all, which is what `PolicyInterpreter` reads.
@@ -43,11 +43,11 @@ module Hecks
43
43
 
44
44
  private
45
45
 
46
- # What this head points at with an aggregate-level `reference_to`. NOT the
46
+ # What this head points at with an aggregate-level `reference_to`. Not the
47
47
  # self-references its verbs carry — the first version read
48
48
  # `commands.filter_map(&:references)` and gave Pizza two targets of "Pizza",
49
49
  # its own verbs pointing at itself, where the builder recorded none. An
50
- # aggregate-level `reference_to X` leaves a reference ATTRIBUTE behind, so
50
+ # aggregate-level `reference_to X` leaves a reference attribute behind, so
51
51
  # the attributes are where the answer is.
52
52
  def reference_targets(fields)
53
53
  fields.select(&:reference?).map { |field| field.type.target_name }
@@ -57,7 +57,7 @@ module Hecks
57
57
  Build.call("ValueObject", row)
58
58
  end
59
59
 
60
- # S17, ADR 0026 — RECURSES. "That is what `entity` is for, and
60
+ # S17, ADR 0026 — recurses. "That is what `entity` is for, and
61
61
  # `entity` is declared by the language and used zero times in it"
62
62
  # — Dispatch, inside Handler, is the first use, so `row[:entities]`
63
63
  # is built the same way `row` itself was reached: through this
@@ -75,7 +75,7 @@ module Hecks
75
75
 
76
76
  # Every reference learns which head declares it, so it can reach the chapter
77
77
  # 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
78
+ # reference the walk misses resolves to nil, and a nil target is skipped
79
79
  # rather than refused, so the guarantee would go quiet instead of red.
80
80
  def stamp_references(aggregate)
81
81
  lists = [aggregate.attributes, *aggregate.commands.map(&:attributes), *aggregate.queries.map(&:attributes)]
@@ -84,7 +84,7 @@ module Hecks
84
84
  lists.flatten.select(&:reference?).each { |field| field.type.declared_in = aggregate }
85
85
  end
86
86
 
87
- # S17, ADR 0026 — walks NESTED entities too (Dispatch, inside
87
+ # S17, ADR 0026 — walks nested entities too (Dispatch, inside
88
88
  # Handler), not only an aggregate's own direct ones.
89
89
  def entity_reference_lists(entities, lists)
90
90
  entities.each do |piece|
@@ -95,7 +95,7 @@ module Hecks
95
95
  end
96
96
  end
97
97
 
98
- # THREE LANGUAGE FIELDS, ONE IR OBJECT. `state_field`, `state_start` and
98
+ # Three language fields, one IR object. `state_field`, `state_start` and
99
99
  # `transitions` are separate declarations; the IR keeps one Lifecycle. The
100
100
  # contract names them derived, and this is what derives them.
101
101
  def lifecycle_of(row)
@@ -110,8 +110,8 @@ module Hecks
110
110
  end
111
111
 
112
112
  # 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
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
115
115
  # froms — grouping by command alone would fuse two declarations that move the
116
116
  # same verb to different states.
117
117
  def transitions(rows)
@@ -1,7 +1,7 @@
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
6
  # There used to be a method per category here — `value_object(row)`,
7
7
  # `command(row)`, `policy(row)` — each one gathering the same keywords the
@@ -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
11
  # `derived:` used to be a list of names, and a list of names is a promise with
12
12
  # nobody holding it. The coverage gate only asked whether a field was accounted
13
13
  # for — so writing `derived: %i[version]` would have satisfied it while dropping
14
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:
15
+ # arc has found. Naming a field derived is a claim, and a claim needs a kind:
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,16 +25,16 @@ 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
35
  def reader(key) = Hash(reads)[key.to_sym]
36
36
 
37
- # How an appendable LIST becomes rows the walk can offer. A list absent from
37
+ # How an appendable list becomes rows the walk can offer. A list absent from
38
38
  # here reads straight off the node ; one that is present names the shaper,
39
39
  # because the IR keeps a shape the language does not — a transition whose
40
40
  # `from` is a list is several rows, an append binds several fields at once,
@@ -45,12 +45,19 @@ module Hecks
45
45
 
46
46
  def kind_of(field) = derived[field]
47
47
 
48
- # WHERE A FOLDED FIELD ACTUALLY LIVES, as [object, member].
48
+ # The fields the walk supplies every `derived: { field => :walk }` claim.
49
+ # The language declares them (`attribute :position, Position`) so the
50
+ # judge can order siblings, but no constructor takes one. This is the one
51
+ # place that fact is stated ; `Specializer` and `Model::Deviations` read it
52
+ # here rather than each keeping their own `%i[position]`.
53
+ def walked = derived.select { |_field, kind| kind == :walk }.keys
54
+
55
+ # Where a folded field actually lives, as [object, member].
49
56
  #
50
57
  # `[:folded, :lifecycle, :field]` says the language's `state_field` is the
51
58
  # `field` of the IR's one Lifecycle. That is the same fact `Readings` used to
52
59
  # 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
60
+ # drives both directions: the walk reads the member on the way in, and the
54
61
  # reconstruction gathers the members back into the object on the way out.
55
62
  #
56
63
  # A nil member means the fold has no single member to name — `rows` is a
@@ -64,15 +71,15 @@ module Hecks
64
71
  [kind[1], kind[2]]
65
72
  end
66
73
 
67
- # COMPUTED means worked out, not merely answerable.
74
+ # Computed means worked out, not merely answerable.
68
75
  #
69
76
  # Asking only whether the holder responds was too weak, and measurably so:
70
77
  # `[:computed, :version]` passed, because `Bluebook` does answer to
71
78
  # `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
79
+ # the constructor takes is stored, and calling it computed is how a chapter's
73
80
  # version would have gone missing while the gate said yes.
74
81
  #
75
- # So a computed field is one the holder answers AND the constructor does not
82
+ # So a computed field is one the holder answers and the constructor does not
76
83
  # accept. `query_name` qualifies (`Naming.snake(name)`) ; `version` cannot.
77
84
  def computes?(method)
78
85
  return false unless holder
@@ -92,17 +99,33 @@ module Hecks
92
99
  end
93
100
  end
94
101
 
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
102
+ # The fields that point at a parent without being spelled `*_id` the one list,
103
+ # read by the assembly gate (a `:parent` claim), the model generator and
104
+ # QueryIR (a declared field the model composes instead of storing).
105
+ #
106
+ # aggregate, bluebook the bare parent link a creating command mints
107
+ # (ADR 0025) — exactly the `parent_key`s `Plan` reads
108
+ # off the language
109
+ # owner Entity's own text twin of that link, which is why
110
+ # Entity's contract claims `owner: :parent`
111
+ #
112
+ # It used to read `%i[owner shape handler]` here while the model's copy read
113
+ # `owner aggregate bluebook`: `shape` and `handler` were Member's and
114
+ # Dispatch's parent fields before S17 (ADR 0026) made both nested entities,
115
+ # and nothing noticed the two lists had stopped agreeing. spec/assembly_spec
116
+ # now derives this set from `Plan` and the contracts and fails on any drift.
117
+ PARENT_POINTERS = %i[aggregate bluebook owner].freeze
118
+
119
+ def self.parent_pointer?(field)
120
+ field.to_s.end_with?("_id") || PARENT_POINTERS.include?(field.to_sym)
121
+ end
99
122
 
100
123
  # The only fields allowed to claim they describe something other than the
101
124
  # construct they hang off, each with the reason spelled out.
102
125
  #
103
126
  # normalisations the canonical-form table belongs to the expression grammar.
104
127
  # A chapter's rules are canonicalised on the way in, so the
105
- # language models the table — but no chapter STORES one, and
128
+ # language models the table — but no chapter stores one, and
106
129
  # `Bluebook#to_h` splices it in from Expression.
107
130
  ELSEWHERE = {
108
131
  Bluebook: %i[normalisations]