hecks 0.2.0 → 0.3.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 (408) 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 +403 -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 +62 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +148 -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 +131 -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 +80 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +391 -0
  31. data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
  32. data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
  33. data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
  34. data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
  35. data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
  36. data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
  37. data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
  38. data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
  39. data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
  40. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
  41. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
  42. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  43. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
  44. data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
  45. data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
  46. data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
  47. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  48. data/lib/hecks/adapters/driven/prism.rb +80 -0
  49. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  50. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  51. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  52. data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
  53. data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
  54. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
  55. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  56. data/lib/hecks/adapters/driven/sqlite.rb +285 -0
  57. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  58. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  59. data/lib/hecks/adapters/driven.rb +36 -0
  60. data/lib/hecks/adapters.rb +6 -0
  61. data/lib/hecks/behaviors/dsl.rb +96 -0
  62. data/lib/hecks/behaviors/expectations.rb +280 -0
  63. data/lib/hecks/behaviors/ir.rb +31 -0
  64. data/lib/hecks/behaviors/rspec.rb +42 -0
  65. data/lib/hecks/behaviors/runner.rb +96 -0
  66. data/lib/hecks/behaviors.rb +25 -0
  67. data/lib/hecks/bluebook/aggregate.rb +108 -0
  68. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  69. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  70. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  71. data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
  72. data/lib/hecks/bluebook/assembly/marks.rb +227 -0
  73. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  74. data/lib/hecks/bluebook/assembly.rb +78 -0
  75. data/lib/hecks/bluebook/attribute.rb +96 -0
  76. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  77. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  78. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  79. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  80. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  81. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  82. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  83. data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
  84. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  85. data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
  86. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  87. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  88. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  89. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  90. data/lib/hecks/bluebook/chapter.rb +68 -0
  91. data/lib/hecks/bluebook/command.rb +124 -0
  92. data/lib/hecks/bluebook/domain_port.rb +102 -0
  93. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  94. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
  95. data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
  96. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  97. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
  98. data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
  99. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  100. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  101. data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
  102. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  103. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  104. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  105. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  106. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  107. data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
  108. data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
  109. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  110. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
  111. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  112. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  113. data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
  114. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  115. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  116. data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
  117. data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
  118. data/lib/hecks/bluebook/dsl.rb +45 -0
  119. data/lib/hecks/bluebook/entity.rb +103 -0
  120. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
  121. data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
  122. data/lib/hecks/bluebook/expression/projection.json +170 -0
  123. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  124. data/lib/hecks/bluebook/expression/resolver.rb +630 -0
  125. data/lib/hecks/bluebook/expression.rb +13 -0
  126. data/lib/hecks/bluebook/hexagon.rb +60 -0
  127. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  128. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  129. data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
  130. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  131. data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
  132. data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
  133. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
  134. data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
  135. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  136. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  137. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  138. data/lib/hecks/bluebook/meta_validator.rb +452 -0
  139. data/lib/hecks/bluebook/model_check.rb +318 -0
  140. data/lib/hecks/bluebook/pattern_subset.rb +120 -0
  141. data/lib/hecks/bluebook/policy.rb +41 -0
  142. data/lib/hecks/bluebook/process_manager.rb +86 -0
  143. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  144. data/lib/hecks/bluebook/project_loader.rb +40 -0
  145. data/lib/hecks/bluebook/project_register.rb +82 -0
  146. data/lib/hecks/bluebook/query.rb +61 -0
  147. data/lib/hecks/bluebook/read_model.rb +109 -0
  148. data/lib/hecks/bluebook/reference.rb +74 -0
  149. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  150. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  151. data/lib/hecks/bluebook/translation.rb +92 -0
  152. data/lib/hecks/bluebook/value_object.rb +58 -0
  153. data/lib/hecks/bluebook.rb +74 -0
  154. data/lib/hecks/codemod.rb +342 -0
  155. data/lib/hecks/construct.rb +71 -0
  156. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  157. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  158. data/lib/hecks/deploy/oidc.json +18 -0
  159. data/lib/hecks/doc/reference.rb +389 -0
  160. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  161. data/lib/hecks/facade/cli_door.rb +116 -0
  162. data/lib/hecks/facade/cli_runner.rb +190 -0
  163. data/lib/hecks/facade/command_request.rb +105 -0
  164. data/lib/hecks/facade/handle.rb +173 -0
  165. data/lib/hecks/facade/json_door.rb +154 -0
  166. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  167. data/lib/hecks/facade/surface/chapter.rb +107 -0
  168. data/lib/hecks/facade/surface.rb +48 -0
  169. data/lib/hecks/facade.rb +44 -0
  170. data/lib/hecks/forms/app.rb +300 -0
  171. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  172. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  173. data/lib/hecks/forms/field_renderer.rb +170 -0
  174. data/lib/hecks/forms/field_shape.rb +232 -0
  175. data/lib/hecks/forms/html.rb +53 -0
  176. data/lib/hecks/forms/index_renderer.rb +35 -0
  177. data/lib/hecks/forms/page.rb +157 -0
  178. data/lib/hecks/forms/params.rb +132 -0
  179. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  180. data/lib/hecks/forms/record_renderer.rb +115 -0
  181. data/lib/hecks/forms/record_table.rb +63 -0
  182. data/lib/hecks/forms/reference_options.rb +30 -0
  183. data/lib/hecks/forms/value_object_shape.rb +46 -0
  184. data/lib/hecks/forms.rb +54 -0
  185. data/lib/hecks/fqn.rb +94 -0
  186. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  187. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  188. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  189. data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
  190. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  191. data/lib/hecks/framework/oidc.json +39 -0
  192. data/lib/hecks/framework.rb +90 -0
  193. data/lib/hecks/freezer.rb +67 -0
  194. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  195. data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
  196. data/lib/hecks/fuzzing/properties.rb +1146 -0
  197. data/lib/hecks/fuzzing/replay.rb +635 -0
  198. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  199. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
  200. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  201. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
  202. data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
  203. data/lib/hecks/fuzzing/value_generator.rb +177 -0
  204. data/lib/hecks/fuzzing.rb +12 -0
  205. data/lib/hecks/grammar/evolve.rb +291 -0
  206. data/lib/hecks/grammar/expression.bluebook +417 -0
  207. data/lib/hecks/grammar/expression_operators.json +1152 -0
  208. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  209. data/lib/hecks/grammar/oidc.json +69 -0
  210. data/lib/hecks/grammar/translation.bluebook +203 -0
  211. data/lib/hecks/grammar.rb +178 -0
  212. data/lib/hecks/ir.rb +126 -0
  213. data/lib/hecks/language/adapter.bluebook +116 -0
  214. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  215. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  216. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  217. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  218. data/lib/hecks/language/bluebook/command.bluebook +437 -0
  219. data/lib/hecks/language/bluebook/entity.bluebook +316 -0
  220. data/lib/hecks/language/bluebook/policy.bluebook +172 -0
  221. data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
  222. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  223. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  224. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  225. data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
  226. data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
  227. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  228. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  229. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  230. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  231. data/lib/hecks/language/oidc.json +313 -0
  232. data/lib/hecks/language/port.bluebook +92 -0
  233. data/lib/hecks/language/translation/translation.bluebook +110 -0
  234. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  235. data/lib/hecks/language/world/wiring.bluebook +62 -0
  236. data/lib/hecks/language/world/world.bluebook +84 -0
  237. data/lib/hecks/literal.rb +125 -0
  238. data/lib/hecks/naming.rb +121 -0
  239. data/lib/hecks/ports/access_control.port +4 -0
  240. data/lib/hecks/ports/access_control.rb +62 -0
  241. data/lib/hecks/ports/agent/answers.rb +104 -0
  242. data/lib/hecks/ports/agent.port +4 -0
  243. data/lib/hecks/ports/agent.rb +167 -0
  244. data/lib/hecks/ports/authentication.port +4 -0
  245. data/lib/hecks/ports/authentication.rb +50 -0
  246. data/lib/hecks/ports/authorization.port +4 -0
  247. data/lib/hecks/ports/authorization.rb +59 -0
  248. data/lib/hecks/ports/clock.port +4 -0
  249. data/lib/hecks/ports/clock.rb +62 -0
  250. data/lib/hecks/ports/extraction.port +4 -0
  251. data/lib/hecks/ports/extraction.rb +37 -0
  252. data/lib/hecks/ports/identity_assignment.port +4 -0
  253. data/lib/hecks/ports/identity_assignment.rb +45 -0
  254. data/lib/hecks/ports/identity_generation.port +4 -0
  255. data/lib/hecks/ports/identity_generation.rb +49 -0
  256. data/lib/hecks/ports/identity_resolution.port +4 -0
  257. data/lib/hecks/ports/identity_resolution.rb +40 -0
  258. data/lib/hecks/ports/loading.port +4 -0
  259. data/lib/hecks/ports/loading.rb +13 -0
  260. data/lib/hecks/ports/persistence/append_only.rb +102 -0
  261. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  262. data/lib/hecks/ports/persistence/execution.rb +18 -0
  263. data/lib/hecks/ports/persistence/lineage.rb +277 -0
  264. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  266. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  267. data/lib/hecks/ports/persistence.port +4 -0
  268. data/lib/hecks/ports/persistence.rb +39 -0
  269. data/lib/hecks/ports/projection.port +4 -0
  270. data/lib/hecks/ports/projection.rb +76 -0
  271. data/lib/hecks/ports/query/in_memory.rb +60 -0
  272. data/lib/hecks/ports/query/ordering.rb +41 -0
  273. data/lib/hecks/ports/query.rb +36 -0
  274. data/lib/hecks/ports.rb +27 -0
  275. data/lib/hecks/projections/diagrams.rb +413 -0
  276. data/lib/hecks/projections/ir.rb +18 -0
  277. data/lib/hecks/projections/model/deviations.rb +98 -0
  278. data/lib/hecks/projections/model.rb +145 -0
  279. data/lib/hecks/projections/oidc.rb +110 -0
  280. data/lib/hecks/projections/parser_table.rb +159 -0
  281. data/lib/hecks/projections/reference.rb +38 -0
  282. data/lib/hecks/projections/shape.rb +31 -0
  283. data/lib/hecks/projections/statements.rb +110 -0
  284. data/lib/hecks/projections/vocabulary.rb +100 -0
  285. data/lib/hecks/projections.rb +32 -0
  286. data/lib/hecks/projector/cli_projector.rb +437 -0
  287. data/lib/hecks/projector/docs_projector.rb +321 -0
  288. data/lib/hecks/projector/exporter.rb +141 -0
  289. data/lib/hecks/projector/ir_projector.rb +18 -0
  290. data/lib/hecks/projector/narrate_projector.rb +243 -0
  291. data/lib/hecks/projector/target.rb +97 -0
  292. data/lib/hecks/projector.rb +186 -0
  293. data/lib/hecks/query_ir.rb +392 -0
  294. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  295. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  296. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  297. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  298. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  299. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  300. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  301. data/lib/hecks/query_specification/common/null_policy.rb +80 -0
  302. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  303. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  304. data/lib/hecks/query_specification/common/options.rb +32 -0
  305. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  306. data/lib/hecks/query_specification/common/specification.rb +10 -0
  307. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  308. data/lib/hecks/query_specification/field_path.rb +89 -0
  309. data/lib/hecks/query_specification/hop_path.rb +130 -0
  310. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  311. data/lib/hecks/query_specification.rb +14 -0
  312. data/lib/hecks/rendering.rb +48 -0
  313. data/lib/hecks/router/namespace_installer.rb +157 -0
  314. data/lib/hecks/router.rb +70 -0
  315. data/lib/hecks/runtime/caller.rb +50 -0
  316. data/lib/hecks/runtime/capability_graph.rb +44 -0
  317. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
  318. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
  319. data/lib/hecks/runtime/command_interpreter.rb +413 -0
  320. data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
  321. data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
  322. data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
  323. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  324. data/lib/hecks/runtime/command_rules/references.rb +181 -0
  325. data/lib/hecks/runtime/command_rules.rb +28 -0
  326. data/lib/hecks/runtime/dependency_planning.rb +231 -0
  327. data/lib/hecks/runtime/dispatcher.rb +286 -0
  328. data/lib/hecks/runtime/entity_element.rb +253 -0
  329. data/lib/hecks/runtime/entity_interpreter.rb +228 -0
  330. data/lib/hecks/runtime/era_check.rb +136 -0
  331. data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
  332. data/lib/hecks/runtime/era_guard.rb +193 -0
  333. data/lib/hecks/runtime/era_tamper.rb +61 -0
  334. data/lib/hecks/runtime/errors.rb +100 -0
  335. data/lib/hecks/runtime/event.rb +51 -0
  336. data/lib/hecks/runtime/identity.rb +128 -0
  337. data/lib/hecks/runtime/instance.rb +120 -0
  338. data/lib/hecks/runtime/interpreting.rb +69 -0
  339. data/lib/hecks/runtime/loader.rb +143 -0
  340. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  341. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  342. data/lib/hecks/runtime/query_interpreter.rb +254 -0
  343. data/lib/hecks/runtime/reaction_invocation.rb +245 -0
  344. data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
  345. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  346. data/lib/hecks/runtime/reference_hop.rb +99 -0
  347. data/lib/hecks/runtime/refusal_wording.rb +124 -0
  348. data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
  349. data/lib/hecks/runtime/registry/verification.rb +158 -0
  350. data/lib/hecks/runtime/registry.rb +218 -0
  351. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  352. data/lib/hecks/runtime/routing.rb +96 -0
  353. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  354. data/lib/hecks/runtime/saga_interpreter.rb +301 -0
  355. data/lib/hecks/runtime/storage_shape.rb +120 -0
  356. data/lib/hecks/runtime/tenant_check.rb +84 -0
  357. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  358. data/lib/hecks/runtime/value/admission.rb +112 -0
  359. data/lib/hecks/runtime/value/coercion.rb +500 -0
  360. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  361. data/lib/hecks/runtime/value.rb +125 -0
  362. data/lib/hecks/runtime.rb +95 -0
  363. data/lib/hecks/translation/audit/approval_digest.rb +31 -0
  364. data/lib/hecks/translation/audit/layer_one.rb +37 -0
  365. data/lib/hecks/translation/audit/layer_two.rb +77 -0
  366. data/lib/hecks/translation/audit/unfed_report.rb +42 -0
  367. data/lib/hecks/translation/audit.rb +70 -0
  368. data/lib/hecks/translation/reattest.rb +72 -0
  369. data/lib/hecks/translation/rule_compiler.rb +120 -0
  370. data/lib/hecks/translation/scaffold/differ.rb +183 -0
  371. data/lib/hecks/translation/scaffold/renderer.rb +41 -0
  372. data/lib/hecks/translation/scaffold/writer.rb +28 -0
  373. data/lib/hecks/translation/scaffold.rb +30 -0
  374. data/lib/hecks/translation.rb +11 -0
  375. data/lib/hecks/version.rb +16 -0
  376. data/lib/hecks/vocabulary.rb +213 -0
  377. data/lib/hecks.rb +118 -6
  378. metadata +401 -222
  379. data/bin/hecks +0 -7
  380. data/bin/hecks-package +0 -65
  381. data/bin/hecks_console +0 -12
  382. data/bin/hecks_serverless +0 -6
  383. data/lib/cli/build.rb +0 -14
  384. data/lib/cli/command_runner.rb +0 -28
  385. data/lib/cli/console.rb +0 -10
  386. data/lib/cli/generate.rb +0 -37
  387. data/lib/cli/hecks-cli.rb +0 -27
  388. data/lib/cli/test.rb +0 -57
  389. data/lib/console/commands.rb +0 -8
  390. data/lib/console/hecks-console.rb +0 -1
  391. data/lib/packager/README.md +0 -0
  392. data/lib/packager/app_runner.rb +0 -21
  393. data/lib/packager/args.rb +0 -26
  394. data/lib/packager/compatibility/fixnum.rb +0 -6
  395. data/lib/packager/hecks.rb +0 -39
  396. data/lib/packager/query_runner.rb +0 -21
  397. data/lib/packager/resources/Dockerfile +0 -11
  398. data/lib/packager/resources/app_binary +0 -7
  399. data/lib/packager/resources/bundle_config +0 -3
  400. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  401. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  402. data/lib/serverless/Domain +0 -32
  403. data/lib/serverless/cli.rb +0 -75
  404. data/lib/serverless/resources/command_name.js +0 -5
  405. data/lib/serverless/resources/environment.js +0 -7
  406. data/lib/serverless/resources/handler.js.tt +0 -28
  407. data/lib/serverless/resources/run_binary.js +0 -22
  408. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,318 @@
