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,62 @@
1
+ require_relative "../runtime/registry"
2
+
3
+ module Hecks
4
+ module Ports
5
+ # WHO CAN SIGN IN, AND WHAT ROLE DO THEY HOLD — the domain-specific
6
+ # half of sign-in `Ports::Authentication` deliberately stays out of
7
+ # (see that port's own header: it only ever talks to the external
8
+ # provider, never this registry's own data). Resolved the same way
9
+ # every other port here resolves its adapter: one adapter
10
+ # registry-wide answers this, since an app has no reason to want a
11
+ # different "who's allowed in" source per aggregate.
12
+ #
13
+ # Every domain shapes this differently — who its own "a person who
14
+ # can sign in" aggregate is, what admits them, what grants them
15
+ # access — so this port is pure delegation, same as every sibling
16
+ # port. The one piece of this that IS generic (a live Governance
17
+ # grant should win over whatever an aggregate's own role field
18
+ # says) lives on `Ports::Authorization#live_role_for` instead —
19
+ # a domain's adapter here calls that directly, rather than this
20
+ # port re-deriving Governance-query logic another port already owns.
21
+ module AccessControl
22
+ NAME = "access_control"
23
+
24
+ module_function
25
+
26
+ def session_for_identity(registry, identity_id:)
27
+ adapter(registry).session_for_identity(registry, identity_id: identity_id)
28
+ end
29
+
30
+ def provision(registry, email:, issuer:, subject:)
31
+ adapter(registry).provision(registry, email: email, issuer: issuer, subject: subject)
32
+ end
33
+
34
+ def available_roles(registry)
35
+ adapter(registry).available_roles(registry)
36
+ end
37
+
38
+ def grant(registry, email:, role:)
39
+ adapter(registry).grant(registry, email: email, role: role)
40
+ end
41
+
42
+ def all_people(registry)
43
+ adapter(registry).all_people(registry)
44
+ end
45
+
46
+ def adapter(registry)
47
+ implementations = registry.adapters.values.select { |a| a.port == NAME }
48
+
49
+ case implementations.size
50
+ when 1 then Adapters.const_get(implementations.first.name)
51
+ when 0
52
+ raise Runtime::WiringError,
53
+ "no adapter implements the #{NAME} port — nothing can answer who's allowed in"
54
+ else
55
+ raise Runtime::WiringError,
56
+ "#{implementations.size} adapters implement the #{NAME} port " \
57
+ "(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,104 @@
1
+ module Hecks
2
+ module Ports
3
+ module Agent
4
+ # WHERE A RAW HASH BECOMES A STRUCT — the one place, so every
5
+ # adapter (the real `claude_code` one, and any scripted double
6
+ # standing in for it in a spec) is held to the identical shape.
7
+ # An adapter's whole job ends at "here is what came back, already
8
+ # JSON"; whether that hash is USABLE is decided once, here, not
9
+ # re-decided per adapter.
10
+ #
11
+ # Every failure raises `ValidationError` — never lets a bare
12
+ # `NoMethodError` on nil, or a `KeyError`, reach a caller looking
13
+ # for an agent-shaped failure. `bin/interview` catches exactly one
14
+ # class for "the model answered badly," not a grab-bag of Ruby's
15
+ # own.
16
+ module Answers
17
+ VERB_PATTERN = /\A[A-Za-z]+::[A-Za-z]+\.[A-Za-z]+\z/
18
+
19
+ module_function
20
+
21
+ def questions(raw)
22
+ rows(raw, "questions").map { |row| Question.new(text: text!(row, "text"), because: text!(row, "because")) }
23
+ end
24
+
25
+ def proposals(raw)
26
+ rows(raw, "proposals").map do |row|
27
+ Proposal.new(verb: verb!(row), rationale: text!(row, "rationale"), arguments: arguments!(row))
28
+ end
29
+ end
30
+
31
+ def findings(raw)
32
+ rows(raw, "findings").map do |row|
33
+ Finding.new(kind: kind!(row), severity: severity!(row), subject: text!(row, "subject"),
34
+ message: text!(row, "message"))
35
+ end
36
+ end
37
+
38
+ def suggestions(raw)
39
+ rows(raw, "names").map do |row|
40
+ Suggestion.new(name: text!(row, "name"), because: text!(row, "because"),
41
+ rejected: Array(row["rejected"]).map(&:to_s))
42
+ end
43
+ end
44
+
45
+ # ── shared checks, each named for what it refuses ───────────────
46
+
47
+ def rows(raw, key)
48
+ raise ValidationError, "expected a Hash back, got #{raw.class}: #{raw.inspect}" unless raw.is_a?(Hash)
49
+
50
+ value = raw[key]
51
+ return value if value.is_a?(Array)
52
+
53
+ raise ValidationError, "no #{key.inspect} array in the answer: #{raw.inspect}"
54
+ end
55
+
56
+ def text!(row, key)
57
+ value = row[key]
58
+ return value.to_s if value && !value.to_s.strip.empty?
59
+
60
+ raise ValidationError, "#{key.inspect} missing or blank in #{row.inspect}"
61
+ end
62
+
63
+ # A VERB THE LANGUAGE COULD NOT EVEN PARSE IS AN ADAPTER FAULT,
64
+ # refused right here — the same `not_fully_qualified` shape the
65
+ # language's own grammar already refuses by, reused rather than
66
+ # reinvented. A verb naming a real category that turns out to
67
+ # describe the wrong fact is NOT this port's business: that one
68
+ # dispatches, and `Interview::Session#offer` is what says no.
69
+ def verb!(row)
70
+ verb = row["verb"].to_s
71
+ return verb if VERB_PATTERN.match?(verb)
72
+
73
+ raise ValidationError, "#{verb.inspect} is not a fully-qualified verb (Chapter::Aggregate.Command)"
74
+ end
75
+
76
+ def kind!(row)
77
+ kind = row["kind"].to_s.to_sym
78
+ return kind if CRITIQUE_KINDS.include?(kind)
79
+
80
+ raise ValidationError, "#{kind.inspect} is not a critique kind this port knows (#{CRITIQUE_KINDS.join(', ')})"
81
+ end
82
+
83
+ def severity!(row)
84
+ severity = row["severity"].to_s.to_sym
85
+ return severity if SEVERITIES.include?(severity)
86
+
87
+ raise ValidationError, "#{severity.inspect} is not a severity this port knows (#{SEVERITIES.join(', ')})"
88
+ end
89
+
90
+ # ROWS SHAPED EXACTLY AS `Interview::Proposal::Argument` — a
91
+ # reference's `field` is legitimately blank (a reference IS an
92
+ # id), so only `name` is required here.
93
+ def arguments!(row)
94
+ Array(row["arguments"]).map do |argument|
95
+ name = argument["name"].to_s
96
+ raise ValidationError, "argument row has no name: #{argument.inspect}" if name.empty?
97
+
98
+ { name: name, field: argument["field"].to_s, value: argument["value"].to_s }
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,4 @@
1
+ Hecks.port "agent" do
2
+ verb "asked_by"
3
+ signal :reply
4
+ end
@@ -0,0 +1,167 @@
1
+ require_relative "../runtime/registry"
2
+
3
+ module Hecks
4
+ module Ports
5
+ # THE INTERVIEWER — whatever asks the next good question, reads a
6
+ # sentence back into proposed declarations, judges a model on taste
7
+ # rather than structure, or suggests a name. Resolved the singleton
8
+ # way every other port here is (`Ports::IdentityGeneration`'s own
9
+ # zero/one/many — one interviewer per process, never per-aggregate),
10
+ # because a second adapter answering this port is exactly as
11
+ # unchoosable as a second one minting identities.
12
+ #
13
+ # BUILT LAST, ON PURPOSE, AND STILL OPTIONAL. `.claude/skills/
14
+ # interview/SKILL.md` conducts a whole session with NONE of this —
15
+ # Claude Code reads `bin/interview state` itself, asks the human
16
+ # directly with real conversation context, and calls `bin/interview
17
+ # ask`/`propose`/`accept` itself. This port exists for what that
18
+ # cannot reach: a HEADLESS run (`bin/interview suggest`, no model in
19
+ # the room otherwise), a batch `critique` over an already-written
20
+ # chapter, and — the actual point of building it at all — a
21
+ # SCRIPTED double (`spec/fixtures/scripted_agent.rb`) that makes the
22
+ # whole loop testable with no real model involved.
23
+ #
24
+ # WHAT THIS PORT IS NOT. It does not judge whether a declaration is
25
+ # WELL-FORMED — that is the meta-domain's own job, answered by a real
26
+ # dispatch refusing or not (`Interview::Session#offer`). This port
27
+ # only ever produces SHAPE: a question, a proposed declaration, a
28
+ # judgement about taste, a name. A proposal naming a category the
29
+ # language does not declare is an ADAPTER fault, refused HERE, in
30
+ # `Answers`; a proposal naming a real category but describing the
31
+ # wrong domain fact is dispatched anyway, so the LANGUAGE gets to say
32
+ # why. That line is the whole design.
33
+ #
34
+ # PARSING BELONGS TO THIS FILE, NOT THE ADAPTER. An adapter answers
35
+ # with a plain, already-JSON-shaped Hash (a real model's parsed
36
+ # reply, or a spec double's own hand-built one) — never a Struct —
37
+ # so every adapter is validated identically here rather than trusting
38
+ # each one to refuse the same way. Two adapters that parsed
39
+ # differently would mean the SAME malformed answer passing through
40
+ # one and refusing through the other.
41
+ module Agent
42
+ NAME = "agent"
43
+
44
+ # The answer came back and could not be used — no JSON in it, a
45
+ # category the language does not declare, a severity outside the
46
+ # two that exist. A foreign failure (JSON::ParserError, a missing
47
+ # key) wrapped into something this port owns, the same shape
48
+ # `Ports::Authentication::ValidationError` already is, so no
49
+ # caller ever rescues a raw parser error leaking out of an
50
+ # adapter.
51
+ ValidationError = Class.new(StandardError)
52
+
53
+ # Nothing answered at all — the binary is missing, the subprocess
54
+ # died, the call timed out. Separate from ValidationError on
55
+ # purpose: unavailable means retry or fall back to asking the
56
+ # human directly; malformed means the PROMPT is wrong, not the
57
+ # transport, and retrying identically will not fix it.
58
+ Unavailable = Class.new(StandardError)
59
+
60
+ # ── what the four operations hand back ──────────────────────────
61
+ #
62
+ # Structs, not hashes, for the reason `Bluebook::ModelCheck::Finding`
63
+ # already is one: a `keyword_init` Struct raises on a key nobody
64
+ # declared, which turns "the model invented a field" into a
65
+ # failure at the boundary rather than a nil three calls in.
66
+
67
+ # ONE QUESTION. `because` is the reasoning quoted back so a human
68
+ # can see it, not decoration — the same "show your work" the
69
+ # language's own refusal messages already practice.
70
+ Question = Struct.new(:text, :because, keyword_init: true)
71
+
72
+ # ONE PROPOSED DECLARATION, already shaped as
73
+ # `Interview::Proposal`'s own `Argument` rows — `{name:, field:,
74
+ # value:}`, the exact triple `bin/interview propose --arg
75
+ # name:field:value` already takes and `Interview::Lowering`
76
+ # already knows how to lower. Nothing here decides HOW to address
77
+ # a record or whether a value is bare or wrapped — that was
78
+ # already the proposal-writer's job in `interview.bluebook`'s own
79
+ # design (see `Argument`'s own comment there); this only adds the
80
+ # PROSE reasoning a human typed does not carry on its own.
81
+ Proposal = Struct.new(:verb, :arguments, :rationale, keyword_init: true)
82
+
83
+ # CRITIQUE REUSES `Bluebook::ModelCheck::Finding`'S OWN SHAPE —
84
+ # same fields, same severities, so a mechanical finding
85
+ # (`Session#gaps`) and a judgement (this) print in one list and
86
+ # sort together. The KIND vocabulary is NOT shared — ModelCheck's
87
+ # eleven kinds are structural facts about a graph; these are
88
+ # opinions about a model, closed here on purpose (an open kind
89
+ # field is an open prompt, and an open prompt drifts).
90
+ CRITIQUE_KINDS = %i[
91
+ anemic_aggregate wrong_boundary crud_verb leaky_value_object
92
+ missing_lifecycle missing_invariant ubiquitous_language
93
+ overreaching_identity untold_rule
94
+ ].freeze
95
+ SEVERITIES = %i[error warning].freeze
96
+
97
+ Finding = Struct.new(:kind, :severity, :subject, :message, keyword_init: true) do
98
+ def to_s = "#{severity.to_s.upcase.ljust(7)} #{kind.to_s.ljust(20)} #{subject} — #{message}"
99
+ end
100
+
101
+ # ONE SUGGESTED NAME. `rejected` carries the near-misses and why
102
+ # they were passed over — free at the point the model is choosing
103
+ # anyway, and the part a human actually learns from.
104
+ Suggestion = Struct.new(:name, :because, :rejected, keyword_init: true)
105
+
106
+ module_function
107
+
108
+ # THE NEXT BEST QUESTION. `state` is the whole picture — normally
109
+ # `Interview::Session#declaration` plus `#gaps`, so an adapter
110
+ # needs no memory of its own between calls; that is what lets a
111
+ # headless run be a series of one-shot processes, same as
112
+ # `bin/interview` itself already is.
113
+ def ask(registry, state:, asked: [])
114
+ Answers.questions(adapter(registry).ask(state: state, asked: asked))
115
+ end
116
+
117
+ # PROSE -> PROPOSED DECLARATIONS. Returns `[]` when the sentence
118
+ # carried no declaration at all (a clarifying question back from
119
+ # the human, say) — a legitimate answer, not a failure.
120
+ def interpret(registry, prose:, state:)
121
+ Answers.proposals(adapter(registry).interpret(prose: prose, state: state))
122
+ end
123
+
124
+ # WHAT IS WRONG WITH THIS AS A MODEL — handed everything already
125
+ # known (the language's own refusals, `Session#gaps`'s mechanical
126
+ # findings) so it spends its judgement on what neither of those
127
+ # can see, rather than restating them.
128
+ def critique(registry, declared:, refusals: [], findings: [])
129
+ Answers.findings(adapter(registry).critique(declared: declared, refusals: refusals, findings: findings))
130
+ end
131
+
132
+ # VOCABULARY HELP. `near` is what the chapter already calls
133
+ # things, so a suggestion cannot collide with a name in use.
134
+ #
135
+ # NAMED `suggest_name`, NOT `name` — the plan's own word for this
136
+ # operation, but `Module#name` already exists and is load-bearing
137
+ # everywhere (backtraces, RSpec's own description building,
138
+ # `inspect`): a module-function called `name` SHADOWS it the
139
+ # instant it's defined, and `SomeModule.name` (no args) then
140
+ # raises `ArgumentError: missing keywords` the next time anything
141
+ # — not this file, something else entirely — asks the module its
142
+ # own name. Measured, not theoretical: this exact collision broke
143
+ # RSpec's own failure-message formatting the first time it was
144
+ # named `name` here.
145
+ def suggest_name(registry, meaning:, kind:, near: [])
146
+ Answers.suggestions(adapter(registry).suggest_name(meaning: meaning, kind: kind, near: near))
147
+ end
148
+
149
+ def adapter(registry)
150
+ implementations = registry.adapters.values.select { |a| a.port == NAME }
151
+
152
+ case implementations.size
153
+ when 1 then Adapters.const_get(implementations.first.name)
154
+ when 0
155
+ raise Runtime::WiringError,
156
+ "no adapter implements the #{NAME} port — nothing can conduct an interview"
157
+ else
158
+ raise Runtime::WiringError,
159
+ "#{implementations.size} adapters implement the #{NAME} port " \
160
+ "(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ require_relative "agent/answers"
@@ -0,0 +1,4 @@
1
+ Hecks.port "authentication" do
2
+ verb "authenticated_by"
3
+ signal :reply
4
+ end
@@ -0,0 +1,50 @@
1
+ require_relative "../runtime/registry"
2
+
3
+ module Hecks
4
+ module Ports
5
+ # THE OIDC HANDSHAKE ITSELF — building the URL a browser goes to,
6
+ # and turning the code a provider sends back into a verified
7
+ # (issuer, subject, email) triple. Resolved the same way every
8
+ # other port here resolves its adapter: one adapter registry-wide
9
+ # answers this, not a per-aggregate binding, since a domain has no
10
+ # reason to want a different sign-in provider per aggregate.
11
+ #
12
+ # THE OTHER HALF of "who is this" — what a verified pair MEANS to
13
+ # this app, whether it resolves to an existing Identity — is
14
+ # `Ports::IdentityResolution`'s job, not this one's. This port only
15
+ # ever talks to the external provider; it never touches the
16
+ # registry's own Identity/Governance data (it takes `registry`
17
+ # purely to resolve which adapter answers it, same as every port
18
+ # here already does).
19
+ module Authentication
20
+ NAME = "authentication"
21
+
22
+ ValidationError = Class.new(StandardError)
23
+
24
+ module_function
25
+
26
+ def authorization_url(registry)
27
+ adapter(registry).authorization_url
28
+ end
29
+
30
+ def verify(registry, code:, state:, expected_state:)
31
+ adapter(registry).verify(code: code, state: state, expected_state: expected_state)
32
+ end
33
+
34
+ def adapter(registry)
35
+ implementations = registry.adapters.values.select { |a| a.port == NAME }
36
+
37
+ case implementations.size
38
+ when 1 then Adapters.const_get(implementations.first.name)
39
+ when 0
40
+ raise Runtime::WiringError,
41
+ "no adapter implements the #{NAME} port — nothing can authenticate a sign-in"
42
+ else
43
+ raise Runtime::WiringError,
44
+ "#{implementations.size} adapters implement the #{NAME} port " \
45
+ "(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,4 @@
1
+ Hecks.port "authorization" do
2
+ verb "authorized_by"
3
+ signal :reply
4
+ end
@@ -0,0 +1,59 @@
1
+ require_relative "../runtime/registry"
2
+
3
+ module Hecks
4
+ module Ports
5
+ # TWO YES/NO QUESTIONS AND ONE VALUE an application asks BEFORE
6
+ # binding a caller — resolved the same way `Ports::IdentityGeneration`
7
+ # resolves its own adapter: one adapter registry-wide answers this
8
+ # port, not a per-aggregate binding, since a domain has no reason to
9
+ # want a different authorization source per aggregate. What answers
10
+ # it is deliberately not named here — the governance-backed adapter
11
+ # is one implementation, not the only possible one, the same way
12
+ # `SequentialIdentity` and `SecureRandomIdentity` are two
13
+ # implementations of `identity_generation`.
14
+ #
15
+ # A caller decides what to DO with the answer — dispatch under that
16
+ # role, refuse, log, prefer it over some other fallback value — this
17
+ # only answers the question asked. Nothing here binds a
18
+ # `Runtime::Caller`, and nothing here is consulted by
19
+ # `CommandRules::Authorization`: that rule still only compares an
20
+ # ALREADY-BOUND caller's role against a command's, the same as it
21
+ # always has. `spec/act_as_spec.rb` remains the precedent for the
22
+ # OTHER shape — two separate registries, queried directly by name —
23
+ # for whenever Governance is not in the same boot as the caller;
24
+ # this port is the same two questions asked through one adapter
25
+ # when it is.
26
+ module Authorization
27
+ NAME = "authorization"
28
+
29
+ module_function
30
+
31
+ def holds_role?(registry, actor_id:, role:)
32
+ adapter(registry).holds_role?(registry, actor_id: actor_id, role: role)
33
+ end
34
+
35
+ def authorized_as?(registry, from_role:, to_role:)
36
+ adapter(registry).authorized_as?(registry, from_role: from_role, to_role: to_role)
37
+ end
38
+
39
+ def live_role_for(registry, actor_id:)
40
+ adapter(registry).live_role_for(registry, actor_id: actor_id)
41
+ end
42
+
43
+ def adapter(registry)
44
+ implementations = registry.adapters.values.select { |a| a.port == NAME }
45
+
46
+ case implementations.size
47
+ when 1 then Adapters.const_get(implementations.first.name)
48
+ when 0
49
+ raise Runtime::WiringError,
50
+ "no adapter implements the #{NAME} port — nothing can answer a role check"
51
+ else
52
+ raise Runtime::WiringError,
53
+ "#{implementations.size} adapters implement the #{NAME} port " \
54
+ "(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,4 @@
1
+ Hecks.port "clock" do
2
+ verb "timed_by"
3
+ signal :reply
4
+ end
@@ -0,0 +1,62 @@
1
+ require_relative "../runtime/registry"
2
+
3
+ module Hecks
4
+ module Ports
5
+ # WHAT TIME IT IS — the one fact a domain cannot derive and must not invent.
6
+ #
7
+ # Resolved exactly the way `Ports::IdentityGeneration` resolves its own
8
+ # adapter: one adapter registry-wide implements this, not a per-aggregate
9
+ # binding, because two aggregates in one running app have no real reason to
10
+ # disagree about the time.
11
+ #
12
+ # WHY A PORT AND NOT `Time.now`. A staleness rule — "another agent may take
13
+ # this claim after fifteen minutes" — is untestable against the real clock:
14
+ # a spec for it would either sleep for fifteen minutes or never run at all.
15
+ # Bound to a fixed adapter, the same rule is three lines. That is the whole
16
+ # argument, and it is the same one `SequentialIdentity` makes next door.
17
+ #
18
+ # A PREDICATE STILL CANNOT ASK THE TIME, and this does not change that. The
19
+ # expression sublanguage has no clock and should not: a `given` that read
20
+ # the time would evaluate differently on two runs over the same record, and
21
+ # every replay, audit and fuzz oracle in this repository assumes it does
22
+ # not. So `now` remains an ARGUMENT the predicate merely reads, and this
23
+ # port is what lets a caller stop typing it — the door fills it in, the
24
+ # value is baked into the dispatch, and the recorded step carries the
25
+ # concrete number forever after.
26
+ #
27
+ # WHICH IS WHY THE RUNTIME DOES NOT CALL THIS. `IdentityGeneration`'s own
28
+ # note works through the replay question for a minted uuid and lands on
29
+ # "the value gets baked into the caller's args at the first live dispatch".
30
+ # A clock consulted INSIDE the interpreter would not have that property: a
31
+ # recorded corpus step replayed tomorrow would silently get tomorrow's
32
+ # time, and the fuzzer's oracle and the adapter-agreement gate both compare
33
+ # runs of exactly that shape. So the filling happens at the door, where a
34
+ # human or an agent is typing, and never on the dispatch path a replay uses.
35
+ module Clock
36
+ NAME = "clock"
37
+
38
+ module_function
39
+
40
+ # SECONDS, AS AN INTEGER, because that is what the sublanguage can do
41
+ # arithmetic on. `claimed_at.value + window.value <= now.value` is
42
+ # addition and comparison — the only two things it has — and a Time
43
+ # object supports neither.
44
+ def now(registry) = adapter(registry).now
45
+
46
+ def adapter(registry)
47
+ implementations = registry.adapters.values.select { |a| a.port == NAME }
48
+
49
+ case implementations.size
50
+ when 1 then Adapters.const_get(implementations.first.name)
51
+ when 0
52
+ raise Runtime::WiringError,
53
+ "no adapter implements the #{NAME} port — nothing can say what time it is"
54
+ else
55
+ raise Runtime::WiringError,
56
+ "#{implementations.size} adapters implement the #{NAME} port " \
57
+ "(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,4 @@
1
+ Hecks.port "extraction" do
2
+ verb "extracted_by"
3
+ signal :reply
4
+ end
@@ -0,0 +1,37 @@
1
+ require_relative "../runtime/registry"
2
+
3
+ module Hecks
4
+ module Ports
5
+ module Extraction
6
+ NAME = "extraction"
7
+
8
+ module_function
9
+
10
+ def canonical(block) = adapter.canonical(block)
11
+
12
+ def adapter
13
+ registry = Hecks.current_registry
14
+ unless registry
15
+ raise Runtime::WiringError,
16
+ "extraction resolved outside a boot — a predicate can only be " \
17
+ "read while a bluebook is loading"
18
+ end
19
+
20
+ implementations = registry.adapters.values.select { |a| a.port == NAME }
21
+
22
+ case implementations.size
23
+ when 1 then Adapters.const_get(implementations.first.name)
24
+ when 0
25
+ raise Runtime::WiringError,
26
+ "no adapter implements the #{NAME} port — nothing can recover a " \
27
+ "predicate's source"
28
+ else
29
+ raise Runtime::WiringError,
30
+ "#{implementations.size} adapters implement the #{NAME} port " \
31
+ "(#{implementations.map(&:name).sort.join(', ')}) — the runtime " \
32
+ "will not choose for you"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,4 @@
1
+ Hecks.port "identity_assignment" do
2
+ verb "assigned_by"
3
+ signal :reply
4
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "../runtime/registry"
2
+
3
+ module Hecks
4
+ module Ports
5
+ # WHICH VALUE A CREATING COMMAND'S OWN IDENTITY FIELD GETS, when
6
+ # neither a slug-from-another-field nor a sequence-with-prefix
7
+ # mechanically answers it — a driving app's own console, say,
8
+ # falls back to this when a collection's identity strategy names
9
+ # "port" rather than "slug"/"sequence" (both of those stay pure
10
+ # config-driven mechanics, computed by whoever is dispatching,
11
+ # never by this port).
12
+ #
13
+ # Pure delegation, same as every sibling port (AccessControl,
14
+ # IdentityGeneration): an app's own domain declares what "port"
15
+ # actually MEANS for it — mint a real UUID via
16
+ # Ports::IdentityGeneration, derive something else entirely — this
17
+ # port just resolves the one adapter that domain wired and calls
18
+ # it, the same "one adapter registry-wide, refuse if zero or many"
19
+ # resolution every other port here already uses.
20
+ module IdentityAssignment
21
+ NAME = "identity_assignment"
22
+
23
+ module_function
24
+
25
+ def assign(registry, agg_name:, field_name:, args:)
26
+ adapter(registry).assign(registry, agg_name: agg_name, field_name: field_name, args: args)
27
+ end
28
+
29
+ def adapter(registry)
30
+ implementations = registry.adapters.values.select { |a| a.port == NAME }
31
+
32
+ case implementations.size
33
+ when 1 then Adapters.const_get(implementations.first.name)
34
+ when 0
35
+ raise Runtime::WiringError,
36
+ "no adapter implements the #{NAME} port — nothing can assign an identity"
37
+ else
38
+ raise Runtime::WiringError,
39
+ "#{implementations.size} adapters implement the #{NAME} port " \
40
+ "(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,4 @@
1
+ Hecks.port "identity_generation" do
2
+ verb "generated_by"
3
+ signal :reply
4
+ end