hecks 2.7.0 → 2.9.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 (463) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +11 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +18 -99
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres.rb +145 -134
  25. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  26. data/lib/hecks/adapters/driven/prism.rb +7 -30
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  31. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  32. data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
  33. data/lib/hecks/adapters/driven.rb +5 -36
  34. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  35. data/lib/hecks/behaviors/dsl.rb +7 -25
  36. data/lib/hecks/behaviors/expectations.rb +32 -142
  37. data/lib/hecks/behaviors/ir.rb +7 -20
  38. data/lib/hecks/behaviors/rspec.rb +6 -17
  39. data/lib/hecks/behaviors/runner.rb +10 -34
  40. data/lib/hecks/behaviors.rb +4 -8
  41. data/lib/hecks/bluebook/aggregate.rb +9 -75
  42. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  43. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  44. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  45. data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
  46. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  47. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  48. data/lib/hecks/bluebook/assembly.rb +6 -45
  49. data/lib/hecks/bluebook/attribute.rb +12 -61
  50. data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
  51. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  52. data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
  53. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  54. data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
  55. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  56. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  57. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  58. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  59. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  60. data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
  61. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  62. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  63. data/lib/hecks/bluebook/capabilities.rb +17 -49
  64. data/lib/hecks/bluebook/chapter.rb +10 -48
  65. data/lib/hecks/bluebook/command.rb +23 -81
  66. data/lib/hecks/bluebook/domain_port.rb +14 -63
  67. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  68. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  69. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  70. data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
  71. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
  72. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
  73. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  74. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  75. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  76. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
  77. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  78. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  79. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
  80. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  81. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  82. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  83. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  84. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  85. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  86. data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
  87. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  88. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  89. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  90. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  91. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  92. data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
  93. data/lib/hecks/bluebook/entity.rb +13 -71
  94. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  95. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  96. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  97. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  98. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  99. data/lib/hecks/bluebook/expression/resolver.rb +37 -660
  100. data/lib/hecks/bluebook/hexagon.rb +5 -28
  101. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  102. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  103. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  104. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  105. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  106. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  107. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
  108. data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
  110. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  111. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  112. data/lib/hecks/bluebook/meta_validator.rb +79 -349
  113. data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
  114. data/lib/hecks/bluebook/model_check.rb +102 -600
  115. data/lib/hecks/bluebook/pattern_subset.rb +11 -69
  116. data/lib/hecks/bluebook/process_manager.rb +13 -83
  117. data/lib/hecks/bluebook/project_loader.rb +3 -39
  118. data/lib/hecks/bluebook/project_register.rb +12 -60
  119. data/lib/hecks/bluebook/query.rb +9 -48
  120. data/lib/hecks/bluebook/read_model.rb +57 -81
  121. data/lib/hecks/bluebook/reference.rb +7 -41
  122. data/lib/hecks/bluebook/smoke_test.rb +8 -98
  123. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  124. data/lib/hecks/bluebook/translation.rb +26 -62
  125. data/lib/hecks/bluebook/value_object.rb +11 -36
  126. data/lib/hecks/bluebook.rb +5 -22
  127. data/lib/hecks/cache_dir.rb +84 -0
  128. data/lib/hecks/cli/document.rb +86 -0
  129. data/lib/hecks/cli/ir.rb +45 -0
  130. data/lib/hecks/cli/mcp.rb +31 -0
  131. data/lib/hecks/cli/mcp_door.rb +366 -0
  132. data/lib/hecks/cli/model_check.rb +203 -0
  133. data/lib/hecks/cli/project_cli.rb +90 -0
  134. data/lib/hecks/cli/project_diagrams.rb +36 -0
  135. data/lib/hecks/cli/run.rb +173 -0
  136. data/lib/hecks/cli/smoke_test.rb +45 -0
  137. data/lib/hecks/cli/stores.rb +53 -0
  138. data/lib/hecks/cli.rb +160 -0
  139. data/lib/hecks/construct.rb +8 -34
  140. data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
  141. data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
  142. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  143. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  144. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  145. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  146. data/lib/hecks/embryonaut_bluebook.rb +4 -71
  147. data/lib/hecks/facade/cli_door.rb +24 -106
  148. data/lib/hecks/facade/cli_runner.rb +28 -172
  149. data/lib/hecks/facade/command_request.rb +13 -33
  150. data/lib/hecks/facade/handle.rb +34 -121
  151. data/lib/hecks/facade/json_door.rb +31 -146
  152. data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
  153. data/lib/hecks/facade/surface/chapter.rb +10 -54
  154. data/lib/hecks/facade/surface.rb +8 -23
  155. data/lib/hecks/facade.rb +10 -25
  156. data/lib/hecks/forms/app.rb +15 -70
  157. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  158. data/lib/hecks/forms/field_renderer.rb +17 -156
  159. data/lib/hecks/forms/field_shape.rb +30 -222
  160. data/lib/hecks/forms/html.rb +12 -47
  161. data/lib/hecks/forms/index_renderer.rb +1 -4
  162. data/lib/hecks/forms/page.rb +3 -9
  163. data/lib/hecks/forms/params.rb +14 -60
  164. data/lib/hecks/forms/port_argument.rb +2 -19
  165. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  166. data/lib/hecks/forms/record_renderer.rb +8 -75
  167. data/lib/hecks/forms/record_table.rb +7 -50
  168. data/lib/hecks/forms/reference_options.rb +3 -30
  169. data/lib/hecks/forms/value_object_shape.rb +8 -35
  170. data/lib/hecks/forms.rb +5 -33
  171. data/lib/hecks/fqn.rb +5 -12
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  175. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  176. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  177. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  178. data/lib/hecks/framework.rb +11 -80
  179. data/lib/hecks/freezer.rb +5 -39
  180. data/lib/hecks/grammar/expression.bluebook +15 -40
  181. data/lib/hecks/grammar/translation.bluebook +3 -6
  182. data/lib/hecks/grammar.rb +16 -41
  183. data/lib/hecks/ir.rb +14 -83
  184. data/lib/hecks/language/adapter.bluebook +2 -7
  185. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  186. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  187. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
  188. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  189. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  190. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  191. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  192. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  193. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  194. data/lib/hecks/language/bluebook/query.bluebook +12 -43
  195. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  196. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  197. data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
  198. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  199. data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
  200. data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
  201. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  202. data/lib/hecks/language/oidc.json +35 -0
  203. data/lib/hecks/language/port.bluebook +4 -17
  204. data/lib/hecks/language/translation/translation.bluebook +3 -9
  205. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  206. data/lib/hecks/language/world/wiring.bluebook +1 -3
  207. data/lib/hecks/language/world/world.bluebook +5 -7
  208. data/lib/hecks/literal.rb +8 -50
  209. data/lib/hecks/mcp_door_scope.rb +95 -0
  210. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  211. data/lib/hecks/naming.rb +29 -137
  212. data/lib/hecks/ports/access_control.rb +7 -23
  213. data/lib/hecks/ports/agent/answers.rb +5 -23
  214. data/lib/hecks/ports/agent.rb +47 -172
  215. data/lib/hecks/ports/authentication.rb +12 -39
  216. data/lib/hecks/ports/authorization.rb +16 -43
  217. data/lib/hecks/ports/clock.rb +5 -45
  218. data/lib/hecks/ports/extraction.rb +7 -12
  219. data/lib/hecks/ports/identity_assignment.rb +8 -29
  220. data/lib/hecks/ports/identity_generation.rb +6 -29
  221. data/lib/hecks/ports/identity_resolution.rb +2 -10
  222. data/lib/hecks/ports/key_vault.rb +9 -45
  223. data/lib/hecks/ports/persistence/append_only.rb +62 -65
  224. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  225. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  226. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  227. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  228. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
  229. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
  231. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
  232. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
  233. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
  250. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  251. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  252. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  253. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  254. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  255. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  256. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
  257. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
  258. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  259. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  260. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  261. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  262. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  263. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  264. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  266. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  267. data/lib/hecks/ports/persistence/state_codec.rb +23 -103
  268. data/lib/hecks/ports/persistence.rb +7 -14
  269. data/lib/hecks/ports/projection.rb +42 -84
  270. data/lib/hecks/ports/query/in_memory.rb +10 -58
  271. data/lib/hecks/ports/query/ordering.rb +11 -32
  272. data/lib/hecks/ports/query.rb +14 -37
  273. data/lib/hecks/ports.rb +2 -8
  274. data/lib/hecks/projections/bootstrap_table.rb +11 -43
  275. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  276. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  277. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  278. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  279. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  280. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  281. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  282. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  283. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  284. data/lib/hecks/projections/deploy/fargate.rb +35 -202
  285. data/lib/hecks/projections/deploy/lambda.rb +151 -679
  286. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  287. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  288. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  289. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  290. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  291. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  292. data/lib/hecks/projections/deploy/preview/template.rb +2 -58
  293. data/lib/hecks/projections/deploy/preview.rb +2 -67
  294. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  295. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  296. data/lib/hecks/projections/deploy/shared.rb +47 -146
  297. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  298. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  299. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  300. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  301. data/lib/hecks/projections/diagrams.rb +20 -582
  302. data/lib/hecks/projections/glossary/html.rb +14 -130
  303. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  304. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  305. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  306. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  307. data/lib/hecks/projections/glossary.rb +30 -222
  308. data/lib/hecks/projections/ir.rb +3 -10
  309. data/lib/hecks/projections/model/deviations.rb +15 -42
  310. data/lib/hecks/projections/model.rb +12 -51
  311. data/lib/hecks/projections/oidc.rb +12 -56
  312. data/lib/hecks/projections/parser_table.rb +9 -28
  313. data/lib/hecks/projections/reference.rb +9 -16
  314. data/lib/hecks/projections/rust_vocabulary.rb +12 -250
  315. data/lib/hecks/projections/shape.rb +3 -29
  316. data/lib/hecks/projections/statements.rb +7 -86
  317. data/lib/hecks/projections/vocabulary.rb +6 -22
  318. data/lib/hecks/projections.rb +2 -16
  319. data/lib/hecks/projector/cli_projector.rb +60 -375
  320. data/lib/hecks/projector/docs_projector.rb +10 -201
  321. data/lib/hecks/projector/exporter.rb +46 -138
  322. data/lib/hecks/projector/ir_projector.rb +2 -9
  323. data/lib/hecks/projector/narrate_projector.rb +15 -189
  324. data/lib/hecks/projector/target.rb +13 -76
  325. data/lib/hecks/projector.rb +30 -121
  326. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  327. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  328. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  329. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  330. data/lib/hecks/query_specification/common/options.rb +2 -6
  331. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  332. data/lib/hecks/query_specification/field_path.rb +37 -77
  333. data/lib/hecks/query_specification/hop_path.rb +12 -76
  334. data/lib/hecks/release/gem_pin.rb +2 -26
  335. data/lib/hecks/release/runner/ci_publisher.rb +93 -0
  336. data/lib/hecks/release/runner/commands.rb +66 -0
  337. data/lib/hecks/release/runner/console.rb +52 -0
  338. data/lib/hecks/release/runner/gem_publisher.rb +43 -0
  339. data/lib/hecks/release/runner/git.rb +48 -0
  340. data/lib/hecks/release/runner/npm_publisher.rb +75 -0
  341. data/lib/hecks/release/runner/preflight.rb +104 -0
  342. data/lib/hecks/release/runner/published.rb +55 -0
  343. data/lib/hecks/release/runner/tagger.rb +80 -0
  344. data/lib/hecks/release/runner.rb +215 -0
  345. data/lib/hecks/rendering.rb +4 -26
  346. data/lib/hecks/router/namespace_installer.rb +12 -36
  347. data/lib/hecks/router.rb +4 -8
  348. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  349. data/lib/hecks/runtime/boot_gates.rb +3 -18
  350. data/lib/hecks/runtime/caller.rb +19 -59
  351. data/lib/hecks/runtime/capability_graph.rb +5 -21
  352. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  353. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  354. data/lib/hecks/runtime/command_interpreter.rb +125 -307
  355. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  356. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  357. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  358. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  359. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  360. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  361. data/lib/hecks/runtime/dispatcher.rb +82 -241
  362. data/lib/hecks/runtime/entity_element.rb +40 -462
  363. data/lib/hecks/runtime/entity_interpreter.rb +65 -220
  364. data/lib/hecks/runtime/errors.rb +32 -115
  365. data/lib/hecks/runtime/event.rb +3 -25
  366. data/lib/hecks/runtime/identity.rb +18 -154
  367. data/lib/hecks/runtime/instance.rb +18 -150
  368. data/lib/hecks/runtime/interpreting.rb +11 -70
  369. data/lib/hecks/runtime/invocation.rb +28 -164
  370. data/lib/hecks/runtime/loader.rb +48 -186
  371. data/lib/hecks/runtime/outbox.rb +38 -261
  372. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  373. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  374. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  375. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  376. data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
  377. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  378. data/lib/hecks/runtime/reference_hop.rb +8 -81
  379. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  380. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  381. data/lib/hecks/runtime/registry/verification.rb +52 -225
  382. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  383. data/lib/hecks/runtime/registry.rb +103 -244
  384. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  385. data/lib/hecks/runtime/routing.rb +4 -15
  386. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  387. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  388. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  389. data/lib/hecks/runtime/tenant_check.rb +6 -68
  390. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  391. data/lib/hecks/runtime/value/admission.rb +16 -67
  392. data/lib/hecks/runtime/value/coercion.rb +155 -641
  393. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  394. data/lib/hecks/runtime/value.rb +12 -84
  395. data/lib/hecks/runtime.rb +17 -65
  396. data/lib/hecks/storehouse.rb +146 -567
  397. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  398. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  399. data/lib/hecks/vendoring/git_environment.rb +2 -8
  400. data/lib/hecks/vendoring/git_source.rb +4 -10
  401. data/lib/hecks/vendoring.rb +7 -46
  402. data/lib/hecks/version.rb +3 -14
  403. data/lib/hecks/vocabulary.rb +5 -0
  404. data/lib/hecks.rb +32 -82
  405. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  406. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  407. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  408. metadata +30 -59
  409. data/lib/hecks/bench/cli.rb +0 -74
  410. data/lib/hecks/bench/environment.rb +0 -100
  411. data/lib/hecks/bench/postgres_probe.rb +0 -41
  412. data/lib/hecks/bench/report.rb +0 -116
  413. data/lib/hecks/bench/ruby_runner.rb +0 -151
  414. data/lib/hecks/bench/run.rb +0 -55
  415. data/lib/hecks/bench/rust_runner.rb +0 -165
  416. data/lib/hecks/bench/stats.rb +0 -75
  417. data/lib/hecks/bench/suite.rb +0 -172
  418. data/lib/hecks/bench/workload.rb +0 -137
  419. data/lib/hecks/bench.rb +0 -28
  420. data/lib/hecks/codemod.rb +0 -443
  421. data/lib/hecks/corpus.rb +0 -479
  422. data/lib/hecks/doc/reference.rb +0 -585
  423. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  424. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  425. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  426. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  427. data/lib/hecks/fuzzing/differential.rb +0 -192
  428. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  429. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  430. data/lib/hecks/fuzzing/form_census.rb +0 -285
  431. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  432. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  433. data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
  434. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  435. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  436. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  437. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  438. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  439. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  440. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  441. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  442. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  443. data/lib/hecks/fuzzing/properties.rb +0 -267
  444. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  445. data/lib/hecks/fuzzing/replay.rb +0 -930
  446. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  447. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  448. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  449. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  450. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  451. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  452. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  453. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  454. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  455. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  456. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  457. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  458. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  459. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  460. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  461. data/lib/hecks/fuzzing.rb +0 -23
  462. data/lib/hecks/grammar/evolve.rb +0 -524
  463. data/lib/hecks/query_ir.rb +0 -482
