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,7 +1,7 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Ports
|
|
3
3
|
module Persistence
|
|
4
|
-
#
|
|
4
|
+
# **The no-op saga store** — what `Registry#saga_persistence` hands
|
|
5
5
|
# back for a domain whose resolved adapter doesn't implement the
|
|
6
6
|
# (optional) saga-persistence capability: Memory, deliberately
|
|
7
7
|
# (sagas stay in-memory-only, exactly as they always have), and
|
|
@@ -14,8 +14,19 @@ module Hecks
|
|
|
14
14
|
# `Ports::Persistence::AppendOnly` already gives adapters that
|
|
15
15
|
# don't implement `reset!`/`events`/`record_event`.
|
|
16
16
|
class NullSagaStore
|
|
17
|
+
# Accepts and discards a saga checkpoint, whatever keywords it carries.
|
|
18
|
+
#
|
|
19
|
+
# @return [nil] always; nothing is stored
|
|
17
20
|
def save_saga(**) = nil
|
|
21
|
+
|
|
22
|
+
# Accepts and ignores a request to forget a saga.
|
|
23
|
+
#
|
|
24
|
+
# @return [nil] always; there is nothing to delete
|
|
18
25
|
def delete_saga(**) = nil
|
|
26
|
+
|
|
27
|
+
# Yields nothing, because no saga is ever stored here.
|
|
28
|
+
#
|
|
29
|
+
# @return [nil] always; a block, if given, is never called
|
|
19
30
|
def each_saga(*) = nil
|
|
20
31
|
end
|
|
21
32
|
|
|
@@ -8,17 +8,17 @@ module Hecks
|
|
|
8
8
|
# a plugin makes itself known as a side effect of being `require`d, the
|
|
9
9
|
# same way an adapter's own `.rb` file already registers itself
|
|
10
10
|
# (`Runtime::Registry#add_adapter`) rather than core listing adapter
|
|
11
|
-
# names anywhere. `register_plugin` is the
|
|
11
|
+
# names anywhere. `register_plugin` is the entire contract a plugin
|
|
12
12
|
# must satisfy: anything responding to `contribute_boot_gates(registry,
|
|
13
13
|
# gates)` (a no-op is a valid implementation).
|
|
14
14
|
#
|
|
15
|
-
# This is deliberately
|
|
15
|
+
# This is deliberately not the same registry `Hecks::Projector` uses
|
|
16
16
|
# (ADR 0027) or `Runtime::BootGates` (ADR 0031) — each of those is
|
|
17
17
|
# scoped to its own seam (IR-in/artifact-out; per-boot phased gates).
|
|
18
18
|
# A shared base is deferred until a third registry actually wants one.
|
|
19
19
|
#
|
|
20
|
-
#
|
|
21
|
-
# instance per `Loader.boot` call, because gate
|
|
20
|
+
# **Name-keyed, process-wide, not per-boot** — unlike `BootGates` (one
|
|
21
|
+
# instance per `Loader.boot` call, because gate registration is
|
|
22
22
|
# capability-conditional per registry), a persistence plugin is either
|
|
23
23
|
# `require`d into this process or it isn't; there is no "this boot's
|
|
24
24
|
# registry doesn't need it" case to isolate against, so a plain
|
|
@@ -27,18 +27,36 @@ module Hecks
|
|
|
27
27
|
@plugins = {}
|
|
28
28
|
|
|
29
29
|
class << self
|
|
30
|
+
# Adds a plugin under a name, replacing any plugin already registered under it.
|
|
31
|
+
#
|
|
32
|
+
# @param name [Symbol, String] the plugin's name, such as `:era`; stored as a Symbol
|
|
33
|
+
# @param plugin [Object] anything responding to
|
|
34
|
+
# `contribute_boot_gates(registry, gates)`
|
|
35
|
+
# @return [Object] the plugin just registered
|
|
30
36
|
def register(name, plugin)
|
|
31
37
|
@plugins[name.to_sym] = plugin
|
|
32
38
|
end
|
|
33
39
|
|
|
40
|
+
# Reports whether a plugin of that name has been required into this process.
|
|
41
|
+
#
|
|
42
|
+
# @param name [Symbol, String] the plugin's name
|
|
43
|
+
# @return [Boolean] true when a plugin is registered under `name`
|
|
34
44
|
def registered?(name)
|
|
35
45
|
@plugins.key?(name.to_sym)
|
|
36
46
|
end
|
|
37
47
|
|
|
48
|
+
# Yields every registered plugin, in registration order.
|
|
49
|
+
#
|
|
50
|
+
# @yieldparam plugin [Object] a registered plugin
|
|
51
|
+
# @return [Hash{Symbol => Object}, Enumerator] the plugin table when a block is
|
|
52
|
+
# given, otherwise an Enumerator over the plugins
|
|
38
53
|
def each(&)
|
|
39
54
|
@plugins.each_value(&)
|
|
40
55
|
end
|
|
41
56
|
|
|
57
|
+
# Reports whether any persistence plugin is loaded at all.
|
|
58
|
+
#
|
|
59
|
+
# @return [Boolean] true when at least one plugin is registered
|
|
42
60
|
def any? = !@plugins.empty?
|
|
43
61
|
end
|
|
44
62
|
end
|
|
@@ -48,9 +66,29 @@ module Hecks
|
|
|
48
66
|
# directly for a plugin's own file to call at require-time.
|
|
49
67
|
module_function
|
|
50
68
|
|
|
69
|
+
# Registers a persistence plugin; a plugin's own file calls this when it is required.
|
|
70
|
+
#
|
|
71
|
+
# @param name [Symbol, String] the plugin's name, such as `:era`
|
|
72
|
+
# @param plugin [Object] anything responding to `contribute_boot_gates(registry, gates)`
|
|
73
|
+
# @return [Object] the plugin just registered
|
|
51
74
|
def register_plugin(name, plugin) = Plugin.register(name, plugin)
|
|
75
|
+
|
|
76
|
+
# Reports whether the named persistence plugin is loaded in this process.
|
|
77
|
+
#
|
|
78
|
+
# @param name [Symbol, String] the plugin's name
|
|
79
|
+
# @return [Boolean] true when a plugin is registered under `name`
|
|
52
80
|
def plugin?(name) = Plugin.registered?(name)
|
|
81
|
+
|
|
82
|
+
# Yields every loaded persistence plugin, in registration order.
|
|
83
|
+
#
|
|
84
|
+
# @yieldparam plugin [Object] a registered plugin
|
|
85
|
+
# @return [Hash{Symbol => Object}, Enumerator] the plugin table when a block is given,
|
|
86
|
+
# otherwise an Enumerator over the plugins
|
|
53
87
|
def each_plugin(&) = Plugin.each(&)
|
|
88
|
+
|
|
89
|
+
# Reports whether any persistence plugin is loaded in this process.
|
|
90
|
+
#
|
|
91
|
+
# @return [Boolean] true when at least one plugin is registered
|
|
54
92
|
def plugins_loaded? = Plugin.any?
|
|
55
93
|
end
|
|
56
94
|
end
|
|
@@ -7,7 +7,7 @@ require_relative "../../../../runtime/registry"
|
|
|
7
7
|
|
|
8
8
|
module Hecks
|
|
9
9
|
module Runtime
|
|
10
|
-
# The boot-time era gate, run for the adapters that
|
|
10
|
+
# The boot-time era gate, run for the adapters that have eras — the
|
|
11
11
|
# lineage-capable ones. An era is a fact about stored data that some
|
|
12
12
|
# adapter can carry across a shape change; an adapter that cannot
|
|
13
13
|
# translate has no era to hold, and holding one for it would record
|
|
@@ -15,7 +15,7 @@ module Hecks
|
|
|
15
15
|
#
|
|
16
16
|
# The whole domain is handed to the capable adapter's own era check
|
|
17
17
|
# (Postgres: hold, recognize, mint, or refuse toward the scaffold),
|
|
18
|
-
# which keeps its era facts as rows
|
|
18
|
+
# which keeps its era facts as rows beside its data. That
|
|
19
19
|
# co-location is load-bearing — a watermark only means something
|
|
20
20
|
# against the journal it was cut from, and an approval recorded
|
|
21
21
|
# anywhere but the reviewed database binds to nothing.
|
|
@@ -31,50 +31,84 @@ module Hecks
|
|
|
31
31
|
module EraCheck
|
|
32
32
|
module_function
|
|
33
33
|
|
|
34
|
-
#
|
|
34
|
+
# Runs both halves of the era gate over every bluebook in a registry.
|
|
35
|
+
#
|
|
36
|
+
# Each bluebook's own source, not one file read once and reused
|
|
35
37
|
# for every bluebook in the registry — true as long as a domain
|
|
36
38
|
# directory only ever held exactly one, and silently wrong the
|
|
37
39
|
# moment `uses_framework` made a second, differently-sourced
|
|
38
40
|
# bluebook (Governance, Identity — `lib/hecks/framework/bluebook/`,
|
|
39
41
|
# not the domain's own directory) share a boot with the first.
|
|
40
42
|
# Caught the hard way: three domains booted together, one real
|
|
41
|
-
# source text (the domain's own), and every
|
|
42
|
-
# held
|
|
43
|
+
# source text (the domain's own), and every other domain's era-1
|
|
44
|
+
# held that text under its own name — a shadow-parse of it later
|
|
43
45
|
# reconstructs a completely different shape, and every boot after
|
|
44
46
|
# the first refuses toward a scaffold that was never the real
|
|
45
47
|
# drift.
|
|
48
|
+
#
|
|
49
|
+
# @param registry [Runtime::Registry] the registry being booted
|
|
50
|
+
# @param directory [String] path of the domain's own bluebook directory, searched for
|
|
51
|
+
# each bluebook's `.bluebook` source
|
|
52
|
+
# @return [void]
|
|
53
|
+
# @raise [Runtime::WiringError] if a compute rule is bound to an adapter that is not
|
|
54
|
+
# lineage-capable, a persistence binding cannot be resolved, a lineage-bound bluebook
|
|
55
|
+
# has no findable source, or the adapter's own `era_check!` refuses the boot
|
|
46
56
|
def check!(registry, directory)
|
|
47
57
|
check_compute_rules_for_registry!(registry)
|
|
48
58
|
check_lineage!(registry, directory)
|
|
49
59
|
end
|
|
50
60
|
|
|
61
|
+
# Refuses the boot when any bluebook's compute rule is bound away from Postgres.
|
|
62
|
+
#
|
|
51
63
|
# The domain-agnostic half, split out for ADR 0031's boot-gate
|
|
52
64
|
# registry: a compute rule requires Postgres whatever adapter is
|
|
53
|
-
# actually bound, so this must run for
|
|
65
|
+
# actually bound, so this must run for every registry, the same way
|
|
54
66
|
# `registry.verify!` does — it is not conditional on any adapter
|
|
55
67
|
# being lineage-capable, and must never be skipped by
|
|
56
68
|
# `check_lineage!`'s own capability gate below.
|
|
69
|
+
#
|
|
70
|
+
# @param registry [Runtime::Registry] the registry whose bluebooks are all checked
|
|
71
|
+
# @return [void]
|
|
72
|
+
# @raise [Runtime::WiringError] if an aggregate carrying a compute rule is bound to an
|
|
73
|
+
# adapter that is not lineage-capable, or its persistence binding cannot be resolved
|
|
57
74
|
def check_compute_rules_for_registry!(registry)
|
|
58
75
|
registry.bluebooks.each_value { |bluebook| check_compute_rules!(registry, bluebook) }
|
|
59
76
|
end
|
|
60
77
|
|
|
78
|
+
# Hands every bluebook, with its own source text, to its adapter's era check.
|
|
79
|
+
#
|
|
61
80
|
# The capability-gated half — ADR 0031's registered `:era_check`
|
|
62
81
|
# gate. Registration is conditional on `lineage_capable_registry?`;
|
|
63
82
|
# `check_bluebook!` below still carries its own per-bluebook
|
|
64
83
|
# `lineage_capable?` return-early, unchanged, for a registry with a
|
|
65
84
|
# mix of lineage-capable and plain-adapter bluebooks.
|
|
85
|
+
#
|
|
86
|
+
# @param registry [Runtime::Registry] the registry being booted
|
|
87
|
+
# @param directory [String] path of the domain's own bluebook directory
|
|
88
|
+
# @return [void]
|
|
89
|
+
# @raise [Runtime::WiringError] if a persistence binding cannot be resolved, a
|
|
90
|
+
# lineage-bound bluebook has no findable source, or the adapter's own `era_check!`
|
|
91
|
+
# refuses the boot
|
|
66
92
|
def check_lineage!(registry, directory)
|
|
67
93
|
registry.bluebooks.each_value do |bluebook|
|
|
68
|
-
check_bluebook!(registry, bluebook, source_text_for(bluebook, directory), directory: directory)
|
|
94
|
+
check_bluebook!(registry, bluebook, source_text_for(bluebook, directory, registry: registry), directory: directory)
|
|
69
95
|
end
|
|
70
96
|
end
|
|
71
97
|
|
|
98
|
+
# Decides whether a registry binds anything that carries eras.
|
|
99
|
+
#
|
|
72
100
|
# The `:era_check` gate's own registration predicate: true iff at
|
|
73
101
|
# least one bluebook's own anchor (first) aggregate resolves to a
|
|
74
102
|
# lineage-capable adapter — mirrors `check_bluebook!`'s existing
|
|
75
103
|
# per-bluebook anchor check, just asked once, up front, of the
|
|
76
104
|
# whole registry, so a registry with nothing lineage-capable bound
|
|
77
105
|
# anywhere never registers the gate at all.
|
|
106
|
+
#
|
|
107
|
+
# @param registry [Runtime::Registry] the registry whose bluebooks are asked
|
|
108
|
+
# @return [Boolean] true when some bluebook's first aggregate is bound to a
|
|
109
|
+
# lineage-capable adapter; false for an empty registry or one with no such binding
|
|
110
|
+
# @raise [Runtime::WiringError] if a first aggregate's persistence binding is missing,
|
|
111
|
+
# ambiguous, or carries an unsupported role
|
|
78
112
|
def lineage_capable_registry?(registry)
|
|
79
113
|
registry.bluebooks.each_value.any? do |bluebook|
|
|
80
114
|
first = bluebook.aggregates.first
|
|
@@ -84,38 +118,170 @@ module Hecks
|
|
|
84
118
|
end
|
|
85
119
|
end
|
|
86
120
|
|
|
121
|
+
# Reads the source text one bluebook was declared in, wherever that source lives.
|
|
122
|
+
#
|
|
87
123
|
# The domain's own directory first, matched by name — a real app's
|
|
88
124
|
# directory may hold more than one file once `uses_framework`
|
|
89
125
|
# exists, so ".first" alone can no longer be trusted, the exact
|
|
90
126
|
# way it silently wasn't the day this was found: three domains
|
|
91
127
|
# booted together, one real source text read once (the domain's
|
|
92
|
-
# own, ".first"'d), and every
|
|
128
|
+
# own, ".first"'d), and every other domain's era-1 held that text
|
|
93
129
|
# under its own name — a later shadow-parse of it reconstructs a
|
|
94
130
|
# completely different shape, and every boot after the first
|
|
95
131
|
# refuses toward a scaffold that was never the real drift.
|
|
96
132
|
#
|
|
97
|
-
# A single-file directory whose one file names something
|
|
133
|
+
# A single-file directory whose one file names something else
|
|
98
134
|
# falls back to it anyway (a fixture may legitimately name its
|
|
99
|
-
# file differently from the `Hecks.bluebook` it declares) —
|
|
100
|
-
# this bluebook is a known framework member
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
|
|
135
|
+
# file differently from the `Hecks.bluebook` it declares) — unless
|
|
136
|
+
# this bluebook is a known framework member or a vendored
|
|
137
|
+
# embryonaut bluebook (see `vendored_source_for` below), in which
|
|
138
|
+
# case that one file is certainly some other domain's, not this
|
|
139
|
+
# one's, and the real registry is asked instead — the only other
|
|
140
|
+
# two places a bluebook in this registry could have come from, per
|
|
141
|
+
# `uses_framework` and `uses_embryonaut_bluebook`.
|
|
142
|
+
#
|
|
143
|
+
# The bug this guards against, found live: `Framework.members` was
|
|
144
|
+
# the only exclusion checked here, so a domain attaching a
|
|
145
|
+
# vendored bluebook instead (`uses_embryonaut_bluebook`, which has
|
|
146
|
+
# no equivalent registry — see embryonaut_bluebook.rb's own
|
|
147
|
+
# header) fell straight through the single-file fallback: a
|
|
148
|
+
# directory holding exactly one `.bluebook` file (the target
|
|
149
|
+
# domain's own) handed that same text back for the vendored
|
|
150
|
+
# bluebook's era check too. PostgresEra minted era 1 for the
|
|
151
|
+
# vendored domain with the target's own source stamped as its
|
|
152
|
+
# `held_text` — the label computed from it, too — so the very
|
|
153
|
+
# next boot re-derived the vendored domain's real shape, found it
|
|
154
|
+
# didn't match what got (wrongly) stored, and refused to boot
|
|
155
|
+
# toward a scaffold for drift that never actually happened.
|
|
156
|
+
# `registry:` is what lets this check the one thing
|
|
157
|
+
# `Framework.members` cannot: whether some hecksagon in this
|
|
158
|
+
# registry declared `uses_embryonaut_bluebook` for this exact
|
|
159
|
+
# bluebook name, the same way `EmbryonautBluebook.load!` itself
|
|
160
|
+
# already resolves the vendored package's own directory.
|
|
161
|
+
#
|
|
162
|
+
# @param bluebook [Bluebook::Chapter] the bluebook whose source is wanted
|
|
163
|
+
# @param directory [String] path of the domain's own bluebook directory
|
|
164
|
+
# @param registry [Runtime::Registry, nil] the registry asked whether some hecksagon
|
|
165
|
+
# vendored this bluebook; nil skips the vendored lookup
|
|
166
|
+
# @return [String, nil] the UTF-8 text of every matching file joined with `"\n"`; nil
|
|
167
|
+
# when no source file could be found
|
|
168
|
+
def source_text_for(bluebook, directory, registry: nil)
|
|
106
169
|
domain_files = Dir[File.join(directory, "*.bluebook")]
|
|
107
|
-
own = domain_files.select
|
|
108
|
-
|
|
109
|
-
line.match?(/\A\s*Hecks\.bluebook\s+#{Regexp.escape(bluebook.name.inspect)}/)
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
own = domain_files if own.empty? && domain_files.size == 1 && !Framework.members.key?(bluebook.name)
|
|
113
|
-
own = [Framework.members[bluebook.name]].compact if own.empty?
|
|
170
|
+
own = domain_files.select { |path| declares_bluebook?(path, bluebook.name) }
|
|
171
|
+
own = fallback_source_files(bluebook, directory, domain_files, registry) if own.empty?
|
|
114
172
|
return if own.empty?
|
|
115
173
|
|
|
116
174
|
own.map { |path| File.read(path, encoding: "UTF-8") }.join("\n")
|
|
117
175
|
end
|
|
118
176
|
|
|
177
|
+
# Scans a file for a `Hecks.bluebook "<name>"` line opening the named bluebook.
|
|
178
|
+
#
|
|
179
|
+
# @param path [String] path of the `.bluebook` file to scan, read as UTF-8
|
|
180
|
+
# @param bluebook_name [String] the declared bluebook name, matched in its `inspect`
|
|
181
|
+
# (double-quoted) spelling
|
|
182
|
+
# @return [Boolean] true when some line, after leading whitespace, starts with
|
|
183
|
+
# `Hecks.bluebook` followed by that quoted name
|
|
184
|
+
def declares_bluebook?(path, bluebook_name)
|
|
185
|
+
File.foreach(path, encoding: "UTF-8").any? do |line|
|
|
186
|
+
line.match?(/\A\s*Hecks\.bluebook\s+#{Regexp.escape(bluebook_name.inspect)}/)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Picks the files to read for a bluebook no file in the domain directory declares.
|
|
191
|
+
#
|
|
192
|
+
# Only reached once nothing in the domain's own directory actually
|
|
193
|
+
# declares this bluebook by name. Three remaining sources, in
|
|
194
|
+
# order: a genuinely single-file domain directory whose one file
|
|
195
|
+
# just happens to name something else (but only when this
|
|
196
|
+
# bluebook isn't already known to come from somewhere else
|
|
197
|
+
# entirely — the exact guard the bug below was missing half of);
|
|
198
|
+
# a framework member; a vendored embryonaut bluebook.
|
|
199
|
+
#
|
|
200
|
+
# @param bluebook [Bluebook::Chapter] the bluebook whose source is wanted
|
|
201
|
+
# @param directory [String] path of the domain's own bluebook directory
|
|
202
|
+
# @param domain_files [Array<String>] paths of every `.bluebook` file in `directory`
|
|
203
|
+
# @param registry [Runtime::Registry, nil] the registry asked for a vendored name; nil
|
|
204
|
+
# skips the vendored lookup
|
|
205
|
+
# @return [Array<String>] file paths to read, in load order; `[]` when the bluebook
|
|
206
|
+
# comes from none of the three sources
|
|
207
|
+
def fallback_source_files(bluebook, directory, domain_files, registry)
|
|
208
|
+
vendored_name = registry && vendored_bluebook_name_for(registry, bluebook.name)
|
|
209
|
+
framework_path = Framework.members[bluebook.name]
|
|
210
|
+
|
|
211
|
+
if domain_files.size == 1 && !framework_path && !vendored_name
|
|
212
|
+
domain_files
|
|
213
|
+
elsif framework_path
|
|
214
|
+
[framework_path]
|
|
215
|
+
elsif vendored_name
|
|
216
|
+
vendored_source_for(directory, vendored_name)
|
|
217
|
+
else
|
|
218
|
+
[]
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Recovers the vendored package name behind a bluebook, if some hecksagon vendored it.
|
|
223
|
+
#
|
|
224
|
+
# The name `uses_embryonaut_bluebook` was actually called with —
|
|
225
|
+
# recovered from whichever hecksagon in this registry recorded it
|
|
226
|
+
# (`HecksagonBuilder#uses_embryonaut_bluebook`'s own
|
|
227
|
+
# `@vendored_bluebooks`), matched the same way
|
|
228
|
+
# `EmbryonautBluebook.load!` itself decides idempotency: the
|
|
229
|
+
# vendored name Pascal-cases to this bluebook's own declared name.
|
|
230
|
+
# Nil for an ordinary bluebook nothing ever vendored.
|
|
231
|
+
#
|
|
232
|
+
# @param registry [Runtime::Registry] the registry whose hecksagons are searched
|
|
233
|
+
# @param bluebook_name [String] the bluebook's declared Pascal-case name
|
|
234
|
+
# @return [String, nil] the package name as written in `uses_embryonaut_bluebook`; nil
|
|
235
|
+
# when no hecksagon vendored a package whose name Pascal-cases to `bluebook_name`
|
|
236
|
+
def vendored_bluebook_name_for(registry, bluebook_name)
|
|
237
|
+
registry.hecksagons.each_value do |hecksagon|
|
|
238
|
+
match = hecksagon.vendored_bluebooks.find { |name| Naming.pascal(name) == bluebook_name }
|
|
239
|
+
return match if match
|
|
240
|
+
end
|
|
241
|
+
nil
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Lists the `.bluebook` files a vendored embryonaut package ships.
|
|
245
|
+
#
|
|
246
|
+
# The same path `EmbryonautBluebook.load!` itself resolves from —
|
|
247
|
+
# `<registry.root>/vendor/embryonaut_bluebooks/<name>/bluebook/`,
|
|
248
|
+
# rebuilt here from `directory` (the domain's own bluebook
|
|
249
|
+
# directory, always `registry.root`'s immediate child — see
|
|
250
|
+
# `Runtime::Loader.boot`) rather than threading `registry.root`
|
|
251
|
+
# through as a second parameter. Every `.bluebook` file the
|
|
252
|
+
# package ships, in the same `Dir.glob` order actually loaded at
|
|
253
|
+
# boot time — order matters here because this text is later
|
|
254
|
+
# re-parsed whole (`EraCheck::shadow`) to reconstruct the shape a
|
|
255
|
+
# held era claims, and that reconstruction must see the files in
|
|
256
|
+
# the order that actually built the live shape.
|
|
257
|
+
#
|
|
258
|
+
# @param directory [String] path of the domain's own bluebook directory, whose parent
|
|
259
|
+
# holds `vendor/embryonaut_bluebooks`
|
|
260
|
+
# @param name [String] the vendored package name, as `vendored_bluebook_name_for`
|
|
261
|
+
# returns it
|
|
262
|
+
# @return [Array<String>] paths of the package's `.bluebook` files in `Dir.glob` order;
|
|
263
|
+
# `[]` when the package directory is missing or empty
|
|
264
|
+
def vendored_source_for(directory, name)
|
|
265
|
+
dir = File.join(File.dirname(directory), "vendor", "embryonaut_bluebooks", name, "bluebook")
|
|
266
|
+
Dir.glob(File.join(dir, "*.bluebook"))
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Hands one bluebook to its adapter's `era_check!`, when that adapter carries eras.
|
|
270
|
+
#
|
|
271
|
+
# The bluebook's first aggregate is the anchor: its binding alone decides
|
|
272
|
+
# which adapter is asked, and a bluebook with no aggregates, or one bound
|
|
273
|
+
# to an adapter that is not lineage-capable, is skipped without a word.
|
|
274
|
+
#
|
|
275
|
+
# @param registry [Runtime::Registry] the registry being booted
|
|
276
|
+
# @param bluebook [Bluebook::Chapter] the bluebook to check
|
|
277
|
+
# @param current_text [String, nil] the bluebook's source, as `source_text_for` found
|
|
278
|
+
# it; nil means no source file was found
|
|
279
|
+
# @param directory [String, nil] the domain's bluebook directory, named in the
|
|
280
|
+
# missing-source refusal and passed through to the adapter
|
|
281
|
+
# @return [void]
|
|
282
|
+
# @raise [Runtime::WiringError] if the first aggregate's binding cannot be resolved,
|
|
283
|
+
# `current_text` is nil for a lineage-bound bluebook, or the adapter's `era_check!`
|
|
284
|
+
# refuses the boot
|
|
119
285
|
def check_bluebook!(registry, bluebook, current_text, directory: nil)
|
|
120
286
|
first = bluebook.aggregates.first
|
|
121
287
|
return unless first
|
|
@@ -136,10 +302,19 @@ module Hecks
|
|
|
136
302
|
)
|
|
137
303
|
end
|
|
138
304
|
|
|
139
|
-
#
|
|
305
|
+
# Refuses the boot when one bluebook's compute rule is bound away from Postgres.
|
|
306
|
+
#
|
|
307
|
+
# The per-rule capability gate — not an era fact, and so it
|
|
140
308
|
# survives on every adapter: a compute rule's SQL is its only
|
|
141
309
|
# implementation, so an aggregate carrying one cannot boot
|
|
142
310
|
# anywhere but Postgres, whatever any shape comparison would say.
|
|
311
|
+
#
|
|
312
|
+
# @param registry [Runtime::Registry] the registry holding the declared translations
|
|
313
|
+
# and the aggregate's bindings
|
|
314
|
+
# @param bluebook [Bluebook::Chapter] the bluebook whose aggregates are checked
|
|
315
|
+
# @return [void]
|
|
316
|
+
# @raise [Runtime::WiringError] if an aggregate carrying a compute rule is bound to an
|
|
317
|
+
# adapter that is not lineage-capable, or its persistence binding cannot be resolved
|
|
143
318
|
def check_compute_rules!(registry, bluebook)
|
|
144
319
|
bluebook.aggregates.each do |aggregate|
|
|
145
320
|
lineage = Ports::Persistence::Lineage.for(registry, bluebook.name, aggregate)
|
|
@@ -152,14 +327,32 @@ module Hecks
|
|
|
152
327
|
end
|
|
153
328
|
end
|
|
154
329
|
|
|
330
|
+
# Names the adapter an aggregate's authoritative persistence bind points at.
|
|
331
|
+
#
|
|
332
|
+
# @param registry [Runtime::Registry] the registry holding the domain's hecksagon
|
|
333
|
+
# @param domain [String, Symbol] name of the domain the aggregate belongs to
|
|
334
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate whose binding is resolved
|
|
335
|
+
# @return [String] the bound adapter's name, such as `"PostgresEra"`; `"Memory"` when
|
|
336
|
+
# the domain declares no hecksagon
|
|
337
|
+
# @raise [Runtime::WiringError] if the aggregate has no persistence bind, more than one
|
|
338
|
+
# authoritative bind, or a bind with a role the port does not support
|
|
155
339
|
def adapter_for(registry, domain, aggregate)
|
|
156
340
|
Ports::Persistence::BindingPolicy.resolve(registry, domain, aggregate).adapter
|
|
157
341
|
end
|
|
158
342
|
|
|
159
|
-
#
|
|
343
|
+
# Asks a named adapter whether it carries eras.
|
|
344
|
+
#
|
|
345
|
+
# The capability idiom: an adapter class that answers
|
|
160
346
|
# lineage_capable? with true carries eras and may act on drift
|
|
161
347
|
# (translate, fork, merge). Postgres alone does today; the seam is
|
|
162
348
|
# what lets a second one arrive without touching this file.
|
|
349
|
+
#
|
|
350
|
+
# @param registry [Runtime::Registry] the registry whose `adapters` must list the name
|
|
351
|
+
# @param adapter_name [String] the adapter's name, as `adapter_for` returns it
|
|
352
|
+
# @return [Boolean] the adapter's own `lineage_capable?` answer; false when the registry
|
|
353
|
+
# does not list the adapter, the adapter does not respond to `lineage_capable?`, or
|
|
354
|
+
# any `StandardError` is raised on the way (including an adapter with no Ruby
|
|
355
|
+
# implementation)
|
|
163
356
|
def lineage_capable?(registry, adapter_name)
|
|
164
357
|
adapter_class = registry.adapters[adapter_name] && registry.adapter_class(adapter_name)
|
|
165
358
|
adapter_class.respond_to?(:lineage_capable?) && adapter_class.lineage_capable?
|
|
@@ -6,16 +6,32 @@ module Hecks
|
|
|
6
6
|
# from the IR it is handed, which is why the Postgres mint path can
|
|
7
7
|
# call it too.
|
|
8
8
|
module ShapeDiff
|
|
9
|
+
# Computes an aggregate's structural signature, for comparing two eras of it.
|
|
10
|
+
#
|
|
11
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate to describe
|
|
12
|
+
# @return [Array<Array>] one `[name, signature]` pair per attribute, sorted by name;
|
|
13
|
+
# `name` is a Symbol and `signature` is whatever `attribute_signature` returns
|
|
9
14
|
def shape(aggregate)
|
|
10
15
|
aggregate.attributes.map do |attribute|
|
|
11
16
|
[attribute.name, attribute_signature(aggregate, attribute.type)]
|
|
12
17
|
end.sort_by(&:first)
|
|
13
18
|
end
|
|
14
19
|
|
|
20
|
+
# Expands a declared type into a signature that exposes its members.
|
|
21
|
+
#
|
|
15
22
|
# A plain type name for a primitive; `[type_name, member_signatures]`
|
|
16
23
|
# for a value object or entity, walked recursively — so two
|
|
17
24
|
# attributes with the same declared type name but a different
|
|
18
25
|
# internal shape are never mistaken for unchanged.
|
|
26
|
+
#
|
|
27
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate whose value objects and entities
|
|
28
|
+
# are searched for `type_name`
|
|
29
|
+
# @param type_name [String, Bluebook::Reference] the attribute's declared type
|
|
30
|
+
# @param seen [Array<String, Bluebook::Reference>] types already being expanded, which
|
|
31
|
+
# stops a self-referencing type from recursing forever
|
|
32
|
+
# @return [String, Bluebook::Reference, Array] `type_name` itself for a primitive, a
|
|
33
|
+
# reference, or a type already in `seen`; otherwise `[type_name, members]`, where
|
|
34
|
+
# `members` is an Array of `[member_name, signature]` pairs sorted by member name
|
|
19
35
|
def attribute_signature(aggregate, type_name, seen = [])
|
|
20
36
|
container = nested_type(aggregate, type_name)
|
|
21
37
|
return type_name if container.nil? || seen.include?(type_name)
|
|
@@ -26,13 +42,21 @@ module Hecks
|
|
|
26
42
|
[type_name, members]
|
|
27
43
|
end
|
|
28
44
|
|
|
45
|
+
# Looks a type name up among an aggregate's value objects, then its entities.
|
|
46
|
+
#
|
|
47
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate to search
|
|
48
|
+
# @param type_name [String, Bluebook::Reference] the declared type to find
|
|
49
|
+
# @return [Bluebook::ValueObject, Bluebook::Entity, nil] the matching declaration; nil
|
|
50
|
+
# when the type is a primitive, a reference, or nothing this aggregate declares
|
|
29
51
|
def nested_type(aggregate, type_name)
|
|
30
52
|
aggregate.value_object(type_name) || aggregate.entities.find { |entity| entity.name == type_name }
|
|
31
53
|
end
|
|
32
54
|
|
|
55
|
+
# Lists the new attributes that would leave an existing record with a required gap.
|
|
56
|
+
#
|
|
33
57
|
# Attributes present now that the held shape never had — the
|
|
34
|
-
# addition side of drift, which `uncovered_attributes`
|
|
35
|
-
# looks at, since it only walks the
|
|
58
|
+
# addition side of drift, which `uncovered_attributes` below never
|
|
59
|
+
# looks at, since it only walks the held shape (vanish/retype).
|
|
36
60
|
# Most additions are free (ADR 0025, "Added attributes and
|
|
37
61
|
# absence"): a default:, a list_of (frozen []), or a value object
|
|
38
62
|
# whose fields all default fill an existing record automatically
|
|
@@ -40,11 +64,20 @@ module Hecks
|
|
|
40
64
|
# non-optional attribute with no way to fill itself — can leave an
|
|
41
65
|
# existing record with the field genuinely absent, and that is
|
|
42
66
|
# what this reports: unfilled by a declared translation's own
|
|
43
|
-
# `backfill`,
|
|
67
|
+
# `backfill`, or by a move/convert that lands an old field inside
|
|
44
68
|
# this brand-new attribute (`Lineage#fills?` — the destination-
|
|
45
69
|
# side question, never `explains?`'s source-side one, since a
|
|
46
70
|
# rename from Crate to Bin can introduce a top-level attribute
|
|
47
71
|
# name that never existed to have "vanished").
|
|
72
|
+
#
|
|
73
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate as currently declared
|
|
74
|
+
# @param held_aggregate [Bluebook::Aggregate] the same aggregate as the held era's text
|
|
75
|
+
# declares it
|
|
76
|
+
# @param lineage [Ports::Persistence::Lineage, nil] the edge's rules for this aggregate; nil
|
|
77
|
+
# when the edge declares none, so nothing can fill an addition
|
|
78
|
+
# @return [Array<Symbol>] names of new, non-optional attributes with no default, no
|
|
79
|
+
# list cardinality, no fully-defaulted value object and no rule filling them; `[]`
|
|
80
|
+
# when every addition is safe
|
|
48
81
|
def unsafe_additions(aggregate, held_aggregate, lineage)
|
|
49
82
|
held_names = held_aggregate.attributes.map(&:name)
|
|
50
83
|
|
|
@@ -55,9 +88,17 @@ module Hecks
|
|
|
55
88
|
.map(&:name)
|
|
56
89
|
end
|
|
57
90
|
|
|
58
|
-
#
|
|
91
|
+
# Decides whether a new attribute could be genuinely absent from an existing record.
|
|
92
|
+
#
|
|
93
|
+
# The four-row table, as a predicate over the fourth row only — the
|
|
59
94
|
# other three (default:, list_of, a fully-defaulted value object)
|
|
60
95
|
# all fill an existing record for free and never reach here.
|
|
96
|
+
#
|
|
97
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate declaring the attribute, searched
|
|
98
|
+
# for the value object its type may name
|
|
99
|
+
# @param attribute [Bluebook::Attribute] the newly added attribute
|
|
100
|
+
# @return [Boolean] false when the attribute is optional, has a default, is a list, or
|
|
101
|
+
# is a value object whose every field has a default; true otherwise
|
|
61
102
|
def possibly_absent?(aggregate, attribute)
|
|
62
103
|
return false if attribute.optional?
|
|
63
104
|
return false unless attribute.default.nil?
|
|
@@ -69,14 +110,24 @@ module Hecks
|
|
|
69
110
|
true
|
|
70
111
|
end
|
|
71
112
|
|
|
113
|
+
# Lists the held paths that vanished or changed type with no rule explaining them.
|
|
114
|
+
#
|
|
72
115
|
# Paths the translation needs to explain: attributes that vanished
|
|
73
116
|
# by name, attributes that kept their name but changed type (a
|
|
74
117
|
# `convert` is what lets that be declared at all), and — recursing
|
|
75
|
-
# into a same-named, same-typed value object — its
|
|
118
|
+
# into a same-named, same-typed value object — its own members
|
|
76
119
|
# vanishing or changing type one level down, reported as a dotted
|
|
77
120
|
# path ("price.currency"). A pure addition, at any depth, never
|
|
78
121
|
# needs covering; only vanish-or-retype does, matching the
|
|
79
122
|
# top-level rule at every depth.
|
|
123
|
+
#
|
|
124
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate as currently declared
|
|
125
|
+
# @param held_aggregate [Bluebook::Aggregate] the same aggregate as the held era's text
|
|
126
|
+
# declares it
|
|
127
|
+
# @param lineage [Ports::Persistence::Lineage, nil] the edge's rules for this aggregate; nil
|
|
128
|
+
# when the edge declares none, so every vanished or retyped path is reported
|
|
129
|
+
# @return [Array<String>] bare attribute names and dotted member paths such as
|
|
130
|
+
# `"price.currency"`; `[]` when the whole diff is explained
|
|
80
131
|
def uncovered_attributes(aggregate, held_aggregate, lineage)
|
|
81
132
|
paths = held_aggregate.attributes.flat_map do |held_attribute|
|
|
82
133
|
current_attribute = aggregate.attribute(held_attribute.name)
|
|
@@ -90,20 +141,37 @@ module Hecks
|
|
|
90
141
|
paths.reject { |path| lineage.explains?(path) }
|
|
91
142
|
end
|
|
92
143
|
|
|
93
|
-
#
|
|
94
|
-
#
|
|
144
|
+
# Compares one path's held and current types, recursing into shared members.
|
|
145
|
+
#
|
|
146
|
+
# `held_type`/`current_type` are type names, resolved against each
|
|
147
|
+
# side's own value_object and entity declarations — neither is ever
|
|
95
148
|
# nested in the DSL, only in the type graph, so both are always
|
|
96
149
|
# looked up flat off their respective aggregate. A `list_of` entity
|
|
97
|
-
# is only reached here to
|
|
150
|
+
# is only reached here to detect a member vanish-or-retype; there is
|
|
98
151
|
# no per-element translation machinery yet (`move`/`convert`/`drop`
|
|
99
152
|
# only reach into a single nested hash, not each element of an
|
|
100
153
|
# array) — the only way to satisfy a refusal on an entity path
|
|
101
154
|
# today is a top-level `drop` of the whole list attribute, which
|
|
102
155
|
# `explains?` already recognizes as covering everything nested
|
|
103
156
|
# under it. Blunt, but loud beats silent.
|
|
157
|
+
#
|
|
158
|
+
# @param path [String] the bare or dotted path being compared, reported on a mismatch
|
|
159
|
+
# @param held_type [String, Bluebook::Reference] the type the held era declares there
|
|
160
|
+
# @param current_type [String, Bluebook::Reference] the type declared there now
|
|
161
|
+
# @param held_aggregate [Bluebook::Aggregate] the held aggregate, searched for
|
|
162
|
+
# `held_type`'s declaration
|
|
163
|
+
# @param aggregate [Bluebook::Aggregate] the current aggregate, searched for
|
|
164
|
+
# `current_type`'s declaration
|
|
165
|
+
# @param lineage [Ports::Persistence::Lineage, nil] the edge's rules, asked whether a retype
|
|
166
|
+
# pairs the two type names; nil means no retype is accepted
|
|
167
|
+
# @param seen [Array<String, Bluebook::Reference>] current types already being
|
|
168
|
+
# expanded, which stops a self-referencing type from recursing forever
|
|
169
|
+
# @return [Array<String>] `[path]` when the types differ with no retype declared; the
|
|
170
|
+
# dotted paths of members that vanished or changed type beneath it; `[]` when
|
|
171
|
+
# nothing differs or either type is not a value object or entity
|
|
104
172
|
def diff_type(path, held_type, current_type, held_aggregate, aggregate, lineage, seen = [])
|
|
105
173
|
if held_type != current_type
|
|
106
|
-
# A declared retype says the two
|
|
174
|
+
# A declared retype says the two type names mean the same shape
|
|
107
175
|
# — accept the pair, but still recurse into the members so a
|
|
108
176
|
# member drift hiding beneath the rename is caught by name.
|
|
109
177
|
return [path] unless lineage&.retype?(held_type, current_type)
|