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,49 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
# A creating command with no natural key needs a value from
|
|
6
|
+
# SOMEWHERE to be its identity — this is that somewhere. Resolved
|
|
7
|
+
# the same way `Ports::Extraction` resolves its own adapter (one
|
|
8
|
+
# adapter registry-wide implements this port, not a per-aggregate
|
|
9
|
+
# binding the way persistence needs — different aggregates have no
|
|
10
|
+
# real reason to want different id-generation strategies within
|
|
11
|
+
# one running app).
|
|
12
|
+
#
|
|
13
|
+
# REPLAY NEEDS NO SUPPRESSION HERE, ON PURPOSE. A UUID minted for a
|
|
14
|
+
# creating command's identity gets baked into that step's own args
|
|
15
|
+
# — an ordinary string value — the moment it's generated, by
|
|
16
|
+
# whoever issues the first, live dispatch. A recorded corpus
|
|
17
|
+
# script or a captured fuzz-replay step already holds that
|
|
18
|
+
# concrete value; replaying it calls `dispatch` with the SAME
|
|
19
|
+
# args, and this module is never consulted again, for the same
|
|
20
|
+
# reason `SecureRandom.uuid` never runs twice for one
|
|
21
|
+
# already-recorded step today. `Event#occurred_at`
|
|
22
|
+
# (`command_rules/emission.rb`) is the one other "environmental
|
|
23
|
+
# fact, not caller-supplied" this codebase has, and it's handled
|
|
24
|
+
# the identical way: it fires on every dispatch, replay included,
|
|
25
|
+
# and comparison simply excludes it.
|
|
26
|
+
module IdentityGeneration
|
|
27
|
+
NAME = "identity_generation"
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
def uuid(registry) = adapter(registry).uuid
|
|
32
|
+
|
|
33
|
+
def adapter(registry)
|
|
34
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
35
|
+
|
|
36
|
+
case implementations.size
|
|
37
|
+
when 1 then Adapters.const_get(implementations.first.name)
|
|
38
|
+
when 0
|
|
39
|
+
raise Runtime::WiringError,
|
|
40
|
+
"no adapter implements the #{NAME} port — nothing can mint an identity"
|
|
41
|
+
else
|
|
42
|
+
raise Runtime::WiringError,
|
|
43
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
44
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
# AN AUTHENTICATED (issuer, subject) PAIR, RESOLVED TO A STABLE
|
|
6
|
+
# `Identity` — the other half of the same symmetry `Authorization`
|
|
7
|
+
# already has for Governance: one adapter registry-wide answers this
|
|
8
|
+
# port, resolved the same zero/one/many way, so an application never
|
|
9
|
+
# has to name `Identity` directly to ask "who is this."
|
|
10
|
+
#
|
|
11
|
+
# Answers `nil` for a pair nothing has linked — the caller decides
|
|
12
|
+
# what that means (refuse, prompt to register, whatever), the same
|
|
13
|
+
# way `Authorization#holds_role?` answering `false` decides nothing
|
|
14
|
+
# on its own.
|
|
15
|
+
module IdentityResolution
|
|
16
|
+
NAME = "identity_resolution"
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def resolve(registry, issuer:, subject:)
|
|
21
|
+
adapter(registry).resolve(registry, issuer: issuer, subject: subject)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def adapter(registry)
|
|
25
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
26
|
+
|
|
27
|
+
case implementations.size
|
|
28
|
+
when 1 then Adapters.const_get(implementations.first.name)
|
|
29
|
+
when 0
|
|
30
|
+
raise Runtime::WiringError,
|
|
31
|
+
"no adapter implements the #{NAME} port — nothing can resolve an identity"
|
|
32
|
+
else
|
|
33
|
+
raise Runtime::WiringError,
|
|
34
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
35
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require_relative "../../runtime/registry"
|
|
2
|
+
require_relative "execution"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Ports
|
|
6
|
+
module Persistence
|
|
7
|
+
# `mirrors` is durable replication intent. It is part of the same
|
|
8
|
+
# append as the authoritative state, never a second outbox store.
|
|
9
|
+
Entry = Struct.new(:operation, :id, :state, :mirrors, keyword_init: true) do
|
|
10
|
+
def save? = operation == "save"
|
|
11
|
+
def delete? = operation == "delete"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Makes append-before-projection a port invariant. Adapters retain
|
|
15
|
+
# control of their durable format, but every one must accept the same
|
|
16
|
+
# entry stream and materialize current state from it.
|
|
17
|
+
class AppendOnly
|
|
18
|
+
attr_reader :adapter
|
|
19
|
+
|
|
20
|
+
def aggregate = @adapter.aggregate
|
|
21
|
+
|
|
22
|
+
def initialize(adapter)
|
|
23
|
+
@adapter = adapter
|
|
24
|
+
required = %i[append project entries]
|
|
25
|
+
missing = required.reject { |method| adapter.respond_to?(method) }
|
|
26
|
+
raise Runtime::WiringError,
|
|
27
|
+
"#{adapter.class} does not implement append-only persistence: #{missing.join(', ')}" unless missing.empty?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def find(id) = @adapter.find(id)
|
|
31
|
+
def all(**opts) = @adapter.all(**opts)
|
|
32
|
+
def count = @adapter.count
|
|
33
|
+
def entries = @adapter.entries
|
|
34
|
+
|
|
35
|
+
def capabilities
|
|
36
|
+
return [] unless @adapter.respond_to?(:persistence_capabilities)
|
|
37
|
+
|
|
38
|
+
Array(@adapter.persistence_capabilities).map(&:to_sym).freeze
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def reset!
|
|
42
|
+
raise Runtime::WiringError, "append-only adapter cannot reset" unless @adapter.respond_to?(:reset!)
|
|
43
|
+
|
|
44
|
+
@adapter.reset!
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# NOT an endless `def events = ... if ...` — that modifier binds to
|
|
48
|
+
# the WHOLE `def`, not just its body, so it evaluates against
|
|
49
|
+
# `@adapter` while `@adapter` is still nil (class-body time,
|
|
50
|
+
# before `initialize` ever runs) and silently skips defining the
|
|
51
|
+
# method at all. Found live: nothing in this codebase called
|
|
52
|
+
# `AppendOnly#events` before Memory got a `reset!` test that did.
|
|
53
|
+
def events
|
|
54
|
+
@adapter.events if @adapter.respond_to?(:events)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# An append is durable before a projection is attempted. Replaying the
|
|
58
|
+
# log restores a snapshot/table after a crash in that small window.
|
|
59
|
+
def recover!
|
|
60
|
+
entries.each { |entry| project(entry) }
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def append(entry) = @adapter.append(entry)
|
|
65
|
+
def project(entry) = @adapter.project(entry)
|
|
66
|
+
|
|
67
|
+
def save(instance)
|
|
68
|
+
entry = Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
|
|
69
|
+
append(entry)
|
|
70
|
+
project(entry)
|
|
71
|
+
instance
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def atomic_put(instance, insert_only: false)
|
|
75
|
+
unless capabilities.include?(:atomic_put) && @adapter.respond_to?(:atomic_put)
|
|
76
|
+
raise Runtime::WiringError, "#{@adapter.class} advertises no atomic_put persistence capability"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
entry = Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
|
|
80
|
+
status = @adapter.atomic_put(entry, insert_only: insert_only)
|
|
81
|
+
Outcome.new(status: status, instance: instance)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def delete(id)
|
|
85
|
+
return false unless find(id)
|
|
86
|
+
|
|
87
|
+
entry = Entry.new(operation: "delete", id: id.to_s, state: nil)
|
|
88
|
+
append(entry)
|
|
89
|
+
project(entry)
|
|
90
|
+
true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def record_event(event) = @adapter.record_event(event) if @adapter.respond_to?(:record_event)
|
|
94
|
+
def query_read_model(domain, model, args, bluebook = nil)
|
|
95
|
+
return unless @adapter.respond_to?(:query_read_model)
|
|
96
|
+
|
|
97
|
+
@adapter.query_read_model(domain, model, args, bluebook)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require_relative "../../bluebook/hexagon"
|
|
2
|
+
require_relative "../../runtime/registry"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Ports
|
|
6
|
+
module Persistence
|
|
7
|
+
# Resolves one aggregate's persistence topology. An unmarked binding is
|
|
8
|
+
# authoritative; roles are deliberately opt-in so a second adapter can
|
|
9
|
+
# never silently become a read source.
|
|
10
|
+
module BindingPolicy
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def resolve(registry, domain, aggregate)
|
|
14
|
+
hexagon = registry.hecksagon(domain)
|
|
15
|
+
return default_binding(aggregate) unless hexagon
|
|
16
|
+
|
|
17
|
+
bindings = hexagon.binds_for(aggregate.hecks_name, VERB)
|
|
18
|
+
raise missing_binding(domain, aggregate) if bindings.empty?
|
|
19
|
+
|
|
20
|
+
authoritative = bindings.select { |bind| bind.role.nil? || bind.role.empty? }
|
|
21
|
+
raise ambiguous_binding(domain, aggregate, authoritative) unless authoritative.size == 1
|
|
22
|
+
raise unsupported_roles(domain, aggregate, bindings - authoritative) unless (bindings - authoritative).empty?
|
|
23
|
+
|
|
24
|
+
authoritative.first
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def default_binding(aggregate)
|
|
28
|
+
Bluebook::Bind.new(aggregate: aggregate.hecks_name, verb: VERB, adapter: DEFAULT_ADAPTER)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def missing_binding(domain, aggregate)
|
|
32
|
+
Runtime::WiringError.new(
|
|
33
|
+
"#{domain}::#{aggregate.hecks_name} has no #{VERB} bind. #{domain} declares a " \
|
|
34
|
+
"hecksagon, so its wiring is being decided explicitly and an aggregate " \
|
|
35
|
+
"left out is a forgotten decision. Bind it, or say " \
|
|
36
|
+
"#{aggregate.hecks_name}.#{VERB}(#{DEFAULT_ADAPTER.inspect}) to keep it in memory on purpose."
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ambiguous_binding(domain, aggregate, authoritative)
|
|
41
|
+
Runtime::WiringError.new(
|
|
42
|
+
"#{domain}::#{aggregate.hecks_name} has #{authoritative.size} authoritative #{VERB} bindings. " \
|
|
43
|
+
"Declare exactly one adapter without a role."
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def unsupported_roles(domain, aggregate, bindings)
|
|
48
|
+
Runtime::WiringError.new(
|
|
49
|
+
"#{domain}::#{aggregate.hecks_name} uses persistence role#{bindings.size == 1 ? '' : 's'} " \
|
|
50
|
+
"#{bindings.map(&:role).map(&:inspect).join(', ')}. Only persisted_by is supported."
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Ports
|
|
3
|
+
module Persistence
|
|
4
|
+
# Adapter outcomes are runtime facts, never command lifecycle.
|
|
5
|
+
Outcome = Struct.new(:status, :instance, keyword_init: true) do
|
|
6
|
+
STATUSES = %i[inserted replaced updated conflicted missing saved].freeze
|
|
7
|
+
|
|
8
|
+
def initialize(status:, instance: nil)
|
|
9
|
+
normalized = status.to_sym
|
|
10
|
+
raise ArgumentError, "unknown persistence outcome #{status.inspect}" unless STATUSES.include?(normalized)
|
|
11
|
+
|
|
12
|
+
super(status: normalized, instance: instance)
|
|
13
|
+
freeze
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
require_relative "../../naming"
|
|
2
|
+
require_relative "append_only"
|
|
3
|
+
require_relative "../../runtime/registry"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Ports
|
|
7
|
+
module Persistence
|
|
8
|
+
# Translates a journal entry written under an old shape into the one
|
|
9
|
+
# the current bluebook declares — a renamed attribute, a renamed
|
|
10
|
+
# aggregate, or a field crossing a value-object boundary. Applied
|
|
11
|
+
# wherever entries are read, so replay derives the current head from
|
|
12
|
+
# history that was never rewritten — the port owns the meaning,
|
|
13
|
+
# adapters stay oblivious.
|
|
14
|
+
class Lineage
|
|
15
|
+
# `ancestor_name` is the declared name a rename came from (matches
|
|
16
|
+
# the held bluebook's own aggregate names); `ancestor_storage_name`
|
|
17
|
+
# is its derived, snake_case file/table name. Both are nil unless
|
|
18
|
+
# the aggregate itself was renamed.
|
|
19
|
+
attr_reader :ancestor_name, :ancestor_storage_name
|
|
20
|
+
|
|
21
|
+
def self.for(registry, domain, aggregate)
|
|
22
|
+
translation = registry.translations.find do |candidate|
|
|
23
|
+
candidate.domain == domain.to_s && candidate.for_aggregate(aggregate.name)
|
|
24
|
+
end
|
|
25
|
+
return nil unless translation
|
|
26
|
+
|
|
27
|
+
from_declared(translation.for_aggregate(aggregate.name), aggregate.name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# One SPECIFIC edge's rules for one aggregate — what the mint
|
|
31
|
+
# path uses, where `for` would happily answer with whichever
|
|
32
|
+
# edge in the registry mentioned the aggregate first.
|
|
33
|
+
def self.from_declared(declared, aggregate_name)
|
|
34
|
+
return nil unless declared
|
|
35
|
+
|
|
36
|
+
renamed_aggregate = declared.was && declared.was != aggregate_name.to_s
|
|
37
|
+
ancestor_name = renamed_aggregate ? declared.was : nil
|
|
38
|
+
ancestor_storage_name = renamed_aggregate ? Naming.snake(declared.was) : nil
|
|
39
|
+
|
|
40
|
+
new(declared.renames, declared.moves, declared.converts, declared.drops,
|
|
41
|
+
retypes: declared.retypes, computes: declared.computes, rekeys: declared.rekeys,
|
|
42
|
+
backfills: declared.backfills,
|
|
43
|
+
ancestor_name: ancestor_name, ancestor_storage_name: ancestor_storage_name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def initialize(renames, moves = [], converts = [], drops = [], retypes: [], computes: [], rekeys: [],
|
|
47
|
+
backfills: [], ancestor_name: nil, ancestor_storage_name: nil)
|
|
48
|
+
@renames = renames
|
|
49
|
+
@moves = moves
|
|
50
|
+
@converts = converts
|
|
51
|
+
@drops = drops
|
|
52
|
+
@retypes = retypes
|
|
53
|
+
@computes = computes
|
|
54
|
+
@rekeys = rekeys
|
|
55
|
+
@backfills = backfills
|
|
56
|
+
@ancestor_name = ancestor_name
|
|
57
|
+
@ancestor_storage_name = ancestor_storage_name
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Whether any rule in this edge is a compute — the one rule kind
|
|
61
|
+
# with no in-process implementation at all. An aggregate carrying
|
|
62
|
+
# one refuses to boot anywhere but Postgres, per-rule and by name,
|
|
63
|
+
# before the general drift machinery says anything vaguer.
|
|
64
|
+
def computes? = !@computes.empty?
|
|
65
|
+
|
|
66
|
+
# THE SINGLE SOURCE OF TRUTH for "does this edge rekey this
|
|
67
|
+
# aggregate" — every consumer (coverage_check.rb's identity gate,
|
|
68
|
+
# minter.rb's approval gate, layer_two.rb's audit, head_compiler.rb's
|
|
69
|
+
# SQL compilation) asks THIS, never re-derives it from `declared`
|
|
70
|
+
# independently. One accessor to change if what a rekey rule means
|
|
71
|
+
# ever needs to change, not four call sites in four files.
|
|
72
|
+
def rekey? = !@rekeys.empty?
|
|
73
|
+
|
|
74
|
+
# The rekey's own SQL — first-and-only rule, same one-per-aggregate
|
|
75
|
+
# assumption `compute` makes about its own list where it matters
|
|
76
|
+
# (an edge with more than one is a DSL-level decision, not
|
|
77
|
+
# something this reader arbitrates).
|
|
78
|
+
def rekey_sql = @rekeys.first&.sql
|
|
79
|
+
|
|
80
|
+
# The reference semantics for the five PORTABLE rule kinds —
|
|
81
|
+
# rename, move, convert, drop, and the aggregate-level `was:`.
|
|
82
|
+
# `retype` moves nothing (stored state never carries a type name)
|
|
83
|
+
# and `compute` is deliberately not applied here: its SQL is its
|
|
84
|
+
# only implementation, so this transform neither imitates nor
|
|
85
|
+
# checks it — the source field passes through untouched, and the
|
|
86
|
+
# audit verifies compute output against the matview alone.
|
|
87
|
+
def translate(entry)
|
|
88
|
+
return entry unless entry.save? && entry.state
|
|
89
|
+
|
|
90
|
+
# Deep, not shallow: a move or convert reaches INTO a nested
|
|
91
|
+
# value-object hash, and a shallow dup would quietly mutate
|
|
92
|
+
# the caller's copy of the original entry.
|
|
93
|
+
state = deep_dup(entry.state)
|
|
94
|
+
@renames.each { |old_name, new_name| state[new_name] = state.delete(old_name) if state.key?(old_name) }
|
|
95
|
+
@moves.each { |move| apply_move(state, move) }
|
|
96
|
+
@converts.each { |convert| apply_convert(state, convert) }
|
|
97
|
+
@drops.each { |name| apply_drop(state, name) }
|
|
98
|
+
# LAST, and only where nothing already answered — a backfill
|
|
99
|
+
# fills the gap a rename/move/convert left untouched, never
|
|
100
|
+
# overwrites a value that already made it across.
|
|
101
|
+
@backfills.each { |backfill| state[backfill.name] = backfill.default unless state.key?(backfill.name) }
|
|
102
|
+
Entry.new(operation: entry.operation, id: entry.id, state: state, mirrors: entry.mirrors)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Whether this translation names `path` as an old key it accounts
|
|
106
|
+
# for — the rename, move, or convert it came from, or an explicit
|
|
107
|
+
# drop. `path` is a bare name ("cost") or a dotted value-object
|
|
108
|
+
# member ("price.currency"); a rule covering the WHOLE top-level
|
|
109
|
+
# attribute (a rename, a top-level move/convert/drop) also covers
|
|
110
|
+
# anything nested under it, since the whole value travels or goes
|
|
111
|
+
# away together. Used to catch a field — or a value object's own
|
|
112
|
+
# member — that vanished (or silently changed type) without
|
|
113
|
+
# anything explaining it, even when some OTHER field is covered.
|
|
114
|
+
#
|
|
115
|
+
# `backfills` matches on the WHOLE name only, never a dotted
|
|
116
|
+
# prefix — a backfill names a top-level attribute that is new
|
|
117
|
+
# outright (nothing to be a prefix of on the held side), unlike
|
|
118
|
+
# every rule above it, which explains a path that existed and
|
|
119
|
+
# moved, converted, or vanished.
|
|
120
|
+
def explains?(path)
|
|
121
|
+
path = path.to_s
|
|
122
|
+
top = path.split(".").first
|
|
123
|
+
|
|
124
|
+
@renames.key?(top.to_sym) ||
|
|
125
|
+
@moves.any? { |move| move.from == path || move.from.split(".").first == top } ||
|
|
126
|
+
@converts.any? { |convert| convert.from == path || convert.from.split(".").first == top } ||
|
|
127
|
+
@drops.any? { |drop| drop.to_s == path || drop.to_s.split(".").first == top } ||
|
|
128
|
+
@computes.any? { |compute| compute.from == path || compute.from.split(".").first == top } ||
|
|
129
|
+
@backfills.any? { |backfill| backfill.name.to_s == top }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# THE DESTINATION-SIDE TWIN of `explains?` above, which only ever
|
|
133
|
+
# asks about a rule's SOURCE. `unsafe_additions` asks a different
|
|
134
|
+
# question — not "was this vanished path accounted for" but "does
|
|
135
|
+
# an existing record end up with a value here" — and a move or
|
|
136
|
+
# convert whose `to:` lands a old field inside a BRAND-NEW
|
|
137
|
+
# top-level attribute (`weight` becoming `contents.weight` when
|
|
138
|
+
# `Contents` did not exist before) fills that attribute for an
|
|
139
|
+
# existing record exactly as a `backfill` would, even though
|
|
140
|
+
# nothing named `contents` explains any vanished path. `compute`
|
|
141
|
+
# counts on the same terms `explains?` already grants it
|
|
142
|
+
# elsewhere in this file — Postgres-only and audited, not
|
|
143
|
+
# actually applied by THIS method, the same gap the vanish side
|
|
144
|
+
# already lives with.
|
|
145
|
+
#
|
|
146
|
+
# `@renames.value?` belongs here too, and used to be missing: a
|
|
147
|
+
# bare `rename :cost, to: :amount` is the plainest possible
|
|
148
|
+
# covering rule there is (`translate` above applies it
|
|
149
|
+
# unconditionally, no lookup table, no per-record ambiguity —
|
|
150
|
+
# simpler than a move or convert, which both got their `fills?`
|
|
151
|
+
# entry from the start), and its absence meant `unsafe_additions`
|
|
152
|
+
# reported the new name as an unexplained required addition on
|
|
153
|
+
# EVERY rename-only edge, the single most common translation
|
|
154
|
+
# shape there is. `explains?` already checked the source side
|
|
155
|
+
# (`@renames.key?`); `fills?` is the symmetric destination-side
|
|
156
|
+
# check that was never added alongside it.
|
|
157
|
+
def fills?(path)
|
|
158
|
+
path = path.to_s
|
|
159
|
+
|
|
160
|
+
@renames.value?(path.to_sym) ||
|
|
161
|
+
@moves.any? { |move| move.to.split(".").first == path } ||
|
|
162
|
+
@converts.any? { |convert| convert.to.split(".").first == path } ||
|
|
163
|
+
@computes.any? { |compute| compute.to.split(".").first == path } ||
|
|
164
|
+
@backfills.any? { |backfill| backfill.name.to_s == path }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Whether a declared retype says the pair of TYPE names means the
|
|
168
|
+
# same shape — a value object or entity whose own name changed
|
|
169
|
+
# with its members intact. Nothing in the stored data carries the
|
|
170
|
+
# type name, so this never moves a value; it only satisfies the
|
|
171
|
+
# era diff's literal type-name comparison.
|
|
172
|
+
def retype?(held_type, current_type)
|
|
173
|
+
@retypes.any? { |retype| retype.from == held_type.to_s && retype.to == current_type.to_s }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
private
|
|
177
|
+
|
|
178
|
+
def deep_dup(node)
|
|
179
|
+
case node
|
|
180
|
+
when Hash then node.transform_values { |value| deep_dup(value) }
|
|
181
|
+
when Array then node.map { |item| deep_dup(item) }
|
|
182
|
+
else node
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def apply_drop(state, name)
|
|
187
|
+
name = name.to_s
|
|
188
|
+
top, member = name.split(".", 2)
|
|
189
|
+
top = top.to_sym
|
|
190
|
+
|
|
191
|
+
if member
|
|
192
|
+
nested = state[top]
|
|
193
|
+
if nested.is_a?(Hash)
|
|
194
|
+
nested.delete(member)
|
|
195
|
+
state.delete(top) if nested.empty?
|
|
196
|
+
end
|
|
197
|
+
else
|
|
198
|
+
state.delete(top)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# A dotted path's first segment is a top-level (symbol) key; a
|
|
203
|
+
# second segment reaches into a value-object member, stored with a
|
|
204
|
+
# string key exactly as the adapter's own journal reader loaded it.
|
|
205
|
+
def apply_move(state, move)
|
|
206
|
+
old_top, old_member = move.from.split(".", 2)
|
|
207
|
+
new_top, new_member = move.to.split(".", 2)
|
|
208
|
+
old_top = old_top.to_sym
|
|
209
|
+
|
|
210
|
+
value, present = extract(state, old_top, old_member)
|
|
211
|
+
return unless present
|
|
212
|
+
|
|
213
|
+
insert(state, new_top.to_sym, new_member, value, rule: "move #{move.from} to: #{move.to}")
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# A convert is a move whose value has nothing in common with its
|
|
217
|
+
# replacement — the same path machinery, plus a lookup. A value
|
|
218
|
+
# with no entry in the table refuses loudly rather than carry an
|
|
219
|
+
# unrecognized value silently into the new era.
|
|
220
|
+
def apply_convert(state, convert)
|
|
221
|
+
old_top, old_member = convert.from.split(".", 2)
|
|
222
|
+
new_top, new_member = convert.to.split(".", 2)
|
|
223
|
+
old_top = old_top.to_sym
|
|
224
|
+
|
|
225
|
+
raw, present = extract(state, old_top, old_member)
|
|
226
|
+
return unless present
|
|
227
|
+
|
|
228
|
+
unless convert.values.key?(raw)
|
|
229
|
+
raise Runtime::WiringError,
|
|
230
|
+
"cannot translate #{convert.from}: #{raw.inspect} has no mapping in its " \
|
|
231
|
+
"convert's values: table. Add #{raw.inspect} => ... to cover it."
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
insert(state, new_top.to_sym, new_member, convert.values[raw], rule: "convert #{convert.from} to: #{convert.to}")
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def extract(state, top, member)
|
|
238
|
+
return [nil, false] unless state.key?(top)
|
|
239
|
+
return [state.delete(top), true] unless member
|
|
240
|
+
|
|
241
|
+
nested = state[top]
|
|
242
|
+
return [nil, false] unless nested.is_a?(Hash) && nested.key?(member)
|
|
243
|
+
|
|
244
|
+
value = nested.delete(member)
|
|
245
|
+
state.delete(top) if nested.empty?
|
|
246
|
+
[value, true]
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# ADVERSARIAL FINDING, not a hypothetical: a destination whose
|
|
250
|
+
# top segment ALREADY holds a value — most commonly a reference,
|
|
251
|
+
# stored as a bare scalar id — used to be silently clobbered with
|
|
252
|
+
# an empty hash the moment a dotted destination needed to nest
|
|
253
|
+
# under it (`state[top] ||= {}` only guards nil/false, so a
|
|
254
|
+
# truthy non-Hash sailed straight through to `state[top][member] =`,
|
|
255
|
+
# i.e. `"team-1"["detail"] =`, which is String#[]= and raised an
|
|
256
|
+
# unrelated-looking IndexError). Whether it crashed or silently
|
|
257
|
+
# replaced the value, this is a `drop` that never declared
|
|
258
|
+
# itself — the one thing this language exists to make explicit
|
|
259
|
+
# (see apply_convert's own refusal above, the same shape). Refuse
|
|
260
|
+
# by name instead, on both sides: the SQL half (hecks_tr_insert)
|
|
261
|
+
# raises the identical wording.
|
|
262
|
+
def insert(state, top, member, value, rule:)
|
|
263
|
+
return state[top] = value unless member
|
|
264
|
+
|
|
265
|
+
if state.key?(top) && !state[top].is_a?(Hash)
|
|
266
|
+
raise Runtime::WiringError,
|
|
267
|
+
"cannot #{rule}: #{top} already holds #{state[top].inspect}, not a value this can nest " \
|
|
268
|
+
"under — moving into it would discard that value silently. Rename or drop #{top} first."
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
state[top] ||= {}
|
|
272
|
+
state[top][member] = value
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Ports
|
|
3
|
+
module Persistence
|
|
4
|
+
# THE NO-OP SAGA STORE — what `Registry#saga_persistence` hands
|
|
5
|
+
# back for a domain whose resolved adapter doesn't implement the
|
|
6
|
+
# (optional) saga-persistence capability: Memory, deliberately
|
|
7
|
+
# (sagas stay in-memory-only, exactly as they always have), and
|
|
8
|
+
# any `RemoteRuntime`-shaped adapter (Lambda; the real durability
|
|
9
|
+
# for those lives on the other side of the call — Phase 1, not
|
|
10
|
+
# here). `SagaInterpreter`'s hook points call through
|
|
11
|
+
# `saga_persistence(domain)` unconditionally; this is what makes
|
|
12
|
+
# that safe without a `respond_to?` check at every call site,
|
|
13
|
+
# mirroring the same optional-capability shape
|
|
14
|
+
# `Ports::Persistence::AppendOnly` already gives adapters that
|
|
15
|
+
# don't implement `reset!`/`events`/`record_event`.
|
|
16
|
+
class NullSagaStore
|
|
17
|
+
def save_saga(**) = nil
|
|
18
|
+
def delete_saga(**) = nil
|
|
19
|
+
def each_saga(*) = nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
NULL_SAGA_STORE = NullSagaStore.new
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require_relative "../../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
module Persistence
|
|
6
|
+
# THE OTHER SHAPE AN ADAPTER CAN BE. `AppendOnly` names one shape
|
|
7
|
+
# already — something that stores bytes, locally (Postgres, SQLite,
|
|
8
|
+
# Heki) or over the network (D1), doesn't matter, the point is it
|
|
9
|
+
# does real local interpretation and has real entries to replay.
|
|
10
|
+
# This names the second, different shape: not "storage reached
|
|
11
|
+
# remotely," but the real INTERPRETER itself living behind a call
|
|
12
|
+
# boundary — `append`/`project` don't do partial local work and
|
|
13
|
+
# then fail, they raise unconditionally, because there is no local
|
|
14
|
+
# write-ahead log to have; `entries` is always `[]` for the same
|
|
15
|
+
# reason (`AppendOnly#recover!` replaying zero entries here is a
|
|
16
|
+
# correct no-op, not a lie).
|
|
17
|
+
#
|
|
18
|
+
# `Adapters::Lambda` is the first adapter to `include` this, not
|
|
19
|
+
# the only one meant to — any future adapter for some other
|
|
20
|
+
# remote-runtime target (a different compute platform, a gRPC
|
|
21
|
+
# stub, whatever) gets this shape for free by including one module
|
|
22
|
+
# instead of re-deriving the same raise-on-write/always-empty-
|
|
23
|
+
# entries boilerplate from scratch. And because it's a real,
|
|
24
|
+
# checkable module rather than an adapter's own ad hoc
|
|
25
|
+
# implementation, a caller can ask "is this adapter a remote-
|
|
26
|
+
# runtime delegate?" as a genuine capability check
|
|
27
|
+
# (`registry.adapter_class(name) <= RemoteRuntime`,
|
|
28
|
+
# `Runtime::RemoteDispatcher`'s own use) instead of comparing
|
|
29
|
+
# adapter names by string.
|
|
30
|
+
module RemoteRuntime
|
|
31
|
+
def append(*)
|
|
32
|
+
raise Runtime::WiringError,
|
|
33
|
+
"#{self.class.name} is a remote-runtime delegate — dispatch through " \
|
|
34
|
+
"Runtime::RemoteDispatcher instead of writing through the repository directly"
|
|
35
|
+
end
|
|
36
|
+
alias_method :project, :append
|
|
37
|
+
|
|
38
|
+
def entries = []
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require_relative "append_only"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
module Persistence
|
|
6
|
+
# Turns a declared adapter binding plus its world configuration into a
|
|
7
|
+
# concrete repository. Selection policy stays out of adapter creation.
|
|
8
|
+
module RepositoryFactory
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def build(registry, domain, aggregate, bind, recover: true, settings_verb: VERB)
|
|
12
|
+
registry.check_verb(bind)
|
|
13
|
+
settings = (registry.world(domain)&.for_binding(settings_verb, bind.adapter) || {})
|
|
14
|
+
.reject { |key, _| key.to_sym == :role }
|
|
15
|
+
registry.check_settings(bind, settings)
|
|
16
|
+
# The domain and the resolved era ride along after the
|
|
17
|
+
# declared-settings check: a lineage adapter journals per
|
|
18
|
+
# DOMAIN and writes into its own ERA's partition — neither of
|
|
19
|
+
# which a world's settings carry.
|
|
20
|
+
adapter = registry.adapter_class(bind.adapter)
|
|
21
|
+
.new(aggregate: aggregate,
|
|
22
|
+
settings: settings.merge(domain: domain.to_s, era: registry.resolved_eras[domain.to_s]),
|
|
23
|
+
root: registry.root)
|
|
24
|
+
repository = AppendOnly.new(adapter)
|
|
25
|
+
recover ? repository.recover! : repository
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|