@@ -4,38 +4,19 @@ require_relative "field_renderer"
4
4
 
5
5
  module Hecks
6
6
  module Forms
7
- # The two directions between a flat, dotted, all-strings web payload
8
- # (`{"amount.cents"=>"1050", "amount.currency"=>"USD"}`, whether it came
9
- # off a POST form body or a GET query string — Rack hands back the same
10
- # flat shape for both as long as nothing uses `[]` bracket names) and the
11
- # nested, typed hash `Dispatcher#dispatch`/`#query` actually take
12
- # (`{amount: {cents: 1050, currency: "USD"}}`).
7
+ # Converts between flat dotted web params (`{"amount.cents"=>"1050"}`) and the nested
8
+ # hash of typed values a dispatch takes (`{amount: {cents: 1050}}`).
13
9
  #
14
- # `extract` needs the Field tree, not just the raw params — a numeric
15
- # leaf's own runtime check (`Value::Coercion#check_numeric_fields`)
16
- # requires an actual `Integer`/`Float`, not a String that merely looks
17
- # like one (`given.is_a?(expected)`, no coercion attempted there); a web
18
- # form can only ever hand back strings, so the cast has to happen here,
19
- # once, using the same shape `FieldShape` already resolved for
20
- # rendering the input in the first place — one reading of the IR, not
21
- # two that could disagree.
10
+ # `extract` needs the Field tree because the runtime's numeric check rejects
11
+ # numeric-looking Strings, so the cast happens here.
22
12
  module Params
