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
@@ -236,6 +236,21 @@
236
236
  "verb": "Bluebook::Query.Option",
237
237
  "role": "Language"
238
238
  },
239
+ {
240
+ "scope": "bluebook:read_model.all",
241
+ "verb": "Bluebook::ReadModel.All",
242
+ "role": "Language"
243
+ },
244
+ {
245
+ "scope": "bluebook:read_model.any",
246
+ "verb": "Bluebook::ReadModel.Any",
247
+ "role": "Language"
248
+ },
249
+ {
250
+ "scope": "bluebook:read_model.avg",
251
+ "verb": "Bluebook::ReadModel.Avg",
252
+ "role": "Language"
253
+ },
239
254
  {
240
255
  "scope": "bluebook:read_model.count",
241
256
  "verb": "Bluebook::ReadModel.Count",
@@ -256,16 +271,36 @@
256
271
  "verb": "Bluebook::ReadModel.GroupBy",
257
272
  "role": "Language"
258
273
  },
274
+ {
275
+ "scope": "bluebook:read_model.max",
276
+ "verb": "Bluebook::ReadModel.Max",
277
+ "role": "Language"
278
+ },
259
279
  {
260
280
  "scope": "bluebook:read_model.median",
261
281
  "verb": "Bluebook::ReadModel.Median",
262
282
  "role": "Language"
263
283
  },
284
+ {
285
+ "scope": "bluebook:read_model.min",
286
+ "verb": "Bluebook::ReadModel.Min",
287
+ "role": "Language"
288
+ },
264
289
  {
265
290
  "scope": "bluebook:read_model.option",
266
291
  "verb": "Bluebook::ReadModel.Option",
267
292
  "role": "Language"
268
293
  },
