hecks 1.3.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.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -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/prism.rb +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- 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/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -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 +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- 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 +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- 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 +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- 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 +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- 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 +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -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 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- 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 +348 -13
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -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 +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- 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 +118 -66
- 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 +34 -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 +33 -9
- 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 +95 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- 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 +48 -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 +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -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 +23 -3
- 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 +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +61 -15
- 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 +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- 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 +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -4,7 +4,7 @@ require_relative "../statements"
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Projections
|
|
6
6
|
module Glossary
|
|
7
|
-
#
|
|
7
|
+
# Every sentence the glossary says, and the rule each one obeys.
|
|
8
8
|
#
|
|
9
9
|
# Authored text is verbatim: an aggregate's `description`, a
|
|
10
10
|
# command's `goal`, a query's `description`, an invariant's own
|
|
@@ -30,6 +30,11 @@ module Hecks
|
|
|
30
30
|
|
|
31
31
|
# The paragraphs under a term's headword — the definition first,
|
|
32
32
|
# then, when the term carries rules, one "Always true: …" line.
|
|
33
|
+
#
|
|
34
|
+
# @param entry [Glossary::Entry] the term to render
|
|
35
|
+
# @param index [Glossary::Index] the document's link index
|
|
36
|
+
# @return [Array<String>] one or more paragraphs, compacted of any that
|
|
37
|
+
# have nothing to say (such as an entity with no lifecycle)
|
|
33
38
|
def paragraphs(entry, index)
|
|
34
39
|
facts = entry.facts
|
|
35
40
|
case entry.kind
|
|
@@ -47,10 +52,18 @@ module Hecks
|
|
|
47
52
|
|
|
48
53
|
# An aggregate's lede or an entity's entry: what it is, then the
|
|
49
54
|
# states it can be in.
|
|
55
|
+
#
|
|
56
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the holder to describe
|
|
57
|
+
# @return [Array<String>] `holder`'s description, then its lifecycle sentence;
|
|
58
|
+
# either or both may be absent
|
|
50
59
|
def holder_paragraphs(holder)
|
|
51
60
|
[holder.description, holder.lifecycle && lifecycle_sentence(holder.lifecycle)].compact
|
|
52
61
|
end
|
|
53
62
|
|
|
63
|
+
# Describes a lifecycle's starting state and every state it can reach.
|
|
64
|
+
#
|
|
65
|
+
# @param lifecycle [Bluebook::Lifecycle] the state machine to describe
|
|
66
|
+
# @return [String] what state it starts in and every state it can reach
|
|
54
67
|
def lifecycle_sentence(lifecycle)
|
|
55
68
|
states = ([lifecycle.default] + lifecycle.transitions.map { |_name, transition| transition.target }).uniq
|
|
56
69
|
"Starts out #{spoken(lifecycle.default)}. " \
|
|
@@ -58,15 +71,31 @@ module Hecks
|
|
|
58
71
|
end
|
|
59
72
|
|
|
60
73
|
# A state name is spelled `awaiting_credit`; said, it is "awaiting credit".
|
|
74
|
+
#
|
|
75
|
+
# @param state [String, Symbol] a state name
|
|
76
|
+
# @return [String] `state` with underscores turned to spaces
|
|
61
77
|
def spoken(state) = state.to_s.tr("_", " ")
|
|
62
78
|
|
|
79
|
+
# Renders a value object's definition, and its rules if it has any.
|
|
80
|
+
#
|
|
81
|
+
# @param value_object [Bluebook::ValueObject] the value object to describe
|
|
82
|
+
# @param index [Glossary::Index] the document's link index
|
|
83
|
+
# @param within [String, nil] the value object's own holder's name, for
|
|
84
|
+
# resolving a nested value object's own type link
|
|
85
|
+
# @return [Array<String>] the value object's definition sentence, then its
|
|
86
|
+
# "Always true: …" rules line; the rules line is absent when it has none
|
|
63
87
|
def value_object_paragraphs(value_object, index, within)
|
|
64
88
|
rules = value_object.invariants.map { |invariant| Statements.invariant_statement(invariant) }
|
|
65
89
|
[value_object_sentence(value_object, index, within), rules_line(rules)].compact
|
|
66
90
|
end
|
|
67
91
|
|
|
68
|
-
# A one-field object whose field is just "value"
|
|
92
|
+
# A one-field object whose field is just "value" is its type —
|
|
69
93
|
# "Text.", "A whole number." — the field name would add nothing.
|
|
94
|
+
#
|
|
95
|
+
# @param value_object [Bluebook::ValueObject] the value object to describe
|
|
96
|
+
# @param index [Glossary::Index] the document's link index
|
|
97
|
+
# @param within [String, nil] the value object's own holder's name
|
|
98
|
+
# @return [String] the value object's definition sentence
|
|
70
99
|
def value_object_sentence(value_object, index, within)
|
|
71
100
|
return closed_set_sentence(value_object.members) if value_object.closed_set?
|
|
72
101
|
return "A marker with no details of its own." if value_object.attributes.empty?
|
|
@@ -82,20 +111,35 @@ module Hecks
|
|
|
82
111
|
|
|
83
112
|
# "amount (a whole number)" — the field as the author named it,
|
|
84
113
|
# then what kind of thing goes in it.
|
|
114
|
+
#
|
|
115
|
+
# @param field [Bluebook::Attribute] the field to describe
|
|
116
|
+
# @param index [Glossary::Index] the document's link index
|
|
117
|
+
# @param within [String, nil] the field's own holder's name
|
|
118
|
+
# @return [String] the field's name, spoken, with its type in parentheses
|
|
85
119
|
def field_phrase(field, index, within)
|
|
86
120
|
"#{Naming.words(field.name).downcase} (#{type_words(field, index, within)})"
|
|
87
121
|
end
|
|
88
122
|
|
|
123
|
+
# Describes one field's type in plain words.
|
|
124
|
+
#
|
|
125
|
+
# @param field [Bluebook::Attribute] the field to describe
|
|
126
|
+
# @param index [Glossary::Index] the document's link index
|
|
127
|
+
# @param within [String, nil] the field's own holder's name
|
|
128
|
+
# @return [String] the field's type in plain words, or a link to its value
|
|
129
|
+
# object's own headword; "a list of …" when the field is a list
|
|
89
130
|
def type_words(field, index, within)
|
|
90
131
|
type = field.type.to_s
|
|
91
132
|
inner = TYPE_WORDS[type] || index.link(:value_object, type, within: within)
|
|
92
133
|
field.list? ? "a list of #{inner}" : inner
|
|
93
134
|
end
|
|
94
135
|
|
|
95
|
-
# A
|
|
136
|
+
# A closed set's rows — a one-field set is its values; a set
|
|
96
137
|
# whose rows carry more (StatementFrequency's cadence plus a
|
|
97
138
|
# retention and a fee) leads with the first field and keeps the
|
|
98
139
|
# rest beside it, so no row loses what makes it distinct.
|
|
140
|
+
#
|
|
141
|
+
# @param members [Array<Hash{Symbol => Object}>] the closed set's member rows
|
|
142
|
+
# @return [String] "One of: …" (multi-field rows) or "One of …" (single-field)
|
|
99
143
|
def closed_set_sentence(members)
|
|
100
144
|
if members.first && members.first.size > 1
|
|
101
145
|
rows = members.map do |row|
|
|
@@ -112,6 +156,10 @@ module Hecks
|
|
|
112
156
|
# code." — the rules as their author wrote them, kept out of the
|
|
113
157
|
# definition sentence (a rule hidden inside a definition is a rule
|
|
114
158
|
# a reader misses).
|
|
159
|
+
#
|
|
160
|
+
# @param rules [Array<String>] rendered invariant sentences
|
|
161
|
+
# @return [String, nil] "Always true: …" joining every rule, or nil if
|
|
162
|
+
# `rules` is empty
|
|
115
163
|
def rules_line(rules)
|
|
116
164
|
return nil if rules.empty?
|
|
117
165
|
|
|
@@ -119,6 +167,11 @@ module Hecks
|
|
|
119
167
|
"Always true: #{clauses.join('; ')}."
|
|
120
168
|
end
|
|
121
169
|
|
|
170
|
+
# Describes a command's goal and who does it.
|
|
171
|
+
#
|
|
172
|
+
# @param command [Bluebook::Command] the command to describe
|
|
173
|
+
# @return [String, nil] the command's goal and/or its role sentence, or nil
|
|
174
|
+
# if it declares neither
|
|
122
175
|
def command_sentence(command)
|
|
123
176
|
parts = []
|
|
124
177
|
parts << with_period(command.goal) if command.goal
|
|
@@ -126,6 +179,14 @@ module Hecks
|
|
|
126
179
|
parts.empty? ? nil : parts.join(" ")
|
|
127
180
|
end
|
|
128
181
|
|
|
182
|
+
# Describes when an event is recorded and what it prompts.
|
|
183
|
+
#
|
|
184
|
+
# @param facts [Hash{Symbol => Object}] an `:event`-kind entry's facts:
|
|
185
|
+
# `:raised_by` (`Array<Array(Bluebook::Aggregate, Bluebook::Command)>`) and
|
|
186
|
+
# `:policies` (`Array<Bluebook::Policy>`)
|
|
187
|
+
# @param index [Glossary::Index] the document's link index
|
|
188
|
+
# @return [String] which commands record the event, and, when any policy
|
|
189
|
+
# reacts to it, which ones it prompts
|
|
129
190
|
def event_sentence(facts, index)
|
|
130
191
|
raisers = command_links(facts[:raised_by], index)
|
|
131
192
|
sentence = "Recorded after #{Naming.to_sentence_list(raisers, conj: 'or')}."
|
|
@@ -138,6 +199,10 @@ module Hecks
|
|
|
138
199
|
# account, once for each row of Open for customer." — a
|
|
139
200
|
# cross-domain trigger (`across "Compliance"`) is spoken as words
|
|
140
201
|
# with the domain named, since nothing here to link to exists.
|
|
202
|
+
#
|
|
203
|
+
# @param policy [Bluebook::Policy] the policy to describe
|
|
204
|
+
# @param index [Glossary::Index] the document's link index
|
|
205
|
+
# @return [String] when the policy fires and what it asks for
|
|
141
206
|
def policy_sentence(policy, index)
|
|
142
207
|
holder, command = split_trigger(policy.trigger_command)
|
|
143
208
|
asked = if policy.target_domain
|
|
@@ -154,6 +219,13 @@ module Hecks
|
|
|
154
219
|
"#{sentence}."
|
|
155
220
|
end
|
|
156
221
|
|
|
222
|
+
# Describes when a saga begins and ends, and its states along the way.
|
|
223
|
+
#
|
|
224
|
+
# @param shape [Hash{Symbol => Object}] a process manager's `to_h`, read for
|
|
225
|
+
# `:starts_on`, `:ends_on`, and `:states`
|
|
226
|
+
# @param index [Glossary::Index] the document's link index
|
|
227
|
+
# @return [String] when the saga begins and ends, and, when it declares any,
|
|
228
|
+
# the states it can be in along the way
|
|
157
229
|
def saga_sentence(shape, index)
|
|
158
230
|
sentence = "Begins when #{index.link(:event, bare(shape[:starts_on]))} happens " \
|
|
159
231
|
"and ends when #{index.link(:event, bare(shape[:ends_on]))} happens."
|
|
@@ -162,18 +234,29 @@ module Hecks
|
|
|
162
234
|
sentence
|
|
163
235
|
end
|
|
164
236
|
|
|
165
|
-
# A
|
|
237
|
+
# A noun list, deliberately — "Responsible for Credit and Debit",
|
|
166
238
|
# never "Can credit and debit": banking's System role raises
|
|
167
239
|
# `Debited` and `Credited`, and "can … debited" is a wrong
|
|
168
240
|
# sentence. The headwords are already what people say.
|
|
241
|
+
#
|
|
242
|
+
# @param issues [Array<Array(Bluebook::Aggregate, Bluebook::Command)>] the
|
|
243
|
+
# role's own `[holder, command]` pairs
|
|
244
|
+
# @param index [Glossary::Index] the document's link index
|
|
245
|
+
# @return [String] "Responsible for …", naming every command
|
|
169
246
|
def role_sentence(issues, index)
|
|
170
247
|
"Responsible for #{Naming.to_sentence_list(command_links(issues, index))}."
|
|
171
248
|
end
|
|
172
249
|
|
|
173
|
-
#
|
|
250
|
+
# The same word for two different things gets its holder beside it
|
|
174
251
|
# — a role responsible for CardPayment's Reverse and Transfer's
|
|
175
252
|
# Reverse is responsible for "Reverse (card payment)" and
|
|
176
253
|
# "Reverse (transfer)", not for "Reverse" twice.
|
|
254
|
+
#
|
|
255
|
+
# @param issues [Array<Array(Bluebook::Aggregate, Bluebook::Command)>] each
|
|
256
|
+
# `[holder, command]` pair to link
|
|
257
|
+
# @param index [Glossary::Index] the document's link index
|
|
258
|
+
# @return [Array<String>] one Markdown link per distinct `[holder, command]`
|
|
259
|
+
# pair, qualified with the holder's name when the command name repeats
|
|
177
260
|
def command_links(issues, index)
|
|
178
261
|
issues = issues.uniq { |holder, command| [holder.hecks_name, command.hecks_name] }
|
|
179
262
|
repeated = issues.map { |_holder, command| command.hecks_name }.tally.select { |_name, count| count > 1 }
|
|
@@ -187,17 +270,39 @@ module Hecks
|
|
|
187
270
|
|
|
188
271
|
# ── small carpentry ─────────────────────────────────────────────
|
|
189
272
|
|
|
273
|
+
# Splits a dotted `Holder.command` trigger into its two parts.
|
|
274
|
+
#
|
|
275
|
+
# @param dotted [String, Symbol] a `"Holder.command"` reference
|
|
276
|
+
# @return [Array(String, String)] `[holder, command]`
|
|
190
277
|
def split_trigger(dotted)
|
|
191
278
|
holder, _dot, command = dotted.to_s.rpartition(".")
|
|
192
279
|
[holder, command]
|
|
193
280
|
end
|
|
194
281
|
|
|
282
|
+
# Strips a dotted name down to its last segment.
|
|
283
|
+
#
|
|
284
|
+
# @param qualified [String, Symbol] a dotted or bare name, such as
|
|
285
|
+
# `"Handler.Dispatch"` or `"Freeze"`
|
|
286
|
+
# @return [String] the name after the last `.`, or the whole name if it has none
|
|
195
287
|
def bare(qualified) = qualified.to_s.split(".").last
|
|
196
288
|
|
|
289
|
+
# Ensures text ends in sentence-ending punctuation.
|
|
290
|
+
#
|
|
291
|
+
# @param text [String, Symbol, nil] the text to punctuate
|
|
292
|
+
# @return [String] `text`, stripped, with a trailing `.` added if it has no
|
|
293
|
+
# sentence-ending punctuation already
|
|
197
294
|
def with_period(text) = text.to_s.strip.end_with?(".", "!", "?") ? text.to_s.strip : "#{text.to_s.strip}."
|
|
198
295
|
|
|
296
|
+
# Lowercases a leading capital letter.
|
|
297
|
+
#
|
|
298
|
+
# @param text [String] the text to adjust
|
|
299
|
+
# @return [String] `text` with a leading uppercase letter lowercased
|
|
199
300
|
def lower_first(text) = text.sub(/\A[[:upper:]]/, &:downcase)
|
|
200
301
|
|
|
302
|
+
# Uppercases a leading lowercase letter.
|
|
303
|
+
#
|
|
304
|
+
# @param text [String] the text to adjust
|
|
305
|
+
# @return [String] `text` with a leading lowercase letter uppercased
|
|
201
306
|
def upper_first(text) = text.sub(/\A[[:lower:]]/, &:upcase)
|
|
202
307
|
end
|
|
203
308
|
end
|
|
@@ -9,33 +9,37 @@ require_relative "glossary/html"
|
|
|
9
9
|
|
|
10
10
|
module Hecks
|
|
11
11
|
module Projections
|
|
12
|
-
# A
|
|
13
|
-
# whole team, in the sense Evans meant (DDD ch. 2):
|
|
12
|
+
# A chapter, projected as its ubiquitous language — a glossary for the
|
|
13
|
+
# whole team, in the sense Evans meant (DDD ch. 2): one language,
|
|
14
14
|
# shared by domain experts and developers, written the same way in
|
|
15
15
|
# conversation, diagrams, documents and code, so that a subject-matter
|
|
16
16
|
# expert can read the model and say "yes, that's how it works" or
|
|
17
17
|
# "no, that's wrong". A banker, a support rep, the CEO and an engineer
|
|
18
18
|
# read the same page.
|
|
19
19
|
#
|
|
20
|
-
#
|
|
21
|
-
# term is a term, not "an Aggregate" or "a Value Object", and every
|
|
22
|
-
# identifier is spelled as a person says it (`Naming.words`: `ATMCard`
|
|
23
|
-
# is "ATM card"). Second, nothing invented: every sentence is either
|
|
24
|
-
# the domain author's own words (a `description`, a `goal`, an
|
|
25
|
-
# invariant) or built mechanically from a declared fact ("Recorded
|
|
26
|
-
# after Freeze account") — the same discipline `DocsProjector` holds
|
|
27
|
-
# to; a wrong sentence is worse than a missing one. Third, grouped
|
|
28
|
-
# under the aggregate each term belongs to, A to Z within it — a
|
|
29
|
-
# reader thinks "what does Account mean" before "what starts with A",
|
|
30
|
-
# and once there reads the way a dictionary reads.
|
|
20
|
+
# ## Three things follow from that audience
|
|
31
21
|
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
22
|
+
# First, no type jargon: a term is a term, not "an Aggregate" or "a
|
|
23
|
+
# Value Object", and every identifier is spelled as a person says it
|
|
24
|
+
# (`Naming.words`: `ATMCard` is "ATM card"). Second, nothing invented:
|
|
25
|
+
# every sentence is either the domain author's own words (a
|
|
26
|
+
# `description`, a `goal`, an invariant) or built mechanically from a
|
|
27
|
+
# declared fact ("Recorded after Freeze account") — the same
|
|
28
|
+
# discipline `DocsProjector` holds to; a wrong sentence is worse than
|
|
29
|
+
# a missing one. Third, grouped under the aggregate each term belongs
|
|
30
|
+
# to, A to Z within it — a reader thinks "what does Account mean"
|
|
31
|
+
# before "what starts with A", and once there reads the way a
|
|
32
|
+
# dictionary reads.
|
|
33
|
+
#
|
|
34
|
+
# ## Two files from one source
|
|
35
|
+
#
|
|
36
|
+
# `glossary.md` is the document — every construct in it renders on
|
|
37
|
+
# GitHub as-is (headings, blockquotes, ```mermaid fences, lists,
|
|
38
|
+
# in-page links). `html/index.html` is that exact Markdown string
|
|
39
|
+
# rendered into a page with a navigation rail; it is built from the
|
|
40
|
+
# Markdown, not beside it, so the two cannot drift. `bin/project_glossary`
|
|
41
|
+
# writes both to `<domain>/glossary/` beside the bluebook (examples/banking,
|
|
42
|
+
# examples/pizzas, and the QA ledger in qa/).
|
|
39
43
|
#
|
|
40
44
|
# Projector.call(:glossary, bluebook: <the Bluebook chapter>)
|
|
41
45
|
# # => { "glossary.md" => "...", "html/index.html" => "..." }
|
|
@@ -59,6 +63,14 @@ module Hecks
|
|
|
59
63
|
|
|
60
64
|
module_function
|
|
61
65
|
|
|
66
|
+
# Projects `bluebook`'s glossary as a Markdown document and the HTML
|
|
67
|
+
# page built from it.
|
|
68
|
+
#
|
|
69
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to render a
|
|
70
|
+
# glossary for
|
|
71
|
+
# @param options [Hash] unused; accepted to satisfy the registry's call shape
|
|
72
|
+
# @return [Hash{String => String}] `"glossary.md"` and `"html/index.html"`,
|
|
73
|
+
# each mapped to its rendered content
|
|
62
74
|
def call(bluebook:, options: {})
|
|
63
75
|
markdown = Markdown.render(document(bluebook))
|
|
64
76
|
{ "glossary.md" => markdown, "html/index.html" => Html.render(markdown) }
|
|
@@ -66,14 +78,25 @@ module Hecks
|
|
|
66
78
|
|
|
67
79
|
# ── the document ─────────────────────────────────────────────────
|
|
68
80
|
|
|
81
|
+
# Gathers every term, groups it into sections, assigns headings and
|
|
82
|
+
# slugs, and builds the index links resolve through.
|
|
83
|
+
#
|
|
84
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to render
|
|
85
|
+
# @return [Document] the fully assembled document, ready for `Markdown.render`
|
|
69
86
|
def document(bluebook)
|
|
70
87
|
sections = sections(bluebook, entries(bluebook))
|
|
71
88
|
Slugs.assign!(bluebook, sections)
|
|
72
89
|
Document.new(bluebook: bluebook, sections: sections, index: Index.new(sections))
|
|
73
90
|
end
|
|
74
91
|
|
|
75
|
-
#
|
|
92
|
+
# Aggregates first, a to Z by their spoken name ("Account" before
|
|
76
93
|
# "ATM card"), then the three groups nothing homes to one aggregate.
|
|
94
|
+
#
|
|
95
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
|
|
96
|
+
# aggregates to group by
|
|
97
|
+
# @param entries [Array<Entry>] every gathered term
|
|
98
|
+
# @return [Array<Section>] one section per aggregate, sorted, followed by
|
|
99
|
+
# any of the Roles/Read models/Reactions sections that have entries
|
|
77
100
|
def sections(bluebook, entries)
|
|
78
101
|
grouped = entries.group_by(&:section)
|
|
79
102
|
list = bluebook.aggregates.sort_by { |aggregate| Naming.words(aggregate.hecks_name).downcase }.map do |aggregate|
|
|
@@ -87,10 +110,16 @@ module Hecks
|
|
|
87
110
|
list
|
|
88
111
|
end
|
|
89
112
|
|
|
90
|
-
# A
|
|
113
|
+
# A headword is qualified only when it would repeat within its own
|
|
91
114
|
# section — "Open" the action and "Open (the list)" the question;
|
|
92
115
|
# "Return (key issuance)" beside another Return — never numbered
|
|
93
116
|
# (Chicago 18.9, MDN's disambiguation pages: qualify the headword).
|
|
117
|
+
#
|
|
118
|
+
# @param entries [Array<Entry>] the section's own entries
|
|
119
|
+
# @param section_name [String] the entries' own section name, for telling
|
|
120
|
+
# an entry's own section apart from a borrowed one when qualifying
|
|
121
|
+
# @return [Array<Entry>] `entries`, headword-assigned, sorted by headword
|
|
122
|
+
# then kind
|
|
94
123
|
def with_headwords(entries, section_name)
|
|
95
124
|
entries.each { |entry| entry.headword = Naming.words(entry.name) }
|
|
96
125
|
entries.group_by(&:headword).each_value do |group|
|
|
@@ -101,6 +130,14 @@ module Hecks
|
|
|
101
130
|
entries.sort_by { |entry| [entry.headword.downcase, entry.kind.to_s] }
|
|
102
131
|
end
|
|
103
132
|
|
|
133
|
+
# Disambiguates one entry's headword from the rest of its group.
|
|
134
|
+
#
|
|
135
|
+
# @param entry [Entry] the entry to qualify
|
|
136
|
+
# @param group [Array<Entry>] every entry sharing `entry`'s plain headword
|
|
137
|
+
# @param section_name [String] the section `entry` and `group` are being
|
|
138
|
+
# rendered under
|
|
139
|
+
# @return [String] `entry`'s headword, qualified with "(the list)" or its
|
|
140
|
+
# own holder's name when needed to tell it apart from the rest of `group`
|
|
104
141
|
def qualified(entry, group, section_name)
|
|
105
142
|
base = Naming.words(entry.name)
|
|
106
143
|
return "#{base} (the list)" if entry.kind == :query && group.any? { |other| other.kind == :command }
|
|
@@ -111,16 +148,24 @@ module Hecks
|
|
|
111
148
|
|
|
112
149
|
# ── gathering ────────────────────────────────────────────────────
|
|
113
150
|
|
|
114
|
-
#
|
|
151
|
+
# An entity can carry its own commands and queries too — walked the
|
|
115
152
|
# same one level down `DocsProjector` and `Projections::Diagrams`
|
|
116
153
|
# already walk it.
|
|
154
|
+
#
|
|
155
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to walk
|
|
156
|
+
# @return [Array<Bluebook::Aggregate, Bluebook::Entity>] every aggregate, each
|
|
157
|
+
# immediately followed by its own nested entities
|
|
117
158
|
def holders(bluebook)
|
|
118
159
|
bluebook.aggregates.flat_map { |aggregate| [aggregate, *aggregate.entities] }
|
|
119
160
|
end
|
|
120
161
|
|
|
121
|
-
#
|
|
162
|
+
# Every holder's own aggregate, one hop or zero — an aggregate maps
|
|
122
163
|
# to itself, an entity to whichever aggregate declared it. The one
|
|
123
164
|
# fact the grouping is built on.
|
|
165
|
+
#
|
|
166
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to walk
|
|
167
|
+
# @return [Hash{String => String}] every aggregate and entity name, mapped to
|
|
168
|
+
# its own owning aggregate's name
|
|
124
169
|
def holder_aggregate(bluebook)
|
|
125
170
|
bluebook.aggregates.each_with_object({}) do |aggregate, map|
|
|
126
171
|
map[aggregate.hecks_name] = aggregate.hecks_name
|
|
@@ -128,17 +173,32 @@ module Hecks
|
|
|
128
173
|
end
|
|
129
174
|
end
|
|
130
175
|
|
|
131
|
-
#
|
|
132
|
-
# so its home is whichever aggregate the
|
|
176
|
+
# Every event's raisers — an event is never declared, only emitted,
|
|
177
|
+
# so its home is whichever aggregate the first command that raises
|
|
133
178
|
# it belongs to; a policy or saga reacting to it inherits that home.
|
|
179
|
+
#
|
|
180
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to walk
|
|
181
|
+
# @return [Hash{String => Array<Array(Bluebook::Aggregate, Bluebook::Command)>}]
|
|
182
|
+
# every emitted event name, mapped to `[holder, command]` pairs for every
|
|
183
|
+
# command that raises it; a name absent from the Hash raises nothing
|
|
134
184
|
def event_raisers(bluebook)
|
|
135
185
|
holders(bluebook).each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |holder, map|
|
|
136
186
|
holder.commands.each { |command| command.emits.each { |event| map[event] << [holder, command] } }
|
|
137
187
|
end
|
|
138
188
|
end
|
|
139
189
|
|
|
190
|
+
# Strips a dotted name down to its last segment.
|
|
191
|
+
#
|
|
192
|
+
# @param qualified [String, Symbol] a dotted or bare name, such as
|
|
193
|
+
# `"Handler.Dispatch"` or `"Freeze"`
|
|
194
|
+
# @return [String] the name after the last `.`, or the whole name if it has none
|
|
140
195
|
def bare(qualified) = qualified.to_s.split(".").last
|
|
141
196
|
|
|
197
|
+
# Gathers every term the glossary carries, ungrouped and unordered.
|
|
198
|
+
#
|
|
199
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather terms from
|
|
200
|
+
# @return [Array<Entry>] one entry per entity, value object, command, query,
|
|
201
|
+
# event, policy, saga, role, and read model
|
|
142
202
|
def entries(bluebook)
|
|
143
203
|
homes = holder_aggregate(bluebook)
|
|
144
204
|
raisers = event_raisers(bluebook)
|
|
@@ -156,6 +216,10 @@ module Hecks
|
|
|
156
216
|
entries
|
|
157
217
|
end
|
|
158
218
|
|
|
219
|
+
# Gathers one entry per entity.
|
|
220
|
+
#
|
|
221
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather entities from
|
|
222
|
+
# @return [Array<Entry>] one `:entity`-kind entry per entity
|
|
159
223
|
def entity_entries(bluebook)
|
|
160
224
|
bluebook.aggregates.flat_map do |aggregate|
|
|
161
225
|
aggregate.entities.map do |entity|
|
|
@@ -165,8 +229,13 @@ module Hecks
|
|
|
165
229
|
end
|
|
166
230
|
end
|
|
167
231
|
|
|
168
|
-
#
|
|
232
|
+
# Aggregates only — an entity declares no value objects of its own
|
|
169
233
|
# (`Bluebook::Entity` deliberately does not answer `value_objects`).
|
|
234
|
+
#
|
|
235
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather value
|
|
236
|
+
# objects from
|
|
237
|
+
# @return [Array<Entry>] one `:value_object`-kind entry per aggregate-declared
|
|
238
|
+
# value object
|
|
170
239
|
def value_object_entries(bluebook)
|
|
171
240
|
bluebook.aggregates.flat_map do |aggregate|
|
|
172
241
|
aggregate.value_objects.map do |value_object|
|
|
@@ -176,6 +245,14 @@ module Hecks
|
|
|
176
245
|
end
|
|
177
246
|
end
|
|
178
247
|
|
|
248
|
+
# Gathers one entry per command and query.
|
|
249
|
+
#
|
|
250
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather commands
|
|
251
|
+
# and queries from
|
|
252
|
+
# @param homes [Hash{String => String}] every holder's name, mapped to its
|
|
253
|
+
# owning aggregate's name (as `holder_aggregate` builds)
|
|
254
|
+
# @return [Array<Entry>] one `:command`-kind or `:query`-kind entry per command
|
|
255
|
+
# and query, commands first
|
|
179
256
|
def verb_entries(bluebook, homes)
|
|
180
257
|
holders(bluebook).flat_map do |holder|
|
|
181
258
|
commands = holder.commands.map do |command|
|
|
@@ -190,6 +267,15 @@ module Hecks
|
|
|
190
267
|
end
|
|
191
268
|
end
|
|
192
269
|
|
|
270
|
+
# Gathers one entry per raised event.
|
|
271
|
+
#
|
|
272
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
|
|
273
|
+
# policies checked for reactions
|
|
274
|
+
# @param raisers [Hash{String => Array<Array(Bluebook::Aggregate, Bluebook::Command)>}]
|
|
275
|
+
# every event name, mapped to its raising `[holder, command]` pairs (as
|
|
276
|
+
# `event_raisers` builds)
|
|
277
|
+
# @param home_of [Proc, #call] answers an event name's owning aggregate name, or nil
|
|
278
|
+
# @return [Array<Entry>] one `:event`-kind entry per raised event
|
|
193
279
|
def event_entries(bluebook, raisers, home_of)
|
|
194
280
|
raisers.map do |event, raised_by|
|
|
195
281
|
reactions = bluebook.policies.select { |policy| bare(policy.on_event) == event }
|
|
@@ -198,6 +284,11 @@ module Hecks
|
|
|
198
284
|
end
|
|
199
285
|
end
|
|
200
286
|
|
|
287
|
+
# Gathers one entry per declared policy.
|
|
288
|
+
#
|
|
289
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather policies from
|
|
290
|
+
# @param home_of [Proc, #call] answers an event name's owning aggregate name, or nil
|
|
291
|
+
# @return [Array<Entry>] one `:policy`-kind entry per declared policy
|
|
201
292
|
def policy_entries(bluebook, home_of)
|
|
202
293
|
bluebook.policies.map do |policy|
|
|
203
294
|
Entry.new(name: policy.name, kind: :policy, section: home_of.call(bare(policy.on_event)),
|
|
@@ -205,6 +296,11 @@ module Hecks
|
|
|
205
296
|
end
|
|
206
297
|
end
|
|
207
298
|
|
|
299
|
+
# Gathers one entry per declared process manager.
|
|
300
|
+
#
|
|
301
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather sagas from
|
|
302
|
+
# @param home_of [Proc, #call] answers an event name's owning aggregate name, or nil
|
|
303
|
+
# @return [Array<Entry>] one `:saga`-kind entry per declared process manager
|
|
208
304
|
def saga_entries(bluebook, home_of)
|
|
209
305
|
bluebook.process_managers.map do |saga|
|
|
210
306
|
shape = saga.to_h
|
|
@@ -213,9 +309,12 @@ module Hecks
|
|
|
213
309
|
end
|
|
214
310
|
end
|
|
215
311
|
|
|
216
|
-
#
|
|
312
|
+
# Cross-cutting by nature — `System` and `Customer` issue commands
|
|
217
313
|
# across half the aggregates here — so a role belongs to no single
|
|
218
314
|
# one and gets its own section.
|
|
315
|
+
#
|
|
316
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather roles from
|
|
317
|
+
# @return [Array<Entry>] one `:role`-kind entry per distinct role named by a command
|
|
219
318
|
def role_entries(bluebook)
|
|
220
319
|
by_role = Hash.new { |hash, key| hash[key] = [] }
|
|
221
320
|
holders(bluebook).each do |holder|
|
|
@@ -228,6 +327,10 @@ module Hecks
|
|
|
228
327
|
|
|
229
328
|
# A read model joins heads from more than one aggregate — its own
|
|
230
329
|
# header says so — so it belongs to none of them.
|
|
330
|
+
#
|
|
331
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather read
|
|
332
|
+
# models from
|
|
333
|
+
# @return [Array<Entry>] one `:read_model`-kind entry per declared read model
|
|
231
334
|
def read_model_entries(bluebook)
|
|
232
335
|
bluebook.read_models.map do |read_model|
|
|
233
336
|
Entry.new(name: read_model.name, kind: :read_model, section: READ_MODELS, facts: { read_model: read_model })
|
|
@@ -236,7 +339,7 @@ module Hecks
|
|
|
236
339
|
|
|
237
340
|
# ── anchors ──────────────────────────────────────────────────────
|
|
238
341
|
|
|
239
|
-
#
|
|
342
|
+
# **GitHub's own heading slugs, reproduced** — lowercase, punctuation
|
|
240
343
|
# dropped, spaces to hyphens, a repeat gets "-1", "-2" in document
|
|
241
344
|
# order. Computed here, once, in the order the headings will appear,
|
|
242
345
|
# so a link written into the Markdown lands on the same heading
|
|
@@ -244,8 +347,20 @@ module Hecks
|
|
|
244
347
|
module Slugs
|
|
245
348
|
module_function
|
|
246
349
|
|
|
350
|
+
# Reproduces GitHub's own heading-to-slug transform.
|
|
351
|
+
#
|
|
352
|
+
# @param text [String] the heading text to slugify
|
|
353
|
+
# @return [String] `text` lowercased, stripped of punctuation, spaces turned
|
|
354
|
+
# to hyphens
|
|
247
355
|
def github(text) = text.to_s.downcase.gsub(/[^\p{Word}\- ]/, "").tr(" ", "-")
|
|
248
356
|
|
|
357
|
+
# Assigns every section's and every entry's own slug, in document order.
|
|
358
|
+
#
|
|
359
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter the document
|
|
360
|
+
# titles itself after
|
|
361
|
+
# @param sections [Array<Section>] the sections to assign slugs to, in the
|
|
362
|
+
# order they render
|
|
363
|
+
# @return [void]
|
|
249
364
|
def assign!(bluebook, sections)
|
|
250
365
|
seen = Hash.new(0)
|
|
251
366
|
take = lambda do |text|
|
|
@@ -261,12 +376,16 @@ module Hecks
|
|
|
261
376
|
end
|
|
262
377
|
end
|
|
263
378
|
|
|
264
|
-
#
|
|
265
|
-
# by its holder
|
|
379
|
+
# **Where a declared reference points** — keyed structurally (a command
|
|
380
|
+
# by its holder and name, an event by its bare name), never by
|
|
266
381
|
# searching prose for a matching word. Anything not declared here
|
|
267
382
|
# (a cross-domain command like `Notifications.Send`) answers nil,
|
|
268
383
|
# and the sentence says it in words with no link.
|
|
269
384
|
class Index
|
|
385
|
+
# Builds the lookup table every `link` call resolves through.
|
|
386
|
+
#
|
|
387
|
+
# @param sections [Array<Section>] the document's own sections, already
|
|
388
|
+
# built and slugged
|
|
270
389
|
def initialize(sections)
|
|
271
390
|
@by_key = {}
|
|
272
391
|
sections.each do |section|
|
|
@@ -275,6 +394,12 @@ module Hecks
|
|
|
275
394
|
end
|
|
276
395
|
end
|
|
277
396
|
|
|
397
|
+
# Builds one entry's lookup key.
|
|
398
|
+
#
|
|
399
|
+
# @param entry [Entry] the entry to key
|
|
400
|
+
# @return [Array(Symbol, String, String), Array(Symbol, String)] `[kind, within,
|
|
401
|
+
# name]` for a command, query, or value object (which can share a bare name
|
|
402
|
+
# across holders); `[kind, name]` otherwise
|
|
278
403
|
def key_of(entry)
|
|
279
404
|
case entry.kind
|
|
280
405
|
when :command, :query, :value_object then [entry.kind, entry.within, entry.name]
|
|
@@ -282,6 +407,14 @@ module Hecks
|
|
|
282
407
|
end
|
|
283
408
|
end
|
|
284
409
|
|
|
410
|
+
# Looks up one entry or aggregate section by its key.
|
|
411
|
+
#
|
|
412
|
+
# @param kind [Symbol] the entry's kind, or `:aggregate` for a section
|
|
413
|
+
# @param name [String] the entry's or aggregate's name
|
|
414
|
+
# @param within [String, nil] the holder's name, for a command, query, or
|
|
415
|
+
# value object; nil otherwise
|
|
416
|
+
# @return [Entry, Section, nil] the matching entry or aggregate section, or
|
|
417
|
+
# nil if the chapter declares none
|
|
285
418
|
def [](kind, name, within: nil)
|
|
286
419
|
@by_key[within ? [kind, within, name] : [kind, name]]
|
|
287
420
|
end
|
|
@@ -289,6 +422,15 @@ module Hecks
|
|
|
289
422
|
# A Markdown link to a term's own heading, or the plain words when
|
|
290
423
|
# the chapter declares no such term. `label:` overrides the link
|
|
291
424
|
# text for a sentence that has to tell two same-named terms apart.
|
|
425
|
+
#
|
|
426
|
+
# @param kind [Symbol] the target's kind, or `:aggregate` for a section
|
|
427
|
+
# @param name [String] the target's or aggregate's name
|
|
428
|
+
# @param within [String, nil] the holder's name, for a command, query, or
|
|
429
|
+
# value object; nil otherwise
|
|
430
|
+
# @param label [String, nil] link text to use instead of the target's own
|
|
431
|
+
# headword or title
|
|
432
|
+
# @return [String] a `[text](#slug)` Markdown link, or the plain words when
|
|
433
|
+
# the chapter declares no such term
|
|
292
434
|
def link(kind, name, within: nil, label: nil)
|
|
293
435
|
target = self[kind, name, within: within]
|
|
294
436
|
return label || Naming.words(name) unless target
|
data/lib/hecks/projections/ir.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Hecks
|
|
|
7
7
|
# The canonical IR, as a constant. The implementation already existed
|
|
8
8
|
# and is already golden-tested (`Projector::IRProjector`, registered
|
|
9
9
|
# as `:ir`) — this only gives it the constant spelling every other
|
|
10
|
-
# target has, by re-registering the
|
|
10
|
+
# target has, by re-registering the same module under the same key.
|
|
11
11
|
#
|
|
12
12
|
# Deliberately not a new implementation: two things named `IR` that
|
|
13
13
|
# each rendered IR their own way is exactly the drift this namespace
|