hecks 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +65 -0
  3. data/lib/hecks/adapters/driven/folder.rb +73 -0
  4. data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
  5. data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
  6. data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
  7. data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
  8. data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
  9. data/lib/hecks/adapters/driven/heki.rb +93 -2
  10. data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
  11. data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
  12. data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
  13. data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
  14. data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
  15. data/lib/hecks/adapters/driven/lambda.rb +43 -2
  16. data/lib/hecks/adapters/driven/local_storage.rb +67 -1
  17. data/lib/hecks/adapters/driven/memory.rb +13 -13
  18. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
  19. data/lib/hecks/adapters/driven/prism.rb +46 -7
  20. data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
  21. data/lib/hecks/adapters/driven/system_clock.rb +3 -0
  22. data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
  23. data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
  24. data/lib/hecks/adapters/driven.rb +2 -0
  25. data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
  26. data/lib/hecks/behaviors/dsl.rb +58 -0
  27. data/lib/hecks/behaviors/expectations.rb +168 -7
  28. data/lib/hecks/behaviors/ir.rb +11 -0
  29. data/lib/hecks/behaviors/rspec.rb +8 -0
  30. data/lib/hecks/behaviors/runner.rb +19 -0
  31. data/lib/hecks/behaviors.rb +8 -0
  32. data/lib/hecks/bluebook/aggregate.rb +30 -0
  33. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
  34. data/lib/hecks/bluebook/assembly/build.rb +25 -4
  35. data/lib/hecks/bluebook/assembly/contract.rb +66 -14
  36. data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
  37. data/lib/hecks/bluebook/assembly/marks.rb +143 -14
  38. data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
  39. data/lib/hecks/bluebook/assembly.rb +25 -7
  40. data/lib/hecks/bluebook/attribute.rb +17 -3
  41. data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
  42. data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
  43. data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
  44. data/lib/hecks/bluebook/behaviour/command.rb +33 -3
  45. data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
  46. data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
  47. data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
  48. data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
  49. data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
  50. data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
  51. data/lib/hecks/bluebook/behaviour/query.rb +5 -0
  52. data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
  53. data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
  54. data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
  55. data/lib/hecks/bluebook/chapter.rb +23 -0
  56. data/lib/hecks/bluebook/command.rb +53 -8
  57. data/lib/hecks/bluebook/domain_port.rb +25 -0
  58. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
  59. data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
  60. data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
  62. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
  63. data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
  64. data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
  65. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
  66. data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
  67. data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
  68. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
  69. data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
  70. data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
  71. data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
  72. data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
  73. data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
  74. data/lib/hecks/bluebook/entity.rb +29 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
  77. data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
  78. data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
  79. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
  80. data/lib/hecks/bluebook/expression/resolver.rb +320 -77
  81. data/lib/hecks/bluebook/hexagon.rb +34 -0
  82. data/lib/hecks/bluebook/lifecycle.rb +11 -0
  83. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
  84. data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
  85. data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
  86. data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
  88. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
  90. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
  91. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
  92. data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
  93. data/lib/hecks/bluebook/meta_validator.rb +180 -84
  94. data/lib/hecks/bluebook/model_check.rb +268 -24
  95. data/lib/hecks/bluebook/pattern_subset.rb +23 -1
  96. data/lib/hecks/bluebook/process_manager.rb +13 -0
  97. data/lib/hecks/bluebook/project_discovery.rb +5 -0
  98. data/lib/hecks/bluebook/project_loader.rb +40 -0
  99. data/lib/hecks/bluebook/project_register.rb +44 -0
  100. data/lib/hecks/bluebook/query.rb +27 -0
  101. data/lib/hecks/bluebook/read_model.rb +21 -1
  102. data/lib/hecks/bluebook/reference.rb +21 -8
  103. data/lib/hecks/bluebook/smoke_test.rb +29 -6
  104. data/lib/hecks/bluebook/synthesizer.rb +34 -0
  105. data/lib/hecks/bluebook/translation.rb +30 -1
  106. data/lib/hecks/bluebook/value_object.rb +23 -5
  107. data/lib/hecks/bluebook.rb +3 -4
  108. data/lib/hecks/codemod.rb +107 -20
  109. data/lib/hecks/construct.rb +15 -1
  110. data/lib/hecks/corpus.rb +146 -25
  111. data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
  112. data/lib/hecks/doc/reference.rb +185 -16
  113. data/lib/hecks/embryonaut_bluebook.rb +32 -9
  114. data/lib/hecks/facade/handle.rb +76 -3
  115. data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
  116. data/lib/hecks/forms/field_shape.rb +3 -0
  117. data/lib/hecks/forms/page.rb +14 -0
  118. data/lib/hecks/forms/port_argument.rb +12 -0
  119. data/lib/hecks/forms/query_form_renderer.rb +63 -0
  120. data/lib/hecks/forms/record_renderer.rb +58 -0
  121. data/lib/hecks/forms/record_table.rb +27 -0
  122. data/lib/hecks/forms/reference_options.rb +24 -0
  123. data/lib/hecks/forms/value_object_shape.rb +10 -0
  124. data/lib/hecks/fqn.rb +58 -0
  125. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  126. data/lib/hecks/framework/oidc.json +15 -0
  127. data/lib/hecks/framework.rb +43 -20
  128. data/lib/hecks/freezer.rb +17 -1
  129. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
  130. data/lib/hecks/fuzzing/combination_miner.rb +59 -0
  131. data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
  132. data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
  133. data/lib/hecks/fuzzing/differential.rb +34 -0
  134. data/lib/hecks/fuzzing/domain_generator.rb +188 -11
  135. data/lib/hecks/fuzzing/era_boundary.rb +45 -15
  136. data/lib/hecks/fuzzing/form_census.rb +86 -0
  137. data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
  138. data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
  139. data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
  140. data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
  141. data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
  142. data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
  143. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
  144. data/lib/hecks/fuzzing/properties/guards.rb +44 -0
  145. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
  146. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
  147. data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
  148. data/lib/hecks/fuzzing/properties/querying.rb +68 -14
  149. data/lib/hecks/fuzzing/properties.rb +24 -15
  150. data/lib/hecks/fuzzing/qa_settings.rb +12 -0
  151. data/lib/hecks/fuzzing/replay.rb +137 -29
  152. data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
  153. data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
  154. data/lib/hecks/fuzzing/self_consistency.rb +189 -40
  155. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
  156. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
  157. data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
  158. data/lib/hecks/fuzzing/shrinker.rb +114 -2
  159. data/lib/hecks/fuzzing/structural_skips.rb +18 -4
  160. data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
  161. data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
  162. data/lib/hecks/fuzzing/value_generator.rb +98 -10
  163. data/lib/hecks/grammar/evolve.rb +178 -2
  164. data/lib/hecks/grammar.rb +46 -0
  165. data/lib/hecks/ir.rb +38 -7
  166. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  167. data/lib/hecks/literal.rb +32 -0
  168. data/lib/hecks/naming.rb +88 -7
  169. data/lib/hecks/ports/access_control.rb +5 -10
  170. data/lib/hecks/ports/authorization.rb +3 -6
  171. data/lib/hecks/ports/identity_assignment.rb +1 -2
  172. data/lib/hecks/ports/identity_resolution.rb +1 -2
  173. data/lib/hecks/ports/key_vault.port +6 -0
  174. data/lib/hecks/ports/key_vault.rb +58 -0
  175. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
  176. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
  177. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
  178. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
  179. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
  180. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  181. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
  182. data/lib/hecks/ports/query/in_memory.rb +39 -6
  183. data/lib/hecks/ports/query/ordering.rb +15 -0
  184. data/lib/hecks/ports.rb +1 -0
  185. data/lib/hecks/projections/bootstrap_table.rb +43 -8
  186. data/lib/hecks/projections/diagrams.rb +243 -7
  187. data/lib/hecks/projections/glossary/html.rb +88 -0
  188. data/lib/hecks/projections/glossary/markdown.rb +47 -0
  189. data/lib/hecks/projections/glossary/mermaid.rb +48 -0
  190. data/lib/hecks/projections/glossary/sentences.rb +105 -0
  191. data/lib/hecks/projections/glossary.rb +161 -19
  192. data/lib/hecks/projections/model/deviations.rb +44 -0
  193. data/lib/hecks/projections/model.rb +51 -1
  194. data/lib/hecks/projections/oidc.rb +40 -3
  195. data/lib/hecks/projections/parser_table.rb +49 -8
  196. data/lib/hecks/projections/reference.rb +12 -0
  197. data/lib/hecks/projections/rust_vocabulary.rb +219 -16
  198. data/lib/hecks/projections/shape.rb +8 -0
  199. data/lib/hecks/projections/statements.rb +63 -16
  200. data/lib/hecks/projections/vocabulary.rb +17 -0
  201. data/lib/hecks/projector/cli_projector.rb +218 -10
  202. data/lib/hecks/projector/docs_projector.rb +145 -19
  203. data/lib/hecks/projector/exporter.rb +65 -11
  204. data/lib/hecks/projector/ir_projector.rb +6 -0
  205. data/lib/hecks/projector/narrate_projector.rb +136 -15
  206. data/lib/hecks/projector/target.rb +29 -5
  207. data/lib/hecks/projector.rb +74 -6
  208. data/lib/hecks/query_ir.rb +47 -0
  209. data/lib/hecks/query_specification/common/null_policy.rb +5 -3
  210. data/lib/hecks/rendering.rb +6 -0
  211. data/lib/hecks/router/namespace_installer.rb +13 -0
  212. data/lib/hecks/router.rb +55 -0
  213. data/lib/hecks/runtime/aggregate_lock.rb +9 -0
  214. data/lib/hecks/runtime/boot_gates.rb +18 -0
  215. data/lib/hecks/runtime/caller.rb +32 -0
  216. data/lib/hecks/runtime/capability_graph.rb +11 -0
  217. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
  218. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
  219. data/lib/hecks/runtime/command_interpreter.rb +42 -17
  220. data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
  221. data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
  222. data/lib/hecks/runtime/command_rules/references.rb +118 -28
  223. data/lib/hecks/runtime/dependency_planning.rb +45 -0
  224. data/lib/hecks/runtime/dispatcher.rb +28 -50
  225. data/lib/hecks/runtime/entity_element.rb +161 -8
  226. data/lib/hecks/runtime/entity_interpreter.rb +44 -9
  227. data/lib/hecks/runtime/errors.rb +18 -4
  228. data/lib/hecks/runtime/event.rb +10 -5
  229. data/lib/hecks/runtime/identity.rb +71 -3
  230. data/lib/hecks/runtime/instance.rb +67 -7
  231. data/lib/hecks/runtime/interpreting.rb +13 -5
  232. data/lib/hecks/runtime/invocation.rb +118 -36
  233. data/lib/hecks/runtime/loader.rb +94 -8
  234. data/lib/hecks/runtime/outbox.rb +145 -7
  235. data/lib/hecks/runtime/policy_interpreter.rb +22 -9
  236. data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
  237. data/lib/hecks/runtime/query_interpreter.rb +40 -12
  238. data/lib/hecks/runtime/reaction_invocation.rb +53 -8
  239. data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
  240. data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
  241. data/lib/hecks/runtime/reference_hop.rb +42 -0
  242. data/lib/hecks/runtime/refusal_wording.rb +50 -0
  243. data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
  244. data/lib/hecks/runtime/registry/verification.rb +119 -4
  245. data/lib/hecks/runtime/registry.rb +157 -4
  246. data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
  247. data/lib/hecks/runtime/routing.rb +27 -2
  248. data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
  249. data/lib/hecks/runtime/saga_interpreter.rb +27 -13
  250. data/lib/hecks/runtime/tenant_check.rb +26 -6
  251. data/lib/hecks/runtime/tenant_scope.rb +18 -0
  252. data/lib/hecks/runtime/value/coercion.rb +255 -33
  253. data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
  254. data/lib/hecks/runtime/value.rb +50 -0
  255. data/lib/hecks/runtime.rb +32 -0
  256. data/lib/hecks/storehouse.rb +305 -9
  257. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  258. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  259. data/lib/hecks/version.rb +1 -1
  260. data/lib/hecks.rb +79 -1
  261. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
  262. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
  263. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
  264. metadata +11 -5
  265. data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
  266. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
  267. data/lib/hecks/deprecation.rb +0 -95