1
+ require "set"
2
+
3
+ module Hecks
4
+ module Bluebook
5
+ # Lightweight formal methods over the IR — the same family as TLA+/
6
+ # Alloy/P: every lifecycle is a declared finite state machine and
7
+ # every process manager a declared protocol, and BOTH are already
8
+ # data, not code, so they can be MODEL-CHECKED rather than merely
9
+ # executed. Static analysis only — no bluebook boots twice, no
10
+ # runtime is touched — over what meta_validator/judge.rb and the
11
+ # builders' own `validate!` methods leave uncovered (an undeclared
12
+ # transition target, a dispatch to nowhere, a compensation nothing
13
+ # can ever reach).
14
+ #
15
+ # THE RARE PROPERTY THIS RESTS ON: the model IS the implementation.
16
+ # A checker over TLA+ verifies a SPEC a human keeps in sync with
17
+ # code by hand ; this verifies the same IR the runtime dispatches
18
+ # against, so there is no second copy to drift.
19
+ module ModelCheck
20
+ Finding = Struct.new(:kind, :severity, :subject, :message, keyword_init: true) do
21
+ def to_s = "#{severity.to_s.upcase.ljust(7)} #{kind.to_s.ljust(20)} #{subject} — #{message}"
22
+ end
23
+
24
+ # A FINDING SHIPPED, NOT SILENCED — the coverage-gate idiom, empty
25
+ # allowlists enforced BOTH directions (spec/model_check_spec.rb holds
26
+ # this exact table: an error the checker reports and this does not
27
+ # name is a regression, an entry the checker no longer reports is
28
+ # stale and must be deleted). bin/model_check reads this same
29
+ # constant, so the tool and the spec can never drift apart.
30
+ #
31
+ # "banking"/ExternalSettlement — found on the first real run:
32
+ # ExternalSettlement declares `ends_on "ExternalTransferSent"`, and
33
+ # ExternalTransfer.Send genuinely emits it — the event is real, and
34
+ # the AGGREGATE reaches "sent" (its own, separate lifecycle) — but
35
+ # the SAGA'S protocol has no `on "ExternalTransferSent"` handler, so
36
+ # its own `state "sent"` is unreachable through the chain the
37
+ # checker walks, and the saga's own bookkeeping (saga_log, ends_on)
38
+ # never closes it. Real domain activity is unaffected; the saga's
39
+ # OWN tracking of it is not. Left named rather than redesigning a
40
+ # corpus fixture that is not this checker's to redesign.
41
+ # S7, ADR 0025 — the ExternalSettlement finding this used to
42
+ # allowlist is GONE, not just quieted: its "sent" state was a
43
+ # `state "x"` line never named by any handler's own from:/to:, a
44
+ # pure declaration-drift artifact. States are DERIVED from the
45
+ # transitions that name them now (ProcessManagerBuilder#derived_
46
+ # states), so a state nothing ever transitions into or out of no
47
+ # longer exists to be unreachable — the finding this allowlisted
48
+ # cannot occur any more, by construction.
49
+ ALLOWED_FINDINGS = {}.freeze
50
+
51
+ module_function
52
+
53
+ def call(bluebook)
54
+ findings = []
55
+ bluebook.aggregates.each do |aggregate|
56
+ findings.concat(lifecycle_findings(aggregate, aggregate))
57
+ aggregate.entities.each { |entity| findings.concat(lifecycle_findings(aggregate, entity)) }
58
+ end
59
+ bluebook.process_managers.each { |pm| findings.concat(saga_findings(bluebook, pm)) }
60
+ bluebook.policies.each { |policy| findings.concat(policy_findings(bluebook, policy)) }
61
+ findings
62
+ end
63
+
64
+ # ── lifecycles (aggregate AND entity — a piece may declare one too) ──
65
+
66
+ def lifecycle_findings(aggregate, declaring)
67
+ lifecycle = declaring.lifecycle
68
+ return [] unless lifecycle
69
+
70
+ subject = declaring.equal?(aggregate) ? aggregate.hecks_name : "#{aggregate.hecks_name}::#{declaring.hecks_name}"
71
+ commands = Array(declaring.commands).map(&:hecks_name)
72
+
73
+ findings = []
74
+ findings.concat(unknown_transition_commands(lifecycle, commands, subject))
75
+
76
+ full = full_states(lifecycle)
77
+ reached = reachable_states(lifecycle)
78
+
79
+ (full - reached.to_a).each do |state|
80
+ findings << Finding.new(kind: :unreachable_state, severity: :error, subject: subject,
81
+ message: "#{state.inspect} is declared (in a transition's from: or target) " \
82
+ "but no path from #{lifecycle.default.inspect} ever reaches it")
83
+ end
84
+
85
+ lifecycle.transitions.each do |command, transition|
86
+ next unless transition.constrained?
87
+ next if Array(transition.from).any? { |source| reached.include?(source) }
88
+
89
+ findings << Finding.new(kind: :dead_transition, severity: :error, subject: subject,
90
+ message: "#{command} from #{Array(transition.from).inspect} can never fire — " \
91
+ "none of those states is ever reached")
92
+ end
93
+
94
+ any_unconstrained = lifecycle.transitions.any? { |_, t| !t.constrained? }
95
+ (reached - terminal_exempt(lifecycle)).each do |state|
96
+ next if any_unconstrained
97
+ next if lifecycle.transitions.any? { |_, t| t.constrained? && Array(t.from).include?(state) }
98
+
99
+ findings << Finding.new(kind: :stuck_state, severity: :warning, subject: subject,
100
+ message: "#{state.inspect} is reached but no transition ever leaves it — " \
101
+ "fine if that is meant to be terminal")
102
+ end
103
+
104
+ findings
105
+ end
106
+
107
+ def unknown_transition_commands(lifecycle, commands, subject)
108
+ lifecycle.transitions.filter_map do |command, _transition|
109
+ next if commands.include?(command)
110
+
111
+ Finding.new(kind: :unknown_command, severity: :error, subject: subject,
112
+ message: "a transition names #{command.inspect}, which this construct declares no command for")
113
+ end
114
+ end
115
+
116
+ # default, every declared target, and every declared from — a
117
+ # from-only state (declared nowhere as a target) is real and is
118
+ # exactly the hole `Lifecycle#states` leaves: it answers default
119
+ # plus targets only.
120
+ def full_states(lifecycle)
121
+ (
122
+ [lifecycle.default] +
123
+ lifecycle.transitions.map { |_, t| t.target } +
124
+ lifecycle.transitions.flat_map { |_, t| Array(t.from) }
125
+ ).uniq
126
+ end
127
+
128
+ # Least fixpoint from the default state: an UNCONSTRAINED
129
+ # transition always fires, from wherever the machine is ; a
130
+ # constrained one fires once any of its named sources is reached.
131
+ def reachable_states(lifecycle)
132
+ reached = Set.new([lifecycle.default])
133
+ loop do
134
+ grown = false
135
+ lifecycle.transitions.each do |_, transition|
136
+ next if reached.include?(transition.target)
137
+ next if transition.constrained? && Array(transition.from).none? { |source| reached.include?(source) }
138
+
139
+ reached << transition.target
140
+ grown = true
141
+ end
142
+ break unless grown
143
+ end
144
+ reached
145
+ end
146
+
147
+ # A state with no OUTGOING declared path at all is exempt from the
148
+ # stuck-state WARNING for a different reason than "it fires an
149
+ # unconstrained transition" — the default state of a lifecycle
150
+ # with only constrained transitions is legitimately allowed to sit
151
+ # forever, since nothing about *entering* it via default implies
152
+ # anything must eventually move it, unlike a state a transition
153
+ # explicitly delivered somewhere.
154
+ def terminal_exempt(_lifecycle) = []
155
+
156
+ # ── process managers / sagas ──────────────────────────────────────
157
+
158
+ def saga_findings(bluebook, pm)
159
+ findings = []
160
+ emitted = emitted_events(bluebook)
161
+ verbs = verbs_of(bluebook)
162
+
163
+ [pm.starts_on, pm.ends_on].compact.each do |event|
164
+ next if emitted.include?(bare(event))
165
+
166
+ findings << Finding.new(kind: :deaf_trigger, severity: :error, subject: pm.name,
167
+ message: "starts_on/ends_on names #{event.inspect}, which no command in this " \
168
+ "domain emits")
169
+ end
170
+
171
+ reached = pm_reachable_states(pm, emitted)
172
+ (Array(pm.states) - reached.to_a).each do |state|
173
+ findings << Finding.new(kind: :unreachable_pm_state, severity: :error, subject: pm.name,
174
+ message: "#{state.inspect} is declared but no handler chain from " \
175
+ "#{pm.states.first.inspect} ever reaches it")
176
+ end
177
+
178
+ pm.handlers.each do |handler|
179
+ # The compensating leg answers REFUSED, a synthetic trigger no
180
+ # command ever emits by name (ProcessManager::REFUSED) — not
181
+ # a deaf handler, the one handler this domain's own events can
182
+ # never satisfy on purpose.
183
+ if handler.event_type != ProcessManager::REFUSED && !emitted.include?(bare(handler.event_type))
184
+ findings << Finding.new(kind: :deaf_handler, severity: :error, subject: pm.name,
185
+ message: "a handler answers #{handler.event_type.inspect}, which no command " \
186
+ "in this domain emits")
187
+ end
188
+
189
+ handler.dispatches.each do |dispatch|
190
+ # SAME-DOMAIN, same as `SagaInterpreter#qualified` — a dispatch
191
+ # naming no domain at all (the ordinary shape a bare command
192
+ # constant now produces, S6) means THIS one, and is compared
193
+ # against `verbs_of`'s own fully-qualified spelling qualified
194
+ # the identical way, not left bare to miss it on a technicality.
195
+ qualified = dispatch.command_name.include?("::") ? dispatch.command_name : "#{bluebook.name}::#{dispatch.command_name}"
196
+ next if verbs.include?(qualified)
197
+
198
+ findings << Finding.new(kind: :unknown_dispatch, severity: :error, subject: pm.name,
199
+ message: "dispatches #{dispatch.command_name.inspect}, which this domain " \
200
+ "declares no command at — cross-domain dispatch is out of this " \
201
+ "checker's scope, same as CommandRules#resolve_references")
202
+ end
203
+ end
204
+
205
+ if pm.saga? && !reached.include?(pm.saga.from_state)
206
+ findings << Finding.new(kind: :dead_compensation, severity: :error, subject: pm.name,
207
+ message: "the compensation leaves #{pm.saga.from_state.inspect}, which no " \
208
+ "handler chain ever reaches — a refusal here can never fire it")
209
+ end
210
+
211
+ findings
212
+ end
213
+
214
+ # A handler edge is only usable in the closure if it can actually
215
+ # FIRE — REFUSED always can (it is a compensation trigger, not an
216
+ # event), and any other handler needs its event genuinely emitted.
217
+ # Without this, a deaf handler's declared from_state -> to_state
218
+ # pair reads as connected even though nothing can ever traverse
219
+ # it, which would hide exactly the states this walk exists to
220
+ # catch (a state only "reachable" through a handler that itself
221
+ # never fires).
222
+ def pm_reachable_states(pm, emitted)
223
+ return Set.new if Array(pm.states).empty?
224
+
225
+ reached = Set.new([pm.states.first])
226
+ loop do
227
+ grown = false
228
+ pm.handlers.each do |handler|
229
+ next unless handler.event_type == ProcessManager::REFUSED || emitted.include?(bare(handler.event_type))
230
+ next unless reached.include?(handler.from_state)
231
+ next if reached.include?(handler.to_state)
232
+
233
+ reached << handler.to_state
234
+ grown = true
235
+ end
236
+ break unless grown
237
+ end
238
+ reached
239
+ end
240
+
241
+ # ── policies ───────────────────────────────────────────────────────
242
+
243
+ def policy_findings(bluebook, policy)
244
+ return [] if policy.target_domain # cross-domain: CommandRules skips it too; so does this checker.
245
+
246
+ emitted = emitted_events(bluebook)
247
+ findings = []
248
+
249
+ # `policy.event_name` (Naming.unqualified) — NOT `bare`, which only
250
+ # strips a "::" domain qualifier. An AGGREGATE-scoped policy's
251
+ # `on_event` carries a "." aggregate qualifier instead (PolicyBuilder
252
+ # stores whatever was typed, verbatim — see `on "Account.
253
+ # AccountFrozen"`), and `bare` left it untouched, silently comparing
254
+ # "Account.AccountFrozen" against a list of bare emitted names that
255
+ # can never contain it. Latent until now : banking's one aggregate-
256
+ # scoped same-domain policy check would have caught it, but its only
257
+ # prior aggregate-scoped policy (ReviewOnFreeze) is also cross-domain
258
+ # (`across "Compliance"`), which exits this method one line above
259
+ # before the mismatch is ever reached.
260
+ unless emitted.include?(policy.event_name)
261
+ findings << Finding.new(kind: :deaf_policy, severity: :error, subject: policy.name,
262
+ message: "on #{policy.on_event.inspect}, which no command in this domain emits")
263
+ end
264
+
265
+ # `trigger` is spelled "Aggregate.Command" (or "Entity.Command" one
266
+ # level down), completed to an FQN by PolicyInterpreter#deliver as
267
+ # "#{domain}::#{trigger_command}" — the same join `verbs_of` builds
268
+ # independently, so the two spellings have to be compared as FQNs,
269
+ # never as bare command names.
270
+ unless verbs_of(bluebook).include?("#{bluebook.name}::#{policy.trigger_command}")
271
+ findings << Finding.new(kind: :unknown_trigger, severity: :error, subject: policy.name,
272
+ message: "trigger #{policy.trigger_command.inspect} resolves to no command " \
273
+ "this domain declares")
274
+ end
275
+
276
+ findings
277
+ end
278
+
279
+ # ── shared enumeration ────────────────────────────────────────────
280
+
281
+ # A PORT OPERATION EMITS TOO — the primary/driving port an adapter
282
+ # outside the bluebook calls through (see hecksagon_builder.rb) is a
283
+ # second, real source of events, alongside a command's own `emits`.
284
+ # Ports attach to the aggregate/bluebook from the SIBLING `.hecksagon`
285
+ # file, not this one — a caller that boots only the `.bluebook` (as
286
+ # the fixtures under spec/fixtures/model_check/ do, having no
287
+ # hecksagon at all) simply finds none, which is correct : nothing
288
+ # can be deaf to an event that isn't even wired up yet.
289
+ def emitted_events(bluebook)
290
+ aggregate_emits = bluebook.aggregates.flat_map do |aggregate|
291
+ aggregate.commands.map(&:emits) +
292
+ aggregate.entities.flat_map { |entity| entity.commands.map(&:emits) } +
293
+ aggregate.ports.flat_map { |port| port.operations.map(&:emits) }
294
+ end
295
+ chapter_emits = bluebook.ports.flat_map { |port| port.operations.map(&:emits) }
296
+
297
+ (aggregate_emits + chapter_emits).flatten.uniq
298
+ end
299
+
300
+ # Fully-qualified, the same spelling DispatchSpec#command_name
301
+ # carries and fuzzing/sequence_generator/catalog.rb builds
302
+ # independently for the same reason: a saga dispatch and a fuzzer
303
+ # step both have to name a verb the same way the door does.
304
+ def verbs_of(bluebook)
305
+ bluebook.aggregates.flat_map do |aggregate|
306
+ verbs = aggregate.commands.map { |command| "#{bluebook.name}::#{aggregate.hecks_name}.#{command.hecks_name}" }
307
+ verbs + aggregate.entities.flat_map do |entity|
308
+ entity.commands.map { |command|
309
+ "#{bluebook.name}::#{aggregate.hecks_name}.#{entity.hecks_name}.#{command.hecks_name}"
310
+ }
311
+ end
312
+ end
313
+ end
314
+
315
+ def bare(event) = event.to_s.split("::").last
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,120 @@
1
+ module Hecks
2
+ module Bluebook
3
+ # WHICH REGEXES A BLUEBOOK MAY SAY.
4
+ #
5
+ # A `pattern:` is a fact about a value, carried in a bluebook — declared
6
+ # data, not Ruby code, so it must not lean on what any one engine happens
7
+ # to accept. Regex engines disagree in two different ways :
8
+ #
9
+ # ONLY A BACKTRACKING ENGINE CAN MATCH IT — lookahead, lookbehind,
10
+ # backreferences, atomic groups, possessive quantifiers. None of these
11
+ # can be matched in linear time, and linear-time engines refuse them
12
+ # outright. Refused here for the same reason.
13
+ #
14
+ # EVERY ENGINE PARSES IT AND THEY MEAN DIFFERENT THINGS — the dangerous
15
+ # half, because nothing errors. `\d` `\w` `\s` are ASCII in some engines
16
+ # and Unicode in others ; `[:digit:]` and friends flip the same way in
17
+ # the other direction. Both families are refused, and a domain spells
18
+ # the range it means.
19
+ #
20
+ # What remains — explicit ranges, alternation, quantifiers, anchors,
21
+ # groups — reads identically everywhere, with `^` and `$` as LINE anchors
22
+ # (Ruby's reading). The evidence is spec/corpus/fixtures/patterns.json.
23
+ module PatternSubset
24
+ Rejection = Struct.new(:construct, :reason)
25
+
26
+ REASONS = {
27
+ backreference:
28
+ "backreferences cannot be matched in linear time and portable " \
29
+ "engines refuse them ; a declared pattern may not depend on one",
30
+ named_backreference:
31
+ "a named backreference is still a backreference — it cannot be " \
32
+ "matched in linear time ; a declared pattern may not depend on one",
33
+ perl_class:
34
+ "engines read it in OPPOSITE directions : ASCII in some and " \
35
+ "Unicode in others, so an Arabic-Indic digit satisfies one and not " \
36
+ "the other. Spell the range you mean — [0-9], [A-Za-z0-9_], [ \t] " \
37
+ "— which every engine reads the same way",
38
+ posix_class:
39
+ "[:digit:] and friends flip between ASCII and Unicode across " \
40
+ "engines — the mirror of the perl classes, and wrong in the same " \
41
+ "way. Spell the range you mean",
42
+ lookahead:
43
+ "lookahead cannot be matched in linear time and portable engines " \
44
+ "refuse it ; a declared pattern may not depend on it",
45
+ lookbehind:
46
+ "lookbehind cannot be matched in linear time and portable engines " \
47
+ "refuse it ; a declared pattern may not depend on it",
48
+ atomic_group:
49
+ "an atomic group is a backtracking-engine control knob — " \
50
+ "linear-time engines reject `(?>` as a syntax error",
51
+ possessive:
52
+ "a possessive quantifier is a backtracking-engine control knob — " \
53
+ "linear-time engines reject it as a syntax error"
54
+ }.freeze
55
+
56
+ module_function
57
+
58
+ # nil when the pattern is admitted, a Rejection when it is not.
59
+ #
60
+ # A CHARACTER WALK, deliberately plain : the subset is defined by this
61
+ # walk, and a cleverer spelling would hide what it admits. An escaped
62
+ # construct is a LITERAL, not a violation — `\(\?=` is the three
63
+ # characters "(?=" and says nothing about lookahead — which is why this
64
+ # steps over each backslash pair rather than matching the pattern as a
65
+ # whole.
66
+ def validate(pattern)
67
+ chars = pattern.to_s.chars
68
+ index = 0
69
+
70
+ while index < chars.length
71
+ if chars[index] == "\\"
72
+ nxt = chars[index + 1]
73
+ return refuse(:backreference) if nxt&.match?(/[1-9]/)
74
+ return refuse(:named_backreference) if %w[k g].include?(nxt)
75
+ return refuse(:perl_class) if %w[d D w W s S].include?(nxt)
76
+
77
+ index += nxt ? 2 : 1
78
+ next
79
+ end
80
+
81
+ if chars[index] == "(" && chars[index + 1] == "?"
82
+ return refuse(:lookahead) if %w[= !].include?(chars[index + 2])
83
+ return refuse(:lookbehind) if chars[index + 2] == "<" && %w[= !].include?(chars[index + 3])
84
+ return refuse(:atomic_group) if chars[index + 2] == ">"
85
+ end
86
+
87
+ return refuse(:posix_class) if posix_class_at?(chars, index)
88
+ return refuse(:possessive) if %w[* + ?].include?(chars[index]) && chars[index + 1] == "+"
89
+
90
+ index += 1
91
+ end
92
+
93
+ nil
94
+ end
95
+
96
+ # SPELLED OUT, not derived from the key : these strings are the refusal
97
+ # a caller reads.
98
+ CONSTRUCTS = {
99
+ backreference: "backreference",
100
+ named_backreference: "named backreference",
101
+ perl_class: "perl character class",
102
+ posix_class: "posix bracket class",
103
+ lookahead: "lookahead",
104
+ lookbehind: "lookbehind",
105
+ atomic_group: "atomic group",
106
+ possessive: "possessive quantifier"
107
+ }.freeze
108
+
109
+ def refuse(key) = Rejection.new(CONSTRUCTS.fetch(key), REASONS.fetch(key))
110
+
111
+ def posix_class_at?(chars, index)
112
+ return false unless chars[index] == "[" && chars[index + 1] == ":"
113
+
114
+ cursor = index + 2
115
+ cursor += 1 while chars[cursor]&.match?(/[a-zA-Z]/)
116
+ chars[cursor] == ":" && chars[cursor + 1] == "]"
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,41 @@
1
+ # GENERATED — projected from the language's own Policy aggregate.
2
+ # DO NOT EDIT: the holding half is rendered, and Behaviour::Policy
3
+ # is where anything hand-written belongs.
4
+ require_relative "behaviour/policy"
5
+
6
+ module Hecks
7
+ module Bluebook
8
+ class Policy
9
+ include Hecks::IR
10
+ include Behaviour::Policy
11
+
12
+ emits_ir(
13
+ name: :name,
14
+ on_event: :on_event,
15
+ trigger_command: :trigger_command,
16
+ target_domain: :target_domain,
17
+ where: :where,
18
+ for_each: :for_each,
19
+ with_spec: -> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } }
20
+ )
21
+
22
+ attr_reader :name, :on_event, :trigger_command, :target_domain, :where, :for_each, :with_spec
23
+
24
+ # AGGREGATE, DECLARED AND DELIBERATELY OFF THE WIRE
25
+ # the wire format is a pinned contract, and it does not carry
26
+ # where a policy was written before the builder hoisted it
27
+ attr_accessor :aggregate
28
+
29
+ def initialize(name:, on_event: nil, trigger_command: nil, target_domain: nil, where: nil, for_each: nil, with_spec: [], aggregate: nil)
30
+ @name = name.to_s
31
+ @on_event = on_event
32
+ @trigger_command = trigger_command
33
+ @target_domain = target_domain
34
+ @where = where
35
+ @for_each = for_each
36
+ @with_spec = with_spec
37
+ @aggregate = aggregate&.to_s
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,86 @@
1
+ require_relative "behaviour/process_manager"
2
+ require_relative "../ir"
3
+
4
+ module Hecks
5
+ module Bluebook
6
+ DispatchSpec = Struct.new(:command_name, :with_spec, keyword_init: true) do
7
+ # A Struct already answers to_h; including the mixin puts the
8
+ # DECLARED emission ahead of Struct's own in the ancestry, which
9
+ # is what makes the shape data rather than a method body.
10
+ include Hecks::IR
11
+
12
+ emits_ir(
13
+ command_name: -> { command_name.to_s },
14
+ with_spec: -> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } }
15
+ )
16
+ end
17
+
18
+ ProcessManagerHandler = Struct.new(:event_type, :from_state, :to_state,
19
+ :dispatches, keyword_init: true) do
20
+ include Hecks::IR
21
+
22
+ emits_ir(
23
+ event_type: -> { event_type.to_s },
24
+ from_state: -> { from_state.to_s },
25
+ to_state: -> { to_state.to_s },
26
+ dispatches: many(:dispatches)
27
+ )
28
+ end
29
+
30
+ # The compensation half of a procedure, as its own thing.
31
+ #
32
+ # A PROCESS MANAGER coordinates: legs, states, an opinion about who goes
33
+ # next. A SAGA undoes: what makes the world good again when a leg it
34
+ # dispatched is refused. Two concepts, and the industry slurs them into one
35
+ # word — so here they are two objects, and a procedure either has a saga or
36
+ # does not.
37
+ #
38
+ # `undoes` is the ordered list of commands the compensation sends. Today that
39
+ # order is the AUTHOR's, written by hand in one `on :refused` leg, and the
40
+ # runtime does not know which legs actually completed. When compensation
41
+ # moves beside each dispatch — `reverses` on the step it reverses — this is
42
+ # where the completed ones, newest first, will live. The shape is already
43
+ # right for it; only the source of the order changes.
44
+ Saga = Struct.new(:trigger, :from_state, :to_state, :reversals, keyword_init: true) do
45
+ def undoes = reversals.map(&:command_name)
46
+
47
+ def to_s = "#{trigger} → #{to_state} (#{undoes.join(', ')})"
48
+ end
49
+
50
+ class ProcessManager
51
+ # The BLUEBOOK's name for this construct, asked the same way of a class
52
+ # that has crossed over and of an IR object that has not. Collapses into
53
+ # Construct when this one crosses.
54
+ # The trigger of a compensating leg. Not an event name — no aggregate
55
+ # announces that a leg the procedure dispatched was declined — so it lives
56
+ # here beside the thing it triggers rather than in the runtime that
57
+ # notices it. Declared in the language's Trigger vocabulary, which
58
+ # spec/vocabulary_conformance_spec holds to this constant.
59
+ REFUSED = Hecks::Vocabulary.fetch("Trigger").first
60
+
61
+ include Hecks::IR
62
+ include Behaviour::ProcessManager
63
+
64
+ emits_ir(
65
+ name: :name,
66
+ correlates_by: -> { correlates_by.to_s },
67
+ starts_on: :starts_on,
68
+ ends_on: :ends_on,
69
+ states: :states,
70
+ handlers: many(:handlers)
71
+ )
72
+
73
+ attr_reader :name, :correlates_by, :starts_on, :ends_on, :states, :handlers
74
+
75
+ def initialize(name:, correlates_by: nil, starts_on: nil, ends_on: nil,
76
+ states: [], handlers: [])
77
+ @name = name.to_s
78
+ @correlates_by = correlates_by
79
+ @starts_on = starts_on
80
+ @ends_on = ends_on
81
+ @states = states
82
+ @handlers = handlers
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,30 @@
1
+ require "find"
2
+
3
+ module Hecks
4
+ module Bluebook
5
+ # Finds domain declaration folders without loading or executing them.
6
+ class ProjectDiscovery
7
+ SKIPPED_DIRECTORIES = %w[.git .bundle data node_modules target tmp vendor generated coverage].freeze
8
+
9
+ attr_reader :root
10
+
11
+ def initialize(root)
12
+ @root = File.expand_path(root)
13
+ end
14
+
15
+ def bluebook_directories
16
+ found = []
17
+ Find.find(root) do |path|
18
+ if File.directory?(path)
19
+ if SKIPPED_DIRECTORIES.include?(File.basename(path))
20
+ Find.prune
21
+ elsif File.basename(path) == "bluebook" && Dir.glob(File.join(path, "*.bluebook")).any?
22
+ found << path
23
+ end
24
+ end
25
+ end
26
+ found.sort
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,40 @@
1
+ require_relative "project_discovery"
2
+ require_relative "project_register"
3
+
4
+ module Hecks
5
+ module Bluebook
6
+ # Boots each discovered directory and feeds its declarations into the
7
+ # deterministic project register. Discovery and FQN registration live in
8
+ # their own objects so either can be used without executing domains.
9
+ class ProjectLoader
10
+ Entry = ProjectRegister::Entry
11
+ DuplicateFqn = ProjectRegister::DuplicateFqn
12
+ MissingRealm = ProjectRegister::MissingRealm
13
+ LatestMismatch = ProjectRegister::LatestMismatch
14
+
15
+ attr_reader :root, :discovery, :register
16
+
17
+ def self.load(root) = new(root).load
18
+
19
+ def initialize(root, discovery: ProjectDiscovery.new(root), register: ProjectRegister.new)
20
+ @root = File.expand_path(root)
21
+ @discovery = discovery
22
+ @register = register
23
+ end
24
+
25
+ def load
26
+ discovery.bluebook_directories.each do |directory|
27
+ runtime = Runtime.boot(directory)
28
+ register.register(runtime.registry.bluebooks.values, runtime.registry, runtime, directory)
29
+ end
30
+ self
31
+ end
32
+
33
+ def entries = register.entries
34
+ def fetch(address) = register.fetch(address)
35
+ def include?(address) = register.include?(address)
36
+ def commands = register.commands
37
+ def queries = register.queries
38
+ end
39
+ end
40
+ end