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
@@ -1,524 +0,0 @@
1
- module Hecks
2
- module Grammar
3
- # The file surgery under bin/evolve: reading and rewriting the
4
- # aggregate-local KeywordSeed/ArgumentSeed rows as text, so a
5
- # proposed word enters the table exactly as a hand would write it
6
- # and an admitted one loses its ceremony (an absent status reads as
7
- # admitted — the grown-column convention).
8
- #
9
- # Text, not IR, on purpose: the syntax table is source, its comments
10
- # and grouping are part of the declaration, and a rewrite that
11
- # round-tripped it through the IR would flatten both. Everything
12
- # here touches only bare `member ` lines inside Keyword's own body
13
- # (S3, ADR 0025 — no `one_of do ... end` wrapper anymore) and leaves
14
- # every other byte alone.
15
- module Evolve
16
- class Refusal < StandardError; end
17
-
18
- module_function
19
-
20
- # bin/evolve's own `--name value` flag reader. Only consumes the
21
- # next argv element as the value when that element doesn't itself
22
- # look like a flag — otherwise `--foo --bar` would swallow `--bar`
23
- # as `--foo`'s value (and `--bar` would then never be seen at
24
- # all), and a value-less `--foo` at the end of argv would bypass
25
- # whatever default `foo` promised instead of falling back to it.
26
- # @param argv [Array<String>] the command-line argument list to scan
27
- # @param name [String] the flag's name, without its leading `--`
28
- # @param default [String, nil] value to use when the flag is absent or has no value
29
- # @return [String, nil] the flag's value, or `default`
30
- def option(argv, name, default = nil)
31
- index = argv.index("--#{name}")
32
- return default unless index
33
-
34
- value = argv[index + 1]
35
- value.nil? || value.start_with?("-") ? default : value
36
- end
37
-
38
- # The snapshot/restore ceremony bin/evolve wraps every mutating
39
- # command in: read every `paths` file, run the block, and put
40
- # every file back exactly as it was if the block raises partway
41
- # through — `rename`'s keyword-row write followed by its
42
- # argument cascade across several files (`cascade_argument_rename`
43
- # above) included, not only a declared gate failing after the
44
- # block has already returned cleanly (bin/evolve's own `guarded`
45
- # still handles that half on its own, since it depends on running
46
- # the gate specs, not on anything this method knows about). A
47
- # clean return leaves the snapshots unused; the caller decides
48
- # from there whether the tree stands.
49
- #
50
- # @param paths [Array<String>] files to snapshot before running the block
51
- # @yield the mutating work to run, restored from snapshot if it raises
52
- # @return [Object] the block's result
53
- # @raise [StandardError] re-raises whatever the block raised, after restoring
54
- # every snapshotted file
55
- def restore_on_raise(paths)
56
- snapshots = paths.to_h { |path| [path, File.read(path)] }
57
- yield
58
- rescue StandardError
59
- snapshots.each { |path, content| File.write(path, content) }
60
- raise
61
- end
62
-
63
- # Every syntax-table file declaring a `KeywordSeed` or `ArgumentSeed`
64
- # value object.
65
- #
66
- # @return [Array<String>] matching `.bluebook` file paths
67
- def syntax_paths
68
- Dir.glob(File.expand_path("../language/**/*.bluebook", __dir__)).select do |path|
69
- source = File.read(path)
70
- source.include?('value_object "KeywordSeed"') || source.include?('value_object "ArgumentSeed"')
71
- end
72
- end
73
-
74
- # Kept as a narrow compatibility door for callers deliberately doing
75
- # single-file surgery. Normal operation uses `syntax_paths` and discovers
76
- # the owning concept from the row itself.
77
- #
78
- # @return [String, nil] the first syntax-table path, or nil when there are none
79
- def syntax_path = syntax_paths.first
80
-
81
- # Resolves the file(s) a call should search or write.
82
- #
83
- # @param path [String, nil] an explicit single file, or nil for every syntax path
84
- # @return [Array<String>] `[path]` when given, else `syntax_paths`
85
- def paths_for(path) = path ? Array(path) : syntax_paths
86
-
87
- # The Keyword one_of's member rows, parsed leniently off the text —
88
- # enough to know each row's (word, context, status), which is all
89
- # the tool ever asks.
90
- #
91
- # @param path [String, nil] an explicit single file to read, or nil for every
92
- # syntax path
93
- # @return [Array<Hash>] one Hash per member row, with `:word`, `:context`,
94
- # `:status` (`"admitted"` when unspelled), and `:was` (nil unless renamed)
95
- # @raise [Refusal] if `path` is given and declares no `KeywordSeed` value object
96
- def keyword_rows(path = nil)
97
- paths_for(path).flat_map do |candidate|
98
- blocks = seed_blocks(File.read(candidate), "KeywordSeed")
99
- raise Refusal, "source declares no KeywordSeed value object" if path && blocks.empty?
100
-
101
- blocks.flat_map do |block|
102
- block.scan(/^\s*member (.+)$/).map do |(cells)|
103
- row = cells.scan(/(\w+): "((?:[^"\\]|\\.)*)"/).to_h
104
- { word: row["word"], context: row["context"],
105
- status: row.fetch("status", "admitted"), was: row["was"] }
106
- end
107
- end
108
- end
109
- end
110
-
111
- # Declares a new, proposed keyword row in the syntax table that owns
112
- # `context` (or `opens`'s own aggregate, for a `File`-context word).
113
- #
114
- # @param word [String] the keyword's spelling
115
- # @param context [String] the grammar context the word is declared in
116
- # @param body [String] the keyword's body shape; `"none"` by default
117
- # @param inner [String] the keyword's inner shape, if any
118
- # @param opens [String] the aggregate concept a `File`-context word opens
119
- # @param fills [String] the field the keyword fills, if any
120
- # @param path [String, nil] an explicit single file to search/write, or nil to
121
- # search every syntax path
122
- # @return [void]
123
- # @raise [Refusal] if `context`.`word` is already declared, or no syntax table
124
- # owns `context`
125
- def propose(word:, context:, body: "none", inner: "", opens: "", fills: "", path: nil)
126
- if keyword_rows(path).any? do |row|
127
- row[:word] == word && row[:context] == context
128
- end
129
- raise Refusal,
130
- "#{context}.#{word} is already declared — one row per (word, context, form)"
131
- end
132
-
133
- path = owner_path(context: context, word: word, opens: opens, paths: paths_for(path))
134
- source = File.read(path)
135
- block = keyword_blocks(source).find { |candidate| candidate.include?(%(context: "#{context}")) } || keyword_block(source)
136
- indent = block[/^(\s*)member /, 1] || " "
137
- row = %(#{indent}member word: "#{word}", context: "#{context}", body: "#{body}", ) +
138
- %(inner: "#{inner}", opens: "#{opens}", fills: "#{fills}", status: "proposed"\n)
139
-
140
- # At the end of the one_of — grouping by context is a courtesy of
141
- # the hand; a proposed row sits at the bottom until admission,
142
- # when whoever admits it may move it home.
143
- closing = block.rindex(/^\s*end\s*$/)
144
- updated = block[0...closing] + row + block[closing..]
145
- File.write(path, source.sub(block, updated))
146
- end
147
-
148
- # Rewrites a declared keyword row's `status:` cell in place.
149
- #
150
- # @param word [String] the keyword's spelling
151
- # @param context [String] the grammar context the word is declared in
152
- # @param to [String] the new status: `"proposed"`, `"admitted"`, `"deprecated"`,
153
- # or `"retired"`
154
- # @param path [String, nil] an explicit single file to search/write, or nil to
155
- # search every syntax path
156
- # @return [void]
157
- # @raise [Refusal] if `to` is not one of the four stations, or `context`.`word`
158
- # is not declared
159
- def set_status(word:, context:, to:, path: nil)
160
- raise Refusal, "#{to.inspect} is not a station a word's life admits" unless %w[proposed admitted deprecated
161
- retired].include?(to)
162
-
163
- path = path_holding_keyword(word, context, paths_for(path))
164
- source = File.read(path)
165
- block = keyword_blocks(source).find { |candidate| candidate.lines.any? { |line| member_row?(line, word, context) } }
166
- rows = block.lines.select { |line| member_row?(line, word, context) }
167
- raise Refusal, "#{context}.#{word} is not declared" if rows.empty?
168
-
169
- updated = block.lines.map do |line|
170
- next line unless member_row?(line, word, context)
171
-
172
- stripped = line.sub(/,\s*status: "[^"]*"/, "")
173
- # Admitted is the default and stays unspelled — only a word
174
- # entering or leaving the language carries its status.
175
- to == "admitted" ? stripped : stripped.sub(/\n\z/, %(, status: "#{to}"\n))
176
- end.join
177
-
178
- File.write(path, source.sub(block, updated))
179
- end
180
-
181
- # A rename respells the row's word and holds the old spelling in
182
- # `was:` — one hop only. Renaming an already-renamed word refuses
183
- # until the language grows real eras for its own words; renaming
184
- # onto a spelling the context already declares refuses too. The
185
- # word's Argument rows follow it — row-aware, not a blind
186
- # substitution (see `cascade_argument_rename`).
187
- #
188
- # @param word [String] the keyword's current spelling
189
- # @param context [String] the grammar context the word is declared in
190
- # @param to [String] the keyword's new spelling
191
- # @param path [String, nil] an explicit single file to search/write, or nil to
192
- # search every syntax path
193
- # @return [void]
194
- # @raise [Refusal] if `context`.`word` is not declared, was already renamed once,
195
- # or `to` is already declared in `context`
196
- def rename(word:, context:, to:, path: nil)
197
- row = keyword_rows(path).find { |r| r[:word] == word && r[:context] == context }
198
- raise Refusal, "#{context}.#{word} is not declared" unless row
199
- raise Refusal, "#{context}.#{word} was already #{row[:was]} — one rename hop, then eras" if row[:was]
200
- if keyword_rows(path).any? do |r|
201
- r[:word] == to && r[:context] == context
202
- end
203
- raise Refusal,
204
- "#{context}.#{to} is already declared — a rename cannot land on a living word"
205
- end
206
-
207
- paths = paths_for(path)
208
- path = path_holding_keyword(word, context, paths)
209
- source = File.read(path)
210
- block = keyword_blocks(source).find { |candidate| candidate.lines.any? { |line| member_row?(line, word, context) } }
211
- updated = block.lines.map do |line|
212
- next line unless member_row?(line, word, context)
213
-
214
- line.sub(%(word: "#{word}"), %(word: "#{to}"))
215
- .sub(/\n\z/, %(, was: "#{word}"\n))
216
- end.join
217
- source = source.sub(block, updated)
218
- File.write(path, source)
219
-
220
- cascade_argument_rename(keyword: word, context: context, to: to, path: paths)
221
- end
222
-
223
- # Tells whether `line` is a KeywordSeed member row for `(word, context)`.
224
- #
225
- # @param line [String] one raw source line
226
- # @param word [String] the keyword's spelling
227
- # @param context [String] the grammar context
228
- # @return [Boolean]
229
- def member_row?(line, word, context)
230
- line =~ /^\s*member / && line.include?(%(word: "#{word}")) && line.include?(%(context: "#{context}"))
231
- end
232
-
233
- # Finds which syntax-table path declares a keyword row.
234
- #
235
- # @param word [String] the keyword's spelling
236
- # @param context [String] the grammar context
237
- # @param paths [Array<String>] candidate syntax-table paths to search
238
- # @return [String] the path whose KeywordSeed declares `(word, context)`
239
- # @raise [Refusal] if no path in `paths` declares that row
240
- def path_holding_keyword(word, context, paths = syntax_paths)
241
- paths.find do |candidate|
242
- keyword_blocks(File.read(candidate)).any? { |block| block.lines.any? { |line| member_row?(line, word, context) } }
243
- end || raise(Refusal, "#{context}.#{word} is not declared")
244
- end
245
-
246
- # Finds which syntax-table path declares an argument row.
247
- #
248
- # @param keyword [String] the argument's owning keyword
249
- # @param context [String] the grammar context
250
- # @param at [String] the argument's positional slot, `""` for a named-only argument
251
- # @param named [String] the argument's keyword name, `""` for a positional-only argument
252
- # @param paths [Array<String>] candidate syntax-table paths to search
253
- # @return [String] the path whose ArgumentSeed declares this row
254
- # @raise [Refusal] if no path in `paths` declares that row
255
- def path_holding_argument(keyword, context, at, named, paths = syntax_paths)
256
- paths.find do |candidate|
257
- argument_blocks(File.read(candidate)).any? do |block|
258
- block.lines.any? do |line|
259
- argument_row?(line, keyword, context, at, named)
260
- end
261
- end
262
- end || raise(Refusal, "#{context}.#{keyword}'s argument at #{at.inspect}/named #{named.inspect} is not declared")
263
- end
264
-
265
- # A new row belongs wherever that context's existing rows live. File is
266
- # intentionally wider than one aggregate; for a new entry point, `opens`
267
- # identifies the aggregate concept whose file should own it.
268
- #
269
- # @param context [String] the grammar context a new row is being added to
270
- # @param word [String] the word being added, used only in the refusal message
271
- # @param opens [String] the aggregate concept a `File`-context word opens
272
- # @param paths [Array<String>] candidate syntax-table paths to search
273
- # @return [String] the path that should own the new row
274
- # @raise [Refusal] if no candidate path declares an existing row for `context`
275
- # (and, for a `File` context with `opens` given, no path declares that aggregate)
276
- def owner_path(context:, word:, opens: "", paths: syntax_paths)
277
- if context == "File" && !opens.to_s.empty?
278
- aggregate_path = paths.find { |candidate| File.read(candidate).match?(/^\s*aggregate "#{Regexp.escape(opens)}" do$/) }
279
- return aggregate_path if aggregate_path
280
- end
281
-
282
- paths.find do |candidate|
283
- source = File.read(candidate)
284
- %w[KeywordSeed ArgumentSeed].any? do |seed|
285
- seed_blocks(source, seed).any? { |block| block.include?(%(context: "#{context}")) }
286
- end
287
- end || raise(Refusal, "no aggregate-local syntax table owns context #{context.inspect} for #{word}")
288
- end
289
-
290
- # Every `value_object "<name>"` block's full source text, from its
291
- # opener to its closing `end`.
292
- #
293
- # @param source [String] a `.bluebook` file's source text
294
- # @param name [String] the value object's name, such as `"KeywordSeed"`
295
- # @return [Array<String>] each matching block's raw source, including the
296
- # opener and closing `end` lines
297
- def seed_blocks(source, name)
298
- opener = /^([ \t]*)value_object "#{Regexp.escape(name)}" do$/
299
- source.to_enum(:scan, opener).map do
300
- match = Regexp.last_match
301
- start = match.begin(0)
302
- indent = match[1]
303
- closing = source.index(/^#{Regexp.escape(indent)}end\s*$/, match.end(0))
304
- closing = source.index(/\n/, closing) + 1
305
- source[start...closing]
306
- end
307
- end
308
-
309
- # The first `value_object "<name>"` block's source text.
310
- #
311
- # @param source [String] a `.bluebook` file's source text
312
- # @param name [String] the value object's name, such as `"KeywordSeed"`
313
- # @param required [Boolean] whether a missing block should raise instead of
314
- # returning nil
315
- # @return [String, nil] the block's raw source, or nil when absent and not
316
- # `required`
317
- # @raise [Refusal] if `required` and `source` declares no such value object
318
- def seed_block(source, name, required: false)
319
- block = seed_blocks(source, name).first
320
- unless block
321
- raise Refusal, "source declares no #{name} value object" if required
322
-
323
- return
324
- end
325
- block
326
- end
327
-
328
- # From `value_object "KeywordSeed"` to its own closing `end` — `member`
329
- # rows sit bare now (S3, ADR 0025 — the `one_of do ... end` wrapper
330
- # is gone), so the first bare `end` line after the opener already
331
- # is the value object's own, the same fact the original one_of-
332
- # nested version of this method leaned on (nothing else nested
333
- # inside it either, before or after).
334
- # @param source [String] a `.bluebook` file's source text
335
- # @return [Array<String>] each KeywordSeed block's raw source
336
- def keyword_blocks(source) = seed_blocks(source, "KeywordSeed")
337
-
338
- # The first KeywordSeed block's source text.
339
- #
340
- # @param source [String] a `.bluebook` file's source text
341
- # @return [String] the first KeywordSeed block's raw source
342
- # @raise [Refusal] if `source` declares no KeywordSeed value object
343
- def keyword_block(source) = seed_block(source, "KeywordSeed", required: true)
344
-
345
- # ── the Argument rows — a word's own arguments, at last with tooling
346
- # of their own rather than the rename-only cascade above. A word may
347
- # carry several argument rows (one per position, one per named
348
- # kwarg), so identity here is the full (keyword, context, at, named)
349
- # tuple, not the two-field key a Keyword row answers to.
350
-
351
- # The ArgumentSeed's member rows, parsed leniently off the text.
352
- #
353
- # @param path [String, nil] an explicit single file to read, or nil for every
354
- # syntax path
355
- # @return [Array<Hash>] one Hash per member row, with `:keyword`, `:context`,
356
- # `:at`, `:named`, `:kind`, `:required`, `:fills`, and `:status`
357
- # (`"admitted"` when unspelled)
358
- # @raise [Refusal] if `path` is given and declares no `ArgumentSeed` value object
359
- def argument_rows(path = nil)
360
- paths_for(path).flat_map do |candidate|
361
- blocks = seed_blocks(File.read(candidate), "ArgumentSeed")
362
- raise Refusal, "source declares no ArgumentSeed value object" if path && blocks.empty?
363
-
364
- blocks.flat_map do |block|
365
- block.scan(/^\s*member (.+)$/).map do |(cells)|
366
- row = cells.scan(/(\w+): "((?:[^"\\]|\\.)*)"/).to_h
367
- { keyword: row["keyword"], context: row["context"], at: row["at"].to_s,
368
- named: row["named"].to_s, kind: row["kind"], required: row["required"],
369
- fills: row["fills"].to_s, status: row.fetch("status", "admitted") }
370
- end
371
- end
372
- end
373
- end
374
-
375
- # `pairs_shape` — for a `pairs` argument that fills one field with a
376
- # whole key/value list rather than naming a field per pair (the
377
- # shape `Handler.dispatch`'s own `with:` already carries). Without
378
- # it, `spec/syntax_conformance_spec.rb` reads a pairs argument
379
- # naming a single field as a row that "names a single field, which
380
- # it cannot fill" — correctly, since the two shapes are genuinely
381
- # different and only one of them can be checked the same way.
382
- # @param keyword [String] the argument's owning keyword
383
- # @param context [String] the grammar context
384
- # @param kind [String] the argument's value kind
385
- # @param required [String] `"true"` or `"false"`, as text like every other cell
386
- # @param at [String] the argument's positional slot, `""` for a named-only argument
387
- # @param named [String] the argument's keyword name, `""` for a positional-only argument
388
- # @param fills [String] the field the argument fills, if any
389
- # @param pairs_shape [String, nil] the shape a `pairs` argument's key/value list
390
- # fills, or nil when this argument is not a `pairs` argument
391
- # @param path [String, nil] an explicit single file to search/write, or nil to
392
- # search every syntax path
393
- # @return [void]
394
- # @raise [Refusal] if this (keyword, context, at, named) row is already declared,
395
- # or no syntax table owns `context`
396
- def propose_argument(keyword:, context:, kind:, required: "false", at: "", named: "", fills: "",
397
- pairs_shape: nil, path: nil)
398
- if argument_rows(path).any? { |r| argument_identity(r) == [keyword, context, at, named] }
399
- raise Refusal, "#{context}.#{keyword}'s argument at #{at.inspect}/named #{named.inspect} is " \
400
- "already declared — one row per (keyword, context, at, named)"
401
- end
402
-
403
- path = owner_path(context: context, word: keyword, paths: paths_for(path))
404
- source = File.read(path)
405
- block = argument_blocks(source).find do |candidate|
406
- candidate.include?(%(context: "#{context}"))
407
- end || argument_block(source)
408
- indent = block[/^(\s*)member /, 1] || " "
409
- shape = pairs_shape.to_s.empty? ? "" : %(pairs_shape: "#{pairs_shape}", )
410
- row = %(#{indent}member keyword: "#{keyword}", context: "#{context}", at: "#{at}", ) +
411
- %(named: "#{named}", kind: "#{kind}", required: "#{required}", fills: "#{fills}", ) +
412
- shape + %(status: "proposed"\n)
413
-
414
- closing = block.rindex(/^\s*end\s*$/)
415
- updated = block[0...closing] + row + block[closing..]
416
- File.write(path, source.sub(block, updated))
417
- end
418
-
419
- # Rewrites a declared argument row's `status:` cell in place.
420
- #
421
- # @param keyword [String] the argument's owning keyword
422
- # @param context [String] the grammar context
423
- # @param to [String] the new status: `"proposed"`, `"admitted"`, `"deprecated"`,
424
- # or `"retired"`
425
- # @param at [String] the argument's positional slot, `""` for a named-only argument
426
- # @param named [String] the argument's keyword name, `""` for a positional-only argument
427
- # @param path [String, nil] an explicit single file to search/write, or nil to
428
- # search every syntax path
429
- # @return [void]
430
- # @raise [Refusal] if `to` is not one of the four stations, or the row is not declared
431
- def set_argument_status(keyword:, context:, to:, at: "", named: "", path: nil)
432
- raise Refusal, "#{to.inspect} is not a station an argument's life admits" unless %w[proposed admitted deprecated
433
- retired].include?(to)
434
-
435
- path = path_holding_argument(keyword, context, at, named, paths_for(path))
436
- source = File.read(path)
437
- block = argument_blocks(source).find do |candidate|
438
- candidate.lines.any? do |line|
439
- argument_row?(line, keyword, context, at, named)
440
- end
441
- end
442
- rows = block.lines.select { |line| argument_row?(line, keyword, context, at, named) }
443
- if rows.empty?
444
- raise Refusal, "#{context}.#{keyword}'s argument at #{at.inspect}/named #{named.inspect} is not " \
445
- "declared"
446
- end
447
-
448
- updated = block.lines.map do |line|
449
- next line unless argument_row?(line, keyword, context, at, named)
450
-
451
- stripped = line.sub(/,\s*status: "[^"]*"/, "")
452
- to == "admitted" ? stripped : stripped.sub(/\n\z/, %(, status: "#{to}"\n))
453
- end.join
454
-
455
- File.write(path, source.sub(block, updated))
456
- end
457
-
458
- # Tells whether `line` is an ArgumentSeed member row for this
459
- # (keyword, context, at, named) tuple.
460
- #
461
- # @param line [String] one raw source line
462
- # @param keyword [String] the argument's owning keyword
463
- # @param context [String] the grammar context
464
- # @param at [String] the argument's positional slot
465
- # @param named [String] the argument's keyword name
466
- # @return [Boolean]
467
- def argument_row?(line, keyword, context, at, named)
468
- line =~ /^\s*member / &&
469
- line.include?(%(keyword: "#{keyword}")) && line.include?(%(context: "#{context}")) &&
470
- line.include?(%(at: "#{at}")) && line.include?(%(named: "#{named}"))
471
- end
472
-
473
- # The identity tuple an argument row is keyed by.
474
- #
475
- # @param row [Hash] an argument row, as `argument_rows` returns one
476
- # @return [Array(String, String, String, String)] the row's `(keyword, context,
477
- # at, named)` identity tuple
478
- def argument_identity(row) = [row[:keyword], row[:context], row[:at], row[:named]]
479
-
480
- # The rename cascade, row-aware — only the rows that actually belong
481
- # to the renamed word, spelling updated in place, rather than a
482
- # blind `gsub` on every `keyword: "word",` substring in the file
483
- # (which a coincidentally-matching row elsewhere could have
484
- # corrupted, and which read nothing before writing).
485
- # @param keyword [String] the keyword whose argument rows follow its rename
486
- # @param context [String] the grammar context
487
- # @param to [String] the keyword's new spelling
488
- # @param path [String, nil] an explicit single file to search/write, or nil to
489
- # search every syntax path
490
- # @return [void]
491
- def cascade_argument_rename(keyword:, context:, to:, path: nil)
492
- paths_for(path).each do |candidate|
493
- source = File.read(candidate)
494
- original = source
495
- argument_blocks(source).each do |block|
496
- updated = block.lines.map do |line|
497
- next line unless line =~ /^\s*member / &&
498
- line.include?(%(keyword: "#{keyword}")) && line.include?(%(context: "#{context}"))
499
-
500
- line.sub(%(keyword: "#{keyword}"), %(keyword: "#{to}"))
501
- end.join
502
- source = source.sub(block, updated) if updated != block
503
- end
504
- File.write(candidate, source) if source != original
505
- end
506
- end
507
-
508
- # From `value_object "ArgumentSeed"` to its own closing `end` — see
509
- # `keyword_block`'s own comment for why the first bare `end` after
510
- # the opener is already the right one, now that `member` rows sit
511
- # bare (S3, ADR 0025).
512
- # @param source [String] a `.bluebook` file's source text
513
- # @return [Array<String>] each ArgumentSeed block's raw source
514
- def argument_blocks(source) = seed_blocks(source, "ArgumentSeed")
515
-
516
- # The first ArgumentSeed block's source text.
517
- #
518
- # @param source [String] a `.bluebook` file's source text
519
- # @return [String] the first ArgumentSeed block's raw source
520
- # @raise [Refusal] if `source` declares no ArgumentSeed value object
521
- def argument_block(source) = seed_block(source, "ArgumentSeed", required: true)
522
- end
523
- end
524
- end