@@ -0,0 +1,130 @@
1
+ # ITS OWN BOUNDED CONTEXT, booted centrally like `Deploy` — never
2
+ # attached via `uses_framework` (a framework member's aggregate folds
3
+ # into the ATTACHING domain's own per-boot registry, framework.rb:43-66
4
+ # — since hecks gives each tenant its own separate `Runtime.boot`, a
5
+ # framework-attached Tenant would exist as a single-row copy inside
6
+ # EVERY tenant's own boot, never as one cross-tenant list, defeating
7
+ # this domain's whole point). `Deploy::Tenant` (deploy.bluebook) stays
8
+ # a SEPARATE, validation-only aggregate — a shape check before
9
+ # provisioning; this is the durable, reactable runtime fact that
10
+ # aggregate's own comment named and deliberately deferred. Two
11
+ # different meanings of "Tenant," on purpose, translated at the one
12
+ # explicit seam `tenancy.hecksagon`'s own `translates` line names.
13
+ Hecks.bluebook "Tenancy" do
14
+ vision "Which tenants exist, which realm and storage each one owns, and whether each is currently allowed to run, is one queryable, historical fact — not scattered across a world-file overlay and a validation-only command."
15
+
16
+ supporting
17
+
18
+ aggregate "Tenant" do
19
+ description "One tenant's durable identity and lifecycle — registered, then active or suspended, until retired."
20
+
21
+ identified_by :slug
22
+
23
+ attribute :slug, Slug
24
+ attribute :domain, DomainName
25
+ attribute :realm, Realm
26
+ attribute :schema, Schema
27
+ attribute :status, Status, default: { value: "Active" }
28
+
29
+ value_object "Slug" do
30
+ attribute :value, String, pattern: '^[a-z][a-z0-9_]*$'
31
+ invariant("a tenant slug is named") { !value.to_s.empty? }
32
+ end
33
+
34
+ value_object "DomainName" do
35
+ attribute :value, String
36
+ invariant("a tenant names the domain it is for") { !value.to_s.empty? }
37
+ end
38
+
39
+ value_object "Realm" do
40
+ attribute :value, String
41
+ invariant("a tenant realm is named") { !value.to_s.empty? }
42
+ end
43
+
44
+ value_object "Schema" do
45
+ attribute :value, String, pattern: '^[a-z][a-z0-9_]*$'
46
+ invariant("a schema is named") { !value.to_s.empty? }
47
+ end
48
+
49
+ value_object "Status" do
50
+ attribute :value, String, one_of: ["Active", "Suspended", "Retired"]
51
+ end
52
+
53
+ # FIRES AFTER PROVISIONING, NEVER BEFORE — a pure record with no
54
+ # side effects, dispatched by `tenancy.hecksagon`'s own `translates`
55
+ # reaction once `Deploy::Tenant.Provision`'s adapter has already
56
+ # written the world overlay and proven the domain boots. Driving
57
+ # provisioning FROM this command's own emission would need a
58
+ # process manager reacting to it — the exact durable-saga cost
59
+ # `deploy.bluebook`'s own `Tenant` aggregate comment already argued
60
+ # three local, idempotent, single-process steps don't need.
61
+ command "Register" do
62
+ goal "Record that a tenant now exists, after its shape has been validated and its schema provisioned"
63
+
64
+ attribute :slug, Slug
65
+ attribute :domain, DomainName
66
+ attribute :realm, Realm
67
+ attribute :schema, Schema
68
+
69
+ sets :slug
70
+ sets :domain
71
+ sets :realm
72
+ sets :schema
73
+
74
+ emits "TenantRegistered"
75
+ end
76
+
77
+ # RECORD-ONLY — nothing today reads `status` before dispatch or
78
+ # boot, so suspending a tenant here does not yet stop anything from
79
+ # running. Spelled out in this command's own `goal`, not just a
80
+ # comment, so it survives into generated docs/corpus rather than
81
+ # quietly reading as enforced when it isn't — the exact "a dial
82
+ # says a check runs when nothing runs it" defect class this
83
+ # project has already been burned by once.
84
+ command "Suspend" do
85
+ goal "Record that a tenant should no longer be treated as active — nothing yet enforces this at dispatch or boot time"
86
+
87
+ reference_to Tenant
88
+
89
+ sets :status, to: "Suspended"
90
+
91
+ emits "TenantSuspended"
92
+ end
93
+
94
+ command "Reactivate" do
95
+ goal "Restore a suspended tenant to active"
96
+
97
+ reference_to Tenant
98
+
99
+ sets :status, to: "Active"
100
+
101
+ emits "TenantReactivated"
102
+ end
103
+
104
+ # RECORD-ONLY, DELIBERATELY — dropping a live Postgres schema as an
105
+ # automatic side effect of a command handler would break the pure,
106
+ # no-adapter-calls-from-a-handler shape every other command in this
107
+ # corpus holds to. Real teardown, if ever built, is its own
108
+ # separate, deliberately reviewed tool — never implicit here.
109
+ command "Retire" do
110
+ goal "Record that a tenant is permanently done — real schema teardown is a deliberately separate, manually-run tool, never automatic here"
111
+
112
+ reference_to Tenant
113
+
114
+ sets :status, to: "Retired"
115
+
116
+ emits "TenantRetired"
117
+ end
118
+
119
+ query "Active" do
120
+ description "Every tenant currently allowed to run."
121
+ where(status: "Active")
122
+ end
123
+
124
+ query "BySlug" do
125
+ description "One tenant's current record, by its slug."
126
+ attribute :slug, Slug
127
+ where(slug: :slug)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,32 @@
1
+ Hecks.hecksagon "Tenancy" do
2
+ Tenancy::Tenant.persisted_by("LocalStorage")
3
+
4
+ # THE TRANSLATION SEAM — the one explicit place Deploy's "Tenant"
5
+ # (a validated request) becomes Tenancy's "Tenant" (a durable fact).
6
+ # `on` deliberately omits the "Deploy::" prefix: PolicyInterpreter
7
+ # matches by event name plus the emitting aggregate's own demodulised
8
+ # name only, never the domain (confirmed live — a domain-qualified
9
+ # `on` silently never matches).
10
+ #
11
+ # `with:` IS REQUIRED, confirmed live — `TenantProvisioned`'s own
12
+ # payload is the ANSWER (slug/domain/realm/schema) merged with the
13
+ # port operation's OWN dispatch-time args (database/adapter,
14
+ # PortOperationInterpreter#ask's `ctx.args.merge(spread(answer))`),
15
+ # so the whole payload forwarded verbatim carries two fields
16
+ # `Register` never declares and gets refused outright.
17
+ # CHECKED TOGETHER, NOT IN ISOLATION — `bin/model_check`'s own
18
+ # `deaf_policy` check only sees ONE domain's own emitted events by
19
+ # default, which would flag this reaction as deaf (it reacts to a
20
+ # FOREIGN event, not one Tenancy itself emits). Fixed by
21
+ # `global_emitted_events:`, a cross-domain set `bin/model_check`
22
+ # builds across every target it boots in one run — run `bin/model_check
23
+ # lib/hecks/deploy lib/hecks/tenancy` (both targets, one invocation),
24
+ # not each directory separately, or this reads as a false positive
25
+ # again. Neither `deploy` nor `tenancy` is in
26
+ # `Hecks::Corpus::MODEL_CHECK_KINDS`, so the automatic sweep never
27
+ # reaches this at all, checked or not.
28
+ translates "RegisterProvisionedTenant" do
29
+ on Tenant::TenantProvisioned
30
+ trigger Tenant::Register, with: { slug: :slug, domain: :domain, realm: :realm, schema: :schema }
31
+ end
32
+ end
data/lib/hecks/version.rb CHANGED
@@ -12,5 +12,5 @@ module Hecks
12
12
  # it there, or even in the consuming Gemfile, never closed the gap,
