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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a91dfe0fb86ceb9bb96ecc6b08da680213fed526f49f922f1a2f66d9cb89c80e
4
- data.tar.gz: 22fc90a2e361f45e27796248c22391997281e62fbfeec01b25ba35378e6e5681
3
+ metadata.gz: 6f6baf48801bf4f341c3a39a05215c2a0ce3f345b22068f84d0e04c91c3c76f6
4
+ data.tar.gz: 6eda1623f06f2607400ff7a2b13c10775ed60416c91f36f7d4a36bdb9d6adf39
5
5
  SHA512:
6
- metadata.gz: 824fd1d23971f6dacfd3c3ea18a35f5d0d61e7f07e2056b789d7be7d105a7bc47d13a61e0391d7aeb46d20bcfe8400e307bf05728ed10f9e4c69b46ac7d0df45
7
- data.tar.gz: 3f6e4679c8187f212f913de1d17944c3bac2ed0e4385bcff3bcd259e1b6ccb49d6c8270c7f84885fcc3f308e9ca7def653341050c649d1a048b8a91228ee65e7
6
+ metadata.gz: c8d8670a30de7d7318a84cf6a25f78b394c6974fb42f61697fbe20d0bc5642455740c644fd055cfe7f347d0bed254ab93fd42ce596fc444af6de8bcde86e7291
7
+ data.tar.gz: dc3b18d73758d235d369c0158729b8186e8d6a6d8c2c5040d26f772876b43d8ff8eb29a784e166585af1d890bf5620de0fa456934bf6568ba96d1318858e9079
data/exe/hecks ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # The `hecks` command the gem installs: run, read, check and serve a domain.
4
+ # Routes to `Hecks::CLI` (lib/hecks/cli.rb); run with no arguments to list subcommands.
5
+
6
+ # The gem's own `lib` goes first, as for every `bin/` script, so this also
7
+ # runs straight from a checkout.
8
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
9
+ require "hecks/cli"
10
+
11
+ exit Hecks::CLI.start(ARGV)
@@ -6,31 +6,8 @@ require_relative "../../ports/agent"
6
6
 
7
7
  module Hecks
8
8
  module Adapters
9
- # **The real `agent` fulfillment** — shells out to the `claude` CLI
10
- # itself, `claude -p --output-format json`, one process per call.
11
- # `Ports::Agent`'s own scripted double (`spec/fixtures/scripted_
12
- # agent.{adapter,rb}`) is the deterministic sibling every spec binds
13
- # instead, the same relationship `SecureRandomIdentity` already has
14
- # to `SequentialIdentity`.
15
- #
16
- # **This file owns transport only** — spawning the process, unwrapping
17
- # the CLI's own JSON envelope (`{"result": "..."}`) down to the
18
- # model's raw text, and parsing that text as JSON. It hands back a
19
- # plain Hash. Whether that Hash has the keys a caller asked for, and
20
- # whether its values are within the closed vocabularies this port
21
- # recognizes (a critique's `kind`, a proposal's `verb` pattern) is
22
- # `Ports::Agent::Answers`' job, not this file's — see `ports/agent.rb`'s
23
- # own header for why that split is load-bearing.
24
- #
25
- # `Open3.capture2` with an explicit argv array, never a shell string
26
- # — the same shape every other subprocess call in this repo already
27
- # uses (`bin/rust_conformance`), and the reason `claude` being
28
- # shell-aliased to `claude --dangerously-skip-permissions` in a
29
- # human's own terminal never reaches this code at all: an argv spawn
30
- # invokes the real binary directly, no shell, no alias.
31
- #
32
- # `--allowedTools ""` — this adapter asks a question and reads back
33
- # text; it never wants the model reaching for a tool mid-answer.
9
+ # Real `agent` fulfillment: one `claude -p --output-format json` process per call.
10
+ # Transport only; shape and vocabulary checks belong to `Ports::Agent::Answers`.
34
11
  module ClaudeCode
35
12
  TIMEOUT_SECONDS = 120
36
13
 
@@ -40,22 +17,6 @@ module Hecks
40
17
 
41
18
  module_function
42
19
 
