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,559 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Bluebook
|
|
3
|
+
module MetaValidator
|
|
4
|
+
# Offers every declaration in a built bluebook to the meta-domain.
|
|
5
|
+
#
|
|
6
|
+
# This used to be one hand-written branch per category, and the cost of that
|
|
7
|
+
# shape was fourteen verbs the language declared and the judge never offered
|
|
8
|
+
# — among them `Command.Argument` and `ValueObject.Field`, so a command's own
|
|
9
|
+
# arguments and a value object's own fields were NEVER judged. Every rule
|
|
10
|
+
# hanging off them was decoration. Nothing went red, because a branch that
|
|
11
|
+
# does not exist cannot fail.
|
|
12
|
+
#
|
|
13
|
+
# So there are no branches. The judge WALKS: it reads the plan the language
|
|
14
|
+
# makes of itself (Plan), and for each node offers the creating command, then
|
|
15
|
+
# each list through the command that appends to it, then each child. A verb
|
|
16
|
+
# in the plan with no offer is now impossible — there is no branch left in
|
|
17
|
+
# which to forget one, and spec/judge_coverage_spec holds it to that.
|
|
18
|
+
#
|
|
19
|
+
# What is NOT uniform lives in Readings, and only where the IR's SHAPE
|
|
20
|
+
# differs from the language's. Naming differences do not appear at all: the
|
|
21
|
+
# language spells its fields as the IR spells them.
|
|
22
|
+
#
|
|
23
|
+
# Only the DISPATCH half of the round trip lives here. Reconstruction is the
|
|
24
|
+
# experiment's business ; judging does not need it.
|
|
25
|
+
class Judge
|
|
26
|
+
include Readings
|
|
27
|
+
|
|
28
|
+
# Children offered BEFORE the parent's own lists. An attribute's type is
|
|
29
|
+
# offered as the id of the thing it names, so both the value objects and the
|
|
30
|
+
# entities have to exist before any attribute names one.
|
|
31
|
+
EAGER_CHILDREN = { "Aggregate" => %w[Entity ValueObject] }.freeze
|
|
32
|
+
|
|
33
|
+
# Categories an ENTITY declares as well as an aggregate. The IR reuses
|
|
34
|
+
# Command and Query for a piece's own commands and queries, so the
|
|
35
|
+
# language reuses Command and Query — and the plan cannot express a second
|
|
36
|
+
# parent, because a category's parent is derived from the one `*_id` argument
|
|
37
|
+
# its creating command carries. This says the other edge out loud.
|
|
38
|
+
WITHIN_ENTITY = %w[Command Query].freeze
|
|
39
|
+
|
|
40
|
+
attr_reader :refusals
|
|
41
|
+
|
|
42
|
+
# THE RECORDS SURVIVE THE VERDICT.
|
|
43
|
+
#
|
|
44
|
+
# Judging a bluebook and HOLDING one differ by exactly this: whether anyone
|
|
45
|
+
# keeps the runtime the declarations were dispatched into. Nobody did, so
|
|
46
|
+
# `spec/round_trip_spec` reached the records by `Judge.allocate` and four
|
|
47
|
+
# `instance_variable_set` calls. Reading the chapter back is the point now,
|
|
48
|
+
# not a curiosity, so the runtime is simply readable.
|
|
49
|
+
attr_reader :runtime
|
|
50
|
+
|
|
51
|
+
def initialize(bluebook)
|
|
52
|
+
@bluebook = bluebook
|
|
53
|
+
@refusals = []
|
|
54
|
+
@runtime = MetaValidator.fresh_runtime
|
|
55
|
+
@plan = Plan.for(MetaValidator.grammar_registry)
|
|
56
|
+
judge!
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# ABSENT is not EMPTY. The rules read "if you declare it, declare
|
|
62
|
+
# something" — a description never given is legal. Passing "" for a nil
|
|
63
|
+
# turns every one of them into "you must declare it".
|
|
64
|
+
#
|
|
65
|
+
# An Integer stays an Integer : RowCount declares `attribute :value,
|
|
66
|
+
# Integer`, so stringifying a member count fails the type gate rather than
|
|
67
|
+
# feeding the rule it was meant to feed.
|
|
68
|
+
def v(text)
|
|
69
|
+
return nil if text.nil?
|
|
70
|
+
return { value: text } if text.is_a?(Integer)
|
|
71
|
+
|
|
72
|
+
{ value: text.to_s }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# A REFERENCE IS AN ID, AND AN ID IS A SCALAR.
|
|
76
|
+
#
|
|
77
|
+
# Every other field goes to the meta-domain as a one-field value object,
|
|
78
|
+
# because that is what it is. A reference is not: it carries the id of a
|
|
79
|
+
# head, and wrapping an id in an object was the language saying `{value:
|
|
80
|
+
# "Banking::Customer"}` where it meant `"Banking::Customer"`. The plan
|
|
81
|
+
# answers which arguments those are, read from the language's own IR, so
|
|
82
|
+
# nothing here needs to know the names.
|
|
83
|
+
def carried(plan, verb, argument, value)
|
|
84
|
+
return v(value) unless plan && verb && plan.references?(verb, argument)
|
|
85
|
+
|
|
86
|
+
value
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def args(pairs) = pairs.reject { |_, value| value.nil? }
|
|
90
|
+
|
|
91
|
+
def offer(label)
|
|
92
|
+
yield
|
|
93
|
+
rescue Runtime::GivenNotMet, Runtime::InvariantViolation,
|
|
94
|
+
Runtime::TypeMismatch, Runtime::NotFound => e
|
|
95
|
+
# NotFound is a VERDICT, not noise. An attribute's type is a reference to
|
|
96
|
+
# its value object, so "no ValueObject with id …" IS the rule `attributes
|
|
97
|
+
# must use value-object types` refusing.
|
|
98
|
+
@refusals << "#{label}: #{e.message}"
|
|
99
|
+
rescue Runtime::UnknownVerb
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def send_to(verb, label, to: nil, **payload)
|
|
104
|
+
offer(label) { @runtime.dispatch(verb, to: to, with: args(payload)) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def judge!
|
|
108
|
+
declare_node("Bluebook", @bluebook, nil, 0)
|
|
109
|
+
detail_node("Bluebook", @bluebook, nil, 0)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# DECLARED BEFORE DETAILED, for every set of siblings.
|
|
113
|
+
#
|
|
114
|
+
# A node used to be offered whole — declared, then its lists, then its
|
|
115
|
+
# children — one sibling at a time. Which means an aggregate's attributes
|
|
116
|
+
# were offered before its later siblings existed, and an attribute that
|
|
117
|
+
# POINTS AT another aggregate could only resolve if that aggregate happened
|
|
118
|
+
# to be declared earlier in the file. Banking survives on luck: Customer is
|
|
119
|
+
# written above Account.
|
|
120
|
+
#
|
|
121
|
+
# So siblings are declared in one pass and detailed in a second. It is the
|
|
122
|
+
# same ordering the walk already used one level down — value objects before
|
|
123
|
+
# the attributes that name them — lifted to the level above, and it is what
|
|
124
|
+
# lets a reference be a REFERENCE rather than a string nobody can check.
|
|
125
|
+
def declare_node(category, node, parent_id, index, extra = {}, receiver: nil)
|
|
126
|
+
plan = @plan.category(category)
|
|
127
|
+
return unless plan
|
|
128
|
+
|
|
129
|
+
declare(plan, category, node, identify(category, parent_id, node, index), parent_id, index, extra)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def detail_node(category, node, parent_id, index, extra = {}, receiver: nil)
|
|
133
|
+
plan = @plan.category(category)
|
|
134
|
+
return unless plan
|
|
135
|
+
|
|
136
|
+
id = identify(category, parent_id, node, index)
|
|
137
|
+
# `extra` is the CUMULATIVE identity of every entity-owned
|
|
138
|
+
# ancestor above this node (Handler's own `event_type` AND
|
|
139
|
+
# ProcessManager's own `bluebook`/`name`, by the time Dispatch
|
|
140
|
+
# is reached — S17, ADR 0026's two-level chain). `identity`
|
|
141
|
+
# adds THIS node's own on top — computed for EVERY category,
|
|
142
|
+
# entity-owned or not, because a node need not be entity-owned
|
|
143
|
+
# itself to OWN one (ValueObject isn't, and Member still needs
|
|
144
|
+
# its `aggregate:`/`name:`) — it is simply what THIS node's own
|
|
145
|
+
# `identified_by` resolves to, the same fields `identify` two
|
|
146
|
+
# lines up already derives the joined id FROM.
|
|
147
|
+
#
|
|
148
|
+
# `own` is the SUBSET actually spent on a dispatch payload —
|
|
149
|
+
# only when THIS category is itself entity-owned, since an
|
|
150
|
+
# ordinary category (Command's own "Rule"/"Argument", offered
|
|
151
|
+
# through the SAME `extra` SLOT for a DIFFERENT reason, see
|
|
152
|
+
# `within_entity` below) locates the record it attaches to
|
|
153
|
+
# through the parent id `id:` already carries, and merging
|
|
154
|
+
# unrecognized `aggregate:`/`entity_id:` into THEIR payload
|
|
155
|
+
# would have the runtime refuse them for an argument they
|
|
156
|
+
# never declared.
|
|
157
|
+
identity = extra.merge(node_identity(plan, category, node, index, parent_id))
|
|
158
|
+
receiver ||= { aggregate: id, entities: [] }
|
|
159
|
+
eager, later = children_of(category).partition { |child| eager?(category, child) }
|
|
160
|
+
|
|
161
|
+
eager.each { |child| walk_all(child, node, id, entity_child_extra(child, identity), receiver: receiver) }
|
|
162
|
+
setters(plan, category, node, receiver)
|
|
163
|
+
appends(plan, category, node, receiver)
|
|
164
|
+
later.each { |child| walk_all(child, node, id, entity_child_extra(child, identity), receiver: receiver) }
|
|
165
|
+
within_entity(category, node, id, parent_id)
|
|
166
|
+
nest_entities(category, node, id, parent_id)
|
|
167
|
+
sealers(plan, category, receiver)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# WHAT A CHILD'S OWN `extra` STARTS FROM. An entity-owned child's
|
|
171
|
+
# own dotted dispatch needs every ANCESTOR's identity, which is
|
|
172
|
+
# exactly `identity` — already accumulated one level at a time by
|
|
173
|
+
# `detail_node` itself (regardless of whether each ancestor is
|
|
174
|
+
# ITSELF entity-owned — ValueObject contributes its own `aggregate:
|
|
175
|
+
# `/`name:` to Member's payload despite being an ordinary top-
|
|
176
|
+
# level category), so there is nothing left to re-derive here. An
|
|
177
|
+
# ordinary child (one with a real top-level aggregate of its own
|
|
178
|
+
# to dispatch a bare verb into) needs none of it.
|
|
179
|
+
def entity_child_extra(child, identity)
|
|
180
|
+
@plan.category(child)&.entity_owned ? identity : {}
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# ONE NODE'S OWN IDENTITY, read off its own declaration — S17,
|
|
184
|
+
# ADR 0026. Three cases, the same three `identify`/`identity_part`
|
|
185
|
+
# already resolve one level up, unified here because a chain now
|
|
186
|
+
# walks more than one level (Handler -> Dispatch) and each level
|
|
187
|
+
# needs the SAME three answered about itself, not just the first:
|
|
188
|
+
#
|
|
189
|
+
# the parent link (plan.parent_key) -> `parent_id`, the id
|
|
190
|
+
# the walk already carries in from one level up
|
|
191
|
+
# a walk-minted one (POSITION) -> the walk INDEX itself ;
|
|
192
|
+
# never a stored field (Member's own header:
|
|
193
|
+
# "position is not a mint — it is read straight
|
|
194
|
+
# out of the source file")
|
|
195
|
+
# a real field (anything else) -> `field_value`, same
|
|
196
|
+
# reader every other field in this file uses
|
|
197
|
+
# (Handler's own `event_type`, Dispatch's own
|
|
198
|
+
# `command_name`)
|
|
199
|
+
#
|
|
200
|
+
# `carried` still decides bare-vs-wrapped the normal way ; POSITION
|
|
201
|
+
# is the one case with no verb to ask `carried` about (`plan.
|
|
202
|
+
# declare` is always nil for an entity-owned category — Plan#read's
|
|
203
|
+
# own comment says why), so it is minted straight as a value object,
|
|
204
|
+
# matching exactly what `declare`'s own field loop already mints a
|
|
205
|
+
# POSITION field as.
|
|
206
|
+
def node_identity(plan, category, node, index, parent_id)
|
|
207
|
+
plan.identity_paths.each_with_object({}) do |path, fields|
|
|
208
|
+
head = path.to_s.split(".").first
|
|
209
|
+
next if head == OWNER
|
|
210
|
+
|
|
211
|
+
if head == POSITION
|
|
212
|
+
fields[head.to_sym] = v(index)
|
|
213
|
+
else
|
|
214
|
+
raw = head == plan.parent_key.to_s ? parent_id : field_value(category, node, head.to_sym, parent_id)
|
|
215
|
+
fields[head.to_sym] = carried(plan, plan.declare, head, raw)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# THE FULL DOTTED PREFIX a category's own verbs hang off — the
|
|
221
|
+
# plain name for an ordinary category (its own top-level
|
|
222
|
+
# aggregate reaches every verb bare), or its PARENT's own prefix
|
|
223
|
+
# with this category's name appended, for an entity-owned one.
|
|
224
|
+
# Dispatch's own parent, Handler, is itself entity-owned (S17,
|
|
225
|
+
# ADR 0026's two-level chain — `ProcessManager.Handler.Dispatch`),
|
|
226
|
+
# so this recurses rather than reading one level and stopping.
|
|
227
|
+
def dotted_prefix(plan)
|
|
228
|
+
return plan.name unless plan.entity_owned
|
|
229
|
+
|
|
230
|
+
"#{dotted_prefix(@plan.category(plan.parent))}.#{plan.name}"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# ENTITY-OWNED categories have no top-level aggregate for the runtime
|
|
234
|
+
# to route a bare verb into any more — `Member`'s own "Pair" reaches
|
|
235
|
+
# the runtime as `ValueObject.Member.Pair`, and a NESTED one
|
|
236
|
+
# (`Dispatch`, inside `Handler`) reaches it as `ProcessManager.
|
|
237
|
+
# Handler.Dispatch.Bind` — the dotted shape `EntityInterpreter#call`
|
|
238
|
+
# already splits any real entity's own verb into, one hop per
|
|
239
|
+
# segment (`walk_entity_chain`, entity_interpreter.rb).
|
|
240
|
+
def verb_for(plan, verb)
|
|
241
|
+
"#{dotted_prefix(plan)}.#{verb}"
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def walk_all(category, node, parent_id, extra = {}, receiver: nil)
|
|
245
|
+
reader = collection_reader(category)
|
|
246
|
+
return unless node.respond_to?(reader)
|
|
247
|
+
|
|
248
|
+
children = Array(node.public_send(reader))
|
|
249
|
+
children.each_with_index { |child, index| declare_node(category, child, parent_id, index, extra) }
|
|
250
|
+
children.each_with_index do |child, index|
|
|
251
|
+
child_plan = @plan.category(category)
|
|
252
|
+
child_receiver = if child_plan&.entity_owned
|
|
253
|
+
child_id = identify(category, parent_id, child, index)
|
|
254
|
+
root = receiver || { aggregate: parent_id, entities: [] }
|
|
255
|
+
{ aggregate: root[:aggregate], entities: Array(root[:entities]) + [child_id] }
|
|
256
|
+
end
|
|
257
|
+
detail_node(category, child, parent_id, index, extra, receiver: child_receiver)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# A piece's commands and queries, addressed under the PIECE so two commands
|
|
262
|
+
# of the same name on an aggregate and on one of its entities cannot collide,
|
|
263
|
+
# while `aggregate` still names the aggregate the reference resolves
|
|
264
|
+
# against and `entity_id` says which piece declared it. `entity_id`
|
|
265
|
+
# keeps its own `_id` — an EXPLICIT `as:` on `reference_to Entity`,
|
|
266
|
+
# never touched by ADR 0025's rename (only the DEFAULT, un-aliased
|
|
267
|
+
# mint dropped the suffix; `aggregate` did precisely because
|
|
268
|
+
# `Command#reference_to Aggregate`/`Query#reference_to Aggregate`
|
|
269
|
+
# carry no `as:` of their own).
|
|
270
|
+
def within_entity(category, node, id, aggregate)
|
|
271
|
+
return unless category == "Entity"
|
|
272
|
+
|
|
273
|
+
WITHIN_ENTITY.each do |child|
|
|
274
|
+
plan = @plan.category(child)
|
|
275
|
+
walk_all(child, node, id, {
|
|
276
|
+
aggregate: carried(plan, plan&.declare, "aggregate", aggregate),
|
|
277
|
+
entity_id: carried(plan, plan&.declare, "entity_id", id)
|
|
278
|
+
})
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# AN ENTITY MAY NEST FURTHER ENTITIES — S17, ADR 0026's own words:
|
|
283
|
+
# "That is what `entity` is for, and `entity` is declared by the
|
|
284
|
+
# language and used zero times in it." `Dispatch`, inside
|
|
285
|
+
# `Handler`, is the first real use. The GENERIC "Entity" category
|
|
286
|
+
# cannot express this through `children_of`/`EAGER_CHILDREN` the
|
|
287
|
+
# way Aggregate's own entities/value_objects can — there is only
|
|
288
|
+
# ONE "Entity" Plan category, describing what ANY entity looks
|
|
289
|
+
# like, not one per nesting level — so this recurses by hand,
|
|
290
|
+
# the same special case `within_entity` (above) already is for
|
|
291
|
+
# Command/Query.
|
|
292
|
+
#
|
|
293
|
+
# `owner` is the field this repurposes — `entity.bluebook`
|
|
294
|
+
# declares it (`attribute :owner, EntityText`) and it has held
|
|
295
|
+
# exactly one value since ADR 0025's rename: the SAME id
|
|
296
|
+
# `aggregate` already carries, kept as a wrapped-text COPY,
|
|
297
|
+
# never read back anywhere else in this codebase (grep finds no
|
|
298
|
+
# second reference). For a NESTED entity, the two finally
|
|
299
|
+
# diverge — `aggregate` stays the ROOT (Dispatch resolves
|
|
300
|
+
# exactly the way any other Entity record does, by its root
|
|
301
|
+
# aggregate), and `owner` becomes THIS entity's own DIRECT
|
|
302
|
+
# parent (Handler, not ProcessManager) — which is exactly the
|
|
303
|
+
# fact `Reconstruction#direct_entities` needs to tell a
|
|
304
|
+
# root-level entity apart from a nested one sharing the same
|
|
305
|
+
# root.
|
|
306
|
+
def nest_entities(category, node, id, aggregate)
|
|
307
|
+
return unless category == "Entity"
|
|
308
|
+
|
|
309
|
+
plan = @plan.category("Entity")
|
|
310
|
+
walk_all("Entity", node, aggregate, {
|
|
311
|
+
aggregate: carried(plan, plan&.declare, "aggregate", aggregate),
|
|
312
|
+
owner: carried(plan, plan&.declare, "owner", id)
|
|
313
|
+
})
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# WHERE IT SITS AMONG ITS SIBLINGS IS A FACT ABOUT THE WALK, not about the
|
|
317
|
+
# node : a command does not know it is the third command on its aggregate.
|
|
318
|
+
# The walk knows, so the walk supplies it, and every other field still
|
|
319
|
+
# comes from the node. Declaration order used to survive only because the
|
|
320
|
+
# meta store happened to iterate in insertion order — an accident that an
|
|
321
|
+
# ask ordered any other way would have taken away, and Reconstruction is
|
|
322
|
+
# the one reader that must have the SOURCE'S order rather than a stable one.
|
|
323
|
+
POSITION = "position"
|
|
324
|
+
|
|
325
|
+
def declare(plan, category, node, id, parent_id, index, extra = {})
|
|
326
|
+
return unless plan.declare
|
|
327
|
+
|
|
328
|
+
payload = {}
|
|
329
|
+
payload[plan.parent_key.to_sym] = carried(plan, plan.declare, plan.parent_key, parent_id) if plan.parent_key
|
|
330
|
+
plan.fields.each do |field|
|
|
331
|
+
payload[field.to_sym] = if field == POSITION
|
|
332
|
+
v(index)
|
|
333
|
+
else
|
|
334
|
+
carried(plan, plan.declare, field, field_value(category, node, field.to_sym, parent_id))
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
send_to("Bluebook::#{verb_for(plan, plan.declare)}", id, to: id, **payload.merge(extra))
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# A setter whose every source is absent is not dispatched. An aggregate
|
|
342
|
+
# with no lifecycle has no Lifecycle to offer, and a creating command has
|
|
343
|
+
# no root to act on — offering either as "" would make a rule refuse a
|
|
344
|
+
# bluebook that is perfectly well formed.
|
|
345
|
+
def setters(plan, category, node, receiver)
|
|
346
|
+
plan.setters.each do |setter|
|
|
347
|
+
payload = setter.targets.to_h do |target, argument|
|
|
348
|
+
[argument.to_sym, v(setter_value(category, node, target))]
|
|
349
|
+
end
|
|
350
|
+
next if payload.values.all?(&:nil?)
|
|
351
|
+
|
|
352
|
+
send_to("Bluebook::#{verb_for(plan, setter.verb)}", receiver[:aggregate], to: receiver, **payload)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def appends(plan, category, node, receiver)
|
|
357
|
+
id = receiver[:entities].last || receiver[:aggregate]
|
|
358
|
+
plan.appends.each do |list_name, append|
|
|
359
|
+
rows_for(category, list_name, node).each_with_index do |row, index|
|
|
360
|
+
chosen = append_for(category, list_name, append, row, node)
|
|
361
|
+
# `position` IS THE WALK INDEX here exactly as it is in `declare` —
|
|
362
|
+
# an appended element that names its position (ValueObject.Member,
|
|
363
|
+
# S17) is ordered by where the walk found it, never by a field the
|
|
364
|
+
# row happens to hold.
|
|
365
|
+
payload = chosen.map.to_h do |field, argument|
|
|
366
|
+
value = if field.to_s == POSITION
|
|
367
|
+
v(index)
|
|
368
|
+
else
|
|
369
|
+
carried(@plan.category(category), chosen.verb, argument,
|
|
370
|
+
cell(category, list_name, row, field, id, chosen))
|
|
371
|
+
end
|
|
372
|
+
[argument.to_sym, value]
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
send_to("Bluebook::#{verb_for(plan, chosen.verb)}", "#{id}##{list_name}[#{index}]",
|
|
376
|
+
to: receiver, **payload)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def sealers(plan, category, receiver)
|
|
382
|
+
id = receiver[:entities].last || receiver[:aggregate]
|
|
383
|
+
plan.sealers.each { |verb| send_to("Bluebook::#{verb_for(plan, verb)}", id, to: receiver) }
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# An aggregate's attribute names its value object by TYPE, and the language
|
|
387
|
+
# models that as a reference — so the type is offered as the value object's
|
|
388
|
+
# own id. This is the rule "attributes must use value-object types",
|
|
389
|
+
# enforced by reference resolution rather than by a predicate.
|
|
390
|
+
# An attribute's type is offered as the ID OF THE THING IT NAMES, so the
|
|
391
|
+
# language resolves it as a reference and "the type is declared" costs no
|
|
392
|
+
# predicate. Three kinds, three ids: a value object and an entity both hang
|
|
393
|
+
# off this aggregate, so they share its prefix; another aggregate's head
|
|
394
|
+
# hangs off the chapter.
|
|
395
|
+
def cell(category, list_name, row, field, id, append)
|
|
396
|
+
value = row_value(row, field)
|
|
397
|
+
# A default keeps its TYPE by being written as a literal — 0.0 rather than
|
|
398
|
+
# "0.0" — because the language holds it as text and text alone forgets.
|
|
399
|
+
return encode_literal(value) if field == :default
|
|
400
|
+
return value unless field == :type
|
|
401
|
+
# A REFERENCE names another head WHEREVER it is written — on a head, on
|
|
402
|
+
# a command, on a piece, on an ask — so it is offered as that head's
|
|
403
|
+
# id in all four. Only the head's own attributes additionally qualify
|
|
404
|
+
# an ordinary type into a value object's id ; a command argument's
|
|
405
|
+
# type is text and stays text.
|
|
406
|
+
return points_at(row, id) if append.verb == "Reference"
|
|
407
|
+
return value unless "#{category}.#{list_name}" == "Aggregate.attributes"
|
|
408
|
+
|
|
409
|
+
Naming.identity([owning_aggregate_id(id, value), value])
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# `id` NAMES THE ATTRIBUTE'S OWN AGGREGATE, not necessarily the
|
|
413
|
+
# value object's — Wave 7's own translation.bluebook/translation_
|
|
414
|
+
# aggregate.bluebook split proved the difference live:
|
|
415
|
+
# TranslationAggregate's own `was`/every rename-rule's own `from`/
|
|
416
|
+
# `to`/... all deliberately reuse the SIBLING "Translation"
|
|
417
|
+
# aggregate's own `TranslationName` (that file's own header:
|
|
418
|
+
# "the shared TranslationName every non-identity field below
|
|
419
|
+
# uses"), a real, intentional cross-aggregate reuse — not the
|
|
420
|
+
# local-only ownership every OTHER real domain in this corpus
|
|
421
|
+
# happens to have used until now.
|
|
422
|
+
#
|
|
423
|
+
# `id` (already `Naming.identity([chapter, aggregate])`-joined)
|
|
424
|
+
# only ever composes with the LOCAL aggregate for real, non-
|
|
425
|
+
# entity-owned attributes — an entity's own `id` never matches
|
|
426
|
+
# any TOP-LEVEL aggregate here, so `local` stays nil and this
|
|
427
|
+
# returns `id` unchanged, exactly the prior behavior. Same for
|
|
428
|
+
# every attribute whose type IS locally declared (the overwhelming
|
|
429
|
+
# common case, Banking's own Customer/Account included) — this
|
|
430
|
+
# only ever changes the answer when the local aggregate does NOT
|
|
431
|
+
# declare `value` itself, falling back to the first (declaration-
|
|
432
|
+
# order) OTHER aggregate in the SAME chapter that does.
|
|
433
|
+
def owning_aggregate_id(id, value)
|
|
434
|
+
local = @bluebook.aggregates.find { |aggregate| Naming.identity([@bluebook.name, aggregate.name]) == id }
|
|
435
|
+
return id unless local
|
|
436
|
+
return id if names?(local, value)
|
|
437
|
+
|
|
438
|
+
owner = @bluebook.aggregates.find { |aggregate| aggregate != local && names?(aggregate, value) }
|
|
439
|
+
return id unless owner
|
|
440
|
+
|
|
441
|
+
Naming.identity([@bluebook.name, owner.name])
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
# Whichever construct kind `value` actually is — a value object
|
|
445
|
+
# (Attribute) or an entity this aggregate holds (Holds); `cell`'s
|
|
446
|
+
# own caller already knows which verb it dispatches, but not
|
|
447
|
+
# which collection to search here without re-deriving that same
|
|
448
|
+
# decision, so this simply checks both. The self-hosted grammar's
|
|
449
|
+
# own Bluebook:Syntax#attributes proved entities need the same
|
|
450
|
+
# cross-aggregate fallback value objects do — Syntax's own
|
|
451
|
+
# `Argument`-typed attribute names Command's entity, not one of
|
|
452
|
+
# Syntax's own.
|
|
453
|
+
def names?(aggregate, value)
|
|
454
|
+
aggregate.value_objects.any? { |vo| vo.hecks_name == value } ||
|
|
455
|
+
aggregate.entities.any? { |entity| entity.hecks_name == value }
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# Which verb an attribute row belongs to. The plan cannot decide this — all
|
|
459
|
+
# three append to the same list — and what tells them apart is the row:
|
|
460
|
+
#
|
|
461
|
+
# Attribute its type names a value object of this aggregate
|
|
462
|
+
# Reference its type is Reference<X>, another aggregate's head
|
|
463
|
+
# Holds its type names an entity this aggregate declares
|
|
464
|
+
#
|
|
465
|
+
# NOTHING IS SKIPPED any more. Reference and Holds did not exist, so the
|
|
466
|
+
# walk dropped both kinds and the meta-domain silently did not contain
|
|
467
|
+
# Account#customer_id or Account#ledger.
|
|
468
|
+
# Each alternate carries its OWN map, read from the language. Borrowing the
|
|
469
|
+
# primary's map dispatched `type:` where Reference declares `points_at:`,
|
|
470
|
+
# and the payload gate caught it — which is the gate paying for itself.
|
|
471
|
+
# `reference_to` can be written in FOUR places — on a head, a command, a
|
|
472
|
+
# piece, an ask — and each keeps its own list of attributes, so each
|
|
473
|
+
# needs the Reference alternate. Only a head can hold a piece, so Holds
|
|
474
|
+
# stays where it was.
|
|
475
|
+
def append_for(category, list_name, append, row, node)
|
|
476
|
+
return append unless list_name.to_s == "attributes"
|
|
477
|
+
return alternate(category, "Reference") || append if reference_row?(row)
|
|
478
|
+
return alternate(category, "Holds") || append if entity_row?(row, node)
|
|
479
|
+
|
|
480
|
+
append
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def alternate(category, verb)
|
|
484
|
+
@plan.category(category).alternates.find { |append| append.verb == verb }
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def reference_row?(row) = row.respond_to?(:reference?) && row.reference?
|
|
488
|
+
|
|
489
|
+
# Only a HEAD declares pieces, and now that every attribute list reaches
|
|
490
|
+
# this, the node may be a command, a piece or an ask — none of which
|
|
491
|
+
# answer `entities` at all.
|
|
492
|
+
def entity_row?(row, node)
|
|
493
|
+
return false unless node.respond_to?(:entities)
|
|
494
|
+
|
|
495
|
+
Array(node.entities).any? { |entity| entity.hecks_name == row.type.to_s }
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# A RECORD'S ID IS ITS DECLARED IDENTITY, JOINED — the same join the runtime
|
|
499
|
+
# does, off the same declaration, because there is only one way to name a
|
|
500
|
+
# thing and it should be written once.
|
|
501
|
+
#
|
|
502
|
+
# This was a branch per category: "#{parent}::#{name}" for an aggregate,
|
|
503
|
+
# "#{parent}.#{name}" for most, "#{parent}##{index}" for the three that had
|
|
504
|
+
# no name to use. It was the composite identity all along, hand-written here
|
|
505
|
+
# because the language could not say it — which is why the language having
|
|
506
|
+
# no identity of its own and this method existing were the same fact.
|
|
507
|
+
#
|
|
508
|
+
# A part resolves from one of three places, and the declaration says which:
|
|
509
|
+
# the parent reference is the walk's parent_id, `position` is where the walk
|
|
510
|
+
# is, and anything else is read off the node.
|
|
511
|
+
def identify(category, parent_id, node, index)
|
|
512
|
+
plan = @plan.category(category)
|
|
513
|
+
return declared_name(node) unless plan
|
|
514
|
+
|
|
515
|
+
Naming.identity(plan.identity_paths.map { |path| identity_part(plan, path, parent_id, node, index, category) })
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# "owner_id" is a SECOND reserved head, beside `position` : it names
|
|
519
|
+
# whichever record is walking THIS one right now, aggregate or entity
|
|
520
|
+
# alike, without saying which — Command and Query read it so an
|
|
521
|
+
# entity's verbs are the entity's own. It is never a declared
|
|
522
|
+
# attribute (declaring one for a field that names two different types
|
|
523
|
+
# would be a lie about which), so it cannot be read through
|
|
524
|
+
# `field_value` ; it is read the same way `plan.parent_key` already is,
|
|
525
|
+
# because it IS that fact, spelled for the walk's immediate parent
|
|
526
|
+
# rather than for one specific kind of one.
|
|
527
|
+
OWNER = "owner_id"
|
|
528
|
+
|
|
529
|
+
def identity_part(plan, path, parent_id, node, index, category)
|
|
530
|
+
head = path.to_s.split(".").first
|
|
531
|
+
return parent_id.to_s if head == plan.parent_key.to_s || head == OWNER
|
|
532
|
+
return index.to_s if head == POSITION
|
|
533
|
+
|
|
534
|
+
v_scalar(field_value(category, node, head.to_sym, parent_id))
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
# The scalar inside whatever the reading handed back — a name is already one,
|
|
538
|
+
# a value object is not.
|
|
539
|
+
def v_scalar(held)
|
|
540
|
+
return held.to_s unless held.respond_to?(:to_h) && !held.is_a?(String)
|
|
541
|
+
|
|
542
|
+
held.to_h.values.first.to_s
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def children_of(category)
|
|
546
|
+
@plan.names.select { |name| @plan.category(name).parent == category }
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def eager?(category, child) = Array(EAGER_CHILDREN[category]).include?(child)
|
|
550
|
+
|
|
551
|
+
# Command -> commands, ValueObject -> value_objects, Query -> queries.
|
|
552
|
+
# Convention, not a table : the IR names a collection after what it holds.
|
|
553
|
+
# The pluraliser lives in Naming because there used to be two of them and
|
|
554
|
+
# one was wrong — see Naming.plural.
|
|
555
|
+
def collection_reader(category) = Naming.plural(Naming.snake(category))
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
end
|