13
13
  # because gemspec evaluation happens before anything Bundler
14
14
  # resolves is actually loadable yet.
15
- VERSION = "1.4.0".freeze
15
+ VERSION = "1.5.0".freeze
16
16
  end
data/lib/hecks.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require_relative "hecks/version"
2
- require_relative "hecks/deprecation"
3
2
  # The closed sets the runtime computes with, generated from
4
3
  # vocabulary.bluebook. Plain data, required first, because some of
5
4
  # them are read while a bluebook is still being parsed.
@@ -67,6 +66,16 @@ module Hecks
67
66
  # resolves its own env var name and passes the resulting string
68
67
  # straight through, e.g. `Hecks.boot(path, environment:
69
68
  # ENV.fetch("MYAPP_ENV", "development"))`.
69
+ #
70
+ # @param path [String] path to a domain directory, or a file inside one
71
+ # @param shared [String, nil] a shared-root override; see `Runtime::Loader.boot`
72
+ # @param install_facade [Boolean] whether to install the `Widget::Item.Add`-style
73
+ # Ruby facade constants for this boot
74
+ # @param environment [String, nil] the environment name whose
75
+ # `environments/<name>.hecksagon`/`.world` overlay, if present, loads after the
76
+ # domain's own
77
+ # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the dispatcher bound
78
+ # to the booted domain
70
79
  def boot(path, shared: nil, install_facade: true, environment: nil)
