hecks 1.2.0 → 1.4.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.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +60 -2
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/postgres_era.adapter +5 -0
- data/lib/hecks/adapters/driven/prism.rb +4 -4
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +51 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -3
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +122 -86
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +301 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -20
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +361 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +124 -0
- data/lib/hecks/fuzzing/form_census.rb +199 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +226 -38
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
- data/lib/hecks/fuzzing/properties/guards.rb +129 -26
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
- data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +84 -39
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +224 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +676 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +39 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
- data/lib/hecks/fuzzing/value_generator.rb +66 -14
- data/lib/hecks/fuzzing.rb +11 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +365 -15
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +89 -21
- data/lib/hecks/ports/access_control.rb +58 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +160 -58
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +44 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +338 -83
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +29 -6
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +250 -0
- data/lib/hecks/projections/glossary/markdown.rb +105 -0
- data/lib/hecks/projections/glossary/mermaid.rb +110 -0
- data/lib/hecks/projections/glossary/page.css +271 -0
- data/lib/hecks/projections/glossary/page.js +72 -0
- data/lib/hecks/projections/glossary/sections.rb +17 -0
- data/lib/hecks/projections/glossary/sentences.rb +205 -0
- data/lib/hecks/projections/glossary.rb +214 -286
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +15 -22
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +132 -24
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +57 -13
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +64 -67
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +94 -19
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +282 -46
- data/lib/hecks/runtime/entity_interpreter.rb +160 -69
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +111 -56
- data/lib/hecks/runtime/reaction_invocation.rb +76 -9
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -112
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +56 -27
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -88
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +93 -56
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +379 -226
- data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
- data/lib/hecks/runtime/value.rb +28 -23
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +207 -5
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +43 -2
|
@@ -3,30 +3,30 @@ require_relative "../../query_specification/common/null_policy"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Ports
|
|
5
5
|
module Query
|
|
6
|
-
# What order an ask
|
|
6
|
+
# What order an ask answers in — the meaning of the ask, not a property
|
|
7
7
|
# of the store that happens to hold it. Declared here once so an adapter
|
|
8
8
|
# may satisfy it natively but never redefine it : SQLite pushes both
|
|
9
9
|
# tiers into SQL (NullPolicy.sql_order renders `field DIR, id DIR`),
|
|
10
10
|
# while Heki and Memory have no query engine and delegate straight back
|
|
11
11
|
# to InMemory.
|
|
12
12
|
#
|
|
13
|
-
# Two tiers, in this order : the
|
|
14
|
-
# then
|
|
13
|
+
# Two tiers, in this order : the declared order_by when there is one,
|
|
14
|
+
# then identity, always. The identity tier is what makes an ask total.
|
|
15
15
|
# Without it, an ask with no order_by — or a declared order with tied
|
|
16
16
|
# keys — hands back whatever order the store happened to hold, and
|
|
17
17
|
# store order was quietly standing in for a rule while every
|
|
18
18
|
# hand-written query in the corpus stayed green : not one of them
|
|
19
19
|
# had a tie for store order to decide.
|
|
20
20
|
#
|
|
21
|
-
# An adapter that pushes ordering down
|
|
21
|
+
# An adapter that pushes ordering down must push limit down with it.
|
|
22
22
|
# Re-ordering a page the store already cut would be a top-N of the
|
|
23
23
|
# wrong N — the one way this can be got quietly, expensively wrong.
|
|
24
24
|
module Ordering
|
|
25
25
|
module_function
|
|
26
26
|
|
|
27
27
|
def apply(rows, order_by, null_semantics = nil, identity:, &value_of)
|
|
28
|
-
#
|
|
29
|
-
# otherwise swap arbitrarily, and a tier meant to
|
|
28
|
+
# Stable, because sort_by is not : two rows whose identity ties would
|
|
29
|
+
# otherwise swap arbitrarily, and a tier meant to remove store-dependence
|
|
30
30
|
# would be adding a coin flip of its own.
|
|
31
31
|
rows = rows.each_with_index.sort_by { |row, index| [identity.call(row), index] }.map(&:first)
|
|
32
32
|
return rows unless order_by
|
data/lib/hecks/ports/query.rb
CHANGED
|
@@ -6,10 +6,34 @@ module Hecks
|
|
|
6
6
|
# fallback engine for stores with no query engine of their own) are
|
|
7
7
|
# its collaborators — query/ordering.rb and query/in_memory.rb.
|
|
8
8
|
module Query
|
|
9
|
+
# A declared query the chosen adapter cannot honour as written — refused
|
|
10
|
+
# by `validate!` before the adapter runs it.
|
|
9
11
|
class Unsupported < StandardError; end
|
|
10
12
|
|
|
11
13
|
module_function
|
|
12
14
|
|
|
15
|
+
# Runs a declared query through the adapter's own `query` hook, if it has one.
|
|
16
|
+
#
|
|
17
|
+
# A nil answer means "no native engine here", and the caller falls back to the shared
|
|
18
|
+
# interpreter or to `InMemory.execute`; it never means "no rows", which is `[]`.
|
|
19
|
+
#
|
|
20
|
+
# @param repository [Persistence::AppendOnly, Object] the repository whose `adapter` is
|
|
21
|
+
# asked, or a bare persistence adapter (anything that does not answer `adapter`)
|
|
22
|
+
# @param specification [QuerySpecification::Common::Options] the declared query: a
|
|
23
|
+
# `Bluebook::Query`, a read-model specification, or a delegator wrapping one
|
|
24
|
+
# @param args [Hash{Symbol => Object}] the caller's arguments, which a where-clause,
|
|
25
|
+
# limit or offset written as a Symbol reads its value from
|
|
26
|
+
# @param context [Hash{Symbol => Object}] passed through to the adapter: `domain:`,
|
|
27
|
+
# `aggregate:` and, where a comparator needs one, `registry:`
|
|
28
|
+
# @return [Array<Runtime::Instance>, nil] the matching records, filtered, ordered and
|
|
29
|
+
# paged by the adapter (`[]` when none match); nil if the adapter has no `query`
|
|
30
|
+
# method
|
|
31
|
+
# @raise [Ports::Query::Unsupported] if the specification combines cursor and offset
|
|
32
|
+
# pagination, or asks for inspection the adapter cannot provide
|
|
33
|
+
# @raise [Runtime::WiringError] if a where-clause names an operation no comparator
|
|
34
|
+
# handles (raised by the in-memory engine behind `Memory`, `Heki` and the like)
|
|
35
|
+
# @raise [ArgumentError] if a SQL-backed adapter cannot compile a where-clause's
|
|
36
|
+
# operator
|
|
13
37
|
def execute(repository, specification, args = {}, context: {})
|
|
14
38
|
adapter = repository.respond_to?(:adapter) ? repository.adapter : repository
|
|
15
39
|
return nil unless adapter.respond_to?(:query)
|
|
@@ -18,6 +42,17 @@ module Hecks
|
|
|
18
42
|
adapter.query(specification, args, context: context)
|
|
19
43
|
end
|
|
20
44
|
|
|
45
|
+
# Refuses a specification the adapter about to run it cannot honour.
|
|
46
|
+
#
|
|
47
|
+
# Inspection is honoured by an adapter with an `inspect_query` method, or, for the
|
|
48
|
+
# `"sql"` mode alone, by any adapter with a native `query`.
|
|
49
|
+
#
|
|
50
|
+
# @param specification [QuerySpecification::Common::Options] the declared query to check
|
|
51
|
+
# @param adapter [Object] the persistence adapter that would run it; only what it
|
|
52
|
+
# responds to (`inspect_query`, `query`) is read
|
|
53
|
+
# @return [void]
|
|
54
|
+
# @raise [Ports::Query::Unsupported] if the specification declares both a cursor and
|
|
55
|
+
# an offset, or asks for inspection the adapter cannot provide
|
|
21
56
|
def validate!(specification, adapter)
|
|
22
57
|
raise Unsupported, "a query cannot combine cursor and offset pagination" if specification.cursor && specification.offset
|
|
23
58
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
require_relative "../projector"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
# **The bootstrap-window fallbacks, projected** — lib/hecks/bluebook/dsl/
|
|
6
|
+
# bootstrap_table.rb rendered from the chapter's own Keyword rows.
|
|
7
|
+
#
|
|
8
|
+
# Projector.call(:bootstrap_table, bluebook: <the Bluebook chapter>)
|
|
9
|
+
#
|
|
10
|
+
# While `MetaValidator.bootstrapping?` the grammar table does not exist
|
|
11
|
+
# yet, so `WordGate#method_missing` and `RuleReference#lookup` cannot
|
|
12
|
+
# read `calls:`/`resolves_via:`/`disambiguator:` off it. They used to
|
|
13
|
+
# read two hand-kept Hashes instead, each "kept in sync by hand" with
|
|
14
|
+
# those columns. Kept in sync by hand meant a subset: 48 of the 87 live
|
|
15
|
+
# `calls:` rows, chosen word by word by grepping which ones the core
|
|
16
|
+
# chapters happened to use during bootstrap. A partition, not a filter:
|
|
17
|
+
# every live row now lands in the table, and the ones no bootstrap
|
|
18
|
+
# chapter calls cost nothing — a builder with its own `def` never
|
|
19
|
+
# reaches `method_missing` at all.
|
|
20
|
+
#
|
|
21
|
+
# The table cannot be built at boot for the same reason it exists: it
|
|
22
|
+
# is read before the grammar it comes from has been assembled. So it is
|
|
23
|
+
# committed, like lib/hecks/vocabulary.rb, and spec/bootstrap_table_
|
|
24
|
+
# spec.rb re-projects it in memory and refuses a diff.
|
|
25
|
+
module BootstrapTable
|
|
26
|
+
extend Projector::Target
|
|
27
|
+
|
|
28
|
+
projects_as :bootstrap_table, declares: "Syntax"
|
|
29
|
+
|
|
30
|
+
class Conflict < StandardError; end
|
|
31
|
+
|
|
32
|
+
HEADER = <<~RUBY.freeze
|
|
33
|
+
# Generated — projected from the language's own Keyword rows (the
|
|
34
|
+
# `calls:`, `resolves_via:` and `disambiguator:` columns of every
|
|
35
|
+
# KeywordSeed under lib/hecks/language/).
|
|
36
|
+
#
|
|
37
|
+
# Do not edit. spec/bootstrap_table_spec.rb re-projects this in memory
|
|
38
|
+
# and refuses a diff — run bin/project_bootstrap_table instead.
|
|
39
|
+
#
|
|
40
|
+
# Plain data, no requires: this is read while the grammar table it was
|
|
41
|
+
# projected from is still being built (`MetaValidator.bootstrapping?`).
|
|
42
|
+
RUBY
|
|
43
|
+
|
|
44
|
+
module_function
|
|
45
|
+
|
|
46
|
+
def call(bluebook:, options: {}) = render(bluebook)
|
|
47
|
+
|
|
48
|
+
# Retired rows are out of the language; admitted and deprecated rows
|
|
49
|
+
# still dispatch — the same `status != "retired"` reading
|
|
50
|
+
# `GenericDispatch.shape_for` gives the live table.
|
|
51
|
+
def live_keywords
|
|
52
|
+
Bluebook::MetaValidator::SyntaxBoot.call[:keywords].reject { |row| row[:status] == "retired" }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# `[context, word] => :method` — WordGate's own key order.
|
|
56
|
+
#
|
|
57
|
+
# An overloaded word has one row per argument shape, and they all
|
|
58
|
+
# name the same method. Two that don't would make the table keep
|
|
59
|
+
# whichever came last, so that is refused instead.
|
|
60
|
+
def calls(rows = live_keywords)
|
|
61
|
+
rows.reject { |row| row[:calls].to_s.empty? }
|
|
62
|
+
.group_by { |row| [row[:context], row[:word]] }
|
|
63
|
+
.to_h do |key, same_word|
|
|
64
|
+
targets = same_word.map { |row| row[:calls] }.uniq
|
|
65
|
+
raise Conflict, "#{key.inspect} names more than one method: #{targets.join(', ')}" if targets.size > 1
|
|
66
|
+
|
|
67
|
+
[key, targets.first.to_sym]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# `[word, context] => { resolves_via:, disambiguator: }` —
|
|
72
|
+
# RuleReference's own key order, blank columns omitted, the same
|
|
73
|
+
# shape its live `lookup` answers.
|
|
74
|
+
def resolves(rows = live_keywords)
|
|
75
|
+
rows.reject { |row| row[:resolves_via].to_s.empty? }.to_h do |row|
|
|
76
|
+
rule = { resolves_via: row[:resolves_via], disambiguator: row[:disambiguator] }
|
|
77
|
+
[[row[:word], row[:context]], rule.reject { |_, value| value.to_s.empty? }]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def render(_bluebook)
|
|
82
|
+
rows = live_keywords
|
|
83
|
+
calls_lines = calls(rows).map { |key, target| " #{key.inspect} => #{target.inspect}" }
|
|
84
|
+
resolves_lines = resolves(rows).map do |key, rule|
|
|
85
|
+
fields = rule.map { |name, value| "#{name}: #{value.inspect}" }.join(", ")
|
|
86
|
+
" #{key.inspect} => { #{fields} }.freeze"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
<<~RUBY
|
|
90
|
+
#{HEADER}
|
|
91
|
+
module Hecks
|
|
92
|
+
module Bluebook
|
|
93
|
+
module DSL
|
|
94
|
+
module BootstrapTable
|
|
95
|
+
# [context, word] => the method a word's whole call forwards to.
|
|
96
|
+
CALLS = {
|
|
97
|
+
#{calls_lines.join(",\n")}
|
|
98
|
+
}.freeze
|
|
99
|
+
|
|
100
|
+
# [word, context] => the RuleReference primitive a bare reference resolves through.
|
|
101
|
+
RESOLVES = {
|
|
102
|
+
#{resolves_lines.join(",\n")}
|
|
103
|
+
}.freeze
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
RUBY
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -2,30 +2,30 @@ require_relative "../projector"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Projections
|
|
5
|
-
# A
|
|
5
|
+
# A domain's own shape, projected as MERMAID diagrams — the same
|
|
6
6
|
# trick `Projections::Reference`/`DocsProjector` already play for
|
|
7
|
-
# prose, one level further: a diagram generated
|
|
7
|
+
# prose, one level further: a diagram generated from the
|
|
8
8
|
# declaration can't drift from it the way a hand-drawn one
|
|
9
9
|
# inevitably does, because there is no second copy to forget to
|
|
10
10
|
# update.
|
|
11
11
|
#
|
|
12
|
-
# MERMAID,
|
|
12
|
+
# MERMAID, not Graphviz (the two considered) — every diagram type
|
|
13
13
|
# below has a Mermaid form purpose-built for exactly what the
|
|
14
|
-
# underlying construct already is (a `lifecycle`
|
|
14
|
+
# underlying construct already is (a `lifecycle` is a state
|
|
15
15
|
# machine, `has_many`/`belongs_to` already speaks in cardinality,
|
|
16
|
-
# `emits`/`trigger` already
|
|
16
|
+
# `emits`/`trigger` already is a directed graph), and the output is
|
|
17
17
|
# plain text that renders natively wherever this project's own docs
|
|
18
18
|
# already live — GitHub markdown, this repo's generated docs, Claude
|
|
19
19
|
# Artifacts — with no build step and no external binary. Graphviz's
|
|
20
|
-
#
|
|
20
|
+
# dot format needs an actual render step (a `dot` binary, or a WASM
|
|
21
21
|
# port) to become anything viewable, which is a real dependency this
|
|
22
22
|
# repository's own discipline (see rust/parser's Cargo.toml: "no
|
|
23
23
|
# dependency earns its way past std") would rather not take just to
|
|
24
24
|
# draw a diagram.
|
|
25
25
|
#
|
|
26
|
-
#
|
|
26
|
+
# Four diagram kinds, one file each per domain except lifecycles
|
|
27
27
|
# (one per lifecycle-bearing construct, since that's how a reader
|
|
28
|
-
# actually reaches for it — looking at
|
|
28
|
+
# actually reaches for it — looking at one aggregate's states, not
|
|
29
29
|
# every aggregate's at once):
|
|
30
30
|
#
|
|
31
31
|
# <Name>_lifecycle.mmd stateDiagram-v2 one per lifecycle
|
|
@@ -47,27 +47,27 @@ module Hecks
|
|
|
47
47
|
# <Name>_surface.mmd flowchart one per aggregate/entity that
|
|
48
48
|
# declares at least one command
|
|
49
49
|
# or query — everything you can
|
|
50
|
-
#
|
|
51
|
-
#
|
|
50
|
+
# do to it and ask about it,
|
|
51
|
+
# and what each command writes,
|
|
52
52
|
# in one place
|
|
53
53
|
# <Name>_saga.mmd stateDiagram-v2 one per process_manager —
|
|
54
54
|
# its own states, and what
|
|
55
55
|
# each transition dispatches
|
|
56
56
|
# elsewhere in the domain
|
|
57
|
-
# frameworks.mmd flowchart every
|
|
57
|
+
# frameworks.mmd flowchart every other domain this one
|
|
58
58
|
# depends on — a shared
|
|
59
59
|
# framework it `uses_framework`,
|
|
60
60
|
# or a domain a policy reaches
|
|
61
61
|
# `across` — the one diagram
|
|
62
|
-
# here that looks
|
|
62
|
+
# here that looks outward past
|
|
63
63
|
# this domain's own boundary
|
|
64
64
|
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
65
|
+
# Construct names (aggregate/entity/command/event) are used bare,
|
|
66
|
+
# unsanitized, as Mermaid node/entity ids — safe because this
|
|
67
67
|
# language's own word grammar only ever admits simple CamelCase/
|
|
68
68
|
# snake_case identifiers there (confirmed: no space or punctuation
|
|
69
69
|
# appears in any real aggregate/command/event name across the corpus
|
|
70
|
-
# this projects from). A `role:`
|
|
70
|
+
# this projects from). A `role:` string is free text, though — the
|
|
71
71
|
# real corpus already has "Back office"/"Vault officer"/"Branch
|
|
72
72
|
# clerk" — so `roles.mmd` is the one diagram here that sanitizes a
|
|
73
73
|
# name into an id (`role_id`) while keeping the real string as the
|
|
@@ -125,8 +125,8 @@ module Hecks
|
|
|
125
125
|
|
|
126
126
|
# ── shared ────────────────────────────────────────────────────────
|
|
127
127
|
|
|
128
|
-
#
|
|
129
|
-
#
|
|
128
|
+
# An entity can carry its own lifecycle, relationship, or command
|
|
129
|
+
# too — its own `lifecycle`/`reference_to`/`command` block,
|
|
130
130
|
# addressed through its holding aggregate the same way
|
|
131
131
|
# `DocsProjector` already treats an aggregate and its entities
|
|
132
132
|
# alike. Walking both here means a domain's entity gaining any of
|
|
@@ -137,7 +137,7 @@ module Hecks
|
|
|
137
137
|
|
|
138
138
|
def holders_with_lifecycle(bluebook) = holders(bluebook).select(&:lifecycle)
|
|
139
139
|
|
|
140
|
-
# `chapter_name`
|
|
140
|
+
# `chapter_name` drives the re-run hint always — that's the one
|
|
141
141
|
# argument `bin/project_diagrams` actually takes, regardless of
|
|
142
142
|
# which single aggregate/entity `subject` happens to name. Passing
|
|
143
143
|
# the wrong one here once already produced a real, committed
|
|
@@ -169,12 +169,12 @@ module Hecks
|
|
|
169
169
|
|
|
170
170
|
# ── relationships -> erDiagram ───────────────────────────────────
|
|
171
171
|
|
|
172
|
-
#
|
|
172
|
+
# Standard crow's-foot reading, the same convention every ORM's own
|
|
173
173
|
# ERD generator (Rails' erd gem included) already uses:
|
|
174
|
-
# `has_many`/`has_one` are read from the
|
|
174
|
+
# `has_many`/`has_one` are read from the owning side — one Holder
|
|
175
175
|
# relates to many/one Target. `belongs_to`/`reference_to` are read
|
|
176
|
-
# from the
|
|
177
|
-
#
|
|
176
|
+
# from the target's side instead — one Target can be pointed at by
|
|
177
|
+
# many Holders — because a bare reference carries no promise about
|
|
178
178
|
# how many holders point back at it; "many" is the honest default
|
|
179
179
|
# absent a declared uniqueness rule this language doesn't expose.
|
|
180
180
|
# `optional?` only ever softens the side that can genuinely be
|
|
@@ -205,8 +205,8 @@ module Hecks
|
|
|
205
205
|
|
|
206
206
|
# ── dispatch -> flowchart ─────────────────────────────────────────
|
|
207
207
|
|
|
208
|
-
# A
|
|
209
|
-
#
|
|
208
|
+
# A command node, stadium-shaped (`(["..."])`); an event node,
|
|
209
|
+
# hexagonal (`{{"..."}}`) — one visual vocabulary for "a thing
|
|
210
210
|
# someone does" versus "a fact that happened", matching the
|
|
211
211
|
# language's own verb/event distinction. Command ids are qualified
|
|
212
212
|
# by their owning aggregate (`cmd_Order_Purchase`) since two
|
|
@@ -235,13 +235,13 @@ module Hecks
|
|
|
235
235
|
%( #{command_node(holder.hecks_name, command.hecks_name)} -->|emits| #{event_node(event)})
|
|
236
236
|
end
|
|
237
237
|
|
|
238
|
-
# `on_event`
|
|
239
|
-
# (`"Account.AccountFrozen"`)
|
|
238
|
+
# `on_event` is sometimes aggregate-qualified
|
|
239
|
+
# (`"Account.AccountFrozen"`) and sometimes bare
|
|
240
240
|
# (`"CustomerSuspended"`) in the real corpus — `emits` never is,
|
|
241
241
|
# so this always matches against the bare tail, the same
|
|
242
242
|
# normalization a reader has to do by eye today.
|
|
243
243
|
#
|
|
244
|
-
# A
|
|
244
|
+
# A trigger crossing into another domain (`policy.target_domain`)
|
|
245
245
|
# still draws — the target command just has no incoming `emits`
|
|
246
246
|
# edge of its own here, which honestly shows "dispatch continues
|
|
247
247
|
# elsewhere" rather than silently dropping the edge. The label
|
|
@@ -261,7 +261,7 @@ module Hecks
|
|
|
261
261
|
|
|
262
262
|
# ── roles -> flowchart ────────────────────────────────────────────
|
|
263
263
|
|
|
264
|
-
#
|
|
264
|
+
# Who issues what, across the whole domain — data no existing
|
|
265
265
|
# projection draws at all today (the reference pages' own
|
|
266
266
|
# `command_entry` only ever prints a command's role as a single
|
|
267
267
|
# line of prose, never assembled across commands). A command with
|
|
@@ -284,7 +284,7 @@ module Hecks
|
|
|
284
284
|
|
|
285
285
|
def role_node(role_name) = %(#{role_id(role_name)}((#{role_name})))
|
|
286
286
|
|
|
287
|
-
# A
|
|
287
|
+
# A role name is free text ("Back office", "Vault officer") —
|
|
288
288
|
# unlike every other name this file uses as a bare id, this one
|
|
289
289
|
# has to be sanitized to become a legal Mermaid identifier. The
|
|
290
290
|
# real string still appears as the node's own label
|
|
@@ -293,7 +293,7 @@ module Hecks
|
|
|
293
293
|
|
|
294
294
|
# ── ports -> flowchart ───────────────────────────────────────────
|
|
295
295
|
|
|
296
|
-
# A
|
|
296
|
+
# A port operation is a boundary translation, not a verb or a fact —
|
|
297
297
|
# its own reference page says so plainly ("the builder behind it
|
|
298
298
|
# defines no `given` or `sets`, so an operation cannot read
|
|
299
299
|
# aggregate state or mutate a record itself"), so it gets a third
|
|
@@ -302,7 +302,7 @@ module Hecks
|
|
|
302
302
|
# state landing somewhere, the same reason a data store gets one
|
|
303
303
|
# in an ordinary flowchart.
|
|
304
304
|
#
|
|
305
|
-
#
|
|
305
|
+
# Two edge kinds per operation: a dotted "exposes" edge from the
|
|
306
306
|
# aggregate the port hangs off (always present — a port always
|
|
307
307
|
# belongs to exactly one aggregate), and a solid "to:" edge to
|
|
308
308
|
# whichever aggregate the operation itself names as its receiver
|
|
@@ -311,8 +311,8 @@ module Hecks
|
|
|
311
311
|
# `emits` reuses `dispatch.mmd`'s own `event_node` unchanged — the
|
|
312
312
|
# same fact, reached from a different direction.
|
|
313
313
|
#
|
|
314
|
-
# `bluebook.aggregates`,
|
|
315
|
-
# lifecycle/relationship/command, a port belongs to an
|
|
314
|
+
# `bluebook.aggregates`, not the shared `holders` — unlike a
|
|
315
|
+
# lifecycle/relationship/command, a port belongs to an aggregate
|
|
316
316
|
# only; an entity has no `ports` method at all (confirmed: calling
|
|
317
317
|
# it raises, it isn't just always empty), so walking entities here
|
|
318
318
|
# the way every other diagram in this file does would crash on
|
|
@@ -343,17 +343,17 @@ module Hecks
|
|
|
343
343
|
|
|
344
344
|
# ── read models -> flowchart ─────────────────────────────────────
|
|
345
345
|
|
|
346
|
-
#
|
|
347
|
-
# shows how aggregates reference each other for
|
|
346
|
+
# The read-side complement to `relationships.mmd` — that diagram
|
|
347
|
+
# shows how aggregates reference each other for writes
|
|
348
348
|
# (`has_many`/`belongs_to`/`reference_to`); this shows how a
|
|
349
|
-
# `read_model`
|
|
349
|
+
# `read_model` assembles data for reads, from
|
|
350
350
|
# `aggregate_heads` — the same list `where`/`group_by`/`order_by`
|
|
351
351
|
# all operate over, and the one fact every read_model has
|
|
352
352
|
# regardless of whether it's rooted (`reference_target`) or
|
|
353
353
|
# gathers heads with no root at all (a rootless read model, real
|
|
354
354
|
# in the corpus: `AccountsByKind`).
|
|
355
355
|
#
|
|
356
|
-
# A
|
|
356
|
+
# A read model is a subroutine shape (`[[...]]`, "a predefined
|
|
357
357
|
# process") — a fourth shape, beside `ports.mmd`'s trapezoid and
|
|
358
358
|
# `dispatch.mmd`'s stadium/hexagon: not a verb, not a fact, not a
|
|
359
359
|
# boundary translation, but a standing, reusable view. Every
|
|
@@ -363,7 +363,7 @@ module Hecks
|
|
|
363
363
|
# (real in banking: `Account` feeds four) merges into one node
|
|
364
364
|
# across the whole diagram.
|
|
365
365
|
#
|
|
366
|
-
#
|
|
366
|
+
# The label names the shape of the answer, not just the name —
|
|
367
367
|
# `(count)`/`(median: field)` for the two real aggregations in the
|
|
368
368
|
# corpus, nothing appended for an ordinary row-returning
|
|
369
369
|
# read_model. Still MVP scope: `where`/`group_by`/`order_by`
|
|
@@ -381,12 +381,12 @@ module Hecks
|
|
|
381
381
|
shape = read_model.to_h
|
|
382
382
|
node = %(rm_#{shape[:name]}[["#{read_model_label(shape)}"]])
|
|
383
383
|
Array(shape[:aggregate_heads]).map do |head|
|
|
384
|
-
#
|
|
384
|
+
# **Quoted, not bare** — an edge label containing `[` or `]`
|
|
385
385
|
# (`accounts[]`, marking the "many" side) breaks Mermaid's own
|
|
386
386
|
# `|label|` parser outright if left unquoted: it reads the
|
|
387
|
-
# `[` as the
|
|
387
|
+
# `[` as the start of a new node shape mid-label, not text.
|
|
388
388
|
# Confirmed live against the real parser before this quoting
|
|
389
|
-
# existed — every
|
|
389
|
+
# existed — every other edge label in this file happens to be
|
|
390
390
|
# a bare word or already-quoted string, so this is the one
|
|
391
391
|
# spot that needed it.
|
|
392
392
|
label = head[:many] ? "#{head[:as]}[]" : head[:as]
|
|
@@ -403,49 +403,49 @@ module Hecks
|
|
|
403
403
|
|
|
404
404
|
# ── surface -> flowchart ─────────────────────────────────────────
|
|
405
405
|
|
|
406
|
-
# "
|
|
406
|
+
# "What can I do to this, what can I ask about it" — one file per
|
|
407
407
|
# holder, unlike every other diagram here: `dispatch.mmd` already
|
|
408
408
|
# shows a command's own onward reaction chain, but never an
|
|
409
|
-
# aggregate's own
|
|
409
|
+
# aggregate's own full command/query menu in one place, and
|
|
410
410
|
# `roles.mmd` shows who issues a command without saying what else
|
|
411
411
|
# that same aggregate answers. This is the one diagram meant to
|
|
412
412
|
# be read starting from the aggregate, not from a verb or a fact.
|
|
413
413
|
#
|
|
414
|
-
# A
|
|
414
|
+
# A query is a diamond — a fifth shape, beside `dispatch.mmd`'s
|
|
415
415
|
# stadium/hexagon, `ports.mmd`'s trapezoid, and `read_models.mmd`'s
|
|
416
416
|
# subroutine: a question with an answer, not a verb that changes
|
|
417
417
|
# anything. Command edges are solid ("does"); query edges are
|
|
418
418
|
# dotted ("asks") — the same solid/dotted split `ports.mmd`
|
|
419
419
|
# already uses for "routes to:" versus "exposes".
|
|
420
420
|
#
|
|
421
|
-
# A
|
|
421
|
+
# A write target is a plain rectangle — a sixth shape, the first
|
|
422
422
|
# here with no special bracket at all: an attribute is the
|
|
423
423
|
# smallest, most passive thing this vocabulary names, a single
|
|
424
|
-
# field living
|
|
424
|
+
# field living inside the cylinder rather than a bounded thing of
|
|
425
425
|
# its own. `command.mutations` (`sets`/`increment`/`decrement`/
|
|
426
426
|
# `append`) was invisible everywhere before this — not just in a
|
|
427
|
-
# diagram, in
|
|
427
|
+
# diagram, in any projection, including the prose ones — despite
|
|
428
428
|
# being the single densest fact in the whole IR (53 real
|
|
429
429
|
# mutations across pizzas + banking). `dispatch.mmd` draws what a
|
|
430
|
-
# command
|
|
430
|
+
# command emits; this draws what it writes, the other half of
|
|
431
431
|
# "what actually happens" a command never showed before.
|
|
432
432
|
#
|
|
433
|
-
#
|
|
433
|
+
# The same attribute node merges across commands — real in
|
|
434
434
|
# banking: `Account.Credit` and `Account.Debit` both point at the
|
|
435
435
|
# same `balance` node, the same "one node, several incoming
|
|
436
436
|
# edges" merge `read_models.mmd` already does for an aggregate
|
|
437
437
|
# fed by several read_models.
|
|
438
438
|
#
|
|
439
|
-
#
|
|
439
|
+
# The label names the real source, not just the verb — an
|
|
440
440
|
# increment/decrement/set almost always takes its value from an
|
|
441
|
-
# argument, but not always the
|
|
441
|
+
# argument, but not always the same-named one: real in banking,
|
|
442
442
|
# `Account.Credit`'s own `balance` is incremented by its
|
|
443
443
|
# `amount` argument, and `LedgerEntry.Amend`'s own `amount` is
|
|
444
444
|
# incremented by its `adjustment` argument. A literal source
|
|
445
445
|
# (pizzas' own `Order.Purchase` sets `status` to the literal
|
|
446
446
|
# `"sold"`, not an argument at all) is named as verbatim as
|
|
447
447
|
# every other fact in this file. `append`'s own fields carry no
|
|
448
|
-
# single source at all — its own field
|
|
448
|
+
# single source at all — its own field names are the fact worth
|
|
449
449
|
# stating (real: `Order.AddTopping` appends `name, amount`).
|
|
450
450
|
def surface_diagram(bluebook, holder)
|
|
451
451
|
lines = holder.commands.map do |command|
|
|
@@ -477,9 +477,9 @@ module Hecks
|
|
|
477
477
|
|
|
478
478
|
def mutation_label(shape)
|
|
479
479
|
verb = "#{shape[:op]}s"
|
|
480
|
-
# `fields:` (not `source:`)
|
|
480
|
+
# `fields:` (not `source:`) is the multi-binding shape
|
|
481
481
|
# (`Mutation#to_h`'s own `[:append, :delegate, :corrects]`
|
|
482
|
-
# branch) — checked by the
|
|
482
|
+
# branch) — checked by the key's presence, not by re-listing
|
|
483
483
|
# which ops use it a second time here, the same lesson
|
|
484
484
|
# `Change.op`'s own `admits: Vocabulary::MutationOp` already
|
|
485
485
|
# drew (command.bluebook's own comment): a second list of "the
|
|
@@ -492,7 +492,7 @@ module Hecks
|
|
|
492
492
|
"#{verb}: #{detail}"
|
|
493
493
|
end
|
|
494
494
|
|
|
495
|
-
# A
|
|
495
|
+
# A literal value can contain a double quote of its own — real in
|
|
496
496
|
# banking: `Customer.Reinstate` sets `standing` to a rendered
|
|
497
497
|
# value-object literal, `{:value=>"good"}`, whose own embedded `"`
|
|
498
498
|
# broke this label's outer `|"..."|` quoting outright (caught by
|
|
@@ -515,19 +515,19 @@ module Hecks
|
|
|
515
515
|
|
|
516
516
|
# ── sagas -> stateDiagram-v2 ─────────────────────────────────────
|
|
517
517
|
|
|
518
|
-
# A
|
|
518
|
+
# A saga has a lifecycle too — the same `stateDiagram-v2` shape
|
|
519
519
|
# `lifecycle_diagram` already draws, one file per process_manager
|
|
520
520
|
# the same way lifecycle is one file per lifecycle-bearing holder.
|
|
521
521
|
# What's different is the label: a lifecycle's own edge is labeled
|
|
522
|
-
# by the
|
|
523
|
-
# something was
|
|
524
|
-
# that causes it (a saga advances because something
|
|
522
|
+
# by the command that causes it (an aggregate transitions because
|
|
523
|
+
# something was done to it); a saga's edge is labeled by the event
|
|
524
|
+
# that causes it (a saga advances because something happened,
|
|
525
525
|
# possibly nowhere near the saga itself) — the same command/event
|
|
526
526
|
# split `dispatch.mmd`'s own stadium/hexagon vocabulary already
|
|
527
527
|
# draws, here spent on which noun labels a stateDiagram-v2 edge
|
|
528
528
|
# instead.
|
|
529
529
|
#
|
|
530
|
-
#
|
|
530
|
+
# The label also names what the transition dispatches — a fact no
|
|
531
531
|
# existing diagram states for a saga at all: a lifecycle's own
|
|
532
532
|
# edge only ever names the one command that caused it; a saga's
|
|
533
533
|
# edge can fire several commands at once (real in banking:
|
|
@@ -538,7 +538,7 @@ module Hecks
|
|
|
538
538
|
# fires lands inside its own bluebook chapter, so this never needs
|
|
539
539
|
# `dispatch.mmd`'s own "triggers in X" cross-domain label.
|
|
540
540
|
#
|
|
541
|
-
#
|
|
541
|
+
# The compensating leg reads like any other — its own trigger is
|
|
542
542
|
# the literal string "refused" (`ProcessManager::REFUSED`, this
|
|
543
543
|
# language's own Trigger vocabulary), not invented text: a
|
|
544
544
|
# dispatch declined is exactly as real a cause of a state
|
|
@@ -556,12 +556,12 @@ module Hecks
|
|
|
556
556
|
MERMAID
|
|
557
557
|
end
|
|
558
558
|
|
|
559
|
-
#
|
|
559
|
+
# The REFUSED edge's own dispatch list is partly derived now —
|
|
560
560
|
# per-dispatch saga compensation (`compensates`) moved a saga's own
|
|
561
|
-
# compensating dispatches
|
|
561
|
+
# compensating dispatches off the hand-written `on :refused` leg
|
|
562
562
|
# and onto whichever forward dispatch each one undoes, so
|
|
563
|
-
# `handler.dispatches` alone would render an
|
|
564
|
-
# edge for any saga using it — accurate to the
|
|
563
|
+
# `handler.dispatches` alone would render an empty compensating
|
|
564
|
+
# edge for any saga using it — accurate to the declaration, wrong
|
|
565
565
|
# about what the runtime actually does at refusal (it derives and
|
|
566
566
|
# fires every declared `compensates`, newest first). `saga` is
|
|
567
567
|
# passed through for exactly this — only the REFUSED handler needs
|
|
@@ -569,7 +569,7 @@ module Hecks
|
|
|
569
569
|
# everything real about it.
|
|
570
570
|
def saga_edge(handler, saga)
|
|
571
571
|
label = handler.event_type
|
|
572
|
-
#
|
|
572
|
+
# Derived first, then the hand-written body — the same order
|
|
573
573
|
# `SagaInterpreter#unwind` actually runs them in (every
|
|
574
574
|
# completed leg's own `compensates` before this leg's own
|
|
575
575
|
# hand-written dispatches), not declaration order on the page.
|
|
@@ -591,27 +591,27 @@ module Hecks
|
|
|
591
591
|
|
|
592
592
|
# ── frameworks -> flowchart ─────────────────────────────────────
|
|
593
593
|
|
|
594
|
-
#
|
|
595
|
-
#
|
|
594
|
+
# Every other diagram in this file stays inside one domain's own
|
|
595
|
+
# boundary — this is the one that steps outside it. A real domain
|
|
596
596
|
# depends on another domain's own aggregates in exactly two ways:
|
|
597
597
|
# `uses_framework "X"` in its `.hecksagon` (`Hecksagon#framework_
|
|
598
|
-
# members`), which loads X's whole bluebook into
|
|
598
|
+
# members`), which loads X's whole bluebook into this registry,
|
|
599
599
|
# unconditionally, the moment this domain boots; or a policy's own
|
|
600
600
|
# `across "X"` (`Policy#target_domain`), which only reaches X when
|
|
601
601
|
# the policy's declared event actually fires. Same underlying
|
|
602
602
|
# fact `dispatch.mmd`'s own `trigger_edge` already draws from the
|
|
603
603
|
# command's side ("triggers in X") — this draws it again from the
|
|
604
|
-
#
|
|
604
|
+
# domain's side, next to the structural `uses_framework` fact
|
|
605
605
|
# `dispatch.mmd` never sees at all (that lives in the `.hecksagon`,
|
|
606
606
|
# which no other diagram here is handed).
|
|
607
607
|
#
|
|
608
|
-
#
|
|
609
|
-
# — a whole domain is drawn as
|
|
608
|
+
# Neither this domain nor each dependency gets the holders() treatment
|
|
609
|
+
# — a whole domain is drawn as one cylinder, the same "a bounded,
|
|
610
610
|
# addressable thing" shape every other diagram here already spends
|
|
611
611
|
# on a single aggregate, just scaled up one level: a domain is a
|
|
612
612
|
# bigger box the same kind of box lives inside.
|
|
613
613
|
#
|
|
614
|
-
#
|
|
614
|
+
# Dotted for `attaches`, solid for `reaches across` — the reverse
|
|
615
615
|
# of which fact is "always true" between the two: attaching a
|
|
616
616
|
# framework is a standing declaration, true every time this domain
|
|
617
617
|
# boots, so it gets the same dotted "this always belongs" treatment
|
|
@@ -621,8 +621,8 @@ module Hecks
|
|
|
621
621
|
# draws for the identical fact, kept solid here so the same
|
|
622
622
|
# relationship reads the same way in both diagrams.
|
|
623
623
|
#
|
|
624
|
-
# `options[:hecksagon]`
|
|
625
|
-
#
|
|
624
|
+
# `options[:hecksagon]` is the one diagram in this file that needs
|
|
625
|
+
# more than `bluebook` — `framework_members` lives on the
|
|
626
626
|
# `Hecksagon`, a sibling IR object `bin/project_diagrams` already
|
|
627
627
|
# has in hand (`registry.hecksagon(chapter_name)`) but `bluebook`
|
|
628
628
|
# itself carries no reference to. No hecksagon handed in (an older
|