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,80 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module QuerySpecification
|
|
3
|
+
module Common
|
|
4
|
+
module NullPolicy
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
# STABLE on purpose : rows arrive already in identity order from
|
|
8
|
+
# Ports::Query::Ordering, and that base is what makes a tie deterministic
|
|
9
|
+
# rather than store-dependent. A plain sort_by is not stable in Ruby, so
|
|
10
|
+
# equal keys would shuffle and the identity tier would be lost exactly
|
|
11
|
+
# where it is needed. Descending reverses both partitions, so a tie reads
|
|
12
|
+
# identity-descending too — the same total order sql_order renders as
|
|
13
|
+
# `field DESC, id DESC`.
|
|
14
|
+
def order(records, direction:, policy: nil, &key)
|
|
15
|
+
null_rows, valued_rows = records.partition { |record| key.call(record).nil? }
|
|
16
|
+
sorted = valued_rows.each_with_index.sort_by { |record, index| [key.call(record), index] }.map(&:first)
|
|
17
|
+
if direction.to_s == "desc"
|
|
18
|
+
sorted.reverse!
|
|
19
|
+
null_rows.reverse!
|
|
20
|
+
end
|
|
21
|
+
case policy&.mode.to_s
|
|
22
|
+
when "first" then null_rows + sorted
|
|
23
|
+
when "last" then sorted + null_rows
|
|
24
|
+
else direction.to_s == "desc" ? sorted + null_rows : null_rows + sorted
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def sql_order(expression, direction, policy)
|
|
29
|
+
direction = direction.to_s.downcase == "desc" ? "DESC" : "ASC"
|
|
30
|
+
nulls = case policy&.mode.to_s
|
|
31
|
+
when "first" then " NULLS FIRST"
|
|
32
|
+
when "last" then " NULLS LAST"
|
|
33
|
+
else ""
|
|
34
|
+
end
|
|
35
|
+
"#{expression} #{direction}#{nulls}, id #{direction}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# THE COMPARATORS A NULL CANNOT SATISFY — the other half of
|
|
39
|
+
# `sql_predicate` below. That one answers "the value COMPARED TO is
|
|
40
|
+
# null" (`eq: nil` -> IS NULL, `ne: nil` -> IS NOT NULL, a real
|
|
41
|
+
# convention both adapters already shared). This one answers the
|
|
42
|
+
# case nothing covered: the ROW'S OWN value is null and the value
|
|
43
|
+
# compared to is not.
|
|
44
|
+
#
|
|
45
|
+
# SQL says unknown. `NULL <> 'red'` is NULL, not true, so the row
|
|
46
|
+
# is not returned. Ruby says `nil != "red"` is true, so it is. The
|
|
47
|
+
# two adapters therefore answered the SAME query on the SAME data
|
|
48
|
+
# differently — Memory returning a row SQLite omitted — which is
|
|
49
|
+
# not a difference of opinion a caller can plan around.
|
|
50
|
+
#
|
|
51
|
+
# Resolved toward SQL, and not because SQL is the store: an absent
|
|
52
|
+
# or null field is UNKNOWN, not a value, and a comparison against
|
|
53
|
+
# unknown is unknown rather than true. Making SQL match Ruby
|
|
54
|
+
# instead would mean compiling every `ne:` to
|
|
55
|
+
# `(col <> $1 OR col IS NULL)` — more to get right in two
|
|
56
|
+
# dialects, and a reliable way to lose an index — to make a real
|
|
57
|
+
# query engine agree with an in-memory one.
|
|
58
|
+
#
|
|
59
|
+
# `none_in_state` is deliberately NOT here: it is a 9th, vendored
|
|
60
|
+
# comparator whose `held` is a reference id, and a row holding no
|
|
61
|
+
# reference is genuinely "not in that state" rather than unknown.
|
|
62
|
+
NULL_UNMATCHABLE = %w[eq ne lt lte gt gte in contains].freeze
|
|
63
|
+
|
|
64
|
+
def unmatchable?(operation, held, want)
|
|
65
|
+
held.nil? && !want.nil? && NULL_UNMATCHABLE.include?(operation.to_s)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def sql_predicate(expression, operation, value)
|
|
69
|
+
if value.nil? && operation.to_s == "eq"
|
|
70
|
+
["#{expression} IS NULL", []]
|
|
71
|
+
elsif value.nil? && operation.to_s == "ne"
|
|
72
|
+
["#{expression} IS NOT NULL", []]
|
|
73
|
+
else
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require_relative "null_semantics"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module QuerySpecification
|
|
5
|
+
module Common
|
|
6
|
+
class Options
|
|
7
|
+
attr_reader :wheres, :order_by, :limit, :offset, :cursor,
|
|
8
|
+
:authorization, :null_semantics, :inspection
|
|
9
|
+
|
|
10
|
+
def initialize(wheres: [], order_by: nil, limit: nil, offset: nil, cursor: nil,
|
|
11
|
+
authorization: nil,
|
|
12
|
+
inspection: nil, null_semantics: NullSemantics.default)
|
|
13
|
+
@wheres, @order_by, @limit, @offset, @cursor = wheres, order_by, limit, offset, cursor
|
|
14
|
+
@authorization, @null_semantics, @inspection = authorization, null_semantics, inspection
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def options_to_h
|
|
18
|
+
{ wheres: @wheres.map(&:to_h), order_by: @order_by&.to_h, limit: @limit&.to_h,
|
|
19
|
+
offset: @offset&.to_h, cursor: @cursor&.to_h, authorization: @authorization&.to_h,
|
|
20
|
+
null_semantics: @null_semantics&.to_h, inspection: @inspection&.to_h }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def extra_options_to_h
|
|
24
|
+
options_to_h.reject { |key, value|
|
|
25
|
+
value.nil? || value == [] || (key == :null_semantics && value == { mode: "native" })
|
|
26
|
+
}
|
|
27
|
+
.reject { |key, _| %i[wheres order_by limit].include?(key) }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require_relative "comparators"
|
|
2
|
+
require_relative "where_clause"
|
|
3
|
+
require_relative "order_by"
|
|
4
|
+
require_relative "limit_spec"
|
|
5
|
+
require_relative "offset_spec"
|
|
6
|
+
require_relative "cursor_spec"
|
|
7
|
+
require_relative "authorization_spec"
|
|
8
|
+
require_relative "null_semantics"
|
|
9
|
+
require_relative "inspection_spec"
|
|
10
|
+
require_relative "options"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module QuerySpecification
|
|
3
|
+
# ONE reading of a dotted query-field path, shared by every place that
|
|
4
|
+
# used to invent its own. The reference interpreter did a bare
|
|
5
|
+
# `record[field]` lookup (a dotted path silently matched nothing), the
|
|
6
|
+
# SQL side split dots but judged numericness from the FIRST nested
|
|
7
|
+
# segment only (a two-level path quietly compared text), and the build
|
|
8
|
+
# seal walked the declaration graph a third way. Three
|
|
9
|
+
# implementations of "what does pizza.price_cents.cents mean" is two
|
|
10
|
+
# more than a language gets to have.
|
|
11
|
+
#
|
|
12
|
+
# Two sides, deliberately in one file so they cannot drift:
|
|
13
|
+
#
|
|
14
|
+
# dig(holder, field) — VALUE side: walk a record's held state,
|
|
15
|
+
# segment by segment, through Value
|
|
16
|
+
# objects and plain hashes alike.
|
|
17
|
+
# leaf_attribute / numeric? — DECLARATION side: walk the declared
|
|
18
|
+
# shape to the attribute a path lands on.
|
|
19
|
+
# Callers supply value-object lookup as a
|
|
20
|
+
# block, because an Aggregate and a
|
|
21
|
+
# mid-build AggregateBuilder hold their
|
|
22
|
+
# shapes differently but walk the same.
|
|
23
|
+
module FieldPath
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
NUMERIC_PRIMITIVES = %w[Integer Float].freeze
|
|
27
|
+
SCALAR_PRIMITIVES = %w[String Integer Float TrueClass FalseClass].freeze
|
|
28
|
+
|
|
29
|
+
# The held value a dotted field names, or nil — never a raise. The
|
|
30
|
+
# first segment reads off the record (an Instance, a Value, or a
|
|
31
|
+
# plain row hash); the rest read through whatever each step holds. A
|
|
32
|
+
# stored nested value object is a plain hash by the time it is read
|
|
33
|
+
# back, keyed by symbol in memory and by string off a wire decode,
|
|
34
|
+
# so both spellings are tried.
|
|
35
|
+
def dig(holder, field)
|
|
36
|
+
return nil if field.nil?
|
|
37
|
+
|
|
38
|
+
field.to_s.split(".").reduce(holder) { |current, segment| read(current, segment) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def read(current, segment)
|
|
42
|
+
return nil if current.nil?
|
|
43
|
+
return current[segment.to_sym] || current[segment] if current.is_a?(Hash)
|
|
44
|
+
|
|
45
|
+
current[segment]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The declared attribute a path lands on, or nil. `segments` is the
|
|
49
|
+
# dotted tail — for a bare field it is empty and the root attribute
|
|
50
|
+
# is its own leaf. The walk stops dead at a reference (an id is a
|
|
51
|
+
# scalar; nothing nests under it) and at any member the declared
|
|
52
|
+
# value object does not carry.
|
|
53
|
+
def leaf_attribute(attribute, segments)
|
|
54
|
+
current = attribute
|
|
55
|
+
segments.each do |segment|
|
|
56
|
+
return nil if current.nil? || current.reference?
|
|
57
|
+
|
|
58
|
+
shape = yield(current.type.to_s)
|
|
59
|
+
current = shape&.attributes&.find { |member| member.name.to_s == segment }
|
|
60
|
+
end
|
|
61
|
+
current
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# May an ordered comparator (lt/lte/gt/gte) land here, and may an
|
|
65
|
+
# ORDER BY cast here numerically? A bare field keeps the one-level
|
|
66
|
+
# convention every adapter already implements: a numeric primitive,
|
|
67
|
+
# or a value object carrying at least one numeric member. A dotted
|
|
68
|
+
# path must land on a numeric primitive itself — the convention does
|
|
69
|
+
# not reach through a named path, it IS the absence of one.
|
|
70
|
+
def numeric?(attribute, segments, &value_object)
|
|
71
|
+
leaf = leaf_attribute(attribute, segments, &value_object)
|
|
72
|
+
return false if leaf.nil? || leaf.list? || leaf.reference?
|
|
73
|
+
return true if NUMERIC_PRIMITIVES.include?(leaf.type.to_s)
|
|
74
|
+
return false unless segments.empty?
|
|
75
|
+
|
|
76
|
+
shape = yield(leaf.type.to_s)
|
|
77
|
+
!shape.nil? && shape.attributes.any? { |member| NUMERIC_PRIMITIVES.include?(member.type.to_s) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# A dotted path must end on a SCALAR member — landing on a value
|
|
81
|
+
# object would hand SQL a JSON object where the reference
|
|
82
|
+
# interpreter unwraps a hash, and the two would answer differently.
|
|
83
|
+
def scalar_leaf?(attribute, segments, &value_object)
|
|
84
|
+
leaf = leaf_attribute(attribute, segments, &value_object)
|
|
85
|
+
!leaf.nil? && !leaf.list? && !leaf.reference? && SCALAR_PRIMITIVES.include?(leaf.type.to_s)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
require_relative "../naming"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module QuerySpecification
|
|
5
|
+
# ONE reading of a dotted query-field path that hops THROUGH a
|
|
6
|
+
# reference into another aggregate's own shape — FieldPath's
|
|
7
|
+
# sibling, not its member. FieldPath walks a SHAPE, which cannot
|
|
8
|
+
# loop, and answers nil, never raising, because there is nothing
|
|
9
|
+
# left to say beyond "not found." HopPath walks the REFERENCE
|
|
10
|
+
# GRAPH instead, which the language does not guarantee acyclic —
|
|
11
|
+
# aggregates only refuse a DIRECT bidirectional pair
|
|
12
|
+
# (BluebookBuilder#validate_no_bidirectional_references! explicitly
|
|
13
|
+
# declines to take a position on a longer ring) — so a walk here
|
|
14
|
+
# needs to say WHY it stopped, not just that it did.
|
|
15
|
+
#
|
|
16
|
+
# Every method below takes an ATTRIBUTE ARRAY, never a "shape"
|
|
17
|
+
# object — deliberately, because the two real callers hold their
|
|
18
|
+
# attributes differently. `AggregateBuilder` (tier-1 seal, mid-
|
|
19
|
+
# build) exposes `attributes` as a plain reader, but its own
|
|
20
|
+
# `attribute(name, type = String, ...)` is the DSL's attribute-
|
|
21
|
+
# DECLARING method — calling it as a finder would silently mint a
|
|
22
|
+
# new String attribute instead of looking one up. A sealed
|
|
23
|
+
# `Aggregate` (tier-2 seal, and every runtime target) has a
|
|
24
|
+
# real `attribute(name)` finder, but taking the array either way
|
|
25
|
+
# sidesteps the mismatch instead of asking every caller to know
|
|
26
|
+
# which kind of object it holds.
|
|
27
|
+
module HopPath
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
Hop = Struct.new(:attribute, :target_name, :target, keyword_init: true)
|
|
31
|
+
|
|
32
|
+
# refusal: nil (clean), :unresolvable (a hop's target cannot be
|
|
33
|
+
# found — cross-domain, or simply not declared in this chapter),
|
|
34
|
+
# :too_deep (see MAX_HOPS below).
|
|
35
|
+
Plan = Struct.new(:hops, :tail, :refusal, keyword_init: true)
|
|
36
|
+
|
|
37
|
+
# A hop chain long enough to matter is long enough to be a
|
|
38
|
+
# mistake — NOT a guard against a walk that cannot terminate.
|
|
39
|
+
# Nothing here loops forever regardless of how the reference
|
|
40
|
+
# graph is shaped: a hop chain is a literal dotted string, fixed
|
|
41
|
+
# at declaration time, and every step consumes exactly one of
|
|
42
|
+
# its own segments — the walk is bounded by what was TYPED, not
|
|
43
|
+
# by the graph. A self-referential aggregate hopping through
|
|
44
|
+
# itself more than once (`"parent.parent.name"`, a grandparent
|
|
45
|
+
# query) is real, common, and perfectly safe; this exists only
|
|
46
|
+
# to refuse a chain nobody meant to write this long.
|
|
47
|
+
MAX_HOPS = 8
|
|
48
|
+
|
|
49
|
+
# The segment name a Reference answers to in a hop path — its own
|
|
50
|
+
# declared attribute name, unchanged (ADR 0025, "References":
|
|
51
|
+
# `reference_to` mints that bare name now, no `_id`, so there is
|
|
52
|
+
# no derivation left to apply). `proposal/client` (a query hop)
|
|
53
|
+
# and `proposal.client` (the Ruby accessor,
|
|
54
|
+
# `Facade::Handle#define_reference_accessors`) name the same
|
|
55
|
+
# concept the same way.
|
|
56
|
+
def hop_name(attribute) = attribute.name.to_s
|
|
57
|
+
|
|
58
|
+
# Does this path's HEAD cross into another record via `/`? THE
|
|
59
|
+
# OPERATOR IS THE ANSWER NOW, not a name collision to arbitrate —
|
|
60
|
+
# `.` walks fields inside this record, `/` crosses into another
|
|
61
|
+
# one, so a path with no `/` is never a hop, full stop, and the
|
|
62
|
+
# OLD "a real local attribute wins first" rule (needed only
|
|
63
|
+
# because `.` was overloaded for both meanings, and `client_id`
|
|
64
|
+
# vs `client` was how the two were told apart) has nothing left
|
|
65
|
+
# to arbitrate. Answerable from `attributes` alone — a Reference
|
|
66
|
+
# knows its own `target_name` at declaration, before it can
|
|
67
|
+
# `resolve` it — which is what lets the AGGREGATE seal recognise
|
|
68
|
+
# a hop it cannot yet check.
|
|
69
|
+
def hop_head?(field, attributes)
|
|
70
|
+
head, rest = field.to_s.split("/", 2)
|
|
71
|
+
return false unless rest
|
|
72
|
+
|
|
73
|
+
attributes.any? { |candidate| candidate.reference? && hop_name(candidate) == head }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# ONE STEP: does `field`'s head hop through one of `attributes`'
|
|
77
|
+
# own references? Answers the resolved `Hop` plus the string
|
|
78
|
+
# still left to walk (itself possibly another `/`-hop, against
|
|
79
|
+
# the TARGET's own attributes, or a plain `.`-dotted field walk
|
|
80
|
+
# once the hops run out) — or nil, when the head names nothing or
|
|
81
|
+
# the path has no `/` at all. This is the one primitive
|
|
82
|
+
# Runtime::ReferenceHop needs: it recurses hop by hop through its
|
|
83
|
+
# own `apply`, one ordinary same-aggregate query at a time, and
|
|
84
|
+
# never needs the whole chain resolved up front the way a seal
|
|
85
|
+
# does.
|
|
86
|
+
def next_hop(field, attributes)
|
|
87
|
+
head, rest = field.to_s.split("/", 2)
|
|
88
|
+
return nil unless rest
|
|
89
|
+
|
|
90
|
+
attribute = attributes.find { |candidate| candidate.reference? && hop_name(candidate) == head }
|
|
91
|
+
return nil unless attribute
|
|
92
|
+
|
|
93
|
+
hop = Hop.new(attribute: attribute, target_name: attribute.type.target_name, target: attribute.type.resolve)
|
|
94
|
+
[hop, rest]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# The WHOLE chain, resolved — every hop's target found, in
|
|
98
|
+
# order — for the one caller that needs it all at once:
|
|
99
|
+
# BluebookBuilder#validate_query_hops!, checking a hop chain
|
|
100
|
+
# before anything ever dispatches it.
|
|
101
|
+
def plan(field, attributes)
|
|
102
|
+
hops = []
|
|
103
|
+
remaining = field.to_s
|
|
104
|
+
current = attributes
|
|
105
|
+
|
|
106
|
+
loop do
|
|
107
|
+
step = next_hop(remaining, current)
|
|
108
|
+
break unless step
|
|
109
|
+
|
|
110
|
+
hop, rest = step
|
|
111
|
+
return Plan.new(hops: hops, tail: nil, refusal: :too_deep) if hops.size >= MAX_HOPS
|
|
112
|
+
|
|
113
|
+
# Pushed even unresolved — a caller reporting :unresolvable
|
|
114
|
+
# needs THIS hop's own target_name (real, known at
|
|
115
|
+
# declaration, regardless of whether resolve succeeded), not
|
|
116
|
+
# whatever hop came before it. `.target` is nil on this one
|
|
117
|
+
# entry; every caller checking `hops.last.target` already has
|
|
118
|
+
# to handle that, the same way `next_hop`'s own caller does.
|
|
119
|
+
hops << hop
|
|
120
|
+
return Plan.new(hops: hops, tail: nil, refusal: :unresolvable) unless hop.target
|
|
121
|
+
|
|
122
|
+
current = hop.target.attributes
|
|
123
|
+
remaining = rest
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
Plan.new(hops: hops, tail: remaining, refusal: nil)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require_relative "../common/options"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module QuerySpecification
|
|
5
|
+
module ReadModel
|
|
6
|
+
class Specification < Common::Options
|
|
7
|
+
attr_reader :joins
|
|
8
|
+
|
|
9
|
+
def initialize(joins: [], **options)
|
|
10
|
+
super(**options)
|
|
11
|
+
@joins = joins
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_h = options_to_h.merge(joins: @joins)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
# The declarative shape of an ask — what a query says, held apart from
|
|
3
|
+
# any engine that answers it. `common/` is the vocabulary every ask
|
|
4
|
+
# shares; `read_model/` the read-model-specific specification.
|
|
5
|
+
module QuerySpecification
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require_relative "query_specification/common/specification"
|
|
10
|
+
require_relative "query_specification/common/null_policy"
|
|
11
|
+
require_relative "query_specification/common/dsl"
|
|
12
|
+
require_relative "query_specification/field_path"
|
|
13
|
+
require_relative "query_specification/hop_path"
|
|
14
|
+
require_relative "query_specification/read_model/specification"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
# HOW A VALUE READS INSIDE A REFUSAL, in one place, because a refusal is an
|
|
5
|
+
# ANSWER and its wording is contract — pinned byte-for-byte by the corpus.
|
|
6
|
+
#
|
|
7
|
+
# `inspect` and JSON agree on scalars and disagree on everything composite :
|
|
8
|
+
# a hash is `{"cents"=>100}` against `{"cents":100}`, an array is `["a", "b"]`
|
|
9
|
+
# against `["a","b"]`. Each such spelling is invisible until a composite
|
|
10
|
+
# actually reaches a message, which is why both known cases were found by
|
|
11
|
+
# something deliberately passing the wrong shape rather than by review — the
|
|
12
|
+
# first by hand in spec/corpus/banking.json, the second by bin/fuzz, at a
|
|
13
|
+
# different site, after the first was patched where it was found.
|
|
14
|
+
#
|
|
15
|
+
# So it lives here rather than in whichever file noticed it, and the house style
|
|
16
|
+
# is JSON because that is what every other refusal in the corpus already prints.
|
|
17
|
+
module Rendering
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def describe(value)
|
|
21
|
+
case value
|
|
22
|
+
when nil then "nil"
|
|
23
|
+
when Hash, Array then JSON.generate(value)
|
|
24
|
+
else
|
|
25
|
+
# A Hecks::Runtime::Value reaching a refusal message (an
|
|
26
|
+
# arithmetic op's own `amount`/`current`, an Admissibility
|
|
27
|
+
# `current`, ...) had no case here, falling straight through to
|
|
28
|
+
# Ruby's own #inspect and leaking a raw object pointer
|
|
29
|
+
# ("#<Hecks::Runtime::Value:0x...>") into an otherwise
|
|
30
|
+
# correct domain refusal. Duck-typed on `respond_to?(:to_h)`
|
|
31
|
+
# rather than naming `Runtime::Value` directly — `Runtime::Value`
|
|
32
|
+
# itself requires THIS file (`runtime/value.rb`'s own
|
|
33
|
+
# `require_relative "../rendering"`), so naming it here would be
|
|
34
|
+
# circular. A single-field wrapper (the overwhelming common case
|
|
35
|
+
# — an amount, an id, a lifecycle field) unwraps to its bare
|
|
36
|
+
# scalar, described recursively; a genuinely multi-field value
|
|
37
|
+
# renders as its fields' JSON, same as a bare Hash already does
|
|
38
|
+
# two lines up.
|
|
39
|
+
if value.respond_to?(:to_h) && !value.is_a?(Hash) && !value.is_a?(Array)
|
|
40
|
+
fields = value.to_h
|
|
41
|
+
fields.size == 1 ? describe(fields.values.first) : JSON.generate(fields)
|
|
42
|
+
else
|
|
43
|
+
value.inspect
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
require_relative "../fqn"
|
|
2
|
+
require_relative "../facade/handle"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
class Router
|
|
6
|
+
# Installs optional Ruby syntax over an already-loaded router. FQN
|
|
7
|
+
# resolution stays in Router; this object only adapts it to constants and
|
|
8
|
+
# method calls.
|
|
9
|
+
class NamespaceInstaller
|
|
10
|
+
class OptionsProxy
|
|
11
|
+
def initialize(router:, realm:, domain:, aggregate:, options:)
|
|
12
|
+
unknown = options.keys - [:version]
|
|
13
|
+
raise ArgumentError, "unknown router options: #{unknown.join(', ')}" unless unknown.empty?
|
|
14
|
+
|
|
15
|
+
@router, @realm, @domain, @aggregate = router, realm, domain, aggregate
|
|
16
|
+
@version = options[:version]&.to_s
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def method_missing(verb, **args)
|
|
20
|
+
fqn = fqn_for(verb)
|
|
21
|
+
fqn.command? ? @router.dispatch(fqn.to_s, **args) : @router.query(fqn.to_s, **args)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def respond_to_missing?(verb, include_private = false)
|
|
25
|
+
Fqn.command_name?(verb) || Fqn.query_name?(verb) || super
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def fqn_for(verb)
|
|
31
|
+
if Fqn.command_name?(verb)
|
|
32
|
+
Fqn.command(realm: @realm, domain: @domain, version: @version, aggregate: @aggregate, command: verb)
|
|
33
|
+
elsif Fqn.query_name?(verb)
|
|
34
|
+
Fqn.query(realm: @realm, domain: @domain, version: @version, aggregate: @aggregate, query: verb)
|
|
35
|
+
else
|
|
36
|
+
raise NoMethodError, "#{verb.inspect} is not a Bluebook command or query"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def initialize(router)
|
|
42
|
+
@router = router
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def install!
|
|
46
|
+
current_entries.each { |entry| install_namespace_entry(entry) }
|
|
47
|
+
install_shortcuts!
|
|
48
|
+
install_aggregate_doors!
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
attr_reader :router
|
|
55
|
+
|
|
56
|
+
def current_entries = router.available.reject { |entry| entry.fqn.version }
|
|
57
|
+
|
|
58
|
+
def install_namespace_entry(entry)
|
|
59
|
+
target = namespace_for(entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate)
|
|
60
|
+
address = entry.fqn.to_s
|
|
61
|
+
active_router = router
|
|
62
|
+
target.define_singleton_method(:options) do |**options|
|
|
63
|
+
OptionsProxy.new(router: active_router, realm: entry.fqn.realm, domain: entry.fqn.domain,
|
|
64
|
+
aggregate: entry.fqn.aggregate, options: options)
|
|
65
|
+
end
|
|
66
|
+
if entry.command?
|
|
67
|
+
target.define_singleton_method(entry.fqn.verb) { |**args| active_router.dispatch(address, **args) }
|
|
68
|
+
else
|
|
69
|
+
target.define_singleton_method(entry.fqn.verb) { |**args| active_router.query(address, **args) }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# `.find`/`.all`/`.count`/`.events`/`.repository` — the SAME read/CRUD
|
|
74
|
+
# surface `Facade::Surface::AggregateDoor` gives a plain `Hecks
|
|
75
|
+
# .boot`, missing here until now: `install_namespace_entry` above
|
|
76
|
+
# installs ONE method per declared VERB, so an aggregate with no
|
|
77
|
+
# commands or queries of its own shape (or simply never asked for
|
|
78
|
+
# a `find`-shaped query) had no way to look up one record by id
|
|
79
|
+
# through the router surface at all — real gap, hit live building
|
|
80
|
+
# a `List` aggregate meant to be read this way. Grouped by
|
|
81
|
+
# (realm, domain, aggregate) rather than installed per-verb,
|
|
82
|
+
# because unlike a command or query this is the SAME five methods
|
|
83
|
+
# regardless of which verb happened to trigger this aggregate's
|
|
84
|
+
# own namespace module into existing.
|
|
85
|
+
def install_aggregate_doors!
|
|
86
|
+
current_entries.reject { |entry| entry.fqn.aggregate.nil? }
|
|
87
|
+
.group_by { |entry| [entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate] }
|
|
88
|
+
.each_value { |entries| install_aggregate_door(entries.first) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def install_aggregate_door(entry)
|
|
92
|
+
dispatcher = entry.dispatcher
|
|
93
|
+
domain = entry.fqn.domain
|
|
94
|
+
ir = dispatcher.registry.bluebook(domain)&.aggregate(entry.fqn.aggregate)
|
|
95
|
+
return unless ir # a query/command-only entry whose owner isn't a real aggregate root
|
|
96
|
+
|
|
97
|
+
fqn = "#{domain}::#{ir.hecks_name}"
|
|
98
|
+
target = namespace_for(entry.fqn.realm, domain, entry.fqn.aggregate)
|
|
99
|
+
|
|
100
|
+
target.define_singleton_method(:repository) { dispatcher.registry.repository(domain, ir) }
|
|
101
|
+
target.define_singleton_method(:count) { dispatcher.registry.repository(domain, ir).count }
|
|
102
|
+
target.define_singleton_method(:events) { dispatcher.events.select { |event| event.aggregate == fqn } }
|
|
103
|
+
|
|
104
|
+
target.define_singleton_method(:find) do |id|
|
|
105
|
+
found = dispatcher.registry.repository(domain, ir).find(id)
|
|
106
|
+
found && Facade::Handle.new(dispatcher: dispatcher, domain: domain, ir: ir, instance: found)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
target.define_singleton_method(:all) do
|
|
110
|
+
dispatcher.registry.repository(domain, ir).all.map do |instance|
|
|
111
|
+
Facade::Handle.new(dispatcher: dispatcher, domain: domain, ir: ir, instance: instance)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def install_shortcuts!
|
|
117
|
+
installer = self
|
|
118
|
+
current_entries.reject { |entry| entry.fqn.aggregate.nil? }
|
|
119
|
+
.group_by { |entry| [entry.fqn.aggregate, entry.fqn.verb] }
|
|
120
|
+
.each do |(aggregate, verb), candidates|
|
|
121
|
+
shortcut_target(aggregate).define_singleton_method(verb) { |**args|
|
|
122
|
+
installer.send(:dispatch_short, candidates, **args)
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def dispatch_short(candidates, **args)
|
|
128
|
+
if candidates.length > 1
|
|
129
|
+
shown = candidates.map { |entry| entry.fqn.to_s }.sort.join(", ")
|
|
130
|
+
aggregate, verb = candidates.first.fqn.aggregate, candidates.first.fqn.verb
|
|
131
|
+
raise AmbiguousShortRoute, "#{aggregate}.#{verb} is ambiguous — choose one of: #{shown}"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
entry = candidates.fetch(0)
|
|
135
|
+
entry.command? ? router.dispatch(entry.fqn.to_s, **args) : router.query(entry.fqn.to_s, **args)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def shortcut_target(aggregate)
|
|
139
|
+
constant = Object.const_get(aggregate, false) if Object.const_defined?(aggregate, false)
|
|
140
|
+
raise NameError,
|
|
141
|
+
"cannot install Bluebook shortcut #{aggregate}: it is not a module" if constant && !constant.is_a?(Module)
|
|
142
|
+
|
|
143
|
+
constant || Object.const_set(aggregate, Module.new)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def namespace_for(realm, domain, aggregate)
|
|
147
|
+
[realm, domain, aggregate].compact.reduce(Object) do |parent, name|
|
|
148
|
+
constant = parent.const_get(name, false) if parent.const_defined?(name, false)
|
|
149
|
+
raise NameError,
|
|
150
|
+
"cannot install Bluebook route under #{parent}::#{name}: it is not a module" if constant && !constant.is_a?(Module)
|
|
151
|
+
|
|
152
|
+
constant || parent.const_set(name, Module.new)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|