hecks 2.9.0 → 3.0.1

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 (600) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +51 -6
  3. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  6. data/lib/hecks/adapters/driven/memory.rb +16 -3
  7. data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
  8. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  9. data/lib/hecks/adapters/driven/postgres.rb +13 -4
  10. data/lib/hecks/adapters/driven/prism.rb +41 -8
  11. data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
  12. data/lib/hecks/behaviors/expectations.rb +1 -1
  13. data/lib/hecks/behaviors.rb +1 -1
  14. data/lib/hecks/bench/cli.rb +62 -0
  15. data/lib/hecks/bench/environment.rb +71 -0
  16. data/lib/hecks/bench/postgres_probe.rb +30 -0
  17. data/lib/hecks/bench/report.rb +76 -0
  18. data/lib/hecks/bench/ruby_runner.rb +93 -0
  19. data/lib/hecks/bench/run.rb +55 -0
  20. data/lib/hecks/bench/rust_runner.rb +101 -0
  21. data/lib/hecks/bench/stats.rb +46 -0
  22. data/lib/hecks/bench/suite.rb +98 -0
  23. data/lib/hecks/bench/workload.rb +89 -0
  24. data/lib/hecks/bench.rb +17 -0
  25. data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
  26. data/lib/hecks/bluebook/attribute.rb +1 -1
  27. data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
  28. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
  29. data/lib/hecks/bluebook/chapter.rb +6 -2
  30. data/lib/hecks/bluebook/domain_port.rb +36 -5
  31. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
  32. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
  33. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
  34. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
  35. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
  36. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
  37. data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
  38. data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
  39. data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
  40. data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
  41. data/lib/hecks/bluebook/expression/resolver.rb +3 -2
  42. data/lib/hecks/bluebook/hexagon.rb +13 -5
  43. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
  44. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
  45. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  46. data/lib/hecks/bluebook/meta_validator.rb +9 -4
  47. data/lib/hecks/bluebook/model_check.rb +23 -0
  48. data/lib/hecks/bluebook/pattern_subset.rb +38 -0
  49. data/lib/hecks/bluebook/query.rb +20 -5
  50. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  51. data/lib/hecks/canonical_json.rb +30 -0
  52. data/lib/hecks/chapters.rb +102 -0
  53. data/lib/hecks/cli/behaviors.rb +61 -0
  54. data/lib/hecks/cli/canonicalise.rb +25 -0
  55. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  56. data/lib/hecks/cli/check_era.rb +88 -0
  57. data/lib/hecks/cli/console.rb +66 -0
  58. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  59. data/lib/hecks/cli/doc_coverage.rb +32 -0
  60. data/lib/hecks/cli/document.rb +2 -2
  61. data/lib/hecks/cli/expression_projection.rb +41 -0
  62. data/lib/hecks/cli/follow.rb +88 -0
  63. data/lib/hecks/cli/history.rb +44 -0
  64. data/lib/hecks/cli/ir.rb +1 -1
  65. data/lib/hecks/cli/mcp.rb +3 -3
  66. data/lib/hecks/cli/model_check.rb +15 -8
  67. data/lib/hecks/cli/pattern_cases.rb +47 -0
  68. data/lib/hecks/cli/present.rb +39 -0
  69. data/lib/hecks/cli/project_cli.rb +224 -32
  70. data/lib/hecks/cli/project_diagrams.rb +4 -3
  71. data/lib/hecks/cli/project_glossary.rb +44 -0
  72. data/lib/hecks/cli/reference.rb +46 -0
  73. data/lib/hecks/cli/refresh_projections.rb +44 -0
  74. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  75. data/lib/hecks/cli/release.rb +70 -0
  76. data/lib/hecks/cli/release_gem.rb +59 -0
  77. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  78. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  79. data/lib/hecks/cli/run.rb +3 -3
  80. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  81. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  82. data/lib/hecks/cli/shape.rb +70 -0
  83. data/lib/hecks/cli/smoke_http.rb +230 -0
  84. data/lib/hecks/cli/smoke_test.rb +5 -3
  85. data/lib/hecks/cli/spec_example.rb +27 -0
  86. data/lib/hecks/cli/statements.rb +32 -0
  87. data/lib/hecks/cli/stores.rb +4 -5
  88. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  89. data/lib/hecks/cli.rb +201 -52
  90. data/lib/hecks/codemod.rb +324 -0
  91. data/lib/hecks/corpus.rb +423 -0
  92. data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
  93. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
  94. data/lib/hecks/deploy/oidc.json +82 -2
  95. data/lib/hecks/doc/reference.rb +351 -0
  96. data/lib/hecks/doors/cli_door.rb +196 -0
  97. data/lib/hecks/doors/cli_runner.rb +297 -0
  98. data/lib/hecks/{facade → doors}/command_request.rb +1 -1
  99. data/lib/hecks/{facade → doors}/handle.rb +2 -2
  100. data/lib/hecks/{facade → doors}/json_door.rb +4 -4
  101. data/lib/hecks/doors/launcher_options.rb +129 -0
  102. data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
  103. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  104. data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
  105. data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
  106. data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
  107. data/lib/hecks/doors.rb +61 -0
  108. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
  109. data/lib/hecks/embryonaut_bluebook.rb +1 -1
  110. data/lib/hecks/engine_agreement.rb +156 -0
  111. data/lib/hecks/facade.rb +24 -37
  112. data/lib/hecks/forms/app.rb +6 -6
  113. data/lib/hecks/forms/banking_presentation.rb +50 -0
  114. data/lib/hecks/forms/field_shape.rb +2 -1
  115. data/lib/hecks/forms/port_argument.rb +1 -1
  116. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  117. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  118. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  119. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  120. data/lib/hecks/fuzzing/differential.rb +156 -0
  121. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  122. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  123. data/lib/hecks/fuzzing/form_census.rb +136 -0
  124. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  125. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  126. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  127. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  128. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  129. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  130. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  131. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  132. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  133. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  134. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  135. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  136. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  137. data/lib/hecks/fuzzing/properties.rb +162 -0
  138. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  139. data/lib/hecks/fuzzing/replay.rb +505 -0
  140. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  141. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  142. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  143. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  144. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  145. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  146. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  147. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  148. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  149. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  150. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  151. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  152. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  153. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  154. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  155. data/lib/hecks/fuzzing.rb +23 -0
  156. data/lib/hecks/grammar/evolve.rb +336 -0
  157. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  158. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  159. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  160. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  161. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  162. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  163. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  164. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  165. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  166. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  167. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  168. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  169. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  170. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  171. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  172. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  173. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  174. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  175. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  176. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  177. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  178. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  179. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  180. data/lib/hecks/hecks/adapters/git.rb +102 -0
  181. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  182. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  183. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  184. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  185. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  186. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  187. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  188. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  189. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  190. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  191. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  192. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  193. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  194. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  195. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  196. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  197. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  198. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  199. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  200. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  201. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  202. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  203. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  204. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  205. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  206. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  207. data/lib/hecks/hecks/codebase.behaviors +172 -0
  208. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  209. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  210. data/lib/hecks/hecks/custodian.behaviors +179 -0
  211. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  212. data/lib/hecks/hecks/deploy.behaviors +74 -0
  213. data/lib/hecks/hecks/deploy.world +12 -0
  214. data/lib/hecks/hecks/environments/memory.world +10 -0
  215. data/lib/hecks/hecks/governance.world +7 -0
  216. data/lib/hecks/hecks/hecks.behaviors +62 -0
  217. data/lib/hecks/hecks/hecks.bluebook +124 -0
  218. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  219. data/lib/hecks/hecks/hecks.world +42 -0
  220. data/lib/hecks/kernel_capabilities.rb +194 -0
  221. data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
  222. data/lib/hecks/language/bluebook/query.bluebook +7 -0
  223. data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
  224. data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
  225. data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
  226. data/lib/hecks/mcp_stdio_guard.rb +2 -2
  227. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  228. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  229. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
  231. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +15 -5
  237. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  238. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
  239. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
  240. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  241. data/lib/hecks/ports/persistence/state_codec.rb +24 -5
  242. data/lib/hecks/projection_files.rb +285 -0
  243. data/lib/hecks/projections/bootstrap_table.rb +1 -1
  244. data/lib/hecks/projections/deploy/fargate.rb +5 -5
  245. data/lib/hecks/projections/deploy/lambda.rb +43 -32
  246. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  247. data/lib/hecks/projections/deploy/preview/template.rb +1 -1
  248. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  249. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  250. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  251. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  252. data/lib/hecks/projections/deploy/shared.rb +1 -1
  253. data/lib/hecks/projections/diagrams.rb +3 -3
  254. data/lib/hecks/projections/model/deviations.rb +1 -1
  255. data/lib/hecks/projections/parser_table.rb +4 -4
  256. data/lib/hecks/projections/reference.rb +2 -3
  257. data/lib/hecks/projections/rust_vocabulary.rb +1 -1
  258. data/lib/hecks/projector/cli_projector.rb +106 -37
  259. data/lib/hecks/projector.rb +1 -1
  260. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  261. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  262. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  263. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  264. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  265. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  266. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  267. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  268. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  269. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  270. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  271. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  272. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  273. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  274. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  275. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  276. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  277. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  278. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  279. data/lib/hecks/quality_control/cli/child.rb +45 -0
  280. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  281. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  282. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  283. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  284. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  285. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  286. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  287. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  288. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  289. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  290. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  291. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  292. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  293. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  294. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  295. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  296. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  297. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  298. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  299. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  300. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  301. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  302. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  303. data/lib/hecks/query_ir.rb +346 -0
  304. data/lib/hecks/query_ir_mcp.rb +198 -0
  305. data/lib/hecks/release/runner/ci_publisher.rb +2 -2
  306. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  307. data/lib/hecks/release/runner/console.rb +6 -0
  308. data/lib/hecks/release/runner/gem_publisher.rb +14 -7
  309. data/lib/hecks/release/runner/npm_publisher.rb +6 -36
  310. data/lib/hecks/release/runner/preflight.rb +26 -2
  311. data/lib/hecks/release/runner/published.rb +8 -26
  312. data/lib/hecks/release/runner/tagger.rb +30 -15
  313. data/lib/hecks/release/runner.rb +57 -6
  314. data/lib/hecks/router/namespace_installer.rb +3 -3
  315. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  316. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
  317. data/lib/hecks/runtime/dependency_planning.rb +20 -2
  318. data/lib/hecks/runtime/dispatcher.rb +53 -3
  319. data/lib/hecks/runtime/interpreting.rb +3 -0
  320. data/lib/hecks/runtime/loader.rb +65 -12
  321. data/lib/hecks/runtime/outbox.rb +1 -1
  322. data/lib/hecks/runtime/policy_interpreter.rb +5 -2
  323. data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
  324. data/lib/hecks/runtime/query_interpreter.rb +77 -0
  325. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  326. data/lib/hecks/runtime/refusal_wording.rb +23 -1
  327. data/lib/hecks/runtime/registry/verification.rb +163 -4
  328. data/lib/hecks/runtime/registry.rb +57 -3
  329. data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
  330. data/lib/hecks/runtime/value/coercion.rb +23 -135
  331. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  332. data/lib/hecks/runtime/value.rb +4 -1
  333. data/lib/hecks/runtime.rb +20 -6
  334. data/lib/hecks/rust_build/conformance.rb +158 -0
  335. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  336. data/lib/hecks/rust_build/coverage.rb +218 -0
  337. data/lib/hecks/rust_build/native_build.rb +124 -0
  338. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  339. data/lib/hecks/rust_build/project_rust.rb +332 -0
  340. data/lib/hecks/rust_build/wasm.rb +105 -0
  341. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  342. data/lib/hecks/rust_build.rb +144 -0
  343. data/lib/hecks/storehouse.rb +16 -16
  344. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
  345. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
  346. data/lib/hecks/three_zero/forms.yml +127 -0
  347. data/lib/hecks/three_zero.rb +12 -0
  348. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  349. data/lib/hecks/tools/comment_style.rb +1070 -0
  350. data/lib/hecks/tools/corpus_report.rb +87 -0
  351. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  352. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  353. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  354. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  355. data/lib/hecks/tools/era_reattest.rb +146 -0
  356. data/lib/hecks/tools/evolve_run.rb +189 -0
  357. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  358. data/lib/hecks/tools/heki_compaction.rb +100 -0
  359. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  360. data/lib/hecks/tools/journal_compaction.rb +89 -0
  361. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  362. data/lib/hecks/tools/query_ir_run.rb +81 -0
  363. data/lib/hecks/tools/regeneration_run.rb +166 -0
  364. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  365. data/lib/hecks/tools/sequence_script.rb +62 -0
  366. data/lib/hecks/tools/tail_merge.rb +117 -0
  367. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  368. data/lib/hecks/tools/translation_audit.rb +208 -0
  369. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  370. data/lib/hecks/tools.rb +86 -0
  371. data/lib/hecks/version.rb +1 -1
  372. data/lib/hecks.rb +32 -15
  373. data/qa/settings.yml +66 -0
  374. data/rust/.cargo/config.toml +6 -0
  375. data/rust/.gitignore +2 -0
  376. data/rust/Cargo.lock +54 -0
  377. data/rust/Cargo.toml +61 -0
  378. data/rust/build/.cargo/config.toml +3 -0
  379. data/rust/build/.gitignore +1 -0
  380. data/rust/build/Cargo.lock +7 -0
  381. data/rust/build/Cargo.toml +19 -0
  382. data/rust/build/src/build_artifact.rs +47 -0
  383. data/rust/build/src/cargo_sync.rs +248 -0
  384. data/rust/build/src/json.rs +421 -0
  385. data/rust/build/src/lineage_pass.rs +285 -0
  386. data/rust/build/src/main.rs +51 -0
  387. data/rust/build/src/optional_pass.rs +150 -0
  388. data/rust/build/src/pipeline.rs +264 -0
  389. data/rust/build/src/reserved_names.rs +76 -0
  390. data/rust/build/src/resolve.rs +124 -0
  391. data/rust/build/src/root.rs +25 -0
  392. data/rust/build/src/sidecars.rs +73 -0
  393. data/rust/build/src/subprocess.rs +51 -0
  394. data/rust/build/src/tmp.rs +31 -0
  395. data/rust/codegen/.cargo/config.toml +3 -0
  396. data/rust/codegen/.gitignore +1 -0
  397. data/rust/codegen/Cargo.lock +7 -0
  398. data/rust/codegen/Cargo.toml +20 -0
  399. data/rust/codegen/src/attr.rs +32 -0
  400. data/rust/codegen/src/bridging.rs +481 -0
  401. data/rust/codegen/src/commands.rs +1226 -0
  402. data/rust/codegen/src/constraints.rs +109 -0
  403. data/rust/codegen/src/dependency_planning.rs +209 -0
  404. data/rust/codegen/src/domain_generator.rs +1288 -0
  405. data/rust/codegen/src/exemplar.rs +230 -0
  406. data/rust/codegen/src/expr_emitter.rs +87 -0
  407. data/rust/codegen/src/fielded.rs +192 -0
  408. data/rust/codegen/src/hecks_naming.rs +33 -0
  409. data/rust/codegen/src/json.rs +335 -0
  410. data/rust/codegen/src/json_codec.rs +697 -0
  411. data/rust/codegen/src/literal.rs +207 -0
  412. data/rust/codegen/src/main.rs +290 -0
  413. data/rust/codegen/src/manifest.rs +227 -0
  414. data/rust/codegen/src/mutations.rs +784 -0
  415. data/rust/codegen/src/naming.rs +286 -0
  416. data/rust/codegen/src/ports.rs +208 -0
  417. data/rust/codegen/src/prelude.rs +116 -0
  418. data/rust/codegen/src/queries.rs +786 -0
  419. data/rust/codegen/src/reactions.rs +532 -0
  420. data/rust/codegen/src/read_models.rs +697 -0
  421. data/rust/codegen/src/reference_specs.rs +40 -0
  422. data/rust/codegen/src/registry.rs +966 -0
  423. data/rust/codegen/src/reserved_names.rs +76 -0
  424. data/rust/codegen/src/shared.rs +93 -0
  425. data/rust/codegen/src/skip_reason.rs +25 -0
  426. data/rust/codegen/src/types.rs +362 -0
  427. data/rust/host/.cargo/config.toml +3 -0
  428. data/rust/host/.gitignore +1 -0
  429. data/rust/host/Cargo.lock +4297 -0
  430. data/rust/host/Cargo.toml +61 -0
  431. data/rust/host/HECKS_RELEASE +1 -0
  432. data/rust/host/rds-ca-bundle.pem +2736 -0
  433. data/rust/host/src/api.rs +1596 -0
  434. data/rust/host/src/approval.rs +652 -0
  435. data/rust/host/src/auth.rs +1287 -0
  436. data/rust/host/src/bin/expr_harness.rs +46 -0
  437. data/rust/host/src/bin/lineage_harness.rs +119 -0
  438. data/rust/host/src/bin/mint_harness.rs +135 -0
  439. data/rust/host/src/checkout.rs +265 -0
  440. data/rust/host/src/dispatch.rs +1443 -0
  441. data/rust/host/src/expr_json.rs +1117 -0
  442. data/rust/host/src/field_hints.rs +52 -0
  443. data/rust/host/src/ir.rs +733 -0
  444. data/rust/host/src/journal.rs +874 -0
  445. data/rust/host/src/lambda_client.rs +378 -0
  446. data/rust/host/src/log.rs +147 -0
  447. data/rust/host/src/main.rs +474 -0
  448. data/rust/host/src/mint.rs +1865 -0
  449. data/rust/host/src/payments/keystore.rs +330 -0
  450. data/rust/host/src/payments/tests.rs +1400 -0
  451. data/rust/host/src/payments.rs +767 -0
  452. data/rust/host/src/presentation.rs +698 -0
  453. data/rust/host/src/presentation_write.rs +1431 -0
  454. data/rust/host/src/query_step.rs +149 -0
  455. data/rust/host/src/rate_limit.rs +904 -0
  456. data/rust/host/src/reference_transform.rs +241 -0
  457. data/rust/host/src/reference_validate.rs +338 -0
  458. data/rust/host/src/resend.rs +296 -0
  459. data/rust/host/src/secrets.rs +106 -0
  460. data/rust/host/src/server.rs +663 -0
  461. data/rust/host/src/storage_shape.rs +175 -0
  462. data/rust/host/src/test_pg.rs +40 -0
  463. data/rust/host/src/ui_schema.rs +1351 -0
  464. data/rust/host/src/wasm_runner.rs +191 -0
  465. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  466. data/rust/host/src/web/newsletter/tests.rs +104 -0
  467. data/rust/host/src/web/newsletter.rs +310 -0
  468. data/rust/host/src/web/newsletter_send.rs +357 -0
  469. data/rust/host/src/web/registrations.rs +1505 -0
  470. data/rust/host/src/web.rs +2937 -0
  471. data/rust/lsp/.gitignore +1 -0
  472. data/rust/lsp/Cargo.lock +7 -0
  473. data/rust/lsp/Cargo.toml +16 -0
  474. data/rust/lsp/README.md +133 -0
  475. data/rust/lsp/src/diagnostics.rs +165 -0
  476. data/rust/lsp/src/json.rs +385 -0
  477. data/rust/lsp/src/main.rs +399 -0
  478. data/rust/lsp/src/outline.rs +177 -0
  479. data/rust/lsp/src/rpc.rs +67 -0
  480. data/rust/parser/.cargo/config.toml +5 -0
  481. data/rust/parser/.gitignore +1 -0
  482. data/rust/parser/Cargo.lock +7 -0
  483. data/rust/parser/Cargo.toml +21 -0
  484. data/rust/parser/src/build/closed_sets.rs +27 -0
  485. data/rust/parser/src/build/identity.rs +276 -0
  486. data/rust/parser/src/build/mod.rs +12 -0
  487. data/rust/parser/src/build/naming.rs +141 -0
  488. data/rust/parser/src/build/pattern_subset.rs +161 -0
  489. data/rust/parser/src/build/query_derive.rs +72 -0
  490. data/rust/parser/src/build/query_inference.rs +269 -0
  491. data/rust/parser/src/build/query_options.rs +100 -0
  492. data/rust/parser/src/build/read_model.rs +94 -0
  493. data/rust/parser/src/build/references.rs +32 -0
  494. data/rust/parser/src/canonical.rs +114 -0
  495. data/rust/parser/src/diag.rs +55 -0
  496. data/rust/parser/src/emit.rs +953 -0
  497. data/rust/parser/src/expr/ast_json.rs +147 -0
  498. data/rust/parser/src/expr/evaluator.rs +81 -0
  499. data/rust/parser/src/expr/mod.rs +84 -0
  500. data/rust/parser/src/expr/resolver.rs +497 -0
  501. data/rust/parser/src/ir.rs +387 -0
  502. data/rust/parser/src/keywords.rs +585 -0
  503. data/rust/parser/src/lex.rs +662 -0
  504. data/rust/parser/src/main.rs +252 -0
  505. data/rust/parser/src/parse/aggregate.rs +612 -0
  506. data/rust/parser/src/parse/chapter.rs +517 -0
  507. data/rust/parser/src/parse/command.rs +637 -0
  508. data/rust/parser/src/parse/domain_port.rs +127 -0
  509. data/rust/parser/src/parse/entity.rs +527 -0
  510. data/rust/parser/src/parse/file.rs +67 -0
  511. data/rust/parser/src/parse/hecksagon.rs +231 -0
  512. data/rust/parser/src/parse/lifecycle.rs +162 -0
  513. data/rust/parser/src/parse/mod.rs +1262 -0
  514. data/rust/parser/src/parse/policy.rs +87 -0
  515. data/rust/parser/src/parse/process_manager.rs +305 -0
  516. data/rust/parser/src/parse/query.rs +100 -0
  517. data/rust/parser/src/parse/read_model.rs +214 -0
  518. data/rust/parser/src/parse/value_object.rs +250 -0
  519. data/rust/parser/src/ruby_value.rs +422 -0
  520. data/rust/project/bridging.rb +257 -0
  521. data/rust/project/commands.rb +877 -0
  522. data/rust/project/constraints.rb +86 -0
  523. data/rust/project/dependency_planning.rb +178 -0
  524. data/rust/project/domain_generator.rb +980 -0
  525. data/rust/project/exemplar.rb +148 -0
  526. data/rust/project/expr_emitter.rb +72 -0
  527. data/rust/project/fielded.rb +141 -0
  528. data/rust/project/json_codec.rb +641 -0
  529. data/rust/project/mutations.rb +721 -0
  530. data/rust/project/naming.rb +205 -0
  531. data/rust/project/ports.rb +65 -0
  532. data/rust/project/queries.rb +493 -0
  533. data/rust/project/reactions.rb +317 -0
  534. data/rust/project/read_models.rb +690 -0
  535. data/rust/project/reference_specs.rb +27 -0
  536. data/rust/project/registry.rb +396 -0
  537. data/rust/project/skip_reason.rb +29 -0
  538. data/rust/project/types.rb +225 -0
  539. data/rust/project/write_if_changed.rb +83 -0
  540. data/rust/project.rb +30 -0
  541. data/rust/project_rust_pipeline.rb +383 -0
  542. data/rust/src/exemplar/commands.rs +279 -0
  543. data/rust/src/exemplar/constraints.rs +18 -0
  544. data/rust/src/exemplar/fielded.rs +280 -0
  545. data/rust/src/exemplar/json.rs +277 -0
  546. data/rust/src/exemplar/mod.rs +17 -0
  547. data/rust/src/exemplar/mutations.rs +80 -0
  548. data/rust/src/exemplar/queries.rs +33 -0
  549. data/rust/src/exemplar/reactions.rs +88 -0
  550. data/rust/src/exemplar/read_models.rs +48 -0
  551. data/rust/src/exemplar/registry.rs +166 -0
  552. data/rust/src/exemplar/types.rs +44 -0
  553. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  554. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  555. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  556. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  557. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  558. data/rust/src/kernel/cli.rs +585 -0
  559. data/rust/src/kernel/dispatch.rs +1090 -0
  560. data/rust/src/kernel/expr.rs +458 -0
  561. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  562. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  563. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  564. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  565. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  566. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  567. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  568. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  569. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  570. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  571. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  572. data/rust/src/kernel/expression_operators/text.rs +79 -0
  573. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  574. data/rust/src/kernel/json.rs +779 -0
  575. data/rust/src/kernel/mod.rs +133 -0
  576. data/rust/src/kernel/named_query.rs +219 -0
  577. data/rust/src/kernel/orchestrate.rs +1810 -0
  578. data/rust/src/kernel/pattern.rs +307 -0
  579. data/rust/src/kernel/query_comparators.rs +206 -0
  580. data/rust/src/kernel/query_ordering.rs +103 -0
  581. data/rust/src/kernel/read_model.rs +914 -0
  582. data/rust/src/kernel/reference_lookup.rs +149 -0
  583. data/rust/src/kernel/repository.rs +468 -0
  584. data/rust/src/kernel/routing.rs +612 -0
  585. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  586. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  587. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  588. data/rust/src/kernel/vocab/mod.rs +20 -0
  589. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  590. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  591. data/rust/src/lib.rs +8 -0
  592. data/rust/src/main.rs +16 -0
  593. data/rust/web/.gitignore +1 -0
  594. data/rust/web/Cargo.lock +119 -0
  595. data/rust/web/Cargo.toml +18 -0
  596. data/rust/web/src/lib.rs +8 -0
  597. metadata +477 -12
  598. data/lib/hecks/facade/cli_door.rb +0 -96
  599. data/lib/hecks/facade/cli_runner.rb +0 -127
  600. data/lib/hecks/mcp_door_scope.rb +0 -95
