hecks 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +60 -2
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/postgres_era.adapter +5 -0
- data/lib/hecks/adapters/driven/prism.rb +4 -4
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +51 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -3
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +122 -86
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +301 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -20
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +361 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +124 -0
- data/lib/hecks/fuzzing/form_census.rb +199 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +226 -38
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
- data/lib/hecks/fuzzing/properties/guards.rb +129 -26
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
- data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +84 -39
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +224 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +676 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +39 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
- data/lib/hecks/fuzzing/value_generator.rb +66 -14
- data/lib/hecks/fuzzing.rb +11 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +365 -15
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +89 -21
- data/lib/hecks/ports/access_control.rb +58 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +160 -58
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +44 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +338 -83
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +29 -6
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +250 -0
- data/lib/hecks/projections/glossary/markdown.rb +105 -0
- data/lib/hecks/projections/glossary/mermaid.rb +110 -0
- data/lib/hecks/projections/glossary/page.css +271 -0
- data/lib/hecks/projections/glossary/page.js +72 -0
- data/lib/hecks/projections/glossary/sections.rb +17 -0
- data/lib/hecks/projections/glossary/sentences.rb +205 -0
- data/lib/hecks/projections/glossary.rb +214 -286
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +15 -22
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +132 -24
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +57 -13
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +64 -67
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +94 -19
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +282 -46
- data/lib/hecks/runtime/entity_interpreter.rb +160 -69
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +111 -56
- data/lib/hecks/runtime/reaction_invocation.rb +76 -9
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -112
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +56 -27
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -88
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +93 -56
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +379 -226
- data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
- data/lib/hecks/runtime/value.rb +28 -23
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +207 -5
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +43 -2
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
require_relative "../errors"
|
|
2
2
|
require_relative "../refusal_wording"
|
|
3
3
|
require_relative "../value"
|
|
4
|
+
require_relative "../../rendering"
|
|
5
|
+
require_relative "../../ports/query/in_memory"
|
|
4
6
|
|
|
5
7
|
module Hecks
|
|
6
8
|
module Runtime
|
|
7
9
|
class CommandRules
|
|
8
|
-
# A reference must point at something that
|
|
10
|
+
# A reference must point at something that exists.
|
|
9
11
|
#
|
|
10
12
|
# `reference_to Customer` is the one guarantee an aggregate reference is
|
|
11
13
|
# for, and it was declared 14 times across banking and enforced nowhere :
|
|
@@ -14,7 +16,7 @@ module Hecks
|
|
|
14
16
|
# no corpus step ever passed a dangling reference.
|
|
15
17
|
module References
|
|
16
18
|
# Resolved here rather than in coercion because coercion is pure — it
|
|
17
|
-
# holds no repository. A reference
|
|
19
|
+
# holds no repository. A reference into another domain is left alone : a
|
|
18
20
|
# cross-domain target may legitimately not be loaded, which is the same
|
|
19
21
|
# reading `across` policies already get.
|
|
20
22
|
#
|
|
@@ -40,6 +42,8 @@ module Hecks
|
|
|
40
42
|
# This is what makes a `has_many` declared on an aggregate honest even
|
|
41
43
|
# when a command supplies its list through an ordinary typed argument.
|
|
42
44
|
def resolve_state_references(domain, construct, state)
|
|
45
|
+
own_tenant_field = tenant_field_for(construct)
|
|
46
|
+
|
|
43
47
|
construct.attributes.each do |attribute|
|
|
44
48
|
next unless attribute.reference?
|
|
45
49
|
|
|
@@ -51,6 +55,7 @@ module Hecks
|
|
|
51
55
|
next unless target
|
|
52
56
|
|
|
53
57
|
validate_reference_values(domain, target, held, list: attribute.list?)
|
|
58
|
+
enforce_tenant_boundary(domain, construct, attribute, target, held, state, own_tenant_field)
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
Array(construct.entities).each do |entity|
|
|
@@ -78,7 +83,7 @@ module Hecks
|
|
|
78
83
|
"#{attribute.type.target_name} identity, got nil"
|
|
79
84
|
end
|
|
80
85
|
|
|
81
|
-
# The reference
|
|
86
|
+
# The reference resolves itself — through the chapter's own IR, so the
|
|
82
87
|
# bluebook's declared heads are the index. This used to regex the target's
|
|
83
88
|
# name out of "Reference<Customer>" and then search
|
|
84
89
|
# `registry.bluebook(domain).aggregates` for it — and later reached the
|
|
@@ -96,31 +101,101 @@ module Hecks
|
|
|
96
101
|
next if @registry.repository(domain, target).find(key)
|
|
97
102
|
|
|
98
103
|
raise NotFound,
|
|
99
|
-
RefusalWording.
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
RefusalWording.render_site("NotFound", "reference_target_missing",
|
|
105
|
+
target: target.name, heads: target.identity_heads.join(", "),
|
|
106
|
+
key: key)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Angle-8's own write-side half of `TenantScope.apply` (runtime/
|
|
111
|
+
# tenant_scope.rb) — the query-side mechanism this mirrors. That
|
|
112
|
+
# module turns a declared `authorize policy, tenant: :field` into a
|
|
113
|
+
# synthetic where-clause checked against the caller's own supplied
|
|
114
|
+
# tenant argument; there is no caller-identity/session system this
|
|
115
|
+
# runtime has to check a write's caller against (TenantScope's own
|
|
116
|
+
# header names that as a separate, still-open gap), so this checks
|
|
117
|
+
# the one thing that is available without one: whether the record
|
|
118
|
+
# being written and the record it references agree about which
|
|
119
|
+
# tenant they belong to. `lib/hecks/fuzzing/properties/guards.rb`'s
|
|
120
|
+
# `commands_respect_tenant_scope` states the identical claim,
|
|
121
|
+
# read off `history[:instances]` after the fact — this is what
|
|
122
|
+
# makes that claim hold by construction (a refused write is never
|
|
123
|
+
# stored) rather than merely checked for regression.
|
|
124
|
+
#
|
|
125
|
+
# Hooked into `resolve_state_references` rather than a new
|
|
126
|
+
# DISPATCH_ORDER step deliberately: that method already walks
|
|
127
|
+
# every `reference_to`-typed attribute against the settled,
|
|
128
|
+
# post-mutation state (the same moment `commands_respect_tenant_
|
|
129
|
+
# scope` itself inspects), already resolves the referenced record
|
|
130
|
+
# through the repository right above, and already runs from both
|
|
131
|
+
# `CommandInterpreter#step_save` and `EntityInterpreter#step_save`
|
|
132
|
+
# — one change, both interpreters covered, no new vocabulary step
|
|
133
|
+
# to keep in sync with `Vocabulary::AggregateDispatchOrder`/
|
|
134
|
+
# `EntityDispatchOrder`.
|
|
135
|
+
def enforce_tenant_boundary(domain, construct, attribute, target, held, state, own_tenant_field)
|
|
136
|
+
return unless own_tenant_field && state.key?(own_tenant_field)
|
|
137
|
+
|
|
138
|
+
target_tenant_field = tenant_field_for(target)
|
|
139
|
+
return unless target_tenant_field
|
|
140
|
+
|
|
141
|
+
own_tenant = Ports::Query::InMemory.comparable(state[own_tenant_field])
|
|
142
|
+
|
|
143
|
+
values = attribute.list? ? Array(held) : [held]
|
|
144
|
+
values.each do |value|
|
|
145
|
+
key = reference_key(value)
|
|
146
|
+
next if key.empty?
|
|
147
|
+
|
|
148
|
+
record = @registry.repository(domain, target).find(key)
|
|
149
|
+
next unless record&.state&.key?(target_tenant_field)
|
|
150
|
+
|
|
151
|
+
target_tenant = Ports::Query::InMemory.comparable(record.state[target_tenant_field])
|
|
152
|
+
next if target_tenant == own_tenant
|
|
153
|
+
|
|
154
|
+
raise Unauthorized,
|
|
155
|
+
RefusalWording.render_site("Unauthorized", "cross_tenant_reference",
|
|
156
|
+
aggregate: construct.hecks_name, field: own_tenant_field,
|
|
157
|
+
tenant: Rendering.describe(state[own_tenant_field]),
|
|
158
|
+
attribute: attribute.name, target: target.name,
|
|
159
|
+
target_field: target_tenant_field,
|
|
160
|
+
other: Rendering.describe(record.state[target_tenant_field]))
|
|
102
161
|
end
|
|
103
162
|
end
|
|
104
163
|
|
|
105
|
-
#
|
|
164
|
+
# The field an aggregate's own query names as tenant-scoping — the
|
|
165
|
+
# exact same lookup `Fuzzing::Properties::Guards#tenant_field_for`
|
|
166
|
+
# already established for the property that found this gap, reused
|
|
167
|
+
# here rather than reinvented: an aggregate's own declared tenant
|
|
168
|
+
# field is whichever field one of its own queries names in
|
|
169
|
+
# `authorize policy, tenant: :field`. `nil` for a construct that
|
|
170
|
+
# declares no such query — not every aggregate is tenant-scoped,
|
|
171
|
+
# and an entity never declares a query of its own at all today
|
|
172
|
+
# (`Entity.queries` is always empty in the real corpus), so this
|
|
173
|
+
# answers `nil` for every entity without needing to special-case
|
|
174
|
+
# one.
|
|
175
|
+
def tenant_field_for(construct)
|
|
176
|
+
authorization = construct.queries.filter_map(&:authorization).find(&:tenant)
|
|
177
|
+
authorization&.tenant&.to_sym
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# `value` is the referenced record's own id, exactly as `Identity.of`
|
|
106
181
|
# would build it for that record — a bare scalar for a single-field
|
|
107
182
|
# identity (the overwhelming common case; Banking's own plain
|
|
108
183
|
# `reference_to Customer` holds one already, so `Value.
|
|
109
184
|
# materialize_unwrapped` is a no-op passthrough here), or a
|
|
110
185
|
# `Naming.identity`-joined string for a compound one
|
|
111
186
|
# (`belongs_to Translation, as: :translation_ref` — Translation's
|
|
112
|
-
# own `identified_by :domain, :from, :to`,
|
|
187
|
+
# own `identified_by :domain, :from, :to`, three fields). Before
|
|
113
188
|
# this, plain `value.to_s` on that compound case's own coerced
|
|
114
189
|
# Value hit Ruby's default `Object#to_s` (a raw, run-to-run-random
|
|
115
190
|
# memory address) instead of joining the record's real id — found
|
|
116
191
|
# live via bin/fuzz on the self-hosted "translation" domain
|
|
117
|
-
# (replay_is_deterministic), the
|
|
192
|
+
# (replay_is_deterministic), the same class of gap `Identity.from`
|
|
118
193
|
# already had for a compound `identified_by`'s own bare (undotted)
|
|
119
194
|
# attribute paths. `materialize_unwrapped` recurses a multi-
|
|
120
195
|
# attribute value object to a plain Hash keyed by attribute name,
|
|
121
196
|
# in declaration order — `Naming.identity` on `.values` reproduces
|
|
122
197
|
# the identical join `Identity.of` itself would produce for the
|
|
123
|
-
#
|
|
198
|
+
# same fields.
|
|
124
199
|
def reference_key(value)
|
|
125
200
|
unwrapped = Value.materialize_unwrapped(value)
|
|
126
201
|
return Naming.identity(unwrapped.values).to_s if unwrapped.is_a?(Hash)
|
|
@@ -128,35 +203,35 @@ module Hecks
|
|
|
128
203
|
unwrapped.to_s
|
|
129
204
|
end
|
|
130
205
|
|
|
131
|
-
# A
|
|
206
|
+
# A command argument's own related record, reachable by name from
|
|
132
207
|
# `given`/`ensures` — `disputed_by.status`, say, `CardPayment
|
|
133
208
|
# .Dispute`'s own fresh `Reference<Customer>` argument — without
|
|
134
209
|
# teaching the pure expression evaluator anything about
|
|
135
|
-
# repositories. The lookup happens
|
|
210
|
+
# repositories. The lookup happens here, once, before evaluation;
|
|
136
211
|
# `Resolver#lookup` just digs into a plain Hash exactly as it
|
|
137
212
|
# always has.
|
|
138
213
|
#
|
|
139
|
-
# `owner`
|
|
214
|
+
# `owner` narrowed to `command` only (S12, ADR 0025 — "rules
|
|
140
215
|
# confined to their own aggregate boundary"): dereferencing the
|
|
141
|
-
#
|
|
216
|
+
# declaring aggregate/entity's own stored `reference_to` used to
|
|
142
217
|
# be the other half of this method's job — a live query against
|
|
143
218
|
# another aggregate's own repository, every time a `given`/
|
|
144
219
|
# `ensures`/`invariant` ran. That half is gone; a cross-aggregate
|
|
145
|
-
# fact a rule needs now has to be a `projects`-maintained
|
|
220
|
+
# fact a rule needs now has to be a `projects`-maintained local
|
|
146
221
|
# field (`AggregateBuilder#projects_impl`'s own comment), already
|
|
147
222
|
# present in `subject`'s own state, no hydration needed. A
|
|
148
|
-
# reference-typed
|
|
223
|
+
# reference-typed command argument stays in bounds, though — the
|
|
149
224
|
# ADR's own boundary list names "its command arguments" as
|
|
150
225
|
# readable, and nothing is stored yet for a fresh argument to
|
|
151
|
-
# project from; resolving it once here, synchronous with
|
|
226
|
+
# project from; resolving it once here, synchronous with this
|
|
152
227
|
# command's own admission, is a different shape from a live query
|
|
153
|
-
# against an
|
|
228
|
+
# against an already-persisted reference. `enforce_givens`/
|
|
154
229
|
# `enforce_ensures` are this method's only two remaining callers,
|
|
155
230
|
# both passing `command`/`args`, never a `subject`'s own
|
|
156
231
|
# aggregate — verified before this comment was written, not
|
|
157
232
|
# assumed.
|
|
158
233
|
#
|
|
159
|
-
#
|
|
234
|
+
# Recurses into what it finds, so a chain deeper than one hop
|
|
160
235
|
# still resolves in one pass. Depth-bounded rather than cycle-
|
|
161
236
|
# detected — nothing in this corpus dots more than two hops on a
|
|
162
237
|
# fresh argument, and a bound is simpler than tracking visited
|
|
@@ -20,6 +20,9 @@ module Hecks
|
|
|
20
20
|
|
|
21
21
|
attr_reader :registry
|
|
22
22
|
|
|
23
|
+
# @param registry [Runtime::Registry, nil] the booted registry the rules read bluebooks,
|
|
24
|
+
# repositories and the event log from; nil serves only the rules that read no
|
|
25
|
+
# registry, such as `sign_of`
|
|
23
26
|
def initialize(registry)
|
|
24
27
|
@registry = registry
|
|
25
28
|
end
|
|
@@ -74,12 +74,12 @@ module Hecks
|
|
|
74
74
|
STATEFUL_MUTATIONS = %i[append increment decrement multiply clamp remove].freeze
|
|
75
75
|
|
|
76
76
|
# `root_aggregate:` — Wave 8's own audit surfaced a real bug here,
|
|
77
|
-
# not merely a missing feature: for an
|
|
77
|
+
# not merely a missing feature: for an entity-owned command,
|
|
78
78
|
# `EntityInterpreter` calls this with `aggregate:` set to the
|
|
79
|
-
#
|
|
79
|
+
# entity itself (`element_interpreter.rb`'s own `Analyzer.call
|
|
80
80
|
# (aggregate: entity, command:)`), so `owner_fields` was always
|
|
81
81
|
# the entity's own attribute set. A `given`/`ensures` reading
|
|
82
|
-
# `parent.X` legitimately means the
|
|
82
|
+
# `parent.X` legitimately means the root aggregate's own field —
|
|
83
83
|
# a genuinely different owner — but `classify_path`'s `:parent`
|
|
84
84
|
# branch checked that read against `owner_fields` (the entity's),
|
|
85
85
|
# which can never contain a root-level field, so every entity
|
|
@@ -96,10 +96,10 @@ module Hecks
|
|
|
96
96
|
@owner_fields << aggregate.lifecycle.field.to_sym if aggregate.lifecycle
|
|
97
97
|
@root_owner_fields = root_aggregate.attributes.to_set(&:name)
|
|
98
98
|
@root_owner_fields << root_aggregate.lifecycle.field.to_sym if root_aggregate.lifecycle
|
|
99
|
-
# `projects`
|
|
99
|
+
# `projects` fields (S12, ADR 0025) are owner state too — a
|
|
100
100
|
# `given`/`ensures` reading one (e.g. `customer_status ==
|
|
101
101
|
# "active"`) is reading this record's own stored field, same
|
|
102
|
-
# as any attribute, even though nothing here
|
|
102
|
+
# as any attribute, even though nothing here writes it via a
|
|
103
103
|
# declared mutation (`CommandInterpreter#seed_projected_fields`
|
|
104
104
|
# populates it outside this analysis entirely). Left out of
|
|
105
105
|
# `known_writes` deliberately: `add_preservation_reads` then
|
|
@@ -107,7 +107,7 @@ module Hecks
|
|
|
107
107
|
# a partial mutation, which is exactly right — a projected
|
|
108
108
|
# field's freshness comes from the interpreter reseeding it on
|
|
109
109
|
# save, not from anything a caller-supplied write set carries.
|
|
110
|
-
# Applies to
|
|
110
|
+
# Applies to both `owner_fields` and `root_owner_fields` — an
|
|
111
111
|
# entity's own `parent.*` read can name the root aggregate's
|
|
112
112
|
# projected field just as easily as one of its real attributes
|
|
113
113
|
# (`Banking::Withdrawal.Dispute`'s own `parent.account_customer_
|
|
@@ -237,7 +237,7 @@ module Hecks
|
|
|
237
237
|
end
|
|
238
238
|
end
|
|
239
239
|
|
|
240
|
-
#
|
|
240
|
+
# Known, harmless gap: `corrects ..., as: :name`'s bound name
|
|
241
241
|
# (admissibility.rb's `enforce_correction_target`/`enforce_givens`/
|
|
242
242
|
# `enforce_ensures`) isn't special-cased here the way `:old`/
|
|
243
243
|
# `:parent` are — a given/ensures referencing it falls through to
|
|
@@ -247,7 +247,7 @@ module Hecks
|
|
|
247
247
|
# `hydrate_existing` path instead of the `ATOMIC_PUT` fast path.
|
|
248
248
|
# Not a correctness bug — `as:`'s runtime binding (a plain `attrs`
|
|
249
249
|
# merge, exactly like `old:`'s) resolves and evaluates correctly
|
|
250
|
-
# regardless of what this
|
|
250
|
+
# regardless of what this static analysis concludes — just a real,
|
|
251
251
|
# deliberately-left optimization gap: closing it would mean
|
|
252
252
|
# threading "which names this command declares as correction
|
|
253
253
|
# bindings" into the Analyzer, which doesn't have that per-command
|
|
@@ -258,9 +258,9 @@ module Hecks
|
|
|
258
258
|
name = head.to_sym
|
|
259
259
|
|
|
260
260
|
if name == :parent
|
|
261
|
-
# `root_owner_fields` —
|
|
262
|
-
# command `owner_fields` is the
|
|
263
|
-
# `parent.X` always means the
|
|
261
|
+
# `root_owner_fields` — not `owner_fields`. For an entity-owned
|
|
262
|
+
# command `owner_fields` is the entity's own attribute set;
|
|
263
|
+
# `parent.X` always means the root aggregate's own field, a
|
|
264
264
|
# genuinely different owner (`root_aggregate:`'s own header,
|
|
265
265
|
# above, has the full bug this fixes). Identical for a plain
|
|
266
266
|
# aggregate command, where root_aggregate defaults to aggregate
|