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,342 @@
1
+ require_relative "bluebook/meta_validator"
2
+
3
+ module Hecks
4
+ # SHARED MACHINERY for a codemod that migrates real `.bluebook` source
5
+ # once a DSL builder change makes some previously-required declaration
6
+ # optional/redundant — pulled out of `bin/codemod_implicit_append_fields`
7
+ # (the first one built), which needed three real, hard-won fixes before
8
+ # it could be trusted: a process-lifetime AST cache with no
9
+ # invalidation, a batch-revert granularity that let one unsafe
10
+ # candidate sink every other safe one sharing its boot, and (in the
11
+ # SPINE the codemod migrates FOR, not here) an append-at-end insertion
12
+ # that only round-tripped correctly for whichever field happened to be
13
+ # last. None of those are guessable in advance; they only surface by
14
+ # actually running a real edit against real self-hosted code. This
15
+ # module is that lesson, kept — the NEXT codemod plugs in two rule-
16
+ # specific procs (`find_candidates`, `apply_candidate`) and inherits
17
+ # the boot/safety-net machinery rather than rediscovering it.
18
+ #
19
+ # A CODEMOD IS NOT PATTERN-MATCHING ALONE. Deciding "is this line safe
20
+ # to delete" means knowing what the RUNTIME would resolve it to — so
21
+ # every codemod built on this module follows the same three steps:
22
+ # 1. Boot the real domain (or the self-hosted meta-domain) and read
23
+ # its IR to find CANDIDATES — provided by the caller's own
24
+ # `find_candidates`, since the actual redundancy rule is specific
25
+ # to whichever spine change this migration serves.
26
+ # 2. Locate and remove each candidate's own source text — the
27
+ # caller's own `apply_candidate`.
28
+ # 3. Re-boot from the edited text and diff the full IR export
29
+ # against the pre-edit export. Byte-identical -> keep. Anything
30
+ # else (including a RAISED exception — the self-hosted
31
+ # meta-domain DISPATCHES itself into being, S14, so a bad edit
32
+ # can surface as a runtime refusal, not just a differing export)
33
+ # -> revert and report SKIPPED, never silently guessed past.
34
+ #
35
+ # DESIGN-TIME CHECKLIST for the SPINE change a future codemod migrates
36
+ # corpus text for — both items below are real bugs THIS module's own
37
+ # first use found, not hypothetical:
38
+ # - Does the resolved value get inserted into an ORDER-SENSITIVE
39
+ # list (the exported IR is array-order-sensitive throughout)? If
40
+ # so, the spine's own insertion must preserve the ORIGINAL
41
+ # position, not just append — an append-at-end insertion only
42
+ # round-trips correctly for a value that already happened to be
43
+ # last.
44
+ # - Does resolution depend on ANOTHER construct already being
45
+ # declared (the aggregate/entity a `sets`/`append:` field resolves
46
+ # against)? If a creator command can be declared BEFORE the
47
+ # construct it creates (real, live: `command "Handler"` before
48
+ # `entity "Handler"`, one file down), one-pass resolution
49
+ # genuinely cannot see it yet — not a codemod bug, a structural
50
+ # limit worth naming rather than working around.
51
+ module Codemod
52
+ ROOT = File.expand_path("../..", __dir__)
53
+
54
+ EXAMPLE_ROOTS = Dir.glob(File.join(ROOT, "examples", "*")).select { |p| File.directory?(p) }.sort
55
+ META_FILES = (Dir.glob(File.join(ROOT, "lib/hecks/grammar/*.bluebook")) +
56
+ Dir.glob(File.join(ROOT, "lib/hecks/framework/bluebook/*.bluebook")) +
57
+ Dir.glob(File.join(ROOT, "lib/hecks/language/bluebook/**/*.bluebook"))).sort
58
+
59
+ # THE SAME LIGHTWEIGHT PATH `spec/spec_helper.rb`'s own
60
+ # `boot_in_memory` uses — `Hecks.with_registry` satisfies
61
+ # `Hecks.bluebook`'s own `collect`'s "loaded outside a boot" check
62
+ # without `Hecks.boot`'s full era-check/adapter-wiring path, which
63
+ # needs a LIVE Postgres connection for `compliance` and would make
64
+ # every codemod depend on a database it has no reason to touch — a
65
+ # codemod only ever reads a chapter's own declared IR, never a
66
+ # stored record.
67
+ PERSISTENCE_PORT = File.join(ROOT, "lib/hecks/ports/persistence.port")
68
+ EXTRACTION_PORT = File.join(ROOT, "lib/hecks/ports/extraction.port")
69
+ MEMORY_ADAPTER = File.join(ROOT, "lib/hecks/adapters/driven/memory.adapter")
70
+ PRISM_ADAPTER = File.join(ROOT, "lib/hecks/adapters/driven/prism.adapter")
71
+
72
+ def self.export_json(registry) = Hecks::Projector::Exporter.json(registry)
73
+
74
+ # `Hecks::Adapters::Prism` caches a file's parsed AST for the
75
+ # life of the PROCESS, keyed by path — fine for every existing
76
+ # caller (a file loads once per process: one `bin/ir` run, one
77
+ # rspec worker), but a codemod legitimately reloads the SAME path
78
+ # after editing it, and a stale cached tree reports a
79
+ # `given`/`ensures` block at its OLD line number, which no longer
80
+ # matches the freshly re-executed file's own `block.source_location`
81
+ # — surfacing as "did not survive extraction" on a perfectly valid
82
+ # file. `Prism.forget` is the real invalidation API this module's
83
+ # own first use motivated (found here, fixed at the source rather
84
+ # than left as a private `TREES.clear` poke from outside).
85
+ def self.load_bluebook(path)
86
+ paths = if path.is_a?(Array)
87
+ path
88
+ elsif File.directory?(path)
89
+ Dir.glob(File.join(path, "*.bluebook"))
90
+ else
91
+ [path]
92
+ end
93
+ paths.each { |file| Hecks::Adapters::Prism.forget(file) }
94
+
95
+ registry = Hecks::Runtime::Registry.new
96
+ Hecks.with_registry(registry) do
97
+ Kernel.load(PERSISTENCE_PORT)
98
+ Kernel.load(EXTRACTION_PORT)
99
+ Kernel.load(MEMORY_ADAPTER)
100
+ Kernel.load(PRISM_ADAPTER)
101
+ Hecks::Bluebook::MetaValidator.defer { paths.each { |file| Kernel.load(file) } }
102
+ Hecks::Bluebook::MetaValidator.judge_deferred!(registry)
103
+ end
104
+ registry
105
+ end
106
+
107
+ # `forget_all`, not a single `forget` — the meta-domain is NINE
108
+ # files (`MetaValidator::GRAMMAR_FILES`) merged into one registry,
109
+ # and a caller here (the codemod runner) doesn't generally know in
110
+ # advance which ONE it just edited.
111
+ def self.boot_meta
112
+ Hecks::Adapters::Prism.forget_all
113
+ Hecks::Bluebook::MetaValidator.instance_variable_set(:@grammar_registry, nil)
114
+ export_json(Hecks::Bluebook::MetaValidator.grammar_registry)
115
+ end
116
+
117
+ def self.meta_registry
118
+ Hecks::Adapters::Prism.forget_all
119
+ Hecks::Bluebook::MetaValidator.instance_variable_set(:@grammar_registry, nil)
120
+ Hecks::Bluebook::MetaValidator.grammar_registry
121
+ end
122
+
123
+ # Walks every aggregate (and every nested entity, recursively)
124
+ # across every chapter in a booted registry, yielding [owning
125
+ # construct, command] pairs — `construct` is whichever
126
+ # Aggregate/Entity actually OWNS the command, the same distinction
127
+ # `AggregateBuilder#command` vs `EntityBuilder#command` already
128
+ # draws. Generic enough for any rule that needs to walk real
129
+ # commands, not specific to the attribute-redundancy rule.
130
+ def self.each_command(registry)
131
+ registry.bluebooks.each_value do |chapter|
132
+ chapter.aggregates.each do |aggregate|
133
+ walk = lambda do |construct|
134
+ construct.commands.each { |command| yield construct, command }
135
+ construct.entities.each(&walk) if construct.respond_to?(:entities)
136
+ end
137
+ walk.call(aggregate)
138
+ end
139
+ end
140
+ end
141
+
142
+ def self.owner_attribute(construct, name)
143
+ construct.attributes.find { |attr| attr.name.to_s == name.to_s }
144
+ end
145
+
146
+ # A LIST attribute's own element construct — the value object or
147
+ # entity `list_of(...)` names, resolved by `hecks_name` the same way
148
+ # `AttributeCollector#resolve_identity_field!` already does. Shared
149
+ # because "what does this list actually hold" is a question any
150
+ # append-shaped rule needs answered, not just this one.
151
+ def self.element_construct_for(construct, list_field)
152
+ list_attr = owner_attribute(construct, list_field)
153
+ return nil unless list_attr&.list?
154
+
155
+ pool = construct.respond_to?(:value_objects) ? construct.value_objects.dup : []
156
+ pool.concat(construct.entities) if construct.respond_to?(:entities)
157
+ pool.find { |c| c.hecks_name.to_s == list_attr.type.to_s }
158
+ end
159
+
160
+ # Either a raised exception OR a differing export counts as unsafe
161
+ # — see the module header on why the meta-domain specifically can
162
+ # raise. Returns [value_or_nil, error_message_or_nil].
163
+ def self.safely
164
+ [yield, nil]
165
+ rescue StandardError => e
166
+ [nil, "#{e.class}: #{e.message}"]
167
+ end
168
+
169
+ # THE REUSABLE RUNNER — every real bug fix this module carries lives
170
+ # here, not in a caller's own script. A caller supplies:
171
+ #
172
+ # find_candidates: ->(registry) { [...] }
173
+ # Given a booted registry, return every CANDIDATE this rule
174
+ # could migrate. A candidate is whatever shape the caller wants
175
+ # — `apply_candidate` is the only other thing that reads it.
176
+ #
177
+ # apply_candidate: ->(text, candidate) { [new_text, changed_bool] }
178
+ # Given one file's current text and one candidate, return the
179
+ # edited text and whether a match was actually found/removed —
180
+ # `false` (text unchanged) when the candidate doesn't apply to
181
+ # THIS file, which is how the meta-domain's multi-file search
182
+ # below finds the right one without the caller needing to know
183
+ # which file a candidate lives in ahead of time.
184
+ #
185
+ # label: ->(candidate) { "..." }
186
+ # One-line description for the results report.
187
+ class Runner
188
+ def initialize(find_candidates:, apply_candidate:, label:)
189
+ @find_candidates = find_candidates
190
+ @apply_candidate = apply_candidate
191
+ @label = label
192
+ end
193
+
194
+ def run(dry_run: false)
195
+ results = { applied: [], skipped: [], clean: [] }
196
+ run_example_domains(results, dry_run)
197
+ run_meta_domain(results, dry_run)
198
+ results
199
+ end
200
+
201
+ def report(results, dry_run:)
202
+ puts "== results (#{dry_run ? 'DRY RUN — nothing written' : 'applied'}) =="
203
+ puts "clean (no candidates): #{results[:clean].join(', ')}" unless results[:clean].empty?
204
+ results[:applied].each { |r| puts "APPLIED #{r[:file]}: #{r[:candidates].join(', ')}" }
205
+ results[:skipped].each { |r| puts "SKIPPED #{r[:file]} (#{r[:reason]}): #{Array(r[:candidates]).join(', ')}" }
206
+ end
207
+
208
+ private
209
+
210
+ def apply_many(text, candidates)
211
+ applied = []
212
+ candidates.each do |c|
213
+ text, changed = @apply_candidate.call(text, c)
214
+ applied << c if changed
215
+ end
216
+ [text, applied]
217
+ end
218
+
219
+ # The per-candidate write/verify/revert sequence below is one
220
+ # coherent unit (see the comment ahead of the candidates.each
221
+ # loop for why it can't batch) — splitting it across methods just
222
+ # to satisfy the line count would scatter state four ways for no
223
+ # readability gain.
224
+ # rubocop:disable-next Metrics/BlockLength
225
+ def run_example_domains(results, dry_run)
226
+ Codemod::EXAMPLE_ROOTS.each do |domain_dir|
227
+ bluebook_files = Dir.glob(File.join(domain_dir, "bluebook", "*.bluebook"))
228
+ next if bluebook_files.empty?
229
+
230
+ before_json = Codemod.export_json(Codemod.load_bluebook(bluebook_files))
231
+ candidates = @find_candidates.call(Codemod.load_bluebook(bluebook_files))
232
+
233
+ if candidates.empty?
234
+ results[:clean] << domain_dir
235
+ next
236
+ end
237
+
238
+ live = bluebook_files.to_h { |file| [file, File.read(file)] }
239
+ applied_by_file = Hash.new { |hash, file| hash[file] = [] }
240
+
241
+ candidates.each do |candidate|
242
+ target_file = bluebook_files.find { |file| apply_many(live[file], [candidate]).last.any? }
243
+ unless target_file
244
+ results[:skipped] << { file: domain_dir, reason: "no matching source line", candidates: [@label.call(candidate)] }
245
+ next
246
+ end
247
+
248
+ original_text = live[target_file]
249
+ text, = apply_many(original_text, [candidate])
250
+ live[target_file] = text
251
+ File.write(target_file, text)
252
+ after_json, error = Codemod.safely do
253
+ Codemod.export_json(Codemod.load_bluebook(bluebook_files))
254
+ end
255
+
256
+ if after_json == before_json && !dry_run
257
+ applied_by_file[target_file] << candidate
258
+ elsif after_json == before_json
259
+ live[target_file] = original_text
260
+ File.write(target_file, original_text)
261
+ Codemod.safely { Codemod.load_bluebook(bluebook_files) }
262
+ applied_by_file[target_file] << candidate
263
+ else
264
+ live[target_file] = original_text
265
+ File.write(target_file, original_text)
266
+ Codemod.safely { Codemod.load_bluebook(bluebook_files) }
267
+ reason = error ? "reboot raised after edit (#{error}) — reverted" : "IR changed after edit — reverted"
268
+ results[:skipped] << { file: target_file, reason: reason, candidates: [@label.call(candidate)] }
269
+ end
270
+ end
271
+
272
+ applied_by_file.each do |file, applied|
273
+ results[:applied] << { file: file, candidates: applied.map(&@label) }
274
+ end
275
+ end
276
+ end
277
+
278
+ # PER-CANDIDATE, not one batched write-then-verify — the
279
+ # meta-domain is ONE shared registry (SyntaxBoot DISPATCHES it
280
+ # into itself, S14), so a single unsafe candidate among many would
281
+ # otherwise sink every OTHER, genuinely safe candidate in the same
282
+ # run: this module's first real run found exactly that (25
283
+ # candidates, one dispatch-time break, all 25 reverted as a batch
284
+ # before this per-candidate loop existed). `before_meta` stays the
285
+ # fixed reference throughout — a truly safe candidate never
286
+ # changes the IR, so the running state's own export should always
287
+ # still equal the pristine original after each kept edit, by
288
+ # definition, no moving target needed.
289
+ def run_meta_domain(results, dry_run)
290
+ before_meta = Codemod.boot_meta
291
+ meta_candidates = @find_candidates.call(Codemod.meta_registry)
292
+
293
+ if meta_candidates.empty?
294
+ results[:clean] << "meta-domain"
295
+ return
296
+ end
297
+
298
+ live_meta = Codemod::META_FILES.to_h { |f| [f, File.read(f)] }
299
+ applied_by_file = Hash.new { |h, k| h[k] = [] }
300
+
301
+ meta_candidates.each do |candidate|
302
+ target_file = Codemod::META_FILES.find { |f| apply_many(live_meta[f], [candidate]).last.any? }
303
+ unless target_file
304
+ results[:skipped] << { file: "meta-domain", reason: "no matching source line", candidates: [@label.call(candidate)] }
305
+ next
306
+ end
307
+
308
+ text, = apply_many(live_meta[target_file], [candidate])
309
+ original_text = live_meta[target_file]
310
+ live_meta[target_file] = text
311
+ Codemod::META_FILES.each { |f| File.write(f, live_meta[f]) }
312
+
313
+ # DRY RUN STILL VERIFIES — see run_example_domains' own
314
+ # comment; the edit is always written and rebooted for real,
315
+ # then always reverted afterward when dry-run (whether or not
316
+ # it was safe) so the next candidate is judged against the
317
+ # true original state, never an accumulated hypothetical.
318
+ after_meta, error = Codemod.safely { Codemod.boot_meta }
319
+
320
+ if after_meta == before_meta && !dry_run
321
+ applied_by_file[target_file] << candidate
322
+ elsif after_meta == before_meta # dry-run, safe — revert, but count as applied
323
+ live_meta[target_file] = original_text
324
+ File.write(target_file, original_text)
325
+ Codemod.safely { Codemod.boot_meta }
326
+ applied_by_file[target_file] << candidate
327
+ else
328
+ live_meta[target_file] = original_text
329
+ File.write(target_file, original_text)
330
+ Codemod.safely { Codemod.boot_meta } # restore cache/memoized state to the reverted text
331
+ reason = error ? "reboot raised (#{error})" : "IR changed"
332
+ results[:skipped] << { file: "meta-domain", reason: reason, candidates: [@label.call(candidate)] }
333
+ end
334
+ end
335
+
336
+ applied_by_file.each do |file, candidates|
337
+ results[:applied] << { file: file, candidates: candidates.map(&@label) }
338
+ end
339
+ end
340
+ end
341
+ end
342
+ end
@@ -0,0 +1,71 @@
1
+ module Hecks
2
+ # The invisible field a built construct carries.
3
+ #
4
+ # A construct is a RECORD WITH AN OWNER CHAIN — the chapter (Bluebook)
5
+ # owns its aggregates, an aggregate owns everything declared on it — and the
6
+ # bluebook identity is carried in its own field, under a `hecks_` prefix that
7
+ # no domain attribute can collide with. Invisible means exactly that: not an
8
+ # attribute, not a key in `to_h`, not a reader on instances. Framework
9
+ # metadata about the construct, not part of the domain it describes.
10
+ #
11
+ # The identity is COMPUTED by walking owners rather than stamped, so nothing
12
+ # has to be re-stamped when a chapter is assembled after its aggregates:
13
+ #
14
+ # Pizzas the chapter — no owner
15
+ # Pizzas::Pizza an aggregate joins its chapter with ::
16
+ # Pizzas::Pizza.Price everything else joins its owner with .
17
+ #
18
+ # That spelling is not invented here. It is the id `MetaValidator::Judge`
19
+ # already mints in `#identify`, so a construct and the meta-domain's
20
+ # record OF that construct carry the same identity, and there is no
21
+ # translation table between them to be quietly wrong in.
22
+ #
23
+ # Usage:
24
+ #
25
+ # price = Class.new(ValueObject) # a declaration holder
26
+ # price.hecks_name = "Price"
27
+ # price.hecks_owner = pizza_ir # the Aggregate that declares it
28
+ # price.hecks_fqn # => "Pizzas::Pizza.Price"
29
+ module Construct
30
+ # A construct asked for an identity it cannot compute.
31
+ class Unowned < StandardError; end
32
+
33
+ # What declares this one — the chapter above an aggregate, the aggregate
34
+ # above a value object. nil for a chapter, which is the top.
35
+ attr_accessor :hecks_owner
36
+
37
+ attr_writer :hecks_name
38
+
39
+ # A chapter is the only construct that legitimately has no owner. Everything
40
+ # else is DECLARED IN something, so a missing owner is an unstamped construct
41
+ # rather than a top — see hecks_fqn.
42
+ attr_writer :hecks_root
43
+
44
+ def hecks_root? = @hecks_root ? true : false
45
+
46
+ # The name as the bluebook declares it, never the constant path.
47
+ def hecks_name = @hecks_name
48
+
49
+ # How this construct joins its owner. An aggregate is a member of its
50
+ # chapter's namespace (`::`) ; everything else is declared ON its owner
51
+ # (`.`). Overridden by Aggregate, defaulted here for every other construct.
52
+ def hecks_separator = "."
53
+
54
+ # REFUSES rather than guesses. A construct with no owner and no claim to be a
55
+ # chapter has simply not been stamped yet — entity commands are in that state
56
+ # while entities are still IR objects — and answering the bare name would be a
57
+ # plausible half-truth that no test would notice. That shape of falsehood is
58
+ # what this repo keeps finding, so it goes red instead.
59
+ def hecks_fqn
60
+ return hecks_name.to_s if hecks_root?
61
+
62
+ unless hecks_owner
63
+ raise Construct::Unowned,
64
+ "#{hecks_name.inspect} cannot say what declares it, so it has no " \
65
+ "identity — it was never stamped with an owner"
66
+ end
67
+
68
+ "#{hecks_owner.hecks_fqn}#{hecks_separator}#{hecks_name}"
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,219 @@
1
+ # A TOOLING-INTERNAL DOMAIN, not a business one — never attached via
2
+ # `uses_framework`, never seen by an end-user domain. `bin/project_deploy`
3
+ # boots this directory directly (Ports::Loading.bootstrap + load_domain,
4
+ # the same pipeline lib/hecks/runtime/loader.rb uses, minus a
5
+ # `.world`/`.hecksagon` this domain has no need for) and dispatches one
6
+ # command against it before generating anything from a domain's own
7
+ # `deployed_to("AwsLambda")` settings.
8
+ #
9
+ # WHY THIS EXISTS: `world.bluebook`'s `Wiring`/`Setting` is a DELIBERATELY
10
+ # open, untyped map — "a verb's schema is not even contained in the file
11
+ # being parsed" (syntax.bluebook's own header on this). That's the right
12
+ # call for `Wiring` itself (an adapter verb's shape genuinely isn't the
13
+ # world language's business), but it leaves `deployed_to`'s ACTUAL shape
14
+ # validated nowhere — `bin/project_deploy` used to check it by hand
15
+ # (`deploy_settings[:adapter] == "AwsLambda" or abort ...`,
16
+ # `fetch(:region) { abort ... }`), the exact raw-Ruby-refusal pattern
17
+ # every other kind of bluebook mistake in this codebase does NOT use.
18
+ # This closes that gap the same way any other rule gets enforced here:
19
+ # as a real command, real invariants, a real corpus-testable refusal —
20
+ # not a hand-written abort string discovered only by running the script.
21
+ Hecks.bluebook "Deploy" do
22
+ aggregate "LambdaTarget" do
23
+ description "One domain's validated AWS Lambda deploy target — what deployed_to(\"AwsLambda\") must resolve to before bin/project_deploy generates anything from it."
24
+
25
+ identified_by :domain
26
+
27
+ attribute :domain, DomainName
28
+ attribute :region, Region
29
+ attribute :memory, Memory
30
+ attribute :timeout, Timeout
31
+ attribute :database, Database
32
+ attribute :web, Web
33
+
34
+ value_object "DomainName" do
35
+ attribute :value, String
36
+ invariant("a domain is named") { !value.to_s.empty? }
37
+ end
38
+
39
+ value_object "Region" do
40
+ attribute :value, String
41
+ invariant("a region is named") { !value.to_s.empty? }
42
+ end
43
+
44
+ # "Postgres" (a plain AWS::RDS::DBInstance, Embryonaut's own live
45
+ # choice — bin/project_deploy's Ruby-side default when a domain's
46
+ # own deployed_to("AwsLambda") names none, so every domain
47
+ # declared before this attribute existed keeps generating the
48
+ # identical template it always has) or "Aurora" (Aurora Serverless
49
+ # v2, Postgres-compatible — the SAME `Adapters::Postgres`/
50
+ # `tokio_postgres` code on both the Ruby and rust/host sides
51
+ # either way; this only changes which CloudFormation resource
52
+ # bin/project_deploy emits, never a runtime adapter).
53
+ # "Shared" — no RDS/VPC of this domain's own at all; it borrows
54
+ # another already-deployed domain's instance instead (the
55
+ # storehouse), isolated from that domain's own data by a native
56
+ # Postgres SCHEMA (this domain's own lowercase name), not a
57
+ # separate database. Requires a sibling `owner "OtherDomain"`
58
+ # setting inside the SAME deployed_to("AwsLambda") block, naming
59
+ # which already-deployed domain owns the shared instance —
60
+ # bin/project_deploy checks for that setting itself (Ruby-level,
61
+ # the same as `pg_version`/`google_oauth_present` below it), not
62
+ # here: which domain owns the instance is a deploy-time fact, not
63
+ # a business invariant this validated aggregate's job to hold.
64
+ value_object "Database" do
65
+ attribute :value, String, one_of: ["Postgres", "Aurora", "Shared"]
66
+ end
67
+
68
+ # "None" (the default — nothing changes for a domain that names
69
+ # neither this nor drops a `lambda_handler.rb`, e.g. Banking) or
70
+ # "Rust" — the domain's OWN Lambda (rust/host) also serves a public
71
+ # web UI in-process (rust/host/src/web.rs), reading HECKS_IR_PATH
72
+ # (a plain-JSON sidecar bin/project_wasm writes beside the .wasm
73
+ # artifact) and dispatching through the SAME in-process
74
+ # dispatch::handle/read the internal {"verb"}/{"read"} path already
75
+ # uses — no second Lambda, no VPC Interface Endpoint, no Ruby.
76
+ # Deliberately NOT "Ruby" as a third member here: a Ruby WebFunction
77
+ # is the pre-existing, SEPARATE `lambda_handler.rb`-file-presence
78
+ # mechanism (Embryonaut's own), untouched by this attribute —
79
+ # bin/project_deploy refuses generation outright if a domain
80
+ # declares `web "Rust"` AND has a `lambda_handler.rb`, rather than
81
+ # silently picking one.
82
+ value_object "Web" do
83
+ attribute :value, String, one_of: ["None", "Rust"]
84
+ end
85
+
86
+ # 128-10240 MB — AWS Lambda's own real ceiling and floor, not a
87
+ # number this domain invented. A typo here used to fail deep inside
88
+ # a real `sam deploy`, sometimes minutes in; now it refuses before
89
+ # a single file is generated.
90
+ value_object "Memory" do
91
+ attribute :value, Integer
92
+ invariant("memory is at least 128 MB") { value >= 128 }
93
+ invariant("memory is at most 10240 MB") { value <= 10240 }
94
+ end
95
+
96
+ # 900s — Lambda's own real ceiling.
97
+ value_object "Timeout" do
98
+ attribute :value, Integer
99
+ invariant("a timeout is positive") { value.positive? }
100
+ invariant("a timeout fits Lambda's own 900s ceiling") { value <= 900 }
101
+ end
102
+
103
+ # ALL FOUR REQUIRED — deliberately, not `optional: true` +
104
+ # `default:`. Confirmed directly against CommandInterpreter's own
105
+ # `normalize_args`/`coerce_declared_arguments` (interpreting.rb):
106
+ # `next unless normalized.key?(attribute.name)` skips coercion
107
+ # entirely for an absent key, so a command-level `default:` is
108
+ # honored ONLY at the JSON/Rust-codegen boundary (`scalar_from_json_
109
+ # expr`'s own domain), never by Ruby's own direct dispatch — a
110
+ # `default:` here would be quietly false advertising. "What memory
111
+ # defaults to when unspecified" stays exactly where it already
112
+ # lived (bin/project_deploy's own `fetch(:memory, 512)`); this
113
+ # command validates a FULLY-RESOLVED target, and bin/project_deploy
114
+ # resolves the default before ever dispatching.
115
+ command "Declare" do
116
+ goal "Validate one domain's AwsLambda deploy target before anything is generated from it"
117
+
118
+ attribute :domain, DomainName
119
+ attribute :region, Region
120
+ attribute :memory, Memory
121
+ attribute :timeout, Timeout
122
+ attribute :database, Database
123
+ attribute :web, Web
124
+
125
+ sets :domain
126
+ sets :region
127
+ sets :memory
128
+ sets :timeout
129
+ sets :database
130
+ sets :web
131
+
132
+ emits "LambdaTargetDeclared"
133
+ end
134
+ end
135
+
136
+ # `bin/project_tenant`'S OWN VALIDATED SHAPE — same split LambdaTarget
137
+ # draws with `bin/project_deploy`: this aggregate validates, that
138
+ # script GENERATES/ACTS (writes `environments/<slug>.world`, ensures
139
+ # the Postgres schema, boots once to prove it). Deliberately NOT a
140
+ # saga/process manager with durable runtime state — every step
141
+ # `bin/project_tenant` takes is already idempotent by construction
142
+ # (`CREATE SCHEMA IF NOT EXISTS` — PostgresEra#connect_for's own,
143
+ # File.write of the overlay — a plain overwrite, `Hecks.boot` itself),
144
+ # the same "generator script, not a business process" shape
145
+ # `bin/project_deploy` already is; a saga's own resumability earns
146
+ # its cost coordinating a real distributed transaction across
147
+ # multiple ports with real failure boundaries (Payments::Payment's
148
+ # own PaymentGateway, say) — three local, idempotent, single-process
149
+ # steps have no such boundary to resume across. A durable, reactable
150
+ # RUNTIME fact ("this tenant exists, is active, is suspended") is a
151
+ # different, later, business-facing thing — deliberately not built
152
+ # here, and deliberately not named Deploy::Tenant to avoid colliding
153
+ # with this validation-only aggregate should it ever land.
154
+ aggregate "Tenant" do
155
+ description "One tenant's validated identity — which realm and Postgres schema `bin/project_tenant` provisions before booting a domain directory under Runtime.boot's own `environment:` overlay for it."
156
+
157
+ identified_by :slug
158
+
159
+ attribute :slug, Slug
160
+ attribute :domain, DomainName
161
+ attribute :realm, Realm
162
+ attribute :schema, Schema
163
+
164
+ # LOWERCASE, NO SPACES — a slug becomes both the `environments/
165
+ # <slug>.world` filename and (by convention, not enforced here —
166
+ # `schema` is its own, separately-declared field precisely so a
167
+ # tenant CAN choose a different schema name than its slug)
168
+ # ordinarily the schema name too.
169
+ value_object "Slug" do
170
+ attribute :value, String, pattern: '^[a-z][a-z0-9_]*$'
171
+ invariant("a tenant slug is named") { !value.to_s.empty? }
172
+ end
173
+
174
+ value_object "DomainName" do
175
+ attribute :value, String
176
+ invariant("a tenant names the domain it is for") { !value.to_s.empty? }
177
+ end
178
+
179
+ value_object "Realm" do
180
+ attribute :value, String
181
+ invariant("a tenant realm is named") { !value.to_s.empty? }
182
+ end
183
+
184
+ # POSTGRES'S OWN REAL IDENTIFIER RULES, loosely — lowercase (this
185
+ # adapter never quotes a mixed-case identifier back out consistently
186
+ # once created), letters/digits/underscores, starting with a letter.
187
+ # NOT a byte-length ceiling too (Postgres's own real 63-byte
188
+ # NAMEDATALEN limit) — the expression grammar invariants compile
189
+ # through admits a narrow arithmetic/comparison subset, not
190
+ # arbitrary method dispatch (`bytesize` included; found live,
191
+ # writing this: `Bluebook::Expression::Evaluator` tries to resolve
192
+ # a dotted chain as a FIELD lookup, not a method call, the same
193
+ # "admits + but not -, no parentheses" narrowness
194
+ # embryonaut_bluebooks/payments' own Refund command already
195
+ # documents). Postgres itself refuses a too-long identifier loudly
196
+ # on its own; not worth fighting the expression grammar for a
197
+ # corner case this rare.
198
+ value_object "Schema" do
199
+ attribute :value, String, pattern: '^[a-z][a-z0-9_]*$'
200
+ invariant("a schema is named") { !value.to_s.empty? }
201
+ end
202
+
203
+ command "Declare" do
204
+ goal "Validate one tenant's shape before bin/project_tenant provisions its schema and overlay world"
205
+
206
+ attribute :slug, Slug
207
+ attribute :domain, DomainName
208
+ attribute :realm, Realm
209
+ attribute :schema, Schema
210
+
211
+ sets :slug
212
+ sets :domain
213
+ sets :realm
214
+ sets :schema
215
+
216
+ emits "TenantDeclared"
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,4 @@
1
+ # The Deploy domain, bound so it boots standing alone.
2
+ Hecks.hecksagon "Deploy" do
3
+ persisted_by "Memory"
4
+ end
@@ -0,0 +1,18 @@
1
+ {
2
+ "audience": "Deploy",
3
+ "scopes": [
4
+ {
5
+ "scope": "deploy:lambda_target.declare",
6
+ "verb": "Deploy::LambdaTarget.Declare",
7
+ "role": null
8
+ },
9
+ {
10
+ "scope": "deploy:tenant.declare",
11
+ "verb": "Deploy::Tenant.Declare",
12
+ "role": null
13
+ }
14
+ ],
15
+ "roles": [
16
+
17
+ ]
18
+ }