hecks 2.8.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 (404) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +4 -6
  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 +17 -98
  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 +5 -48
  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 +31 -141
  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 +14 -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 +25 -31
  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 +16 -69
  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 -194
  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 -111
  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 +53 -267
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +43 -305
  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 +6 -21
  114. data/lib/hecks/bluebook/model_check.rb +100 -597
  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 +4 -23
  128. data/lib/hecks/cli/document.rb +3 -21
  129. data/lib/hecks/cli/ir.rb +3 -5
  130. data/lib/hecks/cli/mcp.rb +2 -3
  131. data/lib/hecks/cli/mcp_door.rb +19 -114
  132. data/lib/hecks/cli/model_check.rb +18 -85
  133. data/lib/hecks/cli/project_cli.rb +7 -40
  134. data/lib/hecks/cli/project_diagrams.rb +0 -7
  135. data/lib/hecks/cli/run.rb +12 -58
  136. data/lib/hecks/cli/smoke_test.rb +2 -16
  137. data/lib/hecks/cli/stores.rb +1 -2
  138. data/lib/hecks/cli.rb +18 -24
  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 +82 -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 +9 -90
  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 +61 -64
  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 +6 -25
  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 +18 -124
  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 +150 -678
  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 +2 -15
  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 +42 -134
  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 +3 -6
  336. data/lib/hecks/release/runner/commands.rb +2 -4
  337. data/lib/hecks/release/runner/gem_publisher.rb +2 -3
  338. data/lib/hecks/release/runner/npm_publisher.rb +6 -20
  339. data/lib/hecks/release/runner/preflight.rb +4 -19
  340. data/lib/hecks/release/runner/tagger.rb +2 -9
  341. data/lib/hecks/release/runner.rb +3 -32
  342. data/lib/hecks/rendering.rb +4 -26
  343. data/lib/hecks/router/namespace_installer.rb +12 -36
  344. data/lib/hecks/router.rb +4 -8
  345. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  346. data/lib/hecks/runtime/boot_gates.rb +3 -18
  347. data/lib/hecks/runtime/caller.rb +19 -59
  348. data/lib/hecks/runtime/capability_graph.rb +5 -21
  349. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  350. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  351. data/lib/hecks/runtime/command_interpreter.rb +124 -306
  352. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  353. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  354. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  355. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  356. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  357. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  358. data/lib/hecks/runtime/dispatcher.rb +82 -241
  359. data/lib/hecks/runtime/entity_element.rb +40 -462
  360. data/lib/hecks/runtime/entity_interpreter.rb +64 -219
  361. data/lib/hecks/runtime/errors.rb +32 -115
  362. data/lib/hecks/runtime/event.rb +3 -25
  363. data/lib/hecks/runtime/identity.rb +18 -154
  364. data/lib/hecks/runtime/instance.rb +18 -150
  365. data/lib/hecks/runtime/interpreting.rb +11 -70
  366. data/lib/hecks/runtime/invocation.rb +28 -164
  367. data/lib/hecks/runtime/loader.rb +48 -186
  368. data/lib/hecks/runtime/outbox.rb +38 -261
  369. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  370. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  371. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  372. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  373. data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
  374. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  375. data/lib/hecks/runtime/reference_hop.rb +8 -81
  376. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  377. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  378. data/lib/hecks/runtime/registry/verification.rb +52 -225
  379. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  380. data/lib/hecks/runtime/registry.rb +99 -244
  381. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  382. data/lib/hecks/runtime/routing.rb +4 -15
  383. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  384. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  385. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  386. data/lib/hecks/runtime/tenant_check.rb +6 -68
  387. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  388. data/lib/hecks/runtime/value/admission.rb +16 -67
  389. data/lib/hecks/runtime/value/coercion.rb +155 -641
  390. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  391. data/lib/hecks/runtime/value.rb +12 -84
  392. data/lib/hecks/runtime.rb +17 -65
  393. data/lib/hecks/storehouse.rb +141 -568
  394. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  395. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  396. data/lib/hecks/vendoring/git_environment.rb +2 -8
  397. data/lib/hecks/vendoring/git_source.rb +4 -10
  398. data/lib/hecks/vendoring.rb +7 -46
  399. data/lib/hecks/version.rb +3 -14
  400. data/lib/hecks.rb +26 -81
  401. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  402. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  403. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  404. metadata +3 -2
@@ -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
@@ -1,42 +1,8 @@
1
1
  require_relative "runtime/errors"
2
2
 
3
3
  module Hecks