23
13
  # Casts a flat, all-strings web payload into the nested, typed arguments a command or
24
14
  # query takes, guided by the field tree the form was rendered from.
25
15
  #
26
- # Every leaf field carries its own full dotted path regardless of how
27
- # deep `FieldShape` nested it to get there — a single-attribute value
28
- # object unwraps to a leaf sitting at the top of the fields array with
29
- # a two-segment path (`"reference.value"`), the exact same shape a
30
- # `:group`'s own child carries. So this collects every leaf as
31
- # (full path -> value) first, and nests by the path's own segments
32
- # last — one nesting rule, blind to how a field arrived at its path.
33
- #
34
- # @param fields [Array<Forms::Field>] the resolved field tree the payload was posted
35
- # against
16
+ # @param fields [Array<Forms::Field>] the resolved field tree the payload was posted against
36
17
  # @param raw [Hash{String => String}] the flat web payload, keyed by dotted field path
37
- # @return [Hash{Symbol => Object}] nested, typed arguments keyed by path segment, ready
38
- # to splat into a dispatch; a blank optional field is left out
18
+ # @return [Hash{Symbol => Object}] nested, typed arguments ready to splat into a dispatch;
19
+ # a blank optional field is left out
39
20
  # @raise [ArgumentError] if a number field's text is not numeric, or two fields collide
40
21
  # at one path (see `nest`)
41
22
  # @raise [TypeError] if a required number field is missing from `raw`
@@ -68,23 +49,8 @@ module Hecks
68
49
  # Nests values keyed by dotted path into a Hash keyed by path segment, refusing two
69
50
  # paths that cannot share one result.
70
51
  #
