hecks 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +61 -15
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -2,15 +2,16 @@ module Hecks
|
|
|
2
2
|
module Bluebook
|
|
3
3
|
# An attribute that points at another aggregate's head.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
5
|
+
# Holds the target directly, and answers `resolve` with the target's
|
|
6
|
+
# Aggregate, rather than being spelled as the string `"Reference<Customer>"`
|
|
7
|
+
# that `AggregateBuilder#reference_to` and `CommandBuilder#cross_reference`
|
|
8
|
+
# would otherwise mint from the real constant just handed in. That string
|
|
9
|
+
# would need five separate readers to parse back apart — a regex in the
|
|
10
|
+
# command interpreter, string equality in the read-model interpreter and
|
|
11
|
+
# the SQLite adapter, and `delete_prefix` in the bluebook builder — one
|
|
12
|
+
# writer inventing a spelling, five readers reparsing it.
|
|
11
13
|
#
|
|
12
|
-
#
|
|
13
|
-
# Aggregate. Resolution is LAZY and deliberately so: `reference_to
|
|
14
|
+
# Resolution is lazy and deliberately so: `reference_to
|
|
14
15
|
# Customer` may name an aggregate declared lower in the file — banking's
|
|
15
16
|
# Account points at Customer and survives only because Customer happens to
|
|
16
17
|
# be written above — so the edge cannot be resolved at declaration time.
|
|
@@ -29,13 +30,20 @@ module Hecks
|
|
|
29
30
|
# up to the chapter, stamped once every sibling has been read.
|
|
30
31
|
attr_accessor :declared_in
|
|
31
32
|
|
|
33
|
+
# @param target_name [Module, String, Symbol] the aggregate constant this
|
|
34
|
+
# reference points at, or its already-spelled name
|
|
32
35
|
def initialize(target_name)
|
|
33
36
|
@target_name = Naming.demodulise(target_name).to_s
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
# The Aggregate this points at, or nil when the target belongs to
|
|
37
|
-
#
|
|
40
|
+
# another domain — a cross-domain target may legitimately not be loaded,
|
|
38
41
|
# the same reading `across` policies get.
|
|
42
|
+
#
|
|
43
|
+
# @return [Bluebook::Aggregate, nil] the target aggregate, or `nil` when
|
|
44
|
+
# `declared_in`'s own chapter does not declare one by this name
|
|
45
|
+
# @raise [DSL::Malformed] if `declared_in` is unset, so there is no
|
|
46
|
+
# chapter to resolve the target against
|
|
39
47
|
def resolve
|
|
40
48
|
unless declared_in
|
|
41
49
|
raise DSL::Malformed,
|
|
@@ -48,16 +56,18 @@ module Hecks
|
|
|
48
56
|
end
|
|
49
57
|
|
|
50
58
|
# The IR spelling, the one the export carries.
|
|
59
|
+
#
|
|
60
|
+
# @return [String] `"Reference<TargetName>"`
|
|
51
61
|
def to_s = "Reference<#{@target_name}>"
|
|
52
62
|
def inspect = "#<Reference #{@target_name}>"
|
|
53
63
|
|
|
54
|
-
#
|
|
55
|
-
# the
|
|
64
|
+
# Value equality, not identity — without this, two references to
|
|
65
|
+
# the same target, parsed from two separate bluebook reads (era
|
|
56
66
|
# N's own boot and a held era's own shadow reconstruction,
|
|
57
67
|
# coverage_check.rb's own comparison), are different objects and
|
|
58
68
|
# compare unequal by Ruby's default `==`. `EraGuard::ShapeDiff
|
|
59
69
|
# #diff_type`'s `held_type != current_type` check then reads as
|
|
60
|
-
# true for
|
|
70
|
+
# true for every reference_to attribute on every mint, regardless
|
|
61
71
|
# of whether the reference actually changed — a real refusal for
|
|
62
72
|
# attributes nothing about. `declared_in` (which aggregate carries
|
|
63
73
|
# this reference) is deliberately excluded: the same reference
|
|
@@ -65,7 +75,10 @@ module Hecks
|
|
|
65
75
|
# from separately-parsed bluebooks with structurally different
|
|
66
76
|
# (if same-shaped) owning aggregates, and `declared_in` is a
|
|
67
77
|
# cross-reference for `resolve`, not part of what this attribute
|
|
68
|
-
# itself
|
|
78
|
+
# itself is.
|
|
79
|
+
#
|
|
80
|
+
# @param other [Object] the value to compare against
|
|
81
|
+
# @return [Boolean] whether `other` is a `Reference` to the same target
|
|
69
82
|
def ==(other) = other.is_a?(Reference) && target_name == other.target_name
|
|
70
83
|
alias eql? ==
|
|
71
84
|
def hash = [self.class, target_name].hash
|
|
@@ -4,10 +4,10 @@ require "fileutils"
|
|
|
4
4
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Bluebook
|
|
7
|
-
#
|
|
7
|
+
# Boots a real bluebook and actually dispatches against it — the
|
|
8
8
|
# sibling `bin/model_check` never had: that tool proves a bluebook
|
|
9
|
-
# is
|
|
10
|
-
# without ever running a single command; this proves it
|
|
9
|
+
# is structurally sound (no dead states, no unreachable transitions)
|
|
10
|
+
# without ever running a single command; this proves it works,
|
|
11
11
|
# catching exactly the class of bug static analysis structurally
|
|
12
12
|
# cannot see. Built and proved inside `bin/interview`'s own `smoke`
|
|
13
13
|
# command, where it first found two real bugs — a lifecycle field
|
|
@@ -15,20 +15,24 @@ module Hecks
|
|
|
15
15
|
# missing `Command.ActsOn` — that reloaded clean, checked clean,
|
|
16
16
|
# and only broke once something actually dispatched against them.
|
|
17
17
|
#
|
|
18
|
-
#
|
|
18
|
+
# Synthesized args, never random (`Synthesizer`) — a String becomes
|
|
19
19
|
# a fixed marker, a closed set uses its own first admitted member, a
|
|
20
20
|
# reference reuses whatever this same run already minted for that
|
|
21
|
-
# target.
|
|
21
|
+
# target. One call per declared command, one call per declared
|
|
22
22
|
# report, per created root; every failure is collected and reported
|
|
23
23
|
# together rather than stopping at the first.
|
|
24
24
|
#
|
|
25
|
-
#
|
|
26
|
-
# WRONG — the read-model join bug this exact tool's own first user
|
|
27
|
-
# hit is the textbook case, a query that silently returned an empty
|
|
28
|
-
# array rather than raising. Smoke-testing proves nothing crashes;
|
|
29
|
-
# it does not prove the answer is the right one.
|
|
25
|
+
# ## What this cannot catch
|
|
30
26
|
#
|
|
31
|
-
# A
|
|
27
|
+
# A call that dispatches cleanly but answers wrong — the read-model join
|
|
28
|
+
# bug this exact tool's own first user hit is the textbook case, a query
|
|
29
|
+
# that silently returned an empty array rather than raising.
|
|
30
|
+
# Smoke-testing proves nothing crashes; it does not prove the answer is
|
|
31
|
+
# the right one.
|
|
32
|
+
#
|
|
33
|
+
# ## False positives from a naive synthesizer
|
|
34
|
+
#
|
|
35
|
+
# A failure here is not necessarily a domain bug, either — it may
|
|
32
36
|
# just be `Synthesizer`'s own naive values (0, an empty list,
|
|
33
37
|
# "smoke-test") not satisfying a real invariant this simple
|
|
34
38
|
# generator was never built to reason about (a positive amount, a
|
|
@@ -54,11 +58,11 @@ module Hecks
|
|
|
54
58
|
# per-domain realm resolution requires and a plain boot never
|
|
55
59
|
# did. Reports are reachable the identical way regardless of
|
|
56
60
|
# boot path — `dispatcher.query("Domain.report_name", **args)`
|
|
57
|
-
# is the
|
|
58
|
-
# was never actually required for this, only its
|
|
59
|
-
#
|
|
61
|
+
# is the same `Dispatcher#query` either boot hands back; Router
|
|
62
|
+
# was never actually required for this, only its own namespace
|
|
63
|
+
# sugar (`Domain.report_name(...)` as a bare method call) was.
|
|
60
64
|
#
|
|
61
|
-
#
|
|
65
|
+
# Never boots `dir`'s own real bindings — measured, not a
|
|
62
66
|
# precaution taken on spec: pointed at `examples/pizzas` (a real,
|
|
63
67
|
# persistent store carrying real accumulated records), a
|
|
64
68
|
# synthesized `CreatePizza` collided with an actual pre-existing
|
|
@@ -73,7 +77,7 @@ module Hecks
|
|
|
73
77
|
# `install_facade: false` — this only ever dispatches by FQN string
|
|
74
78
|
# (below), so it never needs the `Widget::Item.Add(...)` Ruby
|
|
75
79
|
# sugar `Hecks.boot` installs by default. Skipping it matters
|
|
76
|
-
# here specifically: that sugar lands as a
|
|
80
|
+
# here specifically: that sugar lands as a bare global constant on
|
|
77
81
|
# `Object` per domain and per aggregate name, with no scoping and
|
|
78
82
|
# no cleanup — and this tool boots throwaway domains under
|
|
79
83
|
# whatever generic names the caller's `.bluebook` happens to use
|
|
@@ -82,6 +86,11 @@ module Hecks
|
|
|
82
86
|
# measured, not hypothetical: it once left a stale `Widget` constant
|
|
83
87
|
# that corrupted an unrelated spec's own unrelated use of the same
|
|
84
88
|
# bare name.
|
|
89
|
+
#
|
|
90
|
+
# @param dir [String] path to a bootable hecks app directory — a saved
|
|
91
|
+
# domain, or a throwaway one rendered into a temp dir for this purpose
|
|
92
|
+
# @return [Array<Failure>] every dispatch or query failure collected,
|
|
93
|
+
# empty when `dir` declares no bluebook or nothing failed
|
|
85
94
|
def call(dir)
|
|
86
95
|
Dir.mktmpdir("hecks-smoke-") do |scratch|
|
|
87
96
|
isolate!(dir, scratch)
|
|
@@ -93,9 +102,9 @@ module Hecks
|
|
|
93
102
|
end
|
|
94
103
|
end
|
|
95
104
|
|
|
96
|
-
#
|
|
105
|
+
# Only the `.bluebook`, never `.world` either — a real world's own
|
|
97
106
|
# settings (`persisted_by("Heki") { dir "..." }`) are keyed to the
|
|
98
|
-
#
|
|
107
|
+
# real adapter it names, not to Memory; copied verbatim, they
|
|
99
108
|
# apply to the wrong binding and refuse with a `WiringError`
|
|
100
109
|
# ("Memory does not declare :dir") — measured, not assumed, the
|
|
101
110
|
# first time this ran against Banking. A plain `Hecks.boot`
|
|
@@ -105,6 +114,11 @@ module Hecks
|
|
|
105
114
|
# excluded — those are earlier, superseded versions of the same
|
|
106
115
|
# domain; smoke-testing the current declaration is the point, not
|
|
107
116
|
# its whole history.
|
|
117
|
+
#
|
|
118
|
+
# @param dir [String] path to the source hecks app directory to isolate
|
|
119
|
+
# @param scratch [String] path to the throwaway directory to copy the
|
|
120
|
+
# `.bluebook` files into
|
|
121
|
+
# @return [void]
|
|
108
122
|
def isolate!(dir, scratch)
|
|
109
123
|
source = Adapters::Folder.new.bluebook_directory(dir)
|
|
110
124
|
target = File.join(scratch, "bluebook")
|
|
@@ -112,8 +126,8 @@ module Hecks
|
|
|
112
126
|
Dir.glob(File.join(source, "*.bluebook")).each { |file| FileUtils.cp(file, target) }
|
|
113
127
|
end
|
|
114
128
|
|
|
115
|
-
#
|
|
116
|
-
# a
|
|
129
|
+
# Aggregates walked in declaration order, on purpose — so whatever
|
|
130
|
+
# a later aggregate's own shape needs from an earlier one (a
|
|
117
131
|
# `TripItem` needing a real `PackingItem`, say) already exists by
|
|
118
132
|
# the time that aggregate's own turn comes, the same reason
|
|
119
133
|
# `Synthesizer#args_for`'s `created` map is threaded through in
|
|
@@ -123,6 +137,14 @@ module Hecks
|
|
|
123
137
|
# documents; splitting would turn that ordering into an implicit contract on
|
|
124
138
|
# parameter/return passing instead of one visible sequence.
|
|
125
139
|
# rubocop:disable-next Metrics/AbcSize
|
|
140
|
+
#
|
|
141
|
+
# @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the
|
|
142
|
+
# booted dispatcher to dispatch synthesized commands and queries through
|
|
143
|
+
# @param domain [String] the domain name to smoke-test, a key of
|
|
144
|
+
# `dispatcher.registry.bluebooks`
|
|
145
|
+
# @return [Array<Failure>] every dispatch or query failure collected, in
|
|
146
|
+
# declaration order; empty when `domain` names no loaded chapter or
|
|
147
|
+
# nothing failed
|
|
126
148
|
def smoke_domain(dispatcher, domain)
|
|
127
149
|
chapter = dispatcher.registry.bluebook(domain)
|
|
128
150
|
return [] unless chapter
|
|
@@ -136,7 +158,7 @@ module Hecks
|
|
|
136
158
|
creating.each do |command|
|
|
137
159
|
args = Synthesizer.args_for(chapter, aggregate, command, created)
|
|
138
160
|
begin
|
|
139
|
-
result = dispatcher.
|
|
161
|
+
result = dispatcher.dispatch_flat("#{domain}::#{aggregate.name}.#{command.hecks_name}", args)
|
|
140
162
|
created[aggregate.name] = result.instance.id
|
|
141
163
|
rescue StandardError => e
|
|
142
164
|
failures << Failure.new(domain: domain, aggregate: aggregate.name, command: command.hecks_name,
|
|
@@ -146,7 +168,7 @@ module Hecks
|
|
|
146
168
|
|
|
147
169
|
noncreating.each do |nc_command|
|
|
148
170
|
nc_args = Synthesizer.args_for(chapter, aggregate, nc_command, created).merge(id: created[aggregate.name])
|
|
149
|
-
dispatcher.
|
|
171
|
+
dispatcher.dispatch_flat("#{domain}::#{aggregate.name}.#{nc_command.hecks_name}", nc_args)
|
|
150
172
|
rescue StandardError => e
|
|
151
173
|
failures << Failure.new(domain: domain, aggregate: aggregate.name, command: nc_command.hecks_name,
|
|
152
174
|
error: "#{e.class}: #{e.message}")
|
|
@@ -156,7 +178,8 @@ module Hecks
|
|
|
156
178
|
|
|
157
179
|
chapter.read_models.each do |model|
|
|
158
180
|
root_id = created[model.reference_target]
|
|
159
|
-
|
|
181
|
+
# Nothing was created for this root — nothing to smoke-test yet, not a failure.
|
|
182
|
+
next unless root_id
|
|
160
183
|
|
|
161
184
|
dispatcher.query("#{domain}.#{model.query_name}", model.reference_name => root_id)
|
|
162
185
|
rescue StandardError => e
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Bluebook
|
|
3
|
-
#
|
|
3
|
+
# **One synthesized argument per declared attribute** — never a random
|
|
4
4
|
# guess. Reads only real `IR` (`Aggregate`, `Command`,
|
|
5
|
-
# `Attribute`, `ValueObject`), so it works against
|
|
5
|
+
# `Attribute`, `ValueObject`), so it works against any
|
|
6
6
|
# loaded bluebook, not one particular domain: a String becomes a
|
|
7
|
-
# fixed marker; an Integer becomes 0; a closed set uses
|
|
7
|
+
# fixed marker; an Integer becomes 0; a closed set uses its own
|
|
8
8
|
# first admitted member (never a value a real `one_of` would
|
|
9
|
-
# legitimately refuse); a reference uses whatever id the
|
|
9
|
+
# legitimately refuse); a reference uses whatever id the caller
|
|
10
10
|
# already knows for that target (`created`), so a command whose
|
|
11
11
|
# own shape depends on an earlier-created record — a `TripItem`
|
|
12
12
|
# needing a real `PackingItem`, say — gets something real to work
|
|
@@ -16,8 +16,8 @@ module Hecks
|
|
|
16
16
|
# got built and proved — the logic never actually knew anything
|
|
17
17
|
# about the Interview domain or any one app; it only ever read IR.
|
|
18
18
|
module Synthesizer
|
|
19
|
-
#
|
|
20
|
-
# a value object's own field names is
|
|
19
|
+
# The only type names that are ever truly scalar — anything else
|
|
20
|
+
# a value object's own field names is another value object,
|
|
21
21
|
# nested (`Pizza.price_cents: Price`, itself wrapping `cents:
|
|
22
22
|
# Integer` — a real, ordinary shape, not an edge case). Missing
|
|
23
23
|
# this the first time round meant a nested field silently
|
|
@@ -26,7 +26,7 @@ module Hecks
|
|
|
26
26
|
# only by testing against Pizzas instead of re-testing against
|
|
27
27
|
# the one simpler domain this was first built for, where every
|
|
28
28
|
# value object happened to be single-field and primitive-typed.
|
|
29
|
-
#
|
|
29
|
+
# The same list `Attribute::PRIMITIVES` holds, referenced rather
|
|
30
30
|
# than repeated. It was a second, byte-identical copy — and only
|
|
31
31
|
# the Attribute one is held to `vocabulary.bluebook` by
|
|
32
32
|
# spec/vocabulary_conformance_spec, so this copy could drift from
|
|
@@ -38,10 +38,21 @@ module Hecks
|
|
|
38
38
|
# `chapter`/`aggregate` are the real `Bluebook`/`Aggregate`
|
|
39
39
|
# the command belongs to — needed to resolve a value object type
|
|
40
40
|
# name back to its own declared shape. `created` maps an
|
|
41
|
-
# aggregate
|
|
41
|
+
# aggregate name to a real id already minted for it earlier in
|
|
42
42
|
# the same run; a reference whose target isn't in there yet gets
|
|
43
43
|
# a placeholder instead of failing outright, since the caller may
|
|
44
44
|
# not care about that particular argument's real value.
|
|
45
|
+
#
|
|
46
|
+
# @param chapter [Bluebook::Chapter] the chapter the command's aggregate
|
|
47
|
+
# belongs to, needed to resolve a value object type name back to its
|
|
48
|
+
# own declared shape
|
|
49
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `command` belongs to
|
|
50
|
+
# @param command [Class] the command class (a `Bluebook::Command`
|
|
51
|
+
# subclass) to synthesize arguments for
|
|
52
|
+
# @param created [Hash{String => Object}] aggregate name mapped to a real
|
|
53
|
+
# id already minted for it earlier in this same run
|
|
54
|
+
# @return [Hash{Symbol => Object}] one synthesized value per declared
|
|
55
|
+
# attribute, keyed by attribute name
|
|
45
56
|
def args_for(chapter, aggregate, command, created = {})
|
|
46
57
|
command.attributes.to_h do |attribute|
|
|
47
58
|
if attribute.reference?
|
|
@@ -52,13 +63,23 @@ module Hecks
|
|
|
52
63
|
end
|
|
53
64
|
end
|
|
54
65
|
|
|
55
|
-
# A value object's
|
|
66
|
+
# A value object's own synthesized value — its first admitted
|
|
56
67
|
# member if it's a closed set (the one value guaranteed not to be
|
|
57
68
|
# refused), otherwise one synthesized scalar per declared field.
|
|
58
69
|
# Falls back to searching every aggregate in the chapter, the
|
|
59
70
|
# same tolerance `bin/interview shape`'s own lookup already
|
|
60
71
|
# needed: a value object referenced by name doesn't have to be
|
|
61
|
-
# declared on the
|
|
72
|
+
# declared on the same aggregate using it.
|
|
73
|
+
#
|
|
74
|
+
# @param chapter [Bluebook::Chapter] the chapter to search when
|
|
75
|
+
# `type_name` is not declared on `aggregate` itself
|
|
76
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `type_name` is
|
|
77
|
+
# looked up on first
|
|
78
|
+
# @param type_name [String] the value object's declared type name
|
|
79
|
+
# @return [String, Hash{Symbol => Object}] the string `"smoke-test"` when
|
|
80
|
+
# no such value object is declared; otherwise a Hash of one value per
|
|
81
|
+
# field — the closed set's own first admitted member's fields, or one
|
|
82
|
+
# freshly synthesized scalar per declared field
|
|
62
83
|
def value_for(chapter, aggregate, type_name)
|
|
63
84
|
value_object = aggregate.value_object(type_name) ||
|
|
64
85
|
chapter.aggregates.filter_map { |a| a.value_object(type_name) }.first
|
|
@@ -72,17 +93,30 @@ module Hecks
|
|
|
72
93
|
|
|
73
94
|
# A single field's own synthesized value — a plain scalar if its
|
|
74
95
|
# type is one of the true primitives, otherwise that type names
|
|
75
|
-
#
|
|
96
|
+
# another value object, resolved the identical way `value_for`
|
|
76
97
|
# resolves any other one (its own closed set or its own nested
|
|
77
98
|
# fields, however deep that nesting actually goes).
|
|
99
|
+
#
|
|
100
|
+
# @param chapter [Bluebook::Chapter] see `value_for`
|
|
101
|
+
# @param aggregate [Bluebook::Aggregate] see `value_for`
|
|
102
|
+
# @param type_name [String] the field's declared type name
|
|
103
|
+
# @return [Integer, Float, true, false, String, Hash{Symbol => Object}] a
|
|
104
|
+
# bare scalar when `type_name` is a true primitive (see `scalar_for`),
|
|
105
|
+
# otherwise `value_for`'s own result for the nested value object it names
|
|
78
106
|
def field_value_for(chapter, aggregate, type_name)
|
|
79
107
|
PRIMITIVES.include?(type_name.to_s) ? scalar_for(type_name) : value_for(chapter, aggregate, type_name)
|
|
80
108
|
end
|
|
81
109
|
|
|
82
110
|
# A bare primitive's own synthesized value. Never used for a
|
|
83
|
-
# closed set's
|
|
111
|
+
# closed set's own field (that's `value_for`'s job, reading the
|
|
84
112
|
# set's real first member) — only for a plain scalar field with
|
|
85
113
|
# no declared vocabulary to respect.
|
|
114
|
+
#
|
|
115
|
+
# @param primitive [String, Symbol] the primitive type name, such as
|
|
116
|
+
# `"Integer"` or `"String"`
|
|
117
|
+
# @return [Integer, Float, false, String] `0` for `"Integer"`, `0.0` for
|
|
118
|
+
# `"Float"`, `false` for `"TrueClass"`/`"FalseClass"`, or the string
|
|
119
|
+
# `"smoke-test"` for anything else
|
|
86
120
|
def scalar_for(primitive)
|
|
87
121
|
case primitive.to_s
|
|
88
122
|
when "Integer" then 0
|
|
@@ -5,7 +5,7 @@ module Hecks
|
|
|
5
5
|
# Paths are dotted ("price.cents") for a VO member, bare otherwise.
|
|
6
6
|
TranslationMove = Struct.new(:from, :to)
|
|
7
7
|
|
|
8
|
-
# One field whose
|
|
8
|
+
# One field whose value, not just its name or position, changed —
|
|
9
9
|
# an old value has nothing in common with a new one, so the only
|
|
10
10
|
# honest way to bridge it is a declared, exhaustive lookup table.
|
|
11
11
|
# Paths follow `TranslationMove`'s convention (dotted reaches a VO
|
|
@@ -16,10 +16,10 @@ module Hecks
|
|
|
16
16
|
# rubocop:disable-next Lint/StructNewOverride
|
|
17
17
|
TranslationConvert = Struct.new(:from, :to, :values)
|
|
18
18
|
|
|
19
|
-
# A value object's or entity's own
|
|
19
|
+
# A value object's or entity's own type name changed with its member
|
|
20
20
|
# structure unchanged — the one drift `rename`/`move` cannot express,
|
|
21
21
|
# because the attribute kept its name and only the type it points at
|
|
22
|
-
#
|
|
22
|
+
# changed name. Mirrors `was:` one level deeper.
|
|
23
23
|
TranslationRetype = Struct.new(:from, :to)
|
|
24
24
|
|
|
25
25
|
# A computed transform — rescale, reformat, split, merge — whose only
|
|
@@ -29,7 +29,7 @@ module Hecks
|
|
|
29
29
|
# every other adapter.
|
|
30
30
|
TranslationCompute = Struct.new(:from, :to, :sql)
|
|
31
31
|
|
|
32
|
-
# The aggregate's own
|
|
32
|
+
# The aggregate's own identity changed what it's computed from — not
|
|
33
33
|
# a field crossing a boundary (that's `move`), a value objects's type
|
|
34
34
|
# name (`retype`), or a value transform (`compute`): the record's own
|
|
35
35
|
# key. No `from:`/`to:` path, unlike every other rule here, because
|
|
@@ -41,7 +41,7 @@ module Hecks
|
|
|
41
41
|
TranslationRekey = Struct.new(:sql)
|
|
42
42
|
|
|
43
43
|
# A newly added, required attribute with no source in old data at
|
|
44
|
-
# all — not a rename, move, or convert, all of which need a
|
|
44
|
+
# all — not a rename, move, or convert, all of which need a from
|
|
45
45
|
# path in the old shape. `default` is the value an existing record
|
|
46
46
|
# reads until the next command against it writes a real one; unlike
|
|
47
47
|
# `compute`, this is adapter-agnostic — applied in-process by
|
|
@@ -57,6 +57,23 @@ module Hecks
|
|
|
57
57
|
class TranslationAggregate
|
|
58
58
|
attr_reader :name, :was, :renames, :moves, :converts, :drops, :retypes, :computes, :rekeys, :backfills
|
|
59
59
|
|
|
60
|
+
# @param name [String, Symbol] the aggregate's name in the destination era
|
|
61
|
+
# @param was [String, Symbol, nil] the aggregate's name in the origin era, or `nil`
|
|
62
|
+
# if it was not renamed
|
|
63
|
+
# @param renames [Hash{Symbol => Symbol}] each renamed field, old name to new name
|
|
64
|
+
# @param moves [Array<Bluebook::TranslationMove>] fields crossing a value-object
|
|
65
|
+
# boundary
|
|
66
|
+
# @param converts [Array<Bluebook::TranslationConvert>] fields whose value is
|
|
67
|
+
# remapped through a declared lookup table
|
|
68
|
+
# @param drops [Array<Symbol>] fields deliberately not carried forward
|
|
69
|
+
# @param retypes [Array<Bluebook::TranslationRetype>] value object or entity type
|
|
70
|
+
# renames with their member structure unchanged
|
|
71
|
+
# @param computes [Array<Bluebook::TranslationCompute>] fields computed by a SQL
|
|
72
|
+
# expression evaluated only inside the compiled Postgres head
|
|
73
|
+
# @param rekeys [Array<Bluebook::TranslationRekey>] SQL expressions that recompute
|
|
74
|
+
# the aggregate's own identity
|
|
75
|
+
# @param backfills [Array<Bluebook::TranslationBackfill>] newly added required
|
|
76
|
+
# fields with no source in old data
|
|
60
77
|
def initialize(name:, was: nil, renames: {}, moves: [], converts: [], drops: [], retypes: [],
|
|
61
78
|
computes: [], rekeys: [], backfills: [])
|
|
62
79
|
@name = name.to_s
|
|
@@ -81,6 +98,13 @@ module Hecks
|
|
|
81
98
|
class Translation
|
|
82
99
|
attr_reader :domain, :from, :to, :aggregates, :retired
|
|
83
100
|
|
|
101
|
+
# @param domain [String, Symbol] the domain this translation carries forward
|
|
102
|
+
# @param from [String, Symbol] the origin era
|
|
103
|
+
# @param to [String, Symbol] the destination era
|
|
104
|
+
# @param aggregates [Array<Bluebook::TranslationAggregate>] each aggregate's own
|
|
105
|
+
# translation rules
|
|
106
|
+
# @param retired [Array<String>] the names of aggregates gone outright in the
|
|
107
|
+
# destination era, rather than renamed
|
|
84
108
|
def initialize(domain:, from:, to:, aggregates: [], retired: [])
|
|
85
109
|
@domain = domain.to_s
|
|
86
110
|
@from = from
|
|
@@ -89,6 +113,11 @@ module Hecks
|
|
|
89
113
|
@retired = retired
|
|
90
114
|
end
|
|
91
115
|
|
|
116
|
+
# Finds one aggregate's own translation rules by its destination-era name.
|
|
117
|
+
#
|
|
118
|
+
# @param name [String, Symbol] the aggregate's name in the destination era
|
|
119
|
+
# @return [Bluebook::TranslationAggregate, nil] the aggregate's translation, or
|
|
120
|
+
# `nil` if `name` carries no translation rules
|
|
92
121
|
def for_aggregate(name) = @aggregates.find { |aggregate| aggregate.name == name.to_s }
|
|
93
122
|
end
|
|
94
123
|
end
|
|
@@ -5,7 +5,7 @@ module Hecks
|
|
|
5
5
|
module Bluebook
|
|
6
6
|
Invariant = Struct.new(:description, :canonical, :predicate, :ast, keyword_init: true)
|
|
7
7
|
|
|
8
|
-
# A value object — a
|
|
8
|
+
# A value object — a declaration holder, never instantiated.
|
|
9
9
|
#
|
|
10
10
|
# `ValueObjectBuilder` returns an anonymous subclass whose singleton
|
|
11
11
|
# carries the declaration : attributes, invariants, members. Nothing ever
|
|
@@ -16,11 +16,11 @@ module Hecks
|
|
|
16
16
|
#
|
|
17
17
|
# `to_h` does not move. It is a byte-for-byte contract pinned by the
|
|
18
18
|
# golden fixtures, so it keeps spelling the short declared name, which
|
|
19
|
-
# is what `hecks_name` carries.
|
|
20
|
-
#
|
|
19
|
+
# is what `hecks_name` carries. Declarations in the graph, strings in the
|
|
20
|
+
# export.
|
|
21
21
|
class ValueObject
|
|
22
22
|
extend Construct
|
|
23
|
-
#
|
|
23
|
+
# Extended, not included — this construct is a class, so its
|
|
24
24
|
# emission is a class method. See Hecks::IR's own note on
|
|
25
25
|
# the two shapes.
|
|
26
26
|
extend Hecks::IR
|
|
@@ -29,7 +29,7 @@ module Hecks
|
|
|
29
29
|
emits_ir(
|
|
30
30
|
name: :hecks_name,
|
|
31
31
|
attributes: many(:attributes),
|
|
32
|
-
# `ast:` — a JSON-serializable rendering of the
|
|
32
|
+
# `ast:` — a JSON-serializable rendering of the same predicate
|
|
33
33
|
# `canonical` already spells as text, alongside it rather than
|
|
34
34
|
# replacing it (`canonical` stays the human-facing/doctest-facing
|
|
35
35
|
# form; parsing it back would just re-derive what `ast` already
|
|
@@ -39,14 +39,14 @@ module Hecks
|
|
|
39
39
|
# has no kernel crate to parse `canonical` with.
|
|
40
40
|
invariants: -> { invariants.map { |rule| Expression::AstJson.rule_row(rule) } },
|
|
41
41
|
closed_set: :closed_set?,
|
|
42
|
-
#
|
|
42
|
+
# The field name is stringified, never the value. A `member` row can
|
|
43
43
|
# hold any of the scalar types an attribute declares — `Integer 84`
|
|
44
44
|
# (`StatementFrequency#retention_months`, statements.bluebook), not
|
|
45
|
-
# only `String` — and
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
45
|
+
# only `String` — and stringifying it on the way out would erase
|
|
46
|
+
# that: `84` and `"84"` (a member some other row might legitimately
|
|
47
|
+
# spell as text) would become indistinguishable once they reached
|
|
48
|
+
# `to_h`. The declared name still moves (`field.to_s`) — that half
|
|
49
|
+
# was never a Ruby object with a type to lose.
|
|
50
50
|
members: -> { members.map { |member| member.map { |field, value| [field.to_s, value] } } }
|
|
51
51
|
)
|
|
52
52
|
|
|
@@ -55,6 +55,17 @@ module Hecks
|
|
|
55
55
|
|
|
56
56
|
# One declared shape — a subclass rather than an instance, so the thing
|
|
57
57
|
# the bluebook declares and the thing Ruby holds are one object.
|
|
58
|
+
#
|
|
59
|
+
# @param name [String, Symbol] the value object's declared type name
|
|
60
|
+
# @param attributes [Array<Bluebook::Attribute>] the value object's declared
|
|
61
|
+
# fields
|
|
62
|
+
# @param invariants [Array<Bluebook::Invariant>] the rules checked against every
|
|
63
|
+
# instance of this value object
|
|
64
|
+
# @param members [Array<Hash{Symbol => Object}>] the declared `one_of` members, one
|
|
65
|
+
# row of field values per member
|
|
66
|
+
# @param closed_set [Boolean] whether a `one_of` was declared, even with no
|
|
67
|
+
# members; defaults to whether `members` is non-empty
|
|
68
|
+
# @return [Class] the minted shape class (a `Bluebook::ValueObject` subclass)
|
|
58
69
|
def declare(name:, attributes: [], invariants: [], members: [], closed_set: !members.empty?)
|
|
59
70
|
shape = Class.new(self)
|
|
60
71
|
shape.hecks_name = name.to_s
|
|
@@ -63,6 +74,13 @@ module Hecks
|
|
|
63
74
|
shape
|
|
64
75
|
end
|
|
65
76
|
|
|
77
|
+
# Assigns what the language declares onto this shape class.
|
|
78
|
+
#
|
|
79
|
+
# @param attributes [Array<Bluebook::Attribute>] see `declare`
|
|
80
|
+
# @param invariants [Array<Bluebook::Invariant>] see `declare`
|
|
81
|
+
# @param members [Array<Hash{Symbol => Object}>] see `declare`
|
|
82
|
+
# @param closed_set [Boolean] see `declare`
|
|
83
|
+
# @return [void]
|
|
66
84
|
def absorb(attributes:, invariants:, members:, closed_set:)
|
|
67
85
|
@attributes = attributes
|
|
68
86
|
@invariants = invariants
|
data/lib/hecks/bluebook.rb
CHANGED
|
@@ -7,13 +7,12 @@
|
|
|
7
7
|
# DSL (dsl/), and the meta-validator that judges a chapter against the
|
|
8
8
|
# language's own grammar (meta_validator.rb + meta_validator/).
|
|
9
9
|
#
|
|
10
|
-
# The require order below
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# the meta-validator that its builders call at build time.
|
|
10
|
+
# The require order below matters: expression and IR load first (pure
|
|
11
|
+
# declarations), assembly's collaborators load before its face, and the DSL
|
|
12
|
+
# loads before the meta-validator that its builders call at build time.
|
|
14
13
|
|
|
15
14
|
module Hecks
|
|
16
|
-
# Declared as a
|
|
15
|
+
# Declared as a class, not a module — `Hecks::Bluebook` is a
|
|
17
16
|
# chapter (bluebook/chapter.rb carries its body). Everything a chapter
|
|
18
17
|
# declares nests under it, as do the ways to build one (`DSL`) and to
|
|
19
18
|
# judge one (`MetaValidator`). Reopening this anywhere must say `class`.
|
|
@@ -29,7 +28,7 @@ require_relative "literal"
|
|
|
29
28
|
require_relative "query_specification"
|
|
30
29
|
|
|
31
30
|
require_relative "bluebook/expression"
|
|
32
|
-
#
|
|
31
|
+
# **The model itself** — the chapter class's own body first, then everything
|
|
33
32
|
# a chapter declares. Order matters only for reading: each is a bag of
|
|
34
33
|
# declarations with no load-time cross-references.
|
|
35
34
|
require_relative "bluebook/chapter"
|