4
- # What one spawned `bin/hecks_mcp_door` may do: every tool against any domain under
5
- # `Storehouse::BOOT_ROOT` (the default), or, in reader mode, only the reader tools
6
- # against the domains its spawner named. ADR 0072, decision 2.
7
- #
8
- # ## Reach, not identity
9
- #
10
- # This is a capability allowlist, not authentication. It identifies no one and adds no
11
- # secret: whoever can spawn the door chooses its mode, and whoever can write to its stdin
12
- # gets whatever that mode allows. What it buys is a smaller surface per agent. A reader
13
- # door cannot dispatch, cannot run `.behaviors` files, and cannot `Kernel.load` Ruby from
14
- # a domain its spawner did not name.
15
- #
16
- # ## Why `HECKS_DOOR_*` and not `HECKS_MCP_*`
17
- #
18
- # `McpStdioGuard` refuses every `HECKS_MCP_*` variable except
19
- # `HECKS_MCP_TRANSPORT=stdio`, and every argument except `--stdio`, so that no option can
20
- # ask a server for another transport. Those refusals belong to ADR 0062. The settings
21
- # here live under their own prefix so the guard stays exactly as that ADR describes it,
22
- # and an unknown `HECKS_DOOR_*` name is refused here, the same fail-closed way.
23
- #
24
- # ## The settings
25
- #
26
- # - `HECKS_DOOR_TOOLS=readers` turns reader mode on. Unset means every tool, as before.
27
- # Any other value refuses to start.
28
- # - `HECKS_DOOR_DOMAINS` lists the domains a reader door may name, separated by
29
- # `File::PATH_SEPARATOR` (`:` on Unix), each resolved against `Storehouse::BOOT_ROOT`.
30
- # Required in reader mode and refused without it.
31
- #
32
- # ## The allowed set is named, not loaded
33
- #
34
- # The door boots a domain afresh on every call and keeps nothing loaded between calls,
35
- # so there is no set of already-loaded domains to check against. The allowed set is the
36
- # list the spawner named, resolved to absolute paths at startup. A call's `domain:` is
37
- # resolved the same way and compared before anything boots, so a path outside the set
38
- # never reaches `Kernel.load`. The comparison is on resolved path text: a symlink under a
39
- # named path is followed when that domain boots, as it is for every domain today.
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).
40
6
  class McpDoorScope
41
7
  ENV_PREFIX = "HECKS_DOOR_".freeze
42
8
  TOOLS_VARIABLE = "HECKS_DOOR_TOOLS".freeze
@@ -44,21 +10,12 @@ module Hecks
44
10
  READERS = "readers".freeze
45
11
  EXIT_STATUS = 2
46
12
 
47
- # The tools a reader door serves. Each reads a domain or the door's own audit log and
48
- # writes no domain state. `query` and `state` still append their call to that audit
49
- # log. `validate` boots the domain it is given, so it is held to the allowed set like
50
- # every other domain-scoped tool. `domains` lists directories and loads nothing.
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.
51
16
  READER_TOOLS = %w[query events state catalog describe validate domains history follow].freeze
52
17
 
53
- # Reads the door's mode from its environment.
54
- #
55
- # @param env [Hash{String => String}] the process environment
56
- # @return [McpDoorScope] an unrestricted scope when no `HECKS_DOOR_*` variable is set;
57
- # otherwise a reader scope over the named domains
58
- # @raise [ArgumentError] with every problem found, one per line, when the settings are
59
- # not a mode this door knows
60
- # @raise [Runtime::TypeMismatch] if a named domain resolves outside
61
- # `Storehouse::BOOT_ROOT`
18
+ # Unrestricted when no HECKS_DOOR_* variable is set.
62
19
  def self.from_env(env = ENV)
63
20
  problems = setting_problems(env)
64
21
  raise ArgumentError, problems.join("\n") unless problems.empty?
@@ -67,15 +24,7 @@ module Hecks
67
24
  new(allowed_domains: env[DOMAINS_VARIABLE].split(File::PATH_SEPARATOR).reject(&:empty?))
68
25
  end
69
26
 
70
- # Reads the door's mode, or refuses to start on settings it does not know.
71
- #
72
- # Everything goes to `stderr`, because stdout carries the MCP protocol.
73
- #
74
- # @param server [String] the server's name, used as the line prefix
75
- # @param env [Hash{String => String}] the process environment
76
- # @param stderr [IO] where a refusal is written
77
- # @return [McpDoorScope] the door's scope
78
- # @raise [SystemExit] with status `EXIT_STATUS` when `from_env` refuses the settings
27
+ # Refuses to stderr, not stdout, which carries the MCP protocol.
79
28
  def self.start!(server:, env: ENV, stderr: $stderr)
