hecks 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
- data/lib/hecks/adapters/driven/claude_code.rb +127 -0
- data/lib/hecks/adapters/driven/d1.adapter +12 -0
- data/lib/hecks/adapters/driven/d1.rb +427 -0
- data/lib/hecks/adapters/driven/folder.adapter +3 -0
- data/lib/hecks/adapters/driven/folder.rb +199 -0
- data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
- data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
- data/lib/hecks/adapters/driven/governance_authorization.rb +101 -0
- data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
- data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
- data/lib/hecks/adapters/driven/heki.adapter +4 -0
- data/lib/hecks/adapters/driven/heki.rb +179 -0
- data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
- data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
- data/lib/hecks/adapters/driven/lambda.adapter +4 -0
- data/lib/hecks/adapters/driven/lambda.rb +145 -0
- data/lib/hecks/adapters/driven/memory.adapter +3 -0
- data/lib/hecks/adapters/driven/memory.rb +103 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
- data/lib/hecks/adapters/driven/postgres/codec.rb +88 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
- data/lib/hecks/adapters/driven/postgres.adapter +5 -0
- data/lib/hecks/adapters/driven/postgres.rb +438 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +17 -0
- data/lib/hecks/adapters/driven/prism.adapter +3 -0
- data/lib/hecks/adapters/driven/prism.rb +80 -0
- data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
- data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
- data/lib/hecks/adapters/driven/sqlite/codec.rb +80 -0
- data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
- data/lib/hecks/adapters/driven/sqlite.rb +306 -0
- data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +14 -0
- data/lib/hecks/adapters/driven.rb +54 -0
- data/lib/hecks/adapters.rb +6 -0
- data/lib/hecks/behaviors/dsl.rb +125 -0
- data/lib/hecks/behaviors/expectations.rb +340 -0
- data/lib/hecks/behaviors/ir.rb +31 -0
- data/lib/hecks/behaviors/rspec.rb +42 -0
- data/lib/hecks/behaviors/runner.rb +96 -0
- data/lib/hecks/behaviors.rb +25 -0
- data/lib/hecks/bluebook/aggregate.rb +108 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
- data/lib/hecks/bluebook/assembly/build.rb +48 -0
- data/lib/hecks/bluebook/assembly/contract.rb +112 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +438 -0
- data/lib/hecks/bluebook/assembly/marks.rb +228 -0
- data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
- data/lib/hecks/bluebook/assembly.rb +91 -0
- data/lib/hecks/bluebook/attribute.rb +96 -0
- data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
- data/lib/hecks/bluebook/behaviour/command.rb +116 -0
- data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +68 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -0
- data/lib/hecks/bluebook/behaviour/query.rb +10 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
- data/lib/hecks/bluebook/chapter.rb +78 -0
- data/lib/hecks/bluebook/command.rb +124 -0
- data/lib/hecks/bluebook/domain_port.rb +102 -0
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +1018 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +714 -0
- data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
- data/lib/hecks/bluebook/dsl/entity_builder.rb +430 -0
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
- data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
- data/lib/hecks/bluebook/dsl/policy_builder.rb +135 -0
- data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
- data/lib/hecks/bluebook/dsl/rule_reference.rb +173 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
- data/lib/hecks/bluebook/dsl/word_gate.rb +228 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +117 -0
- data/lib/hecks/bluebook/dsl.rb +45 -0
- data/lib/hecks/bluebook/entity.rb +103 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +123 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
- data/lib/hecks/bluebook/expression/projection.json +218 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
- data/lib/hecks/bluebook/expression/resolver.rb +781 -0
- data/lib/hecks/bluebook/expression.rb +13 -0
- data/lib/hecks/bluebook/hexagon.rb +60 -0
- data/lib/hecks/bluebook/lifecycle.rb +42 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +621 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
- data/lib/hecks/bluebook/meta_validator.rb +542 -0
- data/lib/hecks/bluebook/model_check.rb +445 -0
- data/lib/hecks/bluebook/pattern_subset.rb +184 -0
- data/lib/hecks/bluebook/policy.rb +41 -0
- data/lib/hecks/bluebook/process_manager.rb +128 -0
- data/lib/hecks/bluebook/project_discovery.rb +30 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +82 -0
- data/lib/hecks/bluebook/query.rb +61 -0
- data/lib/hecks/bluebook/read_model.rb +109 -0
- data/lib/hecks/bluebook/reference.rb +74 -0
- data/lib/hecks/bluebook/smoke_test.rb +166 -0
- data/lib/hecks/bluebook/synthesizer.rb +95 -0
- data/lib/hecks/bluebook/translation.rb +92 -0
- data/lib/hecks/bluebook/value_object.rb +66 -0
- data/lib/hecks/bluebook.rb +74 -0
- data/lib/hecks/codemod.rb +342 -0
- data/lib/hecks/construct.rb +71 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
- data/lib/hecks/deploy/oidc.json +18 -0
- data/lib/hecks/doc/reference.rb +410 -0
- data/lib/hecks/embryonaut_bluebook.rb +75 -0
- data/lib/hecks/facade/cli_door.rb +119 -0
- data/lib/hecks/facade/cli_runner.rb +190 -0
- data/lib/hecks/facade/command_request.rb +105 -0
- data/lib/hecks/facade/handle.rb +173 -0
- data/lib/hecks/facade/json_door.rb +166 -0
- data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
- data/lib/hecks/facade/surface/chapter.rb +107 -0
- data/lib/hecks/facade/surface.rb +48 -0
- data/lib/hecks/facade.rb +44 -0
- data/lib/hecks/forms/app.rb +341 -0
- data/lib/hecks/forms/command_form_renderer.rb +113 -0
- data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
- data/lib/hecks/forms/field_renderer.rb +177 -0
- data/lib/hecks/forms/field_shape.rb +232 -0
- data/lib/hecks/forms/html.rb +84 -0
- data/lib/hecks/forms/index_renderer.rb +35 -0
- data/lib/hecks/forms/page.rb +157 -0
- data/lib/hecks/forms/params.rb +161 -0
- data/lib/hecks/forms/port_argument.rb +46 -0
- data/lib/hecks/forms/query_form_renderer.rb +114 -0
- data/lib/hecks/forms/record_renderer.rb +119 -0
- data/lib/hecks/forms/record_table.rb +68 -0
- data/lib/hecks/forms/reference_options.rb +30 -0
- data/lib/hecks/forms/value_object_shape.rb +46 -0
- data/lib/hecks/forms.rb +54 -0
- data/lib/hecks/fqn.rb +94 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
- data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
- data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
- data/lib/hecks/framework/bluebook/governance.bluebook +145 -0
- data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
- data/lib/hecks/framework/oidc.json +39 -0
- data/lib/hecks/framework.rb +90 -0
- data/lib/hecks/freezer.rb +67 -0
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +527 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
- data/lib/hecks/fuzzing/properties.rb +1192 -0
- data/lib/hecks/fuzzing/replay.rb +668 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
- data/lib/hecks/fuzzing/value_generator.rb +200 -0
- data/lib/hecks/fuzzing.rb +12 -0
- data/lib/hecks/grammar/evolve.rb +324 -0
- data/lib/hecks/grammar/expression.bluebook +420 -0
- data/lib/hecks/grammar/expression_operators.json +1648 -0
- data/lib/hecks/grammar/grammar.hecksagon +20 -0
- data/lib/hecks/grammar/oidc.json +69 -0
- data/lib/hecks/grammar/translation.bluebook +203 -0
- data/lib/hecks/grammar.rb +178 -0
- data/lib/hecks/ir.rb +126 -0
- data/lib/hecks/language/adapter.bluebook +116 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
- data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
- data/lib/hecks/language/bluebook/command.bluebook +471 -0
- data/lib/hecks/language/bluebook/entity.bluebook +392 -0
- data/lib/hecks/language/bluebook/policy.bluebook +189 -0
- data/lib/hecks/language/bluebook/process_manager.bluebook +381 -0
- data/lib/hecks/language/bluebook/projection.bluebook +267 -0
- data/lib/hecks/language/bluebook/query.bluebook +245 -0
- data/lib/hecks/language/bluebook/shape.bluebook +292 -0
- data/lib/hecks/language/bluebook/syntax.bluebook +448 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -0
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
- data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
- data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
- data/lib/hecks/language/oidc.json +333 -0
- data/lib/hecks/language/port.bluebook +120 -0
- data/lib/hecks/language/translation/translation.bluebook +110 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
- data/lib/hecks/language/world/wiring.bluebook +62 -0
- data/lib/hecks/language/world/world.bluebook +84 -0
- data/lib/hecks/literal.rb +125 -0
- data/lib/hecks/naming.rb +174 -0
- data/lib/hecks/ports/access_control.port +9 -0
- data/lib/hecks/ports/access_control.rb +62 -0
- data/lib/hecks/ports/agent/answers.rb +104 -0
- data/lib/hecks/ports/agent.port +8 -0
- data/lib/hecks/ports/agent.rb +167 -0
- data/lib/hecks/ports/authentication.port +6 -0
- data/lib/hecks/ports/authentication.rb +50 -0
- data/lib/hecks/ports/authorization.port +7 -0
- data/lib/hecks/ports/authorization.rb +62 -0
- data/lib/hecks/ports/clock.port +5 -0
- data/lib/hecks/ports/clock.rb +62 -0
- data/lib/hecks/ports/extraction.port +5 -0
- data/lib/hecks/ports/extraction.rb +37 -0
- data/lib/hecks/ports/identity_assignment.port +5 -0
- data/lib/hecks/ports/identity_assignment.rb +45 -0
- data/lib/hecks/ports/identity_generation.port +5 -0
- data/lib/hecks/ports/identity_generation.rb +49 -0
- data/lib/hecks/ports/identity_resolution.port +5 -0
- data/lib/hecks/ports/identity_resolution.rb +40 -0
- data/lib/hecks/ports/loading.port +4 -0
- data/lib/hecks/ports/loading.rb +13 -0
- data/lib/hecks/ports/persistence/append_only.rb +138 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
- data/lib/hecks/ports/persistence/execution.rb +23 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
- data/lib/hecks/ports/persistence/plugin.rb +54 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
- data/lib/hecks/ports/persistence/plugins/era.rb +48 -0
- data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
- data/lib/hecks/ports/persistence.port +4 -0
- data/lib/hecks/ports/persistence.rb +39 -0
- data/lib/hecks/ports/projection.port +4 -0
- data/lib/hecks/ports/projection.rb +96 -0
- data/lib/hecks/ports/query/in_memory.rb +60 -0
- data/lib/hecks/ports/query/ordering.rb +41 -0
- data/lib/hecks/ports/query.rb +36 -0
- data/lib/hecks/ports.rb +27 -0
- data/lib/hecks/projections/diagrams.rb +642 -0
- data/lib/hecks/projections/ir.rb +18 -0
- data/lib/hecks/projections/model/deviations.rb +98 -0
- data/lib/hecks/projections/model.rb +145 -0
- data/lib/hecks/projections/oidc.rb +110 -0
- data/lib/hecks/projections/parser_table.rb +159 -0
- data/lib/hecks/projections/reference.rb +38 -0
- data/lib/hecks/projections/shape.rb +44 -0
- data/lib/hecks/projections/statements.rb +110 -0
- data/lib/hecks/projections/vocabulary.rb +100 -0
- data/lib/hecks/projections.rb +32 -0
- data/lib/hecks/projector/cli_projector.rb +446 -0
- data/lib/hecks/projector/docs_projector.rb +321 -0
- data/lib/hecks/projector/exporter.rb +158 -0
- data/lib/hecks/projector/ir_projector.rb +18 -0
- data/lib/hecks/projector/narrate_projector.rb +243 -0
- data/lib/hecks/projector/target.rb +97 -0
- data/lib/hecks/projector.rb +186 -0
- data/lib/hecks/query_ir.rb +411 -0
- data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
- data/lib/hecks/query_specification/common/comparators.rb +25 -0
- data/lib/hecks/query_specification/common/comparison.rb +174 -0
- data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
- data/lib/hecks/query_specification/common/dsl.rb +58 -0
- data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
- data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
- data/lib/hecks/query_specification/common/null_policy.rb +92 -0
- data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
- data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
- data/lib/hecks/query_specification/common/options.rb +32 -0
- data/lib/hecks/query_specification/common/order_by.rb +9 -0
- data/lib/hecks/query_specification/common/specification.rb +10 -0
- data/lib/hecks/query_specification/common/where_clause.rb +9 -0
- data/lib/hecks/query_specification/field_path.rb +107 -0
- data/lib/hecks/query_specification/hop_path.rb +132 -0
- data/lib/hecks/query_specification/read_model/specification.rb +18 -0
- data/lib/hecks/query_specification.rb +14 -0
- data/lib/hecks/rendering.rb +48 -0
- data/lib/hecks/router/namespace_installer.rb +157 -0
- data/lib/hecks/router.rb +70 -0
- data/lib/hecks/runtime/aggregate_lock.rb +45 -0
- data/lib/hecks/runtime/boot_gates.rb +41 -0
- data/lib/hecks/runtime/caller.rb +66 -0
- data/lib/hecks/runtime/capability_graph.rb +44 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
- data/lib/hecks/runtime/command_interpreter.rb +497 -0
- data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
- data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
- data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
- data/lib/hecks/runtime/command_rules/emission.rb +34 -0
- data/lib/hecks/runtime/command_rules/references.rb +189 -0
- data/lib/hecks/runtime/command_rules.rb +28 -0
- data/lib/hecks/runtime/dependency_planning.rb +245 -0
- data/lib/hecks/runtime/dispatcher.rb +301 -0
- data/lib/hecks/runtime/entity_element.rb +253 -0
- data/lib/hecks/runtime/entity_interpreter.rb +299 -0
- data/lib/hecks/runtime/errors.rb +122 -0
- data/lib/hecks/runtime/event.rb +51 -0
- data/lib/hecks/runtime/identity.rb +156 -0
- data/lib/hecks/runtime/instance.rb +175 -0
- data/lib/hecks/runtime/interpreting.rb +90 -0
- data/lib/hecks/runtime/loader.rb +184 -0
- data/lib/hecks/runtime/policy_interpreter.rb +366 -0
- data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
- data/lib/hecks/runtime/query_interpreter.rb +286 -0
- data/lib/hecks/runtime/reaction_invocation.rb +253 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +341 -0
- data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
- data/lib/hecks/runtime/reference_hop.rb +99 -0
- data/lib/hecks/runtime/refusal_wording.rb +126 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
- data/lib/hecks/runtime/registry/verification.rb +246 -0
- data/lib/hecks/runtime/registry.rb +279 -0
- data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
- data/lib/hecks/runtime/routing.rb +96 -0
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
- data/lib/hecks/runtime/saga_interpreter.rb +503 -0
- data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -0
- data/lib/hecks/runtime/tenant_check.rb +84 -0
- data/lib/hecks/runtime/tenant_scope.rb +55 -0
- data/lib/hecks/runtime/value/admission.rb +130 -0
- data/lib/hecks/runtime/value/coercion.rb +565 -0
- data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
- data/lib/hecks/runtime/value.rb +125 -0
- data/lib/hecks/runtime.rb +107 -0
- data/lib/hecks/storehouse.rb +632 -0
- data/lib/hecks/version.rb +16 -0
- data/lib/hecks/vocabulary.rb +218 -0
- data/lib/hecks.rb +123 -6
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
- metadata +412 -222
- data/bin/hecks +0 -7
- data/bin/hecks-package +0 -65
- data/bin/hecks_console +0 -12
- data/bin/hecks_serverless +0 -6
- data/lib/cli/build.rb +0 -14
- data/lib/cli/command_runner.rb +0 -28
- data/lib/cli/console.rb +0 -10
- data/lib/cli/generate.rb +0 -37
- data/lib/cli/hecks-cli.rb +0 -27
- data/lib/cli/test.rb +0 -57
- data/lib/console/commands.rb +0 -8
- data/lib/console/hecks-console.rb +0 -1
- data/lib/packager/README.md +0 -0
- data/lib/packager/app_runner.rb +0 -21
- data/lib/packager/args.rb +0 -26
- data/lib/packager/compatibility/fixnum.rb +0 -6
- data/lib/packager/hecks.rb +0 -39
- data/lib/packager/query_runner.rb +0 -21
- data/lib/packager/resources/Dockerfile +0 -11
- data/lib/packager/resources/app_binary +0 -7
- data/lib/packager/resources/bundle_config +0 -3
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
- data/lib/serverless/Domain +0 -32
- data/lib/serverless/cli.rb +0 -75
- data/lib/serverless/resources/command_name.js +0 -5
- data/lib/serverless/resources/environment.js +0 -7
- data/lib/serverless/resources/handler.js.tt +0 -28
- data/lib/serverless/resources/run_binary.js +0 -22
- data/lib/serverless/resources/serverless.yml +0 -20
|
@@ -0,0 +1,351 @@
|
|
|
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
|
+
# THE KEY IS STRINGIFIED, NEVER THE VALUE — the same split
|
|
211
|
+
# `Bluebook::ValueObject#to_h`'s own `members:` emission makes
|
|
212
|
+
# (lib/hecks/bluebook/value_object.rb, L7 docs/audits/
|
|
213
|
+
# 2026-08-11-bug-triage.md). `Pair.value` is declared `String`
|
|
214
|
+
# (shape.bluebook), but a real pair can hold whatever native Ruby
|
|
215
|
+
# type the source member line actually wrote (`StatementFrequency`'s
|
|
216
|
+
# own `retention_months: 84`, examples/banking/bluebook/
|
|
217
|
+
# statements.bluebook) — `text` already unwraps the stored Value to
|
|
218
|
+
# that real type, so calling `.to_s` here erased it a second time,
|
|
219
|
+
# on the side spec/round_trip_spec.rb compares straight against a
|
|
220
|
+
# fresh raw load (`Reconstruction.of` directly, no Assembly in
|
|
221
|
+
# between): `declared 84, read back "84"` the moment `to_h` stopped
|
|
222
|
+
# erasing it on the OTHER side and this one kept erasing it alone.
|
|
223
|
+
def members_of(value_object_row)
|
|
224
|
+
Array(value_object_row[:members]).map do |member|
|
|
225
|
+
Array(member[:pairs]).map { |pair| [text(pair[:key]).to_s, text(pair[:value])] }
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def command(row) = declaration("Command", row)
|
|
230
|
+
|
|
231
|
+
def query(row) = declaration("Query", row).merge(options_of(row))
|
|
232
|
+
|
|
233
|
+
# EVERY DIRECT ENTITY OF ONE OWNER — S17, ADR 0026. `declared
|
|
234
|
+
# ("Entity", root_id)` returns EVERY entity sharing the same
|
|
235
|
+
# ROOT aggregate, nested or not (Dispatch and Handler both carry
|
|
236
|
+
# `aggregate: process_manager_id`) — `owner` is the field that
|
|
237
|
+
# actually tells them apart : `Judge#nest_entities`'s own
|
|
238
|
+
# comment explains why it is the one to repurpose. Called with
|
|
239
|
+
# `owner_id == root_id` for an aggregate's own direct entities
|
|
240
|
+
# (Handler), and with `owner_id == some entity's own id` for
|
|
241
|
+
# THAT entity's own nested ones (Dispatch, owned by Handler).
|
|
242
|
+
def direct_entities(root_id, owner_id)
|
|
243
|
+
declared("Entity", root_id).select { |held| text(held[:owner]).to_s == owner_id.to_s }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def entity(row)
|
|
247
|
+
{
|
|
248
|
+
name: text(row[:name]),
|
|
249
|
+
description: text(row[:description]),
|
|
250
|
+
# The same shape as an aggregate's now. It was a String here and a
|
|
251
|
+
# Symbol there — the IR was not uniform about it, and only a round trip
|
|
252
|
+
# ever said so.
|
|
253
|
+
identified_by: identity_paths(row),
|
|
254
|
+
# `text(row[:aggregate])` — the OWNING aggregate, the same one
|
|
255
|
+
# `Judge#owning_aggregate_ref` resolved this piece's own
|
|
256
|
+
# attribute types against on the way in, so reconstruction reads
|
|
257
|
+
# a value-object-typed attribute back the identical way an
|
|
258
|
+
# aggregate's own is (`Shapes#shape_field`'s own comment).
|
|
259
|
+
attributes: Array(row[:attributes]).map { |field| shape_field(field, text(row[:aggregate])) },
|
|
260
|
+
# ADR 0028 — the SAME shape `aggregate(row)`'s own
|
|
261
|
+
# `preconditions:` reads two hand-typed methods up, read by
|
|
262
|
+
# hand for the identical reason: `entity(row)` is hand-typed
|
|
263
|
+
# too, unlike `command`/`value_object`/`query`.
|
|
264
|
+
preconditions: Array(row[:preconditions]).map { |held| rule(held) },
|
|
265
|
+
# Round 7 — the SAME shape `aggregate(row)`'s own
|
|
266
|
+
# `invariants:` reads, one level down: a piece's own shape
|
|
267
|
+
# rule, checked against every instance of this piece.
|
|
268
|
+
invariants: Array(row[:invariants]).map { |held| rule(held) },
|
|
269
|
+
commands: within("Command", row).map { |verb| command(verb) },
|
|
270
|
+
queries: within("Query", row).map { |ask| query(ask) },
|
|
271
|
+
# S17, ADR 0026 — Dispatch, inside Handler : an entity's own
|
|
272
|
+
# NESTED entities, found the same way its own direct ones
|
|
273
|
+
# were one level up.
|
|
274
|
+
entities: direct_entities(text(row[:aggregate]), row[:id]).map { |piece| entity(piece) },
|
|
275
|
+
# An entity has its own state machine, and the language has held it all
|
|
276
|
+
# along — Entity.Lifecycle and Entity.Transition were two of the fourteen
|
|
277
|
+
# verbs that started firing when the judge became a walk. Only the
|
|
278
|
+
# reconstruction had never asked for them.
|
|
279
|
+
lifecycle: lifecycle(row)
|
|
280
|
+
}
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Assembled from three fields, because the IR keeps one object where the
|
|
284
|
+
# language keeps the parts — the same difference Readings holds for the walk,
|
|
285
|
+
# in the other direction.
|
|
286
|
+
def lifecycle(row)
|
|
287
|
+
field = text(row[:state_field])
|
|
288
|
+
return nil if field.to_s.empty?
|
|
289
|
+
|
|
290
|
+
{
|
|
291
|
+
field: field,
|
|
292
|
+
default: text(row[:state_start]),
|
|
293
|
+
transitions: Array(row[:transitions]).map { |move| transition(move) }
|
|
294
|
+
}
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def policy(row) = declaration("Policy", row)
|
|
298
|
+
|
|
299
|
+
# S17, ADR 0026 — Handler is a genuine entity now, nested under
|
|
300
|
+
# ProcessManager, created by `ProcessManager.Handler` (an
|
|
301
|
+
# ordinary append, the same way `Account.LogEntry` creates a
|
|
302
|
+
# LedgerEntry) and mutated by its own dotted `ProcessManager.
|
|
303
|
+
# Handler.Dispatch`/`...Dispatch.Bind`. Its data therefore lives
|
|
304
|
+
# INLINE on the process manager's own dispatched state — same as
|
|
305
|
+
# any other entity list — not behind a separate `DeclaredIn`
|
|
306
|
+
# query : there is no such query any more, the same fix
|
|
307
|
+
# `Reconstruction#members_of` already made for Member.
|
|
308
|
+
def process_manager(row)
|
|
309
|
+
declaration("ProcessManager", row,
|
|
310
|
+
handlers: Array(row[:handlers]).map { |leg| handler(leg) })
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# S17, ADR 0026 — Dispatch, one level further in : nested under
|
|
314
|
+
# Handler, its data lives inline on the HANDLER row this method
|
|
315
|
+
# was just handed (`row` here IS one element of `handlers`,
|
|
316
|
+
# above), not behind any query either.
|
|
317
|
+
def handler(row)
|
|
318
|
+
declaration("Handler", row,
|
|
319
|
+
dispatches: Array(row[:dispatches]).map { |leg| dispatch(leg) })
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# `compensates` — TWO FLAT FIELDS on this same row
|
|
323
|
+
# (`process_manager.bluebook`'s own comment on `Dispatch` for
|
|
324
|
+
# why), assembled BY HAND into the shape its own field actually
|
|
325
|
+
# is — `declaration()`'s generic per-field hash-build has no
|
|
326
|
+
# way to turn two cells into a second object, so this reads
|
|
327
|
+
# them directly and passes the result through `extra:`, the
|
|
328
|
+
# same seam `handler`/`process_manager` already use for a
|
|
329
|
+
# `:children` shape a flat Contract cannot describe.
|
|
330
|
+
# `compensates_command_name` absent means no compensation at
|
|
331
|
+
# all — a plain dispatch with nothing to undo. A PLAIN HASH, the
|
|
332
|
+
# SAME declaration shape `to_h` spells for everything else in
|
|
333
|
+
# this file (this file's own top comment) — never a real
|
|
334
|
+
# `DispatchSpec` here; `Assembly#dispatch` is the one place a
|
|
335
|
+
# declaration hash becomes the real object, and building it
|
|
336
|
+
# twice, in two different shapes, is exactly the kind of drift
|
|
337
|
+
# this whole arc exists to remove.
|
|
338
|
+
def dispatch(row)
|
|
339
|
+
name = text(row[:compensates_command_name])
|
|
340
|
+
compensates = name && { command_name: name, with_spec: pairs(row[:compensates_with_spec]) }
|
|
341
|
+
|
|
342
|
+
declaration("Dispatch", row, compensates: compensates)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def read_model(row)
|
|
346
|
+
declaration("ReadModel", row).merge(query_name: text(row[:query_name])).merge(options_of(row))
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Bluebook
|
|
3
|
+
module MetaValidator
|
|
4
|
+
# The leaf shapes of a reconstructed bluebook, and the encodings that go with
|
|
5
|
+
# them.
|
|
6
|
+
#
|
|
7
|
+
# Reconstruction walks the tree; this rebuilds the small hashes at its tips —
|
|
8
|
+
# an attribute, a rule, a where-clause, a transition — and undoes the two
|
|
9
|
+
# things the walk encodes on the way in: an attribute's type offered as the ID
|
|
10
|
+
# of whatever it names, and an append flattened to one row per binding.
|
|
11
|
+
#
|
|
12
|
+
# Separate from the traversal because they are different concerns, and because
|
|
13
|
+
# the two together were 211 code lines against a 200 limit.
|
|
14
|
+
module Shapes
|
|
15
|
+
# The type came in as the id of what it names, so it goes back out as the
|
|
16
|
+
# name — or, for another aggregate's head, as the encoding the IR spells.
|
|
17
|
+
def attribute(field, aggregate_id)
|
|
18
|
+
type = text(field[:type]).to_s
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
name: text(field[:name])&.to_sym,
|
|
22
|
+
type: owned_type(type, aggregate_id) || reference_type(type),
|
|
23
|
+
list: text(field[:list]).to_s == "true",
|
|
24
|
+
default: decode_literal(text(field[:default])),
|
|
25
|
+
# Read back the same way `list` is — both are booleans about the
|
|
26
|
+
# attribute, held as text, and dropping either would rebuild a
|
|
27
|
+
# bluebook that no longer says what it said.
|
|
28
|
+
optional: text(field[:optional]).to_s == "true",
|
|
29
|
+
pattern: presence(text(field[:pattern])),
|
|
30
|
+
# THE ROUND TRIP IS THE ONLY WAY IN. The grammar registry keeps the
|
|
31
|
+
# ASSEMBLED graph — the language as its own judge read it back — so a
|
|
32
|
+
# fact dropped here is a fact no downstream projection ever sees, no
|
|
33
|
+
# matter how plainly the .bluebook file declares it.
|
|
34
|
+
admits: presence(text(field[:admits])),
|
|
35
|
+
relationship: presence(text(field[:relationship]))
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# A TYPE THIS AGGREGATE OWNS, offered as its id and read back as its name.
|
|
40
|
+
# Prefixed with the owner and the identity join, because that IS the value
|
|
41
|
+
# object's identity — the aggregate it belongs to, then its name.
|
|
42
|
+
def owned_type(type, aggregate_id)
|
|
43
|
+
prefix = Naming.identity([aggregate_id, ""])
|
|
44
|
+
return nil unless type.start_with?(prefix)
|
|
45
|
+
|
|
46
|
+
type.delete_prefix(prefix)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# ONE PART OF AN IDENTITY, read back as the path it went in as. The inverse
|
|
50
|
+
# of `Marks#identity_path`, and named the same so the two directions read
|
|
51
|
+
# as one table.
|
|
52
|
+
def identity_path(part) = text(part[:value]).to_s
|
|
53
|
+
|
|
54
|
+
# An argument, a parameter, a piece's attribute — the three places an
|
|
55
|
+
# attribute is written that carry no owner id to strip... except a
|
|
56
|
+
# piece's own attribute now does : an entity is its own root (repeating
|
|
57
|
+
# the aggregate's whole shape one level down), so ITS attributes name a
|
|
58
|
+
# value object exactly the way an aggregate's own do (`Judge#cell`'s own
|
|
59
|
+
# comment), and reconstruction has to undo the same owned-vs-reference
|
|
60
|
+
# split `attribute`, below, already undoes for an aggregate — not the
|
|
61
|
+
# cruder "any qualified name is a Reference" guess this used before,
|
|
62
|
+
# which mistook a piece's own value-object type for a cross-aggregate
|
|
63
|
+
# head every time.
|
|
64
|
+
#
|
|
65
|
+
# `aggregate_id`, passed only for a piece's own attribute (an
|
|
66
|
+
# argument or a parameter carries no value-object type at all — see
|
|
67
|
+
# entity.bluebook's/command.bluebook's own comments on why — so the
|
|
68
|
+
# generic reader path calls this with one argument and gets the
|
|
69
|
+
# OLD reading unchanged), is the piece's OWNING aggregate — the same
|
|
70
|
+
# one its own attribute types were resolved against on the way in
|
|
71
|
+
# (`Judge#owning_aggregate_ref`).
|
|
72
|
+
#
|
|
73
|
+
# A REFERENCE among the other two came in as the head's ID, because
|
|
74
|
+
# that is what Command/Query.Reference offer, so it goes back out as
|
|
75
|
+
# the encoding the IR spells. A qualified name is the tell : an
|
|
76
|
+
# ordinary type names something declared beside it (`Money`,
|
|
77
|
+
# `AccountNumber`) and never carries a chapter, while a head's id
|
|
78
|
+
# always does.
|
|
79
|
+
def shape_field(field, aggregate_id = nil)
|
|
80
|
+
return attribute(field, aggregate_id) if aggregate_id
|
|
81
|
+
|
|
82
|
+
type = text(field[:type]).to_s
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
name: text(field[:name])&.to_sym,
|
|
86
|
+
# A QUALIFIED name is the tell : an ordinary type names something
|
|
87
|
+
# declared beside it (`Money`, `AccountNumber`) and carries no join at
|
|
88
|
+
# all, where a head's id always does (chapter + name, joined the way
|
|
89
|
+
# every derived id is).
|
|
90
|
+
type: type.include?(Naming::IDENTITY_JOIN) ? reference_type(type) : text(field[:type]),
|
|
91
|
+
list: text(field[:list]).to_s == "true",
|
|
92
|
+
default: decode_literal(text(field[:default])),
|
|
93
|
+
optional: text(field[:optional]).to_s == "true",
|
|
94
|
+
pattern: presence(text(field[:pattern])),
|
|
95
|
+
admits: presence(text(field[:admits])),
|
|
96
|
+
relationship: presence(text(field[:relationship]))
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# An ABSENT pattern is nil, not "". The language holds every field as
|
|
101
|
+
# text, so a field nobody set comes back as the empty string — and ""
|
|
102
|
+
# is a real regex (it matches everything), so keeping it would turn
|
|
103
|
+
# "no pattern" into "a pattern that always passes" and quietly cost the
|
|
104
|
+
# IR its round trip.
|
|
105
|
+
def presence(text)
|
|
106
|
+
value = text.to_s
|
|
107
|
+
value.empty? ? nil : value
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# A literal, read back from the self-describing form Readings#encode_literal
|
|
111
|
+
# wrote — the same reader Assembly::Marks uses, because it is the same
|
|
112
|
+
# spelling. The forms are exactly the five the Primitive vocabulary admits —
|
|
113
|
+
# String, Integer, Float, TrueClass, FalseClass — plus a symbol, and an
|
|
114
|
+
# object literal, which is what `to: { value: "good" }` is: a value object's
|
|
115
|
+
# fields written inline.
|
|
116
|
+
def decode_literal(text) = Literal.read(text)
|
|
117
|
+
|
|
118
|
+
def rule(row) = { description: text(row[:description]), canonical: text(row[:canonical]) }
|
|
119
|
+
|
|
120
|
+
# S12, ADR 0025 — `projects :name, from: :"reference.remote_field"`,
|
|
121
|
+
# read back the same three plain identifiers `rule` above reads
|
|
122
|
+
# description/canonical as.
|
|
123
|
+
def projected_field(row)
|
|
124
|
+
{ name: text(row[:name]), reference: text(row[:reference]), remote_field: text(row[:remote_field]) }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# `provenance from: {...}` rides the same literal encoding `default:`
|
|
128
|
+
# does — an object literal, self-describing via Hecks::Literal — one
|
|
129
|
+
# level up: a whole keyword's argument rather than an attribute's
|
|
130
|
+
# `default:`.
|
|
131
|
+
def provenance(row) = decode_literal(text(row[:provenance]))
|
|
132
|
+
|
|
133
|
+
# `command "Debit", from: "open"` — the SAME literal encoding
|
|
134
|
+
# `provenance`/`default:` already ride (S10, ADR 0025), one
|
|
135
|
+
# state or an array of them, or nil for a command with no
|
|
136
|
+
# lifecycle guard.
|
|
137
|
+
def from(row) = decode_literal(text(row[:from]))
|
|
138
|
+
|
|
139
|
+
# THE OPTION ROWS, GATHERED BACK into the shapes `extra_options_to_h` spells.
|
|
140
|
+
#
|
|
141
|
+
# One row per part, so a compound option is several rows and a repeated one is
|
|
142
|
+
# several groups told apart by `at`. Grouping by option name and then by `at`
|
|
143
|
+
# rebuilds both without either knowing which options exist — the whole point
|
|
144
|
+
# of holding them as an open map.
|
|
145
|
+
def options_of(row)
|
|
146
|
+
Array(row[:options])
|
|
147
|
+
.group_by { |part| text(part[:option]) }
|
|
148
|
+
.to_h { |option, parts| [option.to_sym, gathered(parts)] }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def gathered(parts)
|
|
152
|
+
repeated, single = parts.partition { |part| !text(part[:at]).to_s.empty? }
|
|
153
|
+
return single.to_h { |part| [text(part[:key]).to_sym, text(part[:value])] } if repeated.empty?
|
|
154
|
+
|
|
155
|
+
repeated.group_by { |part| text(part[:at]) }
|
|
156
|
+
.values
|
|
157
|
+
.map { |group| group.to_h { |part| [text(part[:key]).to_sym, text(part[:value])] } }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# The IR keeps a where's field as a STRING, not a symbol — it is read back
|
|
161
|
+
# out, never called. The value stays RAW TEXT here on purpose : this
|
|
162
|
+
# feeds the declaration hash Assembly::Marks#where_clause decodes
|
|
163
|
+
# from (via `read`), and decoding twice is worse than once — a
|
|
164
|
+
# kwarg reference (":ceiling") decoded here into the Symbol :ceiling
|
|
165
|
+
# would have its colon stripped by `read`'s own `.to_s` and come
|
|
166
|
+
# back out as the plain string "ceiling", indistinguishable from a
|
|
167
|
+
# literal of the same name. One decode, at the one place that builds
|
|
168
|
+
# the object every comparator actually reads.
|
|
169
|
+
def where_clause(row)
|
|
170
|
+
{ field: text(row[:field]), op: text(row[:op]), value: text(row[:value]) }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# One object in the IR, two fields in the language.
|
|
174
|
+
def order_by(row)
|
|
175
|
+
field = text(row[:order_field])
|
|
176
|
+
return nil if field.to_s.empty?
|
|
177
|
+
|
|
178
|
+
{ field: field, direction: text(row[:order_way]) }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def limit(row)
|
|
182
|
+
ceiling = text(row[:limit])
|
|
183
|
+
return nil if ceiling.to_s.empty?
|
|
184
|
+
|
|
185
|
+
{ value: ceiling }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def transition(row)
|
|
189
|
+
{
|
|
190
|
+
command: text(row[:command]),
|
|
191
|
+
from_state: text(row[:from_state]),
|
|
192
|
+
to_state: text(row[:to_state])
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def head(row)
|
|
197
|
+
{
|
|
198
|
+
aggregate: text(row[:aggregate]),
|
|
199
|
+
# A String, like an entity's identified_by. The IR is not uniform about
|
|
200
|
+
# this and only a round trip says so.
|
|
201
|
+
as: text(row[:as]),
|
|
202
|
+
many: text(row[:many]).to_s == "true"
|
|
203
|
+
}
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def group_by_field(row) = { field: text(row[:field]) }
|
|
207
|
+
|
|
208
|
+
# `count`'s own boolean, read back the SAME way `head`'s own
|
|
209
|
+
# `many` is (`text(row[:many]).to_s == "true"`) — except a
|
|
210
|
+
# `ReadModel.Count` command is dispatched AT ALL only when
|
|
211
|
+
# `@count` was truthy (`MetaValidator::Judge#setters` skips a
|
|
212
|
+
# setter whose every source is absent), so an undeclared read
|
|
213
|
+
# model's own `count` field never gets written and comes back
|
|
214
|
+
# `nil` here, never `"false"` — matching `ReadModel#to_h`'s own
|
|
215
|
+
# `true`/`nil` pair (never `false`) exactly, rather than the
|
|
216
|
+
# unconditional `true`/`false` `head`'s own `many` needs (every
|
|
217
|
+
# head DOES get a `Gather` dispatch, declared or derived).
|
|
218
|
+
def read_model_count(row) = (true if text(row[:count]).to_s == "true")
|
|
219
|
+
|
|
220
|
+
# THE APPEND FLATTENING, IN REVERSE.
|
|
221
|
+
#
|
|
222
|
+
# An append binds several fields at once and the language's Change holds one
|
|
223
|
+
# field/kind/source triple, so the walk offers an append once PER BINDING.
|
|
224
|
+
# Rebuilding groups those rows back into the single mutation the IR keeps —
|
|
225
|
+
# the only place here that undoes something rather than simply reading it.
|
|
226
|
+
def mutations(row)
|
|
227
|
+
Array(row[:mutations])
|
|
228
|
+
.group_by { |change| [text(change[:target]), text(change[:op])] }
|
|
229
|
+
.map { |(target, op), bindings| mutation(target, op, bindings) }
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# `sign:` — read the SAME way the declared side computes it
|
|
233
|
+
# (`Bluebook::Mutation.sign_for`, item #5 of the whole-project
|
|
234
|
+
# table-unification survey), not stored on any row here — the
|
|
235
|
+
# meta-domain's own Change entity carries no `sign` field of its
|
|
236
|
+
# own (it is a pure function of `op`, nothing to persist), so
|
|
237
|
+
# reconstruction recomputes it the same way a freshly-built
|
|
238
|
+
# Mutation's own `to_h` lambda does, rather than leaving the key
|
|
239
|
+
# silently absent (spec/round_trip_spec's whole point: a field the
|
|
240
|
+
# language does not hold is a named gap, not a byte-for-byte one).
|
|
241
|
+
def mutation(target, op, bindings)
|
|
242
|
+
base = { target: target.to_sym, op: op.to_sym, sign: Hecks::Bluebook::Mutation.sign_for(op) }
|
|
243
|
+
# `:delegate`/`:corrects` (CommandBuilder#delegates_to's and
|
|
244
|
+
# #corrects_impl's own comments) ride the SAME multi-binding
|
|
245
|
+
# shape `:append` does.
|
|
246
|
+
return base.merge(fields: appended(bindings)) if ["append", "delegate", "corrects"].include?(op)
|
|
247
|
+
|
|
248
|
+
base.merge(source: classified(bindings.first))
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def appended(bindings)
|
|
252
|
+
bindings.to_h { |binding| [text(binding[:field]).to_sym, text(binding[:source])] }
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def classified(binding)
|
|
256
|
+
kind = text(binding[:kind])
|
|
257
|
+
value = text(binding[:source])
|
|
258
|
+
|
|
259
|
+
return { kind: kind, name: value } if %w[argument state].include?(kind)
|
|
260
|
+
|
|
261
|
+
{ kind: "literal", value: decode_literal(value) }
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|