hecks 0.2.0 → 1.0.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 (419) hide show
  1. checksums.yaml +5 -5
  2. data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +127 -0
  4. data/lib/hecks/adapters/driven/d1.adapter +12 -0
  5. data/lib/hecks/adapters/driven/d1.rb +427 -0
  6. data/lib/hecks/adapters/driven/folder.adapter +3 -0
  7. data/lib/hecks/adapters/driven/folder.rb +199 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
  11. data/lib/hecks/adapters/driven/governance_authorization.rb +101 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +179 -0
  17. data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
  18. data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
  19. data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
  20. data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
  21. data/lib/hecks/adapters/driven/lambda.adapter +4 -0
  22. data/lib/hecks/adapters/driven/lambda.rb +145 -0
  23. data/lib/hecks/adapters/driven/memory.adapter +3 -0
  24. data/lib/hecks/adapters/driven/memory.rb +103 -0
  25. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
  26. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
  27. data/lib/hecks/adapters/driven/postgres/codec.rb +88 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +438 -0
  31. data/lib/hecks/adapters/driven/postgres_era.adapter +17 -0
  32. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  33. data/lib/hecks/adapters/driven/prism.rb +80 -0
  34. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  35. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  36. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  37. data/lib/hecks/adapters/driven/sqlite/codec.rb +80 -0
  38. data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
  39. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
  40. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  41. data/lib/hecks/adapters/driven/sqlite.rb +306 -0
  42. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  43. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  44. data/lib/hecks/adapters/driven.rb +54 -0
  45. data/lib/hecks/adapters.rb +6 -0
  46. data/lib/hecks/behaviors/dsl.rb +125 -0
  47. data/lib/hecks/behaviors/expectations.rb +340 -0
  48. data/lib/hecks/behaviors/ir.rb +31 -0
  49. data/lib/hecks/behaviors/rspec.rb +42 -0
  50. data/lib/hecks/behaviors/runner.rb +96 -0
  51. data/lib/hecks/behaviors.rb +25 -0
  52. data/lib/hecks/bluebook/aggregate.rb +108 -0
  53. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  54. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  55. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  56. data/lib/hecks/bluebook/assembly/contracts.rb +438 -0
  57. data/lib/hecks/bluebook/assembly/marks.rb +228 -0
  58. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  59. data/lib/hecks/bluebook/assembly.rb +91 -0
  60. data/lib/hecks/bluebook/attribute.rb +96 -0
  61. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  62. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  63. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  64. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  65. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  66. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  67. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  68. data/lib/hecks/bluebook/behaviour/lifecycle.rb +68 -0
  69. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  70. data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -0
  71. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  73. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  75. data/lib/hecks/bluebook/chapter.rb +78 -0
  76. data/lib/hecks/bluebook/command.rb +124 -0
  77. data/lib/hecks/bluebook/domain_port.rb +102 -0
  78. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  79. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +1018 -0
  80. data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
  81. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  82. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
  83. data/lib/hecks/bluebook/dsl/command_builder.rb +714 -0
  84. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  85. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  86. data/lib/hecks/bluebook/dsl/entity_builder.rb +430 -0
  87. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  88. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  89. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  90. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  91. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  92. data/lib/hecks/bluebook/dsl/policy_builder.rb +135 -0
  93. data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
  94. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  95. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -0
  96. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  97. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  98. data/lib/hecks/bluebook/dsl/rule_reference.rb +173 -0
  99. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  100. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  101. data/lib/hecks/bluebook/dsl/word_gate.rb +228 -0
  102. data/lib/hecks/bluebook/dsl/world_builder.rb +117 -0
  103. data/lib/hecks/bluebook/dsl.rb +45 -0
  104. data/lib/hecks/bluebook/entity.rb +103 -0
  105. data/lib/hecks/bluebook/expression/canonical_form.rb +123 -0
  106. data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
  107. data/lib/hecks/bluebook/expression/projection.json +218 -0
  108. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  109. data/lib/hecks/bluebook/expression/resolver.rb +781 -0
  110. data/lib/hecks/bluebook/expression.rb +13 -0
  111. data/lib/hecks/bluebook/hexagon.rb +60 -0
  112. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  113. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  114. data/lib/hecks/bluebook/meta_validator/judge.rb +621 -0
  115. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  116. data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
  117. data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
  118. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
  119. data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -0
  120. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  121. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  122. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  123. data/lib/hecks/bluebook/meta_validator.rb +542 -0
  124. data/lib/hecks/bluebook/model_check.rb +445 -0
  125. data/lib/hecks/bluebook/pattern_subset.rb +184 -0
  126. data/lib/hecks/bluebook/policy.rb +41 -0
  127. data/lib/hecks/bluebook/process_manager.rb +128 -0
  128. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  129. data/lib/hecks/bluebook/project_loader.rb +40 -0
  130. data/lib/hecks/bluebook/project_register.rb +82 -0
  131. data/lib/hecks/bluebook/query.rb +61 -0
  132. data/lib/hecks/bluebook/read_model.rb +109 -0
  133. data/lib/hecks/bluebook/reference.rb +74 -0
  134. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  135. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  136. data/lib/hecks/bluebook/translation.rb +92 -0
  137. data/lib/hecks/bluebook/value_object.rb +66 -0
  138. data/lib/hecks/bluebook.rb +74 -0
  139. data/lib/hecks/codemod.rb +342 -0
  140. data/lib/hecks/construct.rb +71 -0
  141. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  142. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  143. data/lib/hecks/deploy/oidc.json +18 -0
  144. data/lib/hecks/doc/reference.rb +410 -0
  145. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  146. data/lib/hecks/facade/cli_door.rb +119 -0
  147. data/lib/hecks/facade/cli_runner.rb +190 -0
  148. data/lib/hecks/facade/command_request.rb +105 -0
  149. data/lib/hecks/facade/handle.rb +173 -0
  150. data/lib/hecks/facade/json_door.rb +166 -0
  151. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  152. data/lib/hecks/facade/surface/chapter.rb +107 -0
  153. data/lib/hecks/facade/surface.rb +48 -0
  154. data/lib/hecks/facade.rb +44 -0
  155. data/lib/hecks/forms/app.rb +341 -0
  156. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  157. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  158. data/lib/hecks/forms/field_renderer.rb +177 -0
  159. data/lib/hecks/forms/field_shape.rb +232 -0
  160. data/lib/hecks/forms/html.rb +84 -0
  161. data/lib/hecks/forms/index_renderer.rb +35 -0
  162. data/lib/hecks/forms/page.rb +157 -0
  163. data/lib/hecks/forms/params.rb +161 -0
  164. data/lib/hecks/forms/port_argument.rb +46 -0
  165. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  166. data/lib/hecks/forms/record_renderer.rb +119 -0
  167. data/lib/hecks/forms/record_table.rb +68 -0
  168. data/lib/hecks/forms/reference_options.rb +30 -0
  169. data/lib/hecks/forms/value_object_shape.rb +46 -0
  170. data/lib/hecks/forms.rb +54 -0
  171. data/lib/hecks/fqn.rb +94 -0
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  175. data/lib/hecks/framework/bluebook/governance.bluebook +145 -0
  176. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  177. data/lib/hecks/framework/oidc.json +39 -0
  178. data/lib/hecks/framework.rb +90 -0
  179. data/lib/hecks/freezer.rb +67 -0
  180. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +527 -0
  181. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  182. data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
  183. data/lib/hecks/fuzzing/properties.rb +1192 -0
  184. data/lib/hecks/fuzzing/replay.rb +668 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
  189. data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
  190. data/lib/hecks/fuzzing/value_generator.rb +200 -0
  191. data/lib/hecks/fuzzing.rb +12 -0
  192. data/lib/hecks/grammar/evolve.rb +324 -0
  193. data/lib/hecks/grammar/expression.bluebook +420 -0
  194. data/lib/hecks/grammar/expression_operators.json +1648 -0
  195. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  196. data/lib/hecks/grammar/oidc.json +69 -0
  197. data/lib/hecks/grammar/translation.bluebook +203 -0
  198. data/lib/hecks/grammar.rb +178 -0
  199. data/lib/hecks/ir.rb +126 -0
  200. data/lib/hecks/language/adapter.bluebook +116 -0
  201. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  202. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  203. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  204. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  205. data/lib/hecks/language/bluebook/command.bluebook +471 -0
  206. data/lib/hecks/language/bluebook/entity.bluebook +392 -0
  207. data/lib/hecks/language/bluebook/policy.bluebook +189 -0
  208. data/lib/hecks/language/bluebook/process_manager.bluebook +381 -0
  209. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  210. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  211. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  212. data/lib/hecks/language/bluebook/syntax.bluebook +448 -0
  213. data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -0
  214. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  215. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  216. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  217. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  218. data/lib/hecks/language/oidc.json +333 -0
  219. data/lib/hecks/language/port.bluebook +120 -0
  220. data/lib/hecks/language/translation/translation.bluebook +110 -0
  221. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  222. data/lib/hecks/language/world/wiring.bluebook +62 -0
  223. data/lib/hecks/language/world/world.bluebook +84 -0
  224. data/lib/hecks/literal.rb +125 -0
  225. data/lib/hecks/naming.rb +174 -0
  226. data/lib/hecks/ports/access_control.port +9 -0
  227. data/lib/hecks/ports/access_control.rb +62 -0
  228. data/lib/hecks/ports/agent/answers.rb +104 -0
  229. data/lib/hecks/ports/agent.port +8 -0
  230. data/lib/hecks/ports/agent.rb +167 -0
  231. data/lib/hecks/ports/authentication.port +6 -0
  232. data/lib/hecks/ports/authentication.rb +50 -0
  233. data/lib/hecks/ports/authorization.port +7 -0
  234. data/lib/hecks/ports/authorization.rb +62 -0
  235. data/lib/hecks/ports/clock.port +5 -0
  236. data/lib/hecks/ports/clock.rb +62 -0
  237. data/lib/hecks/ports/extraction.port +5 -0
  238. data/lib/hecks/ports/extraction.rb +37 -0
  239. data/lib/hecks/ports/identity_assignment.port +5 -0
  240. data/lib/hecks/ports/identity_assignment.rb +45 -0
  241. data/lib/hecks/ports/identity_generation.port +5 -0
  242. data/lib/hecks/ports/identity_generation.rb +49 -0
  243. data/lib/hecks/ports/identity_resolution.port +5 -0
  244. data/lib/hecks/ports/identity_resolution.rb +40 -0
  245. data/lib/hecks/ports/loading.port +4 -0
  246. data/lib/hecks/ports/loading.rb +13 -0
  247. data/lib/hecks/ports/persistence/append_only.rb +138 -0
  248. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  249. data/lib/hecks/ports/persistence/execution.rb +23 -0
  250. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  251. data/lib/hecks/ports/persistence/plugin.rb +54 -0
  252. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
  253. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
  254. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
  255. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
  256. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
  257. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
  258. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
  259. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
  260. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
  261. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
  262. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
  263. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
  264. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
  265. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
  266. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
  267. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
  268. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  269. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
  270. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
  271. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
  272. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
  273. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
  274. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
  275. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
  276. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
  277. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
  278. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
  279. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
  280. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
  281. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
  282. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
  283. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
  284. data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
  285. data/lib/hecks/ports/persistence/plugins/era.rb +48 -0
  286. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  287. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  288. data/lib/hecks/ports/persistence.port +4 -0
  289. data/lib/hecks/ports/persistence.rb +39 -0
  290. data/lib/hecks/ports/projection.port +4 -0
  291. data/lib/hecks/ports/projection.rb +96 -0
  292. data/lib/hecks/ports/query/in_memory.rb +60 -0
  293. data/lib/hecks/ports/query/ordering.rb +41 -0
  294. data/lib/hecks/ports/query.rb +36 -0
  295. data/lib/hecks/ports.rb +27 -0
  296. data/lib/hecks/projections/diagrams.rb +642 -0
  297. data/lib/hecks/projections/ir.rb +18 -0
  298. data/lib/hecks/projections/model/deviations.rb +98 -0
  299. data/lib/hecks/projections/model.rb +145 -0
  300. data/lib/hecks/projections/oidc.rb +110 -0
  301. data/lib/hecks/projections/parser_table.rb +159 -0
  302. data/lib/hecks/projections/reference.rb +38 -0
  303. data/lib/hecks/projections/shape.rb +44 -0
  304. data/lib/hecks/projections/statements.rb +110 -0
  305. data/lib/hecks/projections/vocabulary.rb +100 -0
  306. data/lib/hecks/projections.rb +32 -0
  307. data/lib/hecks/projector/cli_projector.rb +446 -0
  308. data/lib/hecks/projector/docs_projector.rb +321 -0
  309. data/lib/hecks/projector/exporter.rb +158 -0
  310. data/lib/hecks/projector/ir_projector.rb +18 -0
  311. data/lib/hecks/projector/narrate_projector.rb +243 -0
  312. data/lib/hecks/projector/target.rb +97 -0
  313. data/lib/hecks/projector.rb +186 -0
  314. data/lib/hecks/query_ir.rb +411 -0
  315. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  316. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  317. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  318. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  319. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  320. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  321. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  322. data/lib/hecks/query_specification/common/null_policy.rb +92 -0
  323. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  324. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  325. data/lib/hecks/query_specification/common/options.rb +32 -0
  326. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  327. data/lib/hecks/query_specification/common/specification.rb +10 -0
  328. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  329. data/lib/hecks/query_specification/field_path.rb +107 -0
  330. data/lib/hecks/query_specification/hop_path.rb +132 -0
  331. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  332. data/lib/hecks/query_specification.rb +14 -0
  333. data/lib/hecks/rendering.rb +48 -0
  334. data/lib/hecks/router/namespace_installer.rb +157 -0
  335. data/lib/hecks/router.rb +70 -0
  336. data/lib/hecks/runtime/aggregate_lock.rb +45 -0
  337. data/lib/hecks/runtime/boot_gates.rb +41 -0
  338. data/lib/hecks/runtime/caller.rb +66 -0
  339. data/lib/hecks/runtime/capability_graph.rb +44 -0
  340. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
  341. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
  342. data/lib/hecks/runtime/command_interpreter.rb +497 -0
  343. data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
  344. data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
  345. data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
  346. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  347. data/lib/hecks/runtime/command_rules/references.rb +189 -0
  348. data/lib/hecks/runtime/command_rules.rb +28 -0
  349. data/lib/hecks/runtime/dependency_planning.rb +245 -0
  350. data/lib/hecks/runtime/dispatcher.rb +301 -0
  351. data/lib/hecks/runtime/entity_element.rb +253 -0
  352. data/lib/hecks/runtime/entity_interpreter.rb +299 -0
  353. data/lib/hecks/runtime/errors.rb +122 -0
  354. data/lib/hecks/runtime/event.rb +51 -0
  355. data/lib/hecks/runtime/identity.rb +156 -0
  356. data/lib/hecks/runtime/instance.rb +175 -0
  357. data/lib/hecks/runtime/interpreting.rb +90 -0
  358. data/lib/hecks/runtime/loader.rb +184 -0
  359. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  360. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  361. data/lib/hecks/runtime/query_interpreter.rb +286 -0
  362. data/lib/hecks/runtime/reaction_invocation.rb +253 -0
  363. data/lib/hecks/runtime/read_model_interpreter.rb +341 -0
  364. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  365. data/lib/hecks/runtime/reference_hop.rb +99 -0
  366. data/lib/hecks/runtime/refusal_wording.rb +126 -0
  367. data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
  368. data/lib/hecks/runtime/registry/verification.rb +246 -0
  369. data/lib/hecks/runtime/registry.rb +279 -0
  370. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  371. data/lib/hecks/runtime/routing.rb +96 -0
  372. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  373. data/lib/hecks/runtime/saga_interpreter.rb +503 -0
  374. data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -0
  375. data/lib/hecks/runtime/tenant_check.rb +84 -0
  376. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  377. data/lib/hecks/runtime/value/admission.rb +130 -0
  378. data/lib/hecks/runtime/value/coercion.rb +565 -0
  379. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  380. data/lib/hecks/runtime/value.rb +125 -0
  381. data/lib/hecks/runtime.rb +107 -0
  382. data/lib/hecks/storehouse.rb +632 -0
  383. data/lib/hecks/version.rb +16 -0
  384. data/lib/hecks/vocabulary.rb +218 -0
  385. data/lib/hecks.rb +123 -6
  386. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
  387. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
  388. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
  389. metadata +412 -222
  390. data/bin/hecks +0 -7
  391. data/bin/hecks-package +0 -65
  392. data/bin/hecks_console +0 -12
  393. data/bin/hecks_serverless +0 -6
  394. data/lib/cli/build.rb +0 -14
  395. data/lib/cli/command_runner.rb +0 -28
  396. data/lib/cli/console.rb +0 -10
  397. data/lib/cli/generate.rb +0 -37
  398. data/lib/cli/hecks-cli.rb +0 -27
  399. data/lib/cli/test.rb +0 -57
  400. data/lib/console/commands.rb +0 -8
  401. data/lib/console/hecks-console.rb +0 -1
  402. data/lib/packager/README.md +0 -0
  403. data/lib/packager/app_runner.rb +0 -21
  404. data/lib/packager/args.rb +0 -26
  405. data/lib/packager/compatibility/fixnum.rb +0 -6
  406. data/lib/packager/hecks.rb +0 -39
  407. data/lib/packager/query_runner.rb +0 -21
  408. data/lib/packager/resources/Dockerfile +0 -11
  409. data/lib/packager/resources/app_binary +0 -7
  410. data/lib/packager/resources/bundle_config +0 -3
  411. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  412. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  413. data/lib/serverless/Domain +0 -32
  414. data/lib/serverless/cli.rb +0 -75
  415. data/lib/serverless/resources/command_name.js +0 -5
  416. data/lib/serverless/resources/environment.js +0 -7
  417. data/lib/serverless/resources/handler.js.tt +0 -28
  418. data/lib/serverless/resources/run_binary.js +0 -22
  419. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,179 @@
