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,169 @@
1
+ require_relative "../../../../ports/persistence"
2
+ require_relative "../../../../ports/persistence/binding_policy"
3
+ require_relative "lineage"
4
+ require_relative "../../../../naming"
5
+ require_relative "../../../../framework"
6
+ require_relative "../../../../runtime/registry"
7
+
8
+ module Hecks
9
+ module Runtime
10
+ # The boot-time era gate, run for the adapters that HAVE eras — the
11
+ # lineage-capable ones. An era is a fact about stored data that some
12
+ # adapter can carry across a shape change; an adapter that cannot
13
+ # translate has no era to hold, and holding one for it would record
14
+ # history about data nothing can act on.
15
+ #
16
+ # The whole domain is handed to the capable adapter's own era check
17
+ # (Postgres: hold, recognize, mint, or refuse toward the scaffold),
18
+ # which keeps its era facts as rows BESIDE its data. That
19
+ # co-location is load-bearing — a watermark only means something
20
+ # against the journal it was cut from, and an approval recorded
21
+ # anywhere but the reviewed database binds to nothing.
22
+ #
23
+ # The capability is asked of the adapter, never of its name: a
24
+ # second adapter that grows an era story declares lineage_capable?
25
+ # and era_check! and needs no change here.
26
+ #
27
+ # Detection and identity are separate jobs: this check never hashes
28
+ # anything — era names come from the store, already minted (by the
29
+ # scaffold) or absent. Refusal wordings are contract, pinned by the
30
+ # corpus.
31
+ module EraCheck
32
+ module_function
33
+
34
+ # EACH BLUEBOOK'S OWN SOURCE, not one file read once and reused
35
+ # for every bluebook in the registry — true as long as a domain
36
+ # directory only ever held exactly one, and silently wrong the
37
+ # moment `uses_framework` made a second, differently-sourced
38
+ # bluebook (Governance, Identity — `lib/hecks/framework/bluebook/`,
39
+ # not the domain's own directory) share a boot with the first.
40
+ # Caught the hard way: three domains booted together, one real
41
+ # source text (the domain's own), and every OTHER domain's era-1
42
+ # held THAT text under its own name — a shadow-parse of it later
43
+ # reconstructs a completely different shape, and every boot after
44
+ # the first refuses toward a scaffold that was never the real
45
+ # drift.
46
+ def check!(registry, directory)
47
+ check_compute_rules_for_registry!(registry)
48
+ check_lineage!(registry, directory)
49
+ end
50
+
51
+ # The domain-agnostic half, split out for ADR 0031's boot-gate
52
+ # registry: a compute rule requires Postgres whatever adapter is
53
+ # actually bound, so this must run for EVERY registry, the same way
54
+ # `registry.verify!` does — it is not conditional on any adapter
55
+ # being lineage-capable, and must never be skipped by
56
+ # `check_lineage!`'s own capability gate below.
57
+ def check_compute_rules_for_registry!(registry)
58
+ registry.bluebooks.each_value { |bluebook| check_compute_rules!(registry, bluebook) }
59
+ end
60
+
61
+ # The capability-gated half — ADR 0031's registered `:era_check`
62
+ # gate. Registration is conditional on `lineage_capable_registry?`;
63
+ # `check_bluebook!` below still carries its own per-bluebook
64
+ # `lineage_capable?` return-early, unchanged, for a registry with a
65
+ # mix of lineage-capable and plain-adapter bluebooks.
66
+ def check_lineage!(registry, directory)
67
+ registry.bluebooks.each_value do |bluebook|
68
+ check_bluebook!(registry, bluebook, source_text_for(bluebook, directory), directory: directory)
69
+ end
70
+ end
71
+
72
+ # The `:era_check` gate's own registration predicate: true iff at
73
+ # least one bluebook's own anchor (first) aggregate resolves to a
74
+ # lineage-capable adapter — mirrors `check_bluebook!`'s existing
75
+ # per-bluebook anchor check, just asked once, up front, of the
76
+ # whole registry, so a registry with nothing lineage-capable bound
77
+ # anywhere never registers the gate at all.
78
+ def lineage_capable_registry?(registry)
79
+ registry.bluebooks.each_value.any? do |bluebook|
80
+ first = bluebook.aggregates.first
81
+ next false unless first
82
+
83
+ lineage_capable?(registry, adapter_for(registry, bluebook.name, first))
84
+ end
85
+ end
86
+
87
+ # The domain's own directory first, matched by name — a real app's
88
+ # directory may hold more than one file once `uses_framework`
89
+ # exists, so ".first" alone can no longer be trusted, the exact
90
+ # way it silently wasn't the day this was found: three domains
91
+ # booted together, one real source text read once (the domain's
92
+ # own, ".first"'d), and every OTHER domain's era-1 held THAT text
93
+ # under its own name — a later shadow-parse of it reconstructs a
94
+ # completely different shape, and every boot after the first
95
+ # refuses toward a scaffold that was never the real drift.
96
+ #
97
+ # A single-file directory whose one file names something ELSE
98
+ # falls back to it anyway (a fixture may legitimately name its
99
+ # file differently from the `Hecks.bluebook` it declares) — UNLESS
100
+ # this bluebook is a known framework member, in which case that
101
+ # one file is certainly some OTHER domain's, not this one's, and
102
+ # the framework registry is asked instead — the only other place a
103
+ # bluebook in this registry could have come from, per
104
+ # `uses_framework`.
105
+ def source_text_for(bluebook, directory)
106
+ domain_files = Dir[File.join(directory, "*.bluebook")]
107
+ own = domain_files.select do |path|
108
+ File.foreach(path, encoding: "UTF-8").any? { |line| line.match?(/\A\s*Hecks\.bluebook\s+#{Regexp.escape(bluebook.name.inspect)}/) }
109
+ end
110
+ own = domain_files if own.empty? && domain_files.size == 1 && !Framework.members.key?(bluebook.name)
111
+ own = [Framework.members[bluebook.name]].compact if own.empty?
112
+ return if own.empty?
113
+
114
+ own.map { |path| File.read(path, encoding: "UTF-8") }.join("\n")
115
+ end
116
+
117
+ def check_bluebook!(registry, bluebook, current_text, directory: nil)
118
+ first = bluebook.aggregates.first
119
+ return unless first
120
+
121
+ adapter_name = adapter_for(registry, bluebook.name, first)
122
+ return unless lineage_capable?(registry, adapter_name)
123
+
124
+ unless current_text
125
+ raise WiringError,
126
+ "cannot boot #{bluebook.name}: bound to a lineage-capable adapter, but no source file for " \
127
+ "it could be found (checked #{directory.inspect} and the framework registry)"
128
+ end
129
+
130
+ settings = registry.world(bluebook.name)&.for_binding(Ports::Persistence::VERB, adapter_name) || {}
131
+ registry.adapter_class(adapter_name).era_check!(
132
+ registry: registry, bluebook: bluebook, current_text: current_text, settings: settings,
133
+ directory: directory
134
+ )
135
+ end
136
+
137
+ # The per-rule capability gate — NOT an era fact, and so it
138
+ # survives on every adapter: a compute rule's SQL is its only
139
+ # implementation, so an aggregate carrying one cannot boot
140
+ # anywhere but Postgres, whatever any shape comparison would say.
141
+ def check_compute_rules!(registry, bluebook)
142
+ bluebook.aggregates.each do |aggregate|
143
+ lineage = Ports::Persistence::Lineage.for(registry, bluebook.name, aggregate)
144
+ next unless lineage&.computes?
145
+
146
+ adapter = adapter_for(registry, bluebook.name, aggregate)
147
+ next if lineage_capable?(registry, adapter)
148
+
149
+ raise WiringError, "compute rules require the Postgres adapter; #{aggregate.name} is bound to #{adapter}"
150
+ end
151
+ end
152
+
153
+ def adapter_for(registry, domain, aggregate)
154
+ Ports::Persistence::BindingPolicy.resolve(registry, domain, aggregate).adapter
155
+ end
156
+
157
+ # The capability idiom: an adapter CLASS that answers
158
+ # lineage_capable? with true carries eras and may act on drift
159
+ # (translate, fork, merge). Postgres alone does today; the seam is
160
+ # what lets a second one arrive without touching this file.
161
+ def lineage_capable?(registry, adapter_name)
162
+ adapter_class = registry.adapters[adapter_name] && registry.adapter_class(adapter_name)
163
+ adapter_class.respond_to?(:lineage_capable?) && adapter_class.lineage_capable?
164
+ rescue StandardError
165
+ false
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,128 @@
1
+ module Hecks
2
+ module Runtime
3
+ module EraGuard
4
+ # The pure half: computing an aggregate's structural signature and
5
+ # diffing two eras' shapes. No file I/O — everything here answers
6
+ # from the IR it is handed, which is why the Postgres mint path can
7
+ # call it too.
8
+ module ShapeDiff
9
+ def shape(aggregate)
10
+ aggregate.attributes.map { |attribute|
11
+ [attribute.name, attribute_signature(aggregate, attribute.type)]
12
+ }.sort_by(&:first)
13
+ end
14
+
15
+ # A plain type name for a primitive; `[type_name, member_signatures]`
16
+ # for a value object or entity, walked recursively — so two
17
+ # attributes with the same declared type name but a different
18
+ # internal shape are never mistaken for unchanged.
19
+ def attribute_signature(aggregate, type_name, seen = [])
20
+ container = nested_type(aggregate, type_name)
21
+ return type_name if container.nil? || seen.include?(type_name)
22
+
23
+ members = container.attributes.map { |member|
24
+ [member.name, attribute_signature(aggregate, member.type, seen + [type_name])]
25
+ }.sort_by(&:first)
26
+ [type_name, members]
27
+ end
28
+
29
+ def nested_type(aggregate, type_name)
30
+ aggregate.value_object(type_name) || aggregate.entities.find { |entity| entity.name == type_name }
31
+ end
32
+
33
+ # Attributes present now that the held shape never had — the
34
+ # addition side of drift, which `uncovered_attributes` above never
35
+ # looks at, since it only walks the HELD shape (vanish/retype).
36
+ # Most additions are free (ADR 0025, "Added attributes and
37
+ # absence"): a default:, a list_of (frozen []), or a value object
38
+ # whose fields all default fill an existing record automatically
39
+ # via `Instance.hydrate_with_defaults`. Only the fourth case — a
40
+ # non-optional attribute with no way to fill itself — can leave an
41
+ # existing record with the field genuinely absent, and that is
42
+ # what this reports: unfilled by a declared translation's own
43
+ # `backfill`, OR by a move/convert that lands an OLD field inside
44
+ # this brand-new attribute (`Lineage#fills?` — the destination-
45
+ # side question, never `explains?`'s source-side one, since a
46
+ # rename from Crate to Bin can introduce a top-level attribute
47
+ # name that never existed to have "vanished").
48
+ def unsafe_additions(aggregate, held_aggregate, lineage)
49
+ held_names = held_aggregate.attributes.map(&:name)
50
+
51
+ aggregate.attributes
52
+ .reject { |attribute| held_names.include?(attribute.name) }
53
+ .select { |attribute| possibly_absent?(aggregate, attribute) }
54
+ .reject { |attribute| lineage&.fills?(attribute.name.to_s) }
55
+ .map(&:name)
56
+ end
57
+
58
+ # THE FOUR-ROW TABLE, as a predicate over the fourth row only — the
59
+ # other three (default:, list_of, a fully-defaulted value object)
60
+ # all fill an existing record for free and never reach here.
61
+ def possibly_absent?(aggregate, attribute)
62
+ return false if attribute.optional?
63
+ return false unless attribute.default.nil?
64
+ return false if attribute.list?
65
+
66
+ value_object = aggregate.value_object(attribute.type)
67
+ return false if value_object && value_object.attributes.all? { |field| !field.default.nil? }
68
+
69
+ true
70
+ end
71
+
72
+ # Paths the translation needs to explain: attributes that vanished
73
+ # by name, attributes that kept their name but changed type (a
74
+ # `convert` is what lets that be declared at all), and — recursing
75
+ # into a same-named, same-typed value object — its OWN members
76
+ # vanishing or changing type one level down, reported as a dotted
77
+ # path ("price.currency"). A pure addition, at any depth, never
78
+ # needs covering; only vanish-or-retype does, matching the
79
+ # top-level rule at every depth.
80
+ def uncovered_attributes(aggregate, held_aggregate, lineage)
81
+ paths = held_aggregate.attributes.flat_map do |held_attribute|
82
+ current_attribute = aggregate.attribute(held_attribute.name)
83
+ next [held_attribute.name.to_s] unless current_attribute
84
+
85
+ diff_type(held_attribute.name.to_s, held_attribute.type, current_attribute.type, held_aggregate, aggregate, lineage)
86
+ end
87
+
88
+ return paths unless lineage
89
+
90
+ paths.reject { |path| lineage.explains?(path) }
91
+ end
92
+
93
+ # `held_type`/`current_type` are type NAMES, resolved against each
94
+ # side's OWN value_object AND entity declarations — neither is ever
95
+ # nested in the DSL, only in the type graph, so both are always
96
+ # looked up flat off their respective aggregate. A `list_of` entity
97
+ # is only reached here to DETECT a member vanish-or-retype; there is
98
+ # no per-element translation machinery yet (`move`/`convert`/`drop`
99
+ # only reach into a single nested hash, not each element of an
100
+ # array) — the only way to satisfy a refusal on an entity path
101
+ # today is a top-level `drop` of the whole list attribute, which
102
+ # `explains?` already recognizes as covering everything nested
103
+ # under it. Blunt, but loud beats silent.
104
+ def diff_type(path, held_type, current_type, held_aggregate, aggregate, lineage, seen = [])
105
+ if held_type != current_type
106
+ # A declared retype says the two TYPE names mean the same shape
107
+ # — accept the pair, but still recurse into the members so a
108
+ # member drift hiding beneath the rename is caught by name.
109
+ return [path] unless lineage&.retype?(held_type, current_type)
110
+ end
111
+ return [] if seen.include?(current_type)
112
+
113
+ held_container = nested_type(held_aggregate, held_type)
114
+ current_container = nested_type(aggregate, current_type)
115
+ return [] unless held_container && current_container
116
+
117
+ held_container.attributes.flat_map do |held_member|
118
+ current_member = current_container.attribute(held_member.name)
119
+ next ["#{path}.#{held_member.name}"] unless current_member
120
+
121
+ diff_type("#{path}.#{held_member.name}", held_member.type, current_member.type, held_aggregate, aggregate, lineage,
122
+ seen + [current_type])
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,161 @@
1
+ require_relative "era_guard/shape_diff"
2
+ require_relative "../../../../bluebook/dsl/malformed"
3
+ require_relative "../../../../bluebook/meta_validator"
4
+ require_relative "../../../../ports/loading"
5
+ require_relative "../../../../runtime/registry"
6
+
7
+ module Hecks
8
+ module Runtime
9
+ # The shape-drift coverage primitives — ADR 0032. Not a driver: nothing
10
+ # here walks a registry or reads/writes a held snapshot on its own.
11
+ # `PostgresEra::LineageManager::CoverageCheck` calls `uncovered_
12
+ # attributes`/`unsafe_additions`/`refuse_uncovered!`/`refuse_unsafe_
13
+ # addition!`/`check_vanished_aggregates!` directly, per translation
14
+ # edge, over its own DB-held shapes; `Translation::Reattest` and
15
+ # `PostgresEra::LineageManager` call `shadow_parse` directly, to read
16
+ # historical bluebook text under old grammar defaults. Both are real,
17
+ # independent, currently-shipped consumers.
18
+ #
19
+ # A prior version of this module ALSO drove its own top-level check —
20
+ # `check!`/`check_bluebook!`, walking a registry and reading/writing a
21
+ # held snapshot under `data/eras/*.bluebook` — duplicating, on its own,
22
+ # the same per-aggregate walk `CoverageCheck` already performs against
23
+ # `PostgresEra`'s own DB-held shapes. Nothing in production ever called
24
+ # it (only a direct unit spec did); deleted rather than kept unwired,
25
+ # per ADR 0032. Wanted again, it's rebuilt informed by `CoverageCheck`'s
26
+ # real orchestration, not resurrected from here.
27
+ module EraGuard
28
+ extend ShapeDiff
29
+
30
+ module_function
31
+
32
+ # An aggregate that existed in the held text and answers to no
33
+ # current name — renamed silently, with nothing declaring `was:` to
34
+ # explain where its data went — is exactly the disease this guards
35
+ # against, and a plain per-aggregate diff would never see it: the
36
+ # current aggregate simply has no held counterpart to compare to.
37
+ def check_vanished_aggregates!(registry, bluebook, held_bluebook)
38
+ held_bluebook.aggregates.each do |held_aggregate|
39
+ claimed = bluebook.aggregates.any? do |aggregate|
40
+ aggregate.name == held_aggregate.name ||
41
+ registry.translations.any? do |translation|
42
+ translation.domain == bluebook.name &&
43
+ translation.for_aggregate(aggregate.name)&.was == held_aggregate.name
44
+ end
45
+ end
46
+ claimed ||= registry.translations.any? do |translation|
47
+ translation.domain == bluebook.name && translation.retired.include?(held_aggregate.name)
48
+ end
49
+ next if claimed
50
+
51
+ raise WiringError,
52
+ "cannot boot #{bluebook.name}: #{held_aggregate.name} existed and now doesn't, " \
53
+ "and nothing declares was: #{held_aggregate.name.inspect} to explain where its data went."
54
+ end
55
+ end
56
+
57
+ # The Layer-1 coverage refusal — one wording, shared with whoever
58
+ # calls it (today, `PostgresEra::LineageManager::CoverageCheck`'s
59
+ # own mint-time coverage check).
60
+ def refuse_uncovered!(bluebook, aggregate, uncovered)
61
+ raise WiringError,
62
+ "cannot boot #{bluebook.name}::#{aggregate.name}: its shape changed and " \
63
+ "#{uncovered.map { |path| render_path(path) }.join(', ')} #{uncovered.size == 1 ? 'is' : 'are'} not " \
64
+ "explained by any rename, move, convert, retype, or drop. Update bluebook/translations/*.bluebook, e.g. " \
65
+ "#{suggestion(uncovered.first)}."
66
+ end
67
+
68
+ # The addition-side sibling of refuse_uncovered! above — same
69
+ # wording shape, different cause: nothing vanished or changed type,
70
+ # something new arrived that an existing record has no way to hold.
71
+ def refuse_unsafe_addition!(bluebook, aggregate, unsafe)
72
+ raise WiringError,
73
+ "cannot boot #{bluebook.name}::#{aggregate.name}: #{unsafe.map { |name| ":#{name}" }.join(', ')} " \
74
+ "#{unsafe.size == 1 ? 'is new and required' : 'are new and required'}, with no default: to fill " \
75
+ "an existing record and no translation explaining what one should read there. Give it a " \
76
+ "default:, make it optional: true or list_of, or declare bluebook/translations/*.bluebook, e.g. " \
77
+ "`backfill :#{unsafe.first}, default: ...`."
78
+ end
79
+
80
+ def render_path(path) = path.include?(".") ? path.inspect : ":#{path}"
81
+
82
+ def suggestion(path)
83
+ if path.include?(".")
84
+ "`move #{path.inspect}, to: #{path.inspect}` or `drop #{path.inspect}`"
85
+ else
86
+ "`rename :#{path}, to: :new_name` or `drop :#{path}`"
87
+ end
88
+ end
89
+
90
+ # Parses held source into its own IR, in a scratch registry so a past
91
+ # era's text never touches the one actually booting.
92
+ #
93
+ # NORMAL PARSE FIRST, shadow only as a FALLBACK — not shadow-parsing
94
+ # unconditionally, which is what this used to do. A handful of DSL
95
+ # defaults fork on `MetaValidator.shadow_parsing?` for a reason
96
+ # that has NOTHING to do with syntax the live grammar can no longer
97
+ # read at all (`identified_by { }`, `belongs_to`, `has_one`,
98
+ # `has_many` — genuinely removed spellings, exactly what shadow-
99
+ # parsing exists to keep readable): `reference_to`'s own default
100
+ # mint name (`default_reference_name`, attribute_collector.rb)
101
+ # changed from `_id`-suffixed to bare under ADR 0025, and THAT fork
102
+ # applies even to text using nothing but current, live syntax.
103
+ # Held text minted under the CURRENT grammar — every real era in
104
+ # this corpus today, since nothing has ever minted a second one —
105
+ # parses fine normally; only the reference-naming DEFAULT differed
106
+ # once shadow mode engaged unconditionally, so it silently
107
+ # reconstructed a DIFFERENT shape (and hash) than a fresh parse of
108
+ # the identical text — the same text hashing two different ways
109
+ # depending on which code path read it, breaking `ensure_named!`'s
110
+ # own from/to edge lookup with a spurious "no translation edge
111
+ # covers it" refusal that has nothing to do with any real
112
+ # translation gap.
113
+ #
114
+ # A normal parse can only ever SUCCEED on text the live grammar
115
+ # fully understands — there is no way for it to silently produce a
116
+ # wrong-but-plausible answer for genuinely legacy text, since every
117
+ # removed spelling refuses loudly (`Malformed`) rather than
118
+ # degrading. So: try normal first — if the ordinary grammar reads
119
+ # this text without complaint, that IS the canonical, unambiguous
120
+ # interpretation, the same one `label_of`/`mint_hash` on the same
121
+ # source text always computes, whoever's asking. Only on a
122
+ # `Malformed` refusal — the one signal that actually means "this
123
+ # spelling doesn't exist anymore" — fall back to the legacy
124
+ # grammar, exactly as before this change. Any OTHER exception (a
125
+ # genuine syntax error, an unrelated validation refusal) propagates
126
+ # unchanged; swallowing it here to retry under shadow mode would
127
+ # risk masking a real defect in the held text behind a confusing
128
+ # second failure instead of the original, more specific one.
129
+ #
130
+ # `MetaValidator.while_shadow_parsing` (ADR 0025, docs/dsl-work-
131
+ # slices.md's S0a) is what makes the fallback a LEGACY grammar
132
+ # rather than just a second copy of today's: it stops
133
+ # `BluebookBuilder.build` from judging this text against the
134
+ # grammar as it stands NOW, which is the one thing that would make
135
+ # a removed spelling refuse HISTORY the day it is removed from
136
+ # live source. The scratch registry is throwaway either way —
137
+ # nothing here is dispatched against or exposed to the real one —
138
+ # so skipping the judge/assemble round-trip changes nothing this
139
+ # method reads: `shape`, `uncovered_attributes`, and friends only
140
+ # ever ask the built IR for its own structure.
141
+ def shadow_parse(source, path)
142
+ parse_bluebook(source, path, shadow: false)
143
+ rescue Hecks::Bluebook::DSL::Malformed
144
+ parse_bluebook(source, path, shadow: true)
145
+ end
146
+
147
+ def parse_bluebook(source, path, shadow:)
148
+ scratch = Registry.new
149
+ loading = Ports::Loading.bootstrap
150
+ run = lambda do
151
+ Hecks.with_registry(scratch) do
152
+ loading.load_library
153
+ Kernel.eval(source, TOPLEVEL_BINDING, path, 1)
154
+ end
155
+ end
156
+ shadow ? Hecks::Bluebook::MetaValidator.while_shadow_parsing(&run) : run.call
157
+ scratch.bluebooks.values.first
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,61 @@
1
+ require "json"
2
+ require "tempfile"
3
+ require_relative "era_guard"
4
+ require_relative "storage_shape"
5
+
6
+ module Hecks
7
+ module Runtime
8
+ # The refusal wording for a held era whose digest no longer matches its
9
+ # frozen text — the digest mismatch alone is what DETECTS tampering (a
10
+ # plain SHA256 comparison over raw bytes, unrelated to any of this);
11
+ # this only supplies the wording once that's already fired.
12
+ #
13
+ # LIVES HERE, NOT UNDER `Ports::Persistence` — `project` below directly
14
+ # calls `Runtime::EraGuard.shadow_parse`/`Runtime::StorageShape.project`
15
+ # (DSL-execution machinery), the "an adapter/port reaches into the
16
+ # runtime instead of being handed already-computed data" shape that
17
+ # has caused trouble here before. A capability this dependent on the
18
+ # runtime is a runtime-owned one that the Postgres adapter CALLS, not
19
+ # a ports-level module that happens to reach sideways into it.
20
+ #
21
+ # Used to also distinguish a cosmetic edit from a real shape change
22
+ # here, by re-parsing the edited text — a pure quality-of-message
23
+ # nicety, not a safety property, since dropped: every tamper refusal
24
+ # reaches the same generic wording now, the one this already fell to
25
+ # whenever it couldn't classify an edit anyway. An operator judges
26
+ # "did this matter" themselves,
27
+ # reading the still-archived original — an anomalous recovery moment
28
+ # already, not a normal boot path.
29
+ module EraTamper
30
+ module_function
31
+
32
+ def refusal(domain:, ordinal:)
33
+ "cannot boot #{domain}: the held text of era #{ordinal} was edited after it was frozen — " \
34
+ "held era texts are storage facts; restore the original text, or reset the data"
35
+ end
36
+
37
+ # The storage-shape projection of a bluebook text, JSON-normalized
38
+ # for structural comparison against a stored projection; nil when
39
+ # the text does not load. ALWAYS parsed through a fresh tempfile,
40
+ # never the held file's own path: the predicate extractor caches
41
+ # source by path, and a held path whose content has changed (the
42
+ # very situation this module exists for) would hand it stale
43
+ # lines.
44
+ def project(text, _source_path = nil)
45
+ file = Tempfile.new(["hecks-tamper-", ".bluebook"])
46
+ begin
47
+ file.write(text)
48
+ file.flush
49
+ bluebook = EraGuard.shadow_parse(text, file.path)
50
+ ensure
51
+ file.close!
52
+ end
53
+ return nil unless bluebook
54
+
55
+ JSON.parse(JSON.generate(StorageShape.project(bluebook)))
56
+ rescue StandardError, SyntaxError
57
+ nil
58
+ end
59
+ end
60
+ end
61
+ end