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
|
@@ -20,56 +20,81 @@ module Hecks
|
|
|
20
20
|
UNSET = Object.new.freeze
|
|
21
21
|
private_constant :UNSET
|
|
22
22
|
|
|
23
|
+
# Returns the attributes declared so far, minting the accumulating list on first use.
|
|
24
|
+
#
|
|
25
|
+
# @return [Array<Bluebook::Attribute>] the attributes declared so far, in declaration order
|
|
23
26
|
def attributes = @attributes ||= []
|
|
24
27
|
|
|
25
28
|
# Value objects synthesised from inline closed sets, collected here and
|
|
26
29
|
# installed by whoever owns value objects (the aggregate).
|
|
30
|
+
#
|
|
31
|
+
# @return [Array<Bluebook::ValueObject>] the value objects synthesised so far
|
|
27
32
|
def closed_sets = @closed_sets ||= []
|
|
28
33
|
|
|
29
|
-
#
|
|
34
|
+
# Declares one field on the owning construct, with its type and, optionally, a default,
|
|
35
|
+
# an optionality flag, a pattern, a closed-set reference, or an inline closed set.
|
|
36
|
+
#
|
|
37
|
+
# `admits:` names a closed set that is already declared elsewhere —
|
|
30
38
|
#
|
|
31
39
|
# attribute :op, String, admits: "Vocabulary::QueryComparator"
|
|
32
40
|
#
|
|
33
|
-
# which is the difference between it and `one_of`: `one_of`
|
|
41
|
+
# which is the difference between it and `one_of`: `one_of` synthesises
|
|
34
42
|
# a fresh value object named for the attribute, so it can only ever name
|
|
35
43
|
# something new. `admits` points at a set the language already holds, so
|
|
36
44
|
# the same set can be named from many places without being written twice.
|
|
37
45
|
#
|
|
38
|
-
#
|
|
46
|
+
# Qualified, because a closed set is a value object inside an aggregate
|
|
39
47
|
# and `reference_to` reaches heads only — so this is text, checked where
|
|
40
48
|
# it is read rather than by reference resolution.
|
|
41
49
|
#
|
|
42
|
-
#
|
|
50
|
+
# And written as text, not as the constant path `Vocabulary::QueryComparator`
|
|
43
51
|
# it reads like. The constant spelling was tried — `ConstShim` returning
|
|
44
52
|
# a Module so Ruby's `::` reaches a second `const_missing` — and it
|
|
45
53
|
# cannot hold: `Facade::Surface` installs every aggregate name as a
|
|
46
|
-
#
|
|
54
|
+
# top-level constant, so the moment any facade exists, `Vocabulary`
|
|
47
55
|
# resolves to that module and the shim is never asked. A spelling that
|
|
48
56
|
# works only until a facade is built is worse than a quoted one.
|
|
49
57
|
#
|
|
50
|
-
#
|
|
58
|
+
# The type position takes a bare constant, always required (ADR 0025,
|
|
51
59
|
# "Attributes") — omitting it (a mint default of String) and quoting
|
|
52
60
|
# it as text both refuse now. Neither had a real reason left: no
|
|
53
61
|
# corpus attribute ever omitted the type, and `ConstShim#const_missing`
|
|
54
62
|
# (S0b) already resolves a bare, not-yet-declared constant to the
|
|
55
|
-
#
|
|
63
|
+
# same forward reference the quoted form existed for — a bareword
|
|
56
64
|
# `Name` reaches a value object named "Name" declared later in the
|
|
57
|
-
# same block exactly
|
|
58
|
-
# renders either one identically.
|
|
59
|
-
# era text (checked directly), so
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
# The word `attribute` itself is
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
# name
|
|
65
|
+
# same block, exactly what the quoted form `"Name"` would have
|
|
66
|
+
# reached — `spell`'s own `to_s` renders either one identically.
|
|
67
|
+
# Neither form appears in any frozen era text (checked directly), so
|
|
68
|
+
# both are refused unconditionally — nothing for
|
|
69
|
+
# `MetaValidator.shadow_parsing?` to answer for.
|
|
70
|
+
#
|
|
71
|
+
# The word `attribute` itself is not a real method any builder answers
|
|
72
|
+
# directly: every (context, word) Keyword row for it carries
|
|
73
|
+
# `calls: "attribute_impl"`, and `GenericDispatch` forwards the whole
|
|
74
|
+
# call here untouched. `attribute_collector_spec.rb`
|
|
75
|
+
# (`AttributeCollector has no method without a test` —
|
|
76
|
+
# dsl_coverage_spec.rb) and the bootstrap fallback
|
|
77
|
+
# (`GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`) both name this same
|
|
78
|
+
# string; they must never drift apart.
|
|
79
|
+
#
|
|
80
|
+
# @param name [Symbol] the attribute's name
|
|
81
|
+
# @param type [Module, Symbol, ListOf, OneOf] the attribute's type: a bare constant
|
|
82
|
+
# (resolved by `ConstShim`), or the `ListOf`/`OneOf` wrapper `list_of`/`one_of` return
|
|
83
|
+
# when called in this same type position; quoted text is refused
|
|
84
|
+
# @param default [Object, nil] the value an omitted attribute defaults to; not type-checked
|
|
85
|
+
# here
|
|
86
|
+
# @param optional [Boolean] whether the attribute may be omitted entirely
|
|
87
|
+
# @param pattern [Regexp, String, nil] a pattern the attribute's value must match; refused
|
|
88
|
+
# if it uses a construct `PatternSubset` disallows
|
|
89
|
+
# @param admits [String, nil] the qualified name of an already-declared closed set this
|
|
90
|
+
# attribute's value may come from, such as `"Vocabulary::QueryComparator"`
|
|
91
|
+
# @param one_of [Array<String, Symbol>, nil] permitted values for a field-shaped closed
|
|
92
|
+
# set, declared inline via `one_of: [...]`; meaningful only inside a `value_object`
|
|
93
|
+
# (`ValueObjectBuilder` overrides `install_inline_closed_set`) — refused everywhere else
|
|
94
|
+
# @return [void]
|
|
95
|
+
# @raise [Bluebook::DSL::Malformed] if `name` is already declared, `type` is omitted or
|
|
96
|
+
# quoted text, `pattern` uses a disallowed construct, or `one_of:` is given outside a
|
|
97
|
+
# `value_object`
|
|
73
98
|
def attribute_impl(name, type = UNSET, default: nil, optional: false, pattern: nil,
|
|
74
99
|
admits: nil, one_of: nil)
|
|
75
100
|
# moved to the language: FieldName invariant, on Root.Attribute
|
|
@@ -109,20 +134,25 @@ module Hecks
|
|
|
109
134
|
install_inline_closed_set(name, one_of) if one_of
|
|
110
135
|
end
|
|
111
136
|
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
# position (`attribute :x,
|
|
115
|
-
# list_of`
|
|
116
|
-
#
|
|
117
|
-
# fallback, the same
|
|
118
|
-
#
|
|
119
|
-
# it), so
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
# reasoning as the
|
|
137
|
+
# Wraps a type so `attribute_impl` records it as a list-valued attribute.
|
|
138
|
+
#
|
|
139
|
+
# Called in an attribute's own type position (`attribute :x,
|
|
140
|
+
# list_of(Y)`), never through a `def list_of` any one builder
|
|
141
|
+
# answers as its own word — reached via
|
|
142
|
+
# `WordGate#word_gate_dispatch`'s "Type"-context fallback, the same
|
|
143
|
+
# one `one_of_impl` below uses. Bootstrap-reachable (every core
|
|
144
|
+
# chapter's own list-typed attributes use it), so
|
|
145
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` carries a single
|
|
146
|
+
# `["Type", "list_of"]` entry rather than one per calling context —
|
|
147
|
+
# the bootstrap branch checks that key too, same reasoning as the
|
|
148
|
+
# ordinary fallback.
|
|
149
|
+
#
|
|
150
|
+
# @param type [Module, Symbol] the list's element type, a bare constant
|
|
151
|
+
# @return [Bluebook::DSL::AttributeCollector::ListOf] the wrapper `attribute_impl` reads
|
|
152
|
+
# to mark the attribute list-valued
|
|
123
153
|
def list_of_impl(type) = ListOf.new(type)
|
|
124
154
|
|
|
125
|
-
# `reference_to Account`
|
|
155
|
+
# `reference_to Account` mints `:account` — no `_id` — the default
|
|
126
156
|
# every `reference_to`-shaped word (`AggregateBuilder`,
|
|
127
157
|
# `EntityBuilder`, `CommandBuilder#cross_reference`,
|
|
128
158
|
# `QueryBuilder`, `PortOperationBuilder`, all `include
|
|
@@ -132,13 +162,13 @@ module Hecks
|
|
|
132
162
|
# `.`; deriving the hop name from `account_id` is what made an
|
|
133
163
|
# explicit operator impossible before).
|
|
134
164
|
#
|
|
135
|
-
#
|
|
136
|
-
# was minted under the
|
|
137
|
-
# reconstructs a held aggregate's shape to
|
|
138
|
-
# current one — reading that text through the
|
|
139
|
-
# silently reconstruct the
|
|
165
|
+
# Legacy under shadow-parsing (S0a's own bridge): frozen era text
|
|
166
|
+
# was minted under the old default, and `EraGuard.shadow_parse`
|
|
167
|
+
# reconstructs a held aggregate's shape to diff against the
|
|
168
|
+
# current one — reading that text through the new default would
|
|
169
|
+
# silently reconstruct the wrong historical name, not merely
|
|
140
170
|
# refuse a spelling the way S1's `identified_by` legacy forms do.
|
|
141
|
-
# This is a mint
|
|
171
|
+
# This is a mint default changing, not a syntax being removed, so
|
|
142
172
|
# there is nothing to refuse here — only a fork in what gets
|
|
143
173
|
# minted when `as:` is omitted.
|
|
144
174
|
private def default_reference_name(target)
|
|
@@ -146,25 +176,31 @@ module Hecks
|
|
|
146
176
|
:"#{Naming.snake(target)}#{suffix}"
|
|
147
177
|
end
|
|
148
178
|
|
|
149
|
-
# A closed set declared
|
|
179
|
+
# A closed set declared inline on the attribute:
|
|
150
180
|
#
|
|
151
181
|
# attribute :status, one_of("open", "shut")
|
|
152
182
|
#
|
|
153
183
|
# Desugars to a value object named for the attribute, so it goes through
|
|
154
184
|
# exactly the machinery a hand-written one_of does — and so the
|
|
155
|
-
# attribute's type is still a
|
|
185
|
+
# attribute's type is still a declared value object, which is now a
|
|
156
186
|
# structural rule rather than a predicate.
|
|
157
187
|
#
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
# pinned
|
|
162
|
-
#
|
|
163
|
-
#
|
|
188
|
+
# Wraps a list of permitted values so `attribute_impl` synthesises a closed-set value
|
|
189
|
+
# object for them.
|
|
190
|
+
#
|
|
191
|
+
# The desugaring is pinned — the same bluebook must always yield the
|
|
192
|
+
# same IR, so a plain String attribute silently discarding the
|
|
193
|
+
# values is not an outcome this spelling can produce.
|
|
194
|
+
#
|
|
195
|
+
# Reached the same way `list_of_impl` above is. Same name as
|
|
164
196
|
# `ValueObjectBuilder#one_of_impl`'s own override on purpose —
|
|
165
197
|
# that method's own `super(*values)` call (the no-block, bare
|
|
166
|
-
# type-position case) resolves by
|
|
198
|
+
# type-position case) resolves by method name up the ancestor
|
|
167
199
|
# chain, and renaming only one side would silently break it.
|
|
200
|
+
#
|
|
201
|
+
# @param values [Array<String, Symbol>] the permitted values
|
|
202
|
+
# @return [Bluebook::DSL::AttributeCollector::OneOf] the wrapper `attribute_impl` reads to
|
|
203
|
+
# synthesise the closed-set value object
|
|
168
204
|
def one_of_impl(*values) = OneOf.new(values)
|
|
169
205
|
|
|
170
206
|
private
|
|
@@ -180,11 +216,11 @@ module Hecks
|
|
|
180
216
|
)
|
|
181
217
|
end
|
|
182
218
|
|
|
183
|
-
# `one_of:`
|
|
219
|
+
# `one_of:` names a closed set on the field itself (ADR 0025,
|
|
184
220
|
# "Attributes") — joining `pattern:`/`admits:` where value
|
|
185
221
|
# constraints already live, for the one context where it means
|
|
186
222
|
# anything: a `value_object` block, where it replaces the old
|
|
187
|
-
# `one_of do member ... end` wrapper for a
|
|
223
|
+
# `one_of do member ... end` wrapper for a single-field set (a
|
|
188
224
|
# multi-field set still writes bare `member` lines, unwrapped).
|
|
189
225
|
# `ValueObjectBuilder` overrides this; every other includer
|
|
190
226
|
# (Aggregate/Entity/Command/Query/PortOperation) inherits this
|
|
@@ -198,15 +234,15 @@ module Hecks
|
|
|
198
234
|
"with the type-position one_of(...) instead"
|
|
199
235
|
end
|
|
200
236
|
|
|
201
|
-
# A
|
|
202
|
-
#
|
|
237
|
+
# A name declared twice on the same owner is two attributes sharing
|
|
238
|
+
# one name, and nothing downstream disambiguates them — every
|
|
203
239
|
# reader that walks `attributes` looking for one by name
|
|
204
240
|
# (`seal_mutation_targets`, `seal_query_field`, `projects`'s own
|
|
205
241
|
# local check, `Instance#[]`, ...) uses `Array#find`/`any?`, which
|
|
206
242
|
# silently answers whichever declaration happens to come first and
|
|
207
|
-
# discards the second
|
|
208
|
-
#
|
|
209
|
-
#
|
|
243
|
+
# discards the second — left unrefused, both declarations would
|
|
244
|
+
# survive into the IR, one of them permanently unreachable by name.
|
|
245
|
+
# Refused here, at the one place every owner
|
|
210
246
|
# (Aggregate/Entity/Command/Query/PortOperation/ValueObject, each
|
|
211
247
|
# `include AttributeCollector`) mints an attribute through, rather
|
|
212
248
|
# than taught to each of those readers individually.
|
|
@@ -216,7 +252,7 @@ module Hecks
|
|
|
216
252
|
raise Malformed, "#{name} is declared twice — an attribute name is declared once, not twice"
|
|
217
253
|
end
|
|
218
254
|
|
|
219
|
-
# A pattern is refused
|
|
255
|
+
# A pattern is refused at declaration, not when a value first meets it :
|
|
220
256
|
# a regex whose meaning depends on which engine reads it is a defect in
|
|
221
257
|
# the bluebook, and a bluebook that loads is one whose patterns carry
|
|
222
258
|
# one meaning. PatternSubset says which those are, and why each is refused.
|
|
@@ -246,9 +282,9 @@ module Hecks
|
|
|
246
282
|
def resolve_identity_field!(field, value_objects, context_name)
|
|
247
283
|
attr = attributes.find { |a| a.name == field }
|
|
248
284
|
|
|
249
|
-
# `:id`
|
|
250
|
-
# not a typo to refuse — it is the language's own
|
|
251
|
-
#
|
|
285
|
+
# `:id` or an `_id`-suffixed name with no matching attribute is
|
|
286
|
+
# not a typo to refuse — it is the language's own walk-parent/
|
|
287
|
+
# fallback-identity convention. The `_id` suffix is the meta-
|
|
252
288
|
# domain's own (the `Command`/`Entity`/`Aggregate` etc. records
|
|
253
289
|
# identify by `owner_id`/`bluebook_id`/`aggregate_id`, supplied
|
|
254
290
|
# by the judge's replay rather than declared locally — see
|
|
@@ -285,26 +321,26 @@ module Hecks
|
|
|
285
321
|
"#{context_name}.identified_by #{target} mints :#{field}, but that attribute is already declared"
|
|
286
322
|
end
|
|
287
323
|
|
|
288
|
-
# `Attribute.new`
|
|
289
|
-
# entry — `target` is `Naming.demodulise`'d
|
|
324
|
+
# `Attribute.new` directly, not the public `attribute(...)` DSL
|
|
325
|
+
# entry — `target` is `Naming.demodulise`'d text, not a bareword
|
|
290
326
|
# the bluebook author typed (the type position's own quoted-text
|
|
291
327
|
# refusal is about DSL source, not internal minting), and `vo`
|
|
292
|
-
#
|
|
328
|
+
# itself can't be passed either: it is the real, already-built
|
|
293
329
|
# `ValueObject` subclass, permanently anonymous from Ruby's own
|
|
294
330
|
# `to_s` (only `hecks_name` carries its name) — `Attribute#spell`
|
|
295
331
|
# would demodulise it to "#<Class:0x...>", not "PizzaName".
|
|
296
332
|
attributes << Attribute.new(name: field, type: target)
|
|
297
|
-
#
|
|
333
|
+
# Moved to `insert_at` — the attribute count at the moment
|
|
298
334
|
# `identified_by` was actually called, captured by the caller —
|
|
299
335
|
# not left where `attribute` just appended it. Resolution happens
|
|
300
|
-
# at
|
|
301
|
-
# already run, so appending would put the identity field
|
|
336
|
+
# at build time, after every other attribute in the block has
|
|
337
|
+
# already run, so appending would put the identity field last
|
|
302
338
|
# regardless of where `identified_by` was actually written.
|
|
303
339
|
# Most real bluebooks write it first (insert_at 0); one (a
|
|
304
340
|
# ScheduledPayment corpus member) writes it after a reference_to
|
|
305
|
-
# and an attribute — this matches either,
|
|
306
|
-
# hand-writing `attribute field, Type` at that
|
|
307
|
-
#
|
|
341
|
+
# and an attribute — this matches either, producing the same
|
|
342
|
+
# result a person hand-writing `attribute field, Type` at that
|
|
343
|
+
# exact point would.
|
|
308
344
|
attributes.insert(insert_at, attributes.pop)
|
|
309
345
|
vo.attributes.flat_map do |attribute|
|
|
310
346
|
identity_paths_for_attribute(attribute, value_objects, context_name,
|
|
@@ -332,13 +368,14 @@ module Hecks
|
|
|
332
368
|
raise Malformed, "#{context_name}'s identity value objects form a cycle: #{cycle}"
|
|
333
369
|
end
|
|
334
370
|
|
|
335
|
-
# A
|
|
371
|
+
# **A bare field derives one scalar**. `identified_by :ref` (or one leg
|
|
336
372
|
# of a compound `identified_by :a, :b`) names a single field, and
|
|
337
|
-
# deriving
|
|
373
|
+
# deriving its path only makes sense while every value object along
|
|
338
374
|
# the way wraps exactly one field itself — the same "single-field
|
|
339
|
-
# value object" ADR 0025 names this shape after.
|
|
340
|
-
# value object here
|
|
341
|
-
# own fields, minting an unannounced compound key
|
|
375
|
+
# value object" ADR 0025 names this shape after. Left unrefused, a
|
|
376
|
+
# multi-field value object here would expand silently into every
|
|
377
|
+
# one of its own fields, minting an unannounced compound key
|
|
378
|
+
# nothing declared.
|
|
342
379
|
if nested.attributes.size != 1
|
|
343
380
|
candidates = nested.attributes.map(&:name).join(", ")
|
|
344
381
|
raise Malformed,
|
|
@@ -7,6 +7,13 @@ module Hecks
|
|
|
7
7
|
# `Payment.persisted_by "Postgres"` into a queued `Bind`, and `#port`
|
|
8
8
|
# is the one real method, for `Aggregate.port("Name") do ... end`.
|
|
9
9
|
class BindingProxy
|
|
10
|
+
# Mints the stand-in module a bare domain constant resolves to inside a `.hecksagon` block.
|
|
11
|
+
#
|
|
12
|
+
# @param domain [Symbol, String] the domain name, the first segment of `Domain::Aggregate`
|
|
13
|
+
# @param collector [Array<Bluebook::Bind>] the list every bind made through the module's
|
|
14
|
+
# proxies is appended to
|
|
15
|
+
# @return [Module] an anonymous module whose `const_missing` answers a `BindingProxy`
|
|
16
|
+
# for `"Domain::Aggregate"`
|
|
10
17
|
def self.namespace(domain, collector)
|
|
11
18
|
Module.new do
|
|
12
19
|
define_singleton_method(:const_missing) do |aggregate|
|
|
@@ -15,17 +22,30 @@ module Hecks
|
|
|
15
22
|
end
|
|
16
23
|
end
|
|
17
24
|
|
|
25
|
+
# @param fqn [String] the aggregate's qualified name, `"Domain::Aggregate"`
|
|
26
|
+
# @param collector [Array<Bluebook::Bind>] the list each bind made on this proxy is
|
|
27
|
+
# appended to
|
|
18
28
|
def initialize(fqn, collector)
|
|
19
29
|
@fqn = fqn
|
|
20
30
|
@collector = collector
|
|
21
31
|
end
|
|
22
32
|
|
|
23
|
-
#
|
|
33
|
+
# Declares a port on this aggregate and attaches it to the already-registered bluebook.
|
|
34
|
+
#
|
|
35
|
+
# **The aggregate-scoped port** — `Payments::Payment.port("Gateway") do
|
|
24
36
|
# ... end`, the same receiver a plain bind like `.persisted_by(...)`
|
|
25
37
|
# already reaches, because a port belongs to exactly one aggregate
|
|
26
|
-
# the same way a bind does. A
|
|
38
|
+
# the same way a bind does. A real method, not method_missing : its
|
|
27
39
|
# shape (a name and a block building operations) has nothing to do
|
|
28
40
|
# with `Bind`, so it does not belong in that generic verb path.
|
|
41
|
+
#
|
|
42
|
+
# @param name [String] the port's name, such as `"Gateway"`
|
|
43
|
+
# @yield the port body, evaluated against a `DomainPortBuilder`: either `verb`/`signal`
|
|
44
|
+
# or `operation`/`tells`/`asks` blocks
|
|
45
|
+
# @return [Bluebook::DSL::BindingProxy] this proxy, so further binds can chain
|
|
46
|
+
# @raise [Bluebook::DSL::Malformed] if the current registry holds no such aggregate, or
|
|
47
|
+
# the body declares both a verb and operations, neither, or an operation the port
|
|
48
|
+
# grammar refuses
|
|
29
49
|
def port(name, &block)
|
|
30
50
|
domain, aggregate_name = @fqn.split("::")
|
|
31
51
|
aggregate_ir = Hecks.current_registry.bluebook(domain)&.aggregate(aggregate_name) or
|
|
@@ -57,6 +77,16 @@ module Hecks
|
|
|
57
77
|
end
|
|
58
78
|
|
|
59
79
|
def method_missing(verb, *args, **kwargs, &block)
|
|
80
|
+
# A BARE CALL — no args, no kwargs, no block — starts (or
|
|
81
|
+
# continues, on `AttributePath` itself below) a Privacy
|
|
82
|
+
# marking chain: `Registration.attendee.medications.has_phi(
|
|
83
|
+
# readable_by: "Privacy officer")`. No existing real
|
|
84
|
+
# `.hecksagon` bind is ever called this way (verified by
|
|
85
|
+
# grep before adding this branch — `persisted_by`/
|
|
86
|
+
# `opened_by`/every other bind always takes at least one
|
|
87
|
+
# arg), so this cannot collide with recording a `Bind`.
|
|
88
|
+
return AttributePath.new(@fqn, [verb.to_s]) if args.empty? && kwargs.empty? && !block
|
|
89
|
+
|
|
60
90
|
@collector << Bind.new(
|
|
61
91
|
aggregate: @fqn,
|
|
62
92
|
verb: verb.to_s,
|
|
@@ -71,6 +101,55 @@ module Hecks
|
|
|
71
101
|
|
|
72
102
|
def to_s = @fqn
|
|
73
103
|
end
|
|
104
|
+
|
|
105
|
+
# What a Privacy-marking chain resolves to after its first bare segment —
|
|
106
|
+
# `Registration.attendee` returns one of these, `.medications` returns another
|
|
107
|
+
# (one segment longer), and a terminal `has_<category>(readable_by:)` records the
|
|
108
|
+
# marking and ends the chain. See `BindingProxy#method_missing`'s own header for why
|
|
109
|
+
# a bare call is unambiguously the start of one of these, never a `Bind`.
|
|
110
|
+
class AttributePath
|
|
111
|
+
# @param fqn [String] the aggregate's own qualified name, `"Domain::Aggregate"`
|
|
112
|
+
# @param path [Array<String>] every segment named so far, e.g. `["attendee"]`
|
|
113
|
+
def initialize(fqn, path)
|
|
114
|
+
@fqn = fqn
|
|
115
|
+
@path = path
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @param verb [Symbol] `has_<category>` to record the marking and end the chain;
|
|
119
|
+
# any other bare name to extend the path one segment further
|
|
120
|
+
# @param readable_by [String] required only for a `has_<category>` call — the
|
|
121
|
+
# Governance role a read must hold to see this field unredacted
|
|
122
|
+
# @return [Bluebook::DSL::AttributePath, nil] a longer chain for a plain segment;
|
|
123
|
+
# `nil` (nothing further to chain) for a `has_<category>` call
|
|
124
|
+
# @raise [Malformed] if a `has_<category>` call omits `readable_by:`, or any call
|
|
125
|
+
# carries positional args or a block (neither shape this chain supports)
|
|
126
|
+
def method_missing(verb, *args, readable_by: nil, **kwargs, &block)
|
|
127
|
+
name = verb.to_s
|
|
128
|
+
return record_marking(name.delete_prefix("has_"), readable_by) if name.start_with?("has_")
|
|
129
|
+
|
|
130
|
+
if !args.empty? || !kwargs.empty? || block
|
|
131
|
+
raise Malformed, "#{@fqn}.#{@path.join('.')}.#{name} — an attribute path chain takes no " \
|
|
132
|
+
"arguments except a terminal has_<category>(readable_by:)"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
AttributePath.new(@fqn, @path + [name])
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def respond_to_missing?(_name, _include_private = false) = true
|
|
139
|
+
|
|
140
|
+
def to_s = "#{@fqn}.#{@path.join('.')}"
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def record_marking(category, readable_by)
|
|
145
|
+
raise Malformed, "#{self}.has_#{category} needs readable_by: (the Governance role a read must hold)" unless readable_by
|
|
146
|
+
|
|
147
|
+
Hecks.current_registry.add_pending_privacy_marking(
|
|
148
|
+
domain: @fqn, attribute_path: @path.join("."), category: category, readable_by: readable_by
|
|
149
|
+
)
|
|
150
|
+
nil
|
|
151
|
+
end
|
|
152
|
+
end
|
|
74
153
|
end
|
|
75
154
|
end
|
|
76
155
|
end
|