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,350 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Bluebook
|
|
3
|
+
module MetaValidator
|
|
4
|
+
# The parts of a bluebook the walk cannot read by name alone.
|
|
5
|
+
#
|
|
6
|
+
# The language now spells its fields exactly as the IR spells them, so the
|
|
7
|
+
# judge reads almost everything straight through: `command.givens`,
|
|
8
|
+
# `value_object.invariants`, `read_model.aggregate_heads`. What remains here
|
|
9
|
+
# is not naming drift — it is places where the IR's SHAPE differs from the
|
|
10
|
+
# language's, and no amount of renaming would close that:
|
|
11
|
+
#
|
|
12
|
+
# transitions one declaration expands to SEVERAL rows, because `from`
|
|
13
|
+
# may be a list of states
|
|
14
|
+
# value_objects the IR holds objects ; the language holds their names
|
|
15
|
+
# normalisations not on the bluebook at all — they come from the canonical
|
|
16
|
+
# form table the expression grammar keeps
|
|
17
|
+
# members plain hashes, one Member root per row, pairs per entry
|
|
18
|
+
# lifecycle one IR object feeding two separate fields
|
|
19
|
+
#
|
|
20
|
+
# Everything in this file is a difference in shape. If something here is
|
|
21
|
+
# only a difference in NAME, it is in the wrong file: rename the language.
|
|
22
|
+
module Readings
|
|
23
|
+
# A list the walk is about to offer, as rows it can shape into dispatches.
|
|
24
|
+
#
|
|
25
|
+
# FROM THE TABLE. This was nine hand-written cases keyed "Category.list", and
|
|
26
|
+
# every one of them was a fact `Assembly::Contracts` is the right place to
|
|
27
|
+
# keep: which shaper turns this list into rows. A list with no shaper reads
|
|
28
|
+
# straight off the node, which is most of them.
|
|
29
|
+
def rows_for(category, list_name, node)
|
|
30
|
+
shaper = Assembly.contract(category).shaper(list_name)
|
|
31
|
+
return Array(node.public_send(list_name)) unless shaper
|
|
32
|
+
|
|
33
|
+
public_send(shaper, node)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# A where-clause is read through its own to_h, which is where the IR spells a
|
|
37
|
+
# symbol argument as ":ceiling". Reading the OBJECT instead lost the colon,
|
|
38
|
+
# and nothing downstream could tell an argument from a literal of the same
|
|
39
|
+
# name.
|
|
40
|
+
def where_rows(node) = Array(node.wheres).map(&:to_h)
|
|
41
|
+
|
|
42
|
+
# The language holds a value object's NAME here ; the IR holds the object.
|
|
43
|
+
def value_object_names(node) = node.value_objects.map { |shape| { name: shape.hecks_name } }
|
|
44
|
+
|
|
45
|
+
# AN IDENTITY IS A LIST OF PARTS, so it is offered one part at a time — the
|
|
46
|
+
# same way attributes and transitions are. The IR holds the paths ; the
|
|
47
|
+
# language holds a row per path, and the ORDER between them is the whole
|
|
48
|
+
# meaning, because the identity is their join.
|
|
49
|
+
def identity_rows(node) = node.identity_paths.map { |path| { value: path } }
|
|
50
|
+
|
|
51
|
+
# Through to_h, which is where Bluebook.render_value spells a symbol argument as
|
|
52
|
+
# ":source". The raw with_spec lost the colon, and a binding that reads an
|
|
53
|
+
# argument became indistinguishable from one carrying a literal string.
|
|
54
|
+
def with_spec_rows(node) = pair_rows(node.to_h[:with_spec])
|
|
55
|
+
|
|
56
|
+
# A read model carries the same options an ask does, plus its filters — see
|
|
57
|
+
# option_rows.
|
|
58
|
+
def read_model_option_rows(node) = option_rows(node, filters: true)
|
|
59
|
+
|
|
60
|
+
# The canonical-form table is the expression grammar's, not this chapter's, so
|
|
61
|
+
# the node is not consulted at all.
|
|
62
|
+
def normalisation_table(_node) = normalisation_rows
|
|
63
|
+
|
|
64
|
+
# `lifecycle :status do transition "Retire" => "retired", from: ["issued", "active"] end`
|
|
65
|
+
# is ONE declaration and TWO transitions. Offering it once would leave the
|
|
66
|
+
# second unjudged, which is the whole failure this judge exists to avoid.
|
|
67
|
+
def transition_rows(node)
|
|
68
|
+
lifecycle = node.respond_to?(:lifecycle) ? node.lifecycle : nil
|
|
69
|
+
return [] unless lifecycle
|
|
70
|
+
|
|
71
|
+
lifecycle.transitions.flat_map do |command, transition|
|
|
72
|
+
froms = transition.constrained? ? Array(transition.from) : [nil]
|
|
73
|
+
froms.map do |from|
|
|
74
|
+
{ command: command, from_state: from, to_state: transition.target }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# An OPEN MAP — a member's fields, a dispatch's argument bindings — has no
|
|
80
|
+
# value object that can hold it, so each entry becomes its own row. This is
|
|
81
|
+
# why Member and Dispatch are roots in the language rather than lists.
|
|
82
|
+
def pair_rows(map)
|
|
83
|
+
Array(map&.to_h).map { |key, value| { key: key, value: value } }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# EVERY SPECIFICATION OPTION AN ASK CARRIES, flattened to rows.
|
|
87
|
+
#
|
|
88
|
+
# `offset`, `cursor`, `nulls`, `authorize` and `inspect_query` are
|
|
89
|
+
# five options, one compound (authorize names a policy AND a
|
|
90
|
+
# tenant). `extra_options_to_h` already spells every one of them
|
|
91
|
+
# and drops the absent ones, so this reads that rather than
|
|
92
|
+
# naming them here — a sixth option needs no change on either
|
|
93
|
+
# side.
|
|
94
|
+
#
|
|
95
|
+
# `filters: true` adds a read model's wheres, order_by and limit —
|
|
96
|
+
# `at` tells repeated rows apart, so two wheres do not collapse.
|
|
97
|
+
#
|
|
98
|
+
# THE LANGUAGE MAY HOLD MORE THAN `to_h` CARRIES, and this is where that
|
|
99
|
+
# mattered. Until 2026-08-11, `ReadModel#to_h` omitted all three —
|
|
100
|
+
# `extra_options_to_h` rejects them by name, still does — so a read
|
|
101
|
+
# model's filtering had never been in the wire contract, and I first
|
|
102
|
+
# read that as a wall: if the wire cannot carry it, the language cannot
|
|
103
|
+
# hold it, and a graph assembled from the language must lose it.
|
|
104
|
+
#
|
|
105
|
+
# That was the wrong conclusion. `to_h` is a PROJECTION ; the language
|
|
106
|
+
# is the SOURCE. They have to agree about everything
|
|
107
|
+
# to_h spells, not about everything the language knows. Held as option
|
|
108
|
+
# rows, the filters survived the round trip regardless of whether the
|
|
109
|
+
# wire carried them too — which is exactly why, when a LATER task
|
|
110
|
+
# (Rust read-model codegen) needed `wheres`/`order_by`/`limit` on the
|
|
111
|
+
# wire for an unrelated reason, `ReadModel#to_h` could be extended to
|
|
112
|
+
# spell them (the same mechanism `Query#to_h` already used) without
|
|
113
|
+
# touching this method at all: this reads `node.wheres`/`node.
|
|
114
|
+
# order_by`/`node.limit` off the live object directly below
|
|
115
|
+
# (`filter_options`), never off `to_h`, so the wire format moving did
|
|
116
|
+
# not move this.
|
|
117
|
+
#
|
|
118
|
+
# Named `wheres`, `order_by` and `limit` so they gather back into exactly the
|
|
119
|
+
# declaration keys the assembly already reads.
|
|
120
|
+
def option_rows(node, filters: false)
|
|
121
|
+
return [] unless node.respond_to?(:extra_options_to_h)
|
|
122
|
+
|
|
123
|
+
spelled = node.extra_options_to_h
|
|
124
|
+
spelled = filter_options(node).merge(spelled) if filters
|
|
125
|
+
|
|
126
|
+
spelled.flat_map do |option, held|
|
|
127
|
+
case held
|
|
128
|
+
when Array then held.each_with_index.flat_map { |one, at| parts(option, one, at) }
|
|
129
|
+
else parts(option, held, nil)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def filter_options(node)
|
|
135
|
+
{
|
|
136
|
+
wheres: Array(node.wheres).map(&:to_h),
|
|
137
|
+
order_by: node.order_by&.to_h,
|
|
138
|
+
limit: node.limit&.to_h
|
|
139
|
+
}.reject { |_, held| held.nil? || held == [] }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def parts(option, held, at)
|
|
143
|
+
Hash(held).map do |key, value|
|
|
144
|
+
{ option: option.to_s, key: key.to_s, value: value, at: at&.to_s }
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# A mutation is ONE declaration, but the language's Change holds a single
|
|
149
|
+
# field/kind/source triple — and an append binds SEVERAL fields at once
|
|
150
|
+
# (`append: { name: :name, amount: :amount }`). So an append is offered
|
|
151
|
+
# once per binding, and each one is judged.
|
|
152
|
+
#
|
|
153
|
+
# The judge used to send `field: v(""), kind: v("argument"), source: v("")`
|
|
154
|
+
# here — three stubbed values, so every rule about what a mutation reads
|
|
155
|
+
# was being handed a blank and could never refuse.
|
|
156
|
+
def mutation_rows(node)
|
|
157
|
+
Array(node.mutations).flat_map do |mutation|
|
|
158
|
+
# `:delegate` (CommandBuilder#delegates_to's own comment) rides
|
|
159
|
+
# the SAME multi-binding shape `:append` does — `with: {...}`
|
|
160
|
+
# is a field map, same as append's own `fields:`.
|
|
161
|
+
next set_row(mutation) unless [:append, :delegate].include?(mutation.op)
|
|
162
|
+
|
|
163
|
+
mutation.source.map do |field, argument|
|
|
164
|
+
# Spelled the way Mutation#appended_fields spells it, because
|
|
165
|
+
# Assembly::Marks reads this row back through the same reader it
|
|
166
|
+
# reads that field with. `then_set :marks, append: { direction:
|
|
167
|
+
# "out" }` binds a LITERAL, and storing it raw made it
|
|
168
|
+
# indistinguishable from an argument called out.
|
|
169
|
+
{ target: mutation.target, op: mutation.op, field: field,
|
|
170
|
+
kind: argument.is_a?(Symbol) ? "argument" : "literal",
|
|
171
|
+
source: Literal.render(argument) }
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# A set/increment/decrement reads one thing: a command argument, or a
|
|
177
|
+
# literal written into the bluebook.
|
|
178
|
+
def set_row(mutation)
|
|
179
|
+
classified = mutation.to_h[:source] || {}
|
|
180
|
+
|
|
181
|
+
[{ target: mutation.target, op: mutation.op, field: mutation.target,
|
|
182
|
+
kind: classified[:kind],
|
|
183
|
+
source: classified[:name] || encode_literal(classified[:value]) }]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# The normalisation table belongs to the expression grammar, not to any one
|
|
187
|
+
# bluebook — it is how the canonical form of a rule is spelled. The language
|
|
188
|
+
# models it because a bluebook's rules are canonicalised on the way in.
|
|
189
|
+
def normalisation_rows
|
|
190
|
+
table = Expression::CanonicalForm.table
|
|
191
|
+
return [] unless table
|
|
192
|
+
|
|
193
|
+
table.map do |entry|
|
|
194
|
+
{
|
|
195
|
+
strategy: entry[:strategy],
|
|
196
|
+
source_token: entry[:source_token],
|
|
197
|
+
replacement: entry[:replacement],
|
|
198
|
+
boundary: entry[:boundary],
|
|
199
|
+
position: entry[:position]
|
|
200
|
+
}
|
|
201
|
+
end
|
|
202
|
+
rescue StandardError
|
|
203
|
+
# The table is a convenience of the Ruby side ; a bluebook that cannot
|
|
204
|
+
# produce one is not malformed.
|
|
205
|
+
[]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# What the BLUEBOOK calls a node, whichever kind of thing the node is.
|
|
209
|
+
#
|
|
210
|
+
# This used to sniff — `respond_to?(:hecks_name) ? … : node.name` — because
|
|
211
|
+
# only value objects had crossed over. Every construct answers now, so
|
|
212
|
+
# there is nothing to choose between. It disappears entirely when the DSL
|
|
213
|
+
# stops handing the judge nodes at all.
|
|
214
|
+
def declared_name(node) = node.hecks_name
|
|
215
|
+
|
|
216
|
+
# One field of a Declare payload. Mostly a reader of the same name — the
|
|
217
|
+
# exceptions are fields the IR keeps somewhere else, or not at all.
|
|
218
|
+
# READ FROM THE TABLE, not from a branch per category.
|
|
219
|
+
#
|
|
220
|
+
# These were eight hand-written cases — `Entity.owner`, `Member.shape`, two
|
|
221
|
+
# lifecycle members twice over, and three of a query's — each one restating
|
|
222
|
+
# something `Assembly::Contracts` already declares. A parent pointer is
|
|
223
|
+
# `:parent` there ; a folded field names the object and member it lives in.
|
|
224
|
+
# So the exceptions are looked up rather than repeated, and a new fold is one
|
|
225
|
+
# line in one file instead of two lines in two.
|
|
226
|
+
def field_value(category, node, field, parent_id)
|
|
227
|
+
return declared_name(node) if field == :name
|
|
228
|
+
|
|
229
|
+
contract = Assembly.contract(category)
|
|
230
|
+
# A setter names its target as a STRING and a Declare field arrives a Symbol,
|
|
231
|
+
# so the lookup keys on a Symbol either way. The case statement this replaced
|
|
232
|
+
# was type-blind because it interpolated ; a Hash is not.
|
|
233
|
+
named = field.to_sym
|
|
234
|
+
return parent_id if contract.kind_of(named) == :parent
|
|
235
|
+
|
|
236
|
+
object, member = contract.folded(named)
|
|
237
|
+
return through(node, object, member) if member
|
|
238
|
+
|
|
239
|
+
# `limit` is a language field AND an object in the IR — `Array(an_object)`
|
|
240
|
+
# wraps rather than destructures, so offering it stored
|
|
241
|
+
# "#<struct LimitSpec value=3>".
|
|
242
|
+
return node.limit&.to_h&.fetch(:value, nil) if "#{category}.#{field}" == "Query.limit"
|
|
243
|
+
|
|
244
|
+
# `provenance from: {...}` is a HASH offered into a text field, and
|
|
245
|
+
# handing it over raw let the runtime's own coercion spell it — which
|
|
246
|
+
# meant Ruby's `Hash#to_s`, whose spelling changed under us between
|
|
247
|
+
# 3.3 and 3.4. Encoded here, the same way `default:` already is and the
|
|
248
|
+
# same way Shapes#provenance reads it back.
|
|
249
|
+
return encode_literal(node.provenance) if field == :provenance
|
|
250
|
+
|
|
251
|
+
# `identified_by` is no longer a FIELD of any declaration — it is a list,
|
|
252
|
+
# filled by Identify one part at a time, so it is read through `identity_rows`
|
|
253
|
+
# like every other list rather than special-cased here. What this branch
|
|
254
|
+
# existed to protect is now structural : a path cannot come back as its head,
|
|
255
|
+
# because there is nowhere left that holds only a head.
|
|
256
|
+
|
|
257
|
+
node.respond_to?(field) ? node.public_send(field) : nil
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# One member of the object a field folds into. `to_h` first, because the
|
|
261
|
+
# member names are the ones the IR SPELLS — a Lifecycle's `default`, an
|
|
262
|
+
# OrderBy's `direction` — and reading the object raw is how a colon or a type
|
|
263
|
+
# goes missing.
|
|
264
|
+
def through(node, object, member)
|
|
265
|
+
held = node.respond_to?(object) ? node.public_send(object) : nil
|
|
266
|
+
return nil unless held
|
|
267
|
+
|
|
268
|
+
member == :transitions ? held : held.to_h[member]
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# What a setting command writes. A setter whose source is absent is not
|
|
272
|
+
# dispatched at all — ABSENT is not EMPTY, and offering "" would turn every
|
|
273
|
+
# "if you declare it, declare something" rule into "you must declare it".
|
|
274
|
+
def setter_value(category, node, target)
|
|
275
|
+
# `rows` folds into `closed_set` and `members` between them, with no single
|
|
276
|
+
# member to name, so it keeps its own reading — see Contract#folded.
|
|
277
|
+
return closed_set_size(node) if "#{category}.#{target}" == "ValueObject.rows"
|
|
278
|
+
|
|
279
|
+
object, member = Assembly.contract(category).folded(target.to_sym)
|
|
280
|
+
return through(node, object, member) if member
|
|
281
|
+
|
|
282
|
+
node.respond_to?(target) ? node.public_send(target) : nil
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Only a DECLARED closed set has a row count. An empty one is the defect,
|
|
286
|
+
# so `rows` must stay absent rather than arrive as zero.
|
|
287
|
+
def closed_set_size(node)
|
|
288
|
+
return nil unless node.respond_to?(:closed_set?) && node.closed_set?
|
|
289
|
+
|
|
290
|
+
Array(node.members).size
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# `Reference<Customer>` is an IR ENCODING, not a domain fact. The fact is
|
|
294
|
+
# that the attribute points at Customer's head — so the language is offered
|
|
295
|
+
# that head's ID, and resolution does the rest. Encoding and decoding both
|
|
296
|
+
# live here, because this is where the IR's shape differs from the
|
|
297
|
+
# language's and nowhere else should know the spelling.
|
|
298
|
+
def points_at(row, aggregate_id)
|
|
299
|
+
return nil unless row.reference?
|
|
300
|
+
|
|
301
|
+
# THE CHAPTER THIS HEAD IS IN, AND THE HEAD IT POINTS AT — which is exactly
|
|
302
|
+
# how an aggregate is identified, so it is built the same way rather than
|
|
303
|
+
# spelled again with a separator of its own. This is dispatched as a REAL
|
|
304
|
+
# REFERENCE VALUE (`Aggregate.Reference`'s `points_at:`), resolved by
|
|
305
|
+
# `repository.find` against the target Aggregate-within-Meta record's OWN
|
|
306
|
+
# stored id — so it MUST equal what that record's identity actually
|
|
307
|
+
# derives, not a wire-format spelling. `reference_type`, below, is the
|
|
308
|
+
# separate later reader that un-derives it back into "Reference<X>".
|
|
309
|
+
Naming.identity([aggregate_id.split(Naming::IDENTITY_JOIN).first, row.type.target_name])
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# A LITERAL, written so it can be read back exactly.
|
|
313
|
+
#
|
|
314
|
+
# The language holds a default and a literal mutation source as text, and
|
|
315
|
+
# `to_s` threw the type away: 0.0 came back "0.0", and `{ value: "good" }`
|
|
316
|
+
# came back its inspect string with nowhere to say it had been a hash. The
|
|
317
|
+
# language already stores code as text — `canonical: "cents >= 0"` — so an
|
|
318
|
+
# encoding is in keeping; it simply has to be SELF-DESCRIBING. That rule is
|
|
319
|
+
# now Hecks::Literal's, stated once and shared with every other
|
|
320
|
+
# to_h-bound literal field ; Shapes#decode_literal reads it back.
|
|
321
|
+
#
|
|
322
|
+
# nil stays nil rather than becoming "nil": absent is a real answer here,
|
|
323
|
+
# and the language's own field is optional.
|
|
324
|
+
def encode_literal(value) = value.nil? ? nil : Literal.render(value)
|
|
325
|
+
|
|
326
|
+
# The way back out: an aggregate id becomes the type the IR spells. The
|
|
327
|
+
# id is a JOIN of chapter + name (Naming::IDENTITY_JOIN, the same join
|
|
328
|
+
# `points_at` built it with, not the "::" a real bluebook's own type
|
|
329
|
+
# names never carry) ; the wire format wants only the bare name.
|
|
330
|
+
def reference_type(points_at_id) = "Reference<#{points_at_id.to_s.split(Naming::IDENTITY_JOIN).last}>"
|
|
331
|
+
|
|
332
|
+
# One value out of a row, named by the value object's field.
|
|
333
|
+
def row_value(row, field)
|
|
334
|
+
# A Hash FIRST. Hash answers to `key` (Hash#key(value)) and to `value` on
|
|
335
|
+
# some rows, so asking respond_to? before checking for a Hash reads a
|
|
336
|
+
# member pair through entirely the wrong method.
|
|
337
|
+
return row[field] if row.is_a?(Hash)
|
|
338
|
+
return row.public_send(field) if row.respond_to?(field)
|
|
339
|
+
# A Struct answers to [] but RAISES for a member it does not have, so it
|
|
340
|
+
# is read through to_h — a field the row simply lacks reads as absent.
|
|
341
|
+
return row.to_h[field] if row.respond_to?(:to_h) && !row.is_a?(String)
|
|
342
|
+
|
|
343
|
+
# A bare scalar row — `emits` is a list of event NAMES, and the
|
|
344
|
+
# Announcement value object has to call that string something.
|
|
345
|
+
row
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Bluebook
|
|
3
|
+
module MetaValidator
|
|
4
|
+
# A bluebook rebuilt FROM the meta-domain, in the shape the builder produces.
|
|
5
|
+
#
|
|
6
|
+
# This is the second half of the claim at the top of bluebook.bluebook —
|
|
7
|
+
# "loading a domain becomes dispatching commands into this meta-domain ; the
|
|
8
|
+
# IR it stores must equal the IR the DSL builder produces". The judge is the
|
|
9
|
+
# first half. This reads the records back and assembles `to_h`.
|
|
10
|
+
#
|
|
11
|
+
# It is the INVERSE OF THE WALK and shares its plan: the walk reads a node's
|
|
12
|
+
# lists through the command that appends to each, and this reads them back out
|
|
13
|
+
# of the rows those commands wrote. The retired `experiment/replay.rb` needed
|
|
14
|
+
# 420 hand-written lines because the dispatch half was hand-written too; there
|
|
15
|
+
# is nothing to hand-write when both directions are one table.
|
|
16
|
+
#
|
|
17
|
+
# This file is the TRAVERSAL. The hashes at its tips, and the encodings they
|
|
18
|
+
# undo, are in Shapes.
|
|
19
|
+
#
|
|
20
|
+
# IT READS LEVEL BY LEVEL, THROUGH `DeclaredIn`, AND NOT THROUGH THE READ
|
|
21
|
+
# MODEL — which is the difference between a reconstruction that can be the
|
|
22
|
+
# SOURCE and one that can only be a check.
|
|
23
|
+
#
|
|
24
|
+
# `Meta.whole_bluebook` gathers a chapter in a single read, and it sorts:
|
|
25
|
+
# `ReadModelInterpreter#matching` ends `.sort_by(&:id)` deliberately, because
|
|
26
|
+
# a store's iteration order is an accident, and a read model returning store
|
|
27
|
+
# order would let that accident leak into an answer.
|
|
28
|
+
# Right for a read model, fatal here — the order a bluebook declares its
|
|
29
|
+
# commands in is a FACT ABOUT THE SOURCE, and the IR is a contract field for
|
|
30
|
+
# field and index for index ; the export carries that order verbatim.
|
|
31
|
+
# `DeclaredIn` preserves it (spec/executes_spec says so), so this asks each
|
|
32
|
+
# level for its own children rather than filtering one sorted gather.
|
|
33
|
+
#
|
|
34
|
+
# The parent key of every level is read from the language's own plan, the same
|
|
35
|
+
# Plan the walk dispatches from — so the two directions really are one table,
|
|
36
|
+
# which is what the header above has always claimed.
|
|
37
|
+
#
|
|
38
|
+
# WHAT IT CANNOT REBUILD matters as much as what it can, and
|
|
39
|
+
# spec/round_trip_spec pins the difference as an exact set: a field the language
|
|
40
|
+
# does not hold appears there as a named gap, and a field it stops holding
|
|
41
|
+
# appears as a failure.
|
|
42
|
+
class Reconstruction
|
|
43
|
+
include Readings
|
|
44
|
+
include Shapes
|
|
45
|
+
|
|
46
|
+
def self.of(runtime, chapter) = new(runtime, chapter).to_h
|
|
47
|
+
|
|
48
|
+
def initialize(runtime, chapter)
|
|
49
|
+
@runtime = runtime
|
|
50
|
+
@plan = Plan.for(MetaValidator.grammar_registry)
|
|
51
|
+
@chapter = runtime.query("Bluebook::Bluebook.Called", name: { value: chapter }).first or
|
|
52
|
+
raise NotFound, "the meta-domain holds no bluebook called #{chapter.inspect}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def to_h
|
|
56
|
+
{
|
|
57
|
+
name: text(@chapter[:name]),
|
|
58
|
+
version: text(@chapter[:version]),
|
|
59
|
+
vision: text(@chapter[:vision]),
|
|
60
|
+
classification: text(@chapter[:classification]),
|
|
61
|
+
formerly_known_as: text(@chapter[:formerly_known_as]),
|
|
62
|
+
attaches_to: attached_contexts(@chapter),
|
|
63
|
+
aggregates: declared("Aggregate", chapter_id).map { |row| aggregate(row) },
|
|
64
|
+
read_models: declared("ReadModel", chapter_id).map { |row| read_model(row) },
|
|
65
|
+
policies: declared("Policy", chapter_id).map { |row| policy(row) },
|
|
66
|
+
process_managers: declared("ProcessManager", chapter_id).map { |row| process_manager(row) }
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def chapter_id = @chapter[:id].to_s
|
|
73
|
+
|
|
74
|
+
# Everything DECLARED IN one parent, in the order it was declared. The key
|
|
75
|
+
# is the one the language's own creating command carries, read from Plan.
|
|
76
|
+
def declared(category, parent_id)
|
|
77
|
+
key = @plan.category(category).parent_key
|
|
78
|
+
@runtime.query("Bluebook::#{category}.DeclaredIn", key.to_sym => { value: parent_id.to_s })
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# ONE DECLARATION, BUILT FROM THE CONTRACT.
|
|
82
|
+
#
|
|
83
|
+
# There were eleven methods here, one per category, each spelling out the same
|
|
84
|
+
# keys `Assembly::Contracts` already names — which is the shape the judge used
|
|
85
|
+
# to have and paid fourteen unoffered verbs for. The keys come from the table
|
|
86
|
+
# now, and the exceptions come from its `reads:` column: a list needs a reader
|
|
87
|
+
# per element, a folded field is gathered rather than fetched, and everything
|
|
88
|
+
# else is one cell.
|
|
89
|
+
#
|
|
90
|
+
# `extra` is what the containment supplies — children, and the folded objects
|
|
91
|
+
# a row cannot hold on its own.
|
|
92
|
+
def declaration(category, row, extra = {})
|
|
93
|
+
contract = Assembly.contract(category)
|
|
94
|
+
|
|
95
|
+
contract.fields.each_with_object({}) do |(_keyword, (key, _build)), out|
|
|
96
|
+
next if extra.key?(key)
|
|
97
|
+
|
|
98
|
+
out[key] = read_row(contract.reader(key), key, row)
|
|
99
|
+
end.merge(extra)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def read_row(spec, key, row)
|
|
103
|
+
case spec
|
|
104
|
+
when nil then text(row[key])
|
|
105
|
+
when :symbol then text(row[key])&.to_sym
|
|
106
|
+
when :names then Array(row[key]).map { |held| text(held[:name]) }
|
|
107
|
+
when Array then read_shaped(spec, key, row)
|
|
108
|
+
else send(spec, row)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# The readers are the ones `Shapes` already has, called by name — no wrappers,
|
|
113
|
+
# so nothing shadows them. `:each_with_id` is the single exception that needs
|
|
114
|
+
# more than the element: an attribute's type came in as the ID of whatever it
|
|
115
|
+
# names, so reading it back needs the row it belongs to.
|
|
116
|
+
def read_shaped(spec, key, row)
|
|
117
|
+
shape, named = spec
|
|
118
|
+
|
|
119
|
+
case shape
|
|
120
|
+
when :each then Array(row[key]).map { |held| send(named, held) }
|
|
121
|
+
when :each_with_id then Array(row[key]).map { |held| send(named, held, row[:id]) }
|
|
122
|
+
when :call then send(named, row)
|
|
123
|
+
when :from then pairs(row[named])
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def pairs(with) = Array(with).map { |binding| [text(binding[:key]), text(binding[:value])] }
|
|
128
|
+
|
|
129
|
+
# THE PARTS, IN THE ORDER THEY WENT IN, because the identity is their join
|
|
130
|
+
# and a join read out of order names a different record.
|
|
131
|
+
def identity_paths(row) = Array(row[:identified_by]).map { |part| text(part[:value]).to_s }
|
|
132
|
+
|
|
133
|
+
# THE CONTEXTS ONE CHAPTER NAMES ITSELF ONTO, in the order they were
|
|
134
|
+
# attached — same shape identity_paths reads back, one level up.
|
|
135
|
+
def attached_contexts(row) = Array(row[:attaches_to]).map { |part| text(part[:value]).to_s }
|
|
136
|
+
|
|
137
|
+
# Every cell of the meta-domain is a single-field value object, so a row
|
|
138
|
+
# arrives holding Values rather than Strings.
|
|
139
|
+
def text(cell)
|
|
140
|
+
return nil if cell.nil?
|
|
141
|
+
return cell.to_h.values.first if cell.respond_to?(:to_h) && !cell.is_a?(String)
|
|
142
|
+
|
|
143
|
+
cell
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# An aggregate's OWN verbs and asks — the ones no entity declared. Both carry
|
|
147
|
+
# the parent link either way, because that is the head the reference resolves
|
|
148
|
+
# against, so the entity ones have to be told apart by `entity_id`. Rejecting
|
|
149
|
+
# from an ORDERED read keeps the order.
|
|
150
|
+
def own(category, aggregate_id)
|
|
151
|
+
declared(category, aggregate_id).reject { |row| text(row[:entity_id]).to_s != "" }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# A piece's own verbs and asks. There is no `DeclaredIn` keyed by entity, so
|
|
155
|
+
# this reads the aggregate's — in declaration order — and keeps the ones that
|
|
156
|
+
# name this piece. `row[:aggregate]` — an ENTITY row's own `reference_to
|
|
157
|
+
# Aggregate` (bare, no `_id` since ADR 0025) — not `entity_id`, which
|
|
158
|
+
# STAYS suffixed below : that one is Command/Query's own EXPLICIT `as:`,
|
|
159
|
+
# never touched by the rename.
|
|
160
|
+
def within(category, row)
|
|
161
|
+
declared(category, text(row[:aggregate]))
|
|
162
|
+
.select { |held| text(held[:entity_id]).to_s == row[:id].to_s }
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def aggregate(row)
|
|
166
|
+
id = row[:id]
|
|
167
|
+
|
|
168
|
+
{
|
|
169
|
+
name: text(row[:name]),
|
|
170
|
+
description: text(row[:description]),
|
|
171
|
+
identified_by: identity_paths(row),
|
|
172
|
+
attributes: Array(row[:attributes]).map { |field| attribute(field, id) },
|
|
173
|
+
value_objects: declared("ValueObject", id).map { |shape| value_object(shape) },
|
|
174
|
+
commands: own("Command", id).map { |verb| command(verb) },
|
|
175
|
+
# THE AGGREGATE BOUNDARY, and the precondition a command may
|
|
176
|
+
# reference by name (S10, ADR 0025 — "Rules") — the same
|
|
177
|
+
# `rule` reader `command`'s own givens/ensures already use
|
|
178
|
+
# (Assembly::Contracts' generic `declaration()` path), read
|
|
179
|
+
# here by hand because `aggregate(row)` itself is hand-typed,
|
|
180
|
+
# unlike `command`/`value_object`/`query` below.
|
|
181
|
+
invariants: Array(row[:invariants]).map { |held| rule(held) },
|
|
182
|
+
preconditions: Array(row[:preconditions]).map { |held| rule(held) },
|
|
183
|
+
# S12, ADR 0025 — same reason invariants/preconditions are
|
|
184
|
+
# read by hand two lines up: `aggregate(row)` is hand-typed.
|
|
185
|
+
projected_fields: Array(row[:projected_fields]).map { |held| projected_field(held) },
|
|
186
|
+
lifecycle: lifecycle(row),
|
|
187
|
+
entities: direct_entities(id, id).map { |piece| entity(piece) },
|
|
188
|
+
queries: own("Query", id).map { |ask| query(ask) },
|
|
189
|
+
provenance: provenance(row)
|
|
190
|
+
}
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def value_object(row) = declaration("ValueObject", row)
|
|
194
|
+
|
|
195
|
+
def closed_set_of(row) = !text(row[:rows]).nil?
|
|
196
|
+
|
|
197
|
+
# A closed set's admitted rows. S17, ADR 0026 — Member is a genuine
|
|
198
|
+
# ENTITY now (`entity "Member" do ... end`, nested under
|
|
199
|
+
# ValueObject), created by `ValueObject.Member` (an ordinary append,
|
|
200
|
+
# the same way `Account.LogEntry` creates a LedgerEntry) and mutated
|
|
201
|
+
# by its own dotted `ValueObject.Member.Pair`. Its data therefore
|
|
202
|
+
# lives INLINE on the value object's own dispatched state — same as
|
|
203
|
+
# any other entity list — not behind a separate `DeclaredIn` query:
|
|
204
|
+
# there is no such query any more, because there is no top-level
|
|
205
|
+
# "Member" aggregate left to hold one. Pairs are still an OPEN MAP,
|
|
206
|
+
# which no value object can hold, so they still come back one pair
|
|
207
|
+
# at a time.
|
|
208
|
+
def members_row(row) = members_of(row)
|
|
209
|
+
|
|
210
|
+
def members_of(value_object_row)
|
|
211
|
+
Array(value_object_row[:members]).map do |member|
|
|
212
|
+
Array(member[:pairs]).map { |pair| [text(pair[:key]).to_s, text(pair[:value]).to_s] }
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def command(row) = declaration("Command", row)
|
|
217
|
+
|
|
218
|
+
def query(row) = declaration("Query", row).merge(options_of(row))
|
|
219
|
+
|
|
220
|
+
# EVERY DIRECT ENTITY OF ONE OWNER — S17, ADR 0026. `declared
|
|
221
|
+
# ("Entity", root_id)` returns EVERY entity sharing the same
|
|
222
|
+
# ROOT aggregate, nested or not (Dispatch and Handler both carry
|
|
223
|
+
# `aggregate: process_manager_id`) — `owner` is the field that
|
|
224
|
+
# actually tells them apart : `Judge#nest_entities`'s own
|
|
225
|
+
# comment explains why it is the one to repurpose. Called with
|
|
226
|
+
# `owner_id == root_id` for an aggregate's own direct entities
|
|
227
|
+
# (Handler), and with `owner_id == some entity's own id` for
|
|
228
|
+
# THAT entity's own nested ones (Dispatch, owned by Handler).
|
|
229
|
+
def direct_entities(root_id, owner_id)
|
|
230
|
+
declared("Entity", root_id).select { |held| text(held[:owner]).to_s == owner_id.to_s }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def entity(row)
|
|
234
|
+
{
|
|
235
|
+
name: text(row[:name]),
|
|
236
|
+
description: text(row[:description]),
|
|
237
|
+
# The same shape as an aggregate's now. It was a String here and a
|
|
238
|
+
# Symbol there — the IR was not uniform about it, and only a round trip
|
|
239
|
+
# ever said so.
|
|
240
|
+
identified_by: identity_paths(row),
|
|
241
|
+
attributes: Array(row[:attributes]).map { |field| shape_field(field) },
|
|
242
|
+
# ADR 0028 — the SAME shape `aggregate(row)`'s own
|
|
243
|
+
# `preconditions:` reads two hand-typed methods up, read by
|
|
244
|
+
# hand for the identical reason: `entity(row)` is hand-typed
|
|
245
|
+
# too, unlike `command`/`value_object`/`query`.
|
|
246
|
+
preconditions: Array(row[:preconditions]).map { |held| rule(held) },
|
|
247
|
+
# Round 7 — the SAME shape `aggregate(row)`'s own
|
|
248
|
+
# `invariants:` reads, one level down: a piece's own shape
|
|
249
|
+
# rule, checked against every instance of this piece.
|
|
250
|
+
invariants: Array(row[:invariants]).map { |held| rule(held) },
|
|
251
|
+
commands: within("Command", row).map { |verb| command(verb) },
|
|
252
|
+
queries: within("Query", row).map { |ask| query(ask) },
|
|
253
|
+
# S17, ADR 0026 — Dispatch, inside Handler : an entity's own
|
|
254
|
+
# NESTED entities, found the same way its own direct ones
|
|
255
|
+
# were one level up.
|
|
256
|
+
entities: direct_entities(text(row[:aggregate]), row[:id]).map { |piece| entity(piece) },
|
|
257
|
+
# An entity has its own state machine, and the language has held it all
|
|
258
|
+
# along — Entity.Lifecycle and Entity.Transition were two of the fourteen
|
|
259
|
+
# verbs that started firing when the judge became a walk. Only the
|
|
260
|
+
# reconstruction had never asked for them.
|
|
261
|
+
lifecycle: lifecycle(row)
|
|
262
|
+
}
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Assembled from three fields, because the IR keeps one object where the
|
|
266
|
+
# language keeps the parts — the same difference Readings holds for the walk,
|
|
267
|
+
# in the other direction.
|
|
268
|
+
def lifecycle(row)
|
|
269
|
+
field = text(row[:state_field])
|
|
270
|
+
return nil if field.to_s.empty?
|
|
271
|
+
|
|
272
|
+
{
|
|
273
|
+
field: field,
|
|
274
|
+
default: text(row[:state_start]),
|
|
275
|
+
transitions: Array(row[:transitions]).map { |move| transition(move) }
|
|
276
|
+
}
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def policy(row) = declaration("Policy", row)
|
|
280
|
+
|
|
281
|
+
# S17, ADR 0026 — Handler is a genuine entity now, nested under
|
|
282
|
+
# ProcessManager, created by `ProcessManager.Handler` (an
|
|
283
|
+
# ordinary append, the same way `Account.LogEntry` creates a
|
|
284
|
+
# LedgerEntry) and mutated by its own dotted `ProcessManager.
|
|
285
|
+
# Handler.Dispatch`/`...Dispatch.Bind`. Its data therefore lives
|
|
286
|
+
# INLINE on the process manager's own dispatched state — same as
|
|
287
|
+
# any other entity list — not behind a separate `DeclaredIn`
|
|
288
|
+
# query : there is no such query any more, the same fix
|
|
289
|
+
# `Reconstruction#members_of` already made for Member.
|
|
290
|
+
def process_manager(row)
|
|
291
|
+
declaration("ProcessManager", row,
|
|
292
|
+
handlers: Array(row[:handlers]).map { |leg| handler(leg) })
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# S17, ADR 0026 — Dispatch, one level further in : nested under
|
|
296
|
+
# Handler, its data lives inline on the HANDLER row this method
|
|
297
|
+
# was just handed (`row` here IS one element of `handlers`,
|
|
298
|
+
# above), not behind any query either.
|
|
299
|
+
def handler(row)
|
|
300
|
+
declaration("Handler", row,
|
|
301
|
+
dispatches: Array(row[:dispatches]).map { |leg| dispatch(leg) })
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def dispatch(row) = declaration("Dispatch", row)
|
|
305
|
+
|
|
306
|
+
def read_model(row)
|
|
307
|
+
declaration("ReadModel", row).merge(query_name: text(row[:query_name])).merge(options_of(row))
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|