71
80
  Runtime.boot(path, shared: shared, install_facade: install_facade, environment: environment)
72
81
  end
@@ -75,12 +84,30 @@ module Hecks
75
84
  # `.hecksagon`, optionally a `.world`), loaded in place from wherever
76
85
  # they actually live — see Runtime::Loader.boot_files's own header for
77
86
  # why this exists beside `boot` rather than as a special case of it.
87
+ #
88
+ # @param paths [String, Array<String>] one or more file paths within the domain
89
+ # to load, instead of the whole directory
90
+ # @param shared [String, nil] a shared-root override; see `Runtime::Loader.boot_files`
91
+ # @param install_facade [Boolean] whether to install the `Widget::Item.Add`-style
92
+ # Ruby facade constants for this boot
93
+ # @param environment [String, nil] the environment name passed through to the
94
+ # selected-file loader
95
+ # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the dispatcher bound
96
+ # to the booted domain
78
97
  def boot_files(paths, shared: nil, install_facade: true, environment: nil)
79
98
  Runtime.boot_files(paths, shared: shared, install_facade: install_facade, environment: environment)
80
99
  end
81
100
 
101
+ # Binds the ambient registry for the duration of the block.
102
+ #
103
+ # @param registry [Runtime::Registry] the registry to make current for the block
104
+ # @yield the code that should see `registry` as `current_registry`
105
+ # @return [Object] the block's result
82
106
  def with_registry(registry, &) = Runtime.with_registry(registry, &)
