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,181 @@
|
|
|
1
|
+
require_relative "../errors"
|
|
2
|
+
require_relative "../refusal_wording"
|
|
3
|
+
require_relative "../value"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Runtime
|
|
7
|
+
class CommandRules
|
|
8
|
+
# A reference must point at something that EXISTS.
|
|
9
|
+
#
|
|
10
|
+
# `reference_to Customer` is the one guarantee an aggregate reference is
|
|
11
|
+
# for, and it was declared 14 times across banking and enforced nowhere :
|
|
12
|
+
# an Account could belong to a customer who was never registered, and
|
|
13
|
+
# every gate stayed green because
|
|
14
|
+
# no corpus step ever passed a dangling reference.
|
|
15
|
+
module References
|
|
16
|
+
# Resolved here rather than in coercion because coercion is pure — it
|
|
17
|
+
# holds no repository. A reference INTO ANOTHER DOMAIN is left alone : a
|
|
18
|
+
# cross-domain target may legitimately not be loaded, which is the same
|
|
19
|
+
# reading `across` policies already get.
|
|
20
|
+
#
|
|
21
|
+
# Shared by CommandInterpreter and EntityInterpreter — an entity command
|
|
22
|
+
# can declare a reference-typed attribute the same way an aggregate
|
|
23
|
+
# command can, even though nothing in the real corpus does yet.
|
|
24
|
+
def resolve_references(domain, command, args)
|
|
25
|
+
command.attributes.each do |attribute|
|
|
26
|
+
next unless attribute.reference?
|
|
27
|
+
next unless args.key?(attribute.name)
|
|
28
|
+
|
|
29
|
+
held = args[attribute.name]
|
|
30
|
+
next if held.nil?
|
|
31
|
+
|
|
32
|
+
target = referenced_aggregate(attribute)
|
|
33
|
+
next unless target
|
|
34
|
+
|
|
35
|
+
validate_reference_values(domain, target, held, list: attribute.list?)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Structural references are checked again against the settled state.
|
|
40
|
+
# This is what makes a `has_many` declared on an aggregate honest even
|
|
41
|
+
# when a command supplies its list through an ordinary typed argument.
|
|
42
|
+
def resolve_state_references(domain, construct, state)
|
|
43
|
+
construct.attributes.each do |attribute|
|
|
44
|
+
next unless attribute.reference?
|
|
45
|
+
|
|
46
|
+
held = state[attribute.name]
|
|
47
|
+
validate_relationship_cardinality(construct, attribute, held)
|
|
48
|
+
next if held.nil?
|
|
49
|
+
|
|
50
|
+
target = referenced_aggregate(attribute)
|
|
51
|
+
next unless target
|
|
52
|
+
|
|
53
|
+
validate_reference_values(domain, target, held, list: attribute.list?)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
Array(construct.entities).each do |entity|
|
|
57
|
+
field = construct.attribute(Naming.snake(entity.hecks_name).to_sym) ||
|
|
58
|
+
construct.attributes.find { |attribute| attribute.type.to_s == entity.hecks_name.to_s }
|
|
59
|
+
next unless field
|
|
60
|
+
|
|
61
|
+
Array(state[field.name]).each { |row| resolve_state_references(domain, entity, row) }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# `has_one` and `belongs_to` mean exactly one target unless the
|
|
66
|
+
# declaration explicitly says optional. State validation owns this
|
|
67
|
+
# boundary because a command may leave an aggregate field untouched;
|
|
68
|
+
# checking only command arguments would let a required relationship be
|
|
69
|
+
# persisted as nil. `has_many` admits zero members, so its empty list is
|
|
70
|
+
# already a valid cardinality and needs no presence refusal.
|
|
71
|
+
def validate_relationship_cardinality(construct, attribute, held)
|
|
72
|
+
return if attribute.relationship.nil? || attribute.list?
|
|
73
|
+
return unless held.nil? && !attribute.optional?
|
|
74
|
+
|
|
75
|
+
raise TypeMismatch,
|
|
76
|
+
"#{construct.hecks_name}.#{attribute.name} is a required " \
|
|
77
|
+
"#{attribute.relationship} relationship — expected one " \
|
|
78
|
+
"#{attribute.type.target_name} identity, got nil"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The reference RESOLVES itself — through the chapter's own IR, so the
|
|
82
|
+
# bluebook's declared heads are the index. This used to regex the target's
|
|
83
|
+
# name out of "Reference<Customer>" and then search
|
|
84
|
+
# `registry.bluebook(domain).aggregates` for it — and later reached the
|
|
85
|
+
# target through Ruby's constant tree, a class thrown away for its `.ir`
|
|
86
|
+
# the moment it was found.
|
|
87
|
+
def referenced_aggregate(attribute)
|
|
88
|
+
attribute.type.resolve
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def validate_reference_values(domain, target, held, list:)
|
|
92
|
+
values = list ? Array(held) : [held]
|
|
93
|
+
values.each do |value|
|
|
94
|
+
key = reference_key(value)
|
|
95
|
+
next if key.empty?
|
|
96
|
+
next if @registry.repository(domain, target).find(key)
|
|
97
|
+
|
|
98
|
+
raise NotFound,
|
|
99
|
+
RefusalWording.render("NotFound", "reference_target_missing",
|
|
100
|
+
target: target.name, heads: target.identity_heads.join(", "),
|
|
101
|
+
key: key.inspect)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# `value` is the referenced record's own id, EXACTLY as `Identity.of`
|
|
106
|
+
# would build it for that record — a bare scalar for a single-field
|
|
107
|
+
# identity (the overwhelming common case; Banking's own plain
|
|
108
|
+
# `reference_to Customer` holds one already, so `Value.
|
|
109
|
+
# materialize_unwrapped` is a no-op passthrough here), or a
|
|
110
|
+
# `Naming.identity`-joined string for a compound one
|
|
111
|
+
# (`belongs_to Translation, as: :translation_ref` — Translation's
|
|
112
|
+
# own `identified_by :domain, :from, :to`, THREE fields). Before
|
|
113
|
+
# this, plain `value.to_s` on that compound case's own coerced
|
|
114
|
+
# Value hit Ruby's default `Object#to_s` (a raw, run-to-run-random
|
|
115
|
+
# memory address) instead of joining the record's real id — found
|
|
116
|
+
# live via bin/fuzz on the self-hosted "translation" domain
|
|
117
|
+
# (replay_is_deterministic), the SAME class of gap `Identity.from`
|
|
118
|
+
# already had for a compound `identified_by`'s own bare (undotted)
|
|
119
|
+
# attribute paths. `materialize_unwrapped` recurses a multi-
|
|
120
|
+
# attribute value object to a plain Hash keyed by attribute name,
|
|
121
|
+
# in declaration order — `Naming.identity` on `.values` reproduces
|
|
122
|
+
# the identical join `Identity.of` itself would produce for the
|
|
123
|
+
# SAME fields.
|
|
124
|
+
def reference_key(value)
|
|
125
|
+
unwrapped = Value.materialize_unwrapped(value)
|
|
126
|
+
return Naming.identity(unwrapped.values).to_s if unwrapped.is_a?(Hash)
|
|
127
|
+
|
|
128
|
+
unwrapped.to_s
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# A related record's OWN fields, reachable by name from `given`/
|
|
132
|
+
# `ensures` — `customer.status`, `account.customer.status` — without
|
|
133
|
+
# teaching the pure expression evaluator anything about
|
|
134
|
+
# repositories. The lookup happens HERE, once, before evaluation;
|
|
135
|
+
# `Resolver#lookup` just digs into a plain Hash exactly as it
|
|
136
|
+
# always has.
|
|
137
|
+
#
|
|
138
|
+
# `owner` is either the declaring aggregate/entity (its OWN
|
|
139
|
+
# `reference_to`, read off `source` — the record's stored state)
|
|
140
|
+
# or the command itself (a reference-typed ARGUMENT, read off
|
|
141
|
+
# `source` — the dispatch payload). Same shape either way: every
|
|
142
|
+
# reference-typed attribute `owner` declares becomes a key in the
|
|
143
|
+
# result, named by stripping the attribute's own `_id` suffix
|
|
144
|
+
# (`customer_id` → `customer`) — a no-op for an aliased reference
|
|
145
|
+
# that already carries no suffix (`reference_to Account, as:
|
|
146
|
+
# :source` → `source`), which is why this needs no separate case
|
|
147
|
+
# for `as:`.
|
|
148
|
+
#
|
|
149
|
+
# RECURSES into what it finds, so a chain like
|
|
150
|
+
# `account.customer.status` resolves in one pass: hydrating
|
|
151
|
+
# `account` also hydrates ITS OWN `customer_id` into a nested
|
|
152
|
+
# `customer` key. Depth-bounded rather than cycle-detected — nothing
|
|
153
|
+
# in this corpus dots more than two hops, and a bound is simpler
|
|
154
|
+
# than tracking visited (type, id) pairs for a cycle nothing here
|
|
155
|
+
# declares.
|
|
156
|
+
DEREFERENCE_DEPTH = 4
|
|
157
|
+
private_constant :DEREFERENCE_DEPTH
|
|
158
|
+
|
|
159
|
+
def dereference(domain, owner, source, depth: DEREFERENCE_DEPTH)
|
|
160
|
+
return {} if depth <= 0 || owner.nil?
|
|
161
|
+
|
|
162
|
+
owner.attributes.each_with_object({}) do |attribute, hydrated|
|
|
163
|
+
next unless attribute.reference?
|
|
164
|
+
|
|
165
|
+
id = source[attribute.name]
|
|
166
|
+
next if id.nil?
|
|
167
|
+
|
|
168
|
+
target = referenced_aggregate(attribute)
|
|
169
|
+
next unless target
|
|
170
|
+
|
|
171
|
+
record = @registry.repository(domain, target).find(id.to_s)
|
|
172
|
+
next unless record
|
|
173
|
+
|
|
174
|
+
name = attribute.name.to_s.sub(/_id\z/, "").to_sym
|
|
175
|
+
hydrated[name] = record.state.merge(dereference(domain, target, record.state, depth: depth - 1))
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require_relative "command_rules/admissibility"
|
|
2
|
+
require_relative "command_rules/references"
|
|
3
|
+
require_relative "command_rules/arithmetic"
|
|
4
|
+
require_relative "command_rules/emission"
|
|
5
|
+
require_relative "command_rules/authorization"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module Runtime
|
|
9
|
+
# The rules both interpreters consult, one concern per file beside
|
|
10
|
+
# this one: whether a command may run (admissibility), whether its
|
|
11
|
+
# references point at anything (references), what its arithmetic
|
|
12
|
+
# mutations mean (arithmetic), what its emits become (emission), and
|
|
13
|
+
# whether the caller may run it at all (authorization).
|
|
14
|
+
class CommandRules
|
|
15
|
+
include Admissibility
|
|
16
|
+
include References
|
|
17
|
+
include Arithmetic
|
|
18
|
+
include Emission
|
|
19
|
+
include Authorization
|
|
20
|
+
|
|
21
|
+
attr_reader :registry
|
|
22
|
+
|
|
23
|
+
def initialize(registry)
|
|
24
|
+
@registry = registry
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
require_relative "../bluebook/expression"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Runtime
|
|
5
|
+
# Persistence-neutral facts derived from a command's existing semantic IR.
|
|
6
|
+
# This module deliberately does not execute a plan or choose a repository.
|
|
7
|
+
module DependencyPlanning
|
|
8
|
+
ATOMIC_PUT = :atomic_put
|
|
9
|
+
TRANSACTIONAL_FALLBACK = :load_apply_validate_store
|
|
10
|
+
|
|
11
|
+
Plan = Struct.new(
|
|
12
|
+
:read_set,
|
|
13
|
+
:write_set,
|
|
14
|
+
:payload_read_set,
|
|
15
|
+
:complete_state,
|
|
16
|
+
:state_independent,
|
|
17
|
+
:unresolved_dependencies,
|
|
18
|
+
keyword_init: true
|
|
19
|
+
) do
|
|
20
|
+
def complete_state? = complete_state
|
|
21
|
+
def state_independent? = state_independent
|
|
22
|
+
|
|
23
|
+
# Capability negotiation is correctness-first: an optimization is
|
|
24
|
+
# selected only when both the semantic proof and adapter capability
|
|
25
|
+
# are present. This is planning data only; no runtime path calls it yet.
|
|
26
|
+
def strategy_for(capabilities: [])
|
|
27
|
+
return TRANSACTIONAL_FALLBACK unless complete_state? && state_independent?
|
|
28
|
+
return TRANSACTIONAL_FALLBACK unless capabilities.map(&:to_sym).include?(ATOMIC_PUT)
|
|
29
|
+
|
|
30
|
+
ATOMIC_PUT
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module ExpressionReads
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
# Read the same parsed canonical-expression nodes the evaluator uses.
|
|
38
|
+
# A generic Struct walk keeps this additive when the expression grammar
|
|
39
|
+
# gains a composed node; only Lookup nodes carry domain dependencies.
|
|
40
|
+
def paths(canonical)
|
|
41
|
+
collect(Bluebook::Expression::Evaluator.parse(canonical), Set.new)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def collect(node, bound_names)
|
|
45
|
+
case node
|
|
46
|
+
when Bluebook::Expression::Resolver::Lookup
|
|
47
|
+
root = node.path.to_s.split(".", 2).first
|
|
48
|
+
bound_names.include?(root) ? [] : [node.path.to_s]
|
|
49
|
+
when Bluebook::Expression::Resolver::BlockPredicate
|
|
50
|
+
collect(node.receiver, bound_names) +
|
|
51
|
+
collect(node.predicate, bound_names | [node.param.to_s])
|
|
52
|
+
when Struct
|
|
53
|
+
node.each_pair.flat_map { |_name, value| collect(value, bound_names) }
|
|
54
|
+
when Array
|
|
55
|
+
node.flat_map { |value| collect(value, bound_names) }
|
|
56
|
+
else
|
|
57
|
+
[]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class Analyzer
|
|
63
|
+
STATEFUL_MUTATIONS = %i[append increment decrement multiply clamp remove].freeze
|
|
64
|
+
|
|
65
|
+
def self.call(aggregate:, command:) = new(aggregate, command).call
|
|
66
|
+
|
|
67
|
+
def initialize(aggregate, command)
|
|
68
|
+
@aggregate = aggregate
|
|
69
|
+
@command = command
|
|
70
|
+
@owner_fields = aggregate.attributes.to_set(&:name)
|
|
71
|
+
@owner_fields << aggregate.lifecycle.field.to_sym if aggregate.lifecycle
|
|
72
|
+
@payload_fields = command.attributes.to_set(&:name)
|
|
73
|
+
@state_reads = Set.new
|
|
74
|
+
@payload_reads = Set.new
|
|
75
|
+
@writes = Set.new
|
|
76
|
+
@known_writes = Set.new
|
|
77
|
+
@unresolved = Set.new
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def call
|
|
81
|
+
analyze_initial_state
|
|
82
|
+
analyze_mutations
|
|
83
|
+
analyze_lifecycle
|
|
84
|
+
analyze_rules(command.givens, phase: :before)
|
|
85
|
+
analyze_rules(command.ensures, phase: :after)
|
|
86
|
+
analyze_rules(aggregate.invariants, phase: :after)
|
|
87
|
+
add_preservation_reads
|
|
88
|
+
|
|
89
|
+
complete = unresolved.empty? && owner_fields.subset?(known_writes)
|
|
90
|
+
independent = complete && state_reads.empty?
|
|
91
|
+
|
|
92
|
+
Plan.new(
|
|
93
|
+
read_set: sorted(state_reads),
|
|
94
|
+
write_set: sorted(writes),
|
|
95
|
+
payload_read_set: sorted(payload_reads),
|
|
96
|
+
complete_state: complete,
|
|
97
|
+
state_independent: independent,
|
|
98
|
+
unresolved_dependencies: unresolved.to_a.sort.freeze
|
|
99
|
+
).freeze
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
attr_reader :aggregate, :command, :owner_fields, :payload_fields,
|
|
105
|
+
:state_reads, :payload_reads, :writes, :known_writes, :unresolved
|
|
106
|
+
|
|
107
|
+
# A fresh Instance supplies these values without reading a stored
|
|
108
|
+
# record. Keep this aligned with Instance.defaults/default_for. They
|
|
109
|
+
# establish completeness but are not command mutations, so they do not
|
|
110
|
+
# appear in write_set.
|
|
111
|
+
def analyze_initial_state
|
|
112
|
+
aggregate.attributes.each do |attribute|
|
|
113
|
+
known_writes << attribute.name if deterministic_initial_value?(attribute)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
known_writes << aggregate.lifecycle.field.to_sym if aggregate.lifecycle
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def deterministic_initial_value?(attribute)
|
|
120
|
+
return true if attribute.list? || attribute.optional? || !attribute.default.nil?
|
|
121
|
+
return false unless aggregate.respond_to?(:value_object)
|
|
122
|
+
|
|
123
|
+
value_object = aggregate.value_object(attribute.type)
|
|
124
|
+
value_object&.attributes&.all? { |field| !field.default.nil? }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def analyze_mutations
|
|
128
|
+
command.mutations.each do |mutation|
|
|
129
|
+
target = mutation.target.to_sym
|
|
130
|
+
writes << target
|
|
131
|
+
|
|
132
|
+
unless owner_fields.include?(target)
|
|
133
|
+
unresolved << "mutation target #{target} is not an aggregate field"
|
|
134
|
+
next
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
if mutation.op == :set
|
|
138
|
+
known_writes << target if analyze_source?(mutation.source)
|
|
139
|
+
elsif STATEFUL_MUTATIONS.include?(mutation.op)
|
|
140
|
+
state_reads << target
|
|
141
|
+
analyze_source?(mutation.source)
|
|
142
|
+
else
|
|
143
|
+
unresolved << "mutation operation #{mutation.op} has no dependency rule"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Returns true only when the source is known without prior aggregate
|
|
149
|
+
# state. Hash sources are the canonical append binding shape.
|
|
150
|
+
def analyze_source?(source)
|
|
151
|
+
case source
|
|
152
|
+
when Symbol
|
|
153
|
+
if payload_fields.include?(source)
|
|
154
|
+
payload_reads << source
|
|
155
|
+
return true
|
|
156
|
+
end
|
|
157
|
+
if owner_fields.include?(source)
|
|
158
|
+
state_reads << source
|
|
159
|
+
return false
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
unresolved << "mutation source #{source} has no payload or aggregate field"
|
|
163
|
+
false
|
|
164
|
+
when Hash
|
|
165
|
+
source.values.map { |value| analyze_source?(value) }.all?
|
|
166
|
+
else
|
|
167
|
+
true
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def analyze_lifecycle
|
|
172
|
+
lifecycle = aggregate.lifecycle
|
|
173
|
+
return unless lifecycle
|
|
174
|
+
|
|
175
|
+
if command.from
|
|
176
|
+
state_reads << lifecycle.field
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
return if lifecycle.transitions_for(command.hecks_name).empty?
|
|
180
|
+
|
|
181
|
+
writes << lifecycle.field
|
|
182
|
+
known_writes << lifecycle.field
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def analyze_rules(rules, phase:)
|
|
186
|
+
rules.each do |rule|
|
|
187
|
+
ExpressionReads.paths(rule.canonical).each { |path| classify_path(path, phase) }
|
|
188
|
+
rescue ArgumentError => error
|
|
189
|
+
unresolved << "expression #{rule.canonical.inspect} could not be analyzed: #{error.message}"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def classify_path(path, phase)
|
|
194
|
+
head, nested = path.split(".", 2)
|
|
195
|
+
name = head.to_sym
|
|
196
|
+
|
|
197
|
+
if name == :parent
|
|
198
|
+
parent_field = nested.to_s.split(".", 2).first
|
|
199
|
+
if parent_field.empty? || !owner_fields.include?(parent_field.to_sym)
|
|
200
|
+
unresolved << "#{path} does not name parent aggregate state"
|
|
201
|
+
else
|
|
202
|
+
state_reads << parent_field.to_sym
|
|
203
|
+
end
|
|
204
|
+
elsif name == :old
|
|
205
|
+
old_field = nested.to_s.split(".", 2).first
|
|
206
|
+
if old_field.empty? || !owner_fields.include?(old_field.to_sym)
|
|
207
|
+
unresolved << "#{path} does not name prior aggregate state"
|
|
208
|
+
else
|
|
209
|
+
state_reads << old_field.to_sym
|
|
210
|
+
end
|
|
211
|
+
elsif payload_fields.include?(name)
|
|
212
|
+
payload_reads << name
|
|
213
|
+
elsif owner_fields.include?(name)
|
|
214
|
+
state_reads << name if phase == :before || !known_writes.include?(name)
|
|
215
|
+
else
|
|
216
|
+
unresolved << "expression path #{path} has no payload or aggregate field"
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# A partial mutation must preserve every untouched field on the
|
|
221
|
+
# correctness path. Those prior values are real reads even when no rule
|
|
222
|
+
# names them. A deterministic write needs no preservation read.
|
|
223
|
+
def add_preservation_reads
|
|
224
|
+
state_reads.merge(owner_fields - known_writes)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def sorted(values) = values.to_a.sort.freeze
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|