71
- # A path-prefix collision: one field named (say) "price" alongside
72
- # another named "price.cents" implies "price" is both a scalar leaf
73
- # and the parent of a nested group — the two can never coexist in
74
- # the same result hash. Depending on which pair `each_with_object`
75
- # reaches first, an unguarded walk fails in one of two ways: a scalar
76
- # planted first leaves `acc[segment] ||= {}` seeing a truthy non-Hash
77
- # and reusing it as `node`, so the next `node[leaf] = value` blows up
78
- # with a raw `TypeError` from calling `String#[]=` with a Symbol key;
79
- # a scalar planted after the nested group instead sails through
80
- # `node[leaf] = value` and silently clobbers the entire nested hash
81
- # with the scalar, losing every sibling under it with no error at
82
- # all. Both directions are checked explicitly here so either order
83
- # raises the same clear `ArgumentError` instead of a confusing crash
84
- # or silent data loss — this is the family of error every
85
- # command/query submission path in app.rb already rescues into a 422
86
- # (`ArgumentError` sits right alongside the domain refusals in every
87
- # one of those rescue clauses).
52
+ # "price" and "price.cents" cannot coexist. Both orders are checked so a collision
53
+ # raises ArgumentError (rescued into a 422) rather than a TypeError or silent data loss.
88
54
  #
89
55
  # @param pairs [Hash{String => Object}] values keyed by dotted path, such as
90
56
  # `{"amount.cents" => 1050}`
@@ -121,11 +87,8 @@ module Hecks
121
87
 
122
88
  # Reads a `:list` field's textarea into an Array, one element per non-blank line.
123
89
  #
124
- # One line of the textarea per element. A line that itself needs
125
- # several fields (a multi-attribute value object as a list element)
126
- # is read as JSON on that one line — the honest fallback documented in
127
- # docs/command-form-and-query-form-bluebook.md rather than a second
128
- # widget this prototype doesn't build yet.
90
+ # One textarea line per element; a multi-attribute value object element is read as
91
+ # JSON on its line.
129
92
  #
130
93
  # @param field [Forms::Field] a `:list` field whose first child describes one element
131
94
  # @param raw [Hash{String => String}] the flat web payload, keyed by dotted field path
@@ -191,13 +154,8 @@ module Hecks
191
154
  # Flattens held values back into the dotted-path, all-strings pairs a query string
192
155
  # carries — the reverse of `extract`.
193
156
  #
194
- # The other direction — a nested value hash back to the flat dotted
195
- # pairs a GET link's query string carries, so a query view's
196
- # "shareable link" and its filter form stay two renderings of the
197
- # same data rather than two formats that can drift. Reads with
198
- # `FieldRenderer.dig` — the identical full-path lookup a re-rendered
199
- # input's own value comes from — for the same reason `extract` above
200
- # nests by full path rather than by tree shape.
157
+ # Reads with `FieldRenderer.dig`, the same full-path lookup a re-rendered input uses,
158
+ # so a shareable link and its filter form cannot drift.
201
159
  #
202
160
  # @param fields [Array<Forms::Field>] the field tree naming which paths to read
203
161
  # @param values [Hash{String, Symbol => Object}] held values, flat or nested; see
@@ -221,11 +179,7 @@ module Hecks
221
179
  # Lists every dotted path a field tree submits under, whether or not anything was
222
180
  # entered for it.
223
181
  #
224
- # Every leaf/list path a field tree carries, independent of any
225
- # values — what `command_form_renderer.rb`'s inspect panel wants
226
- # ("which fields does this command take"), where `flatten` above wants
227
- # "what does this one submission look like" and returns nothing for a
228
- # field nothing was entered for.
182
+ # Unlike `flatten`, this does not depend on values, so unset fields are listed too.
229
183
  #
230
184
  # @param fields [Array<Forms::Field>] the field tree to walk
231
185
  # @param into [Array<String>] accumulator the paths are appended to in place
@@ -1,24 +1,7 @@
1
1
  module Hecks
2
2
  module Forms
3
- # bin/present's own `-p`/`--port` reader, pulled out of the script so
4
- # it can be driven directly instead of only through a real server
5
- # boot. Two spellings the previous inline version got wrong:
6
- #
7
- # --port=8080 the equals form — the old `ARGV.each_cons(2)` scan
8
- # only ever recognized "--port", "8080" as two
9
- # separate argv entries, so this spelling matched
10
- # nothing and silently fell through to the default.
11
- # -p abc a non-numeric value — the old code did `.to_i` on
12
- # whatever followed unconditionally, so a typo
13
- # quietly became port 0 (Rackup/WEBrick's actual
14
- # behavior for `Port: 0` is to bind an ephemeral
15
- # port — arguably useful on purpose elsewhere, but
16
- # never what a mistyped `-p abc` meant to ask for).
17
- #
18
- # Returns `[port, nil]` on a clean parse (falling back to `default`
19
- # when neither spelling appears at all) or `[nil, message]` when an
20
- # explicit port was given but isn't a real port number — the caller
21
- # decides what to do with a refusal (bin/present aborts on it).
3
+ # Reads bin/present's `-p PORT` / `--port PORT` / `--port=PORT` argument.
4
+ # Returns `[port, nil]` or `[nil, message]`; a non-numeric port is refused, not read as 0.
22
5
  module PortArgument
23
6
  module_function
24
7
 
@@ -9,32 +9,10 @@ require_relative "record_table"
9
9
 
10
10
  module Hecks
11
11
  module Forms
12
- # Query -> the page body for its GET view — the working half of what
13
- # `query_form.bluebook` names (see docs/command-form-and-query-form-
14
- # bluebook.md): a canonical, shareable link (queries are GETs — every
15
- # one of them is already a URL, so this says so up front rather than
16
- # hiding that behind a form); a row of ready-made links for any
17
- # closed-set parameter, since a caller filtering by an enum should not
18
- # have to fill in a form to get a link they could just click; a filter
19
- # form for everything else, which — being a GET form — produces
20
- # exactly the same kind of link on submit; and the results table once
21
- # a request actually supplies parameters.
12
+ # The GET view of a query: canonical link, quick links, filter form and results.
13
+ # See docs/command-form-and-query-form-bluebook.md.
22
14
  module QueryFormRenderer
