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
data/lib/hecks/router.rb
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
# The public, project-wide dispatch door. It owns address resolution only;
|
|
3
|
+
# each discovered Bluebook keeps its own runtime and persistence bindings.
|
|
4
|
+
class Router
|
|
5
|
+
class UnknownAddress < StandardError; end
|
|
6
|
+
class WrongVerbKind < StandardError; end
|
|
7
|
+
class NotBooted < StandardError; end
|
|
8
|
+
class AmbiguousShortRoute < StandardError; end
|
|
9
|
+
|
|
10
|
+
attr_reader :register
|
|
11
|
+
|
|
12
|
+
def self.load(root) = new(Bluebook::ProjectLoader.load(root))
|
|
13
|
+
|
|
14
|
+
# Install one project router for ordinary application calls. The explicit
|
|
15
|
+
# `Router.load` API remains useful for tests and embedded hosts.
|
|
16
|
+
def self.boot(root)
|
|
17
|
+
router = load(root)
|
|
18
|
+
router.install_namespace!
|
|
19
|
+
@default = router
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.default
|
|
23
|
+
@default || raise(NotBooted, "no project router is booted — call Hecks::Router.boot(root) first")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.dispatch(address, **args) = default.dispatch(address, **args)
|
|
27
|
+
def self.query(address, **args) = default.query(address, **args)
|
|
28
|
+
|
|
29
|
+
def initialize(register)
|
|
30
|
+
@register = register
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def available = register.entries.values
|
|
34
|
+
|
|
35
|
+
def install_namespace! = NamespaceInstaller.new(self).install!
|
|
36
|
+
|
|
37
|
+
def resolve(address)
|
|
38
|
+
fqn = Fqn.parse(address)
|
|
39
|
+
raise UnknownAddress, "router addresses require a realm: #{address.inspect}" unless fqn.realm
|
|
40
|
+
|
|
41
|
+
register.fetch(fqn.to_s)
|
|
42
|
+
rescue KeyError
|
|
43
|
+
raise UnknownAddress, "no Bluebook route for #{address.inspect}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def dispatch(address, **args)
|
|
47
|
+
entry = resolve(address)
|
|
48
|
+
raise WrongVerbKind, "#{address.inspect} names a query; use #query" unless entry.command?
|
|
49
|
+
|
|
50
|
+
entry.dispatcher.dispatch(local_verb(entry), **args)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def query(address, **args)
|
|
54
|
+
entry = resolve(address)
|
|
55
|
+
raise WrongVerbKind, "#{address.inspect} names a command; use #dispatch" unless entry.query?
|
|
56
|
+
|
|
57
|
+
entry.dispatcher.query(local_verb(entry), **args)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def local_verb(entry)
|
|
63
|
+
return "#{entry.fqn.domain}.#{entry.declared_verb}" unless entry.fqn.aggregate
|
|
64
|
+
|
|
65
|
+
"#{entry.fqn.domain}::#{entry.fqn.aggregate}.#{entry.declared_verb}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
require_relative "router/namespace_installer"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Runtime
|
|
3
|
+
# WHO IS DISPATCHING, bound for the duration of a block — the ambient
|
|
4
|
+
# counterpart to `Runtime.with_registry`, but `Thread.current`-backed
|
|
5
|
+
# rather than a plain module ivar: registry-binding happens at BOOT
|
|
6
|
+
# time (single-threaded by construction), this happens at DISPATCH
|
|
7
|
+
# time, which a future per-request caller (docs/rails-integration.md's
|
|
8
|
+
# still-unbuilt `WebDoor`) needs to bind safely under concurrency.
|
|
9
|
+
#
|
|
10
|
+
# A child `Thread.new` spawned mid-block does not inherit this — plain
|
|
11
|
+
# Ruby thread-local semantics — but nothing in this codebase spawns a
|
|
12
|
+
# worker thread mid-dispatch today, so this is a fact worth naming
|
|
13
|
+
# rather than a gap worth solving.
|
|
14
|
+
module Caller
|
|
15
|
+
# `actor_id` is OPTIONAL, on top of the role that has always been
|
|
16
|
+
# here — a caller that states only a role (every caller before this)
|
|
17
|
+
# is checked the way it always was, string equality against the
|
|
18
|
+
# command's own `role`. A caller that also names WHO it is lets
|
|
19
|
+
# `CommandRules::Authorization` check a real Governance
|
|
20
|
+
# `RoleAssignment` instead, once the command's domain has Governance
|
|
21
|
+
# attached — see that module's own header for the full split.
|
|
22
|
+
Current = Struct.new(:role, :actor_id, keyword_init: true)
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
def current = Thread.current[:hecks_caller]
|
|
27
|
+
|
|
28
|
+
def as(role:, actor_id: nil)
|
|
29
|
+
previous = Thread.current[:hecks_caller]
|
|
30
|
+
Thread.current[:hecks_caller] = Current.new(role: role.to_s, actor_id: actor_id&.to_s)
|
|
31
|
+
yield
|
|
32
|
+
ensure
|
|
33
|
+
Thread.current[:hecks_caller] = previous
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# A REACTION IS THE SYSTEM ACTING, not the caller who happened to be
|
|
37
|
+
# on the stack when the triggering command ran — `Dispatcher#reenter`
|
|
38
|
+
# clears the ambient caller around a reaction's own dispatch so a
|
|
39
|
+
# triggering caller's role can neither satisfy nor block a reaction
|
|
40
|
+
# command it has nothing to do with.
|
|
41
|
+
def without
|
|
42
|
+
previous = Thread.current[:hecks_caller]
|
|
43
|
+
Thread.current[:hecks_caller] = nil
|
|
44
|
+
yield
|
|
45
|
+
ensure
|
|
46
|
+
Thread.current[:hecks_caller] = previous
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Runtime
|
|
3
|
+
# WHICH PORTS A BOOT CAN ACTUALLY FULFILL, read off the registry it already
|
|
4
|
+
# holds. `registry.ports` names every port a domain declared a dependency
|
|
5
|
+
# on ; `registry.adapters` names every adapter wired to implement one — the
|
|
6
|
+
# same `adapter.port == port.name` match `Ports::Extraction` and
|
|
7
|
+
# `Ports::IdentityGeneration` already make for themselves, one at a time,
|
|
8
|
+
# each time they resolve. This is that same question asked once, for
|
|
9
|
+
# every port at once, so a gap in the wiring is something a caller can ASK
|
|
10
|
+
# about rather than something a live dispatch discovers by refusing.
|
|
11
|
+
#
|
|
12
|
+
# `cycles` answers `[]`, always, and honestly: nothing in this port model
|
|
13
|
+
# lets one port depend on another — a port names a verb and a signal, an
|
|
14
|
+
# adapter names the port it implements, and neither can point at a third
|
|
15
|
+
# port. There is no edge for a cycle to be made of.
|
|
16
|
+
class CapabilityGraph
|
|
17
|
+
def initialize(registry)
|
|
18
|
+
@registry = registry
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# { port name => [adapter name, ...] }, for every port the registry
|
|
22
|
+
# declares — including the ports nothing implements, so a caller can
|
|
23
|
+
# tell "declared, unfulfilled" from "never declared at all".
|
|
24
|
+
def fulfillments
|
|
25
|
+
@fulfillments ||= @registry.ports.each_key.to_h { |name| [name, adapters_for(name)] }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The ports with zero adapters bound — the gap `Runtime::WiringError`
|
|
29
|
+
# would otherwise only surface at the moment something tries to dispatch
|
|
30
|
+
# through one.
|
|
31
|
+
def unfulfilled
|
|
32
|
+
fulfillments.select { |_, adapters| adapters.empty? }.keys
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def cycles = []
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def adapters_for(port_name)
|
|
40
|
+
@registry.adapters.values.select { |adapter| adapter.port == port_name }.map(&:name)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require_relative "../../naming"
|
|
2
|
+
require_relative "../errors"
|
|
3
|
+
require_relative "../refusal_wording"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Runtime
|
|
7
|
+
class CommandInterpreter
|
|
8
|
+
# The payload gate: a command takes the arguments it declares — all of
|
|
9
|
+
# them, and no others.
|
|
10
|
+
module ArgumentGate
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Anything else used to ride along in the payload untouched —
|
|
14
|
+
# normalize_args walks the DECLARED attributes, so a name the command
|
|
15
|
+
# never had was simply never looked at. A misspelled argument did
|
|
16
|
+
# nothing, in silence.
|
|
17
|
+
#
|
|
18
|
+
# The keys that are legitimately not attributes are the ones that ADDRESS
|
|
19
|
+
# the aggregate rather than describe it : `id`, whatever the aggregate is
|
|
20
|
+
# identified by, and the reference key of the root a command reaches
|
|
21
|
+
# through. Refusing those would refuse every dispatch there is.
|
|
22
|
+
def refuse_unknown_arguments(domain, aggregate, command, args)
|
|
23
|
+
addressing = [:id, *aggregate.identity_heads, reference_key(command)] + correlation_keys(domain)
|
|
24
|
+
known = (command.attributes.map(&:name) + addressing).compact.map(&:to_sym)
|
|
25
|
+
# SORTED. Payload order is whatever the caller happened to write, and
|
|
26
|
+
# refusal wording is contract — pinned byte-for-byte by the corpus, so
|
|
27
|
+
# it cannot depend on hash iteration order.
|
|
28
|
+
unknown = (args.keys.map(&:to_sym) - known).sort
|
|
29
|
+
return if unknown.empty?
|
|
30
|
+
|
|
31
|
+
raise UnknownArgument,
|
|
32
|
+
RefusalWording.render("UnknownArgument", "unknown_args",
|
|
33
|
+
command: command.hecks_name, unknown: unknown.join(", "),
|
|
34
|
+
declared: declared_reading(command))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# And it takes ALL of them. The other half of the same sentence, missing
|
|
38
|
+
# until fuzz went looking : a name the command never declared was refused,
|
|
39
|
+
# while a name it DID declare could simply be left out.
|
|
40
|
+
#
|
|
41
|
+
# `Customer.Register` without its `name` used to be refused — but by
|
|
42
|
+
# ACCIDENT, and with a lie for a message. `then_set :name, to: :name` found
|
|
43
|
+
# nothing to resolve, passed the literal symbol on, and coercion reported
|
|
44
|
+
# `name is a PersonName — pass its fields as an object`, which describes a
|
|
45
|
+
# mistake the caller did not make. The real mistake — an argument simply
|
|
46
|
+
# missing — was never the one named, and nothing refused it on purpose.
|
|
47
|
+
#
|
|
48
|
+
# No command attribute anywhere in the corpus carries a default — checked,
|
|
49
|
+
# all eight chapters, zero — so there is no optional argument for this to
|
|
50
|
+
# step on. Every declared attribute is a fact the command needs.
|
|
51
|
+
#
|
|
52
|
+
# `aggregate:` — PRESENT ONLY FOR A PORT OPERATION. An aggregate
|
|
53
|
+
# command's own self-address never reaches this list at all
|
|
54
|
+
# (`CommandBuilder#reference_to`'s bare self-reference mints no
|
|
55
|
+
# attribute to be absent) — but `PortOperationBuilder#reference_to`
|
|
56
|
+
# ALWAYS mints one (this file's own header on `PortOperation`: "no
|
|
57
|
+
# creates?/acts_on distinction to protect"), because an operation
|
|
58
|
+
# historically had no OTHER way to say which record it addressed.
|
|
59
|
+
# `to:` is that other way now (`Dispatcher#port_invocation`
|
|
60
|
+
# promotes the identity attribute's own value out of the payload
|
|
61
|
+
# and into routing) — which left the attribute still DECLARED,
|
|
62
|
+
# still non-optional, and now never present in `args` at all: every
|
|
63
|
+
# operation with an identity attribute refused its own well-formed
|
|
64
|
+
# calls, dispatched exactly the way `to:` intends. Exempted here on
|
|
65
|
+
# the same terms an aggregate command's self-address always was —
|
|
66
|
+
# an address, not a fact the operation still needs handed back.
|
|
67
|
+
def refuse_absent_arguments(command, args, aggregate: nil)
|
|
68
|
+
given = args.keys.map(&:to_sym)
|
|
69
|
+
exempt = aggregate && command.respond_to?(:identity_attribute) &&
|
|
70
|
+
command.identity_attribute(aggregate.hecks_name)&.name
|
|
71
|
+
required = command.attributes.reject(&:optional?).map { |attribute| attribute.name.to_sym } - [exempt]
|
|
72
|
+
# SORTED, for the same reason the unknown list is : refusal wording is
|
|
73
|
+
# contract, and a pinned wording cannot depend on the order a set
|
|
74
|
+
# difference happens to be computed in.
|
|
75
|
+
absent = (required - given).sort
|
|
76
|
+
return if absent.empty?
|
|
77
|
+
|
|
78
|
+
raise AbsentArgument,
|
|
79
|
+
RefusalWording.render("AbsentArgument", "absent_args",
|
|
80
|
+
command: command.hecks_name, absent: absent.join(", "),
|
|
81
|
+
declared: declared_reading(command))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# A COMMAND THAT DECLARES NOTHING STILL HAS TO SAY SO. `Account
|
|
85
|
+
# .Freeze` is `reference_to Account` and no attributes at all, so
|
|
86
|
+
# `{declared}` rendered empty and the sentence trailed off mid-
|
|
87
|
+
# clause : "Freeze does not declare standing — it takes ". Read
|
|
88
|
+
# against a real refusal that shape is worse than unhelpful — it
|
|
89
|
+
# looks like the message itself is broken, right where a caller
|
|
90
|
+
# is already trying to work out what went wrong.
|
|
91
|
+
#
|
|
92
|
+
# Only the empty case changes. Every command that declares even
|
|
93
|
+
# one attribute renders exactly as it did before, because refusal
|
|
94
|
+
# wording is contract and pinned byte-for-byte by the corpus (see
|
|
95
|
+
# `refuse_unknown_arguments`' own note on sorting for why).
|
|
96
|
+
def declared_reading(command)
|
|
97
|
+
declared = command.attributes.map(&:name)
|
|
98
|
+
declared.empty? ? "none" : declared.join(", ")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# What a process manager correlates by is ROUTING, not description. A saga
|
|
102
|
+
# threads its correlation key through every leg it dispatches so the event
|
|
103
|
+
# each leg emits carries it and the next step can be correlated — so the key
|
|
104
|
+
# arrives on commands that never declare it, and legitimately.
|
|
105
|
+
#
|
|
106
|
+
# This is the weakest part of the gate. Correlation is the SAGA's business,
|
|
107
|
+
# and the better shape is for the saga to stamp its own key onto the event
|
|
108
|
+
# it caused rather than smuggle it through the command's payload. Until it
|
|
109
|
+
# does, refusing the key here would break every saga in the corpus.
|
|
110
|
+
def correlation_keys(domain)
|
|
111
|
+
Array(@registry.bluebook(domain)&.process_managers)
|
|
112
|
+
.filter_map { |saga| saga.correlates_by && saga.correlation_head }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def reference_key(command)
|
|
116
|
+
target = command.references.to_s
|
|
117
|
+
return nil if target.empty?
|
|
118
|
+
|
|
119
|
+
Naming.reference_key(target)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
require_relative "../value"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Runtime
|
|
5
|
+
class CommandInterpreter
|
|
6
|
+
# How a command's declared mutations land on the instance in hand —
|
|
7
|
+
# set, append (with value-object or entity elements), and the
|
|
8
|
+
# arithmetic pair.
|
|
9
|
+
module MutationApplier
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def assign_creation_attributes(instance, aggregate, command, args)
|
|
13
|
+
command.attributes.each do |attr|
|
|
14
|
+
next unless aggregate.attribute(attr.name)
|
|
15
|
+
next unless args.key?(attr.name)
|
|
16
|
+
|
|
17
|
+
instance[attr.name] = Value.for(aggregate, attr.name, args[attr.name])
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def apply(instance, aggregate, mutation, args)
|
|
22
|
+
case mutation.op
|
|
23
|
+
when :set
|
|
24
|
+
value = mutation.source.is_a?(StateRef) ? instance[mutation.source.name] : @rules.resolve_source(mutation.source, args)
|
|
25
|
+
instance[mutation.target] = Value.for(aggregate, mutation.target, value)
|
|
26
|
+
when :append
|
|
27
|
+
instance[mutation.target] = appended(instance, aggregate, mutation, args)
|
|
28
|
+
when :increment, :decrement
|
|
29
|
+
amount = @rules.resolve_source(mutation.source, args)
|
|
30
|
+
attribute = aggregate.attribute(mutation.target)
|
|
31
|
+
current = instance[mutation.target]
|
|
32
|
+
# Vendored fix, not (yet) upstream hecks (migration plan
|
|
33
|
+
# task 9): see #rewrap_arithmetic_result's own comment below
|
|
34
|
+
# -- `amount` is wrapped ONLY when `current` already is, not
|
|
35
|
+
# merely because the target attribute exists.
|
|
36
|
+
amount = Value.for_attribute(aggregate, attribute, amount) if attribute && current.is_a?(Value)
|
|
37
|
+
result = @rules.arithmetic(current, amount, mutation.target, @rules.sign_of(mutation.op))
|
|
38
|
+
instance[mutation.target] = rewrap_arithmetic_result(aggregate, attribute, current, result)
|
|
39
|
+
# Vendored addition, not (yet) upstream hecks (migration
|
|
40
|
+
# plan task 4): remove -- the list-removal counterpart to
|
|
41
|
+
# append, matching an element by value (plan.bluebook's
|
|
42
|
+
# RemoveDependency/DeactivateSprint: "a concurrent Add can
|
|
43
|
+
# never be lost").
|
|
44
|
+
when :remove
|
|
45
|
+
instance[mutation.target] = removed(instance, aggregate, mutation, args)
|
|
46
|
+
# Vendored addition, not (yet) upstream hecks (migration
|
|
47
|
+
# plan task 4, i106): multiply/clamp, the scale/bound pair
|
|
48
|
+
# alongside increment/decrement's add/subtract pair -- see
|
|
49
|
+
# CommandRules::Arithmetic#multiply/#clamp's own comments.
|
|
50
|
+
when :multiply
|
|
51
|
+
amount = @rules.resolve_source(mutation.source, args)
|
|
52
|
+
attribute = aggregate.attribute(mutation.target)
|
|
53
|
+
current = instance[mutation.target]
|
|
54
|
+
amount = Value.for_attribute(aggregate, attribute, amount) if attribute && current.is_a?(Value)
|
|
55
|
+
result = @rules.multiply(current, amount, mutation.target)
|
|
56
|
+
instance[mutation.target] = rewrap_arithmetic_result(aggregate, attribute, current, result)
|
|
57
|
+
# Vendored addition, not (yet) upstream hecks (migration
|
|
58
|
+
# plan task 4, i106): clamp -- bounds the current value into
|
|
59
|
+
# [min, max]. mutation.source is always a literal [min, max]
|
|
60
|
+
# pair, never an argument reference -- resolve_source would be
|
|
61
|
+
# a no-op for an Array (it only special-cases Symbol), so it's
|
|
62
|
+
# read straight.
|
|
63
|
+
when :clamp
|
|
64
|
+
instance[mutation.target] = @rules.clamp(instance[mutation.target], mutation.source, mutation.target)
|
|
65
|
+
# `delegates_to` — CommandBuilder#delegates_to's own comment gives
|
|
66
|
+
# the full reasoning for storing it as a mutation at all. A REAL
|
|
67
|
+
# no-op here, not a gap: it targets no field on THIS instance —
|
|
68
|
+
# `CommandInterpreter#step_delegate_to_entity`, a LATER step in
|
|
69
|
+
# the same dispatch, is what actually applies it, against the
|
|
70
|
+
# target entity element `EntityElement.apply_to_element` reaches,
|
|
71
|
+
# never through this method.
|
|
72
|
+
when :delegate
|
|
73
|
+
nil
|
|
74
|
+
else
|
|
75
|
+
# Every declared op has a `when` above — this is not a real
|
|
76
|
+
# runtime path today, only a backstop against the day one
|
|
77
|
+
# more mutation kind reaches the grammar and this method,
|
|
78
|
+
# unlike every other one it could reach, does not: applying
|
|
79
|
+
# nothing and refusing nothing would be the one silent
|
|
80
|
+
# no-op in a language that otherwise refuses what it cannot
|
|
81
|
+
# check.
|
|
82
|
+
raise Runtime::WiringError, "no mutation applier handles :#{mutation.op} — add one before declaring it"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# A CALLER-SUPPLIED ARG, FIRST -- an append's own field can also
|
|
87
|
+
# name something the SUBJECT ALREADY KNOWS about itself, falling
|
|
88
|
+
# back to the AGGREGATE'S OWN CURRENT FIELD when it isn't one — a
|
|
89
|
+
# caller appending at the end without computing or supplying a
|
|
90
|
+
# position (`position: :next_position`, a field the command never
|
|
91
|
+
# declares as an argument at all). `args.key?`, not a truthiness
|
|
92
|
+
# check on the value — an explicitly-nil argument still counts as
|
|
93
|
+
# "the caller named it," same distinction `assign_creation_attributes`
|
|
94
|
+
# already draws.
|
|
95
|
+
#
|
|
96
|
+
# Extracted to its own method (pure refactor, ternary -> early
|
|
97
|
+
# return, no behavior change) alongside `remove`'s own addition
|
|
98
|
+
# below, purely for readability at this point in the file.
|
|
99
|
+
def resolve_append_source(source, instance, args)
|
|
100
|
+
# `state(:field)` — the record's own value, never an argument
|
|
101
|
+
# (`Literal::StateRef`'s own comment).
|
|
102
|
+
return instance[source.name] if source.is_a?(StateRef)
|
|
103
|
+
return source unless source.is_a?(Symbol)
|
|
104
|
+
return args[source] if args.key?(source)
|
|
105
|
+
|
|
106
|
+
instance[source]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def appended(instance, aggregate, mutation, args)
|
|
110
|
+
fields = mutation.source.transform_values { |source| resolve_append_source(source, instance, args) }
|
|
111
|
+
element_type = aggregate.attribute(mutation.target)&.type
|
|
112
|
+
value_object = aggregate.value_object(element_type)
|
|
113
|
+
if value_object
|
|
114
|
+
value_object.attributes.each do |attribute|
|
|
115
|
+
held = fields[attribute.name]
|
|
116
|
+
# A SINGLE-FIELD VALUE unwraps to its scalar so it bridges
|
|
117
|
+
# into the element's own (differently named) wrapper; a
|
|
118
|
+
# MULTI-FIELD one (a `state(:en_passant_square)` Square
|
|
119
|
+
# copied off the record) has no scalar to stand in for it
|
|
120
|
+
# and is handed across whole — `Value.for_attribute` keeps a
|
|
121
|
+
# value of the element field's own type as it is.
|
|
122
|
+
fields[attribute.name] = Value.scalar(held) if held.is_a?(Value) && held.to_h.size == 1
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
element = value_object ? Value.build(value_object, fields,
|
|
126
|
+
aggregate) : entity_element(aggregate, element_type, instance[mutation.target],
|
|
127
|
+
fields)
|
|
128
|
+
|
|
129
|
+
# FROZEN, like every other value the domain hands back. An
|
|
130
|
+
# appended list used to come back mutable, so a caller could
|
|
131
|
+
# push straight into an aggregate's own state after the
|
|
132
|
+
# dispatch had finished.
|
|
133
|
+
Freezer.deep(Array(instance[mutation.target]) + [element])
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Vendored addition, not (yet) upstream hecks (migration plan
|
|
137
|
+
# task 4): the removal counterpart to #appended -- matches by
|
|
138
|
+
# VALUE EQUALITY, element-wise, no read-modify-write (plan.
|
|
139
|
+
# bluebook's own words: "so a concurrent Add can never be lost").
|
|
140
|
+
# `mutation.source` is a single field reference (`:dependency`),
|
|
141
|
+
# unlike append's field-map -- resolved and Value-coerced the
|
|
142
|
+
# SAME way increment/decrement already coerce their own amount,
|
|
143
|
+
# so the comparison is against a like-shaped Value, not a raw
|
|
144
|
+
# scalar against a wrapped one.
|
|
145
|
+
def removed(instance, aggregate, mutation, args)
|
|
146
|
+
value = @rules.resolve_source(mutation.source, args)
|
|
147
|
+
attribute = aggregate.attribute(mutation.target)
|
|
148
|
+
value = Value.for_attribute(aggregate, attribute, value) if attribute
|
|
149
|
+
Array(instance[mutation.target]).reject { |element| element == value }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Vendored fix, not (yet) upstream hecks (migration plan
|
|
153
|
+
# task 9): #apply's `:increment`/`:decrement`/`:multiply`
|
|
154
|
+
# branches used to wrap `amount` into a `Value` unconditionally
|
|
155
|
+
# whenever the target attribute existed, never checking whether
|
|
156
|
+
# `current` (the field's OWN existing value, read straight off
|
|
157
|
+
# `instance[mutation.target]`) was ALSO wrapped -- the two sides
|
|
158
|
+
# of the same arithmetic call could disagree on Value-ness. On a
|
|
159
|
+
# PHANTOM-CREATED field this is the common case, not an edge
|
|
160
|
+
# one: `Instance.defaults`/`#default_for` leaves a VO-typed
|
|
161
|
+
# attribute with no declared `default:` genuinely absent (nil),
|
|
162
|
+
# and `CommandRules::Arithmetic#arithmetic`/`#multiply`'s own
|
|
163
|
+
# `current ||= 0` then turns that nil into a RAW, unwrapped
|
|
164
|
+
# Integer `0` -- so `current.is_a?(Value) && amount.is_a?(Value)`
|
|
165
|
+
# read false even though `amount` (correctly wrapped by the old
|
|
166
|
+
# unconditional line) genuinely held a valid, correctly-typed
|
|
167
|
+
# number, and the primitive path's `unless amount.is_a?(Numeric)`
|
|
168
|
+
# guard refused it as a TYPE MISMATCH the caller never made --
|
|
169
|
+
# an artifact of this method's own asymmetric wrapping, not bad
|
|
170
|
+
# input.
|
|
171
|
+
#
|
|
172
|
+
# Fixed at the call site (above) by wrapping `amount` ONLY when
|
|
173
|
+
# `current` is ALREADY a `Value` -- so an established VO-typed
|
|
174
|
+
# field (a multi-field Money balance, say, already hydrated from
|
|
175
|
+
# a prior save) keeps going through
|
|
176
|
+
# `Arithmetic#arithmetic_value_object` exactly as before (the
|
|
177
|
+
# "already-Value-wrapped field still mutates correctly" case
|
|
178
|
+
# this fix must not regress), while a phantom field's raw
|
|
179
|
+
# `current` and raw `amount` both take the plain-Numeric path
|
|
180
|
+
# together. This method closes the other half: the plain-Numeric
|
|
181
|
+
# path returns a bare Ruby number, and if the attribute is
|
|
182
|
+
# itself VO-typed (the norm), that raw result needs the SAME
|
|
183
|
+
# wrap `:set` already gives its own resolved value (`Value.for`)
|
|
184
|
+
# before it's stored, so a field's stored shape doesn't depend
|
|
185
|
+
# on which dispatch happened to mutate it first. A no-op
|
|
186
|
+
# whenever `current` was already a Value (the VO branch already
|
|
187
|
+
# returns one) or the mutation targets no declared attribute at
|
|
188
|
+
# all.
|
|
189
|
+
def rewrap_arithmetic_result(aggregate, attribute, current, result)
|
|
190
|
+
return result if current.is_a?(Value) || attribute.nil? || result.is_a?(Value)
|
|
191
|
+
|
|
192
|
+
Value.for_attribute(aggregate, attribute, result)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def entity_element(aggregate, element_type, current, fields)
|
|
196
|
+
entity = aggregate.entities.find { |piece| piece.hecks_name == element_type.to_s }
|
|
197
|
+
return fields unless entity
|
|
198
|
+
|
|
199
|
+
entity.attributes.each do |attribute|
|
|
200
|
+
next unless fields.key?(attribute.name)
|
|
201
|
+
|
|
202
|
+
fields[attribute.name] = Value.for_attribute(aggregate, attribute, fields[attribute.name])
|
|
203
|
+
end
|
|
204
|
+
if entity.identified_by && !fields.key?(entity.identified_by)
|
|
205
|
+
attribute = entity.attribute(entity.identified_by)
|
|
206
|
+
fields[entity.identified_by] = Value.from_identifier(aggregate, attribute, Array(current).size + 1)
|
|
207
|
+
else
|
|
208
|
+
check_entity_collision(aggregate, entity, current, fields)
|
|
209
|
+
end
|
|
210
|
+
fields[entity.lifecycle.field] ||= entity.lifecycle.default if entity.lifecycle
|
|
211
|
+
fields
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# THE SAME CHECK #hydrate GIVES EVERY CREATING AGGREGATE COMMAND
|
|
215
|
+
# (`repository.find(id)`, above this file in command_interpreter.rb),
|
|
216
|
+
# one level down. Reached only on the two branches #entity_element
|
|
217
|
+
# does NOT auto-mint: a CALLER-SUPPLIED identity (the field is
|
|
218
|
+
# already in the append's own field map, so the `if` above skips
|
|
219
|
+
# it) or a COMPOSITE one (`entity.identified_by` is nil for those —
|
|
220
|
+
# Runtime::Identified#derive_identity — so the `if` above is false
|
|
221
|
+
# unconditionally). Neither used to check the sibling list at all:
|
|
222
|
+
# a second LogVisit with the same date+sequence, or a second
|
|
223
|
+
# IssueKey with the same serial, appended a silent duplicate — worse
|
|
224
|
+
# than an ordinary duplicate row, because EntityInterpreter#element_of's
|
|
225
|
+
# `find_index` always matches the FIRST match, so the second becomes
|
|
226
|
+
# permanently unaddressable by any later command.
|
|
227
|
+
#
|
|
228
|
+
# Auto-minted entities never reach here — `identity_heads` for them
|
|
229
|
+
# is still checked at mint time by construction (`current.size + 1`
|
|
230
|
+
# can only repeat if something `remove:`s from the list between
|
|
231
|
+
# mints, which no real domain does today), so they can't be flagged
|
|
232
|
+
# by mistake.
|
|
233
|
+
def check_entity_collision(aggregate, entity, current, fields)
|
|
234
|
+
heads = entity.identity_heads
|
|
235
|
+
return if heads.empty?
|
|
236
|
+
|
|
237
|
+
collision = Array(current).find { |element| heads.all? { |head| element[head] == fields[head] } }
|
|
238
|
+
return unless collision
|
|
239
|
+
|
|
240
|
+
raise(AlreadyExists, RefusalWording.render("AlreadyExists", "entity_duplicate",
|
|
241
|
+
entity: entity.hecks_name, aggregate: aggregate.hecks_name,
|
|
242
|
+
identity: Identity.reading(entity),
|
|
243
|
+
offered: heads.map { |head| Rendering.describe(fields[head]) }.join(", ")))
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|