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,62 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
# WHAT TIME IT IS — the one fact a domain cannot derive and must not invent.
|
|
6
|
+
#
|
|
7
|
+
# Resolved exactly the way `Ports::IdentityGeneration` resolves its own
|
|
8
|
+
# adapter: one adapter registry-wide implements this, not a per-aggregate
|
|
9
|
+
# binding, because two aggregates in one running app have no real reason to
|
|
10
|
+
# disagree about the time.
|
|
11
|
+
#
|
|
12
|
+
# WHY A PORT AND NOT `Time.now`. A staleness rule — "another agent may take
|
|
13
|
+
# this claim after fifteen minutes" — is untestable against the real clock:
|
|
14
|
+
# a spec for it would either sleep for fifteen minutes or never run at all.
|
|
15
|
+
# Bound to a fixed adapter, the same rule is three lines. That is the whole
|
|
16
|
+
# argument, and it is the same one `SequentialIdentity` makes next door.
|
|
17
|
+
#
|
|
18
|
+
# A PREDICATE STILL CANNOT ASK THE TIME, and this does not change that. The
|
|
19
|
+
# expression sublanguage has no clock and should not: a `given` that read
|
|
20
|
+
# the time would evaluate differently on two runs over the same record, and
|
|
21
|
+
# every replay, audit and fuzz oracle in this repository assumes it does
|
|
22
|
+
# not. So `now` remains an ARGUMENT the predicate merely reads, and this
|
|
23
|
+
# port is what lets a caller stop typing it — the door fills it in, the
|
|
24
|
+
# value is baked into the dispatch, and the recorded step carries the
|
|
25
|
+
# concrete number forever after.
|
|
26
|
+
#
|
|
27
|
+
# WHICH IS WHY THE RUNTIME DOES NOT CALL THIS. `IdentityGeneration`'s own
|
|
28
|
+
# note works through the replay question for a minted uuid and lands on
|
|
29
|
+
# "the value gets baked into the caller's args at the first live dispatch".
|
|
30
|
+
# A clock consulted INSIDE the interpreter would not have that property: a
|
|
31
|
+
# recorded corpus step replayed tomorrow would silently get tomorrow's
|
|
32
|
+
# time, and the fuzzer's oracle and the adapter-agreement gate both compare
|
|
33
|
+
# runs of exactly that shape. So the filling happens at the door, where a
|
|
34
|
+
# human or an agent is typing, and never on the dispatch path a replay uses.
|
|
35
|
+
module Clock
|
|
36
|
+
NAME = "clock"
|
|
37
|
+
|
|
38
|
+
module_function
|
|
39
|
+
|
|
40
|
+
# SECONDS, AS AN INTEGER, because that is what the sublanguage can do
|
|
41
|
+
# arithmetic on. `claimed_at.value + window.value <= now.value` is
|
|
42
|
+
# addition and comparison — the only two things it has — and a Time
|
|
43
|
+
# object supports neither.
|
|
44
|
+
def now(registry) = adapter(registry).now
|
|
45
|
+
|
|
46
|
+
def adapter(registry)
|
|
47
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
48
|
+
|
|
49
|
+
case implementations.size
|
|
50
|
+
when 1 then registry.adapter_class(implementations.first.name)
|
|
51
|
+
when 0
|
|
52
|
+
raise Runtime::WiringError,
|
|
53
|
+
"no adapter implements the #{NAME} port — nothing can say what time it is"
|
|
54
|
+
else
|
|
55
|
+
raise Runtime::WiringError,
|
|
56
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
57
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
module Extraction
|
|
6
|
+
NAME = "extraction"
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def canonical(block) = adapter.canonical(block)
|
|
11
|
+
|
|
12
|
+
def adapter
|
|
13
|
+
registry = Hecks.current_registry
|
|
14
|
+
unless registry
|
|
15
|
+
raise Runtime::WiringError,
|
|
16
|
+
"extraction resolved outside a boot — a predicate can only be " \
|
|
17
|
+
"read while a bluebook is loading"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
21
|
+
|
|
22
|
+
case implementations.size
|
|
23
|
+
when 1 then registry.adapter_class(implementations.first.name)
|
|
24
|
+
when 0
|
|
25
|
+
raise Runtime::WiringError,
|
|
26
|
+
"no adapter implements the #{NAME} port — nothing can recover a " \
|
|
27
|
+
"predicate's source"
|
|
28
|
+
else
|
|
29
|
+
raise Runtime::WiringError,
|
|
30
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
31
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime " \
|
|
32
|
+
"will not choose for you"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
# WHICH VALUE A CREATING COMMAND'S OWN IDENTITY FIELD GETS, when
|
|
6
|
+
# neither a slug-from-another-field nor a sequence-with-prefix
|
|
7
|
+
# mechanically answers it — a driving app's own console, say,
|
|
8
|
+
# falls back to this when a collection's identity strategy names
|
|
9
|
+
# "port" rather than "slug"/"sequence" (both of those stay pure
|
|
10
|
+
# config-driven mechanics, computed by whoever is dispatching,
|
|
11
|
+
# never by this port).
|
|
12
|
+
#
|
|
13
|
+
# Pure delegation, same as every sibling port (AccessControl,
|
|
14
|
+
# IdentityGeneration): an app's own domain declares what "port"
|
|
15
|
+
# actually MEANS for it — mint a real UUID via
|
|
16
|
+
# Ports::IdentityGeneration, derive something else entirely — this
|
|
17
|
+
# port just resolves the one adapter that domain wired and calls
|
|
18
|
+
# it, the same "one adapter registry-wide, refuse if zero or many"
|
|
19
|
+
# resolution every other port here already uses.
|
|
20
|
+
module IdentityAssignment
|
|
21
|
+
NAME = "identity_assignment"
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
def assign(registry, agg_name:, field_name:, args:)
|
|
26
|
+
adapter(registry).assign(registry, agg_name: agg_name, field_name: field_name, args: args)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def adapter(registry)
|
|
30
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
31
|
+
|
|
32
|
+
case implementations.size
|
|
33
|
+
when 1 then registry.adapter_class(implementations.first.name)
|
|
34
|
+
when 0
|
|
35
|
+
raise Runtime::WiringError,
|
|
36
|
+
"no adapter implements the #{NAME} port — nothing can assign an identity"
|
|
37
|
+
else
|
|
38
|
+
raise Runtime::WiringError,
|
|
39
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
40
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
# A creating command with no natural key needs a value from
|
|
6
|
+
# SOMEWHERE to be its identity — this is that somewhere. Resolved
|
|
7
|
+
# the same way `Ports::Extraction` resolves its own adapter (one
|
|
8
|
+
# adapter registry-wide implements this port, not a per-aggregate
|
|
9
|
+
# binding the way persistence needs — different aggregates have no
|
|
10
|
+
# real reason to want different id-generation strategies within
|
|
11
|
+
# one running app).
|
|
12
|
+
#
|
|
13
|
+
# REPLAY NEEDS NO SUPPRESSION HERE, ON PURPOSE. A UUID minted for a
|
|
14
|
+
# creating command's identity gets baked into that step's own args
|
|
15
|
+
# — an ordinary string value — the moment it's generated, by
|
|
16
|
+
# whoever issues the first, live dispatch. A recorded corpus
|
|
17
|
+
# script or a captured fuzz-replay step already holds that
|
|
18
|
+
# concrete value; replaying it calls `dispatch` with the SAME
|
|
19
|
+
# args, and this module is never consulted again, for the same
|
|
20
|
+
# reason `SecureRandom.uuid` never runs twice for one
|
|
21
|
+
# already-recorded step today. `Event#occurred_at`
|
|
22
|
+
# (`command_rules/emission.rb`) is the one other "environmental
|
|
23
|
+
# fact, not caller-supplied" this codebase has, and it's handled
|
|
24
|
+
# the identical way: it fires on every dispatch, replay included,
|
|
25
|
+
# and comparison simply excludes it.
|
|
26
|
+
module IdentityGeneration
|
|
27
|
+
NAME = "identity_generation"
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
def uuid(registry) = adapter(registry).uuid
|
|
32
|
+
|
|
33
|
+
def adapter(registry)
|
|
34
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
35
|
+
|
|
36
|
+
case implementations.size
|
|
37
|
+
when 1 then registry.adapter_class(implementations.first.name)
|
|
38
|
+
when 0
|
|
39
|
+
raise Runtime::WiringError,
|
|
40
|
+
"no adapter implements the #{NAME} port — nothing can mint an identity"
|
|
41
|
+
else
|
|
42
|
+
raise Runtime::WiringError,
|
|
43
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
44
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
# AN AUTHENTICATED (issuer, subject) PAIR, RESOLVED TO A STABLE
|
|
6
|
+
# `Identity` — the other half of the same symmetry `Authorization`
|
|
7
|
+
# already has for Governance: one adapter registry-wide answers this
|
|
8
|
+
# port, resolved the same zero/one/many way, so an application never
|
|
9
|
+
# has to name `Identity` directly to ask "who is this."
|
|
10
|
+
#
|
|
11
|
+
# Answers `nil` for a pair nothing has linked — the caller decides
|
|
12
|
+
# what that means (refuse, prompt to register, whatever), the same
|
|
13
|
+
# way `Authorization#holds_role?` answering `false` decides nothing
|
|
14
|
+
# on its own.
|
|
15
|
+
module IdentityResolution
|
|
16
|
+
NAME = "identity_resolution"
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def resolve(registry, issuer:, subject:)
|
|
21
|
+
adapter(registry).resolve(registry, issuer: issuer, subject: subject)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def adapter(registry)
|
|
25
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
26
|
+
|
|
27
|
+
case implementations.size
|
|
28
|
+
when 1 then registry.adapter_class(implementations.first.name)
|
|
29
|
+
when 0
|
|
30
|
+
raise Runtime::WiringError,
|
|
31
|
+
"no adapter implements the #{NAME} port — nothing can resolve an identity"
|
|
32
|
+
else
|
|
33
|
+
raise Runtime::WiringError,
|
|
34
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
35
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
require_relative "../../runtime/registry"
|
|
2
|
+
require_relative "execution"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Ports
|
|
6
|
+
module Persistence
|
|
7
|
+
# `mirrors` is durable replication intent. It is part of the same
|
|
8
|
+
# append as the authoritative state, never a second outbox store.
|
|
9
|
+
Entry = Struct.new(:operation, :id, :state, :mirrors, keyword_init: true) do
|
|
10
|
+
def save? = operation == "save"
|
|
11
|
+
def delete? = operation == "delete"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Makes append-before-projection a port invariant. Adapters retain
|
|
15
|
+
# control of their durable format, but every one must accept the same
|
|
16
|
+
# entry stream and materialize current state from it.
|
|
17
|
+
class AppendOnly
|
|
18
|
+
attr_reader :adapter
|
|
19
|
+
|
|
20
|
+
def aggregate = @adapter.aggregate
|
|
21
|
+
|
|
22
|
+
def initialize(adapter)
|
|
23
|
+
@adapter = adapter
|
|
24
|
+
required = %i[append project entries]
|
|
25
|
+
missing = required.reject { |method| adapter.respond_to?(method) }
|
|
26
|
+
raise Runtime::WiringError,
|
|
27
|
+
"#{adapter.class} does not implement append-only persistence: #{missing.join(', ')}" unless missing.empty?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def find(id) = @adapter.find(id)
|
|
31
|
+
def all(**opts) = @adapter.all(**opts)
|
|
32
|
+
def count = @adapter.count
|
|
33
|
+
def entries = @adapter.entries
|
|
34
|
+
|
|
35
|
+
def capabilities
|
|
36
|
+
return [] unless @adapter.respond_to?(:persistence_capabilities)
|
|
37
|
+
|
|
38
|
+
Array(@adapter.persistence_capabilities).map(&:to_sym).freeze
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def reset!
|
|
42
|
+
raise Runtime::WiringError, "append-only adapter cannot reset" unless @adapter.respond_to?(:reset!)
|
|
43
|
+
|
|
44
|
+
@adapter.reset!
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# NOT an endless `def events = ... if ...` — that modifier binds to
|
|
48
|
+
# the WHOLE `def`, not just its body, so it evaluates against
|
|
49
|
+
# `@adapter` while `@adapter` is still nil (class-body time,
|
|
50
|
+
# before `initialize` ever runs) and silently skips defining the
|
|
51
|
+
# method at all. Found live: nothing in this codebase called
|
|
52
|
+
# `AppendOnly#events` before Memory got a `reset!` test that did.
|
|
53
|
+
def events
|
|
54
|
+
@adapter.events if @adapter.respond_to?(:events)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# An append is durable before a projection is attempted. Replaying the
|
|
58
|
+
# log restores a snapshot/table after a crash in that small window.
|
|
59
|
+
def recover!
|
|
60
|
+
entries.each { |entry| project(entry) }
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def append(entry) = @adapter.append(entry)
|
|
65
|
+
def project(entry) = @adapter.project(entry)
|
|
66
|
+
|
|
67
|
+
# Returns an `Outcome`, not a bare `Instance` — every call site
|
|
68
|
+
# (`CommandInterpreter`/`EntityInterpreter`'s own `step_save`,
|
|
69
|
+
# `RebuildSweep#refresh`) reads it that way.
|
|
70
|
+
#
|
|
71
|
+
# `expected_version:` requests optimistic-concurrency CAS — commit
|
|
72
|
+
# only if the stored record's version still matches what THIS
|
|
73
|
+
# instance was read at. It is `nil` both when a caller explicitly
|
|
74
|
+
# doesn't want CAS (`RebuildSweep#refresh`'s own projection-field
|
|
75
|
+
# touch-up, which has no `given` to protect) and when the instance
|
|
76
|
+
# is brand new (never read from storage, so `instance.version` is
|
|
77
|
+
# nil) — both cases fall through to the plain, unconditional
|
|
78
|
+
# `project(entry)` below, byte-for-byte today's behavior. Only an
|
|
79
|
+
# adapter that both receives a non-nil `expected_version` AND
|
|
80
|
+
# declares `:optimistic_concurrency` gets CAS treatment; every
|
|
81
|
+
# other adapter/call site is unaffected.
|
|
82
|
+
def save(instance, expected_version: nil)
|
|
83
|
+
entry = Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
|
|
84
|
+
append(entry)
|
|
85
|
+
if expected_version && capabilities.include?(:optimistic_concurrency)
|
|
86
|
+
saved = @adapter.project(entry, expected_version: expected_version)
|
|
87
|
+
return Outcome.new(status: :stale, instance: instance) if saved.nil?
|
|
88
|
+
|
|
89
|
+
return Outcome.new(status: :saved, instance: saved)
|
|
90
|
+
end
|
|
91
|
+
saved = @adapter.project(entry)
|
|
92
|
+
Outcome.new(status: :saved, instance: saved || instance)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def atomic_put(instance, insert_only: false)
|
|
96
|
+
unless capabilities.include?(:atomic_put) && @adapter.respond_to?(:atomic_put)
|
|
97
|
+
raise Runtime::WiringError, "#{@adapter.class} advertises no atomic_put persistence capability"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
entry = Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
|
|
101
|
+
status = @adapter.atomic_put(entry, insert_only: insert_only)
|
|
102
|
+
Outcome.new(status: status, instance: instance)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def delete(id)
|
|
106
|
+
return false unless find(id)
|
|
107
|
+
|
|
108
|
+
entry = Entry.new(operation: "delete", id: id.to_s, state: nil)
|
|
109
|
+
append(entry)
|
|
110
|
+
project(entry)
|
|
111
|
+
true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# NOT an endless `def record_event = ... if ...` — same gotcha as
|
|
115
|
+
# `events` above, and it bit for real here: this guard evaluated
|
|
116
|
+
# against `@adapter` at class-body time (nil, always false), so
|
|
117
|
+
# `record_event` was never defined at all. `emission.rb`'s own
|
|
118
|
+
# `repository.record_event(event) if repository.respond_to?(:record_event)`
|
|
119
|
+
# therefore never fired for any adapter, ever — every declared
|
|
120
|
+
# `emits` was computed and reported in `registry.event_log` (an
|
|
121
|
+
# in-process array, gone at exit) but never durably recorded.
|
|
122
|
+
# Caught because a live tail of a domain's own persisted events
|
|
123
|
+
# found nothing to tail. `sqlite_spec.rb`/`postgres_spec.rb`/
|
|
124
|
+
# `postgres_era_spec.rb` all call `adapter.record_event` directly,
|
|
125
|
+
# bypassing this wrapper — which is exactly why no spec noticed.
|
|
126
|
+
def record_event(event)
|
|
127
|
+
@adapter.record_event(event) if @adapter.respond_to?(:record_event)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def query_read_model(domain, model, args, bluebook = nil)
|
|
131
|
+
return unless @adapter.respond_to?(:query_read_model)
|
|
132
|
+
|
|
133
|
+
@adapter.query_read_model(domain, model, args, bluebook)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require_relative "../../bluebook/hexagon"
|
|
2
|
+
require_relative "../../runtime/registry"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Ports
|
|
6
|
+
module Persistence
|
|
7
|
+
# Resolves one aggregate's persistence topology. An unmarked binding is
|
|
8
|
+
# authoritative; roles are deliberately opt-in so a second adapter can
|
|
9
|
+
# never silently become a read source.
|
|
10
|
+
module BindingPolicy
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def resolve(registry, domain, aggregate)
|
|
14
|
+
hexagon = registry.hecksagon(domain)
|
|
15
|
+
return default_binding(aggregate) unless hexagon
|
|
16
|
+
|
|
17
|
+
bindings = hexagon.binds_for(aggregate.hecks_name, VERB)
|
|
18
|
+
raise missing_binding(domain, aggregate) if bindings.empty?
|
|
19
|
+
|
|
20
|
+
authoritative = bindings.select { |bind| bind.role.nil? || bind.role.empty? }
|
|
21
|
+
raise ambiguous_binding(domain, aggregate, authoritative) unless authoritative.size == 1
|
|
22
|
+
raise unsupported_roles(domain, aggregate, bindings - authoritative) unless (bindings - authoritative).empty?
|
|
23
|
+
|
|
24
|
+
authoritative.first
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def default_binding(aggregate)
|
|
28
|
+
Bluebook::Bind.new(aggregate: aggregate.hecks_name, verb: VERB, adapter: DEFAULT_ADAPTER)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def missing_binding(domain, aggregate)
|
|
32
|
+
Runtime::WiringError.new(
|
|
33
|
+
"#{domain}::#{aggregate.hecks_name} has no #{VERB} bind. #{domain} declares a " \
|
|
34
|
+
"hecksagon, so its wiring is being decided explicitly and an aggregate " \
|
|
35
|
+
"left out is a forgotten decision. Bind it, or say " \
|
|
36
|
+
"#{aggregate.hecks_name}.#{VERB}(#{DEFAULT_ADAPTER.inspect}) to keep it in memory on purpose."
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ambiguous_binding(domain, aggregate, authoritative)
|
|
41
|
+
Runtime::WiringError.new(
|
|
42
|
+
"#{domain}::#{aggregate.hecks_name} has #{authoritative.size} authoritative #{VERB} bindings. " \
|
|
43
|
+
"Declare exactly one adapter without a role."
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def unsupported_roles(domain, aggregate, bindings)
|
|
48
|
+
Runtime::WiringError.new(
|
|
49
|
+
"#{domain}::#{aggregate.hecks_name} uses persistence role#{bindings.size == 1 ? '' : 's'} " \
|
|
50
|
+
"#{bindings.map(&:role).map(&:inspect).join(', ')}. Only persisted_by is supported."
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Ports
|
|
3
|
+
module Persistence
|
|
4
|
+
# Adapter outcomes are runtime facts, never command lifecycle.
|
|
5
|
+
Outcome = Struct.new(:status, :instance, keyword_init: true) do
|
|
6
|
+
# `:stale` is an optimistic-concurrency conflict on a plain `save`
|
|
7
|
+
# (someone else committed since this instance was read) — never a
|
|
8
|
+
# domain refusal, distinct from `:conflicted` (a `creates?`
|
|
9
|
+
# command's identity already exists, `AlreadyExists`). See
|
|
10
|
+
# `Runtime::StaleWrite` (runtime/errors.rb) and `AppendOnly#save`.
|
|
11
|
+
STATUSES = %i[inserted replaced updated conflicted missing saved stale].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(status:, instance: nil)
|
|
14
|
+
normalized = status.to_sym
|
|
15
|
+
raise ArgumentError, "unknown persistence outcome #{status.inspect}" unless STATUSES.include?(normalized)
|
|
16
|
+
|
|
17
|
+
super(status: normalized, instance: instance)
|
|
18
|
+
freeze
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Ports
|
|
3
|
+
module Persistence
|
|
4
|
+
# THE NO-OP SAGA STORE — what `Registry#saga_persistence` hands
|
|
5
|
+
# back for a domain whose resolved adapter doesn't implement the
|
|
6
|
+
# (optional) saga-persistence capability: Memory, deliberately
|
|
7
|
+
# (sagas stay in-memory-only, exactly as they always have), and
|
|
8
|
+
# any `RemoteRuntime`-shaped adapter (Lambda; the real durability
|
|
9
|
+
# for those lives on the other side of the call — Phase 1, not
|
|
10
|
+
# here). `SagaInterpreter`'s hook points call through
|
|
11
|
+
# `saga_persistence(domain)` unconditionally; this is what makes
|
|
12
|
+
# that safe without a `respond_to?` check at every call site,
|
|
13
|
+
# mirroring the same optional-capability shape
|
|
14
|
+
# `Ports::Persistence::AppendOnly` already gives adapters that
|
|
15
|
+
# don't implement `reset!`/`events`/`record_event`.
|
|
16
|
+
class NullSagaStore
|
|
17
|
+
def save_saga(**) = nil
|
|
18
|
+
def delete_saga(**) = nil
|
|
19
|
+
def each_saga(*) = nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
NULL_SAGA_STORE = NullSagaStore.new
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Ports
|
|
3
|
+
module Persistence
|
|
4
|
+
# A tiny, real seam — ADR 0033. Core never names a persistence plugin;
|
|
5
|
+
# a plugin makes itself known as a side effect of being `require`d, the
|
|
6
|
+
# same way an adapter's own `.rb` file already registers itself
|
|
7
|
+
# (`Runtime::Registry#add_adapter`) rather than core listing adapter
|
|
8
|
+
# names anywhere. `register_plugin` is the ENTIRE contract a plugin
|
|
9
|
+
# must satisfy: anything responding to `contribute_boot_gates(registry,
|
|
10
|
+
# gates)` (a no-op is a valid implementation).
|
|
11
|
+
#
|
|
12
|
+
# This is deliberately NOT the same registry `Hecks::Projector` uses
|
|
13
|
+
# (ADR 0027) or `Runtime::BootGates` (ADR 0031) — each of those is
|
|
14
|
+
# scoped to its own seam (IR-in/artifact-out; per-boot phased gates).
|
|
15
|
+
# A shared base is deferred until a third registry actually wants one.
|
|
16
|
+
#
|
|
17
|
+
# NAME-KEYED, PROCESS-WIDE, NOT PER-BOOT — unlike `BootGates` (one
|
|
18
|
+
# instance per `Loader.boot` call, because gate REGISTRATION is
|
|
19
|
+
# capability-conditional per registry), a persistence plugin is either
|
|
20
|
+
# `require`d into this process or it isn't; there is no "this boot's
|
|
21
|
+
# registry doesn't need it" case to isolate against, so a plain
|
|
22
|
+
# module-level hash is the right shape here, not an instantiable class.
|
|
23
|
+
module Plugin
|
|
24
|
+
@plugins = {}
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
def register(name, plugin)
|
|
28
|
+
@plugins[name.to_sym] = plugin
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def registered?(name)
|
|
32
|
+
@plugins.key?(name.to_sym)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def each(&)
|
|
36
|
+
@plugins.each_value(&)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def any? = !@plugins.empty?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# `Ports::Persistence.plugin?(:era)` / `.each_plugin` — the ordinary
|
|
44
|
+
# call surface; `Plugin.register`/`.registered?`/`.each` stay reachable
|
|
45
|
+
# directly for a plugin's own file to call at require-time.
|
|
46
|
+
module_function
|
|
47
|
+
|
|
48
|
+
def register_plugin(name, plugin) = Plugin.register(name, plugin)
|
|
49
|
+
def plugin?(name) = Plugin.registered?(name)
|
|
50
|
+
def each_plugin(&) = Plugin.each(&)
|
|
51
|
+
def plugins_loaded? = Plugin.any?
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|