43
- # Asks the model for the next best interview question, as a raw parsed-JSON reply.
44
- #
45
- # **The next best question**. `state` is whatever
46
- # `Interview::Session#declaration`/`#gaps` produced — passed
47
- # through as JSON, not reformatted, so this adapter never
48
- # re-derives what the session already knows.
49
- #
50
- # @param state [Hash] the interview's whole current picture (declaration plus gaps),
51
- # JSON-able
52
- # @param asked [Array<Object>] JSON-able record of the questions already asked
53
- # @return [Object] the parsed JSON reply, expected to be a Hash holding a `"questions"`
54
- # array; shape validated downstream by `Ports::Agent::Answers.questions`
55
- # @raise [Ports::Agent::Unavailable] if the `claude` binary is missing, the subprocess
56
- # fails, or the call times out
57
- # @raise [Ports::Agent::ValidationError] if the CLI's own envelope has no `"result"`, or
58
- # the result text is not valid JSON
59
20
  def ask(state:, asked:)
60
21
  call(
61
22
  system: SYSTEM_PREFIX + "Given the domain model so far, ask the single best next " \
@@ -65,18 +26,6 @@ module Hecks
65
26
  )
66
27
  end
67
28
 
68
- # Turns a human's sentence into proposed declarations, as a raw parsed-JSON reply.
69
- #
70
- # Prose -> proposed declarations.
71
- #
72
- # @param prose [String] a human's plain-English sentence
73
- # @param state [Hash] the interview's whole current picture, JSON-able
74
- # @return [Object] the parsed JSON reply, expected to be a Hash holding a `"proposals"`
75
- # array; shape validated downstream by `Ports::Agent::Answers.proposals`
76
- # @raise [Ports::Agent::Unavailable] if the `claude` binary is missing, the subprocess
77
- # fails, or the call times out
78
- # @raise [Ports::Agent::ValidationError] if the CLI's own envelope has no `"result"`, or
79
- # the result text is not valid JSON
80
29
  def interpret(prose:, state:)
81
30
  call(
82
31
  system: SYSTEM_PREFIX + "Given the domain model so far and a sentence the human just said, " \
@@ -89,22 +38,6 @@ module Hecks
89
38
  )
90
39
  end
91
40
 
92
- # Asks the model to judge a declared model on taste, as a raw parsed-JSON reply.
93
- #
94
- # What is wrong with this as a model — closed to the same kind
95
- # vocabulary `Ports::Agent::CRITIQUE_KINDS` declares, spelled out
96
- # here too since the system prompt is the only place the model
97
- # itself ever sees that list.
98
- #
99
- # @param declared [Hash] the chapter as declared so far, JSON-able
100
- # @param refusals [Array<Object>] JSON-able refusals the language itself already raised
101
- # @param findings [Array<Object>] JSON-able mechanical findings already found
102
- # @return [Object] the parsed JSON reply, expected to be a Hash holding a `"findings"`
103
- # array; shape validated downstream by `Ports::Agent::Answers.findings`
104
- # @raise [Ports::Agent::Unavailable] if the `claude` binary is missing, the subprocess
105
- # fails, or the call times out
106
- # @raise [Ports::Agent::ValidationError] if the CLI's own envelope has no `"result"`, or
107
- # the result text is not valid JSON
108
41
  def critique(declared:, refusals:, findings:)
109
42
  kinds = Ports::Agent::CRITIQUE_KINDS.join(", ")
110
43
  call(
@@ -117,22 +50,7 @@ module Hecks
117
50
  )
118
51
  end
119
52
 
120
- # Suggests a name for a construct, as a raw parsed-JSON reply.
121
- #
122
- # **Vocabulary help**. Named `suggest_name`, not `name` — see
123
- # `Ports::Agent#suggest_name`'s own comment for why `name` is
124
- # never a safe module-function name here.
125
- #
126
- # @param meaning [String] what the new name needs to mean
127
- # @param kind [String] the kind of construct being named, such as `"event"`
128
- # @param near [Array<String>] names already in use nearby, which a suggestion must not
129
- # collide with
130
- # @return [Object] the parsed JSON reply, expected to be a Hash holding a `"names"`
131
- # array; shape validated downstream by `Ports::Agent::Answers.suggestions`
132
- # @raise [Ports::Agent::Unavailable] if the `claude` binary is missing, the subprocess
133
- # fails, or the call times out
134
- # @raise [Ports::Agent::ValidationError] if the CLI's own envelope has no `"result"`, or
135
- # the result text is not valid JSON
53
+ # Named `suggest_name`, not `name`: `name` is never a safe module-function name here.
136
54
  def suggest_name(meaning:, kind:, near:)
137
55
  call(
138
56
  system: SYSTEM_PREFIX + "Suggest a name for a #{kind} meaning \"#{meaning}\", distinct from " \
@@ -142,18 +60,7 @@ module Hecks
142
60
  )
143
61
  end
144
62
 