83
107
 
108
+ # Reads the ambient registry a declaration is currently landing in.
109
+ #
110
+ # @return [Runtime::Registry, nil] the current registry, or nil outside a boot
84
111
  def current_registry = Runtime.current_registry
85
112
 
86
113
  # Bind who is dispatching for the duration of the block — checked
@@ -97,14 +124,39 @@ module Hecks
97
124
  #
98
125
  # `as_of` and `scope` are optional too, same shape — see
99
126
  # `Runtime.as_caller`'s own header for what each does.
127
+ #
128
+ # @param role [String, Symbol] the role to check the caller against
129
+ # @param actor_id [String, nil] who is calling, checked against a real Governance
130
+ # `RoleAssignment` when given; string-equality only against `role` when nil
131
+ # @param as_of [Integer, nil] Unix epoch seconds to check a matching `RoleAssignment`'s
132
+ # own `starts_at` against; unchecked when nil
133
+ # @param scope [String, nil] the scope to check a matching `RoleAssignment`'s own
134
+ # `scope` against; unchecked when nil
135
+ # @yield the code to run with this caller bound
136
+ # @return [Object] the block's result
100
137
  def as_caller(role:, actor_id: nil, as_of: nil, scope: nil, &)
101
138
  Runtime.as_caller(role: role, actor_id: actor_id, as_of: as_of, scope: scope, &)
102
139
  end
103
140
 
141
+ # Declares a chapter — a `.bluebook` file's top-level word.
142
+ #
143
+ # @param name [String] the chapter's declared name
144
+ # @param version [String, nil] the chapter's pinned version, or nil for unversioned
145
+ # @yield the chapter's body, evaluated against a `Bluebook::DSL::BluebookBuilder`
146
+ # @return [Bluebook::Chapter] the built, judged chapter
147
+ # @raise [LoadOutsideBoot] if called outside `Hecks.boot`/`.boot_files`
148
+ # @raise [Bluebook::DSL::Malformed] if the chapter fails the language's own judgment
104
149
  def bluebook(name, version: nil, &)
