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,210 @@
|
|
|
1
|
+
require_relative "interpreting"
|
|
2
|
+
require_relative "command_interpreter/argument_gate"
|
|
3
|
+
require_relative "event"
|
|
4
|
+
require_relative "value"
|
|
5
|
+
require_relative "../naming"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module Runtime
|
|
9
|
+
# The dispatch pipeline for a port operation — called by an adapter living
|
|
10
|
+
# outside the bluebook entirely, never by the domain itself. Deliberately
|
|
11
|
+
# a trimmed CommandInterpreter: the same payload gate and coercion
|
|
12
|
+
# (ArgumentGate, Interpreting#normalize_args), but no `given`, no
|
|
13
|
+
# `mutations`, no lifecycle, no save. A port operation does not act on an
|
|
14
|
+
# aggregate instance — it translates an external call into an event in
|
|
15
|
+
# this domain's own vocabulary, and whatever mutation follows happens
|
|
16
|
+
# wherever a `policy` reacts to that event, exactly as it would for any
|
|
17
|
+
# command-emitted one.
|
|
18
|
+
class PortOperationInterpreter
|
|
19
|
+
include Interpreting
|
|
20
|
+
include CommandInterpreter::ArgumentGate
|
|
21
|
+
|
|
22
|
+
Context = Struct.new(:domain, :aggregate, :operation, :args, :route, :instance, :result)
|
|
23
|
+
|
|
24
|
+
DISPATCH_ORDER = %i[
|
|
25
|
+
refuse_unknown_arguments refuse_absent_arguments normalize_args resolve_references resolve_route emit
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
def initialize(registry, rules:)
|
|
29
|
+
@registry = registry
|
|
30
|
+
@rules = rules
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def call(domain, aggregate, operation, args, route:)
|
|
34
|
+
ctx = Context.new(domain, aggregate, operation, args)
|
|
35
|
+
ctx.route = route
|
|
36
|
+
run_dispatch_order(DISPATCH_ORDER, ctx)
|
|
37
|
+
ctx.result
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def step_refuse_unknown_arguments(ctx)
|
|
43
|
+
step(:refuse_unknown_arguments) { refuse_unknown_arguments(ctx.domain, ctx.aggregate, ctx.operation, ctx.args) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def step_refuse_absent_arguments(ctx)
|
|
47
|
+
step(:refuse_absent_arguments) { refuse_absent_arguments(ctx.operation, ctx.args, aggregate: ctx.aggregate) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def step_normalize_args(ctx)
|
|
51
|
+
ctx.args = step(:normalize_args) { normalize_args(ctx.aggregate, ctx.operation, ctx.args) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def step_resolve_references(ctx)
|
|
55
|
+
step(:resolve_references) { @rules.resolve_references(ctx.domain, ctx.operation, ctx.args) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def step_resolve_route(ctx)
|
|
59
|
+
ctx.instance = step(:resolve_route) do
|
|
60
|
+
@registry.repository(ctx.domain, ctx.aggregate).find(ctx.route.aggregate) ||
|
|
61
|
+
raise(NotFound, "#{ctx.aggregate.hecks_name} #{ctx.route.aggregate.inspect} does not exist")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def step_emit(ctx)
|
|
66
|
+
ctx.result = step(:emit) { ctx.operation.outbound? ? ask(ctx) : emit(ctx) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# THE DOMAIN CALLING OUT, AND BOTH ENDINGS RECORDED.
|
|
70
|
+
#
|
|
71
|
+
# The adapter is found the same way every other port's is — by name,
|
|
72
|
+
# across whatever adapters this boot loaded — so an `asks` is bound by
|
|
73
|
+
# an adapter declaring `port "IssueTracker"` and nothing new to learn.
|
|
74
|
+
#
|
|
75
|
+
# EVERY FAILURE IS AN ANSWER. A raise from the far side of a boundary is
|
|
76
|
+
# not an exception in this domain's terms, it is the outside saying no,
|
|
77
|
+
# and the chapter already named the word for that. So the rescue is
|
|
78
|
+
# deliberately wide: a timeout, a bad credential, an adapter that does
|
|
79
|
+
# not exist, a nil where a number was wanted — all of them become the
|
|
80
|
+
# `refuses` event, carrying what was said. A policy reacts to it, a
|
|
81
|
+
# retry counter reads it, and nothing has to catch anything.
|
|
82
|
+
#
|
|
83
|
+
# AN ASK IS HANDED THE RECORD IT IS ABOUT.
|
|
84
|
+
#
|
|
85
|
+
# An INBOUND operation deliberately cannot read state — it is the
|
|
86
|
+
# anti-corruption boundary, translating a fact from outside, and letting
|
|
87
|
+
# it read the aggregate would make it a second place rules live. That
|
|
88
|
+
# rule was written for that direction and does not survive the crossing.
|
|
89
|
+
#
|
|
90
|
+
# An outbound one almost always needs the record. `asks "File"` names
|
|
91
|
+
# `reference_to Ticket` and the adapter needs the ticket's repository,
|
|
92
|
+
# title and body — which are ON the ticket, and which the policy that
|
|
93
|
+
# triggered this cannot supply because a command's event payload is its
|
|
94
|
+
# ARGUMENTS, not its state. Without this, every ask would have to have
|
|
95
|
+
# its data re-passed through the command that fired it, so the same text
|
|
96
|
+
# would live in two places and could differ.
|
|
97
|
+
#
|
|
98
|
+
# ARGUMENTS WIN over state, because an argument is what THIS call said
|
|
99
|
+
# and state is what the record happens to hold.
|
|
100
|
+
def ask(ctx)
|
|
101
|
+
payload = held_state(ctx).merge(materialise(ctx.args))
|
|
102
|
+
answer = adapter_for(ctx).public_send(Naming.snake(ctx.operation.hecks_name), **payload)
|
|
103
|
+
announce(ctx, ctx.operation.answers, ctx.args.merge(spread(answer)))
|
|
104
|
+
rescue StandardError => e
|
|
105
|
+
announce(ctx, ctx.operation.refuses, ctx.args.merge(refusal: { value: "#{e.class}: #{e.message}" }))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# THE ANSWER IS SPREAD, NOT NESTED — and that is what makes the loop
|
|
109
|
+
# close. A policy re-enters its target with the event payload VERBATIM;
|
|
110
|
+
# it cannot reach inside a key. So an answer tucked under `answered:`
|
|
111
|
+
# can be read by a human and by nothing else, and the command that
|
|
112
|
+
# should record the issue number never gets one.
|
|
113
|
+
#
|
|
114
|
+
# Spread, the adapter's own keys ARE the arguments of whatever command
|
|
115
|
+
# reacts to the answering event. Which is a real contract on the adapter
|
|
116
|
+
# — it must return what that command takes, in the shape the runtime
|
|
117
|
+
# coerces (`{ number: { value: 43 } }`, not `43`) — and naming it here
|
|
118
|
+
# is cheaper than a mapping layer nobody could see into.
|
|
119
|
+
#
|
|
120
|
+
# A non-Hash answer keeps the old shape: a port that returns a URL
|
|
121
|
+
# string has nothing to spread, and `answered:` is the honest word for
|
|
122
|
+
# a single unnamed value.
|
|
123
|
+
def spread(answer)
|
|
124
|
+
return { answered: answer } unless answer.is_a?(Hash)
|
|
125
|
+
|
|
126
|
+
answer.to_h { |key, value| [key.to_sym, deep_symbolize(value)] }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def deep_symbolize(value)
|
|
130
|
+
case value
|
|
131
|
+
when Hash then value.to_h { |k, v| [k.to_sym, deep_symbolize(v)] }
|
|
132
|
+
when Array then value.map { |element| deep_symbolize(element) }
|
|
133
|
+
else value
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# THE RECORD, IF THERE IS ONE. A record that does not exist yet is not
|
|
138
|
+
# an error here — the ask still goes, carrying only its arguments, and
|
|
139
|
+
# whatever the adapter makes of that is its own business. Refusing
|
|
140
|
+
# would put a second existence check behind the one `resolve_references`
|
|
141
|
+
# already performed.
|
|
142
|
+
def held_state(ctx)
|
|
143
|
+
ctx.instance ? Value.materialize(ctx.instance.state) : {}
|
|
144
|
+
rescue StandardError
|
|
145
|
+
{}
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# THE PORT THIS OPERATION BELONGS TO, found by asking the aggregate
|
|
149
|
+
# rather than threading it through the call — the dispatcher already
|
|
150
|
+
# resolved it once to get here, and a second parameter carried purely so
|
|
151
|
+
# this method can read it would be a parameter every OTHER step ignores.
|
|
152
|
+
def port_name_for(ctx)
|
|
153
|
+
owning = ctx.aggregate.ports.find { |port| port.operations.any? { |op| op.equal?(ctx.operation) } }
|
|
154
|
+
owning&.name or raise WiringError,
|
|
155
|
+
"#{ctx.operation.hecks_name} belongs to no port on #{ctx.aggregate.hecks_name}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def adapter_for(ctx)
|
|
159
|
+
name = port_name_for(ctx)
|
|
160
|
+
implementations = @registry.adapters.values.select { |adapter| adapter.port == name }
|
|
161
|
+
|
|
162
|
+
case implementations.size
|
|
163
|
+
when 1 then Adapters.const_get(implementations.first.name).new
|
|
164
|
+
when 0 then raise WiringError, "no adapter implements the #{name} port — nothing can answer #{ctx.operation.hecks_name}"
|
|
165
|
+
else raise WiringError,
|
|
166
|
+
"#{implementations.size} adapters implement the #{name} port " \
|
|
167
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# A Value never crosses the boundary — an adapter is somebody else's
|
|
172
|
+
# code and should be handed plain data, the same reasoning `JsonDoor`
|
|
173
|
+
# gives for materialising before it hands anything to an HTTP caller.
|
|
174
|
+
def materialise(args) = Value.materialize(args)
|
|
175
|
+
|
|
176
|
+
def announce(ctx, event_name, payload)
|
|
177
|
+
event = Event.new(
|
|
178
|
+
name: event_name,
|
|
179
|
+
aggregate: "#{ctx.domain}::#{ctx.aggregate.hecks_name}",
|
|
180
|
+
id: ctx.route.aggregate,
|
|
181
|
+
payload: payload,
|
|
182
|
+
occurred_at: Time.now.utc.iso8601
|
|
183
|
+
)
|
|
184
|
+
@registry.event_log << event
|
|
185
|
+
[event]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# THE ONE PLACE THIS DIFFERS FROM CommandRules::Emission — there is no
|
|
189
|
+
# mutated instance to read an id off, because nothing was hydrated or
|
|
190
|
+
# saved. The record this event is ABOUT is named by whichever attribute
|
|
191
|
+
# is a reference to the owning aggregate (PortOperationBuilder#build
|
|
192
|
+
# already refused to build an operation with none), so its coerced
|
|
193
|
+
# value — already a plain id, never an object, per
|
|
194
|
+
# Value::Coercion#refuse_object_reference — is what stamps the event.
|
|
195
|
+
def emit(ctx)
|
|
196
|
+
ctx.operation.emits.map do |event_name|
|
|
197
|
+
event = Event.new(
|
|
198
|
+
name: event_name,
|
|
199
|
+
aggregate: "#{ctx.domain}::#{ctx.aggregate.hecks_name}",
|
|
200
|
+
id: ctx.route.aggregate,
|
|
201
|
+
payload: ctx.args,
|
|
202
|
+
occurred_at: Time.now.utc.iso8601
|
|
203
|
+
)
|
|
204
|
+
@registry.event_log << event.emit!
|
|
205
|
+
event
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
require_relative "../naming"
|
|
2
|
+
require_relative "../ports/query"
|
|
3
|
+
require_relative "../ports/query/ordering"
|
|
4
|
+
require_relative "../query_specification/field_path"
|
|
5
|
+
require_relative "../query_specification/common/comparison"
|
|
6
|
+
require_relative "errors"
|
|
7
|
+
require_relative "reference_hop"
|
|
8
|
+
require_relative "refusal_wording"
|
|
9
|
+
require_relative "tenant_scope"
|
|
10
|
+
require_relative "value"
|
|
11
|
+
|
|
12
|
+
module Hecks
|
|
13
|
+
module Runtime
|
|
14
|
+
class QueryInterpreter
|
|
15
|
+
attr_reader :registry
|
|
16
|
+
|
|
17
|
+
def initialize(registry)
|
|
18
|
+
@registry = registry
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call(domain, aggregate, query_name, args)
|
|
22
|
+
return entity_rows(domain, aggregate, query_name, args) if query_name.include?(".")
|
|
23
|
+
|
|
24
|
+
declared = declared_query(aggregate, query_name)
|
|
25
|
+
args = normalize_args(aggregate, declared, args)
|
|
26
|
+
declared = TenantScope.apply(declared, args)
|
|
27
|
+
# AFTER TenantScope, so its synthetic clause is already present
|
|
28
|
+
# in `.wheres` and rides through as an ordinary LOCAL clause on
|
|
29
|
+
# the OUTER query. It does not reach the hop's own inner
|
|
30
|
+
# sub-query against the TARGET aggregate — a hop's target may
|
|
31
|
+
# not even declare the same tenant boundary, and propagating one
|
|
32
|
+
# aggregate's tenant scope onto an unrelated aggregate's own
|
|
33
|
+
# query is a real design question of its own, not answered here.
|
|
34
|
+
declared = ReferenceHop.apply(declared, args, registry: @registry, domain: domain, aggregate: aggregate)
|
|
35
|
+
|
|
36
|
+
repository = @registry.repository(domain, aggregate)
|
|
37
|
+
# `registry:` is threaded through so a `none_in_state` where-clause
|
|
38
|
+
# can look its target aggregate up — see
|
|
39
|
+
# QuerySpecification::Common::Comparison#none_in_state? for the
|
|
40
|
+
# comparator itself, and for why an ordinary aggregate-level
|
|
41
|
+
# Memory query needs the registry passed explicitly rather than
|
|
42
|
+
# closing over an instance variable.
|
|
43
|
+
if (native = Ports::Query.execute(repository, declared, args,
|
|
44
|
+
context: { domain: domain, aggregate: aggregate, registry: @registry }))
|
|
45
|
+
records = native
|
|
46
|
+
# `record.state.merge(id: record.id)` — id LAST, not first. See
|
|
47
|
+
# Instance#to_h's own comment: an aggregate free to declare its
|
|
48
|
+
# own attribute literally named `id` has that attribute's own
|
|
49
|
+
# wrapped value sitting in `record.state[:id]` already; merging
|
|
50
|
+
# it OVER a `{id:}.merge(state)` used to let it silently
|
|
51
|
+
# clobber the correct bare identity this row is supposed to
|
|
52
|
+
# carry.
|
|
53
|
+
# A QUERY ROW IS AN ANSWER, NOT A HANDLE. Mutating one edits
|
|
54
|
+
# nobody's state and silently disagrees with the store.
|
|
55
|
+
return Freezer.deep(records.map { |record| record.state.merge(id: record.id) })
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Freezer.deep(interpret(repository.all, declared, args, domain: domain))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The REFERENCE answer — this interpreter's own evaluation, never an
|
|
62
|
+
# adapter's native hook. The fuzzer's query oracle replays every
|
|
63
|
+
# generated ask through both paths and treats a difference as a
|
|
64
|
+
# finding: the differential gate the retired cross-runtime harness
|
|
65
|
+
# should always have been, aimed where the divergence actually
|
|
66
|
+
# lives — between the engines inside this one runtime.
|
|
67
|
+
#
|
|
68
|
+
# A hop clause is answered here by its OWN, deliberately naive
|
|
69
|
+
# walk (reference_where_holds?) — never Runtime::ReferenceHop's
|
|
70
|
+
# partition/fold/IN-clause. Sharing that algorithm would have made
|
|
71
|
+
# this oracle blind to exactly the code the hop feature adds: every
|
|
72
|
+
# PHASE of a shared fold would still get diffed against the native
|
|
73
|
+
# adapters, but the fold itself — the empty candidate set, a
|
|
74
|
+
# duplicate id, a dangling reference, a chain's inside-out
|
|
75
|
+
# resolution order — would only ever be compared against itself.
|
|
76
|
+
def reference_call(domain, aggregate, query_name, args)
|
|
77
|
+
return entity_rows(domain, aggregate, query_name, args) if query_name.include?(".")
|
|
78
|
+
|
|
79
|
+
declared = declared_query(aggregate, query_name)
|
|
80
|
+
args = normalize_args(aggregate, declared, args)
|
|
81
|
+
declared = TenantScope.apply(declared, args)
|
|
82
|
+
reference_interpret(@registry.repository(domain, aggregate).all, declared, args,
|
|
83
|
+
domain: domain, shape: aggregate)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def declared_query(aggregate, query_name)
|
|
89
|
+
aggregate.query(query_name) ||
|
|
90
|
+
raise(UnknownVerb, RefusalWording.render("UnknownVerb", "no_query",
|
|
91
|
+
aggregate: aggregate.hecks_name, query: query_name.inspect))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def interpret(records, declared, args, domain: nil)
|
|
95
|
+
matched = records.select { |r| declared.wheres.all? { |w| where_holds?(w, r, args, domain: domain) } }
|
|
96
|
+
ordered = ordered(matched, declared.order_by, declared.null_semantics)
|
|
97
|
+
# OFFSET FIRST, THEN LIMIT — the order SQL means by `LIMIT n
|
|
98
|
+
# OFFSET m`, and the order Ports::Query::InMemory#execute already
|
|
99
|
+
# applies (see that file's own comment). This interpreter used to
|
|
100
|
+
# never read declared.offset at all — offset silently vanished for
|
|
101
|
+
# any query answered here, not just come out reversed.
|
|
102
|
+
skipped = declared.offset ? ordered.drop(resolve_query_value(declared.offset.value, args).to_i) : ordered
|
|
103
|
+
capped = declared.limit ? skipped.first(resolve_query_value(declared.limit.value, args).to_i) : skipped
|
|
104
|
+
|
|
105
|
+
# id LAST — see the native-path comment above; same clobbering
|
|
106
|
+
# risk for the in-memory reference interpreter's own rows.
|
|
107
|
+
capped.map { |r| r.state.merge(id: r.id) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# `interpret`'s own twin, for reference_call alone — same
|
|
111
|
+
# select/order/limit shape, but a clause that hops through a
|
|
112
|
+
# reference is answered by reference_where_holds? instead of the
|
|
113
|
+
# plain FieldPath.dig(record, field) `where_holds?` uses (which
|
|
114
|
+
# has no concept of a reference at all — it would just read the
|
|
115
|
+
# raw id straight off the record and compare THAT).
|
|
116
|
+
def reference_interpret(records, declared, args, domain:, shape:)
|
|
117
|
+
matched = records.select { |r|
|
|
118
|
+
declared.wheres.all? { |w|
|
|
119
|
+
reference_where_holds?(w, r, args, domain: domain, shape: shape)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
ordered = ordered(matched, declared.order_by, declared.null_semantics)
|
|
123
|
+
# OFFSET FIRST, THEN LIMIT — same fix, same reasoning, as
|
|
124
|
+
# #interpret's own rows above.
|
|
125
|
+
skipped = declared.offset ? ordered.drop(resolve_query_value(declared.offset.value, args).to_i) : ordered
|
|
126
|
+
capped = declared.limit ? skipped.first(resolve_query_value(declared.limit.value, args).to_i) : skipped
|
|
127
|
+
|
|
128
|
+
# id LAST — same reasoning, same fix, as interpret's own rows.
|
|
129
|
+
capped.map { |r| r.state.merge(id: r.id) }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# THE NAIVE READING OF A HOP: not a fold, not an id set — for
|
|
133
|
+
# each candidate row, walk the reference by hand and dig the
|
|
134
|
+
# field out of whatever it actually points at. A nil reference,
|
|
135
|
+
# or one that resolves to nothing (a dangling id), makes the
|
|
136
|
+
# WHOLE clause false outright, whatever the comparator — "points
|
|
137
|
+
# at a client that is not active" is false for a proposal with no
|
|
138
|
+
# client at all, the same way it is false for one whose client
|
|
139
|
+
# really is active; falling through to holds?(clause, nil, args)
|
|
140
|
+
# instead would answer `ne` wrong (nil != "active" is true).
|
|
141
|
+
def reference_where_holds?(clause, record, args, domain:, shape:)
|
|
142
|
+
step = QuerySpecification::HopPath.next_hop(clause.field, shape.attributes)
|
|
143
|
+
return where_holds?(clause, record, args) unless step
|
|
144
|
+
|
|
145
|
+
hop, rest = step
|
|
146
|
+
inner = QuerySpecification::Common::WhereClause.new(field: rest, op: clause.op, value: clause.value)
|
|
147
|
+
held = record[hop.attribute.name]
|
|
148
|
+
reference_ids = hop.attribute.list? ? Array(held) : [held]
|
|
149
|
+
|
|
150
|
+
reference_ids.compact.any? do |reference_id|
|
|
151
|
+
target_record = @registry.repository(domain, hop.target).find(reference_id)
|
|
152
|
+
next false unless target_record
|
|
153
|
+
|
|
154
|
+
reference_where_holds?(inner, target_record, args, domain: domain, shape: hop.target)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def entity_rows(domain, aggregate, dotted, args)
|
|
159
|
+
entity_name, query_name = Naming.split_dotted(dotted)
|
|
160
|
+
entity = aggregate.entities.find { |piece| piece.hecks_name == entity_name } ||
|
|
161
|
+
raise(UnknownVerb, RefusalWording.render("UnknownVerb", "entity_unknown",
|
|
162
|
+
aggregate: aggregate.hecks_name, entity: entity_name.inspect))
|
|
163
|
+
declared = entity.query(query_name) ||
|
|
164
|
+
raise(UnknownVerb, RefusalWording.render("UnknownVerb", "entity_query_missing",
|
|
165
|
+
entity: entity_name, query: query_name.inspect))
|
|
166
|
+
declared = TenantScope.apply(declared, args)
|
|
167
|
+
list_attr = aggregate.attributes.find { |a| a.list? && a.type.to_s == entity_name } ||
|
|
168
|
+
raise(UnknownVerb, RefusalWording.render("UnknownVerb", "entity_holds_no_list",
|
|
169
|
+
aggregate: aggregate.hecks_name, entity: entity_name))
|
|
170
|
+
|
|
171
|
+
parent_key = Naming.reference_key(aggregate.hecks_name)
|
|
172
|
+
rows = @registry.repository(domain, aggregate).all.flat_map do |record|
|
|
173
|
+
Array(record[list_attr.name])
|
|
174
|
+
.select { |el| declared.wheres.all? { |w| element_where_holds?(w, el, args) } }
|
|
175
|
+
.map { |el| { parent_key => record.id }.merge(el) }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
ordered = ordered_elements(rows, declared.order_by, declared.null_semantics,
|
|
179
|
+
parent_key, entity.identity_heads)
|
|
180
|
+
declared.limit ? ordered.first(resolve_query_value(declared.limit.value, args).to_i) : ordered
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def element_where_holds?(clause, element, args)
|
|
184
|
+
holds?(clause, element[clause.field.to_sym], args)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# A row's own key, however the store spells it. A sub-list row is a plain hash
|
|
188
|
+
# merged from stored state, so its keys arrive as strings from one adapter and
|
|
189
|
+
# symbols from another — and reading only one spelling gave every row the SAME
|
|
190
|
+
# identity, which is a tie, which is the exact nondeterminism this tier exists
|
|
191
|
+
# to remove. It rides `comparable` for the same reason a where-clause does : an
|
|
192
|
+
# identity is a value object, and `to_s` on one is an OBJECT ADDRESS — a sort key
|
|
193
|
+
# that differs run to run, which is worse than the store order it replaced.
|
|
194
|
+
def cell(row, key) = row[key.to_sym] || row[key.to_s]
|
|
195
|
+
|
|
196
|
+
# A sub-list row is identified by its PARENT and then its own key : two
|
|
197
|
+
# entities under different parents can share a sequence, so the parent has
|
|
198
|
+
# to lead or the tie is not broken at all.
|
|
199
|
+
#
|
|
200
|
+
# EVERY KEY THE PIECE IS KNOWN BY, in declaration order, for the same
|
|
201
|
+
# reason the parent leads: a part that ties is a part that breaks no tie.
|
|
202
|
+
# This took `identified_by`, which is the SINGLE head and is nil the
|
|
203
|
+
# moment an identity has two parts — and `cell(row, nil)` calls
|
|
204
|
+
# `nil.to_sym`, so a query against a composite piece did not sort wrongly,
|
|
205
|
+
# it raised. A piece known by one key sorts exactly as it did.
|
|
206
|
+
def ordered_elements(rows, order_by, null_semantics, parent_key, entity_keys)
|
|
207
|
+
field = order_by&.field
|
|
208
|
+
Ports::Query::Ordering.apply(
|
|
209
|
+
rows, order_by, null_semantics,
|
|
210
|
+
identity: lambda { |row|
|
|
211
|
+
[row[parent_key].to_s, *Array(entity_keys).map { |key| comparable(cell(row, key)) }]
|
|
212
|
+
}
|
|
213
|
+
) { |row| comparable(QuerySpecification::FieldPath.dig(row, field)) }
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def where_holds?(clause, record, args, domain: nil)
|
|
217
|
+
holds?(clause, QuerySpecification::FieldPath.dig(record, clause.field), args, record: record, domain: domain)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# The comparator table itself lives in
|
|
221
|
+
# QuerySpecification::Common::Comparison. This method and
|
|
222
|
+
# Ports::Query::InMemory#holds? used to carry a copy each and the
|
|
223
|
+
# two drifted — `none_in_state` reached only one of them, and
|
|
224
|
+
# `comparable` disagreed about value objects with two numeric
|
|
225
|
+
# members. What stays here is how a value is REACHED for this
|
|
226
|
+
# path: the registry is instance state rather than an argument.
|
|
227
|
+
def holds?(clause, held, args, record: nil, domain: nil)
|
|
228
|
+
QuerySpecification::Common::Comparison.holds?(
|
|
229
|
+
clause.op, comparable(held), comparable(resolve_query_value(clause.value, args)), registry: @registry
|
|
230
|
+
)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def resolve_query_value(value, args)
|
|
234
|
+
value.is_a?(Symbol) ? args[value] : value
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def normalize_args(aggregate, declared, args)
|
|
238
|
+
declared.attributes.each_with_object(args.dup) do |attribute, normalized|
|
|
239
|
+
next unless normalized.key?(attribute.name)
|
|
240
|
+
|
|
241
|
+
normalized[attribute.name] = Value.for_attribute(aggregate, attribute, normalized[attribute.name])
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def comparable(value) = QuerySpecification::Common::Comparison.comparable(value)
|
|
246
|
+
|
|
247
|
+
def ordered(records, order_by, null_semantics = nil)
|
|
248
|
+
field = order_by&.field
|
|
249
|
+
Ports::Query::Ordering.apply(records, order_by, null_semantics,
|
|
250
|
+
identity: ->(record) { record.id.to_s }) { |record| comparable(record[field]) }
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|