145
- # ── transport ───────────────────────────────────────────────────
146
-
147
- # Runs `claude` as a subprocess with `payload` on stdin and returns its parsed JSON reply.
148
- #
149
- # @param system [String] the system-prompt text appended via `--append-system-prompt`
150
- # @param payload [Hash] the JSON-able payload written to the subprocess's stdin
151
- # @return [Object] the parsed JSON value nested under the CLI envelope's `"result"` key
152
- # (see `unwrap`)
153
- # @raise [Ports::Agent::Unavailable] if the `claude` binary is missing, the subprocess
154
- # exits non-zero, or the call does not finish within `TIMEOUT_SECONDS`
155
- # @raise [Ports::Agent::ValidationError] if the envelope has no `"result"`, or the
156
- # result text is not valid JSON (see `unwrap`)
63
+ # Argv spawn, never a shell, so a `claude` shell alias cannot apply; no tools are allowed.
157
64
  def call(system:, payload:)
158
65
  stdout, status = Timeout.timeout(TIMEOUT_SECONDS) do
159
66
  Open3.capture2(
@@ -171,13 +78,6 @@ module Hecks
171
78
  raise Ports::Agent::Unavailable, "claude is not on PATH: #{e.message}"
172
79
  end
173
80
 
174
- # Unwraps the CLI's own JSON envelope down to the model's parsed reply.
175
- #
176
- # @param stdout [String] the raw stdout of the `claude` CLI invocation, the outer
177
- # `{"result": "..."}` envelope
178
- # @return [Object] the JSON value parsed out of the envelope's `"result"` string
179
- # @raise [Ports::Agent::ValidationError] if the envelope has no `"result"` key, or
180
- # `result` is not valid JSON
181
81
  def unwrap(stdout)
182
82
  envelope = JSON.parse(stdout)
183
83
  result = envelope["result"]
@@ -5,7 +5,7 @@ require "uri"
5
5
  require_relative "sql_query_builder"
6
6
  require_relative "sqlite/schema_builder"
7
7
  require_relative "sqlite/codec"
8
- require_relative "sqlite" # for Sqlite::SchemaBuilder/Sqlite::Codec — see the reuse note below
8
+ require_relative "sqlite"
9
9
  require_relative "../../ports/persistence/append_only"
10
10
  require_relative "../../query_specification/common/null_policy"
11
11
  require_relative "../../query_specification/common/order_by"
@@ -15,59 +15,23 @@ require_relative "../../runtime/instance"
15
15
 
16
16
  module Hecks
17
17
  module Adapters
18
- # Cloudflare D1 — SQLite, managed, reached over its REST API rather
19
- # than a local file. D1 is SQLite, dialect and all, so this file
20
- # reuses Sqlite::SchemaBuilder and Sqlite::Codec unchanged (the DDL
21
- # and the column encode/decode) and SqlQueryBuilder's dialect hooks
22
- # are copied near-verbatim from sqlite.rb — the only real difference
23
- # is the transport (D1::Connection, an HTTP call per query, vs a
24
- # persistent local sqlite3 handle). See sqlite.rb's own header
25
- # comment: "this file supplies only SQLite's dialect" — true here too.
18
+ # Cloudflare D1 (managed SQLite over REST): reuses Sqlite's schema and codec unchanged.
19
+ # Only the transport differs, one HTTP call per query.
26
20
  class D1
27
- # **The transport** — mirrors just the slice of SQLite3::Database's own
28
- # interface (execute/get_first_row/get_first_value, rows as
29
- # column-name-keyed hashes) that Sqlite::SchemaBuilder, Sqlite::Codec,
30
- # and this file's own methods already assume. One stateless HTTP call
31
- # per execute, not a persistent connection — D1 has no connection to
32
- # hold open.
21
+ # HTTP transport exposing the slice of `SQLite3::Database` the shared SQLite code uses.
33
22
  class Connection
34
23
  ENDPOINT = "https://api.cloudflare.com/client/v4".freeze
35
24
 
36
- # @param account_id [String] the Cloudflare account that owns the database
37
- # @param database_id [String] the D1 database's id
38
- # @param api_token [String] a Cloudflare API token, sent as a bearer token on every
39
- # request
40
25
  def initialize(account_id:, database_id:, api_token:)
41
26
  @uri = URI("#{ENDPOINT}/accounts/#{account_id}/d1/database/#{database_id}/query")
42
27
  @api_token = api_token
43
28
  end
44
29
 
45
- # Runs one statement in its own HTTP request and returns its rows.
46
- #
47
- # @param sql [String] the statement, with `?` placeholders
48
- # @param binds [Array<Object>] one value per placeholder, in order
49
- # @return [Array<Hash{String => Object}>] the result rows keyed by column name; `[]`
50
- # for a statement that returns none
51
- # @raise [Runtime::WiringError] if D1 reports the query failed or answers with a body
52
- # that is not JSON
53
30
  def execute(sql, binds = [])
54
31
  response_results({ sql: sql, params: binds }).first.fetch("results", [])
55
32
  end
56
33
 
57
- # Runs several statements as one transaction in a single HTTP request.
58
- #
59
- # D1 batches are SQL transactions: statements execute in order and a
60
- # failure rolls the entire sequence back. Keep the tuple-shaped local
61
- # seam small so adapter code and focused fakes do not need to know the
62
- # REST request envelope.
63
- # https://developers.cloudflare.com/d1/worker-api/d1-database/#batch
64
- #
65
- # @param statements [Array<Array(String, Array)>] `[sql, binds]` pairs in execution
66
- # order; nil binds mean none
67
- # @return [Array<Array<Hash{String => Object}>>] each statement's result rows, in
68
- # the order given
69
- # @raise [Runtime::WiringError] if D1 reports the request or any one statement failed,
70
- # or answers with a body that is not JSON
34
+ # D1 batches are transactions: statements run in order and a failure rolls all back.
71
35
  def batch(statements)
72
36
  payload = {
73
37
  batch: statements.map do |sql, binds|
@@ -104,11 +68,7 @@ module Hecks
104
68
  raise Runtime::WiringError, "D1 query failed: non-JSON response (HTTP #{response.code}): #{response.body}"
105
69
  end
106
70
 
107
- # `messages` is the whole-response :errors fallback, tried last —
108
- # a per-statement `failed["error"]`/`failed["message"]` (checked
109
- # by key presence, not truthiness, so an explicit `nil` still
110
- # counts as "the key was there") is always more specific to which
111
- # statement failed, when either is present.
71
+ # Prefers the statement's own error/message (by key presence) over the whole-response one.
112
72
  def failed_statement_detail(failed, messages)
113
73
  detail =
114
74
  if failed.key?("error")
@@ -123,26 +83,10 @@ module Hecks
123
83
 
124
84
  public
125
85
 
126
- # Runs one statement and keeps only its first row, as `SQLite3::Database` does.
127
- #
128
- # @param sql [String] the statement, with `?` placeholders
129
- # @param binds [Array<Object>] one value per placeholder, in order
130
- # @return [Hash{String => Object}, nil] the first row keyed by column name, or nil when
131
- # the statement returned none
132
- # @raise [Runtime::WiringError] if D1 reports the query failed or answers with a body
133
- # that is not JSON
134
86
  def get_first_row(sql, binds = [])
135
87
  execute(sql, binds).first
136
88
  end
137
89
 
138
- # Runs one statement and keeps only the first column of its first row.
139
- #
140
- # @param sql [String] the statement, with `?` placeholders
141
- # @param binds [Array<Object>] one value per placeholder, in order
142
- # @return [Object, nil] the value as D1's JSON carries it, or nil when the statement
143
- # returned no row
144
- # @raise [Runtime::WiringError] if D1 reports the query failed or answers with a body
145
- # that is not JSON
146
90
  def get_first_value(sql, binds = [])
147
91
  get_first_row(sql, binds)&.values&.first
148
92
  end
@@ -156,22 +100,11 @@ module Hecks
156
100
 
157
101
  attr_reader :aggregate
158
102
 
159
- # Names the optional persistence capabilities `Ports::Persistence::AppendOnly` may rely on.
160
- #
161
- # @return [Array<Symbol>] `[:atomic_put]`
162
103
  def persistence_capabilities = [:atomic_put]
163
104
 
164
- # Checks the credentials are declared and creates the aggregate, journal, event and
165
- # saga tables if absent, one HTTP request per statement.
166
- #
167
- # @param aggregate [Bluebook::Aggregate] the aggregate whose table this adapter owns
168
- # @param settings [Hash{Symbol, String => Object}] world settings for the binding:
169
- # `account_id`, `database_id` and `api_token` (all required) and `domain` (scopes saga
170
- # rows, default the aggregate's name), each read under a Symbol or a String key
171
- # @param root [String, nil] project root directory; accepted for the shared adapter
172
- # constructor shape and ignored
173
- # @raise [Runtime::WiringError] if a required setting is missing or empty, or D1 rejects
174
- # a table-creation statement
105
+ # Creates the aggregate, journal, event and saga tables if absent.
106
+ # Settings: `account_id`, `database_id`, `api_token` (required) and `domain` (saga scope).
107
+ # @raise [Runtime::WiringError] if a required setting is missing or empty
175
108
  def initialize(aggregate:, settings: {}, root: nil)
176
109
  @aggregate = aggregate
177
110
 
@@ -183,12 +116,7 @@ module Hecks
183
116
  end
184
117
 
185
118
  @db = Connection.new(account_id: account_id, database_id: database_id, api_token: api_token)
186
- # The optional saga-persistence capability's own scoping column
187
- # (§2/§4) — D1's domain isolation is by whole-database identity
188
- # (one D1 database per domain in practice), so unlike Sqlite's
189
- # own per-aggregate-file default, there's no "which file does
190
- # this end up in" ambiguity here: every aggregate on D1 within a
191
- # domain already shares one database.
119
+ # Saga rows are scoped by domain; one D1 database per domain, so aggregates share it.
192
120
  @domain = (
193
121
  if settings.key?(:domain)
194
122
  settings[:domain]
@@ -199,9 +127,7 @@ module Hecks
199
127
  end
200
128
  ).to_s
201
129
 
202
- # No PRAGMA synchronous here, unlike Sqlite — D1 is a managed
203
- # durable service; there is no local fsync policy for a caller to
204
- # tune, and the query endpoint has no PRAGMA write-access to it.
130
+ # No PRAGMA synchronous: D1 is managed and its query endpoint disallows PRAGMA writes.
205
131
  create_aggregate_table!
206
132
  create_entry_table!
207
133
  ensure_entry_operation_column!
@@ -210,16 +136,8 @@ module Hecks
210
136
  create_saga_table!
211
137
  end
212
138
 
213
- # Names the aggregate's table; the journal table is keyed off it.
214
- #
215
- # @return [String] the aggregate's snake_case storage name, unquoted
216
139
  def table = @aggregate.storage_name
217
140
 
218
- # Reads the current row for one aggregate identity.
219
- #
220
- # @param id [String, Object] the aggregate identity, bound as `id.to_s`
221
- # @return [Runtime::Instance, nil] the decoded record, or nil when no row has that id
222
- # @raise [Runtime::WiringError] if D1 rejects the statement
223
141
  def find(id)
224
142
  row = @db.get_first_row("SELECT * FROM #{quoted_table} WHERE id = ?", [id.to_s])
225
143
  return nil unless row
@@ -227,19 +145,8 @@ module Hecks
227
145
  Runtime::Instance.new(aggregate: @aggregate, id: row["id"], state: decode(row))
228
146
  end
229
147
 
230
- # Lists every stored record, ordered by id unless an ordering attribute is given.
231
- #
232
- # order_by is a runtime value — see postgres.rb's own all for the
233
- # full reasoning; whitelisted the identical way, same order_clause
234
- # Sqlite's own all reuses (D1 speaks the identical dialect).
235
- #
236
- # @param order_by [String, Symbol, nil] attribute (or dotted value-object path) to sort
237
- # by, with id as the tie-break; nil orders by id alone
238
- # @param direction [Symbol, String] `:asc` or `:desc`, case-insensitive; anything else
239
- # sorts ascending
240
- # @return [Array<Runtime::Instance>] the decoded records, `[]` when the table is empty
241
- # @raise [Runtime::WiringError] if `order_by` names no attribute of the aggregate, or D1
242
- # rejects the statement
148
+ # Lists every record, ordered by id or by `order_by` when it names an attribute.
149
+ # @raise [Runtime::WiringError] if `order_by` names no attribute of the aggregate
243
150
  def all(order_by: nil, direction: :asc)
244
151
  order_sql = "ORDER BY id"
245
152
  if order_by
@@ -258,37 +165,17 @@ module Hecks
258
165
  end
259
166
  end
260
167
 
261
- # Counts the rows in the aggregate's table, deleted records excluded.
262
- #
263
- # @return [Integer] number of current records
264
- # @raise [Runtime::WiringError] if D1 rejects the statement
265
168
  def count = @db.get_first_value("SELECT COUNT(*) FROM #{quoted_table}").to_i
266
169
 
267
- # Inserts one journal row in its own HTTP request.
268
- #
269
- # @param entry [Ports::Persistence::Entry] the save or delete to journal; `state` is
270
- # encoded through the state codec and `mirrors` stored as JSON, or NULL when nil
271
- # @return [Ports::Persistence::Entry] the same `entry`
272
- # @raise [Runtime::WiringError] if D1 rejects the insert
273
170
  def append(entry)
274
171
  @db.execute(
275
172
  "INSERT INTO #{quoted_entry_table} (aggregate_id, operation, state, mirrors) VALUES (?, ?, ?, ?)",
276
- # `mirrors` (unlike `state`) is a nullable column — an absent
277
- # mirrors hash must bind a real SQL NULL, not the four-character
278
- # JSON text `"null"` (`JSON.generate(nil)`), or a future `IS NULL`
279
- # check against it would never match. Same guard `postgres_era.rb`
280
- # already uses for its own journal's `mirrors` column.
173
+ # Absent mirrors bind SQL NULL, not the JSON text "null" (the column is nullable).
281
174
  [entry.id, entry.operation, state_json(entry.state), entry.mirrors && JSON.generate(entry.mirrors)]
282
175
  )
283
176
  entry
284
177
  end
285
178
 
286
- # Replaces or deletes the aggregate's row for one journal entry.
287
- #
288
- # @param entry [Ports::Persistence::Entry] the save or delete to materialize
289
- # @return [Runtime::Instance, Array] for a save, a new instance over the entry's state;
290
- # for a delete, the `DELETE` statement's empty result rows
291
- # @raise [Runtime::WiringError] if D1 rejects the statement
292
179
  def project(entry)
293
180
  return @db.execute("DELETE FROM #{quoted_table} WHERE id = ?", [entry.id]) if entry.delete?
294
181
 
@@ -304,13 +191,7 @@ module Hecks
304
191
  instance
305
192
  end
306
193
 
307
- # Reads the whole journal back in append order, for `AppendOnly#recover!` to replay.
308
- #
309
- # @return [Array<Ports::Persistence::Entry>] every journalled entry, state decoded
310
- # through the state codec and `mirrors` parsed with String keys (nil when none were
311
- # stored); a NULL `operation` reads as `"save"`; `[]` when nothing has been appended
312
- # @raise [Runtime::WiringError] if D1 rejects the statement
313
- # @raise [JSON::ParserError] if a stored `state` or `mirrors` value is not valid JSON
194
+ # The whole journal in append order, for `AppendOnly#recover!` to replay.
314
195
  def entries
315
196
  @db.execute("SELECT aggregate_id, operation, state, mirrors FROM #{quoted_entry_table} ORDER BY sequence").map do |row|
316
197
  state = JSON.parse(row["state"])
@@ -323,70 +204,25 @@ module Hecks
323
204
  end
324
205
  end
325
206
 
326
- # Deletes every row of the aggregate's table and its journal; events and saga rows
327
- # are left in place.
328
- #
329
- # @return [Adapters::D1] self
330
- # @raise [Runtime::WiringError] if D1 rejects a statement
207
+ # Clears the aggregate table and its journal; events and saga rows stay.
331
208
  def reset!
332
209
  @db.execute("DELETE FROM #{quoted_table}")
333
210
  @db.execute("DELETE FROM #{quoted_entry_table}")
334
211
  self
335
212
  end
336
213
 
337
- # Journals and then replaces an instance's current state, as two separate HTTP
338
- # requests rather than one batch.
339
- #
340
- # @param instance [Runtime::Instance] the instance to store
341
- # @return [Runtime::Instance] a new instance over a shallow copy of the saved state
342
- # @raise [Runtime::WiringError] if D1 rejects either statement; a journal row already
343
- # inserted stays
214
+ # Journals then projects as two HTTP requests; the journal row stays if projecting fails.
344
215
  def save(instance)
345
216
  entry = Ports::Persistence::Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
346
217
  append(entry)
347
218
  project(entry)
348
219
  end
349
220
 
350
- # Stores an entry and reports whether it inserted, replaced or conflicted, all in one
351
- # batched HTTP request.
352
- #
353
- # Classification, durable journal append and current-state projection
354
- # are one D1 batch transaction and therefore one HTTP request. The first
355
- # statement supplies the outcome from database state inside that same
356
- # transaction; the runtime performs no preliminary find.
221
+ # Stores an entry in one D1 batch; returns `:inserted`, `:replaced` or `:conflicted`.
357
222
  #
358
- # Under `insert_only:`, spending a separate, earlier round trip finding
359
- # out whether the row exists before building the batch would be a real
360
- # TOCTOU gap (two concurrent creates at the same identity could both
361
- # pass that check before either wrote). D1's batch has no conditional
362
- # branch of its own, true, but it does not need one: a batch's own
363
- # statements already execute in order, atomically, as one transaction
364
- # (Connection#batch's own comment) — the exact guarantee the single-
365
- # connection adapters' own `@db.transaction do ... end` gets locally.
366
- # So the existence check sits inside the batch as its own first
367
- # statement, and the two writes are individually gated with `WHERE NOT
368
- # EXISTS (...)` against that same table, evaluated in the same
369
- # transaction — a row that already existed makes both writes into
370
- # real, zero-row no-ops rather than skipping them from the Ruby side,
371
- # matching Sqlite#atomic_put's `next` (skip append and project both,
372
- # together) with no second HTTP call and no gap for another writer to
373
- # land in between the check and the write.
374
- #
375
- # Three SQL statements, built here and batched together as one
376
- # transaction below — see the paragraph above on the real TOCTOU gap
377
- # this exact shape closes (the existence check inside the
378
- # batch, not run as a separate earlier round trip). Splitting the
379
- # per-statement builders out would still need columns/values/slots/
380
- # not_exists/quoted_table threaded into each, and would separate
381
- # three pieces of one atomic batch across methods with no single
382
- # place left to see that they are, together, the fix.
383
- #
384
- # @param entry [Ports::Persistence::Entry] the save to store
385
- # @param insert_only [Boolean] when true, an existing row turns both writes into
386
- # zero-row no-ops
387
- # @return [Symbol] `:inserted`, `:replaced`, or `:conflicted` when `insert_only` met an
388
- # existing row and nothing was written
389
- # @raise [Runtime::WiringError] if D1 rejects the batch; the whole batch is rolled back
223
+ # The existence check is the batch's first statement and both writes are gated by
224
+ # WHERE NOT EXISTS, so `insert_only` has no gap between check and write.
225
+ # @return [Symbol] `:conflicted` only when `insert_only` met an existing row
390
226
  # rubocop:disable-next Metrics/AbcSize
391
227
  # rubocop:disable-next Metrics/MethodLength
392
228
  def atomic_put(entry, insert_only: false)
@@ -405,7 +241,7 @@ module Hecks
405
241
  "THEN 'replaced' ELSE 'inserted' END AS status"
406
242
  end
407
243
 
408
- # `mirrors` is nullable (unlike `state`) — see `append`'s own comment.
244
+ # `mirrors` is nullable; see `append`.
409
245
  encoded_mirrors = entry.mirrors && JSON.generate(entry.mirrors)
410
246
 
411
247
  entry_sql, entry_binds =
@@ -444,12 +280,6 @@ module Hecks
444
280
  results.fetch(0).fetch(0).fetch("status").to_sym
445
281
  end
446
282
 
447
- # Journals a delete and then removes the row, whether or not a row exists, as two
448
- # separate HTTP requests.
449
- #
450
- # @param id [String, Object] the aggregate identity, journalled as `id.to_s`
451
- # @return [Boolean] always true
452
- # @raise [Runtime::WiringError] if D1 rejects either statement
453
283
  def delete(id)
454
284
  entry = Ports::Persistence::Entry.new(operation: "delete", id: id.to_s, state: nil)
455
285
  append(entry)
@@ -457,11 +287,6 @@ module Hecks
457
287
  true
458
288
  end
459
289
 
460
- # Inserts an emitted event into the database's shared `events` table.
461
- #
462
- # @param event [Runtime::Event] the emitted event; `payload` is stored as JSON
463
- # @return [Array] the insert's empty result rows; callers ignore it
464
- # @raise [Runtime::WiringError] if D1 rejects the insert
465
290
  def record_event(event)
466
291
  @db.execute(
467
292
  "INSERT INTO events (name, aggregate, aggregate_id, payload, occurred_at) VALUES (?, ?, ?, ?, ?)",
@@ -469,12 +294,7 @@ module Hecks
469
294
  )
470
295
  end
471
296
 
472
- # Reads back every recorded event in insertion order — the database's whole `events`
473
- # table, not only this aggregate's rows.
474
- #
475
- # @return [Array<Runtime::Event>] the stored events, `payload` parsed with Symbol keys
476
- # and `occurred_at` as stored; `[]` when none are recorded
477
- # @raise [Runtime::WiringError] if D1 rejects the statement
297
+ # Every recorded event in the shared `events` table, not only this aggregate's.
478
298
  def events
479
299
  @db.execute("SELECT * FROM events ORDER BY id").map do |row|
480
300
  Runtime::Event.new(
@@ -487,13 +307,24 @@ module Hecks
487
307
  end
488
308
  end
489
309
 
490
- # Replaces one saga instance's checkpoint, keyed by domain, process manager and
491
- # correlation.
492
- #
493
- # ── the optional saga-persistence capability (§2) — reuses the DDL
494
- # `Sqlite::SchemaBuilder` already shares with Sqlite (`d1.rb`'s own
495
- # file header). Same `?`-placeholder shape every other write here
496
- # already uses through `Connection#execute`.
310
+ # One record's recorded events, oldest first — pushed down as a `WHERE` clause
311
+ # (`hecks_events_aggregate_id_idx`) instead of filtering `#events`'s whole-table read.
312
+ def events_for(aggregate:, id:)
313
+ @db.execute(
314
+ "SELECT * FROM events WHERE aggregate = ? AND aggregate_id = ? ORDER BY id",
315
+ [aggregate, id.to_s]
316
+ ).map do |row|
317
+ Runtime::Event.new(
318
+ name: row["name"],
319
+ aggregate: row["aggregate"],
320
+ id: row["aggregate_id"],
321
+ payload: JSON.parse(row["payload"], symbolize_names: true),
322
+ occurred_at: row["occurred_at"]
323
+ )
324
+ end
325
+ end
326
+
327
+ # Persists a saga's checkpointed state as one D1 statement.
497
328
  #
498
329
  # @param process_manager [String, Symbol] the process manager's name
499
330
  # @param correlation [String, Object] the instance's correlation value, stored as
@@ -513,13 +344,6 @@ module Hecks
513
344
  )
514
345
  end
515
346
 
516
- # Removes a finished saga instance's checkpoint; a missing row is not an error.
517
- #
518
- # @param process_manager [String, Symbol] the process manager's name
519
- # @param correlation [String, Object] the instance's correlation value, matched as
520
- # `correlation.to_s`
521
- # @return [Array] the statement's empty result rows; callers ignore it
522
- # @raise [Runtime::WiringError] if D1 rejects the statement
523
347
  def delete_saga(process_manager:, correlation:)
524
348
  @db.execute(
525
349
  "DELETE FROM hecks_saga_instances WHERE domain = ? AND process_manager = ? AND correlation = ?",
@@ -527,18 +351,7 @@ module Hecks
527
351
  )
528
352
  end
529
353
 
530
- # Yields every checkpointed saga instance of this adapter's domain, for
531
- # `Registry#rehydrate_sagas!` to restore at boot.
532
- #
533
- # @yieldparam process_manager [String] the process manager's name
534
- # @yieldparam correlation [String] the instance's correlation value
535
- # @yieldparam state [String] the saga's state name
536
- # @yieldparam memory [Hash{Symbol => Object}] the saga's memory, Symbol keys at every depth
537
- # @yieldparam completed_compensations [Array] the completed-compensation ledger, `[]`
538
- # when the column is NULL
539
- # @return [Enumerator, Array<Hash>] an enumerator over the same five values when no
540
- # block is given; otherwise the raw result rows
541
- # @raise [Runtime::WiringError] if D1 rejects the statement
354
+ # Yields each checkpointed saga of this domain, for `Registry#rehydrate_sagas!`.
542
355
  def each_saga
543
356
  return enum_for(:each_saga) unless block_given?
544
357
 
@@ -555,10 +368,7 @@ module Hecks
555
368
 
556
369
  private
557
370
 
558
- # ── SqlQueryBuilder's dialect hooks — identical to Sqlite's, since
559
- # D1 speaks the same dialect (copied, not shared by module include,
560
- # because Sqlite's own copies are private instance methods on a
561
- # different class — see the file header) ───────────────────────
371
+ # SqlQueryBuilder dialect hooks, copied from Sqlite's private methods.
562
372
  def select_list = "*"
563
373
  def from_relation = quoted_table
564
374
  def dialect_name = "D1"
@@ -585,8 +395,7 @@ module Hecks
585
395
  "json_extract(#{quote_ident(name)}, '#{json_path}')"
586
396
  end
587
397
 
588
- # SQLite (and D1, the same engine) has no bare OFFSET — LIMIT -1 is
589
- # its own documented unbounded spelling, exactly for this case.
398
+ # SQLite has no bare OFFSET; LIMIT -1 is its unbounded spelling.
590
399
  def unbounded_limit = " LIMIT -1"
591
400
 
592
401
  def order_clause(order_by, policy)
@@ -597,8 +406,6 @@ module Hecks
597
406
  @db.execute(sql, binds).map { |row| Runtime::Instance.new(aggregate: @aggregate, id: row["id"], state: decode(row)) }
598
407
  end
599
408
 
600
- # ── the rest of the dialect ─────────────────────────────────────
601
-
602
409
  def quote_ident(name)
603
410
  %("#{name.to_s.gsub('"', '""')}")
604
411
  end