105
150
  collect(:add_bluebook, Bluebook::DSL::BluebookBuilder.build(name, version: version, &))
106
151
  end
107
152
 
153
+ # Declares a hecksagon — a `.hecksagon` file's top-level word, binding a
154
+ # domain's aggregates to adapters.
155
+ #
156
+ # @param name [String, Symbol] the domain's name the hecksagon binds
157
+ # @yield the hecksagon's body, evaluated against a `Bluebook::DSL::HecksagonBuilder`
158
+ # @return [Bluebook::Hecksagon] the built hecksagon
159
+ # @raise [LoadOutsideBoot] if called outside `Hecks.boot`/`.boot_files`
108
160
  def hecksagon(name, &) = collect(:add_hecksagon, Bluebook::DSL::HecksagonBuilder.build(name, &))
109
161
  # Repointed to DomainPortBuilder — the migration DomainPort's own
110
162
  # class comment names as its goal, now landed for the top-level
@@ -127,9 +179,35 @@ module Hecks
127
179
  # the literal top-level `.port` file entry point, keeps the older,
128
180
  # looser rule (see `DomainPortBuilder#initialize`'s own comment).
129
181
  def port(name, &) = collect(:add_port, Bluebook::DSL::DomainPortBuilder.build(name, legacy_bare_port: true, &))
182
+
183
+ # Declares an adapter — an `.adapter` file's top-level word.
184
+ #
185
+ # @param name [String] the adapter's name
186
+ # @yield the adapter's body, evaluated against a `Bluebook::DSL::AdapterBuilder`
187
+ # @return [Bluebook::Adapter] the built, judged adapter
188
+ # @raise [LoadOutsideBoot] if called outside `Hecks.boot`/`.boot_files`
130
189
  def adapter(name, &) = collect(:add_adapter, Bluebook::DSL::AdapterBuilder.build(name, &))
190
+
191
+ # Declares a world — a `.world` file's top-level word, naming a domain's
192
+ # deployment realm.
193
+ #
194
+ # @param name [String, Symbol] the domain's name the world describes
195
+ # @yield the world's body, evaluated against a `Bluebook::DSL::WorldBuilder`
196
+ # @return [Bluebook::World] the built, judged world
197
+ # @raise [LoadOutsideBoot] if called outside `Hecks.boot`/`.boot_files`
131
198
  def world(name, &) = collect(:add_world, Bluebook::DSL::WorldBuilder.build(name, &))
132
199
 
200
+ # Declares a data translation — a `.translation` file's top-level word,
201
+ # migrating one domain's stored shape from one era to the next.
202
+ #
203
+ # @param name [String, Symbol] the domain this translation carries forward
204
+ # @param from [String, Symbol] the origin era
205
+ # @param to [String, Symbol] the destination era
206
+ # @yield the translation's body, evaluated against a
207
+ # `Bluebook::DSL::TranslationBuilder`
208
+ # @return [Bluebook::Translation] the built, judged translation
209
+ # @raise [LoadOutsideBoot] if called outside `Hecks.boot`/`.boot_files`
210
+ # @raise [Bluebook::DSL::Malformed] if `name`, `from`, or `to` is empty
133
211
  def data_translation(name, from:, to:, &)
134
212
  collect(:add_translation, Bluebook::DSL::TranslationBuilder.build(name, from: from, to: to, &))
135
213
  end
@@ -13,6 +13,8 @@ module RuboCop
13
13
  # `h[k.to_sym] || h[k]`, reading a value that could arrive keyed
14
14
  # either by symbol or by string off the wire).
15
15
  #
16
+ # ## The fix
17
+ #
16
18
  # The fix this codebase already converged on — see
17
19
  # `lib/hecks/query_specification/field_path.rb#read`, the shared
18
20
  # digger this whole bug class got consolidated behind: check
@@ -28,6 +30,8 @@ module RuboCop
28
30
  # mechanically, before it becomes bug #9, rather than found by
29
31
  # hand again in a future audit.
30
32
  #
33
+ # ## Scope
34
+ #
31
35
  # Scoped to the `[]`/`[]` shape only, deliberately — a receiver
32
36
  # method-call is compared by AST structure (`==`, which ignores
33
37
  # source location), so `hash[a] || hash[b]` is flagged whether
@@ -58,6 +62,10 @@ module RuboCop
58
62
  # @!method bracket_lookup(node)
59
63
  def_node_matcher :bracket_lookup, "(send $_receiver :[] $_key)"
60
64
 
65
+ # Flags an `a[k] || a[k2]` double bracket-lookup on the same receiver.
66
+ #
67
+ # @param node [RuboCop::AST::OrNode] the `||` node being visited
68
+ # @return [void]
61
69
  def on_or(node)
62
70
  lhs_receiver, lhs_key = bracket_lookup(node.lhs)
63
71
  return unless lhs_receiver
@@ -6,6 +6,8 @@ module RuboCop
6
6
  # in the specific rename/permutation shape this codebase has
7
7
  # already lost real data to once.
8
8
  #