@@ -0,0 +1,1847 @@
1
+ Hecks.bluebook "Hecks" do
2
+ # Custodian: operating any domain. Each aggregate here is a concern a client runs against
3
+ # their own domain (ADR 0080, section 2).
4
+
5
+ aggregate "Introspection" do
6
+ description "A look at a booted domain, answered by the runtime without changing anything."
7
+
8
+ attribute :domain, DomainPath
9
+ identified_by :domain
10
+
11
+ value_object "DomainPath" do
12
+ attribute :value, String, pattern: '[^\t\n\r]'
13
+
14
+ invariant("a domain is named by its path") { !value.to_s.empty? }
15
+ end
16
+
17
+ value_object "ChapterName" do
18
+ attribute :value, String, pattern: '[^ \t\n\r]'
19
+
20
+ invariant("a chapter is named") { !value.to_s.empty? }
21
+ end
22
+
23
+ value_object "AggregateName" do
24
+ attribute :value, String, pattern: '[^ \t\n\r]'
25
+
26
+ invariant("an aggregate is named") { !value.to_s.empty? }
27
+ end
28
+
29
+ # Each query below is answered by the DomainRuntime port's adapter: the domain is booted
30
+ # in-process and the answer is the exact text the matching bin script printed.
31
+
32
+ # An answer that is one document of text, exactly as the tool that produced it wrote it.
33
+ value_object "Document" do
34
+ attribute :text, String
35
+ end
36
+
37
+ query "Ir" do
38
+ description "The IR a domain's boot produces, as JSON: what golden specs pin and an era hashes."
39
+ attribute :domain, DomainPath, optional: true
40
+ attribute :translations, Boolean, optional: true
41
+ attribute :meta, Boolean, optional: true
42
+ returns Document
43
+ end
44
+
45
+ query "Shape" do
46
+ description "The storage shape an era's label is minted from: a bluebook file or a directory of them."
47
+ attribute :domain, DomainPath
48
+ returns Document
49
+ end
50
+
51
+ query "Stores" do
52
+ description "Every aggregate's current records: the head, not the journal."
53
+ attribute :domain, DomainPath
54
+ returns Document
55
+ end
56
+
57
+ query "History" do
58
+ description "Every journal entry a domain's append-only adapters hold: the whole write history."
59
+ attribute :domain, DomainPath
60
+ returns Document
61
+ end
62
+
63
+ query "Statements" do
64
+ description "A chapter's declared facts, as plain English sentences."
65
+ attribute :domain, DomainPath
66
+ attribute :chapter, ChapterName
67
+ returns Document
68
+ end
69
+
70
+ query "Narrate" do
71
+ description "A domain read back in English, whole or for one aggregate."
72
+ attribute :domain, DomainPath, optional: true
73
+ attribute :aggregate, AggregateName, optional: true
74
+ returns Document
75
+ end
76
+
77
+ query "Docs" do
78
+ description "A domain's usage document, whole or for one aggregate."
79
+ attribute :domain, DomainPath, optional: true
80
+ attribute :aggregate, AggregateName, optional: true
81
+ returns Document
82
+ end
83
+
84
+ # One file a projection would have written, held as its name and its text.
85
+ value_object "ProjectedFile" do
86
+ attribute :name, String
87
+ attribute :text, String
88
+ end
89
+
90
+ query "ProjectDiagrams" do
91
+ description "A chapter's Mermaid diagrams, as file names mapped to their text; nothing is written."
92
+ attribute :domain, DomainPath
93
+ attribute :chapter, ChapterName
94
+ returns list_of(ProjectedFile)
95
+ end
96
+
97
+ query "Glossary" do
98
+ description "A chapter's Ubiquitous Language glossary, as file names mapped to their text; nothing is written."
99
+ attribute :domain, DomainPath
100
+ attribute :chapter, ChapterName
101
+ returns list_of(ProjectedFile)
102
+ end
103
+ end
104
+
105
+ # A journaled command whose work happens outside the runtime (ADR 0080, section 11). Every
106
+ # command below that runs something follows this one shape, so later aggregates copy it:
107
+ #
108
+ # 1. A creating command records the request as a record in state "requested" and emits.
109
+ # 2. A policy on that event triggers an ask on one of the record's ports. The runtime hands
110
+ # the port's adapter the whole record, so the adapter does all the IO, and only there.
111
+ # 3. The adapter answers (a hash named like a command's arguments) or raises (its message
112
+ # is the refusal). One policy per outcome re-enters the record with a command that stores it.
113
+ #
114
+ # The ports are declared in hecks.hecksagon; each adapter is in adapters/.
115
+
116
+ aggregate "ModelCheckRun" do
117
+ description "One static analysis over domains' models, and what it found: a check's history."
118
+
119
+ attribute :run, RunKey
120
+ attribute :domains, DomainList, optional: true
121
+ attribute :profile, ProfileName, optional: true
122
+ attribute :strict, Switch, optional: true
123
+ attribute :report, Report, optional: true
124
+ attribute :refusal, Report, optional: true
125
+ identified_by :run
126
+
127
+ value_object "RunKey" do
128
+ attribute :value, String, pattern: '[^ \t\n\r]'
129
+
130
+ invariant("a run is named") { !value.to_s.empty? }
131
+ end
132
+
133
+ value_object "DomainList" do
134
+ attribute :value, String, pattern: '[^\t\n\r]'
135
+ end
136
+
137
+ value_object "ProfileName" do
138
+ attribute :value, String, pattern: '^client$'
139
+ end
140
+
141
+ value_object "Switch" do
142
+ attribute :value, Boolean
143
+ end
144
+
145
+ value_object "Report" do
146
+ attribute :value, String
147
+ end
148
+
149
+ lifecycle :status, default: "requested" do
150
+ transition "Pass" => "clean", from: "requested"
151
+ transition "Flag" => "flagged", from: "requested"
152
+ end
153
+
154
+ command "ModelCheck" do
155
+ role "Operator"
156
+ goal "Check domains for dead states and unreachable steps, and keep the verdict"
157
+
158
+ attribute :run, RunKey
159
+ attribute :domains, DomainList, optional: true
160
+ attribute :profile, ProfileName, optional: true
161
+ attribute :strict, Switch, optional: true
162
+
163
+ sets :run
164
+ sets :domains
165
+ sets :profile
166
+ sets :strict
167
+
168
+ emits ModelCheckRequested
169
+ end
170
+
171
+ command "Pass" do
172
+ role "System"
173
+ goal "Record that the analysis left no error"
174
+
175
+ reference_to ModelCheckRun
176
+ attribute :report, Report
177
+
178
+ sets :report
179
+
180
+ emits ModelCheckPassed
181
+ end
182
+
183
+ command "Flag" do
184
+ role "System"
185
+ goal "Record that the analysis found something, or could not run, and what it said"
186
+
187
+ reference_to ModelCheckRun
188
+ attribute :refusal, Report
189
+
190
+ sets :refusal
191
+
192
+ emits ModelCheckFlagged
193
+ end
194
+
195
+ # `hecks model_check` answers once the request is recorded, so the verdict is read here.
196
+ query "Verdict" do
197
+ description "What one check found: its status, and the report or the reason it could not run."
198
+ attribute :run, RunKey
199
+ where(run: :run)
200
+ order_by :run
201
+ end
202
+
203
+ query "Flagged" do
204
+ description "Every check that left a finding or could not run."
205
+ where(status: "flagged")
206
+ order_by :run
207
+ end
208
+ end
209
+
210
+ policy "AnalyseWhenRequested" do
211
+ on "ModelCheckRun.ModelCheckRequested"
212
+ trigger ModelCheckRun::DomainRuntime::Check, with: { run: :run }
213
+ end
214
+
215
+ policy "RecordTheClean" do
216
+ on "ModelCheckRun.ModelCheckAnswered"
217
+ trigger ModelCheckRun::Pass, with: { report: :report }
218
+ end
219
+
220
+ policy "RecordTheFlag" do
221
+ on "ModelCheckRun.ModelCheckRefused"
222
+ trigger ModelCheckRun::Flag, with: { refusal: :refusal }
223
+ end
224
+
225
+ aggregate "Operation" do
226
+ description "One thing an operator ran against a domain, and how it ended."
227
+
228
+ attribute :run, OperationKey
229
+ attribute :subject, Subject, optional: true
230
+ attribute :script, Script, optional: true
231
+ attribute :verb, Verb, optional: true
232
+ attribute :arguments, list_of(Word), optional: true
233
+ attribute :url, Address, optional: true
234
+ attribute :path, RoutePath, optional: true
235
+ attribute :header, HeaderName, optional: true
236
+ attribute :scheme, Scheme, optional: true
237
+ attribute :payload, Payload, optional: true
238
+ attribute :payload_file, PayloadFile, optional: true
239
+ attribute :health_path, RoutePath, optional: true
240
+ attribute :state_path, RoutePath, optional: true
241
+ attribute :output, Output, optional: true
242
+ attribute :refusal, Output, optional: true
243
+ identified_by :run
244
+
245
+ value_object "OperationKey" do
246
+ attribute :value, String, pattern: '[^ \t\n\r]'
247
+
248
+ invariant("an operation is named") { !value.to_s.empty? }
249
+ end
250
+
251
+ # What the operation acts on: a domain directory, or a behaviors file or directory.
252
+ value_object "Subject" do
253
+ attribute :value, String, pattern: '[^\t\n\r]'
254
+ end
255
+
256
+ value_object "Script" do
257
+ attribute :value, String
258
+ end
259
+
260
+ value_object "Verb" do
261
+ attribute :value, String, pattern: '[^ \t\n\r]'
262
+ end
263
+
264
+ # One `name=value` word of the verb a run dispatches.
265
+ value_object "Word" do
266
+ attribute :value, String
267
+ end
268
+
269
+ value_object "Address" do
270
+ attribute :value, String, pattern: '^https?://'
271
+ end
272
+
273
+ value_object "RoutePath" do
274
+ attribute :value, String, pattern: '^/'
275
+ end
276
+
277
+ value_object "HeaderName" do
278
+ attribute :value, String, pattern: '[^ \t\n\r]'
279
+ end
280
+
281
+ value_object "Scheme" do
282
+ attribute :value, String, pattern: '^(timestamped|sha256|hex)$'
283
+ end
284
+
285
+ value_object "Payload" do
286
+ attribute :value, String
287
+ end
288
+
289
+ value_object "PayloadFile" do
290
+ attribute :value, String, pattern: '[^\t\n\r]'
291
+ end
292
+
293
+ value_object "Output" do
294
+ attribute :value, String
295
+ end
296
+
297
+ lifecycle :status, default: "requested" do
298
+ transition "Succeed" => "succeeded", from: "requested"
299
+ transition "Fail" => "failed", from: "requested"
300
+ end
301
+
302
+ # Runs one verb, or a JSON step list, against the domain: `hecks run [domain] script=steps.json`
303
+ # or `hecks run [domain] verb=create_pizza arguments=name.value=Margherita`. The signing secret
304
+ # for SmokeHttp is never an argument: it is read from SMOKE_WEBHOOK_SECRET.
305
+ command "Run" do
306
+ role "Operator"
307
+ goal "Dispatch a verb or a step list against a domain and keep what it reported"
308
+
309
+ attribute :run, OperationKey
310
+ attribute :subject, Subject, optional: true
311
+ attribute :script, Script, optional: true
312
+ attribute :verb, Verb, optional: true
313
+ attribute :arguments, list_of(Word), optional: true
314
+
315
+ given("exactly one of a script and a verb is named: pass script= or verb=, not both") do
316
+ (script.set? && verb.unset?) || (script.unset? && verb.set?)
317
+ end
318
+
319
+ sets :run
320
+ sets :subject
321
+ sets :script
322
+ sets :verb
323
+ sets :arguments
324
+
325
+ emits RunRequested
326
+ end
327
+
328
+ command "RefreshProjections" do
329
+ role "Operator"
330
+ goal "Bring every read-model projection of a domain up to date now"
331
+
332
+ attribute :run, OperationKey
333
+ attribute :subject, Subject
334
+
335
+ sets :run
336
+ sets :subject
337
+
338
+ emits ProjectionsRefreshRequested
339
+ end
340
+
341
+ command "RunBehaviors" do
342
+ role "Operator"
343
+ goal "Run a domain's behavior tests and keep the verdict"
344
+
345
+ attribute :run, OperationKey
346
+ attribute :subject, Subject
347
+
348
+ sets :run
349
+ sets :subject
350
+
351
+ emits BehaviorsRequested
352
+ end
353
+
354
+ command "OpenConsole" do
355
+ role "Operator"
356
+ goal "Open an interactive console on a domain"
357
+
358
+ attribute :run, OperationKey
359
+ attribute :subject, Subject, optional: true
360
+
361
+ sets :run
362
+ sets :subject
363
+
364
+ emits ConsoleRequested
365
+ end
366
+
367
+ command "SmokeTest" do
368
+ role "Operator"
369
+ goal "Dispatch one synthesized call per command and report of a wired domain"
370
+
371
+ attribute :run, OperationKey
372
+ attribute :subject, Subject, optional: true
373
+
374
+ sets :run
375
+ sets :subject
376
+
377
+ emits SmokeTestRequested
378
+ end
379
+
380
+ command "SmokeHttp" do
381
+ role "Operator"
382
+ goal "Check that a running service refuses bad signatures and accepts a good one"
383
+
384
+ attribute :run, OperationKey
385
+ attribute :url, Address, optional: true
386
+ attribute :path, RoutePath
387
+ attribute :header, HeaderName, optional: true
388
+ attribute :scheme, Scheme, optional: true
389
+ attribute :payload, Payload, optional: true
390
+ attribute :payload_file, PayloadFile, optional: true
391
+ attribute :health_path, RoutePath, optional: true
392
+ attribute :state_path, RoutePath, optional: true
393
+
394
+ sets :run
395
+ sets :url
396
+ sets :path
397
+ sets :header
398
+ sets :scheme
399
+ sets :payload
400
+ sets :payload_file
401
+ sets :health_path
402
+ sets :state_path
403
+
404
+ emits SmokeHttpRequested
405
+ end
406
+
407
+ command "Succeed" do
408
+ role "System"
409
+ goal "Record that the operation ended well, and what it reported"
410
+
411
+ reference_to Operation
412
+ attribute :output, Output
413
+
414
+ sets :output
415
+
416
+ emits OperationSucceeded
417
+ end
418
+
419
+ command "Fail" do
420
+ role "System"
421
+ goal "Record that the operation was refused or found a problem, and what it said"
422
+
423
+ reference_to Operation
424
+ attribute :refusal, Output
425
+
426
+ sets :refusal
427
+
428
+ emits OperationFailed
429
+ end
430
+
431
+ query "Outcome" do
432
+ description "How one operation ended: its status, and what it reported or why it failed."
433
+ attribute :run, OperationKey
434
+ where(run: :run)
435
+ order_by :run
436
+ end
437
+
438
+ query "Failed" do
439
+ description "Every operation that was refused or found a problem."
440
+ where(status: "failed")
441
+ order_by :run
442
+ end
443
+
444
+ # A follow's answer: the count to pass back as `since`, the entries past it, and when it looked.
445
+ value_object "Tail" do
446
+ attribute :cursor, Integer
447
+ attribute :events, list_of(FollowedEvent)
448
+ attribute :taken_at, String
449
+ end
450
+
451
+ # One entry of a persisted event log; the payload is its JSON text, since a value object
452
+ # holds no free-form map.
453
+ value_object "FollowedEvent" do
454
+ attribute :name, String
455
+ attribute :aggregate, String
456
+ attribute :id, String
457
+ attribute :payload, String
458
+ attribute :occurred_at, String
459
+ end
460
+
461
+ # A launcher answers once, so a follow is a bounded poll: it answers the entries past `since`
462
+ # and a `cursor` to pass as the next `since`, waiting up to `wait` seconds for the first one.
463
+ query "Follow" do
464
+ description "A domain's persisted event log past a cursor, for tailing it call by call."
465
+ attribute :domain, Subject
466
+ attribute :aggregate, Verb, optional: true
467
+ attribute :since, Integer, optional: true
468
+ attribute :from_now, Boolean, optional: true
469
+ attribute :wait, Integer, optional: true
470
+ attribute :interval, Float, optional: true
471
+ returns Tail
472
+ end
473
+ end
474
+
475
+ policy "RunWhenRequested" do
476
+ on "Operation.RunRequested"
477
+ trigger Operation::DomainRuntime::Execute, with: { run: :run }
478
+ end
479
+
480
+ policy "RefreshWhenRequested" do
481
+ on "Operation.ProjectionsRefreshRequested"
482
+ trigger Operation::DomainRuntime::Refresh, with: { run: :run }
483
+ end
484
+
485
+ policy "VerifyWhenRequested" do
486
+ on "Operation.BehaviorsRequested"
487
+ trigger Operation::DomainRuntime::Verify, with: { run: :run }
488
+ end
489
+
490
+ policy "SmokeWhenRequested" do
491
+ on "Operation.SmokeTestRequested"
492
+ trigger Operation::DomainRuntime::Smoke, with: { run: :run }
493
+ end
494
+
495
+ policy "OpenWhenRequested" do
496
+ on "Operation.ConsoleRequested"
497
+ trigger Operation::Terminal::Open, with: { run: :run }
498
+ end
499
+
500
+ policy "ProbeWhenRequested" do
501
+ on "Operation.SmokeHttpRequested"
502
+ trigger Operation::HostHttp::Probe, with: { run: :run }
503
+ end
504
+
505
+ policy "RecordTheOutput" do
506
+ on "Operation.OperationAnswered"
507
+ trigger Operation::Succeed, with: { output: :output }
508
+ end
509
+
510
+ policy "RecordTheFailure" do
511
+ on "Operation.OperationRefused"
512
+ trigger Operation::Fail, with: { refusal: :refusal }
513
+ end
514
+
515
+ aggregate "Host" do
516
+ description "A running rust/host, and the version and era it last reported."
517
+
518
+ attribute :host, Address
519
+ attribute :expected, ExpectedFile, optional: true
520
+ attribute :timeout, Seconds, optional: true
521
+ attribute :era, EraId, optional: true
522
+ attribute :version, VersionText, optional: true
523
+ attribute :report, Report, optional: true
524
+ attribute :refusal, Report, optional: true
525
+ identified_by :host
526
+
527
+ value_object "Address" do
528
+ attribute :value, String, pattern: '^https?://'
529
+ end
530
+
531
+ # The file listing the eras that should be running, one per line.
532
+ value_object "ExpectedFile" do
533
+ attribute :value, String, pattern: '[^\t\n\r]'
534
+ end
535
+
536
+ value_object "Seconds" do
537
+ attribute :value, Float
538
+
539
+ invariant("a timeout is positive") { value > 0 }
540
+ end
541
+
542
+ value_object "EraId" do
543
+ attribute :value, String, pattern: '[^ \t\n\r]'
544
+ end
545
+
546
+ value_object "VersionText" do
547
+ attribute :value, String
548
+ end
549
+
550
+ value_object "Verdict" do
551
+ attribute :value, String, pattern: '^(match|unlisted|mismatch)$'
552
+ end
553
+
554
+ value_object "Report" do
555
+ attribute :value, String
556
+ end
557
+
558
+ lifecycle :status, default: "requested" do
559
+ transition "Recheck" => "requested", from: %w[observed drifted unreachable]
560
+ transition "Observe" => "observed", from: "requested"
561
+ transition "Drift" => "drifted", from: "requested"
562
+ transition "Lose" => "unreachable", from: "requested"
563
+ end
564
+
565
+ # `hecks check_era <url> expected=era-file [timeout=10]` records the first check of a host under
566
+ # its URL. A host is one record, and a creating command cannot repeat an identity, so a later
567
+ # check of the same host is `hecks recheck <url> expected=era-file`. Both are read with
568
+ # `hecks standing <url>`.
569
+ command "CheckEra" do
570
+ role "Operator"
571
+ goal "Ask a running host which era it reports, and keep whether it is one that should be running"
572
+
573
+ attribute :host, Address
574
+ attribute :expected, ExpectedFile
575
+ attribute :timeout, Seconds, optional: true
576
+
577
+ sets :host
578
+ sets :expected
579
+ sets :timeout
580
+
581
+ emits EraCheckRequested
582
+ end
583
+
584
+ command "Recheck" do
585
+ role "Operator"
586
+ goal "Ask a host already checked which era it reports now"
587
+
588
+ reference_to Host
589
+ attribute :expected, ExpectedFile
590
+ attribute :timeout, Seconds, optional: true
591
+
592
+ sets :expected
593
+ sets :timeout
594
+
595
+ emits EraRecheckRequested
596
+ end
597
+
598
+ # The host reported an era the list accepts, or a list that names none.
599
+ command "Observe" do
600
+ role "System"
601
+ goal "Record the version and era a running host reported"
602
+
603
+ reference_to Host
604
+ attribute :era, EraId
605
+ attribute :version, VersionText
606
+ attribute :report, Report
607
+
608
+ sets :era
609
+ sets :version
610
+ sets :report
611
+
612
+ emits EraObserved
613
+ end
614
+
615
+ # The host reported an era that is not on the list: the finding the roll must stop on.
616
+ command "Drift" do
617
+ role "System"
618
+ goal "Record that a running host reports an era that should not be running"
619
+
620
+ reference_to Host
621
+ attribute :era, EraId
622
+ attribute :version, VersionText
623
+ attribute :report, Report
624
+
625
+ sets :era
626
+ sets :version
627
+ sets :report
628
+
629
+ emits EraDrifted
630
+ end
631
+
632
+ command "Lose" do
633
+ role "System"
634
+ goal "Record that the host could not be reached, or answered no era, and what was said"
635
+
636
+ reference_to Host
637
+ attribute :refusal, Report
638
+
639
+ sets :refusal
640
+
641
+ emits HostLost
642
+ end
643
+
644
+ query "Standing" do
645
+ description "What one check of a host found: its status, era and version, or why it failed."
646
+ attribute :host, Address
647
+ where(host: :host)
648
+ order_by :host
649
+ end
650
+
651
+ query "Drifted" do
652
+ description "Every host that reported an era that should not be running."
653
+ where(status: "drifted")
654
+ order_by :host
655
+ end
656
+ end
657
+
658
+ policy "FetchWhenChecked" do
659
+ on "Host.EraCheckRequested"
660
+ trigger Host::HostHttp::Fetch, with: { host: :host }
661
+ end
662
+
663
+ policy "FetchWhenRechecked" do
664
+ on "Host.EraRecheckRequested"
665
+ trigger Host::HostHttp::Fetch, with: { host: :host }
666
+ end
667
+
668
+ policy "RecordTheEra" do
669
+ on "Host.EraReported"
670
+ where { verdict.value != "mismatch" }
671
+ trigger Host::Observe, with: { era: :era, version: :version, report: :report }
672
+ end
673
+
674
+ policy "RecordTheDrift" do
675
+ on "Host.EraReported"
676
+ where { verdict.value == "mismatch" }
677
+ trigger Host::Drift, with: { era: :era, version: :version, report: :report }
678
+ end
679
+
680
+ policy "RecordTheLostHost" do
681
+ on "Host.HostRefused"
682
+ trigger Host::Lose, with: { refusal: :refusal }
683
+ end
684
+
685
+ aggregate "Era" do
686
+ description "One change to a domain's journal of eras, and how it ended: a store's upkeep."
687
+
688
+ attribute :run, RunKey
689
+ attribute :domain, DomainPath
690
+ attribute :operation, OperationName, optional: true
691
+ attribute :winners, WinnerList, optional: true
692
+ attribute :era, Ordinal, optional: true
693
+ attribute :aggregates, AggregateList, optional: true
694
+ attribute :confirm, Switch, optional: true
695
+ attribute :snapshot, Snapshot, optional: true
696
+ attribute :host_version, HostVersion, optional: true
697
+ attribute :rehearsal, Verdict, optional: true
698
+ attribute :rehearsed_at, Moment, optional: true
699
+ attribute :capable, Switch, optional: true
700
+ attribute :held, Count, optional: true
701
+ attribute :forks, Count, optional: true
702
+ attribute :contested, Count, optional: true
703
+ attribute :edges, Count, optional: true
704
+ attribute :bound, Count, optional: true
705
+ attribute :candidates, Count, optional: true
706
+ attribute :audited, Switch, optional: true
707
+ attribute :rehearsal_needed, Switch, optional: true
708
+ attribute :rehearsal_recorded, Switch, optional: true
709
+ attribute :drifted, Switch, optional: true
710
+ attribute :loadable, Switch, optional: true
711
+ attribute :shape_kept, Switch, optional: true
712
+ attribute :report, Report, optional: true
713
+ attribute :refusal, Report, optional: true
714
+ identified_by :run
715
+
716
+ value_object "RunKey" do
717
+ attribute :value, String, pattern: '[^ \t\n\r]'
718
+
719
+ invariant("a change is named") { !value.to_s.empty? }
720
+ end
721
+
722
+ # The domain whose journal is changed: its directory.
723
+ value_object "DomainPath" do
724
+ attribute :value, String, pattern: '[^\t\n\r]'
725
+ end
726
+
727
+ value_object "OperationName" do
728
+ attribute :value, String,
729
+ pattern: '^(hold_first|merge_tail|reattest|backfill_projections|compact|compact_heki|approve_translation)$'
730
+ end
731
+
732
+ # `id:old,id:new`: which world's record wins for each id both worlds touched since the cut.
733
+ value_object "WinnerList" do
734
+ attribute :value, String, pattern: '^[^ \t\n\r,]+:(old|new)(,[^ \t\n\r,]+:(old|new))*$'
735
+ end
736
+
737
+ value_object "Ordinal" do
738
+ attribute :value, Integer
739
+
740
+ invariant("an era is counted from one") { value > 0 }
741
+ end
742
+
743
+ # Aggregate names, comma separated; every aggregate that keeps a journal when absent.
744
+ value_object "AggregateList" do
745
+ attribute :value, String, pattern: '[^\t\n\r]'
746
+ end
747
+
748
+ value_object "Switch" do
749
+ attribute :value, Boolean
750
+ end
751
+
752
+ value_object "Count" do
753
+ attribute :value, Integer
754
+ end
755
+
756
+ # The rehearsal a compute or rekey edge is approved on: the snapshot it ran against, the host
757
+ # that ran it, whether it passed, and when.
758
+ value_object "Snapshot" do
759
+ attribute :value, String, pattern: '[^\t\n\r]'
760
+ end
761
+
762
+ value_object "HostVersion" do
763
+ attribute :value, String, pattern: '[^ \t\n\r]'
764
+ end
765
+
766
+ value_object "Verdict" do
767
+ attribute :value, String, pattern: '^(pass|fail)$'
768
+ end
769
+
770
+ value_object "Moment" do
771
+ attribute :value, String, pattern: '^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[^ \t\n\r]+$'
772
+ end
773
+
774
+ value_object "Report" do
775
+ attribute :value, String
776
+ end
777
+
778
+ lifecycle :status, default: "requested" do
779
+ transition "Permit" => "admitted", from: "requested"
780
+ transition "Settle" => "settled", from: "admitted"
781
+ transition "Abandon" => "refused", from: %w[requested admitted]
782
+ end
783
+
784
+ # The seven requests. Each records what was asked and names its operation; the journal store
785
+ # then examines the domain and reports facts (`EraExamined`), `Permit` holds the rules that need
786
+ # those facts, and only an admitted change is made (`EraChanged`). A change that changes
787
+ # something needs an explicit `--confirm`.
788
+ command "HoldFirst" do
789
+ role "Operator"
790
+ goal "Hold the first era of a domain that holds none, as its first boot would"
791
+
792
+ attribute :domain, DomainPath
793
+ attribute :run, RunKey
794
+ attribute :confirm, Switch, optional: true
795
+
796
+ given("holding the first era is confirmed") { confirm.value == true }
797
+
798
+ sets :domain
799
+ sets :run
800
+ sets :confirm
801
+ sets :operation, to: "hold_first"
802
+
803
+ emits FirstEraRequested
804
+ end
805
+
806
+ command "MergeTail" do
807
+ role "Operator"
808
+ goal "Interleave the old world's writes since the cut into the live head, by their ordinals"
809
+
810
+ attribute :domain, DomainPath
811
+ attribute :run, RunKey
812
+ attribute :winners, WinnerList, optional: true
813
+ attribute :confirm, Switch, optional: true
814
+
815
+ given("the merge is confirmed") { confirm.value == true }
816
+
817
+ sets :domain
818
+ sets :run
819
+ sets :winners
820
+ sets :confirm
821
+ sets :operation, to: "merge_tail"
822
+
823
+ emits TailMergeRequested
824
+ end
825
+
826
+ command "Reattest" do
827
+ role "Operator"
828
+ goal "Freeze an era's held text as it now stands, after a person has read it"
829
+
830
+ attribute :domain, DomainPath
831
+ attribute :era, Ordinal
832
+ attribute :run, RunKey
833
+ attribute :confirm, Switch, optional: true
834
+
835
+ given("the text is accepted as the era's own") { confirm.value == true }
836
+
837
+ sets :domain
838
+ sets :era
839
+ sets :run
840
+ sets :confirm
841
+ sets :operation, to: "reattest"
842
+
843
+ emits ReattestRequested
844
+ end
845
+
846
+ command "BackfillProjections" do
847
+ role "Operator"
848
+ goal "Store the shape every held era froze with, for the eras that predate it"
849
+
850
+ attribute :domain, DomainPath
851
+ attribute :run, RunKey
852
+
853
+ sets :domain
854
+ sets :run
855
+ sets :operation, to: "backfill_projections"
856
+
857
+ emits BackfillRequested
858
+ end
859
+
860
+ command "Compact" do
861
+ role "Operator"
862
+ goal "Delete the journal entries a Postgres or Sqlite aggregate has already projected"
863
+
864
+ attribute :domain, DomainPath
865
+ attribute :aggregates, AggregateList, optional: true
866
+ attribute :run, RunKey
867
+ attribute :confirm, Switch, optional: true
868
+
869
+ given("the compaction is confirmed") { confirm.value == true }
870
+
871
+ sets :domain
872
+ sets :aggregates
873
+ sets :run
874
+ sets :confirm
875
+ sets :operation, to: "compact"
876
+
877
+ emits CompactionRequested
878
+ end
879
+
880
+ command "CompactHeki" do
881
+ role "Operator"
882
+ goal "Empty a Heki aggregate's journal, keeping the snapshot"
883
+
884
+ attribute :domain, DomainPath
885
+ attribute :aggregates, AggregateList, optional: true
886
+ attribute :run, RunKey
887
+ attribute :confirm, Switch, optional: true
888
+
889
+ given("the compaction is confirmed") { confirm.value == true }
890
+
891
+ sets :domain
892
+ sets :aggregates
893
+ sets :run
894
+ sets :confirm
895
+ sets :operation, to: "compact_heki"
896
+
897
+ emits HekiCompactionRequested
898
+ end
899
+
900
+ # Writes `translations/<edge>.approval` beside the edge, for the host to apply at its next
901
+ # boot. An edge with a compute or rekey rule also needs the rehearsal a person ran by hand.
902
+ command "ApproveTranslation" do
903
+ role "Operator"
904
+ goal "Approve the edge that leads a domain's latest era to its current shape, in a committed file"
905
+
906
+ attribute :domain, DomainPath
907
+ attribute :run, RunKey
908
+ attribute :confirm, Switch, optional: true
909
+ attribute :snapshot, Snapshot, optional: true
910
+ attribute :host_version, HostVersion, optional: true
911
+ attribute :rehearsal, Verdict, optional: true
912
+ attribute :rehearsed_at, Moment, optional: true
913
+
914
+ given("the approval is confirmed") { confirm.value == true }
915
+
916
+ sets :domain
917
+ sets :run
918
+ sets :confirm
919
+ sets :snapshot
920
+ sets :host_version
921
+ sets :rehearsal
922
+ sets :rehearsed_at
923
+ sets :operation, to: "approve_translation"
924
+
925
+ emits ApprovalRequested
926
+ end
927
+
928
+ # The facts the journal store reported, held against the change that was asked for.
929
+ command "Permit" do
930
+ role "System"
931
+ goal "Let a change through only when what was found in the journal allows it"
932
+
933
+ reference_to Era
934
+ attribute :capable, Switch
935
+ attribute :held, Count
936
+ attribute :forks, Count
937
+ attribute :contested, Count
938
+ attribute :edges, Count
939
+ attribute :bound, Count
940
+ attribute :candidates, Count
941
+ attribute :audited, Switch
942
+ attribute :rehearsal_needed, Switch
943
+ attribute :rehearsal_recorded, Switch
944
+ attribute :drifted, Switch
945
+ attribute :loadable, Switch
946
+ attribute :shape_kept, Switch
947
+
948
+ given("the store keeps eras") do
949
+ capable.value || operation.value == "compact" || operation.value == "compact_heki"
950
+ end
951
+ given("no era is held yet") { operation.value != "hold_first" || held.value == 0 }
952
+ given("an era beyond the first stands before a tail is merged") do
953
+ operation.value != "merge_tail" || held.value > 1
954
+ end
955
+ given("exactly one edge leaves each era") { operation.value != "merge_tail" || forks.value == 0 }
956
+ given("every record both worlds touched has a named winner") do
957
+ operation.value != "merge_tail" || contested.value == 0
958
+ end
959
+ given("an aggregate matches") do
960
+ (operation.value != "compact" && operation.value != "compact_heki") || candidates.value > 0
961
+ end
962
+ given("no projection reads the journal that would be emptied") do
963
+ (operation.value != "compact" && operation.value != "compact_heki") || bound.value == 0
964
+ end
965
+ given("exactly one edge leaves the latest era") do
966
+ operation.value != "approve_translation" || edges.value == 1
967
+ end
968
+ given("the audit passed") { operation.value != "approve_translation" || audited.value }
969
+ given("a rehearsal that passed is recorded for an edge with a compute or rekey rule") do
970
+ operation.value != "approve_translation" || !rehearsal_needed.value || rehearsal_recorded.value
971
+ end
972
+ given("the held text no longer matches its digest: there is nothing to re-attest") do
973
+ operation.value != "reattest" || drifted.value
974
+ end
975
+ given("the edited text loads as a bluebook: restore a loadable text") do
976
+ operation.value != "reattest" || loadable.value
977
+ end
978
+ given("the edit kept the era's shape, not just its text: restore a text with the original shape") do
979
+ operation.value != "reattest" || shape_kept.value
980
+ end
981
+
982
+ sets :capable
983
+ sets :held
984
+ sets :forks
985
+ sets :contested
986
+ sets :edges
987
+ sets :bound
988
+ sets :candidates
989
+ sets :audited
990
+ sets :rehearsal_needed
991
+ sets :rehearsal_recorded
992
+ sets :drifted
993
+ sets :loadable
994
+ sets :shape_kept
995
+
996
+ emits EraPermitted
997
+ end
998
+
999
+ command "Settle" do
1000
+ role "System"
1001
+ goal "Record that the admitted change was made, and what it did"
1002
+
1003
+ reference_to Era
1004
+ attribute :report, Report
1005
+
1006
+ sets :report
1007
+
1008
+ emits EraSettled
1009
+ end
1010
+
1011
+ command "Abandon" do
1012
+ role "System"
1013
+ goal "Record that the change was refused, or could not be made, and why"
1014
+
1015
+ reference_to Era
1016
+ attribute :refusal, Report
1017
+
1018
+ sets :refusal
1019
+
1020
+ emits EraAbandoned
1021
+ end
1022
+
1023
+ query "Settlement" do
1024
+ description "What one change did: its status, and the report or the reason it was refused."
1025
+ attribute :run, RunKey
1026
+ where(run: :run)
1027
+ order_by :run
1028
+ end
1029
+
1030
+ query "Abandoned" do
1031
+ description "Every change that was refused or could not be made."
1032
+ where(status: "refused")
1033
+ order_by :run
1034
+ end
1035
+
1036
+ # An answer that is one document of text, exactly as the tool that produced it wrote it.
1037
+ value_object "Document" do
1038
+ attribute :text, String
1039
+ end
1040
+
1041
+ # The reads that used to write. None holds an era or names one: a domain that holds no era
1042
+ # answers "no era held yet", and `hold_first` is the only thing that holds the first.
1043
+ query "ScaffoldTranslation" do
1044
+ description "The edge file that would lead the latest held era to the current shape."
1045
+ attribute :domain, DomainPath
1046
+ returns Document
1047
+ end
1048
+
1049
+ query "AuditTranslation" do
1050
+ description "The audit of the edge that leads the latest held era to the current shape."
1051
+ attribute :domain, DomainPath
1052
+ returns Document
1053
+ end
1054
+
1055
+ query "Attestation" do
1056
+ description "An era's held text as it now stands, and whether it still matches its digest."
1057
+ attribute :domain, DomainPath
1058
+ attribute :era, Ordinal
1059
+ returns Document
1060
+ end
1061
+
1062
+ query "Compaction" do
1063
+ description "What compacting the domain's journals would delete, without deleting it."
1064
+ attribute :domain, DomainPath
1065
+ attribute :aggregates, AggregateList, optional: true
1066
+ returns Document
1067
+ end
1068
+ end
1069
+
1070
+ policy "ExamineWhenFirstEraRequested" do
1071
+ on "Era.FirstEraRequested"
1072
+ trigger Era::JournalStore::Examine, with: { run: :run }
1073
+ end
1074
+
1075
+ policy "ExamineWhenTailMergeRequested" do
1076
+ on "Era.TailMergeRequested"
1077
+ trigger Era::JournalStore::Examine, with: { run: :run }
1078
+ end
1079
+
1080
+ policy "ExamineWhenReattestRequested" do
1081
+ on "Era.ReattestRequested"
1082
+ trigger Era::JournalStore::Examine, with: { run: :run }
1083
+ end
1084
+
1085
+ policy "ExamineWhenBackfillRequested" do
1086
+ on "Era.BackfillRequested"
1087
+ trigger Era::JournalStore::Examine, with: { run: :run }
1088
+ end
1089
+
1090
+ policy "ExamineWhenCompactionRequested" do
1091
+ on "Era.CompactionRequested"
1092
+ trigger Era::JournalStore::Examine, with: { run: :run }
1093
+ end
1094
+
1095
+ policy "ExamineWhenHekiCompactionRequested" do
1096
+ on "Era.HekiCompactionRequested"
1097
+ trigger Era::JournalStore::Examine, with: { run: :run }
1098
+ end
1099
+
1100
+ policy "ExamineWhenApprovalRequested" do
1101
+ on "Era.ApprovalRequested"
1102
+ trigger Era::JournalStore::Examine, with: { run: :run }
1103
+ end
1104
+
1105
+ policy "PermitWhenExamined" do
1106
+ on "Era.EraExamined"
1107
+ trigger Era::Permit, with: {
1108
+ capable: :capable, held: :held, forks: :forks, contested: :contested, edges: :edges,
1109
+ bound: :bound, candidates: :candidates, audited: :audited,
1110
+ rehearsal_needed: :rehearsal_needed, rehearsal_recorded: :rehearsal_recorded,
1111
+ drifted: :drifted, loadable: :loadable, shape_kept: :shape_kept
1112
+ }
1113
+ end
1114
+
1115
+ # `Permit` refuses when a rule is unmet and emits nothing, so this reaction is its complement:
1116
+ # it holds the same rules negated, so an examined change that is not let through is abandoned
1117
+ # rather than left `requested`.
1118
+ policy "AbandonWhenNotPermitted" do
1119
+ on "Era.EraExamined"
1120
+ where do
1121
+ (!capable.value && operation.value != "compact" && operation.value != "compact_heki") ||
1122
+ (operation.value == "hold_first" && held.value != 0) ||
1123
+ (operation.value == "merge_tail" && (held.value <= 1 || forks.value != 0 || contested.value != 0)) ||
1124
+ ((operation.value == "compact" || operation.value == "compact_heki") &&
1125
+ (candidates.value <= 0 || bound.value != 0)) ||
1126
+ (operation.value == "approve_translation" &&
1127
+ (edges.value != 1 || !audited.value || (rehearsal_needed.value && !rehearsal_recorded.value))) ||
1128
+ (operation.value == "reattest" && (!drifted.value || !loadable.value || !shape_kept.value))
1129
+ end
1130
+ trigger Era::Abandon, with: { refusal: "the journal's facts do not admit this change" }
1131
+ end
1132
+
1133
+ policy "ChangeWhenPermitted" do
1134
+ on "Era.EraPermitted"
1135
+ trigger Era::JournalStore::Apply, with: { run: :run }
1136
+ end
1137
+
1138
+ policy "RecordTheChange" do
1139
+ on "Era.EraChanged"
1140
+ trigger Era::Settle, with: { report: :report }
1141
+ end
1142
+
1143
+ policy "RecordTheRefusedChange" do
1144
+ on "Era.EraRefused"
1145
+ trigger Era::Abandon, with: { refusal: :refusal }
1146
+ end
1147
+
1148
+ aggregate "Package" do
1149
+ description "One package of the bluebook registry, vendored into a project and pinned."
1150
+
1151
+ attribute :package, PackageRef
1152
+ attribute :from, Location, optional: true
1153
+ attribute :root, Location, optional: true
1154
+ attribute :report, Report, optional: true
1155
+ attribute :refusal, Report, optional: true
1156
+ identified_by :package
1157
+
1158
+ # `name` for the newest release, `name@1.2.0` for one release, `name@9f2c1ab` for a commit.
1159
+ value_object "PackageRef" do
1160
+ attribute :value, String, pattern: '^[a-z][a-z0-9_]*(@[^ \t\n\r]+)?$'
1161
+
1162
+ invariant("a package is named") { !value.to_s.empty? }
1163
+ end
1164
+
1165
+ # A directory: the source repository (`from`), or the project the files land in (`root`).
1166
+ value_object "Location" do
1167
+ attribute :value, String, pattern: '[^\t\n\r]'
1168
+ end
1169
+
1170
+ value_object "Report" do
1171
+ attribute :value, String
1172
+ end
1173
+
1174
+ lifecycle :status, default: "requested" do
1175
+ transition "Revendor" => "requested", from: %w[vendored refused]
1176
+ transition "Complete" => "vendored", from: "requested"
1177
+ transition "Abandon" => "refused", from: "requested"
1178
+ end
1179
+
1180
+ # `hecks vendor <package[@version]> [from=path] [root=path]`. The record is named by what was
1181
+ # asked for, so a release is one record; vendoring the same spelling again is `hecks revendor`.
1182
+ # `from` defaults to $EMBRYONAUT_BLUEBOOKS_SRC and `root` to the current directory, both read by
1183
+ # the adapter.
1184
+ command "Vendor" do
1185
+ role "Operator"
1186
+ goal "Pin a package of the bluebook registry into this project, at a release or a commit"
1187
+
1188
+ attribute :package, PackageRef
1189
+ attribute :from, Location, optional: true
1190
+ attribute :root, Location, optional: true
1191
+
1192
+ sets :package
1193
+ sets :from
1194
+ sets :root
1195
+
1196
+ emits VendorRequested
1197
+ end
1198
+
1199
+ command "Revendor" do
1200
+ role "Operator"
1201
+ goal "Pin a package that was vendored before again, for the newest release or the same one"
1202
+
1203
+ reference_to Package
1204
+ attribute :from, Location, optional: true
1205
+ attribute :root, Location, optional: true
1206
+
1207
+ sets :from
1208
+ sets :root
1209
+
1210
+ emits RevendorRequested
1211
+ end
1212
+
1213
+ command "Complete" do
1214
+ role "System"
1215
+ goal "Record that the package was pinned, and what changed"
1216
+
1217
+ reference_to Package
1218
+ attribute :report, Report
1219
+
1220
+ sets :report
1221
+
1222
+ emits PackageVendored
1223
+ end
1224
+
1225
+ command "Abandon" do
1226
+ role "System"
1227
+ goal "Record that the package could not be pinned, and why"
1228
+
1229
+ reference_to Package
1230
+ attribute :refusal, Report
1231
+
1232
+ sets :refusal
1233
+
1234
+ emits VendoringRefused
1235
+ end
1236
+
1237
+ query "Pinning" do
1238
+ description "What vendoring one package did: its status, and the report or the refusal."
1239
+ attribute :package, PackageRef
1240
+ where(package: :package)
1241
+ order_by :package
1242
+ end
1243
+
1244
+ query "Unpinned" do
1245
+ description "Every package the last vendoring of which was refused."
1246
+ where(status: "refused")
1247
+ order_by :package
1248
+ end
1249
+ end
1250
+
1251
+ policy "PinWhenVendored" do
1252
+ on "Package.VendorRequested"
1253
+ trigger Package::Git::Pin, with: { package: :package }
1254
+ end
1255
+
1256
+ policy "PinWhenRevendored" do
1257
+ on "Package.RevendorRequested"
1258
+ trigger Package::Git::Pin, with: { package: :package }
1259
+ end
1260
+
1261
+ policy "RecordThePinning" do
1262
+ on "Package.PackagePinned"
1263
+ trigger Package::Complete, with: { report: :report }
1264
+ end
1265
+
1266
+ policy "RecordTheRefusedPin" do
1267
+ on "Package.PinRefused"
1268
+ trigger Package::Abandon, with: { refusal: :refusal }
1269
+ end
1270
+
1271
+ # A run record for opening a door onto the framework, not a door: the doors live in `Hecks::Doors`.
1272
+ aggregate "Door" do
1273
+ description "A way into the framework opened for a project: its launchers, or an MCP server."
1274
+
1275
+ attribute :run, DoorKey
1276
+ attribute :domains, DomainList, optional: true
1277
+ attribute :stdio, Switch, optional: true
1278
+ attribute :output, Note, optional: true
1279
+ attribute :refusal, Note, optional: true
1280
+ identified_by :run
1281
+
1282
+ value_object "DoorKey" do
1283
+ attribute :value, String, pattern: '[^ \t\n\r]'
1284
+
1285
+ invariant("a door is named") { !value.to_s.empty? }
1286
+ end
1287
+
1288
+ # Domain directories under the project, comma separated.
1289
+ value_object "DomainList" do
1290
+ attribute :value, String, pattern: '[^\t\n\r]'
1291
+ end
1292
+
1293
+ value_object "Switch" do
1294
+ attribute :value, Boolean
1295
+ end
1296
+
1297
+ value_object "Note" do
1298
+ attribute :value, String
1299
+ end
1300
+
1301
+ lifecycle :status, default: "requested" do
1302
+ transition "Finish" => "finished", from: "requested"
1303
+ transition "Stop" => "stopped", from: "requested"
1304
+ end
1305
+
1306
+ # Writes a launcher beside each domain. It replaces the generated launcher already there and
1307
+ # deletes nothing, so it runs without `confirm` (`hecks project_cli` never asked for one).
1308
+ command "ProjectCli" do
1309
+ role "Operator"
1310
+ goal "Write a command-line launcher beside each domain of this project"
1311
+
1312
+ attribute :run, DoorKey
1313
+ attribute :domains, DomainList, optional: true
1314
+
1315
+ sets :run
1316
+ sets :domains
1317
+
1318
+ emits LaunchersRequested
1319
+ end
1320
+
1321
+ # A stdio server runs until its client closes stdin, so it cannot be asked and answered
1322
+ # quickly like the others. The command records that the door was started; the Terminal
1323
+ # adapter hands the process over to the door and answers when it closes, as OpenConsole
1324
+ # does for a console. The launcher spells it `serve_mcp`; the `mcp` spelling exe/hecks ships
1325
+ # today stays with its router until the launcher is generated.
1326
+ command "ServeMcp" do
1327
+ role "Operator"
1328
+ goal "Serve the storehouse bus to an MCP client over stdio until it closes"
1329
+
1330
+ attribute :run, DoorKey
1331
+ attribute :stdio, Switch, optional: true
1332
+
1333
+ sets :run
1334
+ sets :stdio
1335
+
1336
+ emits McpRequested
1337
+ end
1338
+
1339
+ command "Finish" do
1340
+ role "System"
1341
+ goal "Record that the door did its work, or was closed, and what it said"
1342
+
1343
+ reference_to Door
1344
+ attribute :output, Note
1345
+
1346
+ sets :output
1347
+
1348
+ emits DoorFinished
1349
+ end
1350
+
1351
+ command "Stop" do
1352
+ role "System"
1353
+ goal "Record that the door could not be opened, and why"
1354
+
1355
+ reference_to Door
1356
+ attribute :refusal, Note
1357
+
1358
+ sets :refusal
1359
+
1360
+ emits DoorStopped
1361
+ end
1362
+
1363
+ query "Ended" do
1364
+ description "How one door ended: its status, and what it said or why it stopped."
1365
+ attribute :run, DoorKey
1366
+ where(run: :run)
1367
+ order_by :run
1368
+ end
1369
+
1370
+ query "Stopped" do
1371
+ description "Every door that could not be opened."
1372
+ where(status: "stopped")
1373
+ order_by :run
1374
+ end
1375
+ end
1376
+
1377
+ policy "WriteWhenRequested" do
1378
+ on "Door.LaunchersRequested"
1379
+ trigger Door::Workspace::Write, with: { run: :run }
1380
+ end
1381
+
1382
+ policy "ServeWhenRequested" do
1383
+ on "Door.McpRequested"
1384
+ trigger Door::Terminal::Serve, with: { run: :run }
1385
+ end
1386
+
1387
+ policy "RecordTheDoor" do
1388
+ on "Door.DoorAnswered"
1389
+ trigger Door::Finish, with: { output: :output }
1390
+ end
1391
+
1392
+ policy "RecordTheStoppedDoor" do
1393
+ on "Door.DoorRefused"
1394
+ trigger Door::Stop, with: { refusal: :refusal }
1395
+ end
1396
+
1397
+ aggregate "Build" do
1398
+ description "One build of a domain for the Rust host, or a check of that build against Ruby."
1399
+
1400
+ attribute :run, BuildKey
1401
+ attribute :domain, DomainPath, optional: true
1402
+ attribute :script, ScriptPath, optional: true
1403
+ attribute :artifact, Artifact, optional: true
1404
+ attribute :seeds, Count, optional: true
1405
+ attribute :steps, Count, optional: true
1406
+ attribute :output, Report, optional: true
1407
+ attribute :refusal, Report, optional: true
1408
+ identified_by :run
1409
+
1410
+ value_object "BuildKey" do
1411
+ attribute :value, String, pattern: '[^ \t\n\r]'
1412
+
1413
+ invariant("a build is named") { !value.to_s.empty? }
1414
+ end
1415
+
1416
+ # A domain directory. Its last part is also a Rust module and a Cargo feature, so it is a
1417
+ # lowercase identifier; the toolchain refuses the reserved words the pattern cannot.
1418
+ value_object "DomainPath" do
1419
+ attribute :value, String, pattern: '(^|/)[a-z][a-z0-9_]*/?$'
1420
+ end
1421
+
1422
+ # A generated module: the directory name a domain was built as.
1423
+ value_object "ModuleName" do
1424
+ attribute :value, String, pattern: '^[a-z][a-z0-9_]*$'
1425
+ end
1426
+
1427
+ # A JSON file of steps for a domain to replay.
1428
+ value_object "ScriptPath" do
1429
+ attribute :value, String, pattern: '[^\t\n\r]'
1430
+ end
1431
+
1432
+ # What a build is replayed through: `native`, `build` (the domain's own feature), a `.wasm`
1433
+ # module, or a binary.
1434
+ value_object "Artifact" do
1435
+ attribute :value, String, pattern: '[^ \t\n\r]'
1436
+ end
1437
+
1438
+ value_object "Count" do
1439
+ attribute :value, Integer
1440
+
1441
+ invariant("a count is positive") { value > 0 }
1442
+ end
1443
+
1444
+ value_object "CodegenName" do
1445
+ attribute :value, String, pattern: '^(ruby|rust)$'
1446
+ end
1447
+
1448
+ value_object "Report" do
1449
+ attribute :value, String
1450
+ end
1451
+
1452
+ lifecycle :status, default: "requested" do
1453
+ transition "Complete" => "completed", from: "requested"
1454
+ transition "Fault" => "faulted", from: "requested"
1455
+ end
1456
+
1457
+ # Writes the domain's Rust source and a Cargo feature for it into the build workspace: the
1458
+ # checkout's own `rust/`, or a copy of the packaged workspace under `.hecks/rust/<version>/`.
1459
+ # Nothing is ever written into the installed gem.
1460
+ command "ProjectRust" do
1461
+ role "Operator"
1462
+ goal "Generate a domain's Rust source for the Rust host"
1463
+
1464
+ attribute :domain, DomainPath
1465
+ attribute :run, BuildKey
1466
+
1467
+ sets :domain
1468
+ sets :run
1469
+
1470
+ emits RustProjectionRequested
1471
+ end
1472
+
1473
+ command "BuildWasm" do
1474
+ role "Operator"
1475
+ goal "Compile a domain to a WASI module a runtime such as wasmtime can run"
1476
+
1477
+ attribute :domain, DomainPath
1478
+ attribute :run, BuildKey
1479
+
1480
+ sets :domain
1481
+ sets :run
1482
+
1483
+ emits WasmBuildRequested
1484
+ end
1485
+
1486
+ command "BuildBrowserWasm" do
1487
+ role "Operator"
1488
+ goal "Compile a domain to an ES module a browser can import"
1489
+
1490
+ attribute :domain, DomainPath
1491
+ attribute :run, BuildKey
1492
+
1493
+ sets :domain
1494
+ sets :run
1495
+
1496
+ emits BrowserWasmBuildRequested
1497
+ end
1498
+
1499
+ # Without an artifact it answers Ruby's own result, which saved to a file becomes a pinned
1500
+ # expectation; with one it compares Ruby's result with that artifact's.
1501
+ command "CheckConformance" do
1502
+ role "Operator"
1503
+ goal "Replay a script through Ruby and a Rust build of the domain and compare what each did"
1504
+
1505
+ attribute :domain, DomainPath
1506
+ attribute :script, ScriptPath
1507
+ attribute :run, BuildKey
1508
+ attribute :artifact, Artifact, optional: true
1509
+
1510
+ sets :domain
1511
+ sets :script
1512
+ sets :run
1513
+ sets :artifact
1514
+
1515
+ emits ConformanceRequested
1516
+ end
1517
+
1518
+ command "FuzzConformance" do
1519
+ role "Operator"
1520
+ goal "Replay generated sequences through Ruby and a Rust build of the domain and compare them"
1521
+
1522
+ attribute :domain, DomainPath
1523
+ attribute :artifact, Artifact
1524
+ attribute :run, BuildKey
1525
+ attribute :seeds, Count, optional: true
1526
+ attribute :steps, Count, optional: true
1527
+
1528
+ sets :domain
1529
+ sets :artifact
1530
+ sets :run
1531
+ sets :seeds
1532
+ sets :steps
1533
+
1534
+ emits ConformanceFuzzRequested
1535
+ end
1536
+
1537
+ command "CheckCoverageAllowlist" do
1538
+ role "Operator"
1539
+ goal "Check that every rule excusing a Rust coverage gap still excuses a real one"
1540
+
1541
+ attribute :run, BuildKey
1542
+
1543
+ sets :run
1544
+
1545
+ emits CoverageAllowlistRequested
1546
+ end
1547
+
1548
+ command "Complete" do
1549
+ role "System"
1550
+ goal "Record that the build or check ended well, and what it reported"
1551
+
1552
+ reference_to Build
1553
+ attribute :output, Report
1554
+
1555
+ sets :output
1556
+
1557
+ emits BuildCompleted
1558
+ end
1559
+
1560
+ command "Fault" do
1561
+ role "System"
1562
+ goal "Record that the build or check was refused or found a difference, and what it said"
1563
+
1564
+ reference_to Build
1565
+ attribute :refusal, Report
1566
+
1567
+ sets :refusal
1568
+
1569
+ emits BuildFaulted
1570
+ end
1571
+
1572
+ query "Result" do
1573
+ description "How one build or check ended: its status, and what it reported or why it failed."
1574
+ attribute :run, BuildKey
1575
+ where(run: :run)
1576
+ order_by :run
1577
+ end
1578
+
1579
+ query "Faulted" do
1580
+ description "Every build or check that was refused or found a difference."
1581
+ where(status: "faulted")
1582
+ order_by :run
1583
+ end
1584
+
1585
+ # An answer that is one document of text, exactly as the tool that produced it wrote it.
1586
+ value_object "Document" do
1587
+ attribute :text, String
1588
+ end
1589
+
1590
+ query "RustCoverage" do
1591
+ description "Whether each construct of a generated module has a routed Rust implementation."
1592
+ attribute :module_name, ModuleName
1593
+ attribute :codegen, CodegenName, optional: true
1594
+ returns Document
1595
+ end
1596
+ end
1597
+
1598
+ policy "GenerateWhenRequested" do
1599
+ on "Build.RustProjectionRequested"
1600
+ trigger Build::RustToolchain::Generate, with: { run: :run }
1601
+ end
1602
+
1603
+ policy "CompileWasmWhenRequested" do
1604
+ on "Build.WasmBuildRequested"
1605
+ trigger Build::RustToolchain::Wasm, with: { run: :run }
1606
+ end
1607
+
1608
+ policy "CompileBrowserWasmWhenRequested" do
1609
+ on "Build.BrowserWasmBuildRequested"
1610
+ trigger Build::RustToolchain::BrowserWasm, with: { run: :run }
1611
+ end
1612
+
1613
+ policy "ConformWhenRequested" do
1614
+ on "Build.ConformanceRequested"
1615
+ trigger Build::RustToolchain::Conform, with: { run: :run }
1616
+ end
1617
+
1618
+ policy "ReplayWhenRequested" do
1619
+ on "Build.ConformanceFuzzRequested"
1620
+ trigger Build::RustToolchain::Replay, with: { run: :run }
1621
+ end
1622
+
1623
+ policy "AuditWhenRequested" do
1624
+ on "Build.CoverageAllowlistRequested"
1625
+ trigger Build::RustToolchain::Audit, with: { run: :run }
1626
+ end
1627
+
1628
+ policy "RecordTheBuild" do
1629
+ on "Build.BuildAnswered"
1630
+ trigger Build::Complete, with: { output: :output }
1631
+ end
1632
+
1633
+ policy "RecordTheFault" do
1634
+ on "Build.BuildRefused"
1635
+ trigger Build::Fault, with: { refusal: :refusal }
1636
+ end
1637
+
1638
+ # Named for the run, as ModelCheckRun is: `Hecks::Fuzzing` is the fuzzing toolkit's own module,
1639
+ # so a hecksagon could not spell an aggregate of that name.
1640
+ aggregate "FuzzRun" do
1641
+ description "One fuzz sweep or benchmark, and how it ended; and the generated sequences it replays."
1642
+
1643
+ attribute :domain, DomainPath, optional: true
1644
+ attribute :run, FuzzKey
1645
+ attribute :seeds, Count, optional: true
1646
+ attribute :steps, Count, optional: true
1647
+ attribute :workers, Count, optional: true
1648
+ attribute :adapter, Persistence, optional: true
1649
+ attribute :domains, NameList, optional: true
1650
+ attribute :targets, NameList, optional: true
1651
+ attribute :iterations, Count, optional: true
1652
+ attribute :warmup, Warmup, optional: true
1653
+ attribute :runs, Count, optional: true
1654
+ attribute :rust_binary, BinaryPath, optional: true
1655
+ attribute :format, Format, optional: true
1656
+ attribute :output, OutputPath, optional: true
1657
+ attribute :report, Report, optional: true
1658
+ attribute :refusal, Report, optional: true
1659
+ identified_by :run
1660
+
1661
+ value_object "FuzzKey" do
1662
+ attribute :value, String, pattern: '[^ \t\n\r]'
1663
+
1664
+ invariant("a sweep is named") { !value.to_s.empty? }
1665
+ end
1666
+
1667
+ # A domain directory; a sweep with none covers every domain of a hecks checkout.
1668
+ value_object "DomainPath" do
1669
+ attribute :value, String, pattern: '[^\t\n\r]'
1670
+
1671
+ invariant("a domain is named by its path") { !value.to_s.empty? }
1672
+ end
1673
+
1674
+ value_object "Count" do
1675
+ attribute :value, Integer
1676
+
1677
+ invariant("a count is positive") { value > 0 }
1678
+ end
1679
+
1680
+ value_object "Warmup" do
1681
+ attribute :value, Integer
1682
+
1683
+ invariant("a warmup is not negative") { value >= 0 }
1684
+ end
1685
+
1686
+ # Which persistence every ephemeral boot of a sweep uses.
1687
+ value_object "Persistence" do
1688
+ attribute :value, String, pattern: '^(memory|sqlite|postgres)$'
1689
+ end
1690
+
1691
+ # Names, comma separated: benchmark domains, or the targets to measure.
1692
+ value_object "NameList" do
1693
+ attribute :value, String, pattern: '^[^ \t\n\r,]+(,[^ \t\n\r,]+)*$'
1694
+ end
1695
+
1696
+ value_object "BinaryPath" do
1697
+ attribute :value, String, pattern: '[^\t\n\r]'
1698
+ end
1699
+
1700
+ value_object "Format" do
1701
+ attribute :value, String, pattern: '^(markdown|json)$'
1702
+ end
1703
+
1704
+ value_object "OutputPath" do
1705
+ attribute :value, String, pattern: '[^\t\n\r]'
1706
+ end
1707
+
1708
+ value_object "Report" do
1709
+ attribute :value, String
1710
+ end
1711
+
1712
+ lifecycle :status, default: "requested" do
1713
+ transition "Conclude" => "concluded", from: "requested"
1714
+ transition "Halt" => "halted", from: "requested"
1715
+ end
1716
+
1717
+ # A sweep replays generated command and query sequences and checks properties over each.
1718
+ # A finding is shrunk and saved under `tmp/fuzz-failures/`, and ends the run as halted, with
1719
+ # what was found. Sweeping every domain of a checkout needs a hecks checkout; a client names
1720
+ # the domain. The sweep runs in child processes started through the ProcessPool adapter, so an
1721
+ # interrupt reaches the workers.
1722
+ command "Fuzz" do
1723
+ role "Operator"
1724
+ goal "Fuzz a domain with generated sequences and keep whether anything broke a property"
1725
+
1726
+ attribute :domain, DomainPath, optional: true
1727
+ attribute :run, FuzzKey
1728
+ attribute :seeds, Count, optional: true
1729
+ attribute :steps, Count, optional: true
1730
+ attribute :workers, Count, optional: true
1731
+ attribute :adapter, Persistence, optional: true
1732
+
1733
+ sets :domain
1734
+ sets :run
1735
+ sets :seeds
1736
+ sets :steps
1737
+ sets :workers
1738
+ sets :adapter
1739
+
1740
+ emits FuzzRequested
1741
+ end
1742
+
1743
+ # Measures each persistence adapter, and the Rust binary when there is one to build or use.
1744
+ # The report is kept in this record, which the Hecks domain binds to Memory: keeping a
1745
+ # domain's benchmark history beside it, in Sqlite under the project's `.hecks/`, is decided
1746
+ # with the persistence binding for Custodian, not here. `output` also writes the full JSON.
1747
+ command "Bench" do
1748
+ role "Operator"
1749
+ goal "Measure command throughput and latency for each target, and keep the report"
1750
+
1751
+ attribute :run, FuzzKey
1752
+ attribute :domains, NameList, optional: true
1753
+ attribute :targets, NameList, optional: true
1754
+ attribute :iterations, Count, optional: true
1755
+ attribute :warmup, Warmup, optional: true
1756
+ attribute :runs, Count, optional: true
1757
+ attribute :rust_binary, BinaryPath, optional: true
1758
+ attribute :format, Format, optional: true
1759
+ attribute :output, OutputPath, optional: true
1760
+
1761
+ sets :run
1762
+ sets :domains
1763
+ sets :targets
1764
+ sets :iterations
1765
+ sets :warmup
1766
+ sets :runs
1767
+ sets :rust_binary
1768
+ sets :format
1769
+ sets :output
1770
+
1771
+ emits BenchRequested
1772
+ end
1773
+
1774
+ command "Conclude" do
1775
+ role "System"
1776
+ goal "Record that the sweep or benchmark ended well, and what it reported"
1777
+
1778
+ reference_to FuzzRun
1779
+ attribute :report, Report
1780
+
1781
+ sets :report
1782
+
1783
+ emits FuzzConcluded
1784
+ end
1785
+
1786
+ command "Halt" do
1787
+ role "System"
1788
+ goal "Record that the sweep found something or the benchmark could not run, and what it said"
1789
+
1790
+ reference_to FuzzRun
1791
+ attribute :refusal, Report
1792
+
1793
+ sets :refusal
1794
+
1795
+ emits FuzzHalted
1796
+ end
1797
+
1798
+ query "Conclusion" do
1799
+ description "How one sweep or benchmark ended: its status, and what it reported or found."
1800
+ attribute :run, FuzzKey
1801
+ where(run: :run)
1802
+ order_by :run
1803
+ end
1804
+
1805
+ query "Halted" do
1806
+ description "Every sweep that found something and every benchmark that could not run."
1807
+ where(status: "halted")
1808
+ order_by :run
1809
+ end
1810
+
1811
+ # An answer that is one document of text, exactly as the tool that produced it wrote it.
1812
+ value_object "Document" do
1813
+ attribute :text, String
1814
+ end
1815
+
1816
+ # The generator `hecks fuzz` draws from, on its own: one valid dispatch sequence as a
1817
+ # replayable script. `adversarial` is the fraction of steps mutated to be refused; 0 is off.
1818
+ query "GenerateSequence" do
1819
+ description "One generated, valid dispatch sequence for a domain, as a script `hecks run` replays."
1820
+ attribute :domain, DomainPath
1821
+ attribute :seed, Integer, optional: true
1822
+ attribute :steps, Integer, optional: true
1823
+ attribute :adversarial, Float, optional: true
1824
+ returns Document
1825
+ end
1826
+ end
1827
+
1828
+ policy "SweepWhenRequested" do
1829
+ on "FuzzRun.FuzzRequested"
1830
+ trigger FuzzRun::ProcessPool::Sweep, with: { run: :run }
1831
+ end
1832
+
1833
+ policy "MeasureWhenRequested" do
1834
+ on "FuzzRun.BenchRequested"
1835
+ trigger FuzzRun::RustToolchain::Measure, with: { run: :run }
1836
+ end
1837
+
1838
+ policy "RecordTheSweep" do
1839
+ on "FuzzRun.FuzzAnswered"
1840
+ trigger FuzzRun::Conclude, with: { report: :report }
1841
+ end
1842
+
1843
+ policy "RecordTheFinding" do
1844
+ on "FuzzRun.FuzzRefused"
1845
+ trigger FuzzRun::Halt, with: { refusal: :refusal }
1846
+ end
1847
+ end