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,175 @@
|
|
|
1
|
+
require_relative "value"
|
|
2
|
+
require_relative "identity"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Runtime
|
|
6
|
+
class Instance
|
|
7
|
+
attr_reader :aggregate, :id
|
|
8
|
+
attr_accessor :state
|
|
9
|
+
# OUT-OF-BAND ADAPTER BOOKKEEPING, NOT DOMAIN STATE — the optimistic-
|
|
10
|
+
# concurrency version a CAS-capable adapter (Postgres today) stamps
|
|
11
|
+
# on a record it reads/writes, so a later `save` can assert "commit
|
|
12
|
+
# only if nobody has written since". Deliberately absent from
|
|
13
|
+
# `to_h`/`[]`/`[]=`/`method_missing` : a domain author never declares
|
|
14
|
+
# this, a `given`/`ensures`/`invariant` can never read it, and no
|
|
15
|
+
# adapter that doesn't understand it (Memory, Heki) ever sets it —
|
|
16
|
+
# `nil` there just means "no CAS attempted", which is exactly what a
|
|
17
|
+
# plain `save` already does. See docs/decisions/ (concurrency-control
|
|
18
|
+
# ADR) for the full mechanism.
|
|
19
|
+
attr_accessor :version
|
|
20
|
+
|
|
21
|
+
# `args:` — THE ORIGINAL COMMAND PAYLOAD, offered only by a fresh
|
|
22
|
+
# creation (`CommandInterpreter#hydrate_legacy_creation`/
|
|
23
|
+
# `#hydrate_complete_state`/`#hydrate_prior_or_initial`, each already
|
|
24
|
+
# holding it when they mint a brand-new record). See
|
|
25
|
+
# `materialize_identity!` for why a composite identity needs it.
|
|
26
|
+
def initialize(aggregate:, id:, state: nil, args: nil)
|
|
27
|
+
@aggregate = aggregate
|
|
28
|
+
@id = id
|
|
29
|
+
@state = state ? self.class.hydrate_with_defaults(aggregate, state) : self.class.defaults(aggregate)
|
|
30
|
+
@version = nil
|
|
31
|
+
materialize_identity!(args)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Loading existing state runs the same default-fill a fresh instance
|
|
35
|
+
# gets: an attribute the record predates — a newly-required field
|
|
36
|
+
# with a declared default:, a list added since the record was
|
|
37
|
+
# written — arrives filled instead of nil. Only declared defaults
|
|
38
|
+
# fill in; an attribute with no default stays absent, exactly as
|
|
39
|
+
# stored.
|
|
40
|
+
def self.hydrate_with_defaults(aggregate, state)
|
|
41
|
+
hydrated = Value.hydrate(aggregate, state)
|
|
42
|
+
defaults(aggregate).each do |name, value|
|
|
43
|
+
hydrated[name] = value unless value.nil? || hydrated.key?(name)
|
|
44
|
+
end
|
|
45
|
+
hydrated
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.defaults(aggregate)
|
|
49
|
+
state = aggregate.attributes.each_with_object({}) do |attr, acc|
|
|
50
|
+
# FROZEN, like a list that has had something appended to it.
|
|
51
|
+
# An untouched list is the easiest one to miss and the easiest
|
|
52
|
+
# to mutate: nothing has replaced it yet, so a caller pushing
|
|
53
|
+
# into it writes straight into the aggregate's own state.
|
|
54
|
+
acc[attr.name] = attr.list? ? Freezer.deep([]) : default_for(aggregate, attr)
|
|
55
|
+
end
|
|
56
|
+
state[aggregate.lifecycle.field.to_sym] = aggregate.lifecycle.default if aggregate.lifecycle
|
|
57
|
+
state
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.default_for(aggregate, attribute)
|
|
61
|
+
return Value.for_attribute(aggregate, attribute, attribute.default) unless attribute.default.nil?
|
|
62
|
+
# An entity's members hydrate through the same path but an entity
|
|
63
|
+
# declares no value objects of its own — nothing to default-build.
|
|
64
|
+
return nil unless aggregate.respond_to?(:value_object)
|
|
65
|
+
|
|
66
|
+
value_object = aggregate.value_object(attribute.type)
|
|
67
|
+
return nil unless value_object && value_object.attributes.all? { |field| !field.default.nil? }
|
|
68
|
+
|
|
69
|
+
Value.build(value_object, {}, aggregate)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def [](name) = @state[name.to_sym]
|
|
73
|
+
|
|
74
|
+
def key?(name) = @state.key?(name.to_sym)
|
|
75
|
+
|
|
76
|
+
def []=(name, value)
|
|
77
|
+
@state[name.to_sym] = value
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def method_missing(name, *args)
|
|
81
|
+
return @state[name] if @state.key?(name)
|
|
82
|
+
|
|
83
|
+
super
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def respond_to_missing?(name, include_private = false)
|
|
87
|
+
@state.key?(name) || super
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# `id: @id` LAST, not first — see Facade::Handle#to_h's own comment
|
|
91
|
+
# for the full story (the same fix, landed there first): an
|
|
92
|
+
# aggregate free to declare its own attribute literally named `id`
|
|
93
|
+
# (BurningManPrep's `Item`, `attribute :id, ItemId`) has that
|
|
94
|
+
# attribute's own wrapped value object sitting in `@state[:id]` —
|
|
95
|
+
# merging `@state` on top of `{ id: @id }` let it silently clobber
|
|
96
|
+
# the correct bare identity. `@id` merged last always wins.
|
|
97
|
+
def to_h = @state.merge(id: @id)
|
|
98
|
+
|
|
99
|
+
# A COPY A MUTATION MAY TOUCH. Every adapter but Memory hands `find`
|
|
100
|
+
# a freshly-decoded Instance already; Memory's holds the record it
|
|
101
|
+
# eventually saves — the SAME state Hash, aliased. Before `ensures`
|
|
102
|
+
# existed, nothing could refuse between apply_mutations and save, so
|
|
103
|
+
# that aliasing was invisible: a dispatch either ran to completion or
|
|
104
|
+
# raised before touching state at all. `ensures` is the first refusal
|
|
105
|
+
# to sit AFTER mutation, and it found the bug the moment it did — an
|
|
106
|
+
# in-memory record left half-mutated by a dispatch that then refused.
|
|
107
|
+
# `command_interpreter`/`entity_interpreter` hydrate an EXISTING
|
|
108
|
+
# record through this, never through the adapter's own return value
|
|
109
|
+
# directly, so a refused ensures leaves the stored record untouched
|
|
110
|
+
# regardless of which adapter is holding it.
|
|
111
|
+
def dup
|
|
112
|
+
copy = super
|
|
113
|
+
copy.state = @state.dup
|
|
114
|
+
copy
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def inspect
|
|
118
|
+
fields = @state.map { |k, v| "#{k}=#{v.inspect}" }.join(" ")
|
|
119
|
+
"#<#{@aggregate.hecks_name} #{@id} #{fields}>"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
# M17 — a COMPOSITE identity (`identity_heads.size > 1`, e.g.
|
|
125
|
+
# `identified_by :branch_code, :box_number`) has no single
|
|
126
|
+
# `identified_by` to fall back to `:id` for — `@aggregate.identified_by`
|
|
127
|
+
# is nil the moment there is more than one head (`Behaviour::Identified
|
|
128
|
+
# #derive_identity`), so the single-head branch below never runs for
|
|
129
|
+
# it at all. A creating command that declares those heads as ordinary
|
|
130
|
+
# attributes but doesn't ALSO `sets` them (redundant with the identity
|
|
131
|
+
# the command's own args already named) used to persist every head as
|
|
132
|
+
# nil — the id correctly named the record, but the record's own
|
|
133
|
+
# attributes forgot what named it.
|
|
134
|
+
#
|
|
135
|
+
# Filled from `args`, never from splitting `@id` back apart — the
|
|
136
|
+
# same reason the single-head branch below won't guess a multi-path
|
|
137
|
+
# identifier from its joined string: `@id` is a display key, not a
|
|
138
|
+
# reversible serialization, and a composite's own separator can
|
|
139
|
+
# collide with a part's own text. `args` is only offered by a FRESH
|
|
140
|
+
# creation (`Instance.new`'s own `args:` comment); an existing record
|
|
141
|
+
# read back from storage has no args to lean on, and doesn't need
|
|
142
|
+
# one since a correctly-persisted record already carries its own
|
|
143
|
+
# heads.
|
|
144
|
+
def materialize_identity!(args = nil)
|
|
145
|
+
return materialize_composite_identity!(args) if @aggregate.identity_heads.size > 1
|
|
146
|
+
|
|
147
|
+
identity = @aggregate.identified_by || :id
|
|
148
|
+
attribute = @aggregate.attribute(identity)
|
|
149
|
+
return unless attribute && @state[identity].nil?
|
|
150
|
+
# Several paths under one head mean the identifier is a display key,
|
|
151
|
+
# not a reversible serialization of the structured value object. A
|
|
152
|
+
# creating command supplies that object explicitly; persisted state
|
|
153
|
+
# hydrates it from storage. Never guess by splitting the joined id.
|
|
154
|
+
return if @aggregate.identity_heads.one? && @aggregate.identity_paths.size > 1
|
|
155
|
+
|
|
156
|
+
@state[identity] = Value.from_identifier(@aggregate, attribute, @id)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def materialize_composite_identity!(args)
|
|
160
|
+
return unless args
|
|
161
|
+
|
|
162
|
+
@aggregate.identity_paths.each do |path|
|
|
163
|
+
head = path.to_s.split(".").first.to_sym
|
|
164
|
+
attribute = @aggregate.attribute(head)
|
|
165
|
+
next unless attribute && @state[head].nil?
|
|
166
|
+
|
|
167
|
+
raw = Identity.from(@aggregate, args, path, value_owner: @aggregate)
|
|
168
|
+
next if raw.nil?
|
|
169
|
+
|
|
170
|
+
@state[head] = Value.from_identifier(@aggregate, attribute, raw)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require_relative "value"
|
|
2
|
+
require_relative "aggregate_lock"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Runtime
|
|
6
|
+
# What every stepwise interpreter shares: the traced step, and the
|
|
7
|
+
# coercion of a command's declared arguments. Two copies of each lived
|
|
8
|
+
# in CommandInterpreter and EntityInterpreter, where they could only
|
|
9
|
+
# ever drift.
|
|
10
|
+
module Interpreting
|
|
11
|
+
# Each including interpreter gets its own `trace` — set by a spec to
|
|
12
|
+
# observe dispatch order (Vocabulary::AggregateDispatchOrder and
|
|
13
|
+
# Vocabulary::EntityDispatchOrder in language/bluebook/vocabulary.bluebook);
|
|
14
|
+
# nil in production, always — one array push and a nil check per step
|
|
15
|
+
# is the entire cost of leaving this in.
|
|
16
|
+
def self.included(interpreter)
|
|
17
|
+
interpreter.singleton_class.attr_accessor :trace
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# Logged AFTER the step's own work, so a step that wraps sub-steps logs
|
|
23
|
+
# itself once everything inside it has already logged — trace order is
|
|
24
|
+
# completion order, which is dispatch order.
|
|
25
|
+
def step(name)
|
|
26
|
+
result = yield
|
|
27
|
+
self.class.trace << name if self.class.trace
|
|
28
|
+
result
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# DRIVES `DISPATCH_ORDER` (CommandInterpreter/EntityInterpreter, each
|
|
32
|
+
# mirroring its own Vocabulary::*DispatchOrder — vocabulary.bluebook,
|
|
33
|
+
# held equal to it by spec/vocabulary_conformance_spec.rb) by `send`ing
|
|
34
|
+
# each declared step name against the including interpreter's own
|
|
35
|
+
# `step_<name>` handler, in declared order. What used to be `call`'s own
|
|
36
|
+
# literal sequence of method calls is now DATA driving that sequence —
|
|
37
|
+
# tracing a real dispatch and comparing it to the declaration is
|
|
38
|
+
# tautological once `call` mechanically follows the declaration; a
|
|
39
|
+
# conditional step (assign_creation_attributes, advance_lifecycle) still
|
|
40
|
+
# has to guard ITSELF at the top of its own handler and skip tracing
|
|
41
|
+
# when it does not fire, rather than the caller branching around it —
|
|
42
|
+
# see CommandInterpreter#step_assign_creation_attributes.
|
|
43
|
+
def run_dispatch_order(order, ctx)
|
|
44
|
+
order.each { |name| send(:"step_#{name}", ctx) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# THE CONCURRENCY-CONTROL SPLIT — see docs/decisions/ (concurrency
|
|
48
|
+
# control ADR) for the full mechanism. A repository that declares
|
|
49
|
+
# `:optimistic_concurrency` (Postgres today) already closes the
|
|
50
|
+
# lost-update gap itself, via `step_save`'s CAS + `#call`'s own
|
|
51
|
+
# `StaleWrite` retry loop — an extra in-process lock here would be
|
|
52
|
+
# pointless overhead, not incorrect, so it's skipped for clarity.
|
|
53
|
+
# Every other repository (Heki, Memory — confirmed process-local
|
|
54
|
+
# data, never a second process writing the same store) gets a
|
|
55
|
+
# striped `Mutex` held for the WHOLE dispatch-order run, so a second
|
|
56
|
+
# thread's own hydrate can't start until the first thread's save has
|
|
57
|
+
# landed. `lock_key_id` is best-effort (`Identity.best_effort`) —
|
|
58
|
+
# `nil` still locks correctly, just coarser (by aggregate type).
|
|
59
|
+
def run_dispatch_order_with_isolation(order, ctx, lock_key_id:)
|
|
60
|
+
if ctx.repository.capabilities.include?(:optimistic_concurrency)
|
|
61
|
+
run_dispatch_order(order, ctx)
|
|
62
|
+
else
|
|
63
|
+
AggregateLock.for(ctx.domain, ctx.aggregate, lock_key_id).synchronize { run_dispatch_order(order, ctx) }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Every declared attribute present in the payload passes the reference
|
|
68
|
+
# gate, then coercion — the same walk whether the command acts on an
|
|
69
|
+
# aggregate or on one of its entity's elements.
|
|
70
|
+
def coerce_declared_arguments(aggregate, command, args)
|
|
71
|
+
command.attributes.each_with_object(args.dup) do |attribute, normalized|
|
|
72
|
+
next unless normalized.key?(attribute.name)
|
|
73
|
+
|
|
74
|
+
Value.refuse_object_reference(command, attribute, normalized[attribute.name])
|
|
75
|
+
normalized[attribute.name] = Value.for_attribute(aggregate, attribute, normalized[attribute.name])
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Coercion only — CommandInterpreter's own refuse_unknown_arguments/
|
|
80
|
+
# refuse_absent_arguments are separate DISPATCH_ORDER steps now (the
|
|
81
|
+
# declared vocabulary lists all three as flat, sequential members, not
|
|
82
|
+
# one nesting the other two), and EntityInterpreter never had them here
|
|
83
|
+
# at all (an entity inherits its aggregate's own gate). One copy,
|
|
84
|
+
# shared, rather than the two identical ones that used to drift.
|
|
85
|
+
def normalize_args(aggregate, command, args)
|
|
86
|
+
coerce_declared_arguments(aggregate, command, args)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
require_relative "../facade/surface"
|
|
2
|
+
require_relative "../ports/loading"
|
|
3
|
+
require_relative "../ports/persistence"
|
|
4
|
+
require_relative "dispatcher"
|
|
5
|
+
require_relative "remote_dispatcher"
|
|
6
|
+
require_relative "boot_gates"
|
|
7
|
+
require_relative "registry"
|
|
8
|
+
|
|
9
|
+
module Hecks
|
|
10
|
+
module Runtime
|
|
11
|
+
class Loader
|
|
12
|
+
# `install_facade:` defaults on — every ordinary caller wants
|
|
13
|
+
# `Widget::Item.Add(...)` sugar. A caller that only ever dispatches
|
|
14
|
+
# by FQN string (`SmokeTest`, the one caller so far) can pass
|
|
15
|
+
# `false` to skip it: `Facade::Surface.install` puts a bare global
|
|
16
|
+
# Ruby constant on `Object` per domain AND per aggregate name, with
|
|
17
|
+
# no scoping and no cleanup hook, so a tool booting arbitrary
|
|
18
|
+
# throwaway domains under generic names ("Widget", "Item", "Tag")
|
|
19
|
+
# would otherwise leak those names into the rest of the process —
|
|
20
|
+
# measured, not hypothetical: this exact leak once made an
|
|
21
|
+
# unrelated `dsl_spec.rb` example resolve a bare `Widget` constant
|
|
22
|
+
# to a stale smoke-test facade from a deleted temp directory instead
|
|
23
|
+
# of raising, corrupting that spec's own unrelated build. Skipping
|
|
24
|
+
# the install is safe because nothing downstream of a raw
|
|
25
|
+
# `Dispatcher` needs the sugar — `Dispatcher#dispatch`/`#query` work
|
|
26
|
+
# identically either way.
|
|
27
|
+
# `environment:` — see Adapters::Folder#load_domain's own comment
|
|
28
|
+
# for the mechanism. hecks never reads ENV itself (every other
|
|
29
|
+
# env-var lookup in this codebase lives in app-owned .world/
|
|
30
|
+
# .hecksagon files, never library internals) — a caller resolves
|
|
31
|
+
# its own env var name and passes the resulting string straight
|
|
32
|
+
# through, e.g. `Hecks.boot(path, environment:
|
|
33
|
+
# ENV.fetch("MYAPP_ENV", "development"))`.
|
|
34
|
+
def self.boot(path, shared: nil, install_facade: true, environment: nil)
|
|
35
|
+
loading = Ports::Loading.bootstrap
|
|
36
|
+
directory = loading.bluebook_directory(path)
|
|
37
|
+
root = loading.shared_root(shared, directory)
|
|
38
|
+
registry = Registry.new(root: File.dirname(directory))
|
|
39
|
+
|
|
40
|
+
Hecks.with_registry(registry) do
|
|
41
|
+
loading.load_library
|
|
42
|
+
loading.load_project(root)
|
|
43
|
+
loading.load_domain(directory, environment: environment)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
run_boot_gates!(registry, directory)
|
|
47
|
+
dispatcher = dispatcher_for(registry)
|
|
48
|
+
install_facade ? bind_runtime(dispatcher) : dispatcher
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# THE EXPLICIT-FILE FORM — `paths` names the exact bluebook/hecksagon/
|
|
52
|
+
# world files to boot, in place, wherever they actually live. `boot`
|
|
53
|
+
# above only ever takes a directory and globs it; that is the right
|
|
54
|
+
# shape for a real deployment (`examples/banking`, a domain someone
|
|
55
|
+
# `cd`s into), and the wrong one for a caller that wants to declare a
|
|
56
|
+
# narrow, explicit scope and have it booted exactly as declared — a
|
|
57
|
+
# `.behaviors` file's own `loads` line is the motivating caller
|
|
58
|
+
# (`Hecks::Behaviors`), but this carries no behaviors-specific
|
|
59
|
+
# logic and is not gated behind requiring that module.
|
|
60
|
+
#
|
|
61
|
+
# NO COPYING, NO TEMP DIRECTORY. A prior port of this same idea
|
|
62
|
+
# (vendored into a downstream consumer, read before writing this)
|
|
63
|
+
# scoped a per-test boot by copying files into `Dir.mktmpdir` — which
|
|
64
|
+
# destroys real relative paths, and worse, makes a `persisted_by`
|
|
65
|
+
# path resolve against the TEMP copy's root instead of the project's
|
|
66
|
+
# own (confirmed there: a file adapter kept reading and writing the
|
|
67
|
+
# same deterministic tmp copy across an entire session, because
|
|
68
|
+
# `Hecks.boot`'s own `root` is always `File.dirname` of whatever
|
|
69
|
+
# directory it was handed). `directory` here is `File.dirname` of the
|
|
70
|
+
# FIRST real path in `paths` — genuinely on disk, not a copy — so
|
|
71
|
+
# every downstream path (`EraCheck`, `persisted_by`, `shared_root`)
|
|
72
|
+
# resolves exactly as an ordinary directory boot's would.
|
|
73
|
+
def self.boot_files(paths, shared: nil, install_facade: true, environment: nil)
|
|
74
|
+
loading = Ports::Loading.bootstrap
|
|
75
|
+
files = Array(paths).map { |path| File.expand_path(path) }
|
|
76
|
+
directory = File.dirname(files.first)
|
|
77
|
+
root = loading.shared_root(shared, directory)
|
|
78
|
+
registry = Registry.new(root: File.dirname(directory))
|
|
79
|
+
|
|
80
|
+
Hecks.with_registry(registry) do
|
|
81
|
+
loading.load_library
|
|
82
|
+
loading.load_project(root)
|
|
83
|
+
loading.load_selected(files, environment: environment)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
run_boot_gates!(registry, directory)
|
|
87
|
+
dispatcher = dispatcher_for(registry)
|
|
88
|
+
install_facade ? bind_runtime(dispatcher) : dispatcher
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# ADR 0031 — replaces two previously-hardcoded, unconditional calls
|
|
92
|
+
# with a per-boot `BootGates` instance holding exactly the gates THIS
|
|
93
|
+
# registry's own bound adapters have a capability for. Ordering is
|
|
94
|
+
# preserved: era-checking (when a persistence plugin contributes one)
|
|
95
|
+
# still runs before `verify!`, saga rehydration still runs after
|
|
96
|
+
# (conservative — see `SagaPersistence#rehydrate_sagas!`'s own
|
|
97
|
+
# comment).
|
|
98
|
+
#
|
|
99
|
+
# ADR 0033 — this loader no longer names `EraCheck`, or any other
|
|
100
|
+
# era-specific class, at all. Every LOADED persistence plugin
|
|
101
|
+
# (`Ports::Persistence.each_plugin` — nothing here if nothing was
|
|
102
|
+
# ever `require`d) is asked to contribute its own `:pre_verify`/
|
|
103
|
+
# `:post_verify` gates generically; `:saga_rehydration` is the one
|
|
104
|
+
# gate core still registers directly, because ADR 0031 already
|
|
105
|
+
# proved it's not era-specific.
|
|
106
|
+
def self.run_boot_gates!(registry, directory)
|
|
107
|
+
gates = BootGates.new
|
|
108
|
+
Ports::Persistence.each_plugin { |plugin| plugin.contribute_boot_gates(registry, gates) }
|
|
109
|
+
check_compute_rules_backstop!(registry)
|
|
110
|
+
|
|
111
|
+
gates.run!(:pre_verify, registry, directory)
|
|
112
|
+
registry.verify!
|
|
113
|
+
|
|
114
|
+
gates.register(:saga_rehydration, ->(reg, _dir) { reg.rehydrate_sagas! }, phase: :post_verify) if
|
|
115
|
+
registry.hecksagons.each_key.any? { |domain| registry.saga_persistence(domain) != Ports::Persistence::NULL_SAGA_STORE }
|
|
116
|
+
gates.run!(:post_verify, registry, directory)
|
|
117
|
+
gates
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# The one piece of the old, era-owned `check_compute_rules!` core
|
|
121
|
+
# still carries — deliberately thinner. `registry.translations` is
|
|
122
|
+
# plain `Bluebook::Translation`/`TranslationAggregate`/
|
|
123
|
+
# `TranslationCompute`/`TranslationRekey` data (`bluebook/
|
|
124
|
+
# translation.rb`, core, no era-specific class involved), so this
|
|
125
|
+
# needs nothing plugin-specific to ask "does anything declare a
|
|
126
|
+
# compute/rekey rule at all." A LOADED persistence plugin (e.g. the
|
|
127
|
+
# era plugin's own `:era_compute_rules` gate, registered above) runs
|
|
128
|
+
# the real, adapter-aware version of this check and refuses by name
|
|
129
|
+
# ("...is bound to Memory") long before this ever would; this only
|
|
130
|
+
# fires when nothing did, because nothing was loaded to.
|
|
131
|
+
def self.check_compute_rules_backstop!(registry)
|
|
132
|
+
return if Ports::Persistence.plugins_loaded?
|
|
133
|
+
|
|
134
|
+
registry.translations.each do |translation|
|
|
135
|
+
translation.aggregates.each do |aggregate|
|
|
136
|
+
next if aggregate.computes.empty? && aggregate.rekeys.empty?
|
|
137
|
+
|
|
138
|
+
raise WiringError,
|
|
139
|
+
"cannot boot #{translation.domain}::#{aggregate.name}: a compute/rekey rule is declared, but no " \
|
|
140
|
+
"persistence plugin that can interpret it is loaded (e.g. require " \
|
|
141
|
+
"\"hecks/ports/persistence/plugins/era\")"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# `RemoteDispatcher` for a domain routed through Lambda,
|
|
147
|
+
# `Dispatcher` otherwise — the ONE place this decision gets
|
|
148
|
+
# made, so everything built on top (`Handle`, `AggregateDoor`,
|
|
149
|
+
# `Facade::Surface`) never has to know which class it's holding.
|
|
150
|
+
# `registry.bluebooks.keys.first` is the just-booted domain's own
|
|
151
|
+
# name (insertion order — the target's own bluebook loads before
|
|
152
|
+
# any `uses_framework` chapter, `bin/project_rust`'s own header
|
|
153
|
+
# draws the identical distinction), not a directory basename.
|
|
154
|
+
#
|
|
155
|
+
# `dispatched_by("Lambda")` is its OWN, EXPLICIT verb — NOT
|
|
156
|
+
# inferred from `deployed_to("AwsLambda")`'s mere presence. Both
|
|
157
|
+
# Banking's and Embryonaut's `.world` files already declare
|
|
158
|
+
# `deployed_to("AwsLambda")` (it only means "a deploy target
|
|
159
|
+
# exists"), so treating that alone as "boot this domain against
|
|
160
|
+
# Lambda" would have silently rerouted Banking's every local
|
|
161
|
+
# boot — every spec, every `bin/console` session — the moment
|
|
162
|
+
# this landed. A domain opts in explicitly, the same way
|
|
163
|
+
# `persisted_by("PostgresEra")` is never inferred from anything
|
|
164
|
+
# else either.
|
|
165
|
+
def self.dispatcher_for(registry)
|
|
166
|
+
domain = registry.bluebooks.keys.first
|
|
167
|
+
settings = registry.world(domain)&.for_verb("dispatched_by") || {}
|
|
168
|
+
return Dispatcher.new(registry) unless settings[:adapter] == "Lambda"
|
|
169
|
+
|
|
170
|
+
RemoteDispatcher.new(registry, region: settings.fetch(:region, "us-east-1"))
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# THE DOOR IS INSTALLED HERE, NOT STAMPED. This used to write the
|
|
174
|
+
# dispatcher onto every aggregate's class (`ruby_class.runtime =`) — the
|
|
175
|
+
# class-level global that made two boots in one process share one
|
|
176
|
+
# name. The facade's modules close over THIS dispatcher instead, so the
|
|
177
|
+
# binding lives in the surface a boot installs, not on anything shared.
|
|
178
|
+
def self.bind_runtime(dispatcher)
|
|
179
|
+
Facade::Surface.install(dispatcher)
|
|
180
|
+
dispatcher
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|