9
+ # ## The bug this follows up on
10
+ #
9
11
  # The bug this is the mechanical follow-up to (M27,
10
12
  # docs/audits/2026-08-10-main-bug-audit.md,
11
13
  # docs/audits/2026-08-11-bug-triage.md; fixed in
@@ -22,7 +24,9 @@ module RuboCop
22
24
  # shape loses data identically for any chain longer than one link
23
25
  # (`a->b->c->a`), not just a two-element swap.
24
26
  #
25
- # **The fix this cop points at** — snapshot every old key's value
27
+ # ## The fix this cop points at
28
+ #
29
+ # Snapshot every old key's value
26
30
  # first, delete all old keys, then write all new keys, so no
27
31
  # rename in the pass ever reads a key this same pass has already
28
32
  # written:
@@ -32,7 +36,9 @@ module RuboCop
32
36
  # snapshot.each { |old_name, _new_name, _value| state.delete(old_name) }
33
37
  # snapshot.each { |_old_name, new_name, value| state[new_name] = value }
34
38
  #
35
- # **What this cop does not attempt** — the general checklist item is
39
+ # ## What this cop does not attempt
40
+ #
41
+ # The general checklist item is
36
42
  # broader than any AST pattern can safely automate ("any method
37
43
  # that both reads and writes the same collection across a loop"
38
44
  # covers plenty of correct code too — accumulating into a result
@@ -84,6 +90,10 @@ module RuboCop
84
90
  (send $_recv :[]= _new_key (send $_recv2 :delete _old_key))
85
91
  PATTERN
86
92
 
93
+ # Flags a `recv[new] = recv.delete(old)` write inside a loop, same receiver both sides.
94
+ #
95
+ # @param node [RuboCop::AST::SendNode] the `[]=` send node being visited
96
+ # @return [void]
87
97
  def on_send(node)
88
98
  rename_write?(node) do |recv, recv2|
89
99
  next unless same_receiver?(recv, recv2)
@@ -9,17 +9,21 @@ module RuboCop
9
9
  # shared, mutable state with no per-thread or mutex-guarded
10
10
  # isolation at all.
11
11
  #
12
+ # ## The bug this follows up on
13
+ #
12
14
  # This is the mechanical follow-up to a real bug already fixed here
13
15
  # (see `dispatcher.rb`'s own `#reenter` comment, and
14
- # `spec/runtime/dispatcher_spec.rb`): `@reaction_depth` used to be a
15
- # plain ivar on `Dispatcher`, so two threads' concurrent top-level
16
- # dispatches corrupted each other's view of "how deep into a
16
+ # `spec/runtime/dispatcher_spec.rb`): a plain `@reaction_depth` ivar on
17
+ # `Dispatcher` let two threads' concurrent top-level
18
+ # dispatches corrupt each other's view of "how deep into a
17
19
  # reaction cascade am I". The fix moved that one ivar to
18
20
  # `Thread.current[:hecks_reaction_depth]`. This cop exists so the
19
21
  # next plain ivar someone adds to either class gets flagged before
20
22
  # it becomes the next instance of the same bug, rather than after.
21
23
  #
22
- # **Scoped narrowly on purpose** — by class name
24
+ # ## Scoped narrowly on purpose
25
+ #
26
+ # By class name
23
27
  # (`Hecks::Runtime::Dispatcher`/`Hecks::Runtime::Registry`), not by
24
28
  # blanket-flagging every ivar mutation in the codebase. Most classes
25
29
  # in this codebase are not shared across threads (a fresh value
@@ -29,7 +33,9 @@ module RuboCop
29
33
  # same reasoning `.rubocop.yml`'s own header gives for every other
30
34
  # cop in this repo.
31
35
  #
32
- # What counts as "PLAIN": `@ivar = ...`, `@ivar ||= ...`, `@ivar +=
36
+ # ## What counts as "plain"
37
+ #
38
+ # `@ivar = ...`, `@ivar ||= ...`, `@ivar +=
33
39
  # ...`, `@ivar << ...`, `@ivar[k] = v`. `initialize` is exempt — an
34
40
  # ivar being set up for the first time, before any other thread can
35
41
  # possibly hold a reference to this object, is not the hazard (see
@@ -67,6 +73,10 @@ module RuboCop
67
73
 
68
74
  RESTRICT_ON_SEND = [:<<, :[]=].freeze
69
75
 
76
+ # Flags a plain `@ivar = ...` assignment inside a thread-shared class.
77
+ #
78
+ # @param node [RuboCop::AST::IvasgnNode] the ivar-assignment node being visited
79
+ # @return [void]
70
80
  def on_ivasgn(node)
71
81
  # A plain `@x = 1` parses as `(ivasgn :@x (int 1))` — two
72
82
  # children. The commissioner also visits the bare `(ivasgn :@x)`
@@ -79,6 +89,11 @@ module RuboCop
79
89
  check(node, node.children.first)
80
90
  end
81
91
 
92
+ # Flags a plain `@ivar += ...` (or similar) compound assignment inside a
93
+ # thread-shared class.
94
+ #
95
+ # @param node [RuboCop::AST::OpAsgnNode] the compound-assignment node being visited
96
+ # @return [void]
82
97
  def on_op_asgn(node)
