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
|
@@ -1,50 +1,49 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Bluebook
|
|
3
3
|
class Assembly
|
|
4
|
-
#
|
|
4
|
+
# **The first specializer** — a projection of `contracts.rb`'s `fields:` table,
|
|
5
5
|
# derived from the language's own description of a category instead of
|
|
6
6
|
# hand-written beside it.
|
|
7
7
|
#
|
|
8
8
|
# `Plan` already reads `grammar_registry` to build the judge's walk ; this
|
|
9
|
-
# reads the same chapter to build the
|
|
10
|
-
# names as duplication — "
|
|
9
|
+
# reads the same chapter to build the other table this arc's own header
|
|
10
|
+
# names as duplication — "spelled as the IR spells them," field for field,
|
|
11
11
|
# for every category simple enough to say so.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
13
|
+
# One case, proven, not the whole table. A field this can speak for is
|
|
14
14
|
# scalar and not a reference — every other field (a list, a reference, a
|
|
15
15
|
# fold like Lifecycle) is exactly what `contracts.rb`'s `reads:`/`derived:`
|
|
16
16
|
# exist to say, and stays hand-written until a later projection learns to
|
|
17
17
|
# derive readers and folds too. Restricting the claim to what can be
|
|
18
|
-
#
|
|
18
|
+
# proven correct — checked in spec/specializer_spec.rb against two
|
|
19
19
|
# independent categories — is the same discipline `derived:` itself
|
|
20
20
|
# enforces : a claim needs a kind, and this one's kind is "plain, checked."
|
|
21
21
|
module Specializer
|
|
22
22
|
module_function
|
|
23
23
|
|
|
24
|
-
# `position` IS THE FIRST FOLD THIS RUNS INTO, and it is a universal
|
|
25
|
-
# one : every category declares `attribute :position, Position` — for
|
|
26
|
-
# the JUDGE's own walk, `order_by :position` on its `DeclaredIn` ask —
|
|
27
|
-
# but no `*` constructor takes it as an argument. `contracts.rb`
|
|
28
|
-
# already says so, in the language every other derived field speaks :
|
|
29
|
-
# `derived: { position: :walk }`. The language says a category HAS a
|
|
30
|
-
# position ; it does not say a category's OWN constructor is handed
|
|
31
|
-
# one, and that second fact is exactly what `fields:` needs to answer.
|
|
32
|
-
# So this is not silently special-cased — it is the one fold named
|
|
33
|
-
# here because it is the one fold that is not a lucky accident of
|
|
34
|
-
# Policy or Handler, but a fact true of every category this arc will
|
|
35
|
-
# ever reach.
|
|
36
|
-
DERIVED_EVERYWHERE = %i[position].freeze
|
|
37
|
-
|
|
38
24
|
# S17, ADR 0026 — `Handler` is a genuine entity now, nested under
|
|
39
25
|
# `ProcessManager`, so `.aggregate` alone no longer finds it —
|
|
40
26
|
# it hangs off some aggregate's own `.entities` instead
|
|
41
27
|
# (searched recursively, the same reason `Value::Coercion#find_
|
|
42
|
-
# entity` does: a
|
|
28
|
+
# entity` does: a nested entity, like `Dispatch` inside
|
|
43
29
|
# `Handler`, is not a direct child of any aggregate either).
|
|
30
|
+
#
|
|
31
|
+
# @param chapter [Bluebook::Chapter] the chapter to search
|
|
32
|
+
# @param name [String, Symbol] the construct's declared name
|
|
33
|
+
# @return [Bluebook::Aggregate, Class, nil] the aggregate, or the entity class
|
|
34
|
+
# (a `Bluebook::Entity` subclass) nested anywhere under one, or `nil` if
|
|
35
|
+
# `chapter` declares no construct by that name
|
|
44
36
|
def construct_for(chapter, name)
|
|
45
37
|
chapter.aggregate(name) || chapter.aggregates.filter_map { |a| find_entity(a, name) }.first
|
|
46
38
|
end
|
|
47
39
|
|
|
40
|
+
# Searches a construct's own entities, recursively, for one by name.
|
|
41
|
+
#
|
|
42
|
+
# @param construct [Bluebook::Aggregate, Class] the aggregate, or entity class,
|
|
43
|
+
# to search under
|
|
44
|
+
# @param name [String, Symbol] the entity's declared name
|
|
45
|
+
# @return [Class, nil] the entity class (a `Bluebook::Entity` subclass), or `nil`
|
|
46
|
+
# if none of `construct`'s nested entities has that name
|
|
48
47
|
def find_entity(construct, name)
|
|
49
48
|
construct.entities.each do |candidate|
|
|
50
49
|
return candidate if candidate.hecks_name == name
|
|
@@ -55,11 +54,29 @@ module Hecks
|
|
|
55
54
|
nil
|
|
56
55
|
end
|
|
57
56
|
|
|
57
|
+
# `position` is the first fold this runs into : a category declares
|
|
58
|
+
# `attribute :position, Position` — for the judge's own walk,
|
|
59
|
+
# `order_by :position` on its `DeclaredIn` ask — but no `*`
|
|
60
|
+
# constructor takes it as an argument. `contracts.rb` already says so,
|
|
61
|
+
# in the language every other derived field speaks :
|
|
62
|
+
# `derived: { position: :walk }`. The language says a category has a
|
|
63
|
+
# position ; it does not say a category's own constructor is handed
|
|
64
|
+
# one, and that second fact is exactly what `fields:` needs to answer.
|
|
65
|
+
# So the skip reads the category's own walk claims (`Contract#walked`)
|
|
66
|
+
# rather than restating `position` here — Handler, which has no
|
|
67
|
+
# walk-minted position, skips nothing.
|
|
68
|
+
#
|
|
69
|
+
# @param category [String, Symbol] the construct category's name, such as
|
|
70
|
+
# `"Aggregate"` or `"Command"`
|
|
71
|
+
# @return [Hash{Symbol => Array(Symbol, Symbol)}] a `contracts.rb`-shaped
|
|
72
|
+
# `fields:` table, one `field => [field, :plain]` entry per scalar,
|
|
73
|
+
# non-reference, non-walked attribute the language declares for `category`
|
|
58
74
|
def fields_for(category)
|
|
59
75
|
language = construct_for(MetaValidator.grammar_registry.bluebook("Bluebook"), category.to_s)
|
|
76
|
+
walked = Assembly.contract(category).walked
|
|
60
77
|
language.attributes.each_with_object({}) do |attribute, fields|
|
|
61
78
|
next if attribute.list? || attribute.reference?
|
|
62
|
-
next if
|
|
79
|
+
next if walked.include?(attribute.name)
|
|
63
80
|
|
|
64
81
|
fields[attribute.name] = [attribute.name, :plain]
|
|
65
82
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Bluebook
|
|
3
|
-
# A
|
|
3
|
+
# A graph built from declarations, rather than from DSL calls.
|
|
4
4
|
#
|
|
5
5
|
# This is the half that lets the language orchestrate. `Reconstruction` reads a
|
|
6
6
|
# chapter back out of the meta-domain, in declaration order, as plain
|
|
@@ -8,32 +8,50 @@ module Hecks
|
|
|
8
8
|
# into the graph the runtime runs: IR aggregates with their verbs, value
|
|
9
9
|
# objects, entities and asks, owned by the chapter that declares them.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
11
|
+
# ## Why a hash, not a runtime
|
|
12
|
+
#
|
|
13
|
+
# It takes a hash, not a runtime, on purpose. That makes it a pure inverse of
|
|
12
14
|
# `to_h` and testable without the meta-domain in the picture at all:
|
|
13
15
|
#
|
|
14
16
|
# Assembly.call(built.to_h).to_h == built.to_h
|
|
15
17
|
#
|
|
16
18
|
# which is the check `spec/assembly_spec` makes for every chapter in the tree.
|
|
17
19
|
# Feed it the reconstruction instead and the same code assembles what the
|
|
18
|
-
#
|
|
20
|
+
# language holds — the only difference being where the declarations came from.
|
|
21
|
+
#
|
|
22
|
+
# ## One table, not a method per category
|
|
19
23
|
#
|
|
20
|
-
#
|
|
24
|
+
# Every field is read from one table. There is no method per category here:
|
|
21
25
|
# `Contracts` names what the language cannot say about a construct, `Build`
|
|
22
|
-
# reads it, and the coverage gate holds the table to the language.
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
+
# reads it, and the coverage gate holds the table to the language. A method
|
|
27
|
+
# per category can decorate a verb the language declares without ever
|
|
28
|
+
# offering it; the table cannot, because `spec/assembly_spec` checks every
|
|
29
|
+
# field the language declares against it.
|
|
30
|
+
#
|
|
31
|
+
# ## What stays hand-written
|
|
26
32
|
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
33
|
+
# The containment — which construct holds which — stays hand-written; it is
|
|
34
|
+
# not a field table. The runtime surface is no longer built here at all — the
|
|
35
|
+
# door is a per-boot projection, facade/surface.rb.
|
|
30
36
|
class Assembly
|
|
37
|
+
# Builds the graph the runtime runs from one chapter's declared hash.
|
|
38
|
+
#
|
|
39
|
+
# @param declaration [Hash{Symbol => Object}] a chapter's declarations, in the
|
|
40
|
+
# shape `Chapter#to_h` spells
|
|
41
|
+
# @return [Bluebook::Chapter] the assembled chapter, with its aggregates, read
|
|
42
|
+
# models and process managers built and wired
|
|
31
43
|
def self.call(declaration) = new(declaration).bluebook
|
|
32
44
|
|
|
45
|
+
# @param declaration [Hash{Symbol => Object}] a chapter's declarations, in the
|
|
46
|
+
# shape `Chapter#to_h` spells
|
|
33
47
|
def initialize(declaration)
|
|
34
48
|
@declaration = declaration
|
|
35
49
|
end
|
|
36
50
|
|
|
51
|
+
# Assembles this instance's declared hash into the chapter's runtime graph.
|
|
52
|
+
#
|
|
53
|
+
# @return [Bluebook::Chapter] the assembled chapter, with its aggregates, read
|
|
54
|
+
# models and process managers built and wired
|
|
37
55
|
def bluebook
|
|
38
56
|
aggregates = Array(@declaration[:aggregates]).map { |row| AggregateAssembly.new(row).aggregate }
|
|
39
57
|
models = Array(@declaration[:read_models]).map { |row| Build.call("ReadModel", row) }
|
|
@@ -50,7 +68,7 @@ module Hecks
|
|
|
50
68
|
private
|
|
51
69
|
|
|
52
70
|
# Every reaction the chapter holds, and each one also handed back to the head
|
|
53
|
-
# that declared it — the builder keeps a policy in
|
|
71
|
+
# that declared it — the builder keeps a policy in both places, hoisting it onto
|
|
54
72
|
# the chapter where the runtime reads it while the head keeps its own list. The
|
|
55
73
|
# language records which head, so the assembly can put it back.
|
|
56
74
|
def reactions(aggregates)
|
|
@@ -72,10 +90,10 @@ module Hecks
|
|
|
72
90
|
dispatches: Array(row[:dispatches]).map { |leg| dispatch(leg) })
|
|
73
91
|
end
|
|
74
92
|
|
|
75
|
-
# `compensates` — a
|
|
93
|
+
# `compensates` — a plain hash on the declaration (`Reconstruction#
|
|
76
94
|
# dispatch`'s own comment for why), built into the real
|
|
77
95
|
# `DispatchSpec` its own field actually is by recursing through
|
|
78
|
-
#
|
|
96
|
+
# this same method, one level in — the identical move `handler`
|
|
79
97
|
# itself takes into `dispatches`, one level up. `nil` when there
|
|
80
98
|
# is nothing to compensate; `Build.call` never sees a
|
|
81
99
|
# `compensates:` key it does not know how to read either way, the
|
|
@@ -28,15 +28,15 @@ module Hecks
|
|
|
28
28
|
|
|
29
29
|
attr_reader :name, :type, :default, :pattern, :admits, :relationship
|
|
30
30
|
|
|
31
|
-
# A Reference is kept
|
|
31
|
+
# A Reference is kept as itself. Every other type is still a name, and
|
|
32
32
|
# crosses over as its construct does.
|
|
33
33
|
#
|
|
34
|
-
# `admits` names an
|
|
34
|
+
# `admits` names an already-declared closed set the value must belong
|
|
35
35
|
# to — `Vocabulary::QueryComparator` — spelled aggregate-qualified
|
|
36
|
-
# because the set is a value object
|
|
36
|
+
# because the set is a value object inside an aggregate, and the
|
|
37
37
|
# aggregate is the only thing `reference_to` can reach.
|
|
38
38
|
#
|
|
39
|
-
#
|
|
39
|
+
# On the wire, because it is a rule and not only a typing hint.
|
|
40
40
|
#
|
|
41
41
|
# It began as neither. The link existed so a generator could type
|
|
42
42
|
# `WhereClause.op` as `WhereOp` — a typing convenience, not worth
|
|
@@ -48,9 +48,24 @@ module Hecks
|
|
|
48
48
|
# refused "burnt" through one reading and emitted the event through
|
|
49
49
|
# another.
|
|
50
50
|
#
|
|
51
|
-
# The wire carries the
|
|
51
|
+
# The wire carries the name, not the members. A reader resolves it
|
|
52
52
|
# against the IR it holds, so the members are declared once and
|
|
53
53
|
# copied nowhere — which is the same reason `admits` exists at all.
|
|
54
|
+
#
|
|
55
|
+
# @param name [Symbol, String] the attribute's name
|
|
56
|
+
# @param type [Module, Bluebook::Reference, String, Symbol] the bare constant naming
|
|
57
|
+
# a primitive or a value object, a `reference_to`-built Reference, or already-spelled
|
|
58
|
+
# text
|
|
59
|
+
# @param list [Boolean] whether this attribute holds a list of values rather than one
|
|
60
|
+
# @param default [Object, nil] the value a new record starts with when none is given
|
|
61
|
+
# @param optional [Boolean] whether a command may omit this attribute
|
|
62
|
+
# @param pattern [String, nil] a regex source the value must match; shared-engine
|
|
63
|
+
# subset only (`PatternSubset`)
|
|
64
|
+
# @param admits [String, nil] an already-declared closed set's aggregate-qualified
|
|
65
|
+
# name, such as `"Vocabulary::QueryComparator"`, that the value must belong to
|
|
66
|
+
# @param relationship [Symbol, nil] the DSL word that minted this attribute
|
|
67
|
+
# (`:reference_to`, `:has_many`, `:has_one` or `:belongs_to`), or `nil` for an
|
|
68
|
+
# ordinary attribute
|
|
54
69
|
def initialize(name:, type:, list: false, default: nil, optional: false, pattern: nil,
|
|
55
70
|
admits: nil, relationship: nil)
|
|
56
71
|
@name = name.to_sym
|
|
@@ -63,13 +78,13 @@ module Hecks
|
|
|
63
78
|
@relationship = relationship&.to_s
|
|
64
79
|
end
|
|
65
80
|
|
|
66
|
-
# A
|
|
81
|
+
# A bare constant in a bluebook is a name, even when Ruby has heard of it.
|
|
67
82
|
#
|
|
68
83
|
# `BluebookBuilder.build` says exactly this and installs a `const_missing`
|
|
69
84
|
# resolver that hands back the symbol — `attribute :target, Target` becomes
|
|
70
85
|
# the name "Target" and nothing looks Target up. That works only while the
|
|
71
|
-
# lookup
|
|
72
|
-
#
|
|
86
|
+
# lookup fails, and `Facade::Surface` installs every aggregate name as a
|
|
87
|
+
# top-level constant (its own comment, and `ConstShim`'s, both say so).
|
|
73
88
|
#
|
|
74
89
|
# So in one process: boot a domain with an aggregate named `Target`, then
|
|
75
90
|
# load a chapter whose own value object is called `Target`, and Ruby
|
|
@@ -77,11 +92,11 @@ module Hecks
|
|
|
77
92
|
# built against somebody else's aggregate — silently, with no refusal —
|
|
78
93
|
# and the attribute stops meaning what the file plainly says.
|
|
79
94
|
#
|
|
80
|
-
#
|
|
95
|
+
# Demodulised, so both paths spell it the same: `:Target` and
|
|
81
96
|
# `QualityControl::Target` are both "Target". A plain class stays itself —
|
|
82
97
|
# `String` demodulises to "String" — so the ordinary case is untouched.
|
|
83
98
|
# This does not undo the constant leak; it makes the leak unable to change
|
|
84
|
-
# what a chapter
|
|
99
|
+
# what a chapter means, which is the part that has to hold.
|
|
85
100
|
def spell(type)
|
|
86
101
|
return type if type.is_a?(Reference)
|
|
87
102
|
return Naming.demodulise(type) if type.is_a?(Module)
|
|
@@ -91,8 +106,7 @@ module Hecks
|
|
|
91
106
|
private :spell
|
|
92
107
|
|
|
93
108
|
# Held because a declared vocabulary pins it — spec/vocabulary_conformance
|
|
94
|
-
# holds `Primitive`'s members to this list.
|
|
95
|
-
# used to read it had no caller anywhere and is gone.
|
|
109
|
+
# holds `Primitive`'s members to this list.
|
|
96
110
|
PRIMITIVES = Hecks::Vocabulary.fetch("Primitive")
|
|
97
111
|
|
|
98
112
|
# `type` is spelled, never handed over. A Reference renders as
|
|
@@ -3,14 +3,14 @@ require_relative "traits"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Bluebook
|
|
5
5
|
module Behaviour
|
|
6
|
-
#
|
|
6
|
+
# What an aggregate does, as opposed to what it holds.
|
|
7
7
|
#
|
|
8
8
|
# The holding half — the field list, the readers, the emission — is
|
|
9
9
|
# the same list the language already declares in
|
|
10
10
|
# `language/bluebook/aggregate.bluebook`, said a second and third
|
|
11
11
|
# time in Ruby. This half is not: derived identity, the name
|
|
12
12
|
# indexes, the owner stamping and the finders are decisions about
|
|
13
|
-
#
|
|
13
|
+
# how the declared shape is used, and no grammar states them.
|
|
14
14
|
#
|
|
15
15
|
# Split so the holding half can be generated from the language
|
|
16
16
|
# without any of this being in the blast radius of a regeneration.
|
|
@@ -24,13 +24,19 @@ module Hecks
|
|
|
24
24
|
include Indexed
|
|
25
25
|
include Owns
|
|
26
26
|
|
|
27
|
-
# An aggregate is a
|
|
28
|
-
# where everything else is declared
|
|
27
|
+
# An aggregate is a member of its chapter's namespace — "Pizzas::Pizza" —
|
|
28
|
+
# where everything else is declared on its owner and joins with ".".
|
|
29
|
+
#
|
|
30
|
+
# @return [String] the literal string `"::"`
|
|
29
31
|
def hecks_separator = "::"
|
|
30
32
|
|
|
31
|
-
#
|
|
33
|
+
# The hook the generated constructor calls once every declared
|
|
32
34
|
# field is assigned. Nothing here is derivable from the
|
|
33
35
|
# declaration, which is exactly why it is not generated.
|
|
36
|
+
#
|
|
37
|
+
# @return [Bluebook::Aggregate] self, once identity is derived, every
|
|
38
|
+
# declaration is indexed and its commands, value objects, entities and
|
|
39
|
+
# queries are stamped as owned by it
|
|
34
40
|
def settle
|
|
35
41
|
derive_identity
|
|
36
42
|
index_declarations
|
|
@@ -40,42 +46,68 @@ module Hecks
|
|
|
40
46
|
self
|
|
41
47
|
end
|
|
42
48
|
|
|
49
|
+
# Builds every by-name finder index `settle` needs — attributes, value
|
|
50
|
+
# objects, commands, queries, ports and projected fields.
|
|
51
|
+
#
|
|
52
|
+
# @return [void]
|
|
43
53
|
def index_declarations
|
|
44
54
|
index_attributes(@attributes)
|
|
45
55
|
@value_objects_by_name = index_by_hecks_name(@value_objects)
|
|
46
56
|
@commands_by_name = index_by_hecks_name(@commands)
|
|
47
57
|
@queries_by_name = index_by_hecks_name(@queries)
|
|
48
58
|
@ports_by_name = @ports.to_h { |port| [port.name, port] }
|
|
49
|
-
# S12, ADR 0025 — keyed by
|
|
59
|
+
# S12, ADR 0025 — keyed by symbol, the same convention
|
|
50
60
|
# `Indexed#attribute` already uses; `GuardState` asks for one
|
|
51
61
|
# by name at every dispatch, the rebuild sweep walks all of
|
|
52
62
|
# them once per pass.
|
|
53
63
|
@projected_fields_by_name = @projected_fields.to_h { |field| [field.name, field] }
|
|
54
64
|
end
|
|
55
65
|
|
|
66
|
+
# Finds a declared `projects` field by its declared name.
|
|
67
|
+
#
|
|
68
|
+
# @param named [String, Symbol] the projected field's declared name
|
|
69
|
+
# @return [Bluebook::ProjectedField, nil] the field named `named`, or
|
|
70
|
+
# `nil` if none is declared under that name
|
|
56
71
|
def projected_field(named) = @projected_fields_by_name[named.to_sym]
|
|
57
72
|
|
|
58
|
-
# A value object is a
|
|
73
|
+
# A value object is a class now, so `name` is Ruby's answer (the constant
|
|
59
74
|
# path) and the declared name is `hecks_name`. This finder is on its way
|
|
60
|
-
# out — once an attribute's type
|
|
75
|
+
# out — once an attribute's type is the class there is nothing to find —
|
|
61
76
|
# but every consumer still asks by type string, so it stays until they
|
|
62
77
|
# stop.
|
|
78
|
+
#
|
|
79
|
+
# @param named [String, Symbol] the value object's declared name
|
|
80
|
+
# @return [Class, nil] the value object class (a `Bluebook::ValueObject`
|
|
81
|
+
# subclass) named `named`, or `nil` if none is declared under that name
|
|
63
82
|
def value_object(named) = @value_objects_by_name[named.to_s]
|
|
83
|
+
|
|
84
|
+
# Finds a port attached to this aggregate by its declared name.
|
|
85
|
+
#
|
|
86
|
+
# @param named [String, Symbol] the port's declared name
|
|
87
|
+
# @return [Bluebook::DomainPort, nil] the port named `named`, or `nil` if
|
|
88
|
+
# none is attached under that name
|
|
64
89
|
def port(named) = @ports_by_name[named.to_s]
|
|
65
90
|
|
|
66
|
-
# A
|
|
91
|
+
# A port is declared in the hecksagon, not the bluebook — the
|
|
67
92
|
# boundary between the domain and its adapters, in hexagonal terms,
|
|
68
|
-
# is exactly what a `.hecksagon` file already
|
|
69
|
-
# port (persistence, projection, ...). So this attaches
|
|
93
|
+
# is exactly what a `.hecksagon` file already is for every other
|
|
94
|
+
# port (persistence, projection, ...). So this attaches after the
|
|
70
95
|
# aggregate already exists and is registered — `HecksagonBuilder`
|
|
71
96
|
# calls it once per `port` declaration, having already stamped each
|
|
72
97
|
# operation's reference attributes with `declared_in = self`, since
|
|
73
98
|
# nothing upstream of a hecksagon load does that for it.
|
|
99
|
+
#
|
|
100
|
+
# @param port [Bluebook::DomainPort] the aggregate-scoped port to attach
|
|
101
|
+
# @return [void]
|
|
74
102
|
def add_port(port)
|
|
75
103
|
@ports << port
|
|
76
104
|
@ports_by_name[port.name] = port
|
|
77
105
|
end
|
|
78
106
|
|
|
107
|
+
# Names the table, file or key persistence adapters store this aggregate
|
|
108
|
+
# under.
|
|
109
|
+
#
|
|
110
|
+
# @return [String] the aggregate's name, snake-cased
|
|
79
111
|
def storage_name = Naming.snake(@name)
|
|
80
112
|
end
|
|
81
113
|
end
|
|
@@ -1,25 +1,38 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Bluebook
|
|
3
3
|
module Behaviour
|
|
4
|
-
#
|
|
4
|
+
# **What an attribute does**. The declared half — name, type, list,
|
|
5
5
|
# default, optional, pattern, admits — is what the language states
|
|
6
6
|
# in `aggregate.bluebook`'s own `Field`. These are the questions
|
|
7
|
-
# readers ask
|
|
7
|
+
# readers ask about that shape, which no declaration states.
|
|
8
8
|
module Attribute
|
|
9
|
+
# Says whether this attribute was declared `list`.
|
|
10
|
+
#
|
|
11
|
+
# @return [Boolean] whether this attribute holds a list of values rather than one
|
|
9
12
|
def list? = @list
|
|
13
|
+
|
|
14
|
+
# Says whether this attribute holds a single value rather than a list.
|
|
15
|
+
#
|
|
16
|
+
# @return [Boolean] whether this attribute holds a single value rather than a list
|
|
10
17
|
def scalar? = !@list
|
|
18
|
+
|
|
19
|
+
# Says whether this attribute's type is another aggregate reached via `reference_to`.
|
|
20
|
+
#
|
|
21
|
+
# @return [Boolean] whether this attribute's type is a `reference_to` another aggregate
|
|
11
22
|
def reference? = @type.is_a?(Reference)
|
|
12
23
|
|
|
13
|
-
#
|
|
24
|
+
# May this fact be left out?
|
|
14
25
|
#
|
|
15
26
|
# Required is the default and by far the common case — a command takes
|
|
16
|
-
# the arguments it declares, and all of them — so the
|
|
27
|
+
# the arguments it declares, and all of them — so the exception is what
|
|
17
28
|
# gets marked. Marking the other way would annotate almost every
|
|
18
29
|
# attribute in the corpus to say nothing.
|
|
19
30
|
#
|
|
20
|
-
# Only a
|
|
31
|
+
# Only a command enforces this. An aggregate's own attributes are filled
|
|
21
32
|
# by the commands that set them, and a value object's by its
|
|
22
33
|
# constructor ; neither is a payload anyone hands in.
|
|
34
|
+
#
|
|
35
|
+
# @return [Boolean] whether a command may omit this attribute from its payload
|
|
23
36
|
def optional? = @optional
|
|
24
37
|
end
|
|
25
38
|
end
|
|
@@ -3,19 +3,22 @@ require_relative "traits"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Bluebook
|
|
5
5
|
module Behaviour
|
|
6
|
-
#
|
|
6
|
+
# **What a chapter does**. The declared half is the roll-call of what a
|
|
7
7
|
# bluebook holds; these are the finders over it, plus `verbs` — the
|
|
8
8
|
# chapter's own list of every dispatchable name, which is derived
|
|
9
9
|
# from the aggregates rather than declared anywhere.
|
|
10
10
|
module Chapter
|
|
11
11
|
include Owns
|
|
12
12
|
|
|
13
|
-
#
|
|
14
|
-
# declaration does not state: that a chapter is the
|
|
13
|
+
# The hook the generated constructor calls. Three things a
|
|
14
|
+
# declaration does not state: that a chapter is the root of the
|
|
15
15
|
# owner chain (nothing declares it, it is what having no owner
|
|
16
|
-
#
|
|
16
|
+
# means), the ports table — which a `.hecksagon` fills later, so
|
|
17
17
|
# the bluebook cannot declare it — and stamping its own children,
|
|
18
18
|
# the same act an Aggregate performs one level down.
|
|
19
|
+
#
|
|
20
|
+
# @return [Bluebook::Chapter] self, once root marking, the ports table and ownership
|
|
21
|
+
# stamping are all set up
|
|
19
22
|
def settle
|
|
20
23
|
@hecks_root = true
|
|
21
24
|
@ports = []
|
|
@@ -24,18 +27,83 @@ module Hecks
|
|
|
24
27
|
self
|
|
25
28
|
end
|
|
26
29
|
|
|
30
|
+
# Finds a declared aggregate by name.
|
|
31
|
+
#
|
|
32
|
+
# @param named [String, Symbol] the aggregate's declared name
|
|
33
|
+
# @return [Bluebook::Aggregate, nil] the aggregate, or `nil` if none is declared
|
|
34
|
+
# by that name
|
|
27
35
|
def aggregate(named) = @aggregates.find { |a| a.name == named.to_s }
|
|
36
|
+
|
|
37
|
+
# Finds a declared read model by its own name or by the query name it answers.
|
|
38
|
+
#
|
|
39
|
+
# @param named [String, Symbol] the read model's declared name, or its query name
|
|
40
|
+
# @return [Bluebook::ReadModel, nil] the read model, or `nil` if none matches
|
|
28
41
|
def read_model(named) = @read_models.find { |model| model.name == named.to_s || model.query_name == named.to_s }
|
|
42
|
+
|
|
43
|
+
# Finds a port declared at this chapter's root by name.
|
|
44
|
+
#
|
|
45
|
+
# @param named [String, Symbol] the port's declared name
|
|
46
|
+
# @return [Bluebook::DomainPort, nil] the port, or `nil` if none is declared
|
|
47
|
+
# by that name
|
|
29
48
|
def port(named) = @ports_by_name[named.to_s]
|
|
30
49
|
|
|
31
|
-
#
|
|
50
|
+
# What this chapter declared it provides — `{ key => local verb }`
|
|
51
|
+
# for one capability, or nil when it declares none. Read by
|
|
52
|
+
# everything that resolves a capability's provider from what a
|
|
53
|
+
# chapter declares, rather than from the chapter's own name
|
|
54
|
+
# (`Registry#authorization_provider_for`).
|
|
55
|
+
#
|
|
56
|
+
# @param capability [String, Symbol] the capability's name, such as
|
|
57
|
+
# `Bluebook::Capabilities::AUTHORIZATION`
|
|
58
|
+
# @return [Hash{Symbol => String}, nil] each declared key mapped to its local verb,
|
|
59
|
+
# or `nil` if this chapter declares no `provides` row for that capability
|
|
60
|
+
def provision(capability)
|
|
61
|
+
rows = @provides.select { |row| row.capability == capability.to_s }
|
|
62
|
+
rows.empty? ? nil : rows.to_h { |row| [row.key.to_sym, row.verb] }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Says whether this chapter declares that it provides a capability.
|
|
66
|
+
#
|
|
67
|
+
# @param capability [String, Symbol] the capability's name
|
|
68
|
+
# @return [Boolean] whether this chapter declares a `provides` row for that capability
|
|
69
|
+
def provides?(capability) = !provision(capability).nil?
|
|
70
|
+
|
|
71
|
+
# The declared verb for `key`, qualified with this chapter's own
|
|
72
|
+
# name — the spelling `Dispatcher#dispatch`/`#query` take.
|
|
73
|
+
#
|
|
74
|
+
# @param capability [String, Symbol] the capability's name
|
|
75
|
+
# @param key [String, Symbol] the provided key to resolve
|
|
76
|
+
# @return [String, nil] the verb qualified as `"ChapterName::verb"`, or `nil` if this
|
|
77
|
+
# chapter provides no such capability or key
|
|
78
|
+
def provided_verb(capability, key)
|
|
79
|
+
local = provision(capability)&.fetch(key.to_sym, nil)
|
|
80
|
+
local && "#{name}::#{local}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# A port is declared in the hecksagon, not the bluebook — so it
|
|
32
84
|
# attaches after the chapter already exists, the same way an
|
|
33
85
|
# aggregate's own ports do.
|
|
86
|
+
#
|
|
87
|
+
# @param port [Bluebook::DomainPort] the operations-shaped port to attach
|
|
88
|
+
# @return [void]
|
|
34
89
|
def add_port(port)
|
|
35
90
|
@ports << port
|
|
36
91
|
@ports_by_name[port.name] = port
|
|
37
92
|
end
|
|
38
93
|
|
|
94
|
+
# A TRANSLATED REACTION IS ALSO DECLARED IN THE HECKSAGON —
|
|
95
|
+
# `HecksagonBuilder#translates` builds an ordinary `Policy` (same
|
|
96
|
+
# shape, same `PolicyInterpreter` runtime, as one written inside a
|
|
97
|
+
# `policy` block in this chapter's own `.bluebook`) and attaches it
|
|
98
|
+
# here, after the chapter already exists, the same reasoning
|
|
99
|
+
# `add_port` gives one method up. Kept out of the bluebook itself
|
|
100
|
+
# on purpose: which FOREIGN domain's event this chapter reacts to
|
|
101
|
+
# is a wiring/context-mapping decision, not a fact the domain
|
|
102
|
+
# states about its own model.
|
|
103
|
+
def add_policy(policy)
|
|
104
|
+
@policies << policy
|
|
105
|
+
end
|
|
106
|
+
|
|
39
107
|
# Every dispatchable name this chapter answers to, spelled exactly
|
|
40
108
|
# as Dispatcher#dispatch takes it. Derived from the aggregates,
|
|
41
109
|
# never declared — which is why Projections::OIDC can hold its own
|
|
@@ -50,6 +118,9 @@ module Hecks
|
|
|
50
118
|
# already proved out for the meta-domain's own grammar (S17, ADR
|
|
51
119
|
# 0026) — entities nest arbitrarily deep (`Dispatch`, inside
|
|
52
120
|
# `Handler`), so one flat level isn't enough.
|
|
121
|
+
#
|
|
122
|
+
# @return [Array<String>] every command verb reachable on this chapter, spelled
|
|
123
|
+
# `"Domain::Aggregate.command"` or, nested, `"Domain::Aggregate.Entity.command"`
|
|
53
124
|
def verbs
|
|
54
125
|
@aggregates.flat_map { |agg| aggregate_verbs(agg) }
|
|
55
126
|
end
|