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,207 @@
|
|
|
1
|
+
require_relative "word_gate"
|
|
2
|
+
module Hecks
|
|
3
|
+
module Bluebook
|
|
4
|
+
module DSL
|
|
5
|
+
class ProcessManagerBuilder
|
|
6
|
+
GRAMMAR_CONTEXT = "ProcessManager"
|
|
7
|
+
|
|
8
|
+
class InvalidProcessManager < StandardError; end
|
|
9
|
+
|
|
10
|
+
include WordGate
|
|
11
|
+
|
|
12
|
+
def initialize(name)
|
|
13
|
+
@name = name
|
|
14
|
+
@handlers = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# `correlates_by`/`starts_on`/`ends_on` — item #13's full
|
|
18
|
+
# metaprogrammed dispatch, slice 1 (whole-project table-
|
|
19
|
+
# unification survey): each a bare, kind-driven coerce-and-
|
|
20
|
+
# assign with nothing else, now executed by `GenericDispatch`.
|
|
21
|
+
|
|
22
|
+
# ONE STATE-MACHINE VOCABULARY (S7, ADR 0025 — "events and
|
|
23
|
+
# reactions"): the SAME word `Lifecycle#transition` already
|
|
24
|
+
# carries, one level over — `transition "AccountDebited" =>
|
|
25
|
+
# "awaiting_credit", from: "requested" do ... end` replaces `on
|
|
26
|
+
# "AccountDebited", transition: { "requested" => "awaiting_
|
|
27
|
+
# credit" } do ... end`. Same bare rocket-pair argument shape
|
|
28
|
+
# (not a NAMED `transition:` kwarg wrapping a second Hash), same
|
|
29
|
+
# `from:` — including the array form Lifecycle's own commands
|
|
30
|
+
# could already take and a process manager's own events could
|
|
31
|
+
# not — and the states a procedure runs on are DERIVED from the
|
|
32
|
+
# transitions that name them, the same way `Behaviour::Lifecycle
|
|
33
|
+
# #states` already derives an aggregate's ; `state "x"` lines
|
|
34
|
+
# duplicated exactly what the transition list already said,
|
|
35
|
+
# and could drift from it (`validate!`'s own "undeclared state"
|
|
36
|
+
# check existed only because they could).
|
|
37
|
+
#
|
|
38
|
+
# `starts_on`/`ends_on` are NOT unified into this — verified
|
|
39
|
+
# against the real corpus rather than assumed: Settlement's own
|
|
40
|
+
# `ends_on "TransferSettled"` names an event NONE of its own
|
|
41
|
+
# transitions ever handle (`Transfer.Settle`'s own emission, a
|
|
42
|
+
# full step downstream of the transition that dispatches it),
|
|
43
|
+
# so "the terminal state's own event" is not a fact the
|
|
44
|
+
# transition graph carries — deriving it would either be wrong
|
|
45
|
+
# for this exact corpus member or need a second new word to
|
|
46
|
+
# cover the case, which is not less vocabulary than keeping the
|
|
47
|
+
# one that already says it correctly.
|
|
48
|
+
#
|
|
49
|
+
# EXPANDS IMMEDIATELY, unlike `Lifecycle#transition` (which
|
|
50
|
+
# defers to `Behaviour::Lifecycle#expand`, called at emission
|
|
51
|
+
# time) — `ProcessManager`'s own IR constructor takes `states:`/
|
|
52
|
+
# `handlers:` exactly as it always has, so the runtime
|
|
53
|
+
# (`Behaviour::ProcessManager`, `SagaInterpreter`, saga
|
|
54
|
+
# persistence/rehydration) needs no change at all: what changed
|
|
55
|
+
# is how the DECLARATION reaches that same shape, not the shape
|
|
56
|
+
# a real run ever sees or persists.
|
|
57
|
+
# RENAMED FROM `transition` — item #13's full metaprogrammed
|
|
58
|
+
# dispatch (slice 4c). Not bootstrap-reachable (checked
|
|
59
|
+
# directly — no core/attached chapter declares a ProcessManager
|
|
60
|
+
# of its own).
|
|
61
|
+
def transition_impl(mapping, &block)
|
|
62
|
+
mapping = mapping.dup
|
|
63
|
+
from = mapping.delete(:from)
|
|
64
|
+
|
|
65
|
+
# ALWAYS REQUIRED, unlike `Lifecycle#transition`'s own `from:`
|
|
66
|
+
# — an aggregate's unconstrained transition is admitted from
|
|
67
|
+
# ANY current state (`Behaviour::Lifecycle#applies_from?`
|
|
68
|
+
# returns true for a nil `from`), a reading `SagaInterpreter#
|
|
69
|
+
# advance_saga`'s own admission check does not share: it tests
|
|
70
|
+
# `instance[:state] == handler.from_state` by plain equality,
|
|
71
|
+
# nothing softer. Leaving that check unchanged (this slice's
|
|
72
|
+
# own scope decision — see the class-level comment on why the
|
|
73
|
+
# runtime stays untouched) means an unconstrained PM
|
|
74
|
+
# transition would build cleanly and then match no instance
|
|
75
|
+
# ever, silently — refused here instead, at the one point that
|
|
76
|
+
# can still see the mistake.
|
|
77
|
+
if from.nil?
|
|
78
|
+
raise InvalidProcessManager,
|
|
79
|
+
"#{@name}'s transition #{mapping.inspect} names no from: — a process manager's own " \
|
|
80
|
+
"admission checks a saga instance's CURRENT state exactly, so a transition with no " \
|
|
81
|
+
"from: would match no instance ever, silently"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
handler = HandlerBuilder.new
|
|
85
|
+
handler.instance_eval(&block) if block
|
|
86
|
+
|
|
87
|
+
mapping.each do |event_type, target|
|
|
88
|
+
state_transition = StateTransition.new(target: target, from: from)
|
|
89
|
+
expand(event_type.to_s, state_transition, handler.dispatches).each { |row| @handlers << row }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def build
|
|
94
|
+
validate!
|
|
95
|
+
|
|
96
|
+
ProcessManager.new(
|
|
97
|
+
name: @name,
|
|
98
|
+
correlates_by: @correlates_by,
|
|
99
|
+
starts_on: @starts_on,
|
|
100
|
+
ends_on: @ends_on,
|
|
101
|
+
states: derived_states,
|
|
102
|
+
handlers: @handlers
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.build(name, &block)
|
|
107
|
+
builder = new(name)
|
|
108
|
+
builder.instance_eval(&block) if block
|
|
109
|
+
builder.build
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# ONE DECLARED TRANSITION IS SEVERAL ROWS when `from` names more
|
|
115
|
+
# than one source state — `Behaviour::Lifecycle#expand`'s own
|
|
116
|
+
# comment, the identical fan-out, one level over: a
|
|
117
|
+
# `ProcessManagerHandler` only ever carries a single `from_state`,
|
|
118
|
+
# so a `from: [...]` transition mints one row per source, each
|
|
119
|
+
# carrying the SAME dispatches.
|
|
120
|
+
def expand(event_type, transition, dispatches)
|
|
121
|
+
sources = transition.from.nil? ? [nil] : Array(transition.from)
|
|
122
|
+
|
|
123
|
+
sources.map do |source|
|
|
124
|
+
ProcessManagerHandler.new(
|
|
125
|
+
event_type: event_type,
|
|
126
|
+
from_state: source.to_s,
|
|
127
|
+
to_state: transition.target,
|
|
128
|
+
dispatches: dispatches
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# DERIVED, not declared (S7) — every state this procedure ever
|
|
134
|
+
# runs on is already named by some transition's own `from_state`
|
|
135
|
+
# or `to_state`; a state nothing transitions into or out of is
|
|
136
|
+
# not a state this procedure has, the same reading
|
|
137
|
+
# `Behaviour::Lifecycle#states` already gives an aggregate's own
|
|
138
|
+
# field. FIRST-SEEN ORDER, walking declaration order — `begin_
|
|
139
|
+
# saga`'s own `pm.states.first` is what a fresh instance starts
|
|
140
|
+
# in, so the order has to survive the derivation, not just the
|
|
141
|
+
# membership.
|
|
142
|
+
def derived_states
|
|
143
|
+
@handlers.flat_map { |h| [h.from_state, h.to_state] }.reject(&:empty?).uniq
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def validate!
|
|
147
|
+
raise InvalidProcessManager, "#{@name} declares no correlates_by — " \
|
|
148
|
+
"nothing would tie its events to one instance" unless @correlates_by
|
|
149
|
+
|
|
150
|
+
# THE FIELD, NAMED — never the value object that carries it. A bare
|
|
151
|
+
# `correlates_by :end_to_end` reads whatever the payload holds under
|
|
152
|
+
# that key AS the correlation key, and what a non-scalar key even
|
|
153
|
+
# is stays open (the object itself? its serialised text?).
|
|
154
|
+
# Requiring the dotted spelling —
|
|
155
|
+
# `:"end_to_end.value"` — makes every correlates_by name a scalar
|
|
156
|
+
# by construction, the same discipline `identified_by` already
|
|
157
|
+
# holds a head to. This is a syntactic check, not a type check: it
|
|
158
|
+
# does not know or care whether the field IS a value object, only
|
|
159
|
+
# that the declaration cannot leave that question open.
|
|
160
|
+
raise InvalidProcessManager, "#{@name} correlates_by #{@correlates_by.inspect}, which names a whole " \
|
|
161
|
+
"field rather than one of its scalars — say which one, e.g. " \
|
|
162
|
+
"#{@correlates_by}.value" unless @correlates_by.to_s.include?(".")
|
|
163
|
+
|
|
164
|
+
raise InvalidProcessManager, "#{@name} declares no starts_on — " \
|
|
165
|
+
"nothing would ever begin it" if @starts_on.to_s.empty?
|
|
166
|
+
|
|
167
|
+
raise InvalidProcessManager, "#{@name} declares no transitions — " \
|
|
168
|
+
"it would start and then ignore every event" if @handlers.empty?
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
class HandlerBuilder
|
|
172
|
+
GRAMMAR_CONTEXT = "Handler"
|
|
173
|
+
|
|
174
|
+
attr_reader :dispatches
|
|
175
|
+
|
|
176
|
+
include WordGate
|
|
177
|
+
|
|
178
|
+
def initialize = @dispatches = []
|
|
179
|
+
|
|
180
|
+
# THE COMMAND ITSELF (ADR 0025, "events and reactions" — command
|
|
181
|
+
# references become first-class), same shape and same reasons
|
|
182
|
+
# as `PolicyBuilder#trigger`'s own header — bare constant live,
|
|
183
|
+
# quoted text only under shadow-parsing (S0a's bridge; frozen
|
|
184
|
+
# era text still writes `dispatch "Banking::Account.Debit"`).
|
|
185
|
+
#
|
|
186
|
+
# RENAMED FROM `dispatch` — item #13's full metaprogrammed
|
|
187
|
+
# dispatch (slice 4), same reasoning as trigger_impl above.
|
|
188
|
+
def dispatch_impl(command_ref, with: nil)
|
|
189
|
+
if command_ref.is_a?(::String) && !MetaValidator.shadow_parsing?
|
|
190
|
+
raise InvalidProcessManager,
|
|
191
|
+
"dispatch #{command_ref.inspect} is quoted text — give the bare command constant " \
|
|
192
|
+
"instead, e.g. dispatch Account::Debit"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
DispatchSpec.new(
|
|
196
|
+
command_name: Naming.command_ref(command_ref),
|
|
197
|
+
with_spec: (with || {}).to_a
|
|
198
|
+
).tap do |dispatch|
|
|
199
|
+
dispatch.instance_variable_set(:@projection_declared, !with.nil?)
|
|
200
|
+
@dispatches << dispatch
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
require_relative "word_gate"
|
|
2
|
+
module Hecks
|
|
3
|
+
module Bluebook
|
|
4
|
+
module DSL
|
|
5
|
+
class QueryBuilder
|
|
6
|
+
GRAMMAR_CONTEXT = "Query"
|
|
7
|
+
|
|
8
|
+
include AttributeCollector
|
|
9
|
+
include QuerySpecification::Common::DSL
|
|
10
|
+
include WordGate
|
|
11
|
+
|
|
12
|
+
def initialize(name)
|
|
13
|
+
@name = name
|
|
14
|
+
@wheres = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def description(value) = @description = value
|
|
18
|
+
|
|
19
|
+
# A query parameter naming another aggregate's own identity
|
|
20
|
+
# (Card.Active's own `Board`, filtering to one board's cards) —
|
|
21
|
+
# just a plain attribute typed as a reference,
|
|
22
|
+
# AttributeCollector#attribute already handling an Reference
|
|
23
|
+
# exactly like any other. No "acts on itself" case to
|
|
24
|
+
# distinguish here the way a command's own reference_to has —
|
|
25
|
+
# a query has no root of its own to act on, only parameters.
|
|
26
|
+
# RENAMED FROM `reference_to` — item #13's full metaprogrammed
|
|
27
|
+
# dispatch (slice 4b). Bootstrap-reachable, in
|
|
28
|
+
# GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
|
|
29
|
+
def reference_to_impl(type, as: nil, optional: false)
|
|
30
|
+
target = Naming.demodulise(type)
|
|
31
|
+
attribute_impl(as || default_reference_name(target), Reference.new(target), optional: optional)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def build
|
|
35
|
+
seal_cursor
|
|
36
|
+
Query.new(
|
|
37
|
+
name: @name,
|
|
38
|
+
description: @description,
|
|
39
|
+
attributes: attributes,
|
|
40
|
+
wheres: @wheres,
|
|
41
|
+
order_by: @order_by,
|
|
42
|
+
limit: @limit,
|
|
43
|
+
offset: @offset,
|
|
44
|
+
cursor: @cursor,
|
|
45
|
+
authorization: @authorization,
|
|
46
|
+
null_semantics: @null_semantics,
|
|
47
|
+
inspection: @inspection
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.build(name, owner_attributes: [], &block)
|
|
52
|
+
builder = new(name)
|
|
53
|
+
builder.instance_eval(&block) if block
|
|
54
|
+
# `send`, not a public call — this isn't a bluebook DSL word (no
|
|
55
|
+
# author ever writes `derive_from_owner!` inside a query block),
|
|
56
|
+
# just internal wiring between this class method and the instance
|
|
57
|
+
# it just built. Kept private below so syntax_conformance_spec's
|
|
58
|
+
# own "every word QueryBuilder answers is declared" check doesn't
|
|
59
|
+
# mistake it for one.
|
|
60
|
+
builder.send(:derive_from_owner!, owner_attributes, block) if block
|
|
61
|
+
builder.build
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# A block parameter names one of the OWNER's (the aggregate or entity
|
|
67
|
+
# this query is declared on) own already-declared attributes —
|
|
68
|
+
# `query "ForDecision" do |decision| where decision: :decision end`
|
|
69
|
+
# on Submission, whose own `attribute :decision, DecisionRef` already
|
|
70
|
+
# says what `decision` is. Restating `attribute :decision, DecisionRef`
|
|
71
|
+
# a second time inside the query was pure duplication; this derives
|
|
72
|
+
# the same type from the owner instead. Only fills in a name the block
|
|
73
|
+
# body did NOT already declare explicitly (checked AFTER instance_eval
|
|
74
|
+
# runs, so an existing bluebook still spelling it out both ways keeps
|
|
75
|
+
# working unchanged — this only removes the need to, never refuses
|
|
76
|
+
# the choice to). A block parameter matching nothing on the owner is
|
|
77
|
+
# left alone; MetaValidator's own unresolved-attribute check names it,
|
|
78
|
+
# the same way a typo in a hand-written `attribute` call already does.
|
|
79
|
+
def derive_from_owner!(owner_attributes, block)
|
|
80
|
+
block.parameters.each do |kind, param_name|
|
|
81
|
+
next unless %i[req opt].include?(kind)
|
|
82
|
+
next if attributes.any? { |a| a.name == param_name }
|
|
83
|
+
|
|
84
|
+
owner_attr = owner_attributes.find { |a| a.name == param_name }
|
|
85
|
+
next unless owner_attr
|
|
86
|
+
|
|
87
|
+
# `Attribute.new` DIRECTLY, not the public `attribute(...)` DSL
|
|
88
|
+
# entry — `owner_attr.type` is already SPELLED (a demodulised
|
|
89
|
+
# String, `Attribute#spell`'s own doing), not a bareword the
|
|
90
|
+
# bluebook author typed, so it must not run through
|
|
91
|
+
# `AttributeCollector#attribute`'s quoted-type refusal (ADR
|
|
92
|
+
# 0025, "Attributes") — that refusal exists for DSL source
|
|
93
|
+
# text, not for a type already resolved elsewhere and copied.
|
|
94
|
+
attributes << Attribute.new(name: param_name, type: owner_attr.type, optional: owner_attr.optional?)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# `cursor` parses, round-trips through the IR, and is read by nothing —
|
|
99
|
+
# no interpreter (Memory, Sqlite, Postgres) ever applies it. Refusing
|
|
100
|
+
# it here, rather than deleting the word, keeps the declared syntax
|
|
101
|
+
# honest (the language still knows the shape) while refusing to let a
|
|
102
|
+
# bluebook author believe cursor-based pagination actually happens.
|
|
103
|
+
def seal_cursor
|
|
104
|
+
return unless @cursor
|
|
105
|
+
|
|
106
|
+
raise Malformed,
|
|
107
|
+
"#{@name} declares cursor, but no interpreter implements cursor " \
|
|
108
|
+
"pagination — use limit/offset instead"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
require_relative "word_gate"
|
|
2
|
+
module Hecks
|
|
3
|
+
module Bluebook
|
|
4
|
+
module DSL
|
|
5
|
+
class ReadModelBuilder
|
|
6
|
+
GRAMMAR_CONTEXT = "ReadModel"
|
|
7
|
+
|
|
8
|
+
include QuerySpecification::Common::DSL
|
|
9
|
+
include WordGate
|
|
10
|
+
|
|
11
|
+
def initialize(name)
|
|
12
|
+
@name = name
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def description(value)
|
|
16
|
+
# moved to the language: ProjectionText / purpose, on Projection.Declare
|
|
17
|
+
@description = value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# RENAMED FROM `reference_to` — item #13's full metaprogrammed
|
|
21
|
+
# dispatch (slice 4b). Bootstrap-reachable, in
|
|
22
|
+
# GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
|
|
23
|
+
def reference_to_impl(type, as: nil)
|
|
24
|
+
raise Malformed, "#{@name} already has a projection reference" if @reference_target
|
|
25
|
+
|
|
26
|
+
@reference_target = Naming.demodulise(type)
|
|
27
|
+
@reference_name = (as || Naming.snake(@reference_target)).to_sym
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Order-independent. `many:` is decided by comparing the included type
|
|
31
|
+
# against the reference target, so this used to REFUSE an include
|
|
32
|
+
# declared before the reference — a rule guarding an implementation
|
|
33
|
+
# limitation rather than a truth about read models. The includes are
|
|
34
|
+
# collected raw and resolved at build, when the reference is known, so
|
|
35
|
+
# there is no rule left to enforce.
|
|
36
|
+
# RENAMED FROM `include`/`group_by` — item #13's full
|
|
37
|
+
# metaprogrammed dispatch (slice 4c). `include` IS bootstrap-
|
|
38
|
+
# reachable (every core chapter's own `read_model` names which
|
|
39
|
+
# aggregates it includes with it — a first grep dismissed this
|
|
40
|
+
# as `Module#include` noise and was wrong; the cold-boot test
|
|
41
|
+
# after this rename caught it directly), so it's in
|
|
42
|
+
# BOOTSTRAP_CALLS_FALLBACK; `group_by` is not (no core read_model
|
|
43
|
+
# groups). The class-level `include WordGate` this file's own
|
|
44
|
+
# class body uses is `Module#include`, a different receiver,
|
|
45
|
+
# unaffected by renaming this INSTANCE method either way.
|
|
46
|
+
def include_impl(type, as: nil)
|
|
47
|
+
@includes ||= []
|
|
48
|
+
@includes << [Naming.demodulise(type), as]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# NAMES which of the eligible head's own fields to nest its rows
|
|
52
|
+
# under — one level per field, the leaf being that row with the
|
|
53
|
+
# named fields removed (they're already spent, as the keys that
|
|
54
|
+
# reached it). The same "exactly one many-side head" rule
|
|
55
|
+
# `seal_query_options` already enforces for where/order_by/etc
|
|
56
|
+
# applies here too (`seal_group_by`) — grouping is a question
|
|
57
|
+
# about ONE collection's own rows, same as those are.
|
|
58
|
+
def group_by_impl(*fields)
|
|
59
|
+
# Hash rows, `{field:}`, not bare symbols — same shape
|
|
60
|
+
# `aggregate_heads` already uses for exactly the reason it
|
|
61
|
+
# does: the language's own self-hosted grammar (`projection
|
|
62
|
+
# .bluebook`'s `GroupByField`) has to have SOMETHING to read a
|
|
63
|
+
# `field:` off of when `Judge` walks this list generically: a
|
|
64
|
+
# bare `Symbol` has no attribute of its own to read.
|
|
65
|
+
@group_by = fields.map { |field| { field: field.to_sym } }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# `count` -- a bare row count over the eligible many-side head's
|
|
69
|
+
# own rows (after `where`/`order_by`/`limit`/`offset` apply, the
|
|
70
|
+
# same rows `group_by` itself would nest) -- ANSWERS "how many
|
|
71
|
+
# match", not "which ones". A sibling REDUCTION to `group_by`,
|
|
72
|
+
# not a filter: `seal_aggregation` refuses combining it with
|
|
73
|
+
# `group_by` or with `median`, the same "exactly one many-side
|
|
74
|
+
# head" rule `seal_group_by` already enforces for the same
|
|
75
|
+
# reason -- a bare marker, so `@count` is left unset (nil, not
|
|
76
|
+
# false) rather than defaulted, matching the "ABSENT is not
|
|
77
|
+
# EMPTY" reading `Lifecycle`'s own optional fields already rely
|
|
78
|
+
# on for the Judge's setter dispatch (Behaviour::ReadModel#
|
|
79
|
+
# count?, ReadModelInterpreter#aggregation_target).
|
|
80
|
+
# `count` — item #13's full metaprogrammed dispatch, slice 1
|
|
81
|
+
# (whole-project table-unification survey): the ONLY Keyword row
|
|
82
|
+
# filling `count` — a bare marker, now stored as literal `true`
|
|
83
|
+
# by `GenericDispatch` off that same table fact.
|
|
84
|
+
|
|
85
|
+
# `median(field)` -- the median VALUE of one numeric field
|
|
86
|
+
# across the eligible many-side head's own rows. EVEN COUNT: the
|
|
87
|
+
# average of its two middle values (the standard definition,
|
|
88
|
+
# not "the lower of the two") -- see
|
|
89
|
+
# Runtime::ReadModelInterpreter#median for where that lands and
|
|
90
|
+
# is documented for a caller. `field` must name a numeric
|
|
91
|
+
# attribute (a bare numeric primitive, or a value object
|
|
92
|
+
# carrying one) -- checked once, at read time, by
|
|
93
|
+
# ReadModelInterpreter#aggregation_target, the same place
|
|
94
|
+
# `group_by`'s own field names are checked.
|
|
95
|
+
# `median` — item #13's full metaprogrammed dispatch, slice 1:
|
|
96
|
+
# same shape as `count`, above (a bare, kind-driven coerce-and-
|
|
97
|
+
# assign).
|
|
98
|
+
|
|
99
|
+
# `reference_to` is now OPTIONAL — a read model with no root is a
|
|
100
|
+
# BULK one: every `include`d head reads its own aggregate whole
|
|
101
|
+
# (no FK match against a root that doesn't exist), and dispatch
|
|
102
|
+
# takes no id argument at all. This used to be REQUIRED, on the
|
|
103
|
+
# assumption a read model was always "one root record's own
|
|
104
|
+
# cross-aggregate view" — true of every real corpus report so
|
|
105
|
+
# far, but not a truth about read models themselves: `group_by`'s
|
|
106
|
+
# own real use (nesting an aggregate's OWN whole table by its own
|
|
107
|
+
# field values) has no root to speak of. Still needs to describe
|
|
108
|
+
# SOMETHING — zero includes AND no reference is refused.
|
|
109
|
+
def build
|
|
110
|
+
raise Malformed,
|
|
111
|
+
"#{@name} needs an aggregate-head reference or at least one include" if !@reference_target && Array(@includes).empty?
|
|
112
|
+
|
|
113
|
+
Array(@includes).each do |target, as|
|
|
114
|
+
add_aggregate_head(target, as, many: target != @reference_target)
|
|
115
|
+
end
|
|
116
|
+
seal_query_options
|
|
117
|
+
seal_group_by
|
|
118
|
+
seal_aggregation
|
|
119
|
+
seal_cursor
|
|
120
|
+
ReadModel.new(name: @name, description: @description, reference_name: @reference_name,
|
|
121
|
+
reference_target: @reference_target, aggregate_heads: @aggregate_heads || [],
|
|
122
|
+
wheres: @wheres || [], order_by: @order_by, limit: @limit, offset: @offset,
|
|
123
|
+
cursor: @cursor,
|
|
124
|
+
authorization: @authorization, null_semantics: @null_semantics,
|
|
125
|
+
inspection: @inspection, group_by: @group_by || [],
|
|
126
|
+
count: @count, median_field: @median_field)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def self.build(name, &block)
|
|
130
|
+
builder = new(name)
|
|
131
|
+
builder.instance_eval(&block) if block
|
|
132
|
+
builder.build
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
# where/order_by/limit/offset/authorize's tenant all apply to exactly
|
|
138
|
+
# one collection — the single `include`d aggregate whose head is
|
|
139
|
+
# "many" (the "one" side, the reference target itself, is a single
|
|
140
|
+
# row; ordering, paging, or tenant-scoping one row means nothing). A
|
|
141
|
+
# read model with zero many-heads has nothing for them to filter ;
|
|
142
|
+
# one with several has no way to say WHICH of several unrelated
|
|
143
|
+
# collections a caller meant — so both refuse here rather than
|
|
144
|
+
# silently applying to an arbitrary one. `authorize`'s tenant counts
|
|
145
|
+
# here too — TenantScope enforces it against this same head, so an
|
|
146
|
+
# ambiguous target is exactly as unusable as it is for the others.
|
|
147
|
+
def seal_query_options
|
|
148
|
+
declared = @wheres&.any? || @order_by || @limit || @offset || @authorization&.tenant
|
|
149
|
+
return unless declared
|
|
150
|
+
|
|
151
|
+
many = Array(@aggregate_heads).count { |head| head[:many] }
|
|
152
|
+
return if many == 1
|
|
153
|
+
|
|
154
|
+
raise Malformed,
|
|
155
|
+
"#{@name} declares where/order_by/limit/offset but includes #{many} many-side " \
|
|
156
|
+
"aggregates, not exactly one — these options apply to a single collection; " \
|
|
157
|
+
"name which one by including only it, or drop the options"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Same shape as `seal_query_options`, same reason — `group_by`
|
|
161
|
+
# answers a question about ONE collection's own rows, so zero or
|
|
162
|
+
# several many-side heads leaves it with no unambiguous target.
|
|
163
|
+
def seal_group_by
|
|
164
|
+
return unless @group_by&.any?
|
|
165
|
+
|
|
166
|
+
many = Array(@aggregate_heads).count { |head| head[:many] }
|
|
167
|
+
return if many == 1
|
|
168
|
+
|
|
169
|
+
raise Malformed,
|
|
170
|
+
"#{@name} declares group_by but includes #{many} many-side " \
|
|
171
|
+
"aggregates, not exactly one — group_by nests a single collection's " \
|
|
172
|
+
"own rows; name which one by including only it"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# `count`/`median` are the OTHER two reductions a read model may
|
|
176
|
+
# declare over its one eligible collection — same "exactly one
|
|
177
|
+
# many-side head" rule as `seal_group_by`, plus a rule
|
|
178
|
+
# `seal_group_by` doesn't need: a read model reports ONE shape,
|
|
179
|
+
# so `count` and `median` cannot both be declared, and neither
|
|
180
|
+
# may combine with `group_by` (nesting rows and reducing them to
|
|
181
|
+
# a scalar are answers to different questions ; a caller asking
|
|
182
|
+
# "how many, nested by state" is not a shape this read model
|
|
183
|
+
# produces today — a real, deliberate scope limit, not an
|
|
184
|
+
# oversight, the same discipline the rootless model's own
|
|
185
|
+
# "each head reads independently" limit already documents).
|
|
186
|
+
def seal_aggregation
|
|
187
|
+
return unless @count || @median_field
|
|
188
|
+
|
|
189
|
+
if @count && @median_field
|
|
190
|
+
raise Malformed,
|
|
191
|
+
"#{@name} declares both count and median — a read model reports " \
|
|
192
|
+
"one shape; choose one"
|
|
193
|
+
end
|
|
194
|
+
if @group_by&.any?
|
|
195
|
+
raise Malformed,
|
|
196
|
+
"#{@name} declares count/median together with group_by — a read " \
|
|
197
|
+
"model reports one shape; choose one"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
many = Array(@aggregate_heads).count { |head| head[:many] }
|
|
201
|
+
return if many == 1
|
|
202
|
+
|
|
203
|
+
raise Malformed,
|
|
204
|
+
"#{@name} declares count/median but includes #{many} many-side " \
|
|
205
|
+
"aggregates, not exactly one — count/median reduce a single " \
|
|
206
|
+
"collection's own rows; name which one by including only it"
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# `cursor` parses, round-trips through the IR, and is read by nothing —
|
|
210
|
+
# no interpreter (Memory, Sqlite, Postgres) ever applies it. Refusing
|
|
211
|
+
# it here, rather than deleting the word, keeps the declared syntax
|
|
212
|
+
# honest (the language still knows the shape) while refusing to let a
|
|
213
|
+
# bluebook author believe cursor-based pagination actually happens.
|
|
214
|
+
def seal_cursor
|
|
215
|
+
return unless @cursor
|
|
216
|
+
|
|
217
|
+
raise Malformed,
|
|
218
|
+
"#{@name} declares cursor, but no interpreter implements cursor " \
|
|
219
|
+
"pagination — use limit/offset instead"
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def add_aggregate_head(type, name, many:)
|
|
223
|
+
@aggregate_heads ||= []
|
|
224
|
+
target = Naming.demodulise(type)
|
|
225
|
+
output = (name || (many ? Naming.plural(Naming.snake(target)) : Naming.snake(target))).to_sym
|
|
226
|
+
raise Malformed, "#{@name} already projects #{output}" if @aggregate_heads.any? { |head| head[:as] == output }
|
|
227
|
+
|
|
228
|
+
@aggregate_heads << { aggregate: target, as: output, many: many }
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|