80
29
  from_env(env)
81
30
  rescue ArgumentError, Runtime::TypeMismatch => e
@@ -83,7 +32,6 @@ module Hecks
83
32
  exit(EXIT_STATUS)
84
33
  end
85
34
 
86
- # @api private
87
35
  def self.setting_problems(env)
88
36
  unknown = env.keys.select { |name| name.start_with?(ENV_PREFIX) } - [TOOLS_VARIABLE, DOMAINS_VARIABLE]
89
37
  problems = unknown.map do |name|
@@ -92,7 +40,6 @@ module Hecks
92
40
  problems + mode_problems(env[TOOLS_VARIABLE], env[DOMAINS_VARIABLE])
93
41
  end
94
42
 
95
- # @api private
96
43
  def self.mode_problems(tools, domains)
97
44
  if tools.nil?
98
45
  domains.nil? ? [] : ["#{DOMAINS_VARIABLE} is set without #{TOOLS_VARIABLE}=#{READERS}; it only narrows a reader door"]
@@ -105,52 +52,28 @@ module Hecks
105
52
  end
106
53
  end
107
54
 
108
- # The named domains, resolved against `Storehouse::BOOT_ROOT`; nil when unrestricted.
109
- #
110
- # @return [Array<String>, nil]
55
+ # The named domains, resolved against Storehouse::BOOT_ROOT; nil when unrestricted.
111
56
  attr_reader :allowed_domains
112
57
 
113
- # @param allowed_domains [Array<String>, nil] the domains a reader door may name, as
114
- # given; nil for an unrestricted door
115
- # @raise [Runtime::TypeMismatch] if a named domain resolves outside `Storehouse::BOOT_ROOT`
116
58
  def initialize(allowed_domains:)
117
59
  @allowed_domains = allowed_domains&.map { |path| Storehouse.confine!(path, DOMAINS_VARIABLE) }.freeze
118
60
  end
119
61
 
120
- # Tells whether this door runs in reader mode.
121
- #
122
- # @return [Boolean] true when the spawner set `HECKS_DOOR_TOOLS=readers`
123
62
  def restricted?
124
63
  !@allowed_domains.nil?
125
64
  end
126
65
 
127
- # Tells whether this door serves a tool.
128
- #
129
- # @param name [String] the tool name from a `tools/call` request
130
- # @return [Boolean] true for every name on an unrestricted door; on a reader door, true
131
- # only for `READER_TOOLS`
132
66
  def permits_tool?(name)
133
67
  !restricted? || READER_TOOLS.include?(name)
134
68
  end
135
69
 
136
- # Builds the answer to a call for a tool this door does not serve.
137
- #
138
- # @param name [String] the tool name from a `tools/call` request
139
- # @return [Hash{Symbol => Object}] `{ok: false, error:}`, the error naming the mode and
140
- # the tools it serves
141
70
  def refusal(name)
142
71
  { ok: false,
143
72
  error: "#{name.inspect} is refused: this door runs in reader mode (#{TOOLS_VARIABLE}=#{READERS}), " \
144
73
  "which serves only #{READER_TOOLS.join(', ')}" }
145
74
  end
146
75
 
147
- # Checks a call's `domain:` against the allowed set, before anything boots.
148
- #
149
- # @param domain [String, nil] the caller's `domain:` argument
150
- # @return [String, nil] `domain` unchanged, so an unrestricted door boots exactly what
151
- # it did before
152
- # @raise [Runtime::TypeMismatch] on a reader door, if `domain` resolves outside
153
- # `Storehouse::BOOT_ROOT` or to a path its spawner did not name
76
+ # Checked before anything boots, so a path outside the allowed set never reaches Kernel.load.
154
77
  def admit_domain!(domain)
155
78
  return domain unless restricted?
156
79
  return domain if @allowed_domains.include?(Storehouse.confine!(domain, "domain"))
@@ -160,10 +83,6 @@ module Hecks
160
83
  "and reads only #{DOMAINS_VARIABLE}: #{@allowed_domains.join(', ')}"
161
84
  end
162
85
 
163
- # The startup warning lines that describe this door's mode.
164
- #
165
- # @return [Array<String>] empty when unrestricted; on a reader door, what it serves,
166
- # what it refuses, the domains it reads, and that it identifies no one
167
86
  def notes
168
87
  return [] unless restricted?
169
88
 
@@ -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