83
98
  # `@x += 1` parses as `(op_asgn (ivasgn :@x) :+ (int 1))` — the
84
99
  # target is an `ivasgn` node carrying just the name (no value
@@ -89,10 +104,19 @@ module RuboCop
89
104
  check(node, ivar_node.children.first)
90
105
  end
91
106
 
107
+ # Flags a plain `@ivar ||= ...` assignment inside a thread-shared class.
108
+ #
109
+ # @param node [RuboCop::AST::OrAsgnNode] the `||=` assignment node being visited
110
+ # @return [void]
92
111
  def on_or_asgn(node)
93
112
  on_op_asgn(node)
94
113
  end
95
114
 
115
+ # Flags a plain `@ivar << ...` or `@ivar[k] = v` mutation inside a thread-shared
116
+ # class.
117
+ #
118
+ # @param node [RuboCop::AST::SendNode] the `<<` or `[]=` send node being visited
119
+ # @return [void]
96
120
  def on_send(node)
97
121
  return unless RESTRICT_ON_SEND.include?(node.method_name)
98
122
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hecks
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Young
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-19 00:00:00.000000000 Z
11
+ date: 2026-09-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prism
@@ -55,6 +55,8 @@ files:
55
55
  - lib/hecks/adapters/driven/identity_registry.adapter
56
56
  - lib/hecks/adapters/driven/identity_registry.rb
57
57
  - lib/hecks/adapters/driven/in_memory_ordering.rb
58
+ - lib/hecks/adapters/driven/in_process_key_vault.adapter
59
+ - lib/hecks/adapters/driven/in_process_key_vault.rb
58
60
  - lib/hecks/adapters/driven/lambda.adapter
59
61
  - lib/hecks/adapters/driven/lambda.rb
60
62
  - lib/hecks/adapters/driven/lambda/client.rb
@@ -83,6 +85,8 @@ files:
83
85
  - lib/hecks/adapters/driven/sqlite/schema_builder.rb
84
86
  - lib/hecks/adapters/driven/system_clock.adapter
85
87
  - lib/hecks/adapters/driven/system_clock.rb
88
+ - lib/hecks/adapters/driven/tenant_provisioner.adapter
89
+ - lib/hecks/adapters/driven/tenant_provisioner.rb
86
90
  - lib/hecks/adapters/driving/github_webhook.rb
87
91
  - lib/hecks/behaviors.rb
88
92
  - lib/hecks/behaviors/dsl.rb
@@ -184,14 +188,11 @@ files:
184
188
  - lib/hecks/bluebook/translation.rb
185
189
  - lib/hecks/bluebook/value_object.rb
186
190
  - lib/hecks/codemod.rb
187
- - lib/hecks/codemod/legacy_dispatch_args.rb
188
- - lib/hecks/codemod/legacy_dispatch_recorder.rb
189
191
  - lib/hecks/construct.rb
190
192
  - lib/hecks/corpus.rb
191
193
  - lib/hecks/deploy/bluebook/deploy.bluebook
192
194
  - lib/hecks/deploy/bluebook/deploy.hecksagon
193
195
  - lib/hecks/deploy/oidc.json
194
- - lib/hecks/deprecation.rb
195
196
  - lib/hecks/doc/reference.rb
196
197
  - lib/hecks/embryonaut_bluebook.rb
197
198
  - lib/hecks/facade.rb
@@ -226,6 +227,7 @@ files:
226
227
  - lib/hecks/framework/bluebook/framework.hecksagon
227
228
  - lib/hecks/framework/bluebook/governance.bluebook
228
229
  - lib/hecks/framework/bluebook/identity.bluebook
230
+ - lib/hecks/framework/bluebook/privacy.bluebook
229
231
  - lib/hecks/framework/oidc.json
230
232
  - lib/hecks/freezer.rb
231
233
  - lib/hecks/fuzzing.rb
@@ -320,6 +322,8 @@ files:
320
322
  - lib/hecks/ports/identity_generation.rb
321
323
  - lib/hecks/ports/identity_resolution.port
322
324
  - lib/hecks/ports/identity_resolution.rb
325
+ - lib/hecks/ports/key_vault.port
326
+ - lib/hecks/ports/key_vault.rb
323
327
  - lib/hecks/ports/loading.port
324
328
  - lib/hecks/ports/loading.rb
325
329
  - lib/hecks/ports/persistence.port
@@ -472,6 +476,8 @@ files:
472
476
  - lib/hecks/runtime/value/entity_list_coercion.rb
473
477
  - lib/hecks/runtime/value/invariant_violation.rb
474
478
  - lib/hecks/storehouse.rb
479
+ - lib/hecks/tenancy/bluebook/tenancy.bluebook
480
+ - lib/hecks/tenancy/bluebook/tenancy.hecksagon
475
481
  - lib/hecks/version.rb
476
482
  - lib/hecks/vocabulary.rb
477
483
  - lib/rubocop/cop/hecks/fallback_hash_lookup.rb