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,157 @@
|
|
|
1
|
+
require_relative "../fqn"
|
|
2
|
+
require_relative "../facade/handle"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
class Router
|
|
6
|
+
# Installs optional Ruby syntax over an already-loaded router. FQN
|
|
7
|
+
# resolution stays in Router; this object only adapts it to constants and
|
|
8
|
+
# method calls.
|
|
9
|
+
class NamespaceInstaller
|
|
10
|
+
class OptionsProxy
|
|
11
|
+
def initialize(router:, realm:, domain:, aggregate:, options:)
|
|
12
|
+
unknown = options.keys - [:version]
|
|
13
|
+
raise ArgumentError, "unknown router options: #{unknown.join(', ')}" unless unknown.empty?
|
|
14
|
+
|
|
15
|
+
@router, @realm, @domain, @aggregate = router, realm, domain, aggregate
|
|
16
|
+
@version = options[:version]&.to_s
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def method_missing(verb, **args)
|
|
20
|
+
fqn = fqn_for(verb)
|
|
21
|
+
fqn.command? ? @router.dispatch(fqn.to_s, **args) : @router.query(fqn.to_s, **args)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def respond_to_missing?(verb, include_private = false)
|
|
25
|
+
Fqn.command_name?(verb) || Fqn.query_name?(verb) || super
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def fqn_for(verb)
|
|
31
|
+
if Fqn.command_name?(verb)
|
|
32
|
+
Fqn.command(realm: @realm, domain: @domain, version: @version, aggregate: @aggregate, command: verb)
|
|
33
|
+
elsif Fqn.query_name?(verb)
|
|
34
|
+
Fqn.query(realm: @realm, domain: @domain, version: @version, aggregate: @aggregate, query: verb)
|
|
35
|
+
else
|
|
36
|
+
raise NoMethodError, "#{verb.inspect} is not a Bluebook command or query"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def initialize(router)
|
|
42
|
+
@router = router
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def install!
|
|
46
|
+
current_entries.each { |entry| install_namespace_entry(entry) }
|
|
47
|
+
install_shortcuts!
|
|
48
|
+
install_aggregate_doors!
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
attr_reader :router
|
|
55
|
+
|
|
56
|
+
def current_entries = router.available.reject { |entry| entry.fqn.version }
|
|
57
|
+
|
|
58
|
+
def install_namespace_entry(entry)
|
|
59
|
+
target = namespace_for(entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate)
|
|
60
|
+
address = entry.fqn.to_s
|
|
61
|
+
active_router = router
|
|
62
|
+
target.define_singleton_method(:options) do |**options|
|
|
63
|
+
OptionsProxy.new(router: active_router, realm: entry.fqn.realm, domain: entry.fqn.domain,
|
|
64
|
+
aggregate: entry.fqn.aggregate, options: options)
|
|
65
|
+
end
|
|
66
|
+
if entry.command?
|
|
67
|
+
target.define_singleton_method(entry.fqn.verb) { |**args| active_router.dispatch(address, **args) }
|
|
68
|
+
else
|
|
69
|
+
target.define_singleton_method(entry.fqn.verb) { |**args| active_router.query(address, **args) }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# `.find`/`.all`/`.count`/`.events`/`.repository` — the SAME read/CRUD
|
|
74
|
+
# surface `Facade::Surface::AggregateDoor` gives a plain `Hecks
|
|
75
|
+
# .boot`, missing here until now: `install_namespace_entry` above
|
|
76
|
+
# installs ONE method per declared VERB, so an aggregate with no
|
|
77
|
+
# commands or queries of its own shape (or simply never asked for
|
|
78
|
+
# a `find`-shaped query) had no way to look up one record by id
|
|
79
|
+
# through the router surface at all — real gap, hit live building
|
|
80
|
+
# a `List` aggregate meant to be read this way. Grouped by
|
|
81
|
+
# (realm, domain, aggregate) rather than installed per-verb,
|
|
82
|
+
# because unlike a command or query this is the SAME five methods
|
|
83
|
+
# regardless of which verb happened to trigger this aggregate's
|
|
84
|
+
# own namespace module into existing.
|
|
85
|
+
def install_aggregate_doors!
|
|
86
|
+
current_entries.reject { |entry| entry.fqn.aggregate.nil? }
|
|
87
|
+
.group_by { |entry| [entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate] }
|
|
88
|
+
.each_value { |entries| install_aggregate_door(entries.first) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def install_aggregate_door(entry)
|
|
92
|
+
dispatcher = entry.dispatcher
|
|
93
|
+
domain = entry.fqn.domain
|
|
94
|
+
ir = dispatcher.registry.bluebook(domain)&.aggregate(entry.fqn.aggregate)
|
|
95
|
+
return unless ir # a query/command-only entry whose owner isn't a real aggregate root
|
|
96
|
+
|
|
97
|
+
fqn = "#{domain}::#{ir.hecks_name}"
|
|
98
|
+
target = namespace_for(entry.fqn.realm, domain, entry.fqn.aggregate)
|
|
99
|
+
|
|
100
|
+
target.define_singleton_method(:repository) { dispatcher.registry.repository(domain, ir) }
|
|
101
|
+
target.define_singleton_method(:count) { dispatcher.registry.repository(domain, ir).count }
|
|
102
|
+
target.define_singleton_method(:events) { dispatcher.events.select { |event| event.aggregate == fqn } }
|
|
103
|
+
|
|
104
|
+
target.define_singleton_method(:find) do |id|
|
|
105
|
+
found = dispatcher.registry.repository(domain, ir).find(id)
|
|
106
|
+
found && Facade::Handle.new(dispatcher: dispatcher, domain: domain, ir: ir, instance: found)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
target.define_singleton_method(:all) do
|
|
110
|
+
dispatcher.registry.repository(domain, ir).all.map do |instance|
|
|
111
|
+
Facade::Handle.new(dispatcher: dispatcher, domain: domain, ir: ir, instance: instance)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def install_shortcuts!
|
|
117
|
+
installer = self
|
|
118
|
+
current_entries.reject { |entry| entry.fqn.aggregate.nil? }
|
|
119
|
+
.group_by { |entry| [entry.fqn.aggregate, entry.fqn.verb] }
|
|
120
|
+
.each do |(aggregate, verb), candidates|
|
|
121
|
+
shortcut_target(aggregate).define_singleton_method(verb) { |**args|
|
|
122
|
+
installer.send(:dispatch_short, candidates, **args)
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def dispatch_short(candidates, **args)
|
|
128
|
+
if candidates.length > 1
|
|
129
|
+
shown = candidates.map { |entry| entry.fqn.to_s }.sort.join(", ")
|
|
130
|
+
aggregate, verb = candidates.first.fqn.aggregate, candidates.first.fqn.verb
|
|
131
|
+
raise AmbiguousShortRoute, "#{aggregate}.#{verb} is ambiguous — choose one of: #{shown}"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
entry = candidates.fetch(0)
|
|
135
|
+
entry.command? ? router.dispatch(entry.fqn.to_s, **args) : router.query(entry.fqn.to_s, **args)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def shortcut_target(aggregate)
|
|
139
|
+
constant = Object.const_get(aggregate, false) if Object.const_defined?(aggregate, false)
|
|
140
|
+
raise NameError,
|
|
141
|
+
"cannot install Bluebook shortcut #{aggregate}: it is not a module" if constant && !constant.is_a?(Module)
|
|
142
|
+
|
|
143
|
+
constant || Object.const_set(aggregate, Module.new)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def namespace_for(realm, domain, aggregate)
|
|
147
|
+
[realm, domain, aggregate].compact.reduce(Object) do |parent, name|
|
|
148
|
+
constant = parent.const_get(name, false) if parent.const_defined?(name, false)
|
|
149
|
+
raise NameError,
|
|
150
|
+
"cannot install Bluebook route under #{parent}::#{name}: it is not a module" if constant && !constant.is_a?(Module)
|
|
151
|
+
|
|
152
|
+
constant || parent.const_set(name, Module.new)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
data/lib/hecks/router.rb
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
# The public, project-wide dispatch door. It owns address resolution only;
|
|
3
|
+
# each discovered Bluebook keeps its own runtime and persistence bindings.
|
|
4
|
+
class Router
|
|
5
|
+
class UnknownAddress < StandardError; end
|
|
6
|
+
class WrongVerbKind < StandardError; end
|
|
7
|
+
class NotBooted < StandardError; end
|
|
8
|
+
class AmbiguousShortRoute < StandardError; end
|
|
9
|
+
|
|
10
|
+
attr_reader :register
|
|
11
|
+
|
|
12
|
+
def self.load(root) = new(Bluebook::ProjectLoader.load(root))
|
|
13
|
+
|
|
14
|
+
# Install one project router for ordinary application calls. The explicit
|
|
15
|
+
# `Router.load` API remains useful for tests and embedded hosts.
|
|
16
|
+
def self.boot(root)
|
|
17
|
+
router = load(root)
|
|
18
|
+
router.install_namespace!
|
|
19
|
+
@default = router
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.default
|
|
23
|
+
@default || raise(NotBooted, "no project router is booted — call Hecks::Router.boot(root) first")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.dispatch(address, **args) = default.dispatch(address, **args)
|
|
27
|
+
def self.query(address, **args) = default.query(address, **args)
|
|
28
|
+
|
|
29
|
+
def initialize(register)
|
|
30
|
+
@register = register
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def available = register.entries.values
|
|
34
|
+
|
|
35
|
+
def install_namespace! = NamespaceInstaller.new(self).install!
|
|
36
|
+
|
|
37
|
+
def resolve(address)
|
|
38
|
+
fqn = Fqn.parse(address)
|
|
39
|
+
raise UnknownAddress, "router addresses require a realm: #{address.inspect}" unless fqn.realm
|
|
40
|
+
|
|
41
|
+
register.fetch(fqn.to_s)
|
|
42
|
+
rescue KeyError
|
|
43
|
+
raise UnknownAddress, "no Bluebook route for #{address.inspect}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def dispatch(address, **args)
|
|
47
|
+
entry = resolve(address)
|
|
48
|
+
raise WrongVerbKind, "#{address.inspect} names a query; use #query" unless entry.command?
|
|
49
|
+
|
|
50
|
+
entry.dispatcher.dispatch(local_verb(entry), **args)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def query(address, **args)
|
|
54
|
+
entry = resolve(address)
|
|
55
|
+
raise WrongVerbKind, "#{address.inspect} names a command; use #dispatch" unless entry.query?
|
|
56
|
+
|
|
57
|
+
entry.dispatcher.query(local_verb(entry), **args)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def local_verb(entry)
|
|
63
|
+
return "#{entry.fqn.domain}.#{entry.declared_verb}" unless entry.fqn.aggregate
|
|
64
|
+
|
|
65
|
+
"#{entry.fqn.domain}::#{entry.fqn.aggregate}.#{entry.declared_verb}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
require_relative "router/namespace_installer"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Runtime
|
|
3
|
+
# A PROCESS-WIDE, STRIPED MUTEX REGISTRY — the concurrency-control
|
|
4
|
+
# mechanism for every adapter that does NOT declare
|
|
5
|
+
# `:optimistic_concurrency` (Heki, Memory today; see
|
|
6
|
+
# `CommandInterpreter#call`/`EntityInterpreter#call`, which choose
|
|
7
|
+
# between this and Postgres's CAS+retry purely off
|
|
8
|
+
# `repository.capabilities`).
|
|
9
|
+
#
|
|
10
|
+
# WHY A LOCK SUFFICES HERE AND CAS IS NOT NEEDED: both adapters hold
|
|
11
|
+
# process-local data. `Adapters::Memory.tenant_capable?`'s own comment
|
|
12
|
+
# states the confirmed fact this relies on — two `Runtime.boot` calls
|
|
13
|
+
# get two entirely separate adapter instances; there is never a SECOND
|
|
14
|
+
# PROCESS writing the same Heki file or the same Memory Hash, only
|
|
15
|
+
# possibly other THREADS within this one process. A `Mutex` held for
|
|
16
|
+
# the full hydrate-through-save critical section closes the identical
|
|
17
|
+
# lost-update gap CAS closes for Postgres, with no schema, no version
|
|
18
|
+
# column, and no retry loop — the second thread simply doesn't start
|
|
19
|
+
# its own hydrate until the first thread's save has landed.
|
|
20
|
+
#
|
|
21
|
+
# STRIPED, NOT ONE GLOBAL LOCK: keyed by `[domain, aggregate.hecks_name,
|
|
22
|
+
# id]`, so two dispatches against two DIFFERENT records never block
|
|
23
|
+
# each other. The registry Hash itself is guarded by its own top-level
|
|
24
|
+
# Mutex only for the moment a new per-key Mutex is created — two
|
|
25
|
+
# threads locking DIFFERENT keys for the first time never wait on one
|
|
26
|
+
# another beyond that brief creation window.
|
|
27
|
+
module AggregateLock
|
|
28
|
+
@registry_lock = Mutex.new
|
|
29
|
+
@locks = {}
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
# `AggregateLock.for(domain, aggregate, id).synchronize { ... }`
|
|
33
|
+
# `id: nil` — identity could not be resolved yet (see
|
|
34
|
+
# `Identity.best_effort`) — locks by aggregate TYPE alone, coarser
|
|
35
|
+
# (every record of this aggregate serializes against every other)
|
|
36
|
+
# but still correct: it can only ever make dispatch MORE
|
|
37
|
+
# conservative than a resolved id would.
|
|
38
|
+
def for(domain, aggregate, id = nil)
|
|
39
|
+
key = id.nil? ? [domain.to_s, aggregate.hecks_name] : [domain.to_s, aggregate.hecks_name, id.to_s]
|
|
40
|
+
@registry_lock.synchronize { @locks[key] ||= Mutex.new }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Runtime
|
|
3
|
+
# A boot's own small set of phase-tagged, conditionally-present gates —
|
|
4
|
+
# ADR 0031. Registration is instance-scoped, ONE PER `Loader.boot`
|
|
5
|
+
# CALL, never a module-level singleton: a process that boots more than
|
|
6
|
+
# one registry in its lifetime (every spec suite does) must never let
|
|
7
|
+
# one boot's capability profile leak into the next boot's gate list.
|
|
8
|
+
#
|
|
9
|
+
# A gate is anything `.call(registry, directory)`-able — an existing
|
|
10
|
+
# module method handed over as a `Method` object (`EraCheck.method
|
|
11
|
+
# (:check_lineage!)`) needs no wrapper; a bare block does. Phases run
|
|
12
|
+
# in the order `run!` is called, gates within a phase in registration
|
|
13
|
+
# order — today exactly one gate per phase, so ordering among
|
|
14
|
+
# same-phase gates has never been exercised.
|
|
15
|
+
#
|
|
16
|
+
# This is deliberately NOT the same registry `Hecks::Projector` uses
|
|
17
|
+
# (ADR 0027) — that one is a process-wide, static IR-in/artifact-out
|
|
18
|
+
# registry with no bindings and no live-state mutation; this one is
|
|
19
|
+
# per-boot and gates real I/O (a Postgres mint, a saga-store read).
|
|
20
|
+
# Sharing a primitive between them is deferred until a third consumer
|
|
21
|
+
# actually wants it (0031's own Rejected Alternatives).
|
|
22
|
+
class BootGates
|
|
23
|
+
def initialize
|
|
24
|
+
@gates = Hash.new { |h, k| h[k] = [] }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def register(name, gate, phase:)
|
|
28
|
+
@gates[phase] << [name, gate]
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def registered?(name)
|
|
33
|
+
@gates.values.flatten(1).any? { |registered_name, _gate| registered_name == name }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def run!(phase, registry, directory)
|
|
37
|
+
@gates[phase].each { |pair| pair.last.call(registry, directory) }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Runtime
|
|
3
|
+
# WHO IS DISPATCHING, bound for the duration of a block — the ambient
|
|
4
|
+
# counterpart to `Runtime.with_registry`, but `Thread.current`-backed
|
|
5
|
+
# rather than a plain module ivar: registry-binding happens at BOOT
|
|
6
|
+
# time (single-threaded by construction), this happens at DISPATCH
|
|
7
|
+
# time, which a future per-request caller (docs/rails-integration.md's
|
|
8
|
+
# still-unbuilt `WebDoor`) needs to bind safely under concurrency.
|
|
9
|
+
#
|
|
10
|
+
# A child `Thread.new` spawned mid-block does not inherit this — plain
|
|
11
|
+
# Ruby thread-local semantics — but nothing in this codebase spawns a
|
|
12
|
+
# worker thread mid-dispatch today, so this is a fact worth naming
|
|
13
|
+
# rather than a gap worth solving.
|
|
14
|
+
module Caller
|
|
15
|
+
# `actor_id` is OPTIONAL, on top of the role that has always been
|
|
16
|
+
# here — a caller that states only a role (every caller before this)
|
|
17
|
+
# is checked the way it always was, string equality against the
|
|
18
|
+
# command's own `role`. A caller that also names WHO it is lets
|
|
19
|
+
# `CommandRules::Authorization` check a real Governance
|
|
20
|
+
# `RoleAssignment` instead, once the command's domain has Governance
|
|
21
|
+
# attached — see that module's own header for the full split.
|
|
22
|
+
#
|
|
23
|
+
# `as_of` and `scope` are BOTH optional too, and both self-asserted
|
|
24
|
+
# by the caller rather than derived from the command — deliberately:
|
|
25
|
+
# `as_of` is filled at the door from `Ports::Clock.now`, never
|
|
26
|
+
# inside the interpreter (see `Ports::Clock`'s own header), so
|
|
27
|
+
# there is no other place to source it from. `scope` stays here
|
|
28
|
+
# rather than becoming a command-level DSL construct on purpose —
|
|
29
|
+
# a scope check that lived in the bluebook would put an
|
|
30
|
+
# authorization concern inside the domain declaration itself; this
|
|
31
|
+
# keeps it an application-boundary fact instead, the same shape
|
|
32
|
+
# `role`/`actor_id` already are. The tradeoff: a caller states what
|
|
33
|
+
# scope it is acting in, and `holds_role?` verifies a live grant
|
|
34
|
+
# exists for THAT scope — it does not independently confirm the
|
|
35
|
+
# scope matches whatever the command's own target data belongs to.
|
|
36
|
+
Current = Struct.new(:role, :actor_id, :as_of, :scope, keyword_init: true)
|
|
37
|
+
|
|
38
|
+
module_function
|
|
39
|
+
|
|
40
|
+
def current = Thread.current[:hecks_caller]
|
|
41
|
+
|
|
42
|
+
def as(role:, actor_id: nil, as_of: nil, scope: nil)
|
|
43
|
+
previous = Thread.current[:hecks_caller]
|
|
44
|
+
Thread.current[:hecks_caller] = Current.new(
|
|
45
|
+
role: role.to_s, actor_id: actor_id&.to_s, as_of: as_of, scope: scope&.to_s
|
|
46
|
+
)
|
|
47
|
+
yield
|
|
48
|
+
ensure
|
|
49
|
+
Thread.current[:hecks_caller] = previous
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# A REACTION IS THE SYSTEM ACTING, not the caller who happened to be
|
|
53
|
+
# on the stack when the triggering command ran — `Dispatcher#reenter`
|
|
54
|
+
# clears the ambient caller around a reaction's own dispatch so a
|
|
55
|
+
# triggering caller's role can neither satisfy nor block a reaction
|
|
56
|
+
# command it has nothing to do with.
|
|
57
|
+
def without
|
|
58
|
+
previous = Thread.current[:hecks_caller]
|
|
59
|
+
Thread.current[:hecks_caller] = nil
|
|
60
|
+
yield
|
|
61
|
+
ensure
|
|
62
|
+
Thread.current[:hecks_caller] = previous
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Runtime
|
|
3
|
+
# WHICH PORTS A BOOT CAN ACTUALLY FULFILL, read off the registry it already
|
|
4
|
+
# holds. `registry.ports` names every port a domain declared a dependency
|
|
5
|
+
# on ; `registry.adapters` names every adapter wired to implement one — the
|
|
6
|
+
# same `adapter.port == port.name` match `Ports::Extraction` and
|
|
7
|
+
# `Ports::IdentityGeneration` already make for themselves, one at a time,
|
|
8
|
+
# each time they resolve. This is that same question asked once, for
|
|
9
|
+
# every port at once, so a gap in the wiring is something a caller can ASK
|
|
10
|
+
# about rather than something a live dispatch discovers by refusing.
|
|
11
|
+
#
|
|
12
|
+
# `cycles` answers `[]`, always, and honestly: nothing in this port model
|
|
13
|
+
# lets one port depend on another — a port names a verb and a signal, an
|
|
14
|
+
# adapter names the port it implements, and neither can point at a third
|
|
15
|
+
# port. There is no edge for a cycle to be made of.
|
|
16
|
+
class CapabilityGraph
|
|
17
|
+
def initialize(registry)
|
|
18
|
+
@registry = registry
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# { port name => [adapter name, ...] }, for every port the registry
|
|
22
|
+
# declares — including the ports nothing implements, so a caller can
|
|
23
|
+
# tell "declared, unfulfilled" from "never declared at all".
|
|
24
|
+
def fulfillments
|
|
25
|
+
@fulfillments ||= @registry.ports.each_key.to_h { |name| [name, adapters_for(name)] }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The ports with zero adapters bound — the gap `Runtime::WiringError`
|
|
29
|
+
# would otherwise only surface at the moment something tries to dispatch
|
|
30
|
+
# through one.
|
|
31
|
+
def unfulfilled
|
|
32
|
+
fulfillments.select { |_, adapters| adapters.empty? }.keys
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def cycles = []
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def adapters_for(port_name)
|
|
40
|
+
@registry.adapters.values.select { |adapter| adapter.port == port_name }.map(&:name)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
require_relative "../../naming"
|
|
2
|
+
require_relative "../errors"
|
|
3
|
+
require_relative "../refusal_wording"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Runtime
|
|
7
|
+
class CommandInterpreter
|
|
8
|
+
# The payload gate: a command takes the arguments it declares — all of
|
|
9
|
+
# them, and no others.
|
|
10
|
+
module ArgumentGate
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Anything else used to ride along in the payload untouched —
|
|
14
|
+
# normalize_args walks the DECLARED attributes, so a name the command
|
|
15
|
+
# never had was simply never looked at. A misspelled argument did
|
|
16
|
+
# nothing, in silence.
|
|
17
|
+
#
|
|
18
|
+
# The keys that are legitimately not attributes are the ones that ADDRESS
|
|
19
|
+
# the aggregate rather than describe it : `id`, whatever the aggregate is
|
|
20
|
+
# identified by, and the reference key of the root a command reaches
|
|
21
|
+
# through. Refusing those would refuse every dispatch there is.
|
|
22
|
+
#
|
|
23
|
+
# `extra_identity_heads:` — EntityInterpreter's own callers only. An
|
|
24
|
+
# entity dispatch addresses not just the root aggregate but every
|
|
25
|
+
# entity ALONG THE CHAIN it walks to reach the piece the command
|
|
26
|
+
# actually belongs to (`Handler.Dispatch.Bind` is two hops), and each
|
|
27
|
+
# hop's own `identity_heads` is addressing the same way the root's
|
|
28
|
+
# are — `element_of` (entity_element.rb) reads them straight out of
|
|
29
|
+
# `args` to find the element, never as a fact the command itself
|
|
30
|
+
# declares. `[]` for a plain aggregate/port-operation dispatch, which
|
|
31
|
+
# has no chain to add.
|
|
32
|
+
def refuse_unknown_arguments(domain, aggregate, command, args, extra_identity_heads: [])
|
|
33
|
+
addressing = [:id, *aggregate.identity_heads, *extra_identity_heads, reference_key(command)] +
|
|
34
|
+
correlation_keys(domain)
|
|
35
|
+
known = (command.attributes.map(&:name) + addressing).compact.map(&:to_sym)
|
|
36
|
+
# SORTED. Payload order is whatever the caller happened to write, and
|
|
37
|
+
# refusal wording is contract — pinned byte-for-byte by the corpus, so
|
|
38
|
+
# it cannot depend on hash iteration order.
|
|
39
|
+
unknown = (args.keys.map(&:to_sym) - known).sort
|
|
40
|
+
return if unknown.empty?
|
|
41
|
+
|
|
42
|
+
raise UnknownArgument,
|
|
43
|
+
RefusalWording.render("UnknownArgument", "unknown_args",
|
|
44
|
+
command: command.hecks_name, unknown: unknown.join(", "),
|
|
45
|
+
declared: declared_reading(command))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# And it takes ALL of them. The other half of the same sentence, missing
|
|
49
|
+
# until fuzz went looking : a name the command never declared was refused,
|
|
50
|
+
# while a name it DID declare could simply be left out.
|
|
51
|
+
#
|
|
52
|
+
# `Customer.Register` without its `name` used to be refused — but by
|
|
53
|
+
# ACCIDENT, and with a lie for a message. `then_set :name, to: :name` found
|
|
54
|
+
# nothing to resolve, passed the literal symbol on, and coercion reported
|
|
55
|
+
# `name is a PersonName — pass its fields as an object`, which describes a
|
|
56
|
+
# mistake the caller did not make. The real mistake — an argument simply
|
|
57
|
+
# missing — was never the one named, and nothing refused it on purpose.
|
|
58
|
+
#
|
|
59
|
+
# No command attribute anywhere in the corpus carries a default — checked,
|
|
60
|
+
# all eight chapters, zero — so there is no optional argument for this to
|
|
61
|
+
# step on. Every declared attribute is a fact the command needs.
|
|
62
|
+
#
|
|
63
|
+
# `aggregate:` — PRESENT ONLY FOR A PORT OPERATION. An aggregate
|
|
64
|
+
# command's own self-address never reaches this list at all
|
|
65
|
+
# (`CommandBuilder#reference_to`'s bare self-reference mints no
|
|
66
|
+
# attribute to be absent) — but `PortOperationBuilder#reference_to`
|
|
67
|
+
# ALWAYS mints one (this file's own header on `PortOperation`: "no
|
|
68
|
+
# creates?/acts_on distinction to protect"), because an operation
|
|
69
|
+
# historically had no OTHER way to say which record it addressed.
|
|
70
|
+
# `to:` is that other way now (`Dispatcher#port_invocation`
|
|
71
|
+
# promotes the identity attribute's own value out of the payload
|
|
72
|
+
# and into routing) — which left the attribute still DECLARED,
|
|
73
|
+
# still non-optional, and now never present in `args` at all: every
|
|
74
|
+
# operation with an identity attribute refused its own well-formed
|
|
75
|
+
# calls, dispatched exactly the way `to:` intends. Exempted here on
|
|
76
|
+
# the same terms an aggregate command's self-address always was —
|
|
77
|
+
# an address, not a fact the operation still needs handed back.
|
|
78
|
+
def refuse_absent_arguments(command, args, aggregate: nil)
|
|
79
|
+
given = args.keys.map(&:to_sym)
|
|
80
|
+
exempt = aggregate && command.respond_to?(:identity_attribute) &&
|
|
81
|
+
command.identity_attribute(aggregate.hecks_name)&.name
|
|
82
|
+
required = command.attributes.reject(&:optional?).map { |attribute| attribute.name.to_sym } - [exempt]
|
|
83
|
+
# SORTED, for the same reason the unknown list is : refusal wording is
|
|
84
|
+
# contract, and a pinned wording cannot depend on the order a set
|
|
85
|
+
# difference happens to be computed in.
|
|
86
|
+
absent = (required - given).sort
|
|
87
|
+
return if absent.empty?
|
|
88
|
+
|
|
89
|
+
raise AbsentArgument,
|
|
90
|
+
RefusalWording.render("AbsentArgument", "absent_args",
|
|
91
|
+
command: command.hecks_name, absent: absent.join(", "),
|
|
92
|
+
declared: declared_reading(command))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# A COMMAND THAT DECLARES NOTHING STILL HAS TO SAY SO. `Account
|
|
96
|
+
# .Freeze` is `reference_to Account` and no attributes at all, so
|
|
97
|
+
# `{declared}` rendered empty and the sentence trailed off mid-
|
|
98
|
+
# clause : "Freeze does not declare standing — it takes ". Read
|
|
99
|
+
# against a real refusal that shape is worse than unhelpful — it
|
|
100
|
+
# looks like the message itself is broken, right where a caller
|
|
101
|
+
# is already trying to work out what went wrong.
|
|
102
|
+
#
|
|
103
|
+
# Only the empty case changes. Every command that declares even
|
|
104
|
+
# one attribute renders exactly as it did before, because refusal
|
|
105
|
+
# wording is contract and pinned byte-for-byte by the corpus (see
|
|
106
|
+
# `refuse_unknown_arguments`' own note on sorting for why).
|
|
107
|
+
def declared_reading(command)
|
|
108
|
+
declared = command.attributes.map(&:name)
|
|
109
|
+
declared.empty? ? "none" : declared.join(", ")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# What a process manager correlates by is ROUTING, not description. A saga
|
|
113
|
+
# threads its correlation key through every leg it dispatches so the event
|
|
114
|
+
# each leg emits carries it and the next step can be correlated — so the key
|
|
115
|
+
# arrives on commands that never declare it, and legitimately.
|
|
116
|
+
#
|
|
117
|
+
# This is the weakest part of the gate. Correlation is the SAGA's business,
|
|
118
|
+
# and the better shape is for the saga to stamp its own key onto the event
|
|
119
|
+
# it caused rather than smuggle it through the command's payload. Until it
|
|
120
|
+
# does, refusing the key here would break every saga in the corpus.
|
|
121
|
+
def correlation_keys(domain)
|
|
122
|
+
Array(@registry.bluebook(domain)&.process_managers)
|
|
123
|
+
.filter_map { |saga| saga.correlates_by && saga.correlation_head }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def reference_key(command)
|
|
127
|
+
target = command.references.to_s
|
|
128
|
+
return nil if target.empty?
|
|
129
|
+
|
|
130
|
+
Naming.reference_key(target)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|