23
- # Renders one query's whole GET view: header, canonical link, quick links, filter
24
- # form, any error, results, and the inspect panel.
25
- #
26
- # @param registry [Runtime::Registry] the booted registry, for resolving reference
27
- # fields' target aggregates
28
- # @param domain [String] the owning chapter's name
29
- # @param aggregate [Bluebook::Aggregate] the aggregate the query belongs to
30
- # @param query [Bluebook::Query] the query being rendered
31
- # @param action [String] the form's `action` URL and the canonical link's base
32
- # @param params [Hash] the request's own query params, pre-filling the filter form
33
- # @param results [Array<Hash>, nil] the query's own answer rows; `nil` renders no
34
- # results section
35
- # @param error [StandardError, nil] a raised error to show in the error banner;
36
- # `nil` renders none
37
- # @return [String] the page body markup
15
+ # Renders one query's whole GET view as the page body.
38
16
  def self.render(registry:, domain:, aggregate:, query:, action:, params: {}, results: nil, error: nil)
39
17
  fields = query.attributes.map { |a| FieldShape.resolve(a, aggregate: aggregate) }
40
18
  reference_options = ReferenceOptions.collect(registry, domain, fields)
@@ -50,12 +28,6 @@ module Hecks
50
28
  HTML
51
29
  end
52
30
 
53
- # Renders the query's title, description and badges.
54
- #
55
- # @param domain [String] the owning chapter's name
56
- # @param aggregate [Bluebook::Aggregate] the aggregate the query belongs to
57
- # @param query [Bluebook::Query] the query being rendered
58
- # @return [String] the header markup
59
31
  def self.header(domain, aggregate, query)
60
32
  <<~HTML
61
33
  <h1>#{Escape.html("#{domain}::#{aggregate.hecks_name}.#{query.hecks_name}")}</h1>
@@ -64,22 +36,12 @@ module Hecks
64
36
  HTML
65
37
  end
66
38
 
67
- # Renders the query's declared badges — today, just its `limit` when it declares one.
68
- #
69
- # @param query [Bluebook::Query] the query being rendered
70
- # @return [String] the badge markup; `""` when the query declares no `limit`
71
39
  def self.badges(query)
72
40
  parts = []
