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,497 @@
|
|
|
1
|
+
require_relative "interpreting"
|
|
2
|
+
require_relative "command_interpreter/argument_gate"
|
|
3
|
+
require_relative "command_interpreter/mutation_applier"
|
|
4
|
+
require_relative "../rendering"
|
|
5
|
+
require_relative "errors"
|
|
6
|
+
require_relative "identity"
|
|
7
|
+
require_relative "dependency_planning"
|
|
8
|
+
require_relative "../ports/persistence/execution"
|
|
9
|
+
require_relative "instance"
|
|
10
|
+
require_relative "refusal_wording"
|
|
11
|
+
require_relative "entity_element"
|
|
12
|
+
require_relative "rebuild_sweep"
|
|
13
|
+
|
|
14
|
+
module Hecks
|
|
15
|
+
module Runtime
|
|
16
|
+
# The dispatch pipeline for a command on an aggregate head. The payload
|
|
17
|
+
# gate lives in command_interpreter/argument_gate.rb, the mutation walk
|
|
18
|
+
# in command_interpreter/mutation_applier.rb; what stays here is the
|
|
19
|
+
# order of the steps and how a record is addressed.
|
|
20
|
+
class CommandInterpreter
|
|
21
|
+
include Interpreting
|
|
22
|
+
include ArgumentGate
|
|
23
|
+
include MutationApplier
|
|
24
|
+
|
|
25
|
+
attr_reader :registry
|
|
26
|
+
|
|
27
|
+
# THE DECLARED ORDER, HAND-TYPED — mirrors Vocabulary::AggregateDispatchOrder
|
|
28
|
+
# (language/bluebook/vocabulary.bluebook:188-205), held equal to it by
|
|
29
|
+
# spec/vocabulary_conformance_spec.rb the same way every other vocabulary
|
|
30
|
+
# in that file is (RefusalWording::TEMPLATES, CommandRules::MUTATION_OPS,
|
|
31
|
+
# ...) rather than read live off the meta-domain at every dispatch —
|
|
32
|
+
# Runtime::RefusalWording's own doc comment gives the same reason.
|
|
33
|
+
DISPATCH_ORDER = Hecks::Vocabulary.symbols("AggregateDispatchOrder")
|
|
34
|
+
|
|
35
|
+
# A LAST-RESORT SAFETY VALVE, NOT THE NORMAL OUTCOME PATH — see
|
|
36
|
+
# `Runtime::StaleWrite`'s own comment. Two concurrent writers
|
|
37
|
+
# against one aggregate resolve through exactly one retry in the
|
|
38
|
+
# ordinary case (the loser's retried hydrate reads the winner's now-
|
|
39
|
+
# committed state and its own `given` refuses for real, raising
|
|
40
|
+
# `GivenNotMet`, not `StaleWrite`) — this cap exists for pathological
|
|
41
|
+
# contention (many concurrent writers on one hot aggregate), not the
|
|
42
|
+
# two-writer case.
|
|
43
|
+
MAX_STALE_WRITE_RETRIES = 5
|
|
44
|
+
|
|
45
|
+
# EVERY CROSS-STEP LOCAL `call` used to thread through its own literal
|
|
46
|
+
# sequence, held in one place now that the sequence is data-driven —
|
|
47
|
+
# `result` and `transition`/`old_state` default to nil until the step
|
|
48
|
+
# that sets them runs, same as they were unset locals before that point.
|
|
49
|
+
Context = Struct.new(:domain, :aggregate, :command, :args, :repository, :instance, :transition, :old_state,
|
|
50
|
+
:result, :correlation, :route, :plan, :strategy, :persistence_outcome, :delegated_events,
|
|
51
|
+
:dry_run)
|
|
52
|
+
|
|
53
|
+
def initialize(registry, rules:)
|
|
54
|
+
@registry = registry
|
|
55
|
+
@rules = rules
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# `dry_run:` — Dispatcher#dry_run?'s own entry point. Every step up
|
|
59
|
+
# through enforce_ensures/enforce_invariants runs exactly as a real
|
|
60
|
+
# dispatch would (givens checked, mutations applied to `ctx.instance`
|
|
61
|
+
# in memory); `step_save`/`step_emit` are the only two that read this
|
|
62
|
+
# flag, each skipping its own real work — see their own comments.
|
|
63
|
+
# RETRIES THE WHOLE METHOD BODY on `StaleWrite` — a fresh `ctx`, a
|
|
64
|
+
# fresh `step_hydrate` re-reading current state, so `enforce_givens`
|
|
65
|
+
# re-evaluates against reality rather than the snapshot that just
|
|
66
|
+
# went stale. See `MAX_STALE_WRITE_RETRIES`/`Runtime::StaleWrite`
|
|
67
|
+
# for why exhaustion is a pathological-contention signal, not the
|
|
68
|
+
# expected shape of a two-writer race.
|
|
69
|
+
def call(domain, aggregate, command, args, correlation = nil, route: nil, dry_run: false)
|
|
70
|
+
attempt = 0
|
|
71
|
+
begin
|
|
72
|
+
ctx = Context.new(domain, aggregate, command, args)
|
|
73
|
+
ctx.correlation = correlation
|
|
74
|
+
ctx.route = route
|
|
75
|
+
ctx.dry_run = dry_run
|
|
76
|
+
ctx.plan = DependencyPlanning::Analyzer.call(aggregate: aggregate, command: command)
|
|
77
|
+
# RESOLVED HERE, ONCE, BEFORE HYDRATION — `Registry#repository`
|
|
78
|
+
# memoizes, so this and `step_hydrate`'s own read of `ctx.repository`
|
|
79
|
+
# (no second fetch there any more) always name the same instance;
|
|
80
|
+
# the isolation decision below (lock vs. CAS+retry) needs the
|
|
81
|
+
# repository's capabilities before a single step runs.
|
|
82
|
+
ctx.repository = @registry.repository(domain, aggregate)
|
|
83
|
+
lock_id = Identity.best_effort(aggregate, args, route, reference_key: reference_key(command))
|
|
84
|
+
run_dispatch_order_with_isolation(DISPATCH_ORDER, ctx, lock_key_id: lock_id)
|
|
85
|
+
[ctx.instance, ctx.result, ctx.plan, ctx.persistence_outcome]
|
|
86
|
+
rescue StaleWrite
|
|
87
|
+
attempt += 1
|
|
88
|
+
retry if attempt < MAX_STALE_WRITE_RETRIES
|
|
89
|
+
raise
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def step_refuse_unknown_arguments(ctx)
|
|
96
|
+
step(:refuse_unknown_arguments) { refuse_unknown_arguments(ctx.domain, ctx.aggregate, ctx.command, ctx.args) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def step_refuse_absent_arguments(ctx)
|
|
100
|
+
step(:refuse_absent_arguments) { refuse_absent_arguments(ctx.command, ctx.args) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def step_normalize_args(ctx)
|
|
104
|
+
ctx.args = step(:normalize_args) { normalize_args(ctx.aggregate, ctx.command, ctx.args) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def step_refuse_role_mismatch(ctx)
|
|
108
|
+
step(:refuse_role_mismatch) { @rules.refuse_role_mismatch(ctx.command, ctx.domain) }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def step_resolve_references(ctx)
|
|
112
|
+
step(:resolve_references) { @rules.resolve_references(ctx.domain, ctx.command, ctx.args) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def step_hydrate(ctx)
|
|
116
|
+
# `ctx.repository` is resolved once, in `#call`, before the
|
|
117
|
+
# isolation decision (lock vs. CAS+retry) — not here any more.
|
|
118
|
+
ctx.strategy = ctx.plan.strategy_for(capabilities: ctx.repository.capabilities)
|
|
119
|
+
ctx.instance = step(:hydrate) {
|
|
120
|
+
if ctx.plan.complete_state? && ctx.plan.state_independent?
|
|
121
|
+
hydrate_complete_state(ctx.repository, ctx.aggregate, ctx.command, ctx.args, ctx.route, ctx.strategy)
|
|
122
|
+
elsif ctx.plan.complete_state?
|
|
123
|
+
hydrate_prior_or_initial(ctx.repository, ctx.aggregate, ctx.command, ctx.args, ctx.route)
|
|
124
|
+
elsif legacy_implicit_creation?(ctx)
|
|
125
|
+
hydrate_legacy_creation(ctx.repository, ctx.aggregate, ctx.command, ctx.args)
|
|
126
|
+
else
|
|
127
|
+
hydrate_existing(ctx.repository, ctx.aggregate, ctx.command, ctx.args, ctx.route)
|
|
128
|
+
end
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def step_enforce_givens(ctx)
|
|
133
|
+
step(:enforce_givens) {
|
|
134
|
+
# STRUCTURAL, before the declared givens — the same ordering
|
|
135
|
+
# NotFound/AlreadyExists already get at hydration: "does the
|
|
136
|
+
# fact this command's corrects names even exist" is not a
|
|
137
|
+
# domain rule an author wrote, it is a precondition for the
|
|
138
|
+
# domain rules to mean anything at all.
|
|
139
|
+
@rules.enforce_correction_target(ctx.instance, ctx.aggregate, ctx.command, domain: ctx.domain)
|
|
140
|
+
@rules.enforce_givens(ctx.instance, ctx.command, ctx.args, domain: ctx.domain,
|
|
141
|
+
declaring: ctx.aggregate, parent: ctx.instance)
|
|
142
|
+
}
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def step_admissible_transition(ctx)
|
|
146
|
+
ctx.transition = step(:admissible_transition) { @rules.admissible_transition(ctx.aggregate, ctx.command, ctx.instance) }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def step_assign_creation_attributes(ctx)
|
|
150
|
+
return unless legacy_implicit_creation?(ctx)
|
|
151
|
+
|
|
152
|
+
step(:assign_creation_attributes) { assign_creation_attributes(ctx.instance, ctx.aggregate, ctx.command, ctx.args) }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def step_apply_mutations(ctx)
|
|
156
|
+
# The state as the givens saw it — what `old` names inside an
|
|
157
|
+
# ensures. A shallow dup suffices: mutations REPLACE fields (set,
|
|
158
|
+
# arithmetic via Value#with, append builds a new array), never
|
|
159
|
+
# edit a held value in place.
|
|
160
|
+
ctx.old_state = ctx.instance.state.dup unless ctx.command.ensures.empty?
|
|
161
|
+
step(:apply_mutations) {
|
|
162
|
+
ctx.command.mutations.each { |mutation|
|
|
163
|
+
apply(ctx.instance, ctx.aggregate, mutation, ctx.args)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def step_advance_lifecycle(ctx)
|
|
169
|
+
return unless ctx.transition
|
|
170
|
+
|
|
171
|
+
step(:advance_lifecycle) { ctx.instance[ctx.aggregate.lifecycle.field] = ctx.transition.target }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# THE SYNCHRONOUS COUSIN OF A POLICY'S OWN `trigger` — see
|
|
175
|
+
# `CommandBuilder#delegates_to`'s own comment for the full reasoning.
|
|
176
|
+
# Runs AFTER this command's own mutations/lifecycle (so a delegating
|
|
177
|
+
# command could in principle still guard with its own `given`s first,
|
|
178
|
+
# though the real use in `domain/chess` declares none) and BEFORE
|
|
179
|
+
# `enforce_ensures`/`enforce_invariants`/`save`, so a refusal here
|
|
180
|
+
# raises a real, unrescued exception and NOTHING from either side —
|
|
181
|
+
# this command's own state, the target element's — has been saved
|
|
182
|
+
# yet. `ctx.instance` is the SAME in-memory record `step_hydrate`
|
|
183
|
+
# loaded and `step_save` will persist; `EntityElement.locate_chain`
|
|
184
|
+
# mutates it in place exactly the way `EntityInterpreter`'s own
|
|
185
|
+
# `step_locate_element`/`step_apply_mutations` mutate their OWN
|
|
186
|
+
# freshly-loaded copy — the only difference is WHICH already-in-
|
|
187
|
+
# memory record gets handed in.
|
|
188
|
+
def step_delegate_to_entity(ctx)
|
|
189
|
+
delegation = ctx.command.mutations.find { |mutation| mutation.op == :delegate }
|
|
190
|
+
return unless delegation
|
|
191
|
+
|
|
192
|
+
step(:delegate_to_entity) {
|
|
193
|
+
entity_name, _dot, command_name = delegation.target.to_s.rpartition(".")
|
|
194
|
+
entity = ctx.aggregate.entities.find { |e| e.hecks_name == entity_name } ||
|
|
195
|
+
raise(WiringError, "#{ctx.command.hecks_name} delegates_to #{entity_name}." \
|
|
196
|
+
"#{command_name}, but #{ctx.aggregate.hecks_name} has no " \
|
|
197
|
+
"entity named #{entity_name.inspect}")
|
|
198
|
+
target_command = entity.command(command_name) ||
|
|
199
|
+
raise(WiringError, "#{ctx.command.hecks_name} delegates_to " \
|
|
200
|
+
"#{entity_name}.#{command_name}, which " \
|
|
201
|
+
"#{entity_name} declares no such command")
|
|
202
|
+
|
|
203
|
+
# `with:` REMAPS, it does not ENUMERATE — starting from a copy
|
|
204
|
+
# of THIS command's own already-resolved args (`ctx.args`) and
|
|
205
|
+
# overlaying the explicit mapping on top means ambient context
|
|
206
|
+
# the caller never had to think about (the aggregate's own
|
|
207
|
+
# identity, addressed the ordinary way to reach `MoveKnight` at
|
|
208
|
+
# all) flows through to the target untouched, the same as it
|
|
209
|
+
# always would for a caller dispatching the entity command
|
|
210
|
+
# directly. Confirmed necessary, not a defensive guess: a real
|
|
211
|
+
# downstream domain's own AdvancePly-on-Moved policy silently
|
|
212
|
+
# failed to re-locate its OWN aggregate (`reaction_log`: "no
|
|
213
|
+
# Game with label.value ..."), because the emitted event's
|
|
214
|
+
# payload — built from `target_args` alone — never carried
|
|
215
|
+
# `label` at all when `with:` named only `id`/`to`.
|
|
216
|
+
target_args = ctx.args.merge(
|
|
217
|
+
delegation.source.to_h { |target_key, source_key| [target_key.to_sym, ctx.args[source_key]] }
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
element = EntityElement.locate_chain(ctx.aggregate, [entity], ctx.instance, target_args, command_name)
|
|
221
|
+
view = Instance.new(aggregate: entity, id: EntityElement.element_identity(entity, element).to_s, state: element)
|
|
222
|
+
|
|
223
|
+
@rules.enforce_givens(view, target_command, target_args, domain: ctx.domain, declaring: entity, parent: ctx.instance)
|
|
224
|
+
transition = @rules.admissible_transition(entity, target_command, view)
|
|
225
|
+
|
|
226
|
+
old_element = target_command.ensures.empty? ? nil : element.dup
|
|
227
|
+
target_command.mutations.each { |mutation|
|
|
228
|
+
EntityElement.apply_to_element(@rules, ctx.aggregate, entity, element, mutation, target_args)
|
|
229
|
+
}
|
|
230
|
+
element[entity.lifecycle.field] = transition.target if transition
|
|
231
|
+
|
|
232
|
+
settled = Instance.new(aggregate: entity, id: view.id, state: element)
|
|
233
|
+
@rules.enforce_ensures(settled, target_command, target_args, old: old_element, domain: ctx.domain, parent: ctx.instance)
|
|
234
|
+
|
|
235
|
+
ctx.delegated_events = @rules.emit(target_command, ctx.domain, ctx.aggregate, ctx.instance, target_args, ctx.repository)
|
|
236
|
+
}
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def step_enforce_ensures(ctx)
|
|
240
|
+
step(:enforce_ensures) {
|
|
241
|
+
@rules.enforce_ensures(ctx.instance, ctx.command, ctx.args, old: ctx.old_state,
|
|
242
|
+
domain: ctx.domain, parent: ctx.instance)
|
|
243
|
+
}
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def step_enforce_invariants(ctx)
|
|
247
|
+
step(:enforce_invariants) { @rules.enforce_invariants(ctx.instance, ctx.aggregate, domain: ctx.domain) }
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# `dry_run:` skips this — see Dispatcher#dry_run?'s own comment. The
|
|
251
|
+
# same conditional-skip shape `step_assign_creation_attributes`
|
|
252
|
+
# already has (`return unless ctx.command.creates?`), not a new
|
|
253
|
+
# pattern: a step that does not apply this time traces nothing,
|
|
254
|
+
# rather than a caller having to branch around it.
|
|
255
|
+
def step_save(ctx)
|
|
256
|
+
return if ctx.dry_run
|
|
257
|
+
|
|
258
|
+
step(:save) do
|
|
259
|
+
@rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state)
|
|
260
|
+
seed_projected_fields(ctx)
|
|
261
|
+
ctx.persistence_outcome = if ctx.strategy == DependencyPlanning::ATOMIC_PUT
|
|
262
|
+
# A SECOND CREATION IS NOT A FRESH ONE — see
|
|
263
|
+
# hydrate_legacy_creation's own comment; the
|
|
264
|
+
# same refusal, on the same terms, for the
|
|
265
|
+
# complete-state path. `insert_only:` asks the
|
|
266
|
+
# ADAPTER to decide and refuse ATOMICALLY
|
|
267
|
+
# (never writing a `creates?` command over an
|
|
268
|
+
# identity that already names a record) rather
|
|
269
|
+
# than this interpreter reading the record
|
|
270
|
+
# first to check — a `repository.find` before
|
|
271
|
+
# every atomic_put would be exactly the read
|
|
272
|
+
# this strategy exists to skip.
|
|
273
|
+
ctx.repository.atomic_put(ctx.instance, insert_only: ctx.command.creates?)
|
|
274
|
+
else
|
|
275
|
+
# `expected_version:` is `ctx.instance.version` — nil for a
|
|
276
|
+
# brand-new record (never read from storage) or when the
|
|
277
|
+
# repository isn't CAS-capable, either of which falls straight
|
|
278
|
+
# through to a plain, unconditional save inside `AppendOnly#save`.
|
|
279
|
+
ctx.repository.save(ctx.instance, expected_version: ctx.instance.version)
|
|
280
|
+
end
|
|
281
|
+
if ctx.persistence_outcome.status == :conflicted
|
|
282
|
+
raise(AlreadyExists, RefusalWording.render("AlreadyExists", "creating_duplicate",
|
|
283
|
+
command: ctx.command.hecks_name, aggregate: ctx.aggregate.hecks_name,
|
|
284
|
+
identity: identity_reading(ctx.aggregate),
|
|
285
|
+
offered: Rendering.describe(ctx.instance.id)))
|
|
286
|
+
elsif ctx.persistence_outcome.status == :stale
|
|
287
|
+
# NOT a `RefusalWording.render` call — this is not a declared
|
|
288
|
+
# vocabulary refusal, just a plain, informative message. See
|
|
289
|
+
# `Runtime::StaleWrite`'s own comment: caught by `#call`'s
|
|
290
|
+
# retry loop, re-raised only once retries are exhausted.
|
|
291
|
+
raise(StaleWrite,
|
|
292
|
+
"#{ctx.command.hecks_name} on #{ctx.aggregate.hecks_name} " \
|
|
293
|
+
"(#{identity_reading(ctx.aggregate)}: #{Rendering.describe(ctx.instance.id)}) lost a race — " \
|
|
294
|
+
"another write committed against this record after it was read")
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# THE ONE-TIME, SYNCHRONOUS HALF OF `projects` (S12, ADR 0025) —
|
|
300
|
+
# `RebuildSweep` (`runtime/rebuild_sweep.rb`) is deliberately the
|
|
301
|
+
# ONLY thing that keeps a projected field current against a
|
|
302
|
+
# target that changes AFTER this record was written — no reactive
|
|
303
|
+
# `Policy#for_each` keeping it live in real time, that stays
|
|
304
|
+
# deferred, same as that file's own header explains. But without
|
|
305
|
+
# SOME synchronous population, a projected field never gets an
|
|
306
|
+
# INITIAL value at all until an operator remembers to run a
|
|
307
|
+
# sweep by hand — every acting command reading it (`given
|
|
308
|
+
# ("customer is active") { customer_status == "active" }`, say)
|
|
309
|
+
# would refuse a freshly created, genuinely active record for no
|
|
310
|
+
# real reason, which is not the eventual-consistency tradeoff the
|
|
311
|
+
# ADR accepts, just a bug. So: every time a record with `projects`
|
|
312
|
+
# fields is about to save — creating or acting, either can be the
|
|
313
|
+
# first time a referenced record resolves — read each one ONCE,
|
|
314
|
+
# here, using the exact same `RebuildSweep.remote_value` a sweep
|
|
315
|
+
# itself would compute. This is still eventually consistent in
|
|
316
|
+
# the sense the ADR means: a change on the TARGET side after this
|
|
317
|
+
# save still needs a sweep to reach here. It is only ever
|
|
318
|
+
# SYNCHRONOUS with THIS record's own write, never a live read
|
|
319
|
+
# triggered by a `given`/`ensures`/`invariant` mid-dispatch — the
|
|
320
|
+
# boundary rule those enforce holds exactly as before.
|
|
321
|
+
def seed_projected_fields(ctx)
|
|
322
|
+
return if ctx.aggregate.projected_fields.empty?
|
|
323
|
+
|
|
324
|
+
ctx.aggregate.projected_fields.each do |field|
|
|
325
|
+
value = RebuildSweep.remote_value(@registry, ctx.domain, ctx.aggregate, ctx.instance.state, field)
|
|
326
|
+
next if value.nil?
|
|
327
|
+
|
|
328
|
+
ctx.instance.state[field.name] = value
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# A DELEGATING COMMAND EMITS NOTHING OF ITS OWN (`CommandBuilder#build`'s
|
|
333
|
+
# own guard refuses declaring `emits` alongside `delegates_to`) — its
|
|
334
|
+
# result IS whatever `step_delegate_to_entity` already collected from
|
|
335
|
+
# the target entity command's own `emits`, not a second, empty call
|
|
336
|
+
# into `@rules.emit` for a command with no announced events at all.
|
|
337
|
+
#
|
|
338
|
+
# `dry_run:` skips this too, same reasoning as `step_save` — nothing
|
|
339
|
+
# was committed, so `ctx.result` stays nil and `Dispatcher#dry_run?`
|
|
340
|
+
# never reads it (its own return value is just whether this raised).
|
|
341
|
+
def step_emit(ctx)
|
|
342
|
+
return if ctx.dry_run
|
|
343
|
+
|
|
344
|
+
ctx.result = step(:emit) {
|
|
345
|
+
# `ctx.delegated_events` is only ever set by `step_delegate_to_entity`,
|
|
346
|
+
# and only when this command carries a `:delegate` mutation — an
|
|
347
|
+
# empty Array (the target genuinely emitted nothing) is still
|
|
348
|
+
# truthy in Ruby, so this reads correctly either way.
|
|
349
|
+
next ctx.delegated_events if ctx.delegated_events
|
|
350
|
+
|
|
351
|
+
@rules.emit(ctx.command, ctx.domain, ctx.aggregate, ctx.instance, ctx.args, ctx.repository, ctx.correlation)
|
|
352
|
+
}
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def hydrate_existing(repository, aggregate, command, args, route = nil)
|
|
356
|
+
if route
|
|
357
|
+
found = repository.find(route.aggregate) ||
|
|
358
|
+
raise(NotFound, RefusalWording.render("NotFound", "record_missing",
|
|
359
|
+
aggregate: aggregate.hecks_name,
|
|
360
|
+
identity: identity_reading(aggregate),
|
|
361
|
+
offered: Rendering.describe(route.aggregate)))
|
|
362
|
+
return found.dup
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
id = identity_of(aggregate, args) ||
|
|
366
|
+
identity_from(aggregate, args, :id) ||
|
|
367
|
+
identity_from(aggregate, args, reference_key(command)) ||
|
|
368
|
+
raise(NotFound, RefusalWording.render("NotFound", "acting_no_identity",
|
|
369
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
370
|
+
identity: identity_reading(aggregate)))
|
|
371
|
+
found = repository.find(id) ||
|
|
372
|
+
raise(NotFound, RefusalWording.render("NotFound", "record_missing",
|
|
373
|
+
aggregate: aggregate.hecks_name,
|
|
374
|
+
identity: identity_reading(aggregate),
|
|
375
|
+
offered: Rendering.describe(id)))
|
|
376
|
+
found.dup
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# Transitional compatibility for live source that has not yet acquired
|
|
380
|
+
# explicit effects. It is deliberately isolated from the normal routing
|
|
381
|
+
# and planning path so `reference_to` no longer chooses how a migrated
|
|
382
|
+
# command hydrates or persists. Wave 8 removes this after the inventory is
|
|
383
|
+
# empty; frozen eras retain their own shadow parser.
|
|
384
|
+
#
|
|
385
|
+
# `ctx.plan.complete_state?` already claimed every command whose plan is
|
|
386
|
+
# fully resolved in the two branches above `step_hydrate` tries first, so
|
|
387
|
+
# reaching this check already means the plan is incomplete — an
|
|
388
|
+
# un-migrated command still routes here on `creates?` alone, the same as
|
|
389
|
+
# the old `hydrate` did, regardless of whether it happens to have any
|
|
390
|
+
# mutations (`write_set`). Requiring an EMPTY write_set here refused
|
|
391
|
+
# every un-migrated creating command that sets even one field.
|
|
392
|
+
def legacy_implicit_creation?(ctx)
|
|
393
|
+
ctx.route.nil? && ctx.command.creates?
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def hydrate_legacy_creation(repository, aggregate, command, args)
|
|
397
|
+
id = identity_of(aggregate, args) ||
|
|
398
|
+
raise(NotFound, RefusalWording.render("NotFound", "creating_no_identity",
|
|
399
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
400
|
+
identity: identity_reading(aggregate)))
|
|
401
|
+
if repository.find(id)
|
|
402
|
+
raise(AlreadyExists, RefusalWording.render("AlreadyExists", "creating_duplicate",
|
|
403
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
404
|
+
identity: identity_reading(aggregate),
|
|
405
|
+
offered: Rendering.describe(id)))
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
Instance.new(aggregate: aggregate, id: id, args: args)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def hydrate_complete_state(repository, aggregate, command, args, route, strategy)
|
|
412
|
+
derived = identity_of(aggregate, args)
|
|
413
|
+
if route && derived && route.aggregate.to_s != derived.to_s
|
|
414
|
+
raise TypeMismatch,
|
|
415
|
+
"#{command.hecks_name} routes to #{route.aggregate.inspect}, but its identity facts name #{derived.inspect}"
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
id = route&.aggregate || derived ||
|
|
419
|
+
raise(NotFound, RefusalWording.render("NotFound", "creating_no_identity",
|
|
420
|
+
command: command.hecks_name,
|
|
421
|
+
aggregate: aggregate.hecks_name,
|
|
422
|
+
identity: identity_reading(aggregate)))
|
|
423
|
+
|
|
424
|
+
# A SECOND CREATION IS NOT A FRESH ONE — see hydrate_legacy_creation's
|
|
425
|
+
# own comment; the same refusal, on the same terms, for the
|
|
426
|
+
# complete-state path. ONLY when `strategy` will NOT be ATOMIC_PUT:
|
|
427
|
+
# an atomic-put-capable adapter enforces this itself, atomically,
|
|
428
|
+
# via `insert_only:` in step_save (no read here, no race with the
|
|
429
|
+
# write) — but `strategy_for` already fell back to a plain `save`
|
|
430
|
+
# for an adapter with no atomic_put capability at all (Heki, today),
|
|
431
|
+
# and a plain `save` never refuses an overwrite on its own. Skipping
|
|
432
|
+
# this check for THAT case silently dropped the refusal instead of
|
|
433
|
+
# deferring it — the very "no such call — no such check" gap Wave
|
|
434
|
+
# 8 exists to close everywhere else.
|
|
435
|
+
if command.creates? && strategy != DependencyPlanning::ATOMIC_PUT && repository.find(id)
|
|
436
|
+
raise(AlreadyExists, RefusalWording.render("AlreadyExists", "creating_duplicate",
|
|
437
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
438
|
+
identity: identity_reading(aggregate),
|
|
439
|
+
offered: Rendering.describe(id)))
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
Instance.new(aggregate: aggregate, id: id, args: args)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# A complete command may still depend on prior state: lifecycle guards
|
|
446
|
+
# are the common case. Read once so an existing record is checked against
|
|
447
|
+
# its real state; when no record exists, the aggregate's declared defaults
|
|
448
|
+
# are the prior state. Completeness, not a reference marker, proves that
|
|
449
|
+
# initializing that missing record is safe.
|
|
450
|
+
def hydrate_prior_or_initial(repository, aggregate, command, args, route)
|
|
451
|
+
derived = identity_of(aggregate, args)
|
|
452
|
+
if route && derived && route.aggregate.to_s != derived.to_s
|
|
453
|
+
raise TypeMismatch,
|
|
454
|
+
"#{command.hecks_name} routes to #{route.aggregate.inspect}, but its identity facts name #{derived.inspect}"
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
id = route&.aggregate || derived ||
|
|
458
|
+
raise(NotFound, RefusalWording.render("NotFound", "creating_no_identity",
|
|
459
|
+
command: command.hecks_name,
|
|
460
|
+
aggregate: aggregate.hecks_name,
|
|
461
|
+
identity: identity_reading(aggregate)))
|
|
462
|
+
found = repository.find(id)
|
|
463
|
+
|
|
464
|
+
# A SECOND CREATION IS NOT A FRESH ONE — see hydrate_legacy_
|
|
465
|
+
# creation's own comment; the same refusal, on the same terms, for
|
|
466
|
+
# a complete-but-state-dependent command (one with a `given`
|
|
467
|
+
# reading its own prior state, which is what routes here instead
|
|
468
|
+
# of hydrate_complete_state). Gated on `command.creates?`: a
|
|
469
|
+
# NON-creating command's own complete payload legitimately means
|
|
470
|
+
# "act on whatever this identity already holds" (`found` IS the
|
|
471
|
+
# prior state this branch exists to supply), so only a genuine
|
|
472
|
+
# creation reusing an already-occupied identity is a duplicate.
|
|
473
|
+
# `SafeDepositBox.Rent` is exactly this shape — `given("box is
|
|
474
|
+
# vacant")` makes it state-dependent, so a second Rent used to
|
|
475
|
+
# silently hydrate the existing box as "prior state" and refuse
|
|
476
|
+
# for the wrong reason (not vacant) instead of the right one
|
|
477
|
+
# (already exists).
|
|
478
|
+
if found && command.creates?
|
|
479
|
+
raise(AlreadyExists, RefusalWording.render("AlreadyExists", "creating_duplicate",
|
|
480
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
481
|
+
identity: identity_reading(aggregate),
|
|
482
|
+
offered: Rendering.describe(id)))
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
found ? found.dup : Instance.new(aggregate: aggregate, id: id, args: args)
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# THE JOIN, THE DIG, AND THE READING — all shared with `EntityInterpreter`
|
|
489
|
+
# now, in `Runtime::Identity`, rather than kept as two copies that could
|
|
490
|
+
# only ever drift. See that module for the reasoning ; these three stay
|
|
491
|
+
# here, at the old names, purely so nothing below has to change.
|
|
492
|
+
def identity_of(aggregate, args) = Identity.of(aggregate, args)
|
|
493
|
+
def identity_from(aggregate, args, key) = Identity.from(aggregate, args, key)
|
|
494
|
+
def identity_reading(construct) = Identity.reading(construct)
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
end
|