hecks 0.2.0 → 0.3.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 +5 -5
- data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
- data/lib/hecks/adapters/driven/claude_code.rb +127 -0
- data/lib/hecks/adapters/driven/d1.adapter +12 -0
- data/lib/hecks/adapters/driven/d1.rb +403 -0
- data/lib/hecks/adapters/driven/folder.adapter +3 -0
- data/lib/hecks/adapters/driven/folder.rb +199 -0
- data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
- data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
- data/lib/hecks/adapters/driven/governance_authorization.rb +62 -0
- data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
- data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
- data/lib/hecks/adapters/driven/heki.adapter +4 -0
- data/lib/hecks/adapters/driven/heki.rb +148 -0
- data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
- data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
- data/lib/hecks/adapters/driven/lambda.adapter +4 -0
- data/lib/hecks/adapters/driven/lambda.rb +131 -0
- data/lib/hecks/adapters/driven/memory.adapter +3 -0
- data/lib/hecks/adapters/driven/memory.rb +103 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
- data/lib/hecks/adapters/driven/postgres/codec.rb +80 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
- data/lib/hecks/adapters/driven/postgres.adapter +5 -0
- data/lib/hecks/adapters/driven/postgres.rb +391 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
- data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
- data/lib/hecks/adapters/driven/prism.adapter +3 -0
- data/lib/hecks/adapters/driven/prism.rb +80 -0
- data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
- data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
- data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
- data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
- data/lib/hecks/adapters/driven/sqlite.rb +285 -0
- data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +14 -0
- data/lib/hecks/adapters/driven.rb +36 -0
- data/lib/hecks/adapters.rb +6 -0
- data/lib/hecks/behaviors/dsl.rb +96 -0
- data/lib/hecks/behaviors/expectations.rb +280 -0
- data/lib/hecks/behaviors/ir.rb +31 -0
- data/lib/hecks/behaviors/rspec.rb +42 -0
- data/lib/hecks/behaviors/runner.rb +96 -0
- data/lib/hecks/behaviors.rb +25 -0
- data/lib/hecks/bluebook/aggregate.rb +108 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
- data/lib/hecks/bluebook/assembly/build.rb +48 -0
- data/lib/hecks/bluebook/assembly/contract.rb +112 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
- data/lib/hecks/bluebook/assembly/marks.rb +227 -0
- data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
- data/lib/hecks/bluebook/assembly.rb +78 -0
- data/lib/hecks/bluebook/attribute.rb +96 -0
- data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
- data/lib/hecks/bluebook/behaviour/command.rb +116 -0
- data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
- data/lib/hecks/bluebook/behaviour/query.rb +10 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
- data/lib/hecks/bluebook/chapter.rb +68 -0
- data/lib/hecks/bluebook/command.rb +124 -0
- data/lib/hecks/bluebook/domain_port.rb +102 -0
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
- data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
- data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
- data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
- data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
- data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
- data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
- data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
- data/lib/hecks/bluebook/dsl.rb +45 -0
- data/lib/hecks/bluebook/entity.rb +103 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
- data/lib/hecks/bluebook/expression/projection.json +170 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
- data/lib/hecks/bluebook/expression/resolver.rb +630 -0
- data/lib/hecks/bluebook/expression.rb +13 -0
- data/lib/hecks/bluebook/hexagon.rb +60 -0
- data/lib/hecks/bluebook/lifecycle.rb +42 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
- data/lib/hecks/bluebook/meta_validator.rb +452 -0
- data/lib/hecks/bluebook/model_check.rb +318 -0
- data/lib/hecks/bluebook/pattern_subset.rb +120 -0
- data/lib/hecks/bluebook/policy.rb +41 -0
- data/lib/hecks/bluebook/process_manager.rb +86 -0
- data/lib/hecks/bluebook/project_discovery.rb +30 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +82 -0
- data/lib/hecks/bluebook/query.rb +61 -0
- data/lib/hecks/bluebook/read_model.rb +109 -0
- data/lib/hecks/bluebook/reference.rb +74 -0
- data/lib/hecks/bluebook/smoke_test.rb +166 -0
- data/lib/hecks/bluebook/synthesizer.rb +95 -0
- data/lib/hecks/bluebook/translation.rb +92 -0
- data/lib/hecks/bluebook/value_object.rb +58 -0
- data/lib/hecks/bluebook.rb +74 -0
- data/lib/hecks/codemod.rb +342 -0
- data/lib/hecks/construct.rb +71 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
- data/lib/hecks/deploy/oidc.json +18 -0
- data/lib/hecks/doc/reference.rb +389 -0
- data/lib/hecks/embryonaut_bluebook.rb +75 -0
- data/lib/hecks/facade/cli_door.rb +116 -0
- data/lib/hecks/facade/cli_runner.rb +190 -0
- data/lib/hecks/facade/command_request.rb +105 -0
- data/lib/hecks/facade/handle.rb +173 -0
- data/lib/hecks/facade/json_door.rb +154 -0
- data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
- data/lib/hecks/facade/surface/chapter.rb +107 -0
- data/lib/hecks/facade/surface.rb +48 -0
- data/lib/hecks/facade.rb +44 -0
- data/lib/hecks/forms/app.rb +300 -0
- data/lib/hecks/forms/command_form_renderer.rb +113 -0
- data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
- data/lib/hecks/forms/field_renderer.rb +170 -0
- data/lib/hecks/forms/field_shape.rb +232 -0
- data/lib/hecks/forms/html.rb +53 -0
- data/lib/hecks/forms/index_renderer.rb +35 -0
- data/lib/hecks/forms/page.rb +157 -0
- data/lib/hecks/forms/params.rb +132 -0
- data/lib/hecks/forms/query_form_renderer.rb +114 -0
- data/lib/hecks/forms/record_renderer.rb +115 -0
- data/lib/hecks/forms/record_table.rb +63 -0
- data/lib/hecks/forms/reference_options.rb +30 -0
- data/lib/hecks/forms/value_object_shape.rb +46 -0
- data/lib/hecks/forms.rb +54 -0
- data/lib/hecks/fqn.rb +94 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
- data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
- data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
- data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
- data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
- data/lib/hecks/framework/oidc.json +39 -0
- data/lib/hecks/framework.rb +90 -0
- data/lib/hecks/freezer.rb +67 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
- data/lib/hecks/fuzzing/properties.rb +1146 -0
- data/lib/hecks/fuzzing/replay.rb +635 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
- data/lib/hecks/fuzzing/value_generator.rb +177 -0
- data/lib/hecks/fuzzing.rb +12 -0
- data/lib/hecks/grammar/evolve.rb +291 -0
- data/lib/hecks/grammar/expression.bluebook +417 -0
- data/lib/hecks/grammar/expression_operators.json +1152 -0
- data/lib/hecks/grammar/grammar.hecksagon +20 -0
- data/lib/hecks/grammar/oidc.json +69 -0
- data/lib/hecks/grammar/translation.bluebook +203 -0
- data/lib/hecks/grammar.rb +178 -0
- data/lib/hecks/ir.rb +126 -0
- data/lib/hecks/language/adapter.bluebook +116 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
- data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
- data/lib/hecks/language/bluebook/command.bluebook +437 -0
- data/lib/hecks/language/bluebook/entity.bluebook +316 -0
- data/lib/hecks/language/bluebook/policy.bluebook +172 -0
- data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
- data/lib/hecks/language/bluebook/projection.bluebook +267 -0
- data/lib/hecks/language/bluebook/query.bluebook +245 -0
- data/lib/hecks/language/bluebook/shape.bluebook +292 -0
- data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
- data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
- data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
- data/lib/hecks/language/oidc.json +313 -0
- data/lib/hecks/language/port.bluebook +92 -0
- data/lib/hecks/language/translation/translation.bluebook +110 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
- data/lib/hecks/language/world/wiring.bluebook +62 -0
- data/lib/hecks/language/world/world.bluebook +84 -0
- data/lib/hecks/literal.rb +125 -0
- data/lib/hecks/naming.rb +121 -0
- data/lib/hecks/ports/access_control.port +4 -0
- data/lib/hecks/ports/access_control.rb +62 -0
- data/lib/hecks/ports/agent/answers.rb +104 -0
- data/lib/hecks/ports/agent.port +4 -0
- data/lib/hecks/ports/agent.rb +167 -0
- data/lib/hecks/ports/authentication.port +4 -0
- data/lib/hecks/ports/authentication.rb +50 -0
- data/lib/hecks/ports/authorization.port +4 -0
- data/lib/hecks/ports/authorization.rb +59 -0
- data/lib/hecks/ports/clock.port +4 -0
- data/lib/hecks/ports/clock.rb +62 -0
- data/lib/hecks/ports/extraction.port +4 -0
- data/lib/hecks/ports/extraction.rb +37 -0
- data/lib/hecks/ports/identity_assignment.port +4 -0
- data/lib/hecks/ports/identity_assignment.rb +45 -0
- data/lib/hecks/ports/identity_generation.port +4 -0
- data/lib/hecks/ports/identity_generation.rb +49 -0
- data/lib/hecks/ports/identity_resolution.port +4 -0
- data/lib/hecks/ports/identity_resolution.rb +40 -0
- data/lib/hecks/ports/loading.port +4 -0
- data/lib/hecks/ports/loading.rb +13 -0
- data/lib/hecks/ports/persistence/append_only.rb +102 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
- data/lib/hecks/ports/persistence/execution.rb +18 -0
- data/lib/hecks/ports/persistence/lineage.rb +277 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
- data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
- data/lib/hecks/ports/persistence.port +4 -0
- data/lib/hecks/ports/persistence.rb +39 -0
- data/lib/hecks/ports/projection.port +4 -0
- data/lib/hecks/ports/projection.rb +76 -0
- data/lib/hecks/ports/query/in_memory.rb +60 -0
- data/lib/hecks/ports/query/ordering.rb +41 -0
- data/lib/hecks/ports/query.rb +36 -0
- data/lib/hecks/ports.rb +27 -0
- data/lib/hecks/projections/diagrams.rb +413 -0
- data/lib/hecks/projections/ir.rb +18 -0
- data/lib/hecks/projections/model/deviations.rb +98 -0
- data/lib/hecks/projections/model.rb +145 -0
- data/lib/hecks/projections/oidc.rb +110 -0
- data/lib/hecks/projections/parser_table.rb +159 -0
- data/lib/hecks/projections/reference.rb +38 -0
- data/lib/hecks/projections/shape.rb +31 -0
- data/lib/hecks/projections/statements.rb +110 -0
- data/lib/hecks/projections/vocabulary.rb +100 -0
- data/lib/hecks/projections.rb +32 -0
- data/lib/hecks/projector/cli_projector.rb +437 -0
- data/lib/hecks/projector/docs_projector.rb +321 -0
- data/lib/hecks/projector/exporter.rb +141 -0
- data/lib/hecks/projector/ir_projector.rb +18 -0
- data/lib/hecks/projector/narrate_projector.rb +243 -0
- data/lib/hecks/projector/target.rb +97 -0
- data/lib/hecks/projector.rb +186 -0
- data/lib/hecks/query_ir.rb +392 -0
- data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
- data/lib/hecks/query_specification/common/comparators.rb +25 -0
- data/lib/hecks/query_specification/common/comparison.rb +174 -0
- data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
- data/lib/hecks/query_specification/common/dsl.rb +58 -0
- data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
- data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
- data/lib/hecks/query_specification/common/null_policy.rb +80 -0
- data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
- data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
- data/lib/hecks/query_specification/common/options.rb +32 -0
- data/lib/hecks/query_specification/common/order_by.rb +9 -0
- data/lib/hecks/query_specification/common/specification.rb +10 -0
- data/lib/hecks/query_specification/common/where_clause.rb +9 -0
- data/lib/hecks/query_specification/field_path.rb +89 -0
- data/lib/hecks/query_specification/hop_path.rb +130 -0
- data/lib/hecks/query_specification/read_model/specification.rb +18 -0
- data/lib/hecks/query_specification.rb +14 -0
- data/lib/hecks/rendering.rb +48 -0
- data/lib/hecks/router/namespace_installer.rb +157 -0
- data/lib/hecks/router.rb +70 -0
- data/lib/hecks/runtime/caller.rb +50 -0
- data/lib/hecks/runtime/capability_graph.rb +44 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
- data/lib/hecks/runtime/command_interpreter.rb +413 -0
- data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
- data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
- data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
- data/lib/hecks/runtime/command_rules/emission.rb +34 -0
- data/lib/hecks/runtime/command_rules/references.rb +181 -0
- data/lib/hecks/runtime/command_rules.rb +28 -0
- data/lib/hecks/runtime/dependency_planning.rb +231 -0
- data/lib/hecks/runtime/dispatcher.rb +286 -0
- data/lib/hecks/runtime/entity_element.rb +253 -0
- data/lib/hecks/runtime/entity_interpreter.rb +228 -0
- data/lib/hecks/runtime/era_check.rb +136 -0
- data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
- data/lib/hecks/runtime/era_guard.rb +193 -0
- data/lib/hecks/runtime/era_tamper.rb +61 -0
- data/lib/hecks/runtime/errors.rb +100 -0
- data/lib/hecks/runtime/event.rb +51 -0
- data/lib/hecks/runtime/identity.rb +128 -0
- data/lib/hecks/runtime/instance.rb +120 -0
- data/lib/hecks/runtime/interpreting.rb +69 -0
- data/lib/hecks/runtime/loader.rb +143 -0
- data/lib/hecks/runtime/policy_interpreter.rb +366 -0
- data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
- data/lib/hecks/runtime/query_interpreter.rb +254 -0
- data/lib/hecks/runtime/reaction_invocation.rb +245 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
- data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
- data/lib/hecks/runtime/reference_hop.rb +99 -0
- data/lib/hecks/runtime/refusal_wording.rb +124 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
- data/lib/hecks/runtime/registry/verification.rb +158 -0
- data/lib/hecks/runtime/registry.rb +218 -0
- data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
- data/lib/hecks/runtime/routing.rb +96 -0
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
- data/lib/hecks/runtime/saga_interpreter.rb +301 -0
- data/lib/hecks/runtime/storage_shape.rb +120 -0
- data/lib/hecks/runtime/tenant_check.rb +84 -0
- data/lib/hecks/runtime/tenant_scope.rb +55 -0
- data/lib/hecks/runtime/value/admission.rb +112 -0
- data/lib/hecks/runtime/value/coercion.rb +500 -0
- data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
- data/lib/hecks/runtime/value.rb +125 -0
- data/lib/hecks/runtime.rb +95 -0
- data/lib/hecks/translation/audit/approval_digest.rb +31 -0
- data/lib/hecks/translation/audit/layer_one.rb +37 -0
- data/lib/hecks/translation/audit/layer_two.rb +77 -0
- data/lib/hecks/translation/audit/unfed_report.rb +42 -0
- data/lib/hecks/translation/audit.rb +70 -0
- data/lib/hecks/translation/reattest.rb +72 -0
- data/lib/hecks/translation/rule_compiler.rb +120 -0
- data/lib/hecks/translation/scaffold/differ.rb +183 -0
- data/lib/hecks/translation/scaffold/renderer.rb +41 -0
- data/lib/hecks/translation/scaffold/writer.rb +28 -0
- data/lib/hecks/translation/scaffold.rb +30 -0
- data/lib/hecks/translation.rb +11 -0
- data/lib/hecks/version.rb +16 -0
- data/lib/hecks/vocabulary.rb +213 -0
- data/lib/hecks.rb +118 -6
- metadata +401 -222
- data/bin/hecks +0 -7
- data/bin/hecks-package +0 -65
- data/bin/hecks_console +0 -12
- data/bin/hecks_serverless +0 -6
- data/lib/cli/build.rb +0 -14
- data/lib/cli/command_runner.rb +0 -28
- data/lib/cli/console.rb +0 -10
- data/lib/cli/generate.rb +0 -37
- data/lib/cli/hecks-cli.rb +0 -27
- data/lib/cli/test.rb +0 -57
- data/lib/console/commands.rb +0 -8
- data/lib/console/hecks-console.rb +0 -1
- data/lib/packager/README.md +0 -0
- data/lib/packager/app_runner.rb +0 -21
- data/lib/packager/args.rb +0 -26
- data/lib/packager/compatibility/fixnum.rb +0 -6
- data/lib/packager/hecks.rb +0 -39
- data/lib/packager/query_runner.rb +0 -21
- data/lib/packager/resources/Dockerfile +0 -11
- data/lib/packager/resources/app_binary +0 -7
- data/lib/packager/resources/bundle_config +0 -3
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
- data/lib/serverless/Domain +0 -32
- data/lib/serverless/cli.rb +0 -75
- data/lib/serverless/resources/command_name.js +0 -5
- data/lib/serverless/resources/environment.js +0 -7
- data/lib/serverless/resources/handler.js.tt +0 -28
- data/lib/serverless/resources/run_binary.js +0 -22
- data/lib/serverless/resources/serverless.yml +0 -20
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
require_relative "interpreting"
|
|
2
|
+
require_relative "../naming"
|
|
3
|
+
require_relative "../rendering"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
require_relative "identity"
|
|
6
|
+
require_relative "instance"
|
|
7
|
+
require_relative "value"
|
|
8
|
+
require_relative "refusal_wording"
|
|
9
|
+
require_relative "routing"
|
|
10
|
+
require_relative "dependency_planning"
|
|
11
|
+
require_relative "../ports/persistence/execution"
|
|
12
|
+
require_relative "entity_element"
|
|
13
|
+
|
|
14
|
+
module Hecks
|
|
15
|
+
module Runtime
|
|
16
|
+
class EntityInterpreter
|
|
17
|
+
include Interpreting
|
|
18
|
+
|
|
19
|
+
attr_reader :registry
|
|
20
|
+
|
|
21
|
+
# THE DECLARED ORDER, HAND-TYPED — mirrors Vocabulary::EntityDispatchOrder
|
|
22
|
+
# (language/bluebook/vocabulary.bluebook:217-232), held equal to it by
|
|
23
|
+
# spec/vocabulary_conformance_spec.rb the same way CommandInterpreter's
|
|
24
|
+
# own DISPATCH_ORDER is; see that constant's doc comment for why this is
|
|
25
|
+
# hand-typed rather than read live off the meta-domain at every dispatch.
|
|
26
|
+
# Shorter than the aggregate order for the same reasons the declaration
|
|
27
|
+
# itself gives : no refuse_unknown_arguments/refuse_absent_arguments (an
|
|
28
|
+
# entity inherits its aggregate's own gate) and no
|
|
29
|
+
# assign_creation_attributes (an entity is never created through this
|
|
30
|
+
# path).
|
|
31
|
+
DISPATCH_ORDER = Hecks::Vocabulary.symbols("EntityDispatchOrder")
|
|
32
|
+
|
|
33
|
+
# `instance` is the PARENT aggregate record (what gets saved and
|
|
34
|
+
# returned) ; `element`/`view` are the entity piece itself — `view`
|
|
35
|
+
# wraps `element` as it stood at `locate_element`, pre-mutation, and
|
|
36
|
+
# `enforce_ensures` builds its own settled wrapper off `element` as it
|
|
37
|
+
# stands after, the same split the original sequential code made.
|
|
38
|
+
#
|
|
39
|
+
# `chain` — S17, ADR 0026 — every entity the dotted verb passes
|
|
40
|
+
# through, root-first (`[Handler, Dispatch]` for `Handler.Dispatch.
|
|
41
|
+
# Bind`) ; `entity`/`entity_name` stay the CHAIN'S OWN LAST entry,
|
|
42
|
+
# the one a command actually belongs to and a mutation actually
|
|
43
|
+
# targets, so every step written before this ADR (enforce_givens,
|
|
44
|
+
# apply_mutations, advance_lifecycle, element_identity, ...) reads
|
|
45
|
+
# exactly as it always has. Only `locate_element` walks the chain.
|
|
46
|
+
Context = Struct.new(:domain, :aggregate, :entity, :entity_name, :command, :command_name,
|
|
47
|
+
:args, :repository, :instance, :chain, :element, :view, :transition,
|
|
48
|
+
:old_element, :result, :route, :plan, :persistence_outcome, :dry_run)
|
|
49
|
+
|
|
50
|
+
def initialize(registry, rules:)
|
|
51
|
+
@registry = registry
|
|
52
|
+
@rules = rules
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# `dry_run:` — CommandInterpreter#call's own twin, see that method's
|
|
56
|
+
# comment for the shared reasoning (Dispatcher#dry_run?'s own entry
|
|
57
|
+
# point). `step_save`/`step_emit` are the only two steps here that
|
|
58
|
+
# read it either.
|
|
59
|
+
def call(domain, aggregate, dotted, legacy_args, route: nil, with: nil, dry_run: false)
|
|
60
|
+
*entity_names, command_name = dotted.to_s.split(".")
|
|
61
|
+
if entity_names.empty?
|
|
62
|
+
raise UnknownVerb, RefusalWording.render("UnknownVerb", "entity_unknown",
|
|
63
|
+
aggregate: aggregate.hecks_name, entity: dotted.to_s.inspect)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
chain = walk_entity_chain(aggregate, entity_names)
|
|
67
|
+
entity = chain.last
|
|
68
|
+
command = entity.command(command_name) ||
|
|
69
|
+
raise(UnknownVerb, RefusalWording.render("UnknownVerb", "entity_no_command",
|
|
70
|
+
entity: entity.hecks_name, command: command_name.inspect))
|
|
71
|
+
|
|
72
|
+
args = Routing.payload(command, with: with, legacy: legacy_args)
|
|
73
|
+
ctx = Context.new(domain, aggregate, entity, entity_names.join("."), command, command_name, args)
|
|
74
|
+
ctx.chain = chain
|
|
75
|
+
ctx.route = route
|
|
76
|
+
ctx.dry_run = dry_run
|
|
77
|
+
ctx.plan = DependencyPlanning::Analyzer.call(aggregate: entity, command: command)
|
|
78
|
+
run_dispatch_order(DISPATCH_ORDER, ctx)
|
|
79
|
+
[ctx.instance, ctx.result, ctx.plan, ctx.persistence_outcome]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
# ONE HOP PER DOTTED SEGMENT — `ProcessManager.Handler.Dispatch.Bind`
|
|
85
|
+
# (once the dispatcher has already stripped "Domain::Aggregate.")
|
|
86
|
+
# walks Handler off the aggregate, then Dispatch off Handler, each
|
|
87
|
+
# step reading `.entities` exactly the way the single-level case
|
|
88
|
+
# always did — a nested entity is "structurally interchangeable
|
|
89
|
+
# with an aggregate" (Entity's own header) for precisely this
|
|
90
|
+
# reason. Two levels is what Handler/Dispatch need today ; nothing
|
|
91
|
+
# here assumes it stops at two.
|
|
92
|
+
def walk_entity_chain(aggregate, entity_names)
|
|
93
|
+
owner = aggregate
|
|
94
|
+
entity_names.map do |name|
|
|
95
|
+
found = owner.entities.find { |piece| piece.hecks_name == name } ||
|
|
96
|
+
raise(UnknownVerb, RefusalWording.render("UnknownVerb", "entity_unknown",
|
|
97
|
+
aggregate: owner.hecks_name, entity: name.inspect))
|
|
98
|
+
owner = found
|
|
99
|
+
found
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def step_normalize_args(ctx)
|
|
104
|
+
ctx.args = step(:normalize_args) { normalize_args(ctx.aggregate, ctx.command, ctx.args) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def step_refuse_role_mismatch(ctx)
|
|
108
|
+
step(:refuse_role_mismatch) { @rules.refuse_role_mismatch(ctx.command, ctx.domain) }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def step_resolve_references(ctx)
|
|
112
|
+
step(:resolve_references) { @rules.resolve_references(ctx.domain, ctx.command, ctx.args) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def step_hydrate_parent(ctx)
|
|
116
|
+
ctx.repository = @registry.repository(ctx.domain, ctx.aggregate)
|
|
117
|
+
ctx.instance = step(:hydrate_parent) {
|
|
118
|
+
parent(ctx.repository, ctx.aggregate, ctx.entity_name, ctx.command_name, ctx.args, ctx.route)
|
|
119
|
+
}
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def step_locate_element(ctx)
|
|
123
|
+
ctx.element = step(:locate_element) {
|
|
124
|
+
EntityElement.locate_chain(ctx.aggregate, ctx.chain, ctx.instance, ctx.args, ctx.command_name, ctx.route)
|
|
125
|
+
}
|
|
126
|
+
# `view` was hydrated ONCE, here, into its OWN state hash
|
|
127
|
+
# (Value.hydrate builds a fresh Hash — never aliased with `element`)
|
|
128
|
+
# — exactly right for enforce_givens, which must read pre-mutation.
|
|
129
|
+
ctx.view = Instance.new(aggregate: ctx.entity, id: EntityElement.element_identity(ctx.entity, ctx.element).to_s,
|
|
130
|
+
state: ctx.element)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def step_enforce_givens(ctx)
|
|
134
|
+
step(:enforce_givens) {
|
|
135
|
+
@rules.enforce_givens(ctx.view, ctx.command, ctx.args, domain: ctx.domain, declaring: ctx.entity, parent: ctx.instance)
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def step_admissible_transition(ctx)
|
|
140
|
+
ctx.transition = step(:admissible_transition) { @rules.admissible_transition(ctx.entity, ctx.command, ctx.view) }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def step_apply_mutations(ctx)
|
|
144
|
+
ctx.old_element = ctx.element.dup unless ctx.command.ensures.empty?
|
|
145
|
+
step(:apply_mutations) {
|
|
146
|
+
ctx.command.mutations.each { |mutation|
|
|
147
|
+
EntityElement.apply_to_element(@rules, ctx.aggregate, ctx.entity, ctx.element, mutation, ctx.args)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def step_advance_lifecycle(ctx)
|
|
153
|
+
return unless ctx.transition
|
|
154
|
+
|
|
155
|
+
step(:advance_lifecycle) { ctx.element[ctx.entity.lifecycle.field] = ctx.transition.target }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# An ensures reads the SETTLED record, so it needs a view hydrated from
|
|
159
|
+
# `element` as it stands now, mutations included — unlike `view` above,
|
|
160
|
+
# built once and read pre-mutation by enforce_givens.
|
|
161
|
+
def step_enforce_ensures(ctx)
|
|
162
|
+
step(:enforce_ensures) do
|
|
163
|
+
settled = Instance.new(aggregate: ctx.entity, id: ctx.view.id, state: ctx.element)
|
|
164
|
+
@rules.enforce_ensures(settled, ctx.command, ctx.args, old: ctx.old_element, domain: ctx.domain, parent: ctx.instance)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# THE PARENT AGGREGATE's own invariants — `ctx.instance` is the
|
|
169
|
+
# parent record an entity mutation writes into (this file's own
|
|
170
|
+
# `Context` comment), the SAME boundary an aggregate-level
|
|
171
|
+
# invariant guards regardless of which interpreter changed it. No
|
|
172
|
+
# separate "entity invariant" exists (S10, ADR 0025 scopes
|
|
173
|
+
# `invariant` to the aggregate only) — see `Admissibility#
|
|
174
|
+
# enforce_invariants`'s own comment.
|
|
175
|
+
def step_enforce_invariants(ctx)
|
|
176
|
+
step(:enforce_invariants) { @rules.enforce_invariants(ctx.instance, ctx.aggregate, domain: ctx.domain) }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# `dry_run:` skips this — see CommandInterpreter#step_save's own
|
|
180
|
+
# comment, same reasoning and the same precedent
|
|
181
|
+
# (`step_assign_creation_attributes`'s own conditional-skip).
|
|
182
|
+
def step_save(ctx)
|
|
183
|
+
return if ctx.dry_run
|
|
184
|
+
|
|
185
|
+
step(:save) do
|
|
186
|
+
@rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state)
|
|
187
|
+
ctx.repository.save(ctx.instance)
|
|
188
|
+
ctx.persistence_outcome = Ports::Persistence::Outcome.new(status: :saved, instance: ctx.instance)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# `dry_run:` skips this too — nothing was committed, so `ctx.result`
|
|
193
|
+
# stays nil and `Dispatcher#dry_run?` never reads it.
|
|
194
|
+
def step_emit(ctx)
|
|
195
|
+
return if ctx.dry_run
|
|
196
|
+
|
|
197
|
+
ctx.result = step(:emit) { @rules.emit(ctx.command, ctx.domain, ctx.aggregate, ctx.instance, ctx.args, ctx.repository) }
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# THE PARENT AGGREGATE, addressed exactly as `CommandInterpreter#hydrate`
|
|
201
|
+
# addresses one acting on itself — derive from the declared identity first
|
|
202
|
+
# (`Identity.of`), and let a bare `id:` name an already-derived record when
|
|
203
|
+
# the identity itself is not what the caller is holding.
|
|
204
|
+
def parent(repository, aggregate, entity_name, command_name, args, route = nil)
|
|
205
|
+
parent_id = route&.aggregate ||
|
|
206
|
+
Identity.of(aggregate, args) ||
|
|
207
|
+
Identity.from(aggregate, args, :id) ||
|
|
208
|
+
raise(NotFound, RefusalWording.render("NotFound", "entity_parent_no_identity",
|
|
209
|
+
command: command_name, aggregate: aggregate.hecks_name,
|
|
210
|
+
entity: entity_name, identity: Identity.reading(aggregate)))
|
|
211
|
+
found = repository.find(parent_id) ||
|
|
212
|
+
raise(NotFound, RefusalWording.render("NotFound", "record_missing",
|
|
213
|
+
aggregate: aggregate.hecks_name,
|
|
214
|
+
identity: Identity.reading(aggregate),
|
|
215
|
+
offered: Rendering.describe(parent_id)))
|
|
216
|
+
found.dup
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# `locate_chain`/`element_of`/`element_identity`/`apply_to_element` and
|
|
220
|
+
# their own helpers used to live here — moved to `Runtime::EntityElement`
|
|
221
|
+
# (see that file's own header) so `CommandInterpreter`'s own
|
|
222
|
+
# `delegate_to_entity` step can locate and mutate the same element the
|
|
223
|
+
# same way, against an aggregate record already held in memory. `call`,
|
|
224
|
+
# above, and every `step_*` method reach them through that module now;
|
|
225
|
+
# nothing about the STEPS themselves changed.
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require_relative "../ports/persistence"
|
|
2
|
+
require_relative "../ports/persistence/binding_policy"
|
|
3
|
+
require_relative "../ports/persistence/lineage"
|
|
4
|
+
require_relative "../naming"
|
|
5
|
+
require_relative "../framework"
|
|
6
|
+
require_relative "registry"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module Runtime
|
|
10
|
+
# The boot-time era gate, run for the adapters that HAVE eras — the
|
|
11
|
+
# lineage-capable ones. An era is a fact about stored data that some
|
|
12
|
+
# adapter can carry across a shape change; an adapter that cannot
|
|
13
|
+
# translate has no era to hold, and holding one for it would record
|
|
14
|
+
# history about data nothing can act on.
|
|
15
|
+
#
|
|
16
|
+
# The whole domain is handed to the capable adapter's own era check
|
|
17
|
+
# (Postgres: hold, recognize, mint, or refuse toward the scaffold),
|
|
18
|
+
# which keeps its era facts as rows BESIDE its data. That
|
|
19
|
+
# co-location is load-bearing — a watermark only means something
|
|
20
|
+
# against the journal it was cut from, and an approval recorded
|
|
21
|
+
# anywhere but the reviewed database binds to nothing.
|
|
22
|
+
#
|
|
23
|
+
# The capability is asked of the adapter, never of its name: a
|
|
24
|
+
# second adapter that grows an era story declares lineage_capable?
|
|
25
|
+
# and era_check! and needs no change here.
|
|
26
|
+
#
|
|
27
|
+
# Detection and identity are separate jobs: this check never hashes
|
|
28
|
+
# anything — era names come from the store, already minted (by the
|
|
29
|
+
# scaffold) or absent. Refusal wordings are contract, pinned by the
|
|
30
|
+
# corpus.
|
|
31
|
+
module EraCheck
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
# EACH BLUEBOOK'S OWN SOURCE, not one file read once and reused
|
|
35
|
+
# for every bluebook in the registry — true as long as a domain
|
|
36
|
+
# directory only ever held exactly one, and silently wrong the
|
|
37
|
+
# moment `uses_framework` made a second, differently-sourced
|
|
38
|
+
# bluebook (Governance, Identity — `lib/hecks/framework/bluebook/`,
|
|
39
|
+
# not the domain's own directory) share a boot with the first.
|
|
40
|
+
# Caught the hard way: three domains booted together, one real
|
|
41
|
+
# source text (the domain's own), and every OTHER domain's era-1
|
|
42
|
+
# held THAT text under its own name — a shadow-parse of it later
|
|
43
|
+
# reconstructs a completely different shape, and every boot after
|
|
44
|
+
# the first refuses toward a scaffold that was never the real
|
|
45
|
+
# drift.
|
|
46
|
+
def check!(registry, directory)
|
|
47
|
+
registry.bluebooks.each_value do |bluebook|
|
|
48
|
+
check_bluebook!(registry, bluebook, source_text_for(bluebook, directory), directory: directory)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The domain's own directory first, matched by name — a real app's
|
|
53
|
+
# directory may hold more than one file once `uses_framework`
|
|
54
|
+
# exists, so ".first" alone can no longer be trusted, the exact
|
|
55
|
+
# way it silently wasn't the day this was found: three domains
|
|
56
|
+
# booted together, one real source text read once (the domain's
|
|
57
|
+
# own, ".first"'d), and every OTHER domain's era-1 held THAT text
|
|
58
|
+
# under its own name — a later shadow-parse of it reconstructs a
|
|
59
|
+
# completely different shape, and every boot after the first
|
|
60
|
+
# refuses toward a scaffold that was never the real drift.
|
|
61
|
+
#
|
|
62
|
+
# A single-file directory whose one file names something ELSE
|
|
63
|
+
# falls back to it anyway (a fixture may legitimately name its
|
|
64
|
+
# file differently from the `Hecks.bluebook` it declares) — UNLESS
|
|
65
|
+
# this bluebook is a known framework member, in which case that
|
|
66
|
+
# one file is certainly some OTHER domain's, not this one's, and
|
|
67
|
+
# the framework registry is asked instead — the only other place a
|
|
68
|
+
# bluebook in this registry could have come from, per
|
|
69
|
+
# `uses_framework`.
|
|
70
|
+
def source_text_for(bluebook, directory)
|
|
71
|
+
domain_files = Dir[File.join(directory, "*.bluebook")]
|
|
72
|
+
own = domain_files.select do |path|
|
|
73
|
+
File.foreach(path, encoding: "UTF-8").any? { |line| line.match?(/\A\s*Hecks\.bluebook\s+#{Regexp.escape(bluebook.name.inspect)}/) }
|
|
74
|
+
end
|
|
75
|
+
own = domain_files if own.empty? && domain_files.size == 1 && !Framework.members.key?(bluebook.name)
|
|
76
|
+
own = [Framework.members[bluebook.name]].compact if own.empty?
|
|
77
|
+
return if own.empty?
|
|
78
|
+
|
|
79
|
+
own.map { |path| File.read(path, encoding: "UTF-8") }.join("\n")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def check_bluebook!(registry, bluebook, current_text, directory: nil)
|
|
83
|
+
check_compute_rules!(registry, bluebook)
|
|
84
|
+
|
|
85
|
+
first = bluebook.aggregates.first
|
|
86
|
+
return unless first
|
|
87
|
+
|
|
88
|
+
adapter_name = adapter_for(registry, bluebook.name, first)
|
|
89
|
+
return unless lineage_capable?(registry, adapter_name)
|
|
90
|
+
|
|
91
|
+
unless current_text
|
|
92
|
+
raise WiringError,
|
|
93
|
+
"cannot boot #{bluebook.name}: bound to a lineage-capable adapter, but no source file for " \
|
|
94
|
+
"it could be found (checked #{directory.inspect} and the framework registry)"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
settings = registry.world(bluebook.name)&.for_binding(Ports::Persistence::VERB, adapter_name) || {}
|
|
98
|
+
registry.adapter_class(adapter_name).era_check!(
|
|
99
|
+
registry: registry, bluebook: bluebook, current_text: current_text, settings: settings,
|
|
100
|
+
directory: directory
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# The per-rule capability gate — NOT an era fact, and so it
|
|
105
|
+
# survives on every adapter: a compute rule's SQL is its only
|
|
106
|
+
# implementation, so an aggregate carrying one cannot boot
|
|
107
|
+
# anywhere but Postgres, whatever any shape comparison would say.
|
|
108
|
+
def check_compute_rules!(registry, bluebook)
|
|
109
|
+
bluebook.aggregates.each do |aggregate|
|
|
110
|
+
lineage = Ports::Persistence::Lineage.for(registry, bluebook.name, aggregate)
|
|
111
|
+
next unless lineage&.computes?
|
|
112
|
+
|
|
113
|
+
adapter = adapter_for(registry, bluebook.name, aggregate)
|
|
114
|
+
next if lineage_capable?(registry, adapter)
|
|
115
|
+
|
|
116
|
+
raise WiringError, "compute rules require the Postgres adapter; #{aggregate.name} is bound to #{adapter}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def adapter_for(registry, domain, aggregate)
|
|
121
|
+
Ports::Persistence::BindingPolicy.resolve(registry, domain, aggregate).adapter
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# The capability idiom: an adapter CLASS that answers
|
|
125
|
+
# lineage_capable? with true carries eras and may act on drift
|
|
126
|
+
# (translate, fork, merge). Postgres alone does today; the seam is
|
|
127
|
+
# what lets a second one arrive without touching this file.
|
|
128
|
+
def lineage_capable?(registry, adapter_name)
|
|
129
|
+
adapter_class = registry.adapters[adapter_name] && registry.adapter_class(adapter_name)
|
|
130
|
+
adapter_class.respond_to?(:lineage_capable?) && adapter_class.lineage_capable?
|
|
131
|
+
rescue StandardError
|
|
132
|
+
false
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Runtime
|
|
3
|
+
module EraGuard
|
|
4
|
+
# The pure half: computing an aggregate's structural signature and
|
|
5
|
+
# diffing two eras' shapes. No file I/O — everything here answers
|
|
6
|
+
# from the IR it is handed, which is why the Postgres mint path can
|
|
7
|
+
# call it too.
|
|
8
|
+
module ShapeDiff
|
|
9
|
+
def shape(aggregate)
|
|
10
|
+
aggregate.attributes.map { |attribute|
|
|
11
|
+
[attribute.name, attribute_signature(aggregate, attribute.type)]
|
|
12
|
+
}.sort_by(&:first)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# A plain type name for a primitive; `[type_name, member_signatures]`
|
|
16
|
+
# for a value object or entity, walked recursively — so two
|
|
17
|
+
# attributes with the same declared type name but a different
|
|
18
|
+
# internal shape are never mistaken for unchanged.
|
|
19
|
+
def attribute_signature(aggregate, type_name, seen = [])
|
|
20
|
+
container = nested_type(aggregate, type_name)
|
|
21
|
+
return type_name if container.nil? || seen.include?(type_name)
|
|
22
|
+
|
|
23
|
+
members = container.attributes.map { |member|
|
|
24
|
+
[member.name, attribute_signature(aggregate, member.type, seen + [type_name])]
|
|
25
|
+
}.sort_by(&:first)
|
|
26
|
+
[type_name, members]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def nested_type(aggregate, type_name)
|
|
30
|
+
aggregate.value_object(type_name) || aggregate.entities.find { |entity| entity.name == type_name }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Attributes present now that the held shape never had — the
|
|
34
|
+
# addition side of drift, which `uncovered_attributes` above never
|
|
35
|
+
# looks at, since it only walks the HELD shape (vanish/retype).
|
|
36
|
+
# Most additions are free (ADR 0025, "Added attributes and
|
|
37
|
+
# absence"): a default:, a list_of (frozen []), or a value object
|
|
38
|
+
# whose fields all default fill an existing record automatically
|
|
39
|
+
# via `Instance.hydrate_with_defaults`. Only the fourth case — a
|
|
40
|
+
# non-optional attribute with no way to fill itself — can leave an
|
|
41
|
+
# existing record with the field genuinely absent, and that is
|
|
42
|
+
# what this reports: unfilled by a declared translation's own
|
|
43
|
+
# `backfill`, OR by a move/convert that lands an OLD field inside
|
|
44
|
+
# this brand-new attribute (`Lineage#fills?` — the destination-
|
|
45
|
+
# side question, never `explains?`'s source-side one, since a
|
|
46
|
+
# rename from Crate to Bin can introduce a top-level attribute
|
|
47
|
+
# name that never existed to have "vanished").
|
|
48
|
+
def unsafe_additions(aggregate, held_aggregate, lineage)
|
|
49
|
+
held_names = held_aggregate.attributes.map(&:name)
|
|
50
|
+
|
|
51
|
+
aggregate.attributes
|
|
52
|
+
.reject { |attribute| held_names.include?(attribute.name) }
|
|
53
|
+
.select { |attribute| possibly_absent?(aggregate, attribute) }
|
|
54
|
+
.reject { |attribute| lineage&.fills?(attribute.name.to_s) }
|
|
55
|
+
.map(&:name)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# THE FOUR-ROW TABLE, as a predicate over the fourth row only — the
|
|
59
|
+
# other three (default:, list_of, a fully-defaulted value object)
|
|
60
|
+
# all fill an existing record for free and never reach here.
|
|
61
|
+
def possibly_absent?(aggregate, attribute)
|
|
62
|
+
return false if attribute.optional?
|
|
63
|
+
return false unless attribute.default.nil?
|
|
64
|
+
return false if attribute.list?
|
|
65
|
+
|
|
66
|
+
value_object = aggregate.value_object(attribute.type)
|
|
67
|
+
return false if value_object && value_object.attributes.all? { |field| !field.default.nil? }
|
|
68
|
+
|
|
69
|
+
true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Paths the translation needs to explain: attributes that vanished
|
|
73
|
+
# by name, attributes that kept their name but changed type (a
|
|
74
|
+
# `convert` is what lets that be declared at all), and — recursing
|
|
75
|
+
# into a same-named, same-typed value object — its OWN members
|
|
76
|
+
# vanishing or changing type one level down, reported as a dotted
|
|
77
|
+
# path ("price.currency"). A pure addition, at any depth, never
|
|
78
|
+
# needs covering; only vanish-or-retype does, matching the
|
|
79
|
+
# top-level rule at every depth.
|
|
80
|
+
def uncovered_attributes(aggregate, held_aggregate, lineage)
|
|
81
|
+
paths = held_aggregate.attributes.flat_map do |held_attribute|
|
|
82
|
+
current_attribute = aggregate.attribute(held_attribute.name)
|
|
83
|
+
next [held_attribute.name.to_s] unless current_attribute
|
|
84
|
+
|
|
85
|
+
diff_type(held_attribute.name.to_s, held_attribute.type, current_attribute.type, held_aggregate, aggregate, lineage)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
return paths unless lineage
|
|
89
|
+
|
|
90
|
+
paths.reject { |path| lineage.explains?(path) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# `held_type`/`current_type` are type NAMES, resolved against each
|
|
94
|
+
# side's OWN value_object AND entity declarations — neither is ever
|
|
95
|
+
# nested in the DSL, only in the type graph, so both are always
|
|
96
|
+
# looked up flat off their respective aggregate. A `list_of` entity
|
|
97
|
+
# is only reached here to DETECT a member vanish-or-retype; there is
|
|
98
|
+
# no per-element translation machinery yet (`move`/`convert`/`drop`
|
|
99
|
+
# only reach into a single nested hash, not each element of an
|
|
100
|
+
# array) — the only way to satisfy a refusal on an entity path
|
|
101
|
+
# today is a top-level `drop` of the whole list attribute, which
|
|
102
|
+
# `explains?` already recognizes as covering everything nested
|
|
103
|
+
# under it. Blunt, but loud beats silent.
|
|
104
|
+
def diff_type(path, held_type, current_type, held_aggregate, aggregate, lineage, seen = [])
|
|
105
|
+
if held_type != current_type
|
|
106
|
+
# A declared retype says the two TYPE names mean the same shape
|
|
107
|
+
# — accept the pair, but still recurse into the members so a
|
|
108
|
+
# member drift hiding beneath the rename is caught by name.
|
|
109
|
+
return [path] unless lineage&.retype?(held_type, current_type)
|
|
110
|
+
end
|
|
111
|
+
return [] if seen.include?(current_type)
|
|
112
|
+
|
|
113
|
+
held_container = nested_type(held_aggregate, held_type)
|
|
114
|
+
current_container = nested_type(aggregate, current_type)
|
|
115
|
+
return [] unless held_container && current_container
|
|
116
|
+
|
|
117
|
+
held_container.attributes.flat_map do |held_member|
|
|
118
|
+
current_member = current_container.attribute(held_member.name)
|
|
119
|
+
next ["#{path}.#{held_member.name}"] unless current_member
|
|
120
|
+
|
|
121
|
+
diff_type("#{path}.#{held_member.name}", held_member.type, current_member.type, held_aggregate, aggregate, lineage,
|
|
122
|
+
seen + [current_type])
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|