73
41
  parts << %(<span class="badge">limit #{Escape.html(query.limit.to_h[:value])}</span>) if query.limit
74
42
  parts.join
75
43
  end
76
44
 
77
- # Renders the query's plain-GET canonical link template, one `path={path}` per
78
- # parameter.
79
- #
80
- # @param action [String] the query's base URL
81
- # @param fields [Array<Forms::Field>] the resolved field tree naming the parameters
82
- # @return [String] the help text and the templated link markup
83
45
  def self.canonical_link(action, fields)
84
46
  paths = Params.paths(fields)
85
47
  template = paths.empty? ? action : "#{action}?#{paths.map { |path| "#{path}={#{path}}" }.join('&')}"
@@ -89,18 +51,7 @@ module Hecks
89
51
  HTML
90
52
  end
91
53
 
92
- # The one enum-shaped parameter (if there is one) rendered as literal,
93
- # clickable links — no form to fill in for "show me the suspended
94
- # ones". Capped at the first closed-set field on purpose: a second one
95
- # would mean a cross product of links, which reads as noise rather
96
- # than help. A query with more than one is still fully reachable
97
- # through the filter form below.
98
- #
99
- # @param action [String] the query's base URL
100
- # @param fields [Array<Forms::Field>] the resolved field tree to search for a
101
- # `:select` or `:radio` field
102
- # @return [String] one clickable link per option of the first closed-set field
103
- # found; `""` when no field is `:select` or `:radio`
54
+ # Capped at the first closed-set field: a second would mean a cross product of links.
104
55
  def self.quick_links(action, fields)
105
56
  field = fields.find { |f| %i[select radio].include?(f.kind) }
106
57
  return "" unless field
@@ -112,14 +63,6 @@ module Hecks
112
63
  %(<div class="example-links">#{links.join}</div>)
113
64
  end
114
65
 
115
- # Renders the GET filter form covering every declared parameter.
116
- #
117
- # @param action [String] the form's `action` URL
118
- # @param fields [Array<Forms::Field>] the resolved field tree to render
119
- # @param params [Hash] the request's own query params, pre-filling each field
120
- # @param reference_options [Hash{String => Array<Array(String, String)>, nil}] each
121
- # `:reference` field's own dropdown options, as `ReferenceOptions.collect` returns
122
- # @return [String] the form markup; `""` when the query declares no parameters
123
66
  def self.filter_form(action, fields, params, reference_options)
124
67
  return "" if fields.empty?
125
68
 
@@ -131,10 +74,6 @@ module Hecks
131
74
  HTML
132
75
  end
133
76
 
134
- # Renders a raised error as a dismissable-looking alert banner.
135
- #
136
- # @param error [StandardError, nil] the error to show
137
- # @return [String] the banner markup; `""` when `error` is `nil`
138
77
  def self.error_banner(error)
139
78
  return "" unless error
140
79
 
@@ -142,26 +81,12 @@ module Hecks
142
81
  "#{Escape.html(error.message)}</p></div>"
143
82
  end
144
83
 
145
- # Renders the results heading and table, once a request actually supplied
146
- # parameters.
147
- #
148
- # @param aggregate [Bluebook::Aggregate] the aggregate whose fields shape the table
149
- # @param results [Array<Hash>, nil] the query's own answer rows
150
- # @param domain [String] the owning chapter's name, for building record links
151
- # @return [String] the results section markup; `""` when `results` is `nil`
152
84
  def self.results_section(aggregate, results, domain)
153
85
  return "" unless results
154
86
 
155
87
  "<h2>Results (#{results.size})</h2>#{RecordTable.render(aggregate, results, domain: domain)}"
156
88
  end
157
89
 
158
- # Renders a collapsed panel with the query's own parameter list and raw IR JSON.
159
- #
160
- # @param domain [String] the owning chapter's name
161
- # @param aggregate [Bluebook::Aggregate] the aggregate the query belongs to
162
- # @param query [Bluebook::Query] the query being rendered
163
- # @param fields [Array<Forms::Field>] the resolved field tree, for the parameter list
164
- # @return [String] the `<details>` panel markup
165
90
  def self.inspect_panel(domain, aggregate, query, fields)
166
91
  verb = "#{domain}::#{aggregate.hecks_name}.#{query.hecks_name}"
167
92
  paths = Params.paths(fields)
@@ -4,19 +4,10 @@ require_relative "record_table"
4
4
 
5
5
  module Hecks
6
6
  module Forms
7
- # The two pages a `.bluebook` doesn't declare but a browsing developer
8
- # always wants: every record of an aggregate, and one record's own
9
- # state plus what it can legally do next. Neither is a `query` — they
10
- # read the repository directly, the same way `AggregateDoor#all`/`#find`
11
- # do (facade/surface/aggregate_door.rb) — so they exist for every
12
- # aggregate whether or not its bluebook declared a query at all.
7
+ # The index and show pages every aggregate gets, whether or not it declares a query.
8
+ # They read the repository directly, like `AggregateDoor#all`/`#find`.
13
9
  module RecordRenderer
14
- # Renders the index page listing every record of one aggregate.
15
- #
16
- # @param registry [Runtime::Registry] the booted registry holding the domain
17
- # @param domain [String, Symbol] the domain name
18
- # @param aggregate [Bluebook::Aggregate] the aggregate whose records are listed
19
- # @return [String] the index page's rendered HTML
10
+ # Renders the page listing every record of one aggregate.
20
11
  def self.index(registry:, domain:, aggregate:)
21
12
  instances = registry.repository(domain, aggregate).all
22
13
  <<~HTML
@@ -29,12 +20,6 @@ module Hecks
29
20
  HTML
30
21
  end
31
22
 
32
- # Renders the "+ Command" buttons for every creating command.
33
- #
34
- # @param domain [String, Symbol] the domain name
35
- # @param aggregate [Bluebook::Aggregate] the aggregate whose creating commands
36
- # are linked
37
- # @return [String] HTML action buttons, or `""` if the aggregate declares none
38
23
  def self.creating_links(domain, aggregate)
39
24
  creators = aggregate.commands.select(&:creates?)
40
25
  return "" if creators.empty?
@@ -46,15 +31,8 @@ module Hecks
46
31
  %(<div class="actions">#{links.join}</div>)
47
32
  end
48
33
 
49
- # Renders one record's state page: its current fields, plus the commands and
50
- # queries it can dispatch next.
51
- #
52
- # @param registry [Runtime::Registry] the booted registry holding the domain
53
- # @param domain [String, Symbol] the domain name
54
- # @param aggregate [Bluebook::Aggregate] the aggregate the record belongs to
55
- # @param id [String, Object] the record's identity
56
- # @return [String, nil] the record page's rendered HTML, or nil if no record
57
- # has that id
34
+ # Renders one record's state plus the commands and queries it can dispatch next.
35
+ # Returns nil when no record has that id.
58
36
  def self.show(registry:, domain:, aggregate:, id:)
59
37
  instance = registry.repository(domain, aggregate).find(id)
60
38
  return nil unless instance
@@ -71,10 +49,6 @@ module Hecks
71
49
  HTML
72
50
  end
73
51
 
74
- # Renders a record's state as an HTML field table.
75
- #
76
- # @param state [Hash{Symbol => Object}] the record's stored attribute values
77
- # @return [String] an HTML table of the state's fields, values escaped
78
52
  def self.state_table(state)
79
53
  rows = state.map do |key, value|
80
54
  "<tr><th>#{Escape.html(Humanize.label(key.to_s))}</th><td>#{Escape.html(render_value(value))}</td></tr>"
@@ -82,13 +56,7 @@ module Hecks
82
56
  %(<div class="table-scroll"><table><tbody>#{rows.join}</tbody></table></div>)
83
57
  end
84
58
 
85
- # `Value.materialize` first — a stored field is a `Runtime::Value`
86
- # wherever its attribute is a value object, not a plain Hash (see
87
- # record_table.rb's own note on the same read).
88
- #
89
- # @param value [Object] a stored field's value, possibly a `Runtime::Value`
90
- # @return [String] the value rendered as text: a Hash as `"key: value, ..."`
91
- # pairs, an Array `;`-joined, anything else its own `to_s`
59
+ # A stored field is a `Runtime::Value` wherever its attribute is a value object.
92
60
  def self.render_value(value)
93
61
  case (value = Runtime::Value.materialize(value))
94
62
  when Hash then value.map { |k, v| "#{k}: #{render_value(v)}" }.join(", ")
@@ -97,27 +65,13 @@ module Hecks
97
65
  end
98
66
  end
99
67
 
100
- # Every non-creating command, except a lifecycle transition that does
101
- # not apply from the record's current state — the same rule
102
- # `Rules#admissible_transition` enforces at dispatch, read here so a
103
- # link that would only refuse is never offered in the first place.
104
- #
105
- # @param domain [String, Symbol] the domain name
106
- # @param aggregate [Bluebook::Aggregate] the aggregate the record belongs to
107
- # @param id [String, Object] the record's identity
108
- # @param current_state [String, nil] the record's current lifecycle state, or
109
- # nil if the aggregate has no lifecycle field or it is unset
110
- # @return [String] an HTML list of admissible command links, or a message
111
- # when no command currently applies
68
+ # Omits lifecycle transitions that dispatch would refuse from the current state.
112
69
  def self.command_links(domain, aggregate, id, current_state)
113
70
  commands = aggregate.commands.reject(&:creates?).select { |cmd| applies?(aggregate, cmd, current_state) }
114
71
  return "<p><em>No commands act on an existing #{Escape.html(aggregate.hecks_name)}.</em></p>" if commands.empty?
115
72
 
116
73
  items = commands.map do |cmd|
117
- # L12 — the id is free-form (S3): percent-encoded as the query
118
- # value (a raw `&` here would smuggle a second bogus query
119
- # parameter), then the assembled href is attribute-escaped as
120
- # usual.
74
+ # The id is free-form, so it is percent-encoded as a query value.
121
75
  href = "/#{domain}/#{aggregate.hecks_name}/#{cmd.hecks_name}.html?to=#{Escape.url(id)}"
122
76
  %(<li><a href="#{Escape.attr(href)}"><span>#{Escape.html(cmd.hecks_name)}</span>) \
123
77
  "<span class=\"kind\">#{Escape.html(cmd.goal.to_s)}</span></a></li>"
@@ -125,14 +79,6 @@ module Hecks
125
79
  %(<ul class="verb-list">#{items.join}</ul>)
126
80
  end
127
81
 
128
- # Whether the aggregate's lifecycle admits dispatching `command` from
129
- # `current_state`.
130
- #
131
- # @param aggregate [Bluebook::Aggregate] the aggregate the command belongs to
132
- # @param command [Bluebook::Command] the command to check
133
- # @param current_state [String, nil] the record's current lifecycle state
134
- # @return [Boolean] true if the aggregate declares no lifecycle transitions for
135
- # this command, or one of them admits `current_state`
136
82
  def self.applies?(aggregate, command, current_state)
137
83
  transitions = aggregate.lifecycle&.transitions_for(command.hecks_name) || []
138
84
  return true if transitions.empty?
@@ -140,12 +86,6 @@ module Hecks
140
86
  transitions.any? { |t| t.from.nil? || Array(t.from).map(&:to_s).include?(current_state.to_s) }
141
87
  end
142
88
 
143
- # Renders the "Queries" link list for an aggregate's own queries.
144
- #
145
- # @param domain [String, Symbol] the domain name
146
- # @param aggregate [Bluebook::Aggregate] the aggregate whose queries are linked
147
- # @return [String] an HTML section listing every query, or `""` if the
148
- # aggregate declares none
149
89
  def self.query_links(domain, aggregate)
150
90
  return "" if aggregate.queries.empty?
151
91
 
@@ -156,13 +96,6 @@ module Hecks
156
96
  %(<h2>Queries</h2><ul class="verb-list">#{items.join}</ul>)
157
97
  end
158
98
 
159
- # Renders the full command-and-query index for an aggregate, independent of
160
- # any one record's state.
161
- #
162
- # @param domain [String, Symbol] the domain name
163
- # @param aggregate [Bluebook::Aggregate] the aggregate whose verbs are listed
164
- # @return [String] an HTML section listing every non-creating command and every
165
- # query, or `""` if the aggregate declares neither
166
99
  def self.verb_list(domain, aggregate)
167
100
  commands = aggregate.commands.reject(&:creates?)
168
101
  return "" if commands.empty? && aggregate.queries.empty?
@@ -3,42 +3,20 @@ require_relative "field_shape"
3
3
 
4
4
  module Hecks
5
5
  module Forms
6
- # The one shape every renderer in this directory reads a record as —
7
- # `id` plus its state hash. `Runtime::Instance` already answers both,
8
- # so `repository.all`'s own records pass straight through; a query's
9
- # answer does not (`QueryInterpreter#call` flattens `{id:}.merge(state)`
10
- # into one hash with no method to call — see
11
- # docs/command-form-and-query-form-bluebook.md's note on why), so
12
- # app.rb wraps those results in one of these before they ever reach a
13
- # renderer, and every renderer here only has to know one shape.
6
+ # A record as every renderer here reads it: `id` plus its state hash.
7
+ # Query results are flat hashes, so they are wrapped in one before rendering.
14
8
  Record = Struct.new(:id, :state)
15
9
 
16
- # An array of Record (or anything answering `#id`/`#state` — a
17
- # Runtime::Instance qualifies without wrapping) -> an HTML table.
18
- # Shared by the aggregate index page (record_renderer.rb) and a
19
- # query's own results (query_form_renderer.rb) — the same records
20
- # read the same columns either way.
10
+ # Records (a Record or anything answering `#id`/`#state`) as an HTML table.
21
11
  module RecordTable
22
- # Picks which fields to show as columns: identity, lifecycle state, then scalar
23
- # attributes, capped at seven.
24
- #
25
- # @param aggregate [Bluebook::Aggregate] the aggregate whose records will be shown
26
- # @return [Array<Symbol>] up to 7 unique field names, in that order; excludes
27
- # reference and list attributes
12
+ # Identity, lifecycle state, then scalar attributes, capped at seven columns.
28
13
  def self.columns(aggregate)
29
14
  lifecycle = aggregate.lifecycle&.field
30
15
  scalars = aggregate.attributes.reject { |a| a.reference? || a.list? }.map(&:name)
31
16
  [*aggregate.identity_heads, lifecycle, *scalars].compact.uniq.first(7)
32
17
  end
33
18
 
34
- # Renders a list of records as an HTML table, one row per record.
35
- #
36
- # @param aggregate [Bluebook::Aggregate] the aggregate the records belong to
37
- # @param instances [Array<Runtime::Instance, Forms::Record>] the records to render;
38
- # anything answering `#id`/`#state` works
39
- # @param domain [String] the owning chapter's name, for building each row's link
40
- # @return [String] the table markup; `"<p><em>No records.</em></p>"` when
41
- # `instances` is empty
19
+ # Renders records as an HTML table, one row per record.
42
20
  def self.render(aggregate, instances, domain:)
43
21
  cols = columns(aggregate)
44
22
  head = (["id"] + cols).map { |name| "<th>#{Escape.html(Humanize.label(name.to_s))}</th>" }.join
@@ -53,36 +31,15 @@ module Hecks
53
31
  HTML
54
32
  end
55
33
 
56
- # Renders one record's own table row, linked to its show page.
57
- #
58
- # @param instance [Runtime::Instance, Forms::Record] the record to render
59
- # @param aggregate [Bluebook::Aggregate] the aggregate the record belongs to
60
- # @param cols [Array<Symbol>] the column field names, as `columns` returns them
61
- # @param domain [String] the owning chapter's name, for building the row's link
62
- # @return [String] the `<tr>` markup
63
34
  def self.row(instance, aggregate, cols, domain)
64
35
  cells = cols.map { |name| "<td>#{Escape.html(cell(instance, name))}</td>" }.join
65
- # L12 — the id is free-form (S3): percent-encoded as the path
66
- # segment, HTML-escaped as the link text, and the assembled href
67
- # is itself attribute-escaped (belt-and-suspenders — nothing else
68
- # in `href` is untrusted, but this matches the convention used
69
- # everywhere else an href is built from parts).
36
+ # The id is free-form, so it is percent-encoded in the path.
70
37
  href = "/#{domain}/#{aggregate.hecks_name}/#{Escape.path(instance.id)}.html"
71
38
  "<tr><td><a href=\"#{Escape.attr(href)}\">#{Escape.html(instance.id)}</a></td>#{cells}</tr>"
72
39
  end
73
40
 
74
- # Reads one field's value for display, unwrapped to a single cell value.
75
- #
76
- # @param instance [Runtime::Instance, Forms::Record] the record to read
77
- # @param name [Symbol] the field name to read
78
- # @return [Object] the field's own value; for a Hash-shaped value object, its
79
- # first member's value; `""` for `nil`
80
41
  def self.cell(instance, name)
81
- # `state` holds `Runtime::Value` wherever an attribute is a value
82
- # object, not a plain Hash — `.materialize` is the runtime's own
83
- # unwrap (value.rb), the same one a `to_h` read anywhere else in
84
- # this codebase goes through, so a table cell agrees with every
85
- # other reader about what a stored field actually contains.
42
+ # `state` holds a `Runtime::Value` wherever an attribute is a value object.
86
43
  value = Runtime::Value.materialize(instance.state[name])
87
44
  case value
88
45
  when Hash then value.values.first
@@ -1,33 +1,15 @@
1
1
  module Hecks
2
2
  module Forms
3
- # A Field tree (field_shape.rb) -> `{path => [[id, label], ...]}` for
4
- # every `:reference` field in it — the options a `<select>` needs.
5
- # Shared by CommandFormRenderer and QueryFormRenderer alike: a
6
- # command's own reference field and a query's own reference parameter
7
- # resolve against the same repository the same way, and neither word
8
- # owns this more than the other.
3
+ # The `<select>` options for every `:reference` field in a Field tree, keyed by path.
4
+ # Shared by the command and query form renderers.
9
5
  module ReferenceOptions
10
- # Collects every `:reference` field's own dropdown options out of a resolved field
11
- # tree.
12
- #
13
- # @param registry [Runtime::Registry] the booted registry to read repositories from
14
- # @param domain [String] the owning chapter's name
15
- # @param fields [Array<Forms::Field>] the resolved field tree to search
16
- # @return [Hash{String => Array<Array(String, String)>, nil}] one entry per
17
- # `:reference` field found, keyed by its `path`; `nil` when the field's target
18
- # could not be resolved or its options could not be read
6
+ # Returns `{path => [[id, id], ...]}`; nil for a field whose options cannot be read.
19
7
  def self.collect(registry, domain, fields)
20
8
  targets = {}
21
9
  walk(fields) { |field| targets[field.path] = field.target_aggregate if field.kind == :reference }
22
10
  targets.transform_values { |aggregate| aggregate && options_for(registry, domain, aggregate) }
23
11
  end
24
12
 
25
- # Visits every field in a tree, depth-first, including each `:list`/`:group`
26
- # field's own children.
27
- #
28
- # @param fields [Array<Forms::Field>] the field tree to walk
29
- # @yieldparam field [Forms::Field] each field visited, parent before children
30
- # @return [void]
31
13
  def self.walk(fields, &block)
32
14
  fields.each do |field|
33
15
  block.call(field)
@@ -35,15 +17,6 @@ module Hecks
35
17
  end
36
18
  end
37
19
 
38
- # Reads up to 200 records' ids as `<select>` options, for one reference field's
39
- # target aggregate.
40
- #
41
- # @param registry [Runtime::Registry] the booted registry to read the repository from
42
- # @param domain [String] the owning chapter's name
43
- # @param aggregate [Bluebook::Aggregate] the referenced aggregate to list
44
- # @return [Array<Array(String, String)>, nil] up to 200 `[id, id]` pairs; `nil` when
45
- # the repository cannot be resolved (a wiring gap degrades to a plain text id
46
- # input, not a 500)
47
20
  def self.options_for(registry, domain, aggregate)
48
21
  registry.repository(domain, aggregate).all.first(200).map { |instance| [instance.id, instance.id] }
49
22
  rescue StandardError
@@ -1,53 +1,26 @@
1
1
  module Hecks
2
2
  module Forms
3
- # The value-object classification every consumer of a resolved VO
4
- # shape needs, spelled once — `field_shape.rb` (a command's own
5
- # form), `ui_schema.rb` (the console app's table/detail-panel
6
- # renderer, a separate app, not this repo), and
7
- # `adapters/driven/sql_query_builder.rb` (a query's own ORDER BY/
8
- # where compiler) each grew their own copy of "is this VO money-
9
- # shaped," "does it have exactly one attribute, worth unwrapping,"
10
- # and "which member of it is numeric" — the same three questions,
11
- # answered identically by definition (a VO's shape doesn't change
12
- # depending on who's asking), so a fix to one copy was never
13
- # guaranteed to reach the others.
3
+ # Shared value-object shape classification, so callers such as
4
+ # field_shape.rb, ui_schema.rb and sql_query_builder.rb agree on it.
14
5
  module ValueObjectShape
15
6
  module_function
16
7
 
17
- # `Price{cents:, currency:}`/`ContractValue{cents:, currency:}` —
18
- # every money-shaped VO in the corpus spells it exactly this way,
19
- # the two-attribute convention the language itself never enforces
20
- # but every real chapter follows.
21
- # @param value_object [Bluebook::ValueObject] the value object to classify
22
- # @return [Boolean] true if its attributes are exactly `cents` and `currency`
8
+ # True if the VO's attributes are exactly `cents` and `currency`, the
9
+ # convention every money-shaped VO in the corpus follows.
23
10
  def money?(value_object)
24
11
  value_object.attributes.map { |a| a.name.to_s }.sort == %w[cents currency]
25
12
  end
26
13
 
27
- # A VO with exactly one attribute is a name for a scalar, not a
28
- # genuine group (EmailAddress{address}, CustomerNumber{value}) —
29
- # [[feedback_name_the_scalar_field]]'s own reasoning, shared here
30
- # rather than re-decided per caller. Returns the sole attribute,
31
- # or nil for anything else.
32
- #
33
- # @param value_object [Bluebook::ValueObject] the value object to inspect
34
- # @return [Bluebook::Attribute, nil] its one declared attribute, or nil if
35
- # it declares zero or more than one
14
+ # A VO with exactly one attribute names a scalar, not a genuine group.
15
+ # Returns that attribute, or nil for anything else.
36
16
  def sole_attribute(value_object)
37
17
  return nil unless value_object.attributes.size == 1
38
18
 
39
19
  value_object.attributes.first
40
20
  end
41
21
 
42
- # The first Integer/Float member, or nil — what a numeric
43
- # comparison or an ORDER BY compiles against when the VO isn't
44
- # money-shaped (money's own two members are handled by `money?`
45
- # instead, since which one governs ordering is a money-specific
46
- # decision, not a general "pick the first number" one).
47
- #
48
- # @param value_object [Bluebook::ValueObject] the value object to inspect
49
- # @return [Bluebook::Attribute, nil] its first Integer- or Float-typed
50
- # attribute, or nil if it declares none
22
+ # The first Integer/Float member, or nil — what a numeric comparison or
23
+ # ORDER BY compiles against when the VO isn't money-shaped.
51
24
  def numeric_member(value_object)
52
25
  value_object.attributes.find { |a| %w[Integer Float].include?(a.type.to_s) }
53
26
  end