hecks 0.2.0 → 1.0.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 +427 -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 +101 -0
- data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
- data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
- data/lib/hecks/adapters/driven/heki.adapter +4 -0
- data/lib/hecks/adapters/driven/heki.rb +179 -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 +145 -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 +88 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
- data/lib/hecks/adapters/driven/postgres.adapter +5 -0
- data/lib/hecks/adapters/driven/postgres.rb +438 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +17 -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 +80 -0
- data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
- data/lib/hecks/adapters/driven/sqlite.rb +306 -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 +54 -0
- data/lib/hecks/adapters.rb +6 -0
- data/lib/hecks/behaviors/dsl.rb +125 -0
- data/lib/hecks/behaviors/expectations.rb +340 -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 +438 -0
- data/lib/hecks/bluebook/assembly/marks.rb +228 -0
- data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
- data/lib/hecks/bluebook/assembly.rb +91 -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 +68 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -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 +78 -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 +1018 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +714 -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 +430 -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 +135 -0
- data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -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 +173 -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 +228 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +117 -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 +123 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
- data/lib/hecks/bluebook/expression/projection.json +218 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
- data/lib/hecks/bluebook/expression/resolver.rb +781 -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 +621 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -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 +542 -0
- data/lib/hecks/bluebook/model_check.rb +445 -0
- data/lib/hecks/bluebook/pattern_subset.rb +184 -0
- data/lib/hecks/bluebook/policy.rb +41 -0
- data/lib/hecks/bluebook/process_manager.rb +128 -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 +66 -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 +410 -0
- data/lib/hecks/embryonaut_bluebook.rb +75 -0
- data/lib/hecks/facade/cli_door.rb +119 -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 +166 -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 +341 -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 +177 -0
- data/lib/hecks/forms/field_shape.rb +232 -0
- data/lib/hecks/forms/html.rb +84 -0
- data/lib/hecks/forms/index_renderer.rb +35 -0
- data/lib/hecks/forms/page.rb +157 -0
- data/lib/hecks/forms/params.rb +161 -0
- data/lib/hecks/forms/port_argument.rb +46 -0
- data/lib/hecks/forms/query_form_renderer.rb +114 -0
- data/lib/hecks/forms/record_renderer.rb +119 -0
- data/lib/hecks/forms/record_table.rb +68 -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 +145 -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/bounded_exhaustive_expressions.rb +527 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
- data/lib/hecks/fuzzing/properties.rb +1192 -0
- data/lib/hecks/fuzzing/replay.rb +668 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
- data/lib/hecks/fuzzing/value_generator.rb +200 -0
- data/lib/hecks/fuzzing.rb +12 -0
- data/lib/hecks/grammar/evolve.rb +324 -0
- data/lib/hecks/grammar/expression.bluebook +420 -0
- data/lib/hecks/grammar/expression_operators.json +1648 -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 +471 -0
- data/lib/hecks/language/bluebook/entity.bluebook +392 -0
- data/lib/hecks/language/bluebook/policy.bluebook +189 -0
- data/lib/hecks/language/bluebook/process_manager.bluebook +381 -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 +448 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -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 +333 -0
- data/lib/hecks/language/port.bluebook +120 -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 +174 -0
- data/lib/hecks/ports/access_control.port +9 -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 +8 -0
- data/lib/hecks/ports/agent.rb +167 -0
- data/lib/hecks/ports/authentication.port +6 -0
- data/lib/hecks/ports/authentication.rb +50 -0
- data/lib/hecks/ports/authorization.port +7 -0
- data/lib/hecks/ports/authorization.rb +62 -0
- data/lib/hecks/ports/clock.port +5 -0
- data/lib/hecks/ports/clock.rb +62 -0
- data/lib/hecks/ports/extraction.port +5 -0
- data/lib/hecks/ports/extraction.rb +37 -0
- data/lib/hecks/ports/identity_assignment.port +5 -0
- data/lib/hecks/ports/identity_assignment.rb +45 -0
- data/lib/hecks/ports/identity_generation.port +5 -0
- data/lib/hecks/ports/identity_generation.rb +49 -0
- data/lib/hecks/ports/identity_resolution.port +5 -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 +138 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
- data/lib/hecks/ports/persistence/execution.rb +23 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
- data/lib/hecks/ports/persistence/plugin.rb +54 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
- data/lib/hecks/ports/persistence/plugins/era.rb +48 -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 +96 -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 +642 -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 +44 -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 +446 -0
- data/lib/hecks/projector/docs_projector.rb +321 -0
- data/lib/hecks/projector/exporter.rb +158 -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 +411 -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 +92 -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 +107 -0
- data/lib/hecks/query_specification/hop_path.rb +132 -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/aggregate_lock.rb +45 -0
- data/lib/hecks/runtime/boot_gates.rb +41 -0
- data/lib/hecks/runtime/caller.rb +66 -0
- data/lib/hecks/runtime/capability_graph.rb +44 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
- data/lib/hecks/runtime/command_interpreter.rb +497 -0
- data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
- data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
- data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
- data/lib/hecks/runtime/command_rules/emission.rb +34 -0
- data/lib/hecks/runtime/command_rules/references.rb +189 -0
- data/lib/hecks/runtime/command_rules.rb +28 -0
- data/lib/hecks/runtime/dependency_planning.rb +245 -0
- data/lib/hecks/runtime/dispatcher.rb +301 -0
- data/lib/hecks/runtime/entity_element.rb +253 -0
- data/lib/hecks/runtime/entity_interpreter.rb +299 -0
- data/lib/hecks/runtime/errors.rb +122 -0
- data/lib/hecks/runtime/event.rb +51 -0
- data/lib/hecks/runtime/identity.rb +156 -0
- data/lib/hecks/runtime/instance.rb +175 -0
- data/lib/hecks/runtime/interpreting.rb +90 -0
- data/lib/hecks/runtime/loader.rb +184 -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 +286 -0
- data/lib/hecks/runtime/reaction_invocation.rb +253 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +341 -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 +126 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
- data/lib/hecks/runtime/registry/verification.rb +246 -0
- data/lib/hecks/runtime/registry.rb +279 -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 +503 -0
- data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -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 +130 -0
- data/lib/hecks/runtime/value/coercion.rb +565 -0
- data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
- data/lib/hecks/runtime/value.rb +125 -0
- data/lib/hecks/runtime.rb +107 -0
- data/lib/hecks/storehouse.rb +632 -0
- data/lib/hecks/version.rb +16 -0
- data/lib/hecks/vocabulary.rb +218 -0
- data/lib/hecks.rb +123 -6
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
- metadata +412 -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,58 @@
|
|
|
1
|
+
require_relative "authorization_spec"
|
|
2
|
+
require_relative "comparators"
|
|
3
|
+
require_relative "cursor_spec"
|
|
4
|
+
require_relative "inspection_spec"
|
|
5
|
+
require_relative "limit_spec"
|
|
6
|
+
require_relative "null_semantics"
|
|
7
|
+
require_relative "offset_spec"
|
|
8
|
+
require_relative "order_by"
|
|
9
|
+
require_relative "where_clause"
|
|
10
|
+
|
|
11
|
+
module Hecks
|
|
12
|
+
module QuerySpecification
|
|
13
|
+
module Common
|
|
14
|
+
module DSL
|
|
15
|
+
# RENAMED FROM `where`/`order_by`/`limit`/`offset`/`authorize`
|
|
16
|
+
# (all below) — item #13's full metaprogrammed dispatch (slice
|
|
17
|
+
# 4c). A SHARED mixin, same shape `attribute_impl` proved in
|
|
18
|
+
# slice 3: ONE renamed method each, both Query and ReadModel
|
|
19
|
+
# Keyword rows name it in `calls:`. `where`/`order_by` are
|
|
20
|
+
# bootstrap-reachable (every core chapter's own `read_model`
|
|
21
|
+
# filters its roster with them), so both are in
|
|
22
|
+
# BOOTSTRAP_CALLS_FALLBACK for the ReadModel context; `limit`/
|
|
23
|
+
# `offset`/`authorize` are not (checked directly).
|
|
24
|
+
def where_impl(clauses)
|
|
25
|
+
@wheres ||= []
|
|
26
|
+
clauses.each do |field, value|
|
|
27
|
+
op, operand = split_comparator(value)
|
|
28
|
+
@wheres << WhereClause.new(field: field, op: op, value: operand)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def order_by_impl(field, direction = :asc)
|
|
33
|
+
@order_by = OrderBy.new(field: field, direction: direction)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def limit_impl(value) = @limit = LimitSpec.new(value: value)
|
|
37
|
+
def offset_impl(value) = @offset = OffsetSpec.new(value: value)
|
|
38
|
+
def cursor(value) = @cursor = CursorSpec.new(value: value)
|
|
39
|
+
def authorize_impl(policy, tenant: nil) = @authorization = AuthorizationSpec.new(policy: policy, tenant: tenant)
|
|
40
|
+
def nulls(mode) = @null_semantics = NullSemantics.new(mode: mode)
|
|
41
|
+
def inspect_query(mode = :sql) = @inspection = InspectionSpec.new(mode: mode)
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def split_comparator(value)
|
|
46
|
+
return [:eq, value] unless value.is_a?(Hash)
|
|
47
|
+
|
|
48
|
+
op, operand = value.first
|
|
49
|
+
unless value.size == 1 && COMPARATORS.include?(op.to_sym)
|
|
50
|
+
raise ArgumentError,
|
|
51
|
+
"unknown comparator #{value.inspect} — expected one of #{COMPARATORS.join(', ')}"
|
|
52
|
+
end
|
|
53
|
+
[op.to_sym, operand]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
# M3 — an UNDECLARED (`native`) null policy used to render no
|
|
29
|
+
# `NULLS ...` clause at all here, leaving each dialect's own
|
|
30
|
+
# default to decide: Postgres puts nulls LAST on ASC (and FIRST
|
|
31
|
+
# on DESC), while `#order` above — this same "native" default,
|
|
32
|
+
# for Memory — puts nulls FIRST on ASC (and LAST on DESC), the
|
|
33
|
+
# SQLite convention. Same query, same data, different row order
|
|
34
|
+
# depending only on which adapter ran it. Rendered explicitly
|
|
35
|
+
# here instead, so an undeclared policy means the SAME total
|
|
36
|
+
# order everywhere rather than "whatever this store already does"
|
|
37
|
+
# — matching `#order`'s own default rather than the other way
|
|
38
|
+
# round, since that default is unconditional (Memory/Heki have no
|
|
39
|
+
# dialect to defer to) and SQLite already agrees with it natively.
|
|
40
|
+
def sql_order(expression, direction, policy)
|
|
41
|
+
direction = direction.to_s.downcase == "desc" ? "DESC" : "ASC"
|
|
42
|
+
nulls = case policy&.mode.to_s
|
|
43
|
+
when "first" then " NULLS FIRST"
|
|
44
|
+
when "last" then " NULLS LAST"
|
|
45
|
+
else direction == "DESC" ? " NULLS LAST" : " NULLS FIRST"
|
|
46
|
+
end
|
|
47
|
+
"#{expression} #{direction}#{nulls}, id #{direction}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# THE COMPARATORS A NULL CANNOT SATISFY — the other half of
|
|
51
|
+
# `sql_predicate` below. That one answers "the value COMPARED TO is
|
|
52
|
+
# null" (`eq: nil` -> IS NULL, `ne: nil` -> IS NOT NULL, a real
|
|
53
|
+
# convention both adapters already shared). This one answers the
|
|
54
|
+
# case nothing covered: the ROW'S OWN value is null and the value
|
|
55
|
+
# compared to is not.
|
|
56
|
+
#
|
|
57
|
+
# SQL says unknown. `NULL <> 'red'` is NULL, not true, so the row
|
|
58
|
+
# is not returned. Ruby says `nil != "red"` is true, so it is. The
|
|
59
|
+
# two adapters therefore answered the SAME query on the SAME data
|
|
60
|
+
# differently — Memory returning a row SQLite omitted — which is
|
|
61
|
+
# not a difference of opinion a caller can plan around.
|
|
62
|
+
#
|
|
63
|
+
# Resolved toward SQL, and not because SQL is the store: an absent
|
|
64
|
+
# or null field is UNKNOWN, not a value, and a comparison against
|
|
65
|
+
# unknown is unknown rather than true. Making SQL match Ruby
|
|
66
|
+
# instead would mean compiling every `ne:` to
|
|
67
|
+
# `(col <> $1 OR col IS NULL)` — more to get right in two
|
|
68
|
+
# dialects, and a reliable way to lose an index — to make a real
|
|
69
|
+
# query engine agree with an in-memory one.
|
|
70
|
+
#
|
|
71
|
+
# `none_in_state` is deliberately NOT here: it is a 9th, vendored
|
|
72
|
+
# comparator whose `held` is a reference id, and a row holding no
|
|
73
|
+
# reference is genuinely "not in that state" rather than unknown.
|
|
74
|
+
NULL_UNMATCHABLE = %w[eq ne lt lte gt gte in contains].freeze
|
|
75
|
+
|
|
76
|
+
def unmatchable?(operation, held, want)
|
|
77
|
+
held.nil? && !want.nil? && NULL_UNMATCHABLE.include?(operation.to_s)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def sql_predicate(expression, operation, value)
|
|
81
|
+
if value.nil? && operation.to_s == "eq"
|
|
82
|
+
["#{expression} IS NULL", []]
|
|
83
|
+
elsif value.nil? && operation.to_s == "ne"
|
|
84
|
+
["#{expression} IS NOT NULL", []]
|
|
85
|
+
else
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
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,107 @@
|
|
|
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 — `key?` first, never `||`, because
|
|
35
|
+
# `||` falls through a genuinely-stored `false` to the OTHER
|
|
36
|
+
# spelling (usually absent) and returns `nil` instead. The seal
|
|
37
|
+
# admits boolean leaves (`SCALAR_PRIMITIVES` below), so a `false`
|
|
38
|
+
# here is a real, held answer, not a missing one.
|
|
39
|
+
def dig(holder, field)
|
|
40
|
+
return nil if field.nil?
|
|
41
|
+
|
|
42
|
+
field.to_s.split(".").reduce(holder) { |current, segment| read(current, segment) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def read(current, segment)
|
|
46
|
+
return nil if current.nil?
|
|
47
|
+
|
|
48
|
+
if current.is_a?(Hash)
|
|
49
|
+
sym = segment.to_sym
|
|
50
|
+
return current.key?(sym) ? current[sym] : current[segment]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# M5 — "or nil, never raise" is this method's whole contract, and
|
|
54
|
+
# an Array broke it: `Array#[]` demands an Integer index, so
|
|
55
|
+
# `current[segment]` (a String) raised `TypeError` straight
|
|
56
|
+
# through `dig` instead of answering nil. A dotted path stepping
|
|
57
|
+
# INTO a list_of attribute (`where "tags.name" == "x"` against a
|
|
58
|
+
# bare list, rather than each element) has no single member a
|
|
59
|
+
# bare index would name anyway — nil is the honest answer, the
|
|
60
|
+
# same one a dangling reference or a missing key already gets.
|
|
61
|
+
return nil if current.is_a?(Array)
|
|
62
|
+
|
|
63
|
+
current[segment]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The declared attribute a path lands on, or nil. `segments` is the
|
|
67
|
+
# dotted tail — for a bare field it is empty and the root attribute
|
|
68
|
+
# is its own leaf. The walk stops dead at a reference (an id is a
|
|
69
|
+
# scalar; nothing nests under it) and at any member the declared
|
|
70
|
+
# value object does not carry.
|
|
71
|
+
def leaf_attribute(attribute, segments)
|
|
72
|
+
current = attribute
|
|
73
|
+
segments.each do |segment|
|
|
74
|
+
return nil if current.nil? || current.reference?
|
|
75
|
+
|
|
76
|
+
shape = yield(current.type.to_s)
|
|
77
|
+
current = shape&.attributes&.find { |member| member.name.to_s == segment }
|
|
78
|
+
end
|
|
79
|
+
current
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# May an ordered comparator (lt/lte/gt/gte) land here, and may an
|
|
83
|
+
# ORDER BY cast here numerically? A bare field keeps the one-level
|
|
84
|
+
# convention every adapter already implements: a numeric primitive,
|
|
85
|
+
# or a value object carrying at least one numeric member. A dotted
|
|
86
|
+
# path must land on a numeric primitive itself — the convention does
|
|
87
|
+
# not reach through a named path, it IS the absence of one.
|
|
88
|
+
def numeric?(attribute, segments, &value_object)
|
|
89
|
+
leaf = leaf_attribute(attribute, segments, &value_object)
|
|
90
|
+
return false if leaf.nil? || leaf.list? || leaf.reference?
|
|
91
|
+
return true if NUMERIC_PRIMITIVES.include?(leaf.type.to_s)
|
|
92
|
+
return false unless segments.empty?
|
|
93
|
+
|
|
94
|
+
shape = yield(leaf.type.to_s)
|
|
95
|
+
!shape.nil? && shape.attributes.any? { |member| NUMERIC_PRIMITIVES.include?(member.type.to_s) }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# A dotted path must end on a SCALAR member — landing on a value
|
|
99
|
+
# object would hand SQL a JSON object where the reference
|
|
100
|
+
# interpreter unwraps a hash, and the two would answer differently.
|
|
101
|
+
def scalar_leaf?(attribute, segments, &value_object)
|
|
102
|
+
leaf = leaf_attribute(attribute, segments, &value_object)
|
|
103
|
+
!leaf.nil? && !leaf.list? && !leaf.reference? && SCALAR_PRIMITIVES.include?(leaf.type.to_s)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
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 — `BluebookBuilder#validate_no_bidirectional_references!`
|
|
11
|
+
# refuses any reference CYCLE at declaration time (ADR 0025,
|
|
12
|
+
# "References" — widened from a direct pair to any ring, DFS over
|
|
13
|
+
# the chapter's own reference graph), so what's left to guard here
|
|
14
|
+
# is depth, not cycles: MAX_HOPS below, and this module's own
|
|
15
|
+
# `refusal` states for a target the chapter doesn't resolve. A walk
|
|
16
|
+
# here still needs to say WHY it stopped, not just that it did.
|
|
17
|
+
#
|
|
18
|
+
# Every method below takes an ATTRIBUTE ARRAY, never a "shape"
|
|
19
|
+
# object — deliberately, because the two real callers hold their
|
|
20
|
+
# attributes differently. `AggregateBuilder` (tier-1 seal, mid-
|
|
21
|
+
# build) exposes `attributes` as a plain reader, but its own
|
|
22
|
+
# `attribute(name, type = String, ...)` is the DSL's attribute-
|
|
23
|
+
# DECLARING method — calling it as a finder would silently mint a
|
|
24
|
+
# new String attribute instead of looking one up. A sealed
|
|
25
|
+
# `Aggregate` (tier-2 seal, and every runtime target) has a
|
|
26
|
+
# real `attribute(name)` finder, but taking the array either way
|
|
27
|
+
# sidesteps the mismatch instead of asking every caller to know
|
|
28
|
+
# which kind of object it holds.
|
|
29
|
+
module HopPath
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
Hop = Struct.new(:attribute, :target_name, :target, keyword_init: true)
|
|
33
|
+
|
|
34
|
+
# refusal: nil (clean), :unresolvable (a hop's target cannot be
|
|
35
|
+
# found — cross-domain, or simply not declared in this chapter),
|
|
36
|
+
# :too_deep (see MAX_HOPS below).
|
|
37
|
+
Plan = Struct.new(:hops, :tail, :refusal, keyword_init: true)
|
|
38
|
+
|
|
39
|
+
# A hop chain long enough to matter is long enough to be a
|
|
40
|
+
# mistake — NOT a guard against a walk that cannot terminate.
|
|
41
|
+
# Nothing here loops forever regardless of how the reference
|
|
42
|
+
# graph is shaped: a hop chain is a literal dotted string, fixed
|
|
43
|
+
# at declaration time, and every step consumes exactly one of
|
|
44
|
+
# its own segments — the walk is bounded by what was TYPED, not
|
|
45
|
+
# by the graph. A self-referential aggregate hopping through
|
|
46
|
+
# itself more than once (`"parent.parent.name"`, a grandparent
|
|
47
|
+
# query) is real, common, and perfectly safe; this exists only
|
|
48
|
+
# to refuse a chain nobody meant to write this long.
|
|
49
|
+
MAX_HOPS = 8
|
|
50
|
+
|
|
51
|
+
# The segment name a Reference answers to in a hop path — its own
|
|
52
|
+
# declared attribute name, unchanged (ADR 0025, "References":
|
|
53
|
+
# `reference_to` mints that bare name now, no `_id`, so there is
|
|
54
|
+
# no derivation left to apply). `proposal/client` (a query hop)
|
|
55
|
+
# and `proposal.client` (the Ruby accessor,
|
|
56
|
+
# `Facade::Handle#define_reference_accessors`) name the same
|
|
57
|
+
# concept the same way.
|
|
58
|
+
def hop_name(attribute) = attribute.name.to_s
|
|
59
|
+
|
|
60
|
+
# Does this path's HEAD cross into another record via `/`? THE
|
|
61
|
+
# OPERATOR IS THE ANSWER NOW, not a name collision to arbitrate —
|
|
62
|
+
# `.` walks fields inside this record, `/` crosses into another
|
|
63
|
+
# one, so a path with no `/` is never a hop, full stop, and the
|
|
64
|
+
# OLD "a real local attribute wins first" rule (needed only
|
|
65
|
+
# because `.` was overloaded for both meanings, and `client_id`
|
|
66
|
+
# vs `client` was how the two were told apart) has nothing left
|
|
67
|
+
# to arbitrate. Answerable from `attributes` alone — a Reference
|
|
68
|
+
# knows its own `target_name` at declaration, before it can
|
|
69
|
+
# `resolve` it — which is what lets the AGGREGATE seal recognise
|
|
70
|
+
# a hop it cannot yet check.
|
|
71
|
+
def hop_head?(field, attributes)
|
|
72
|
+
head, rest = field.to_s.split("/", 2)
|
|
73
|
+
return false unless rest
|
|
74
|
+
|
|
75
|
+
attributes.any? { |candidate| candidate.reference? && hop_name(candidate) == head }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# ONE STEP: does `field`'s head hop through one of `attributes`'
|
|
79
|
+
# own references? Answers the resolved `Hop` plus the string
|
|
80
|
+
# still left to walk (itself possibly another `/`-hop, against
|
|
81
|
+
# the TARGET's own attributes, or a plain `.`-dotted field walk
|
|
82
|
+
# once the hops run out) — or nil, when the head names nothing or
|
|
83
|
+
# the path has no `/` at all. This is the one primitive
|
|
84
|
+
# Runtime::ReferenceHop needs: it recurses hop by hop through its
|
|
85
|
+
# own `apply`, one ordinary same-aggregate query at a time, and
|
|
86
|
+
# never needs the whole chain resolved up front the way a seal
|
|
87
|
+
# does.
|
|
88
|
+
def next_hop(field, attributes)
|
|
89
|
+
head, rest = field.to_s.split("/", 2)
|
|
90
|
+
return nil unless rest
|
|
91
|
+
|
|
92
|
+
attribute = attributes.find { |candidate| candidate.reference? && hop_name(candidate) == head }
|
|
93
|
+
return nil unless attribute
|
|
94
|
+
|
|
95
|
+
hop = Hop.new(attribute: attribute, target_name: attribute.type.target_name, target: attribute.type.resolve)
|
|
96
|
+
[hop, rest]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The WHOLE chain, resolved — every hop's target found, in
|
|
100
|
+
# order — for the one caller that needs it all at once:
|
|
101
|
+
# BluebookBuilder#validate_query_hops!, checking a hop chain
|
|
102
|
+
# before anything ever dispatches it.
|
|
103
|
+
def plan(field, attributes)
|
|
104
|
+
hops = []
|
|
105
|
+
remaining = field.to_s
|
|
106
|
+
current = attributes
|
|
107
|
+
|
|
108
|
+
loop do
|
|
109
|
+
step = next_hop(remaining, current)
|
|
110
|
+
break unless step
|
|
111
|
+
|
|
112
|
+
hop, rest = step
|
|
113
|
+
return Plan.new(hops: hops, tail: nil, refusal: :too_deep) if hops.size >= MAX_HOPS
|
|
114
|
+
|
|
115
|
+
# Pushed even unresolved — a caller reporting :unresolvable
|
|
116
|
+
# needs THIS hop's own target_name (real, known at
|
|
117
|
+
# declaration, regardless of whether resolve succeeded), not
|
|
118
|
+
# whatever hop came before it. `.target` is nil on this one
|
|
119
|
+
# entry; every caller checking `hops.last.target` already has
|
|
120
|
+
# to handle that, the same way `next_hop`'s own caller does.
|
|
121
|
+
hops << hop
|
|
122
|
+
return Plan.new(hops: hops, tail: nil, refusal: :unresolvable) unless hop.target
|
|
123
|
+
|
|
124
|
+
current = hop.target.attributes
|
|
125
|
+
remaining = rest
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
Plan.new(hops: hops, tail: remaining, refusal: nil)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
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
|