1
+ require "json"
2
+ require "fileutils"
3
+ require_relative "heki/snapshot"
4
+ require_relative "heki/journal"
5
+ require_relative "heki/saga_store"
6
+ require_relative "../../ports/persistence/append_only"
7
+ require_relative "../../ports/query/in_memory"
8
+ require_relative "in_memory_ordering"
9
+ require_relative "../../runtime/instance"
10
+
11
+ module Hecks
12
+ module Adapters
13
+ # The file-backed store: a compressed snapshot (heki/snapshot.rb)
14
+ # plus an append-only journal beside it (heki/journal.rb). What stays
15
+ # here is the repository surface — find/all/save/delete, and the
16
+ # entry append/project pair the persistence port drives.
17
+ class Heki
18
+ include Snapshot
19
+ include Journal
20
+
21
+ MAGIC = "HEKI"
22
+ HEADER_BYTES = 8
23
+
24
+ class Malformed < StandardError; end
25
+
26
+ attr_reader :aggregate, :path
27
+
28
+ def initialize(aggregate:, settings: {}, root: nil)
29
+ @aggregate = aggregate
30
+ @path = resolve_path(settings, root)
31
+ @journal_path = "#{@path}.journal"
32
+ @events = []
33
+ # THE OPTIONAL saga-persistence capability's own scoping (§2/§4)
34
+ # — falls back to the aggregate's own name for a directly-
35
+ # instantiated adapter (specs), same fallback shape Postgres's
36
+ # own @domain already uses.
37
+ @domain = (
38
+ if settings.key?(:domain)
39
+ settings[:domain]
40
+ elsif settings.key?("domain")
41
+ settings["domain"]
42
+ else
43
+ aggregate.name
44
+ end
45
+ ).to_s
46
+
47
+ FileUtils.mkdir_p(File.dirname(@path))
48
+ end
49
+
50
+ def find(id)
51
+ record = store[id.to_s]
52
+ return nil unless record
53
+
54
+ instance(id.to_s, record)
55
+ end
56
+
57
+ def all(order_by: nil, direction: :asc)
58
+ records = store.sort_by { |id, _| id }.map { |id, record| instance(id, record) }
59
+ InMemoryOrdering.ordered(records, aggregate: @aggregate, order_by: order_by, direction: direction)
60
+ end
61
+
62
+ def count = store.size
63
+
64
+ # `registry: context[:registry]` — Memory's own `query` already
65
+ # threads this through; Heki's own never did, which made
66
+ # `none_in_state?` (Ports::Query::InMemory) unconditionally
67
+ # return `true` (its own graceful "no registry, no way to look
68
+ # the target up" default) for EVERY `none_in_state` where-clause
69
+ # against a Heki-backed aggregate — silently excluding nothing,
70
+ # always, no matter the actual target state.
71
+ def query(specification, args = {}, context: {})
72
+ Ports::Query::InMemory.execute(all, specification, args, registry: context[:registry])
73
+ end
74
+
75
+ def append(entry)
76
+ @entry_mirrors = entry.mirrors
77
+ append_entry(entry.operation, entry.id, entry.state)
78
+ entry
79
+ ensure
80
+ @entry_mirrors = nil
81
+ end
82
+
83
+ # Reads fresh rather than trusting the memoized `store` — under
84
+ # `with_lock`, another process may have projected a snapshot since
85
+ # this one last read it, and mutating *its* stale copy would
86
+ # overwrite that write on disk rather than layer on top of it.
87
+ def project(entry)
88
+ current = read
89
+ entry.save? ? current[entry.id] = entry.state.dup : current.delete(entry.id)
90
+ write(current)
91
+ @store = current
92
+ entry
93
+ end
94
+
95
+ def save(instance)
96
+ entry = Ports::Persistence::Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
97
+ with_lock do
98
+ append(entry)
99
+ project(entry)
100
+ end
101
+ instance
102
+ end
103
+
104
+ def delete(id)
105
+ return false unless find(id)
106
+
107
+ entry = Ports::Persistence::Entry.new(operation: "delete", id: id.to_s, state: nil)
108
+ with_lock do
109
+ append(entry)
110
+ project(entry)
111
+ end
112
+ true
113
+ end
114
+
115
+ def record_event(event) = @events << event
116
+
117
+ def events = @events
118
+
119
+ # ── the OPTIONAL saga-persistence capability (§2) — Heki's own
120
+ # shape (a sibling snapshot+journal file pair, `SagaStore`,
121
+ # heki/saga_store.rb) rather than a table in a store this adapter
122
+ # doesn't have.
123
+ def save_saga(process_manager:, correlation:, state:, memory:, completed_compensations: [])
124
+ saga_store.save_saga(@domain, process_manager.to_s, correlation.to_s, state.to_s, memory, completed_compensations)
125
+ end
126
+
127
+ def delete_saga(process_manager:, correlation:)
128
+ saga_store.delete_saga(@domain, process_manager.to_s, correlation.to_s)
129
+ end
130
+
131
+ def each_saga(&block) = saga_store.each_saga(@domain, &block)
132
+
133
+ private
134
+
135
+ def saga_store
136
+ @saga_store ||= SagaStore.new(File.dirname(@path))
137
+ end
138
+
139
+ def instance(id, record)
140
+ Runtime::Instance.new(
141
+ aggregate: @aggregate,
142
+ id: id,
143
+ state: record.transform_keys(&:to_sym)
144
+ )
145
+ end
146
+
147
+ def store
148
+ @store ||= read
149
+ end
150
+
151
+ def read
152
+ snapshot = read_snapshot
153
+ replay_journal(snapshot)
154
+ end
155
+
156
+ # `dir: :default` — a bare Symbol, the framework's own convention
157
+ # for "a DECLARED value that resolves by convention, never a silent
158
+ # fallback" — used to crash `File.join` outright
159
+ # (`TypeError: no implicit conversion of Symbol into String`):
160
+ # `resolve_path` only ever checked for a MISSING `dir` setting,
161
+ # never a Symbol one. Treated the same as no setting at all — falls
162
+ # back to the existing "data" default, not a new special case.
163
+ def resolve_path(settings, root)
164
+ declared =
165
+ if settings.key?(:dir)
166
+ settings[:dir]
167
+ elsif settings.key?("dir")
168
+ settings["dir"]
169
+ else
170
+ "data"
171
+ end
172
+ declared = "data" if declared == :default
173
+ dir = declared.start_with?("/") ? declared : File.join(root || Dir.pwd, declared)
174
+
175
+ File.join(dir, "#{@aggregate.storage_name}.heki")
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,3 @@
1
+ Hecks.adapter "IdentityRegistry" do
2
+ port "identity_resolution"
3
+ end
@@ -0,0 +1,26 @@
1
+ module Hecks
2
+ module Adapters
3
+ # THE `identity_resolution` PORT, FULFILLED BY THE Identity FRAMEWORK
4
+ # BLUEBOOK — same registry, same boot, so this is a dispatch against
5
+ # records already sitting in the store this adapter is handed, not a
6
+ # bridge to a second runtime. Same reasoning `GovernanceAuthorization`
7
+ # gives for itself, one port over.
8
+ module IdentityRegistry
9
+ module_function
10
+
11
+ # `nil` for a pair nothing has linked, the first match otherwise —
12
+ # `ResolvedBy` is a lookup by the exact (issuer, subject) an
13
+ # authenticated token carries, not a listing, so more than one row
14
+ # would mean two links share a pair, which `Link`'s own natural
15
+ # key already prevents by construction.
16
+ def resolve(registry, issuer:, subject:)
17
+ rows = Runtime::Dispatcher.new(registry).query(
18
+ "Identity::ExternalIdentifier.ResolvedBy",
19
+ issuer: { value: issuer.to_s }, subject: { value: subject.to_s }
20
+ )
21
+
22
+ rows.first&.fetch(:identity)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,51 @@
1
+ require_relative "../../ports/query/in_memory"
2
+ require_relative "../../ports/query/ordering"
3
+ require_relative "../../query_specification/common/order_by"
4
+ require_relative "../../query_specification/field_path"
5
+ require_relative "../../runtime/errors"
6
+
7
+ module Hecks
8
+ module Adapters
9
+ # SHARED BY every adapter that holds decoded Ruby records rather than
10
+ # running real SQL (Memory, Lambda, Heki) — the identical dotted-path
11
+ # value-object member-picking Postgres/Sqlite/D1's own
12
+ # order_expression does (numeric member wins, else the one-field
13
+ # convention "value"), just walked in Ruby instead of compiled to a
14
+ # JSONB/json_extract path, because there is no query engine
15
+ # underneath any of these three to hide that walk inside.
16
+ module InMemoryOrdering
17
+ module_function
18
+
19
+ # order_by IS A RUNTIME VALUE (an HTTP query param, in the
20
+ # console's case), not framework-authored bluebook source — see
21
+ # postgres.rb's own all for the full reasoning. Whitelisted the
22
+ # identical way before FieldPath.dig ever runs.
23
+ def ordered(records, aggregate:, order_by:, direction:)
24
+ return records unless order_by
25
+
26
+ name = order_by.to_s.split(".").first
27
+ raise Runtime::WiringError, "#{aggregate.name} has no attribute #{order_by.inspect} to order by" unless aggregate.lifecycle&.field.to_s == name || aggregate.attribute(name)
28
+
29
+ path = sortable_path(aggregate, order_by)
30
+ spec = QuerySpecification::Common::OrderBy.new(field: order_by, direction: direction)
31
+ Ports::Query::Ordering.apply(records, spec, nil, identity: ->(record) { record.id.to_s }) do |record|
32
+ Ports::Query::InMemory.comparable(QuerySpecification::FieldPath.dig(record, path))
33
+ end
34
+ end
35
+
36
+ def sortable_path(aggregate, field)
37
+ name, *path = field.to_s.split(".")
38
+ return field.to_s unless path.empty?
39
+
40
+ attribute = aggregate.attribute(name)
41
+ return field.to_s if aggregate.lifecycle&.field.to_s == name || attribute.nil?
42
+
43
+ vo = aggregate.value_object(attribute.type)
44
+ return field.to_s unless vo
45
+
46
+ member = vo.attributes.find { |a| %w[Integer Float].include?(a.type) }&.name || "value"
47
+ "#{name}.#{member}"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,63 @@
1
+ require "json"
2
+
3
+ module Hecks
4
+ module Adapters
5
+ class Lambda
6
+ # THE THIN AWS CLIENT — shared by this adapter's own read methods
7
+ # (Lambda#find/#all/#count/#query) and Runtime::RemoteDispatcher's
8
+ # write methods. One Lambda invoke, one JSON round trip, nothing
9
+ # domain-specific: neither caller needs to know an AWS SDK is
10
+ # involved at all.
11
+ #
12
+ # FUNCTION NAME IS COMPUTED, NOT PASSED — `"hecks-#{domain}"`,
13
+ # lowercased, matches bin/project_deploy's own `stack_name`
14
+ # exactly (bin/project_deploy: `stack_name = "hecks-#{domain_name}"`,
15
+ # `domain_name = File.basename(domain)`). `domain` here is the
16
+ # bluebook's OWN declared name (`Embryonaut`, not the directory);
17
+ # today's real corpus has directory name == declared name
18
+ # lowercased for every domain that deploys, so `.downcase` alone
19
+ # reproduces the same string bin/project_deploy computes from the
20
+ # directory — this is a real, load-bearing assumption, not a
21
+ # coincidence to lean on silently forever if that ever stops
22
+ # holding.
23
+ class Client
24
+ def initialize(domain:, region:)
25
+ require "aws-sdk-lambda"
26
+ @function_name = "hecks-#{domain.to_s.downcase}"
27
+ @client = Aws::Lambda::Client.new(region: region)
28
+ end
29
+
30
+ # THE WHOLE DOMAIN, EVERY TIME — matches dispatch::read's own
31
+ # rehydrate-the-full-journal design (Phase 1, rust/host). No
32
+ # caching here, deliberately not even per-request: Lambda#all
33
+ # used to memoize this across calls, which silently served
34
+ # stale reads for a warm web process's whole lifetime once a
35
+ # write happened elsewhere — see Lambda#instances's own comment
36
+ # on the real, live bug that caught it.
37
+ def read
38
+ invoke({ "read" => true })
39
+ end
40
+
41
+ def dispatch(verb, args, role: nil)
42
+ payload = { "verb" => verb, "args" => args }
43
+ payload["role"] = role if role
44
+ invoke(payload)
45
+ end
46
+
47
+ private
48
+
49
+ def invoke(payload)
50
+ response = @client.invoke(function_name: @function_name, payload: JSON.generate(payload))
51
+ body = JSON.parse(response.payload.read)
52
+
53
+ if response.function_error
54
+ raise Runtime::WiringError,
55
+ "Lambda #{@function_name} (#{response.function_error}): #{body['errorMessage'] || body}"
56
+ end
57
+
58
+ body
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,4 @@
1
+ Hecks.adapter "Lambda" do
2
+ port "persistence"
3
+ field :region
4
+ end
@@ -0,0 +1,145 @@
1
+ require_relative "lambda/client"
2
+ require_relative "../../ports/query/in_memory"
3
+ require_relative "../../ports/persistence/remote_runtime"
4
+ require_relative "in_memory_ordering"
5
+ require_relative "../../runtime/instance"
6
+
7
+ module Hecks
8
+ module Adapters
9
+ # THE READ-SIDE HALF OF LAMBDA ROUTING — `persisted_by("Lambda")`'s
10
+ # own adapter, resolved through the SAME `Ports::Persistence`
11
+ # machinery `persisted_by("Postgres")`/`persisted_by("Memory")`
12
+ # already use (`Runtime::Registry#repository`, `RepositoryFactory
13
+ # .build`) — no new framework plumbing, just a new adapter class at
14
+ # the name the existing lookup already expects. See
15
+ # `Runtime::RemoteDispatcher` for the write-side half; the two
16
+ # compose through one shared `Client` per domain rather than each
17
+ # inventing its own AWS wiring.
18
+ #
19
+ # READ-ONLY, DELIBERATELY — `append`/`project` raise rather than
20
+ # silently no-op. A write reaching this class would mean
21
+ # `Runtime::CommandInterpreter` ran locally against a Lambda-routed
22
+ # domain, which is exactly the bypass `Runtime::RemoteDispatcher`
23
+ # exists to prevent (business-rule validation must happen in Rust
24
+ # for these domains, not be pre-computed here against an
25
+ # incomplete local view). A loud failure here is the correct
26
+ # outcome, not a bug to route around.
27
+ class Lambda
28
+ include Ports::Persistence::RemoteRuntime
29
+
30
+ attr_reader :aggregate
31
+
32
+ def initialize(aggregate:, settings: {}, root: nil)
33
+ @aggregate = aggregate
34
+ domain =
35
+ if settings.key?(:domain)
36
+ settings[:domain]
37
+ elsif settings.key?("domain")
38
+ settings["domain"]
39
+ else
40
+ aggregate.name
41
+ end
42
+ region =
43
+ if settings.key?(:region)
44
+ settings[:region]
45
+ elsif settings.key?("region")
46
+ settings["region"]
47
+ else
48
+ "us-east-1"
49
+ end
50
+ # TWO DIFFERENT "domain"s, deliberately not conflated: `domain`
51
+ # (this aggregate's OWN bluebook name — "Identity", "Governance")
52
+ # only ever prefixes the instances lookup, since that's how
53
+ # rust/host's own `Store::instances()` keys every record
54
+ # (`registry.rb`'s own `"#{a[:domain_name]}::#{a[:name]}#"`
55
+ # dump format, unchanged by which chapter attached it). The
56
+ # FUNCTION to actually call is a different question: Governance
57
+ # and Identity aggregates are compiled into the ATTACHING
58
+ # domain's own Lambda (one merged `Store` per target — Phase 0's
59
+ # framework-bluebook work), never a function of their own, so
60
+ # `settings[:domain]` is the wrong signal for `Client.new`.
61
+ # `root` is the boot's own directory (`Registry#root`, shared by
62
+ # EVERY bluebook in one registry regardless of which one
63
+ # attached it) — `File.basename(root)` reproduces the exact
64
+ # same string `bin/project_deploy`'s own `stack_name` computes
65
+ # from the domain PATH, so the two can never name two
66
+ # different functions for the same deploy... on a LOCAL boot,
67
+ # where `root` is a real project directory. Inside the deployed
68
+ # Lambda itself `root` is ALWAYS `/var/task` (every Lambda's own
69
+ # fixed code root, regardless of domain) — `File.basename` gives
70
+ # "task", not the domain name, and invokes the wrong function
71
+ # entirely. A real, live AccessDeniedException on
72
+ # "hecks-task" caught this: invisible through every earlier
73
+ # phase's own verify step, all run from a local boot, until
74
+ # WebFunction became the first Ruby process to EVER make this
75
+ # exact call from inside a deployed Lambda. `DOMAIN_NAME` (set
76
+ # by bin/project_deploy's own WebFunction Environment) is the
77
+ # real, unambiguous signal in that specific context; the
78
+ # root-basename heuristic stays as the local-boot fallback,
79
+ # unchanged.
80
+ function_domain = ENV["DOMAIN_NAME"] || (root ? File.basename(root) : domain)
81
+ @client = Client.new(domain: function_domain, region: region)
82
+ @prefix = "#{domain}::#{aggregate.hecks_name}#"
83
+ end
84
+
85
+ def find(id)
86
+ instances[id.to_s]
87
+ end
88
+
89
+ def all(order_by: nil, direction: :asc)
90
+ InMemoryOrdering.ordered(instances.values, aggregate: @aggregate, order_by: order_by, direction: direction)
91
+ end
92
+
93
+ def count = instances.size
94
+
95
+ def query(specification, args = {}, context: {})
96
+ Ports::Query::InMemory.execute(instances.values, specification, args)
97
+ end
98
+
99
+ # `entries`/`append`/`project` — the append-only contract
100
+ # `Ports::Persistence::AppendOnly` requires of every adapter
101
+ # (raises at construction if any are missing). Answered by
102
+ # `Ports::Persistence::RemoteRuntime`, included above: `entries` is
103
+ # `[]` unconditionally (Lambda's own Postgres is already the
104
+ # durable store — rust/host's rehydrate-and-replay journal, Phase
105
+ # 1 — there is no local write-ahead log for `recover!` to replay),
106
+ # `append`/`project` raise rather than silently no-op.
107
+
108
+ private
109
+
110
+ # RE-FETCHED EVERY CALL, deliberately NOT memoized across them —
111
+ # this adapter itself is long-lived (one instance per aggregate,
112
+ # held by the registry `RUNTIME = Hecks.boot(...)` builds ONCE
113
+ # per Lambda web process — WebFunction's own top-level constant,
114
+ # reused warm across every HTTP request that process serves, not
115
+ # rebuilt per request the way a memoize-for-one-request comment
116
+ # here used to assume). A real, live bug caught this: a mutation
117
+ # dispatched fine (RemoteDispatcher always calls the dispatch
118
+ # Lambda fresh) and the very next `.all` on the SAME warm
119
+ # container kept returning the state from BEFORE that mutation,
120
+ # forever, until the container cold-started — memoizing here
121
+ # made every write invisible to every read on a warm container.
122
+ # Keyed by bare id (the part after "Domain::Aggregate#"), not the
123
+ # full "Domain::Aggregate#id" string — callers already know which
124
+ # aggregate they're asking this instance about.
125
+ def instances
126
+ @client.read.fetch("instances", {}).filter_map { |key, state|
127
+ next unless key.start_with?(@prefix)
128
+
129
+ [key.delete_prefix(@prefix), build_instance(key.delete_prefix(@prefix), state)]
130
+ }.to_h
131
+ end
132
+
133
+ # Lambda's own JSON response is already Ruby-decoded with STRING
134
+ # keys (plain `JSON.parse`, no `symbolize_names:`) — every other
135
+ # adapter's own `instance(row)` builder (Postgres, Sqlite) hands
136
+ # `Runtime::Instance.new` a SYMBOL-keyed state hash instead. A
137
+ # round trip through `JSON.generate`/`JSON.parse` is simpler and
138
+ # safer than a hand-rolled deep-symbolize helper for what is not
139
+ # a hot path.
140
+ def build_instance(id, state)
141
+ Runtime::Instance.new(aggregate: @aggregate, id: id, state: JSON.parse(JSON.generate(state), symbolize_names: true))
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,3 @@
1
+ Hecks.adapter "Memory" do
2
+ port "persistence"
3
+ end
@@ -0,0 +1,103 @@
1
+ require_relative "../../ports/persistence/append_only"
2
+ require_relative "../../ports/query/in_memory"
3
+ require_relative "in_memory_ordering"
4
+ require_relative "../../runtime/instance"
5
+
6
+ module Hecks
7
+ module Adapters
8
+ class Memory
9
+ # TENANT-CAPABLE TRIVIALLY — see Runtime::TenantCheck's own header
10
+ # for the full reasoning. `@records` is a plain instance variable;
11
+ # two `Runtime.boot` calls build two entirely separate Registry
12
+ # objects and, through them, two entirely separate Memory
13
+ # instances, so two tenant boots never share this adapter's state
14
+ # by construction — nothing here needs to know "tenant" exists.
15
+ def self.tenant_capable? = true
16
+ def persistence_capabilities = [:atomic_put]
17
+
18
+ attr_reader :aggregate
19
+
20
+ def initialize(aggregate:, settings: {}, root: nil)
21
+ @aggregate = aggregate
22
+ @records = {}
23
+ @events = []
24
+ @entries = []
25
+ end
26
+
27
+ def find(id) = @records[id.to_s]
28
+ def count = @records.size
29
+
30
+ def all(order_by: nil, direction: :asc)
31
+ InMemoryOrdering.ordered(@records.values, aggregate: @aggregate, order_by: order_by, direction: direction)
32
+ end
33
+
34
+ def query(specification, args = {}, context: {})
35
+ Ports::Query::InMemory.execute(all, specification, args, registry: context[:registry])
36
+ end
37
+
38
+ def append(entry)
39
+ @entries << entry
40
+ entry
41
+ end
42
+
43
+ def project(entry)
44
+ if entry.save?
45
+ @records[entry.id] = Runtime::Instance.new(aggregate: @aggregate, id: entry.id, state: entry.state.dup)
46
+ else
47
+ @records.delete(entry.id)
48
+ end
49
+ end
50
+
51
+ def save(instance)
52
+ entry = Ports::Persistence::Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
53
+ append(entry)
54
+ project(entry)
55
+ end
56
+
57
+ # One in-memory critical section in the only thread touching this plain
58
+ # Hash: classify and replace without a preliminary repository lookup.
59
+ # Durable append and projection remain ordered exactly as ordinary save.
60
+ def atomic_put(entry, insert_only: false)
61
+ exists = @records.key?(entry.id.to_s)
62
+ return :conflicted if insert_only && exists
63
+
64
+ status = exists ? :replaced : :inserted
65
+ append(entry)
66
+ project(entry)
67
+ status
68
+ end
69
+
70
+ def delete(id)
71
+ entry = Ports::Persistence::Entry.new(operation: "delete", id: id.to_s, state: nil)
72
+ append(entry)
73
+ project(entry)
74
+ end
75
+
76
+ def record_event(event) = @events << event
77
+
78
+ def events = @events
79
+
80
+ def entries = @entries.dup
81
+
82
+ # Every other driven adapter (Postgres/PostgresEra/Sqlite/D1)
83
+ # already implements this — `Ports::Persistence::AppendOnly#reset!`
84
+ # forwards to it and only raises when the wrapped adapter doesn't
85
+ # respond to `reset!` at all, which was always this adapter's own
86
+ # gap, not a deliberate omission (nothing about "in memory" implies
87
+ # "cannot be cleared"). Existing callers that fully re-`Hecks.boot`
88
+ # a domain per test case don't need this — they get a brand new
89
+ # `Memory` instance, with brand new empty `@records`/`@events`/
90
+ # `@entries`, for free. This is for the other case: a caller that
91
+ # deliberately keeps ONE booted runtime across many cases (to skip
92
+ # `load_domain`'s own per-boot parse/verify cost) and wants each
93
+ # case to start from the same clean slate `Hecks.boot` would have
94
+ # given it, without paying for a fresh boot to get there.
95
+ def reset!
96
+ @records = {}
97
+ @events = []
98
+ @entries = []
99
+ self
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,3 @@
1
+ Hecks.adapter "MockStripeAdapter" do
2
+ port "checkout"
3
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Adapters
5
+ # A GENERIC STAND-IN for a real Checkout-Session-style adapter — no
6
+ # network call, no real account, matching whatever real payment
7
+ # adapter a project pairs it with by shape alone
8
+ # (`create_session(event:, registration_id:, success_url:, cancel_url:)
9
+ # -> a URL string`), the same way `Memory` stands in for a real
10
+ # persistence adapter without knowing anything about the domain bound
11
+ # to it. Never reads `event`'s own fields — a real adapter needs the
12
+ # price/name inside it to build a session a payer actually sees;
13
+ # this only needs to look enough like one to swap in.
14
+ #
15
+ # The URL it returns carries the same `registration_id` a real
16
+ # webhook's metadata would, so a caller building its own signed
17
+ # webhook against it (a smoke test, CI) has something real to key
18
+ # off of — the real shape production has, minus the network hop.
19
+ class MockStripeAdapter
20
+ def initialize(aggregate: nil, settings: {}, root: nil); end
21
+
22
+ def create_session(event:, registration_id:, success_url:, cancel_url:)
23
+ separator = success_url.include?("?") ? "&" : "?"
24
+ "#{success_url}#{separator}mock_checkout=1&mock_registration_id=#{registration_id}"
25
+ end
26
+ end
27
+ end
28
+ end