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,23 +2,22 @@ require "json"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Translation
|
|
5
|
-
# The closed,
|
|
6
|
-
# (
|
|
7
|
-
# that turns one TranslationAggregate's declared rules into
|
|
8
|
-
# jsonb-transforming SQL expression. No database connection, no
|
|
9
|
-
# watermark, no era chain, no catalog lookup: those
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# itself.
|
|
5
|
+
# The closed, pure half of Postgres's own SQL compiler
|
|
6
|
+
# (ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb)
|
|
7
|
+
# — the part that turns one TranslationAggregate's declared rules into
|
|
8
|
+
# a jsonb-transforming SQL expression. No database connection, no
|
|
9
|
+
# watermark, no era chain, no catalog lookup: those live in
|
|
10
|
+
# head_compiler.rb's own per-mint assembly, which calls into this
|
|
11
|
+
# module instead of defining these methods itself.
|
|
13
12
|
#
|
|
14
|
-
#
|
|
15
|
-
# Adapters::PostgresEra::Lineage — so a
|
|
13
|
+
# A module of its own — not private methods on
|
|
14
|
+
# Adapters::PostgresEra::Lineage — so a second, adapter-agnostic
|
|
16
15
|
# caller (Exporter.translation_aggregate's build-time SQL export,
|
|
17
16
|
# feeding rust/host's own future boot-time mint) can call the exact
|
|
18
|
-
#
|
|
19
|
-
# that could silently drift the way
|
|
20
|
-
#
|
|
21
|
-
#
|
|
17
|
+
# same code Ruby's own mint path runs, not a hand-ported duplicate
|
|
18
|
+
# that could silently drift the way a hand-kept
|
|
19
|
+
# `Exporter.translation_hash` can drift from `hecks_eras`/
|
|
20
|
+
# `hecks_approvals`' real schema (leaving out rekeys and backfills).
|
|
22
21
|
module RuleCompiler
|
|
23
22
|
module_function
|
|
24
23
|
|
|
@@ -27,6 +26,13 @@ module Hecks
|
|
|
27
26
|
# in the reference transform's phase order (renames, moves,
|
|
28
27
|
# converts, drops), computes last. `retype` compiles to nothing:
|
|
29
28
|
# stored state never carries a type name.
|
|
29
|
+
# Compiles a declared edge's rename/move/convert/drop/compute rules into one nested SQL
|
|
30
|
+
# expression over the `state` jsonb column.
|
|
31
|
+
#
|
|
32
|
+
# @param declared [Bluebook::TranslationAggregate] this edge's declared rules for one
|
|
33
|
+
# aggregate
|
|
34
|
+
# @return [String] a SQL expression, `"state"` unchanged when `declared` declares none
|
|
35
|
+
# of the five rule kinds
|
|
30
36
|
def compile_rules(declared)
|
|
31
37
|
expression = "state"
|
|
32
38
|
declared.renames.each do |old_name, new_name|
|
|
@@ -51,31 +57,47 @@ module Hecks
|
|
|
51
57
|
expression
|
|
52
58
|
end
|
|
53
59
|
|
|
54
|
-
# Whether
|
|
60
|
+
# Whether this edge's declared rules for this aggregate include a
|
|
55
61
|
# rekey — checked directly off the raw IR object, the same way
|
|
56
62
|
# every other rule kind is already read in `compile_rules`
|
|
57
63
|
# (`declared.computes`, `declared.moves`, ...), not through the
|
|
58
64
|
# `Ports::Persistence::Lineage` wrapper the app-level consumers
|
|
59
65
|
# (coverage_check.rb, minter.rb, layer_two.rb) go through — this
|
|
60
66
|
# module builds SQL straight off the IR either way.
|
|
67
|
+
# Reports whether an edge's declared rules for an aggregate include a rekey.
|
|
68
|
+
#
|
|
69
|
+
# @param declared [Bluebook::TranslationAggregate, nil] this edge's declared rules for
|
|
70
|
+
# one aggregate; nil for an aggregate the edge declares nothing about
|
|
71
|
+
# @return [Boolean] true when `declared` is present and its `rekeys` is non-empty
|
|
61
72
|
def rekeyed?(declared) = declared && !declared.rekeys.empty?
|
|
62
73
|
|
|
63
|
-
#
|
|
74
|
+
# The only two places `aggregate_id` needs to change — guarded so
|
|
64
75
|
# the generated SQL for the overwhelming common case (no rekey
|
|
65
76
|
# declared) stays the bare `aggregate_id` passthrough it always
|
|
66
|
-
# was — this
|
|
77
|
+
# was — this case only appears in an edge that actually declares
|
|
67
78
|
# one.
|
|
79
|
+
#
|
|
80
|
+
# @param guard [String] a SQL boolean expression gating when the rekey applies, such as
|
|
81
|
+
# `"operation = 'save'"`
|
|
82
|
+
# @param declared [Bluebook::TranslationAggregate] this edge's declared rules; must
|
|
83
|
+
# declare a rekey (`rekeyed?(declared)` true)
|
|
84
|
+
# @return [String] a `"CASE WHEN ... END AS aggregate_id"` SQL expression
|
|
68
85
|
def id_case(guard, declared)
|
|
69
86
|
"CASE WHEN #{guard} THEN #{compile_id_expression(declared)} ELSE aggregate_id END AS aggregate_id"
|
|
70
87
|
end
|
|
71
88
|
|
|
72
|
-
#
|
|
89
|
+
# The rekey's own SQL — reading `state` directly, not the
|
|
73
90
|
# progressively-built `expression` chain `compile_compute` reads
|
|
74
91
|
# from. A rekey doesn't consume or move any field the way a move
|
|
75
92
|
# or compute does, so there is no same-edge rename/move ordering
|
|
76
93
|
# it needs to see first — it reads the record's stored fields
|
|
77
94
|
# exactly as they already are, the same `__s` convention
|
|
78
95
|
# `compile_compute` exposes.
|
|
96
|
+
#
|
|
97
|
+
# @param declared [Bluebook::TranslationAggregate] this edge's declared rules; its
|
|
98
|
+
# first `rekeys` entry supplies the SQL
|
|
99
|
+
# @return [String] a SQL expression evaluating the rekey's own SQL against the record's
|
|
100
|
+
# current `state`
|
|
79
101
|
def compile_id_expression(declared)
|
|
80
102
|
rekey = declared.rekeys.first
|
|
81
103
|
"(SELECT (#{rekey.sql}) FROM (SELECT (state) AS __s) __outer)"
|
|
@@ -85,6 +107,12 @@ module Hecks
|
|
|
85
107
|
# — evaluated exclusively inside the compiled head, never
|
|
86
108
|
# in-process. The old field is exposed under its own name (as
|
|
87
109
|
# text, exactly as the author's expression expects to cast it).
|
|
110
|
+
#
|
|
111
|
+
# @param expression [String] the SQL expression built so far by `compile_rules`, read as
|
|
112
|
+
# `__s` inside the compute's own SQL
|
|
113
|
+
# @param compute [Bluebook::TranslationCompute] the declared compute rule
|
|
114
|
+
# @return [String] `expression` wrapped so the compute's field lands at its declared
|
|
115
|
+
# destination when the source field is present, unchanged otherwise
|
|
88
116
|
def compile_compute(expression, compute)
|
|
89
117
|
from = compute.from.to_s
|
|
90
118
|
to = compute.to.to_s
|
|
@@ -96,7 +124,7 @@ module Hecks
|
|
|
96
124
|
"LATERAL (SELECT (__s ->> #{text_literal(from)}) AS #{quote(from)}) __fields)"
|
|
97
125
|
end
|
|
98
126
|
|
|
99
|
-
# `PG::Connection.quote_ident` needs the `pg` gem
|
|
127
|
+
# `PG::Connection.quote_ident` needs the `pg` gem loaded, not
|
|
100
128
|
# connected — required here, lazily, the same "a domain that
|
|
101
129
|
# never wires PostgresEra should never need the gem" reasoning
|
|
102
130
|
# `PostgresEra.connect_for`'s own `require "pg"` already holds
|
|
@@ -104,13 +132,24 @@ module Hecks
|
|
|
104
132
|
# non-Postgres-bound domain (there are none today, but nothing
|
|
105
133
|
# here should assume there never will be) doesn't gain a hard
|
|
106
134
|
# dependency on `pg` just by loading this file.
|
|
135
|
+
#
|
|
136
|
+
# @param name [String, Symbol] the identifier to quote
|
|
137
|
+
# @return [String] `name` as a double-quoted Postgres identifier
|
|
107
138
|
def quote(name)
|
|
108
139
|
require "pg"
|
|
109
140
|
PG::Connection.quote_ident(name.to_s)
|
|
110
141
|
end
|
|
111
142
|
|
|
143
|
+
# Renders a Ruby value as a single-quoted SQL text literal, escaping embedded quotes.
|
|
144
|
+
#
|
|
145
|
+
# @param text [String, Symbol, Object] the value to render; converted with `to_s`
|
|
146
|
+
# @return [String] a single-quoted SQL literal
|
|
112
147
|
def text_literal(text) = "'#{text.to_s.gsub("'", "''")}'"
|
|
113
148
|
|
|
149
|
+
# Renders a dotted path as a SQL `text[]` array literal, one element per segment.
|
|
150
|
+
#
|
|
151
|
+
# @param path [String, Symbol] a bare or dotted path, such as `"price.cents"`
|
|
152
|
+
# @return [String] a SQL `ARRAY[...]::text[]` expression
|
|
114
153
|
def path_literal(path)
|
|
115
154
|
segments = path.to_s.split(".").map { |segment| text_literal(segment) }
|
|
116
155
|
"ARRAY[#{segments.join(', ')}]::text[]"
|
|
@@ -8,6 +8,13 @@ module Hecks
|
|
|
8
8
|
# everything ambiguous left unresolved for a human.
|
|
9
9
|
module Differ
|
|
10
10
|
# Diff two bluebook IRs into the edge's declarations.
|
|
11
|
+
#
|
|
12
|
+
# @param held_bluebook [Bluebook::Chapter] the era being translated from
|
|
13
|
+
# @param current_bluebook [Bluebook::Chapter] the era being translated to
|
|
14
|
+
# @return [Hash{Symbol => Object}] `:aggregates` (Array<Scaffold::ScaffoldedAggregate>,
|
|
15
|
+
# one per matched aggregate carrying a rename or a rule), `:retired` (Array<String>,
|
|
16
|
+
# vanished aggregate names with no successor), `:unclaimed` (Array<String>, vanished
|
|
17
|
+
# aggregate names left ambiguous)
|
|
11
18
|
def diff(held_bluebook, current_bluebook)
|
|
12
19
|
held_shapes = projections(held_bluebook)
|
|
13
20
|
current_shapes = projections(current_bluebook)
|
|
@@ -27,17 +34,31 @@ module Hecks
|
|
|
27
34
|
{ aggregates: aggregates, retired: matched[:retired], unclaimed: matched[:unclaimed] }
|
|
28
35
|
end
|
|
29
36
|
|
|
37
|
+
# Projects a bluebook's storage shape and indexes its aggregates by name.
|
|
38
|
+
#
|
|
39
|
+
# @param bluebook [Bluebook::Chapter] the bluebook to project
|
|
40
|
+
# @return [Hash{String => Hash}] the bluebook's storage-shape aggregates (see
|
|
41
|
+
# `Runtime::StorageShape.project`), keyed by aggregate name
|
|
30
42
|
def projections(bluebook)
|
|
31
43
|
Runtime::StorageShape.project(bluebook)["aggregates"].to_h { |shape| [shape["name"], shape] }
|
|
32
44
|
end
|
|
33
45
|
|
|
34
46
|
# A vanished aggregate whose full shape reappears under exactly one
|
|
35
|
-
# new name
|
|
47
|
+
# new name counts as a rename. `retired` is only confident when nothing
|
|
36
48
|
# remains it could plausibly have become — a vanished aggregate
|
|
37
49
|
# beside an unmatched new one might be a rename-plus-reshape, and
|
|
38
50
|
# writing `retired` there would be a guess that strands data.
|
|
39
51
|
# Anything ambiguous stays unclaimed, and the coverage gate names
|
|
40
52
|
# it until a human decides.
|
|
53
|
+
#
|
|
54
|
+
# @param held_shapes [Hash{String => Hash}] the held era's aggregate shapes, keyed by
|
|
55
|
+
# name (see `projections`)
|
|
56
|
+
# @param current_shapes [Hash{String => Hash}] the current era's aggregate shapes,
|
|
57
|
+
# keyed by name
|
|
58
|
+
# @return [Hash{Symbol => Object}] `:pairs` (Hash{String => String, nil}, each current
|
|
59
|
+
# name to its matched held name, or nil when unmatched), `:retired` (Array<String>,
|
|
60
|
+
# vanished names with no successor), `:unclaimed` (Array<String>, vanished names
|
|
61
|
+
# left ambiguous)
|
|
41
62
|
def match_aggregates(held_shapes, current_shapes)
|
|
42
63
|
pairs = {}
|
|
43
64
|
current_shapes.each_key { |name| pairs[name] = held_shapes.key?(name) ? name : nil }
|
|
@@ -61,12 +82,19 @@ module Hecks
|
|
|
61
82
|
end
|
|
62
83
|
|
|
63
84
|
# Every changed path in one aggregate, resolved into rules by
|
|
64
|
-
# signature matching over the
|
|
85
|
+
# signature matching over the full path set — top-level names and
|
|
65
86
|
# dotted members alike. Unique signature pair: a rename (both
|
|
66
87
|
# top-level) or a move (any dotted end). Same path, same members,
|
|
67
88
|
# new type name: a retype. Anything else: unresolved, carrying its
|
|
68
89
|
# type-compatible candidates (an empty list is the arrow toward
|
|
69
90
|
# compute, or drop).
|
|
91
|
+
#
|
|
92
|
+
# @param held_shape [Hash{String => Object}] the held era's aggregate shape (see
|
|
93
|
+
# `projections`)
|
|
94
|
+
# @param current_shape [Hash{String => Object}] the current era's aggregate shape
|
|
95
|
+
# @return [Array<Hash{Symbol => Object}>] one rule Hash per changed path — `:kind`
|
|
96
|
+
# plus `:from`/`:to` for `:rename`/`:move`/`:retype`, or `:from`/`:candidates` for
|
|
97
|
+
# `:unresolved` (see `Renderer#render_rule`); `[]` when nothing changed
|
|
70
98
|
def attribute_rules(held_shape, current_shape)
|
|
71
99
|
rules = []
|
|
72
100
|
rules << identity_hint(held_shape, current_shape)
|
|
@@ -81,7 +109,7 @@ module Hecks
|
|
|
81
109
|
appeared = appeared_paths(held_attrs, current_attrs, retyped)
|
|
82
110
|
|
|
83
111
|
# Mutates `rules` in place (not build-and-concat, like the two
|
|
84
|
-
# passes above) because it must
|
|
112
|
+
# passes above) because it must read its own earlier writes:
|
|
85
113
|
# `taken`, below, is recomputed from `rules` at the top of every
|
|
86
114
|
# iteration, so a target this same loop already claimed for an
|
|
87
115
|
# earlier vanished path is excluded from a later one. Passing a
|
|
@@ -92,7 +120,14 @@ module Hecks
|
|
|
92
120
|
end
|
|
93
121
|
|
|
94
122
|
# retype pass: same attribute name, same member structure, the
|
|
95
|
-
#
|
|
123
|
+
# type's own name changed
|
|
124
|
+
#
|
|
125
|
+
# @param held_attrs [Hash{String => Hash}] the held era's attributes, keyed by name
|
|
126
|
+
# @param current_attrs [Hash{String => Hash}] the current era's attributes, keyed by
|
|
127
|
+
# name
|
|
128
|
+
# @return [Array<Hash{Symbol => String}>] one `{kind: :retype, from:, to:}` rule per
|
|
129
|
+
# attribute whose members are unchanged but whose type name changed; `[]` when none
|
|
130
|
+
# match
|
|
96
131
|
def retype_rules(held_attrs, current_attrs)
|
|
97
132
|
(held_attrs.keys & current_attrs.keys).filter_map do |name|
|
|
98
133
|
held = held_attrs[name]
|
|
@@ -105,6 +140,16 @@ module Hecks
|
|
|
105
140
|
end
|
|
106
141
|
end
|
|
107
142
|
|
|
143
|
+
# Resolves each vanished path into a rename, move or unresolved rule, appended to
|
|
144
|
+
# `rules` in place.
|
|
145
|
+
#
|
|
146
|
+
# @param rules [Array<Hash{Symbol => Object}>] the rule list to append to; also read
|
|
147
|
+
# for `:rename`/`:move` targets an earlier iteration already claimed
|
|
148
|
+
# @param vanished [Hash{String => Object}] vanished paths to type signature (see
|
|
149
|
+
# `vanished_paths`)
|
|
150
|
+
# @param appeared [Hash{String => Object}] appeared paths to type signature (see
|
|
151
|
+
# `appeared_paths`)
|
|
152
|
+
# @return [void]
|
|
108
153
|
def resolve_vanished_rules!(rules, vanished, appeared)
|
|
109
154
|
vanished.each do |path, signature|
|
|
110
155
|
matches = appeared.select { |_, candidate| candidate == signature }.keys
|
|
@@ -125,6 +170,14 @@ module Hecks
|
|
|
125
170
|
# and members that vanished (or changed type) inside a kept
|
|
126
171
|
# attribute — mirroring exactly what EraGuard will demand coverage
|
|
127
172
|
# for.
|
|
173
|
+
#
|
|
174
|
+
# @param held_attrs [Hash{String => Hash}] the held era's attributes, keyed by name
|
|
175
|
+
# @param current_attrs [Hash{String => Hash}] the current era's attributes, keyed by
|
|
176
|
+
# name
|
|
177
|
+
# @param retyped [Array<String>] type names already claimed by a `:retype` rule,
|
|
178
|
+
# excluded here
|
|
179
|
+
# @return [Hash{String => Object}] each vanished or changed path, dotted for a member,
|
|
180
|
+
# valued by that path's type signature
|
|
128
181
|
def vanished_paths(held_attrs, current_attrs, retyped)
|
|
129
182
|
paths = {}
|
|
130
183
|
held_attrs.each do |name, held|
|
|
@@ -146,6 +199,17 @@ module Hecks
|
|
|
146
199
|
paths
|
|
147
200
|
end
|
|
148
201
|
|
|
202
|
+
# Current paths that need explaining: whole attributes that appeared, and members
|
|
203
|
+
# that appeared (or changed type) inside a kept attribute — the mirror of
|
|
204
|
+
# `vanished_paths`.
|
|
205
|
+
#
|
|
206
|
+
# @param held_attrs [Hash{String => Hash}] the held era's attributes, keyed by name
|
|
207
|
+
# @param current_attrs [Hash{String => Hash}] the current era's attributes, keyed by
|
|
208
|
+
# name
|
|
209
|
+
# @param retyped [Array<String>] type names already claimed by a `:retype` rule,
|
|
210
|
+
# excluded here
|
|
211
|
+
# @return [Hash{String => Object}] each appeared or changed path, dotted for a
|
|
212
|
+
# member, valued by that path's type signature
|
|
149
213
|
def appeared_paths(held_attrs, current_attrs, retyped)
|
|
150
214
|
paths = {}
|
|
151
215
|
current_attrs.each do |name, current|
|
|
@@ -166,31 +230,58 @@ module Hecks
|
|
|
166
230
|
paths
|
|
167
231
|
end
|
|
168
232
|
|
|
169
|
-
#
|
|
233
|
+
# The one thing `attribute_rules` could not see before — the shape
|
|
170
234
|
# projection already carries `"identity"` (`StorageShape
|
|
171
235
|
# .project_aggregate`), it was simply never read here. An
|
|
172
236
|
# unresolved placeholder, not a guess: `coverage_check.rb`'s own
|
|
173
237
|
# `check_identity_unchanged!` is the real gate this only hints
|
|
174
238
|
# toward, the same "tool proactively guides you" pattern the
|
|
175
239
|
# generic unresolved message already gives unfed fields.
|
|
240
|
+
#
|
|
241
|
+
# @param held_shape [Hash{String => Object}] the held era's aggregate shape
|
|
242
|
+
# @param current_shape [Hash{String => Object}] the current era's aggregate shape
|
|
243
|
+
# @return [Hash{Symbol => Object}, nil] `{kind: :unresolved, from: :identity,
|
|
244
|
+
# candidates: []}` when the identity paths differ; nil when they match
|
|
176
245
|
def identity_hint(held_shape, current_shape)
|
|
177
246
|
return if held_shape["identity"] == current_shape["identity"]
|
|
178
247
|
|
|
179
248
|
{ kind: :unresolved, from: :identity, candidates: [] }
|
|
180
249
|
end
|
|
181
250
|
|
|
251
|
+
# Finds appeared paths type-compatible with a vanished path's own signature.
|
|
252
|
+
#
|
|
253
|
+
# @param appeared [Hash{String => Object}] appeared paths to type signature (see
|
|
254
|
+
# `appeared_paths`)
|
|
255
|
+
# @param signature [Object] the vanished path's own type signature to match against
|
|
256
|
+
# @return [Array<String>] appeared paths whose scalar signature (see `scalar_of`)
|
|
257
|
+
# matches `signature`'s
|
|
182
258
|
def compatible_candidates(appeared, signature)
|
|
183
259
|
appeared.select { |_, candidate| scalar_of(candidate) == scalar_of(signature) }.keys
|
|
184
260
|
end
|
|
185
261
|
|
|
262
|
+
# Reduces a type signature to the shape `compatible_candidates` compares by.
|
|
263
|
+
#
|
|
264
|
+
# @param signature [Object, Hash] a path's type signature
|
|
265
|
+
# @return [Object] `signature["members"]` for a container Hash, else `signature`
|
|
266
|
+
# unchanged
|
|
186
267
|
def scalar_of(signature) = signature.is_a?(Hash) ? signature["members"] : signature
|
|
187
268
|
|
|
269
|
+
# Reads a container attribute's own members, keyed by name.
|
|
270
|
+
#
|
|
271
|
+
# @param attribute [Hash{String => Object}] one path's type signature
|
|
272
|
+
# @return [Hash{String => Object}] each member's name to its type; `{}` when
|
|
273
|
+
# `attribute` is not a container
|
|
188
274
|
def members_of(attribute)
|
|
189
275
|
return {} unless container?(attribute)
|
|
190
276
|
|
|
191
277
|
attribute["type"]["members"].to_h { |member| [member["name"], member["type"]] }
|
|
192
278
|
end
|
|
193
279
|
|
|
280
|
+
# Reports whether a type signature is a container (value object or entity), not a
|
|
281
|
+
# scalar.
|
|
282
|
+
#
|
|
283
|
+
# @param attribute [Hash{String => Object}] one path's type signature
|
|
284
|
+
# @return [Boolean] true if `attribute["type"]` is a Hash, false for a scalar
|
|
194
285
|
def container?(attribute) = attribute["type"].is_a?(Hash)
|
|
195
286
|
end
|
|
196
287
|
end
|
|
@@ -5,6 +5,10 @@ module Hecks
|
|
|
5
5
|
# ambiguities as parse-refusing `unresolved` constructs, never
|
|
6
6
|
# comments, so an unresolved file can only boot into a refusal.
|
|
7
7
|
module Renderer
|
|
8
|
+
# Renders a scaffolded edge as loadable `.bluebook` translation source.
|
|
9
|
+
#
|
|
10
|
+
# @param edge [Scaffold::Edge] the edge to render
|
|
11
|
+
# @return [String] the edge's `.bluebook` text, ending in a newline
|
|
8
12
|
def render(edge)
|
|
9
13
|
lines = ["Hecks.data_translation #{edge.domain.inspect}, from: #{edge.from.inspect}, to: #{edge.to.inspect} do"]
|
|
10
14
|
edge.aggregates.each do |aggregate|
|
|
@@ -19,6 +23,12 @@ module Hecks
|
|
|
19
23
|
"#{lines.join("\n")}\n"
|
|
20
24
|
end
|
|
21
25
|
|
|
26
|
+
# Renders one scaffolded rule as a line of `.bluebook` source.
|
|
27
|
+
#
|
|
28
|
+
# @param rule [Hash{Symbol => Object}] a rule Hash as `Differ#attribute_rules` builds
|
|
29
|
+
# it: `:kind` plus `:from`/`:to`, or `:from`/`:candidates` for `:unresolved`
|
|
30
|
+
# @return [String, nil] the rendered line; nil if `rule[:kind]` is none of `:rename`,
|
|
31
|
+
# `:move`, `:retype` or `:unresolved`
|
|
22
32
|
def render_rule(rule)
|
|
23
33
|
case rule[:kind]
|
|
24
34
|
when :rename then "rename :#{rule[:from]}, to: :#{rule[:to]}"
|
|
@@ -30,6 +40,11 @@ module Hecks
|
|
|
30
40
|
end
|
|
31
41
|
end
|
|
32
42
|
|
|
43
|
+
# Renders a path as bluebook source: a Symbol literal when bare, a String literal
|
|
44
|
+
# when dotted.
|
|
45
|
+
#
|
|
46
|
+
# @param path [String, Symbol] a bare or dotted path
|
|
47
|
+
# @return [String] `":name"` for a bare path, `path.inspect` for a dotted one
|
|
33
48
|
def render_path(path) = path.to_s.include?(".") ? path.to_s.inspect : ":#{path}"
|
|
34
49
|
end
|
|
35
50
|
end
|
|
@@ -6,10 +6,18 @@ module Hecks
|
|
|
6
6
|
# Put the rendered edge on disk, regenerating in place when a file
|
|
7
7
|
# for the same shape pair already exists.
|
|
8
8
|
module Writer
|
|
9
|
+
# Renders an edge and writes it under `directory/translations`, regenerating in
|
|
10
|
+
# place when a file for the same shape pair already exists.
|
|
11
|
+
#
|
|
9
12
|
# The edge file, regenerated in place when one for the same shape
|
|
10
13
|
# pair already exists (matched textually — an unresolved file
|
|
11
|
-
# cannot be
|
|
14
|
+
# cannot be loaded to ask, that being the whole point of
|
|
12
15
|
# unresolved).
|
|
16
|
+
#
|
|
17
|
+
# @param directory [String] the domain's root directory
|
|
18
|
+
# @param edge [Scaffold::Edge] the edge to render and write
|
|
19
|
+
# @return [String] the path written, either the matched existing file or a new
|
|
20
|
+
# `<ordinal>-<label>.bluebook`
|
|
13
21
|
def write!(directory, edge)
|
|
14
22
|
translations_dir = File.join(directory, "translations")
|
|
15
23
|
FileUtils.mkdir_p(translations_dir)
|
|
@@ -6,13 +6,13 @@ module Hecks
|
|
|
6
6
|
module Translation
|
|
7
7
|
# The scaffold writes translations; humans resolve ambiguity. It
|
|
8
8
|
# diffs the held era's storage-shape projection against the current
|
|
9
|
-
# one (scaffold/differ.rb) and
|
|
9
|
+
# one (scaffold/differ.rb) and writes the edge file
|
|
10
10
|
# (scaffold/renderer.rb, scaffold/writer.rb): confident rules inline
|
|
11
11
|
# (unique signature pairs → rename/move, aggregate renames → `was:`,
|
|
12
12
|
# type renames with identical members → retype, aggregates gone
|
|
13
13
|
# without a successor → retired), ambiguities as parse-refusing
|
|
14
14
|
# `unresolved` constructs — never comments, so an unresolved file can
|
|
15
|
-
# only boot into a refusal, never a guess. It
|
|
15
|
+
# only boot into a refusal, never a guess. It never proposes a
|
|
16
16
|
# `compute` (a computation takes domain judgment no mechanical diff
|
|
17
17
|
# can infer) — an empty-candidate `unresolved` is what points the
|
|
18
18
|
# author there. It never proposes a `drop` either: data loss is a
|
|
@@ -11,7 +11,7 @@ module Hecks
|
|
|
11
11
|
module Ports
|
|
12
12
|
module Persistence
|
|
13
13
|
module Plugins
|
|
14
|
-
# ADR 0033 — requiring
|
|
14
|
+
# ADR 0033 — requiring this file is installing the plugin. Nothing in
|
|
15
15
|
# Hecks core requires it; an app that binds `PostgresEra`, or wants
|
|
16
16
|
# schema-translation support at all, requires it explicitly — the
|
|
17
17
|
# same shape every adapter-specific spec fixture already uses to
|
|
@@ -19,6 +19,8 @@ module Hecks
|
|
|
19
19
|
module Era
|
|
20
20
|
module_function
|
|
21
21
|
|
|
22
|
+
# Registers this plugin's era gates on one boot's gate list.
|
|
23
|
+
#
|
|
22
24
|
# `Runtime::Loader.run_boot_gates!` asks every loaded persistence
|
|
23
25
|
# plugin to contribute here, generically — it never mentions
|
|
24
26
|
# `EraCheck` or "era" by name. Two gates, both `:pre_verify`:
|
|
@@ -29,11 +31,19 @@ module Hecks
|
|
|
29
31
|
# unchanged) — this is the rich, adapter-aware version of that
|
|
30
32
|
# check; `Runtime::Loader`'s own structural backstop (plain
|
|
31
33
|
# `Bluebook::Translation` data, no plugin-specific class) only
|
|
32
|
-
# ever fires when
|
|
34
|
+
# ever fires when no persistence plugin is loaded at all.
|
|
33
35
|
#
|
|
34
36
|
# `:era_check` — conditional, exactly ADR 0031's own gate,
|
|
35
37
|
# unchanged: registered only when this registry has an aggregate
|
|
36
38
|
# actually bound to a lineage-capable adapter.
|
|
39
|
+
#
|
|
40
|
+
# @param registry [Runtime::Registry] the registry being booted, asked whether any
|
|
41
|
+
# bluebook's first aggregate is bound to a lineage-capable adapter
|
|
42
|
+
# @param gates [Runtime::BootGates] this boot's gate list, registered onto in place
|
|
43
|
+
# @return [Runtime::BootGates, nil] `gates` when `:era_check` was registered; nil when
|
|
44
|
+
# the registry binds nothing lineage-capable, so only `:era_compute_rules` was added
|
|
45
|
+
# @raise [Runtime::WiringError] if an aggregate's persistence binding is missing,
|
|
46
|
+
# ambiguous, or carries an unsupported role
|
|
37
47
|
def contribute_boot_gates(registry, gates)
|
|
38
48
|
gates.register(:era_compute_rules, lambda { |reg, _dir|
|
|
39
49
|
Runtime::EraCheck.check_compute_rules_for_registry!(reg)
|
|
@@ -3,12 +3,12 @@ require_relative "../../runtime/registry"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Ports
|
|
5
5
|
module Persistence
|
|
6
|
-
#
|
|
6
|
+
# The other shape an adapter can be. `AppendOnly` names one shape
|
|
7
7
|
# already — something that stores bytes, locally (Postgres, SQLite,
|
|
8
8
|
# Heki) or over the network (D1), doesn't matter, the point is it
|
|
9
9
|
# does real local interpretation and has real entries to replay.
|
|
10
10
|
# This names the second, different shape: not "storage reached
|
|
11
|
-
# remotely," but the real
|
|
11
|
+
# remotely," but the real interpreter itself living behind a call
|
|
12
12
|
# boundary — `append`/`project` don't do partial local work and
|
|
13
13
|
# then fail, they raise unconditionally, because there is no local
|
|
14
14
|
# write-ahead log to have; `entries` is always `[]` for the same
|
|
@@ -28,6 +28,10 @@ module Hecks
|
|
|
28
28
|
# `Runtime::RemoteDispatcher`'s own use) instead of comparing
|
|
29
29
|
# adapter names by string.
|
|
30
30
|
module RemoteRuntime
|
|
31
|
+
# Refuses every local write; `project` is an alias and refuses the same way.
|
|
32
|
+
#
|
|
33
|
+
# @return [void] never returns
|
|
34
|
+
# @raise [Runtime::WiringError] always, pointing the caller at `Runtime::RemoteDispatcher`
|
|
31
35
|
def append(*)
|
|
32
36
|
raise Runtime::WiringError,
|
|
33
37
|
"#{self.class.name} is a remote-runtime delegate — dispatch through " \
|
|
@@ -35,6 +39,9 @@ module Hecks
|
|
|
35
39
|
end
|
|
36
40
|
alias project append
|
|
37
41
|
|
|
42
|
+
# Reports an empty journal, since a remote-runtime delegate keeps no local log.
|
|
43
|
+
#
|
|
44
|
+
# @return [Array] always `[]`
|
|
38
45
|
def entries = []
|
|
39
46
|
end
|
|
40
47
|
end
|
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
require_relative "append_only"
|
|
2
|
+
require_relative "codec_boundary"
|
|
2
3
|
|
|
3
4
|
module Hecks
|
|
4
5
|
module Ports
|
|
5
6
|
module Persistence
|
|
6
7
|
# Turns a declared adapter binding plus its world configuration into a
|
|
7
8
|
# concrete repository. Selection policy stays out of adapter creation.
|
|
9
|
+
#
|
|
10
|
+
# Every adapter it builds is guarded (`CodecBoundary.guard!`) before
|
|
11
|
+
# anything else touches it — `recover!` included — so no adapter a
|
|
12
|
+
# runtime reaches can build an `Instance` from undecoded state.
|
|
8
13
|
module RepositoryFactory
|
|
9
14
|
module_function
|
|
10
15
|
|
|
16
|
+
# Instantiates the adapter a bind names, guards it, and wraps it as a repository.
|
|
17
|
+
#
|
|
18
|
+
# @param registry [Runtime::Registry] the registry supplying the adapter class, the
|
|
19
|
+
# domain's world settings, its resolved eras and the root path
|
|
20
|
+
# @param domain [String, Symbol] name of the domain the aggregate belongs to
|
|
21
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate to persist
|
|
22
|
+
# @param bind [Bluebook::Bind] the bind naming the adapter, as `BindingPolicy.resolve`
|
|
23
|
+
# or the projection port chose it
|
|
24
|
+
# @param recover [Boolean] true to replay the journal through `project` before returning
|
|
25
|
+
# @param settings_verb [String] the verb whose world settings configure the adapter;
|
|
26
|
+
# `"persisted_by"` by default, `Ports::Projection::VERB` for a projection
|
|
27
|
+
# @return [Persistence::AppendOnly] the repository over the guarded adapter
|
|
28
|
+
# @raise [Runtime::WiringError] if the bind's verb or settings fail the registry's
|
|
29
|
+
# checks, no `Hecks::Adapters` constant matches the adapter name, or the adapter
|
|
30
|
+
# lacks `append`, `project` or `entries`
|
|
11
31
|
def build(registry, domain, aggregate, bind, recover: true, settings_verb: VERB)
|
|
12
32
|
registry.check_verb(bind)
|
|
13
33
|
settings = (registry.world(domain)&.for_binding(settings_verb, bind.adapter) || {})
|
|
@@ -15,8 +35,8 @@ module Hecks
|
|
|
15
35
|
registry.check_settings(bind, settings)
|
|
16
36
|
# The domain, the resolved era, and (for an old checkout) the era
|
|
17
37
|
# that superseded it ride along after the declared-settings
|
|
18
|
-
# check: a lineage adapter journals per
|
|
19
|
-
# own
|
|
38
|
+
# check: a lineage adapter journals per domain, writes into its
|
|
39
|
+
# own era's partition, and refuses to write at all once that era
|
|
20
40
|
# is superseded (`PostgresEra#append`, BUG#24) — none of which a
|
|
21
41
|
# world's settings carry.
|
|
22
42
|
adapter = registry.adapter_class(bind.adapter)
|
|
@@ -24,7 +44,7 @@ module Hecks
|
|
|
24
44
|
settings: settings.merge(domain: domain.to_s, era: registry.resolved_eras[domain.to_s],
|
|
25
45
|
superseded_by: registry.superseded_eras[domain.to_s]),
|
|
26
46
|
root: registry.root)
|
|
27
|
-
repository = AppendOnly.new(adapter)
|
|
47
|
+
repository = AppendOnly.new(CodecBoundary.guard!(adapter))
|
|
28
48
|
recover ? repository.recover! : repository
|
|
29
49
|
end
|
|
30
50
|
end
|