294
+ {
295
+ "scope": "bluebook:read_model.percentile",
296
+ "verb": "Bluebook::ReadModel.Percentile",
297
+ "role": "Language"
298
+ },
299
+ {
300
+ "scope": "bluebook:read_model.sum",
301
+ "verb": "Bluebook::ReadModel.Sum",
302
+ "role": "Language"
303
+ },
269
304
  {
270
305
  "scope": "bluebook:syntax.argument",
271
306
  "verb": "Bluebook::Syntax.Argument",
@@ -25,14 +25,8 @@ Hecks.bluebook "Port" do
25
25
  attribute :value, String, one_of: ["reply", "effect"]
26
26
  end
27
27
 
28
- # THE METHOD CONTRACT — what an adapter must actually `respond_to?`
29
- # for a dispatch through this door to reach it, as opposed to `verb`
30
- # and `signal`, which only ever describe the door itself. Optional
31
- # the same way `verb` is (a port declared with none is exactly
32
- # today's pre-existing behavior, unchecked past `verify!`'s existing
33
- # gates), and a plain list of method names rather than a richer
34
- # shape — the same restraint `AdapterField` already takes for
35
- # `field`/`secret`.
28
+ # The method contract: what an adapter must `respond_to?` for a dispatch through this door.
29
+ # A port that declares none is unchecked beyond `verify!`.
36
30
  value_object "PortAnswer" do
37
31
  attribute :value, String
38
32
  invariant("an answer names a real method") { !value.to_s.empty? }
@@ -43,12 +37,7 @@ Hecks.bluebook "Port" do
43
37
  goal "Open a resource door a domain's own aggregates may call by verb"
44
38
 
45
39
  attribute :name, PortName
46
- # OPTIONAL — real, pre-existing PortBuilder behavior (spec/dsl_spec
47
- # .rb's own "signal says whether the domain gets a value back..."
48
- # builds a port with only `signal`, never calling `verb` at all;
49
- # `def verb(value)` has no default, unlike `signal`'s `:reply`).
50
- # Same asymmetry world.bluebook's own Declare command already has
51
- # for `realm`/`latest`.
40
+ # Optional: a port may declare only `signal`; `verb` has no default, unlike `signal`.
52
41
  attribute :verb, PortVerb, optional: true
53
42
  attribute :signal, PortSignal
54
43
 
@@ -71,9 +60,7 @@ Hecks.bluebook "Port" do
71
60
  emits "PortAnswerAdded"
72
61
  end
73
62
 
74
- # HOW PORT IS SPELLED. These rows live with the concept
75
- # they describe; SyntaxBoot discovers every aggregate-local seed table.
76
- # Contexts represented here: File, Port.
63
+ # Keyword rows for File and Port; SyntaxBoot discovers every aggregate-local seed table.
77
64
  value_object "KeywordSeed" do
78
65
  attribute :word, String
79
66
  attribute :context, String, admits: "Syntax::Context"
@@ -24,12 +24,8 @@ Hecks.bluebook "Translation" do
24
24
  invariant("a name says something") { !value.to_s.empty? }
25
25
  end
26
26
 
27
- # ARBITRARY, UNVALIDATED CAPTURE — `convert`'s own `values:` table and
28
- # `backfill`'s own `default:` are open-ended (any literal a real
29
- # migration needs), the same shape `AggregateBuilder#provenance`/
30
- # `CommandBuilder#provenance` already capture as plain, unvalidated
31
- # text (`LiteralText`, aggregate.bluebook) rather than a fixed
32
- # attribute shape a closed self-hosted record could enumerate.
27
+ # `convert` values and `backfill` defaults are open-ended literals, so they are held as
28
+ # unvalidated text, like `provenance` (`LiteralText`, aggregate.bluebook).
33
29
  value_object "TranslationLiteralText" do
34
30
  attribute :value, String
35
31
  end
@@ -60,9 +56,7 @@ Hecks.bluebook "Translation" do
60
56
  emits "TranslationAggregateRetired"
61
57
  end
62
58
 
63
- # HOW TRANSLATION IS SPELLED. These rows live with the concept
64
- # they describe; SyntaxBoot discovers every aggregate-local seed table.
65
- # Contexts represented here: File, Translation.
59
+ # Syntax seed rows for the words this aggregate spells; SyntaxBoot discovers them.
66
60
  value_object "KeywordSeed" do
67
61
  attribute :word, String
68
62
  attribute :context, String, admits: "Syntax::Context"
@@ -6,22 +6,8 @@ Hecks.bluebook "Translation" do
6
6
 
7
7
  identified_by TranslationAggregateName, as: :name
8
8
 
9
- # `TranslationAggregateName` — a DEDICATED type for the identity
10
- # field, not the shared `TranslationName` every non-identity field
11
- # below uses. Found live, not by inspection: reusing one value-
12
- # object type for BOTH an aggregate's own identity field and a
13
- # sibling non-identity field on the same aggregate broke the
14
- # single-field-VO identity-unwrap path — the stored `id` came back
15
- # as a stringified Hash (`"{:value=>\"Account\"}"`) instead of the
16
- # bare scalar (`"Account"`), so a self-reference to this aggregate
17
- # could never find the record `Declare` had just created. Reusing
18
- # the SAME type across MULTIPLE identity fields (Translation's own
19
- # `TranslationEraName`, for both `from` and `to`) is unaffected —
20
- # confirmed directly, not assumed — so the real trigger is identity
21
- # + non-identity sharing, not reuse itself. A genuine framework bug,
22
- # out of scope to fix here; every other self-hosted construct in
23
- # this language already avoids it by convention (a dedicated type
24
- # per identity field), which this now matches too.
9
+ # A dedicated identity type: sharing `TranslationName` with a non-identity field breaks the
10
+ # single-field identity unwrap, storing the id as a stringified Hash instead of a scalar.
25
11
  attribute :was, TranslationName, optional: true
26
12
 
27
13
  value_object "TranslationAggregateName" do
@@ -47,11 +33,8 @@ Hecks.bluebook "Translation" do
47
33
  attribute :rekeys, list_of(TranslationRekey)
48
34
  attribute :backfills, list_of(TranslationBackfill)
49
35
 
50
- # Field names throughout mirror lib/hecks/bluebook/translation.rb
51
- # EXACTLY (`Struct.new(:from, :to)` etc.) — the real Ruby records
52
- # TranslationJudge dispatches from, not the builder's own local
53
- # parameter names (`move(old_path, to:)`'s `old_path` becomes the
54
- # struct's own `from`).
36
+ # Field names mirror the Struct records in lib/hecks/bluebook/translation.rb, not the
37
+ # builder's parameter names (`move(old_path, to:)` stores `old_path` as `from`).
55
38
  value_object "TranslationRename" do
56
39
  attribute :from, TranslationName
57
40
  attribute :to, TranslationName
@@ -199,9 +182,7 @@ Hecks.bluebook "Translation" do
199
182
  emits "TranslationBackfillAdded"
200
183
  end
201
184
 
202
- # HOW TRANSLATIONAGGREGATE IS SPELLED. These rows live with the concept
203
- # they describe; SyntaxBoot discovers every aggregate-local seed table.
204
- # Contexts represented here: TranslationAggregate.
185
+ # Syntax seed rows for the words this aggregate spells; SyntaxBoot discovers them.
205
186
  value_object "KeywordSeed" do
206
187
  attribute :word, String
207
188
  attribute :context, String, admits: "Syntax::Context"
@@ -7,9 +7,7 @@ Hecks.bluebook "World" do
7
7
  # so the bare reference name would collide with it.
8
8
  belongs_to World, as: :world_ref
9
9
 
10
- # THE WORLD IT IS IN, AND THE VERB IT ANSWERS. One world binds one adapter per
11
- # verb — that is what a binding IS — so the verb names the wiring, and the
12
- # world says whose.
10
+ # One world binds one adapter per verb, so the verb names the wiring and the world says whose.
13
11
  identified_by :world, :verb
14
12
 
15
13
  attribute :world, WiringText
@@ -59,9 +59,8 @@ Hecks.bluebook "World" do
59
59
  emits "WorldDeclared"
60
60
  end
61
61
 
62
- # HOW WORLD IS SPELLED. These rows live with the concept
63
- # they describe; SyntaxBoot discovers every aggregate-local seed table.
64
- # Contexts represented here: File, World.
62
+ # Spelling rows for World keywords; SyntaxBoot discovers every
63
+ # aggregate-local seed table like this one.
65
64
  value_object "KeywordSeed" do
66
65
  attribute :word, String
67
66
  attribute :context, String, admits: "Syntax::Context"
@@ -77,10 +76,9 @@ Hecks.bluebook "World" do
77
76
  member word: "world", context: "File", body: "keywords", inner: "World", opens: "World", fills: ""
78
77
  member word: "realm", context: "World", body: "none", inner: "", opens: "", fills: "realm", calls: "realm_impl"
79
78
  member word: "latest", context: "World", body: "none", inner: "", opens: "", fills: "latest", calls: "latest_impl"
80
- # THE TWO CROSS-CHAPTER DEFAULTS — the same connection string and the
81
- # same persistence adapter every chapter of a project repeats, written
82
- # once. A chapter's own world (`persisted_by ... database`) and its own
83
- # hecksagon bind still win; see docs/implemented/reference/world.md.
79
+ # The connection string and persistence adapter every chapter of a
80
+ # project would otherwise repeat, written once here; a chapter's own
81
+ # world or hecksagon bind still wins.
84
82
  member word: "default_database", context: "World", body: "none", inner: "", opens: "", fills: "default_database", calls: "default_database_impl"
85
83
  member word: "default_adapter", context: "World", body: "none", inner: "", opens: "", fills: "default_adapter", calls: "default_adapter_impl"
86
84
 
data/lib/hecks/literal.rb CHANGED
@@ -1,45 +1,13 @@
1
1
  module Hecks
2
- # The one spelling for a captured Ruby literal on the wire.
3
- #
4
- # Several `to_h` fields hold a value the author wrote in a bluebook —
5
- # `where(status: { eq: "open" })`, `then_set append: { direction: { value:
6
- # "credit" } }`, `dispatch ... with: { number: :source }`, an attribute's
7
- # `default:` — as text, because the export has to stand on its own and a
8
- # String field cannot say "this one was a Symbol". Every such field is the
9
- # same question and now gets the same answer.
10
- #
11
- # It was three answers before, and they disagreed on both axes that matter.
12
- # `Mutation#appended_fields` spelled a Symbol bare and everything else
13
- # `inspect`; `QuerySpecification.render_value` spelled a Symbol with a colon
14
- # and everything else `to_s`. So `:amount` crossed as "amount" in a mutation
15
- # and ":amount" in a saga binding — the same value, two spellings, and each
16
- # reader had to know which field it was looking at. Worse, `to_s`/`inspect`
17
- # on a Hash is Ruby's own rendering, which moved under us: 3.3 writes
18
- # `{:value=>"credit"}` and 3.4 writes `{value: "credit"}` for the identical
19
- # Hash, so the wire format was silently pinned to an interpreter version.
20
- #
21
- # Self-describing is the rule, stated once here rather than inherited from
22
- # whatever `inspect` happens to do: a symbol wears its colon, a string wears
23
- # its quotes, a hash wears `{key: value}` braces, a list wears its brackets,
24
- # and a number, a boolean and nil are bare. `read` is the exact inverse, and
25
- # is the only thing that should ever take one of these strings apart.
26
- # A mutation source that reads the record's own state — `sets :positions,
27
- # append: { knights: state(:knights) }`. A bare Symbol in a mutation
28
- # source always names a command argument (and imports it as one when
29
- # the target's own field carries the same name — `CommandBuilder
30
- # #resolve_append_fields!`); before this there was no way to say "the
31
- # value this field already holds", so a command could not snapshot its
32
- # own record — chess's threefold repetition needs exactly that, a
33
- # position copied off the board every ply. Spelled `state(:name)` on
34
- # the wire, read back by `Literal.read`; classified `kind: "state"` in
35
- # a set's own source (`Behaviour::Mutation#classified_source`).
2
+ # A mutation source that reads the record's own state: `append: { knights: state(:knights) }`.
3
+ # A bare Symbol in a mutation source names a command argument, so this is the only way to say it.
36
4
  StateRef = Struct.new(:name) do
37
5
  def to_s = "state(:#{name})"
38
6
  end
39
7
 
40
- # The `render`/`read` pair the header above describes: `render` turns a
41
- # Ruby value into its self-describing wire spelling, `read` is its exact
42
- # inverse.
8
+ # The one wire spelling for a Ruby literal captured from a bluebook: a symbol wears its
9
+ # colon, a string its quotes, a hash `{key: value}`, a list brackets; the rest are bare.
10
+ # Stated here, not left to `inspect`, whose Hash rendering differs between Ruby 3.3 and 3.4.
43
11
  module Literal
44
12
  module_function
45
13
 
@@ -63,16 +31,9 @@ module Hecks
63
31
  end
64
32
  end
65
33
 
66
- # Read one back. Tolerant of a bare word on purpose: the language stores a
67
- # closed set's members and a few hand-written fields as plain text that was
68
- # never rendered, and those must stay the strings they are.
34
+ # Parses a wire spelling back into a Ruby value; the exact inverse of `render`.
35
+ # A bare word stays a String, since some fields are stored as plain text, never rendered.
69
36
  #
70
- # The exact inverse of `render`'s `case`, above — one ordered guard per
71
- # spelling `render` can produce (plus the bare-word tolerance this
72
- # comment already explains). Each guard is a single self-contained
73
- # `return`; splitting them into named predicates would just rename
74
- # each line without changing what it does, and would separate this
75
- # method from the `render` it is the deliberate mirror of.
76
37
  # @param text [String, #to_s] wire spelling produced by `render`, or a bare word
77
38
  # @return [Object] nil, true, false, Integer, Float, Symbol, StateRef, String,
78
39
  # Hash, or Array — or `text` itself, stripped, when it matches no known spelling
@@ -131,10 +92,7 @@ module Hecks
131
92
  # @return [Array<Object>] the parsed values, each read recursively via `read`
132
93
  def read_array(raw) = split_items(raw[1..-2]).map { |item| read(item) }
133
94
 
134
- # Split on the commas that are actually separators — never one inside a
135
- # quoted string or a nested brace/bracket. Scanned rather than
136
- # `String#split(", ")`, which tore `"a, b"` in half and lost the second
137
- # field of anything nested.
95
+ # Splits on separator commas only, never one inside a quoted string or nested brace/bracket.
138
96
  #
139
97
  # @param body [String] the text between a literal's outer braces or brackets
140
98
  # @return [Array<String>] each item's raw text, stripped, with empty items dropped
@@ -0,0 +1,95 @@
1
+ require_relative "runtime/errors"
2
+
3
+ module Hecks
4
+ # What a spawned bin/hecks_mcp_door may do: every tool by default, or only
5
+ # reader tools against named domains in reader mode — reach, not identity (ADR 0072).
6
+ class McpDoorScope
7
+ ENV_PREFIX = "HECKS_DOOR_".freeze
8
+ TOOLS_VARIABLE = "HECKS_DOOR_TOOLS".freeze
9
+ DOMAINS_VARIABLE = "HECKS_DOOR_DOMAINS".freeze
10
+ READERS = "readers".freeze
11
+ EXIT_STATUS = 2
12
+
13
+ # The tools a reader door serves — each reads a domain or the door's own
14
+ # audit log and writes no domain state. `validate` still boots its
15
+ # domain, so it stays bound to the allowed set like every other tool.
16
+ READER_TOOLS = %w[query events state catalog describe validate domains history follow].freeze
17
+
18
+ # Unrestricted when no HECKS_DOOR_* variable is set.
19
+ def self.from_env(env = ENV)
20
+ problems = setting_problems(env)
21
+ raise ArgumentError, problems.join("\n") unless problems.empty?
22
+ return new(allowed_domains: nil) unless env[TOOLS_VARIABLE]
23
+
24
+ new(allowed_domains: env[DOMAINS_VARIABLE].split(File::PATH_SEPARATOR).reject(&:empty?))
25
+ end
26
+
27
+ # Refuses to stderr, not stdout, which carries the MCP protocol.
28
+ def self.start!(server:, env: ENV, stderr: $stderr)
29
+ from_env(env)
30
+ rescue ArgumentError, Runtime::TypeMismatch => e
31
+ e.message.each_line { |line| stderr.puts("#{server}: refusing to start: #{line.chomp}") }
32
+ exit(EXIT_STATUS)
33
+ end
34
+
35
+ def self.setting_problems(env)
36
+ unknown = env.keys.select { |name| name.start_with?(ENV_PREFIX) } - [TOOLS_VARIABLE, DOMAINS_VARIABLE]
37
+ problems = unknown.map do |name|
38
+ "environment #{name} is not accepted; the door takes only #{TOOLS_VARIABLE} and #{DOMAINS_VARIABLE}"
39
+ end
40
+ problems + mode_problems(env[TOOLS_VARIABLE], env[DOMAINS_VARIABLE])
41
+ end
42
+
43
+ def self.mode_problems(tools, domains)
44
+ if tools.nil?
45
+ domains.nil? ? [] : ["#{DOMAINS_VARIABLE} is set without #{TOOLS_VARIABLE}=#{READERS}; it only narrows a reader door"]
46
+ elsif tools != READERS
47
+ ["#{TOOLS_VARIABLE}=#{tools.inspect} is not accepted; the only value is #{READERS.inspect}"]
48
+ elsif domains.to_s.split(File::PATH_SEPARATOR).none? { |path| !path.empty? }
49
+ ["#{TOOLS_VARIABLE}=#{READERS} needs #{DOMAINS_VARIABLE}, the domains this door may read"]
50
+ else
51
+ []
52
+ end
53
+ end
54
+
55
+ # The named domains, resolved against Storehouse::BOOT_ROOT; nil when unrestricted.
56
+ attr_reader :allowed_domains
57
+
58
+ def initialize(allowed_domains:)
59
+ @allowed_domains = allowed_domains&.map { |path| Storehouse.confine!(path, DOMAINS_VARIABLE) }.freeze
60
+ end
61
+
62
+ def restricted?
63
+ !@allowed_domains.nil?
64
+ end
65
+
66
+ def permits_tool?(name)
67
+ !restricted? || READER_TOOLS.include?(name)
68
+ end
69
+
70
+ def refusal(name)
71
+ { ok: false,
72
+ error: "#{name.inspect} is refused: this door runs in reader mode (#{TOOLS_VARIABLE}=#{READERS}), " \
73
+ "which serves only #{READER_TOOLS.join(', ')}" }
74
+ end
75
+
76
+ # Checked before anything boots, so a path outside the allowed set never reaches Kernel.load.
77
+ def admit_domain!(domain)
78
+ return domain unless restricted?
79
+ return domain if @allowed_domains.include?(Storehouse.confine!(domain, "domain"))
80
+
81
+ raise Runtime::TypeMismatch,
82
+ "domain: #{domain.inspect} is refused: this door runs in reader mode (#{TOOLS_VARIABLE}=#{READERS}) " \
83
+ "and reads only #{DOMAINS_VARIABLE}: #{@allowed_domains.join(', ')}"
84
+ end
85
+
86
+ def notes
87
+ return [] unless restricted?
88
+
89
+ ["Reader mode (#{TOOLS_VARIABLE}=#{READERS}): serves #{READER_TOOLS.join(', ')}; " \
90
+ "refuses dispatch (with dry_run and steps), behaviors and every other tool.",
91
+ "domain: boots only #{DOMAINS_VARIABLE}: #{@allowed_domains.join(', ')}.",
92
+ "Reader mode limits reach and identifies no one; it is not authentication."]
93
+ end
94
+ end
95
+ end
@@ -1,41 +1,8 @@
1
1
  require "socket"
2
2
 
3
3
  module Hecks
4
- # The startup gate for the stdio MCP servers in `bin/` (`hecks_mcp_door`,
5
- # `hecks_query_ir_mcp`): it refuses to run when the process was set up to
6
- # speak to anything but a local parent, and it says on stderr what the
7
- # process does and does not protect.
8
- #
9
- # ## Why a gate and not a comment
10
- #
11
- # Both servers have no authentication. What keeps a stranger away from
12
- # them is that only a process that can spawn them can write to their
13
- # stdin. That holds until someone wraps one in `socat TCP-LISTEN:... EXEC:`
14
- # or an `inetd`-style listener, which hands the process a network socket
15
- # as stdin/stdout without changing a line of the server. The gate turns
16
- # that setup into a refusal at startup instead of an exposure nobody
17
- # noticed.
18
- #
19
- # ## What it checks
20
- #
21
- # - **Arguments** — none, except `--stdio`. A `--port` or `--http` flag is
22
- # refused rather than ignored, so a caller that believes it started a
23
- # network server finds out immediately.
24
- # - **Environment** — no `HECKS_MCP_*` variable except
25
- # `HECKS_MCP_TRANSPORT=stdio`. The servers take no configuration, so an
26
- # unknown option in that namespace fails closed.
27
- # - **Descriptors** — stdin and stdout must not be internet-protocol sockets
28
- # (`AF_INET` or `AF_INET6`). A pipe, a file, a terminal and a Unix-domain socket all pass;
29
- # the last matters because some MCP clients spawn servers over a
30
- # `socketpair`, which is local to the machine.
31
- #
32
- # ## What it does not do
33
- #
34
- # It does not authenticate anyone and it cannot see a proxy that copies
35
- # bytes from a network socket into an ordinary pipe. It closes the
36
- # configurations that announce themselves; the ADR on MCP authentication
37
- # (`docs/decisions/0062-mcp-servers-need-real-authentication-before-any-network-transport.md`)
38
- # covers what a network transport would need.
4
+ # Startup gate for the stdio MCP servers in `bin/`: refuses network sockets, extra flags and
5
+ # unknown `HECKS_MCP_*` variables. It is not authentication (ADR 0062).
39
6
  module McpStdioGuard
40
7
  ACCEPTED_ARGS = %w[--stdio].freeze
41
8
  ENV_PREFIX = "HECKS_MCP_".freeze
@@ -71,12 +38,8 @@ module Hecks
71
38
  (COMMON_NOTES + notes).map { |line| "#{server}: #{line}" }
72
39
  end
73
40
 
74
- # Refuses to continue on a non-stdio setup.
75
- #
76
- # A server calls this before it requires anything heavy, so a misconfigured start
77
- # fails in milliseconds and before any protocol byte is answered. Everything goes
78
- # to `stderr`: stdout carries the MCP protocol, and one stray line there corrupts
79
- # the client's framing.
41
+ # Refuses to continue on a non-stdio setup. Everything goes to `stderr`: stdout
42
+ # carries the MCP protocol, and a stray line there corrupts the client's framing.
80
43
  #
81
44
  # @param server [String] the server's name, used as the line prefix
82
45
  # @param argv [Array<String>] the command-line arguments