hecks 1.3.0 → 1.4.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 +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +5 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +4 -4
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +18 -18
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +23 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +101 -87
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +170 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +180 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +140 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +37 -18
- data/lib/hecks/fuzzing/form_census.rb +35 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +62 -62
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +19 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +9 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +154 -49
- data/lib/hecks/fuzzing/properties/guards.rb +42 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +22 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +24 -24
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +64 -40
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +114 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +10 -10
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +141 -112
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +35 -35
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +58 -31
- data/lib/hecks/fuzzing/sequence_generator.rb +111 -23
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +23 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +4 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +49 -18
- data/lib/hecks/fuzzing/value_generator.rb +14 -14
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +25 -25
- data/lib/hecks/ports/access_control.rb +58 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +3 -3
- data/lib/hecks/projections/glossary/markdown.rb +3 -3
- data/lib/hecks/projections/glossary/mermaid.rb +1 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +5 -5
- data/lib/hecks/projections/glossary.rb +15 -15
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +11 -11
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +57 -13
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +42 -63
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +35 -35
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +151 -67
- data/lib/hecks/runtime/entity_interpreter.rb +108 -82
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +55 -64
- data/lib/hecks/runtime/reaction_invocation.rb +20 -20
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +45 -28
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -153
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +64 -64
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +318 -127
- data/lib/hecks/runtime/value/entity_list_coercion.rb +34 -34
- data/lib/hecks/runtime/value.rb +21 -21
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +22 -2
|
@@ -3,41 +3,41 @@ require_relative "../saga_pending_dispatch"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Runtime
|
|
5
5
|
class Registry
|
|
6
|
-
#
|
|
6
|
+
# **Saga persistence** — no new DSL verb, no new world setting.
|
|
7
7
|
# Whatever adapter a domain's own aggregates already use for
|
|
8
8
|
# durability, its sagas use the same one, automatically: three
|
|
9
|
-
#
|
|
9
|
+
# optional adapter methods (`save_saga`/`delete_saga`/`each_saga`),
|
|
10
10
|
# `respond_to?`-checked the same way `Ports::Persistence::AppendOnly`
|
|
11
11
|
# already treats an adapter's own optional methods
|
|
12
12
|
# (`find`/`all`/`count`/`reset!`/`events`/`record_event`).
|
|
13
13
|
module SagaPersistence
|
|
14
|
-
# Resolved and memoized per domain — the
|
|
14
|
+
# Resolved and memoized per domain — the first real aggregate
|
|
15
15
|
# declared in the domain's own bluebook is the resolution
|
|
16
16
|
# anchor. `Ports::Persistence::BindingPolicy.resolve` for that
|
|
17
17
|
# aggregate already falls back to a domain-level default bind
|
|
18
18
|
# (§0's `Hecksagon#bind_for`) before falling back to Memory, so
|
|
19
19
|
# this needs no separate "was a default declared" branch of its
|
|
20
|
-
# own — whatever adapter that anchor resolves to already
|
|
20
|
+
# own — whatever adapter that anchor resolves to already is the
|
|
21
21
|
# domain's own default in the normal case (every aggregate
|
|
22
22
|
# shares one adapter), and is the honest, documented fallback
|
|
23
23
|
# for the rarer domain genuinely split across more than one
|
|
24
24
|
# local adapter with no default declared.
|
|
25
25
|
#
|
|
26
|
-
#
|
|
26
|
+
# Genuinely lazy, genuinely POST-boot — unlike `rehydrate_sagas!`
|
|
27
27
|
# below (boot-only), this is called from live dispatch
|
|
28
28
|
# (`SagaInterpreter#checkpoint`/`#end_saga`, on every saga
|
|
29
|
-
# transition), so the
|
|
29
|
+
# transition), so the first call for a given domain can come from
|
|
30
30
|
# any dispatching thread, not just the boot thread. `@saga_persistence`
|
|
31
31
|
# itself is a plain Hash stood up once in `Registry#initialize` (no
|
|
32
32
|
# race on the container), but `resolve_saga_persistence` is real work
|
|
33
33
|
# (a `BindingPolicy.resolve` plus a lazy `repository` build) whose
|
|
34
|
-
#
|
|
35
|
-
# through — must be the
|
|
34
|
+
# result — the actual adapter instance a domain's sagas persist
|
|
35
|
+
# through — must be the same object for every caller: two threads
|
|
36
36
|
# racing the first lookup and each building their own adapter would
|
|
37
37
|
# silently split one domain's saga writes across two adapter
|
|
38
38
|
# instances (worse than `Dispatcher#reaction_depth`'s M20 — that bug
|
|
39
|
-
# corrupted a counter; this one can corrupt
|
|
40
|
-
# state lands in). Double-checked locking against a
|
|
39
|
+
# corrupted a counter; this one can corrupt which store a saga's
|
|
40
|
+
# state lands in). Double-checked locking against a dedicated mutex
|
|
41
41
|
# — never `@saga_mutex` — see `Registry#initialize`'s own comment for
|
|
42
42
|
# why reusing that one would deadlock.
|
|
43
43
|
def saga_persistence(domain)
|
|
@@ -47,23 +47,23 @@ module Hecks
|
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
#
|
|
50
|
+
# Walks every loaded domain, repopulating `saga_instances` from
|
|
51
51
|
# whatever `saga_persistence(domain)` resolves to — a real store
|
|
52
52
|
# for a domain whose adapter answers the capability, `each_saga`
|
|
53
53
|
# yielding real rows; `NULL_SAGA_STORE`'s own `each_saga` for
|
|
54
54
|
# everything else, which never yields at all, so this needs no
|
|
55
|
-
# `respond_to?` branch of its own — the
|
|
55
|
+
# `respond_to?` branch of its own — the same reason every other
|
|
56
56
|
# call site in this capability never needs one. Called once at
|
|
57
57
|
# boot (`Loader.boot`, between `verify!` and dispatcher
|
|
58
58
|
# construction) — a process that's been running has no reason to
|
|
59
59
|
# re-walk its own already-current `saga_instances`.
|
|
60
60
|
#
|
|
61
|
-
#
|
|
61
|
+
# Boot-time-only (or its test-runner equivalent) — this method's
|
|
62
62
|
# only callers are `Loader.run_boot_gates!` (single-threaded, before
|
|
63
63
|
# `dispatcher_for` ever exists) and `Registry#reset_runtime_state!`
|
|
64
64
|
# (single-threaded test runner — see that method's own comment).
|
|
65
65
|
# Never called from live dispatch, so `@saga_instances` mutation
|
|
66
|
-
# here has no concurrent caller to race, unlike its
|
|
66
|
+
# here has no concurrent caller to race, unlike its other two write
|
|
67
67
|
# points inside `@saga_mutex.synchronize` blocks (`saga_interpreter.
|
|
68
68
|
# rb`), which genuinely do and are guarded accordingly.
|
|
69
69
|
# rubocop:disable-next Hecks/ThreadSharedIvarMutation
|
|
@@ -81,28 +81,28 @@ module Hecks
|
|
|
81
81
|
|
|
82
82
|
private
|
|
83
83
|
|
|
84
|
-
#
|
|
84
|
+
# The other half of the outbox-shaped fix (see saga_pending_
|
|
85
85
|
# dispatch.rb) — `SAGA_PENDING_DISPATCH_KEY`, if the crashed
|
|
86
86
|
# process left it standing, means the row's own `state` was
|
|
87
87
|
# checkpointed but the dispatch cascade that justifies it may
|
|
88
88
|
# never have run. Stripped out of `memory` before it becomes
|
|
89
|
-
# this instance's
|
|
89
|
+
# this instance's live `:memory` (so nothing downstream — a
|
|
90
90
|
# `given`, a `with:` mapping, the fuzzer — ever sees it), and
|
|
91
|
-
# surfaced loudly instead: a
|
|
91
|
+
# surfaced loudly instead: a warning plus a `saga_log` entry,
|
|
92
92
|
# never an automatic redrive (see saga_pending_dispatch.rb for
|
|
93
93
|
# why redriving without idempotent delivery would be worse than
|
|
94
|
-
# the stall). This is real, durable crash-recovery
|
|
94
|
+
# the stall). This is real, durable crash-recovery visibility —
|
|
95
95
|
# still not the reconciliation itself, which stays a human's
|
|
96
96
|
# call until hecks has idempotent redelivery to make it safe.
|
|
97
97
|
# rubocop:disable-next Hecks/ThreadSharedIvarMutation -- same
|
|
98
98
|
# justification as `rehydrate_sagas!`'s own disable comment
|
|
99
|
-
# above: this method's
|
|
99
|
+
# above: this method's only caller is that boot-time-only walk,
|
|
100
100
|
# never live dispatch, so `@saga_log` has no concurrent writer
|
|
101
101
|
# to race here.
|
|
102
102
|
def warn_stalled_saga(domain, process_manager, correlation, state, pending)
|
|
103
103
|
# `.transform_keys(&:to_sym)` — Heki's own `each_saga` only
|
|
104
|
-
# symbolizes `memory`'s
|
|
105
|
-
# own `transform_keys`, one level deep); a value
|
|
104
|
+
# symbolizes `memory`'s top-level keys (`SagaStore#each_saga`'s
|
|
105
|
+
# own `transform_keys`, one level deep); a value nested under
|
|
106
106
|
# one of those keys, like this marker, comes back with plain
|
|
107
107
|
# string keys from Heki specifically, symbol keys already from
|
|
108
108
|
# Postgres/SQLite/D1's own `symbolize_names: true` parse. Normalizing
|
|
@@ -21,11 +21,11 @@ module Hecks
|
|
|
21
21
|
# A domain-level default (§0) — `persisted_by "Heki"` bare,
|
|
22
22
|
# applying to whichever aggregates don't override it — names
|
|
23
23
|
# no aggregate of its own, so there's nothing to look up in
|
|
24
|
-
# the bluebook for
|
|
24
|
+
# the bluebook for this row specifically. Still validate its
|
|
25
25
|
# own adapter/verb shape (the same reason
|
|
26
26
|
# `verify_default_adapter!` checks the framework-wide
|
|
27
27
|
# default the same way, aggregate-less). Coverage of real
|
|
28
|
-
# aggregates that only resolve
|
|
28
|
+
# aggregates that only resolve through this default comes
|
|
29
29
|
# from their own dispatch-time `BindingPolicy.resolve` —
|
|
30
30
|
# deliberately not required to be exhaustive here, the same
|
|
31
31
|
# leniency this method already extended to any aggregate
|
|
@@ -78,8 +78,8 @@ module Hecks
|
|
|
78
78
|
"and cannot satisfy #{bind.verb}"
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
#
|
|
82
|
-
#
|
|
81
|
+
# The method contract a `.port` file's `verb`/`signal` never
|
|
82
|
+
# carried — an adapter can name the right port, satisfy the right
|
|
83
83
|
# verb, and admit every `.world` setting `check_settings` checks,
|
|
84
84
|
# and still be missing the one method a live dispatch will
|
|
85
85
|
# actually call. `answers` is optional per port (an empty list is
|
|
@@ -99,7 +99,7 @@ module Hecks
|
|
|
99
99
|
"#{answers.map(&:inspect).join(', ')}"
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
#
|
|
102
|
+
# **The nine singleton ports' own gap** — `persistence`, `projection`
|
|
103
103
|
# and `loading` are per-aggregate bindings, checked above through
|
|
104
104
|
# every real `bind` a hexagon declares; a singleton port
|
|
105
105
|
# (`clock`, `authorization`, …) is never bound to an aggregate at
|
|
@@ -108,9 +108,9 @@ module Hecks
|
|
|
108
108
|
# already refuses zero or multiple implementations, live, at
|
|
109
109
|
# first dispatch — that stays exactly as-is here (0 or 2+ is
|
|
110
110
|
# ambiguity, not a method-contract question, and asserting every
|
|
111
|
-
# declared port
|
|
111
|
+
# declared port must have exactly one adapter would wrongly
|
|
112
112
|
# refuse a boot that simply never wires a port it doesn't use).
|
|
113
|
-
# This only ever tightens the
|
|
113
|
+
# This only ever tightens the one case those checks don't cover:
|
|
114
114
|
# exactly one adapter, wired, missing a method `answers` names.
|
|
115
115
|
PER_AGGREGATE_PORTS = %w[persistence projection loading].freeze
|
|
116
116
|
|
|
@@ -158,13 +158,13 @@ module Hecks
|
|
|
158
158
|
|
|
159
159
|
private
|
|
160
160
|
|
|
161
|
-
# `role`
|
|
162
|
-
#
|
|
161
|
+
# `role` is real access control only when governance can check it
|
|
162
|
+
# against something — a command that declares a role but whose
|
|
163
163
|
# domain never attaches Governance would leave that role forever
|
|
164
164
|
# unchecked, exactly the defect ADR 0025 §9 names ("role gates
|
|
165
165
|
# access control by exact string equality ... Governance ...
|
|
166
|
-
# connected to none of it"). Checked here, at `verify!` —
|
|
167
|
-
# and
|
|
166
|
+
# connected to none of it"). Checked here, at `verify!` — recovered
|
|
167
|
+
# and moved, not new: this used to run per-block, at hecksagon
|
|
168
168
|
# build time (Bluebook::DSL::HecksagonBuilder#build), which broke
|
|
169
169
|
# the moment a domain could be split across multiple hecksagon
|
|
170
170
|
# blocks (base + an `environments/<name>.hecksagon` overlay,
|
|
@@ -172,20 +172,21 @@ module Hecks
|
|
|
172
172
|
# the recovery provenance): every block but the one declaring
|
|
173
173
|
# `uses_framework "Governance"` would be refused there, even
|
|
174
174
|
# though `Registry#add_hecksagon` merges every block for a domain
|
|
175
|
-
# into
|
|
176
|
-
# Checking the
|
|
175
|
+
# into one Hecksagon before anything ever dispatches against it.
|
|
176
|
+
# Checking the merged result once, here, after every file for
|
|
177
177
|
# this domain has loaded, is both more permissive (no need to
|
|
178
178
|
# repeat `uses_framework` in every file) and strictly more
|
|
179
179
|
# correct (a check against an incomplete, not-yet-merged
|
|
180
180
|
# hecksagon can never see the real final shape).
|
|
181
181
|
#
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
#
|
|
182
|
+
# A provider is RECOGNISED by its declaration, not its name —
|
|
183
|
+
# `authorization_provider_for` answers for the domain's own
|
|
184
|
+
# chapter too, so Governance (which declares `provides
|
|
185
|
+
# "authorization"`) passes here because of what it declares, and
|
|
186
|
+
# the same rule `CommandRules::Authorization#governance_attached?`
|
|
187
|
+
# applies at dispatch time.
|
|
186
188
|
def refuse_ungoverned_roles!(hexagon)
|
|
187
|
-
return if hexagon.domain
|
|
188
|
-
return if hexagon.framework_members.include?("Governance")
|
|
189
|
+
return if authorization_provider_for(hexagon.domain)
|
|
189
190
|
|
|
190
191
|
bluebook_ir = bluebook(hexagon.domain)
|
|
191
192
|
return unless bluebook_ir
|
|
@@ -195,12 +196,21 @@ module Hecks
|
|
|
195
196
|
|
|
196
197
|
raise WiringError,
|
|
197
198
|
"#{offender.hecks_fqn} declares role #{offender.role.inspect}, but " \
|
|
198
|
-
"#{hexagon.domain}'s hecksagon never
|
|
199
|
-
"real access control once
|
|
200
|
-
"it is silent decoration, the exact defect this refusal exists to catch"
|
|
199
|
+
"#{hexagon.domain}'s hecksagon never #{authorization_attachment_hint} — role is only " \
|
|
200
|
+
"real access control once an authorization provider is attached to check it against; " \
|
|
201
|
+
"without that it is silent decoration, the exact defect this refusal exists to catch"
|
|
201
202
|
end
|
|
202
203
|
|
|
203
|
-
#
|
|
204
|
+
# The suggestion, derived from whichever framework members actually
|
|
205
|
+
# declare `provides "authorization"` — never a hardcoded name.
|
|
206
|
+
def authorization_attachment_hint
|
|
207
|
+
providers = Framework.providers_of(Bluebook::Capabilities::AUTHORIZATION)
|
|
208
|
+
return "attaches a chapter that provides \"authorization\" (no framework member declares one)" if providers.empty?
|
|
209
|
+
|
|
210
|
+
providers.map { |name| "uses_framework #{name.inspect}" }.join(" or ")
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Every command this domain declares, an aggregate's own and every
|
|
204
214
|
# entity nested inside one — the same reach `refuse_role_mismatch`
|
|
205
215
|
# itself needs at dispatch time, just walked ahead of time here.
|
|
206
216
|
def commands_in(bluebook_ir)
|
|
@@ -220,7 +230,7 @@ module Hecks
|
|
|
220
230
|
# 0025 named for an unchecked `role`, here applied to saga
|
|
221
231
|
# durability instead.
|
|
222
232
|
#
|
|
223
|
-
# A
|
|
233
|
+
# A warning, not a refusal — unlike `refuse_ungoverned_roles!`,
|
|
224
234
|
# running sagas on a store with no `save_saga` is legitimate on
|
|
225
235
|
# purpose in a fast in-memory test/dev boot (this project's own
|
|
226
236
|
# `saga_durability_spec.rb` boots a process manager on `Memory`
|
|
@@ -228,13 +238,13 @@ module Hecks
|
|
|
228
238
|
# refusing the boot outright would break a choice an author made
|
|
229
239
|
# deliberately. What a deploy needs is for the gap to be loud and
|
|
230
240
|
# undeniable, not for local dev/test to become impossible.
|
|
231
|
-
#
|
|
241
|
+
# The outbox's twin of `warn_undurable_sagas!` — a domain that
|
|
232
242
|
# declares anything a commit could owe a reaction to (a policy
|
|
233
243
|
# listening to one of its events, or a process manager) but is
|
|
234
244
|
# bound to an adapter with no outbox (`AppendOnly#outbox?`) gets
|
|
235
245
|
# reactions the pre-outbox way: run inline, lost on a crash
|
|
236
246
|
# between commit and reaction. A warning, not a refusal, for the
|
|
237
|
-
# reason `warn_undurable_sagas!` gives — and Memory
|
|
247
|
+
# reason `warn_undurable_sagas!` gives — and Memory has an outbox
|
|
238
248
|
# (in-process, like everything else it holds), so a dev/test
|
|
239
249
|
# boot stays quiet; this speaks up for the file/remote adapters
|
|
240
250
|
# that persist state durably but hand reactions to nothing.
|
|
@@ -29,8 +29,8 @@ module Hecks
|
|
|
29
29
|
@event_log = []
|
|
30
30
|
@reaction_log = []
|
|
31
31
|
@saga_log = []
|
|
32
|
-
#
|
|
33
|
-
# rust/src/kernel/orchestrate.rs ports
|
|
32
|
+
# **Additive, Ruby-only** — never merged into saga_log/reaction_log.
|
|
33
|
+
# rust/src/kernel/orchestrate.rs ports those two arrays' exact
|
|
34
34
|
# shape byte-for-byte (spec/rust_conformance_spec.rb's own
|
|
35
35
|
# equality check) — a landmine found by reading that spec before
|
|
36
36
|
# touching anything, not by hitting it. These carry the raw
|
|
@@ -43,7 +43,7 @@ module Hecks
|
|
|
43
43
|
@saga_dispatch_log = []
|
|
44
44
|
@policy_dispatch_log = []
|
|
45
45
|
@saga_instances = Hash.new { |h, k| h[k] = {} }
|
|
46
|
-
#
|
|
46
|
+
# Guards `saga_instances`' own mutation+checkpoint sequence
|
|
47
47
|
# (`SagaInterpreter`'s 4 write points, §7) — the same shape of
|
|
48
48
|
# hazard this codebase's own prior audit already flagged for
|
|
49
49
|
# `Dispatcher#reenter`'s reaction-depth counter (M20: a
|
|
@@ -51,7 +51,7 @@ module Hecks
|
|
|
51
51
|
# `Thread.current`, dispatcher.rb), made meaningfully easier to
|
|
52
52
|
# hit here once a persistence write sits in the same critical
|
|
53
53
|
# section. Held across the in-memory
|
|
54
|
-
# mutation
|
|
54
|
+
# mutation and the checkpoint write together, never across a
|
|
55
55
|
# saga's own dispatch cascade — see `SagaInterpreter#advance_saga`'s
|
|
56
56
|
# own comment for why that distinction matters (non-reentrant
|
|
57
57
|
# Mutex, recursive re-entry is real).
|
|
@@ -59,18 +59,18 @@ module Hecks
|
|
|
59
59
|
@repositories = {}
|
|
60
60
|
@projection_repositories = {}
|
|
61
61
|
@bluebook_builders = {}
|
|
62
|
-
#
|
|
62
|
+
# **Eager, not lazy** — see `#resolved_eras`'s own comment for why. Built
|
|
63
63
|
# here rather than `@resolved_eras ||= {}` on first access so there is
|
|
64
64
|
# no window, post-boot, where two concurrently dispatching threads
|
|
65
65
|
# could race creating this Hash (Hecks/ThreadSharedIvarMutation; the
|
|
66
66
|
# same shape of hazard `Dispatcher#reenter`'s `@reaction_depth` was
|
|
67
|
-
# fixed for). Every
|
|
67
|
+
# fixed for). Every write into it still only ever happens at boot,
|
|
68
68
|
# single-threaded (`EraResolver.check!`, a `:pre_verify` boot gate) —
|
|
69
69
|
# this only removes the race on standing up the container itself for
|
|
70
70
|
# a boot with no era-plugin domain at all, whose first touch would
|
|
71
71
|
# otherwise be a live dispatch's own `RepositoryFactory.build` read.
|
|
72
72
|
@resolved_eras = {}
|
|
73
|
-
#
|
|
73
|
+
# The sibling fact `EraResolver.check!` records for an old checkout:
|
|
74
74
|
# domain name -> the newest held ordinal that superseded the era this
|
|
75
75
|
# boot resolved to; absent for every domain booting the current era.
|
|
76
76
|
# `RepositoryFactory.build` hands it to the adapter as
|
|
@@ -79,7 +79,7 @@ module Hecks
|
|
|
79
79
|
# half that holds even for a connection row-level security cannot
|
|
80
80
|
# bite (BUG#24). Eager for exactly the reason `@resolved_eras` is.
|
|
81
81
|
@superseded_eras = {}
|
|
82
|
-
#
|
|
82
|
+
# **Eager, not lazy** — see `#capability_graph`'s own comment for why.
|
|
83
83
|
# `CapabilityGraph.new` only stores the registry reference; there is
|
|
84
84
|
# no reason to defer it, and doing so removes the exact same
|
|
85
85
|
# first-access race `#resolved_eras` above does, while preserving the
|
|
@@ -87,10 +87,10 @@ module Hecks
|
|
|
87
87
|
# spec.rb` already requires.
|
|
88
88
|
@capability_graph = CapabilityGraph.new(self)
|
|
89
89
|
# `@saga_persistence` itself is eager (see `#saga_persistence`'s own
|
|
90
|
-
# comment) — only the
|
|
91
|
-
# expensive and lazy, guarded by this dedicated mutex.
|
|
90
|
+
# comment) — only the per-domain resolution inside it is genuinely
|
|
91
|
+
# expensive and lazy, guarded by this dedicated mutex. Not the same
|
|
92
92
|
# mutex as `@saga_mutex`: `checkpoint` (saga_interpreter.rb) calls
|
|
93
|
-
# `saga_persistence(domain)` from
|
|
93
|
+
# `saga_persistence(domain)` from inside an `@saga_mutex.synchronize`
|
|
94
94
|
# block, so reusing `@saga_mutex` here would deadlock the very first
|
|
95
95
|
# time a saga advanced (a `Mutex` is not reentrant — the exact
|
|
96
96
|
# warning `@saga_mutex`'s own comment already gives for a different
|
|
@@ -100,23 +100,23 @@ module Hecks
|
|
|
100
100
|
@outbox = Outbox::Relay.new(self)
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
#
|
|
103
|
+
# **The outbox relay** — one per registry, for its whole life (built
|
|
104
104
|
# here, never swapped, so no thread ever sees a different one).
|
|
105
105
|
# It can enqueue from the moment the registry exists; a Dispatcher
|
|
106
106
|
# attaches the interpreters that let it deliver. See
|
|
107
107
|
# `Runtime::Outbox`.
|
|
108
108
|
attr_reader :outbox
|
|
109
109
|
|
|
110
|
-
#
|
|
110
|
+
# The builder stays open for the life of this registry, keyed by chapter
|
|
111
111
|
# name — see the comment on `BluebookBuilder.build`. A chapter split across
|
|
112
112
|
# several files (`language/bluebook/*.bluebook`, all `Hecks.bluebook "Bluebook"`)
|
|
113
|
-
# needs its declarations to accumulate into
|
|
113
|
+
# needs its declarations to accumulate into one builder rather than each
|
|
114
114
|
# file minting its own and silently discarding the one before.
|
|
115
115
|
def bluebook_builder(name)
|
|
116
116
|
@bluebook_builders[name.to_s] ||= yield
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
-
#
|
|
119
|
+
# Boot-time-only, single-threaded — every `add_*` below (through
|
|
120
120
|
# `add_translation`) is called exclusively from `Hecks.collect`
|
|
121
121
|
# (hecks.rb), which is what `Hecks.bluebook`/`.hecksagon`/`.port`/
|
|
122
122
|
# `.adapter`/`.world`/`.translation` run inside while a `.bluebook`/
|
|
@@ -132,11 +132,11 @@ module Hecks
|
|
|
132
132
|
# rubocop:disable Hecks/ThreadSharedIvarMutation
|
|
133
133
|
def add_bluebook(item) = @bluebooks[item.name] = item
|
|
134
134
|
|
|
135
|
-
#
|
|
135
|
+
# Merged, not replaced — recovered, not new (see Runtime::Loader
|
|
136
136
|
# .boot's own comment for the provenance). A domain's hecksagon can
|
|
137
137
|
# now load in more than one block for the same domain (base file
|
|
138
138
|
# plus an `environments/<name>.hecksagon` overlay), and the second
|
|
139
|
-
# block should
|
|
139
|
+
# block should add to what the first declared, not silently
|
|
140
140
|
# discard it.
|
|
141
141
|
def add_hecksagon(item)
|
|
142
142
|
existing = @hecksagons[item.domain]
|
|
@@ -146,7 +146,7 @@ module Hecks
|
|
|
146
146
|
def add_port(item) = @ports[item.name] = item
|
|
147
147
|
def add_adapter(item) = @adapters[item.name] = item
|
|
148
148
|
|
|
149
|
-
#
|
|
149
|
+
# Merged, not replaced — the same generalization for `World` that
|
|
150
150
|
# `add_hecksagon` above recovers for `Hecksagon`: an
|
|
151
151
|
# `environments/<name>.world` overlay (or a host-owned tenancy
|
|
152
152
|
# overlay world, same mechanism) can now add or override settings
|
|
@@ -165,7 +165,7 @@ module Hecks
|
|
|
165
165
|
# rubocop:enable Hecks/ThreadSharedIvarMutation
|
|
166
166
|
|
|
167
167
|
# {domain name => era ordinal} as resolved by the boot-time era
|
|
168
|
-
# gate. A lineage adapter writes into
|
|
168
|
+
# gate. A lineage adapter writes into its own era's partition —
|
|
169
169
|
# which, for an old checkout booting a held-but-superseded shape,
|
|
170
170
|
# is not the newest one. The Hash itself is stood up in `initialize`
|
|
171
171
|
# (see that comment) — this is a plain reader, not a memoizer;
|
|
@@ -182,12 +182,29 @@ module Hecks
|
|
|
182
182
|
|
|
183
183
|
def verbs = @bluebooks.values.flat_map(&:verbs).sort
|
|
184
184
|
|
|
185
|
+
# The chapter that answers a role check for `domain` — the domain's
|
|
186
|
+
# own chapter, or any framework member its hecksagon attaches, that
|
|
187
|
+
# declares `provides "authorization"`. Nil when none does. Replaces
|
|
188
|
+
# every check for the literal name "Governance": Governance is
|
|
189
|
+
# recognised by what it declares, and a chapter that declares the
|
|
190
|
+
# same thing is recognised the same way.
|
|
191
|
+
def authorization_provider_for(domain)
|
|
192
|
+
names = [domain.to_s, *Array(hecksagon(domain)&.framework_members)]
|
|
193
|
+
names.filter_map { |name| bluebook(name) }
|
|
194
|
+
.find { |chapter| chapter.provides?(Bluebook::Capabilities::AUTHORIZATION) }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Every loaded chapter declaring `provides "authorization"`.
|
|
198
|
+
def authorization_providers
|
|
199
|
+
@bluebooks.values.select { |chapter| chapter.provides?(Bluebook::Capabilities::AUTHORIZATION) }
|
|
200
|
+
end
|
|
201
|
+
|
|
185
202
|
def repository(domain, aggregate)
|
|
186
203
|
@repositories[[domain.to_s, aggregate.hecks_name]] ||= Ports::Persistence.repository(self, domain, aggregate)
|
|
187
204
|
end
|
|
188
205
|
|
|
189
|
-
#
|
|
190
|
-
#
|
|
206
|
+
# Everything a dispatch wrote, cleared; nothing a boot declared,
|
|
207
|
+
# touched. Bluebooks, hecksagons, ports, adapters, worlds and the
|
|
191
208
|
# resolved eras are what loading the files produced and stay as
|
|
192
209
|
# they are; the logs, the saga instances and the repositories are
|
|
193
210
|
# what running commands against them produced, and go back to
|
|
@@ -204,8 +221,8 @@ module Hecks
|
|
|
204
221
|
# boot, 76 chess behaviours = two and a half minutes of booting the
|
|
205
222
|
# same two files — can now boot once and reset between tests.
|
|
206
223
|
#
|
|
207
|
-
#
|
|
208
|
-
#
|
|
224
|
+
# Single-threaded caller, the same reason the `add_*` cluster above
|
|
225
|
+
# is exempt — `Behaviors::Expectations.run_one` is this method's only
|
|
209
226
|
# caller (verified by grep before writing this), and it runs one
|
|
210
227
|
# test at a time: `Runner#run` maps over tests sequentially, and
|
|
211
228
|
# `Behaviors.rspec`'s generated examples run under RSpec's own
|
|
@@ -229,7 +246,7 @@ module Hecks
|
|
|
229
246
|
|
|
230
247
|
# Built eagerly in `initialize` (see that comment) — this is a plain
|
|
231
248
|
# reader, not a memoizer; `spec/runtime/capability_graph_spec.rb`
|
|
232
|
-
# asserts the
|
|
249
|
+
# asserts the same instance comes back every call, which this still
|
|
233
250
|
# gives, just without a lazy `||=` race on standing it up.
|
|
234
251
|
attr_reader :capability_graph
|
|
235
252
|
|
|
@@ -262,9 +279,9 @@ module Hecks
|
|
|
262
279
|
false
|
|
263
280
|
end
|
|
264
281
|
|
|
265
|
-
#
|
|
282
|
+
# Recovered — see `add_hecksagon`'s own comment for provenance.
|
|
266
283
|
# Concatenates every list-shaped fact; `binds` in particular is
|
|
267
|
-
# additive because an overlay
|
|
284
|
+
# additive because an overlay rebinding an aggregate (a new
|
|
268
285
|
# `persisted_by` for the same aggregate/verb) is meant to shadow
|
|
269
286
|
# the base's own bind at resolution time, not erase it outright —
|
|
270
287
|
# `Ports::Persistence::BindingPolicy.resolve`'s own "exactly one
|
|
@@ -281,11 +298,11 @@ module Hecks
|
|
|
281
298
|
)
|
|
282
299
|
end
|
|
283
300
|
|
|
284
|
-
#
|
|
301
|
+
# Recovered and generalized — see `add_world`'s own comment. `realm`/
|
|
285
302
|
# `latest` are scalars, so the overlay's value wins when present,
|
|
286
303
|
# else the base's survives; `settings` is a shallow merge keyed by
|
|
287
304
|
# verb (and `"verb:adapter"`) — an overlay entry for a key the base
|
|
288
|
-
# also declares
|
|
305
|
+
# also declares replaces that key's whole resolved hash (the same
|
|
289
306
|
# all-or-nothing shape `WorldBuilder#method_missing` already builds
|
|
290
307
|
# each entry as), it does not deep-merge field by field within it.
|
|
291
308
|
def merge_worlds(base, overlay)
|
|
@@ -9,19 +9,19 @@ require_relative "../ports/persistence/remote_runtime"
|
|
|
9
9
|
|
|
10
10
|
module Hecks
|
|
11
11
|
module Runtime
|
|
12
|
-
#
|
|
13
|
-
# constructs this
|
|
12
|
+
# **The write-side half of lambda routing** — `Runtime::Loader.boot`
|
|
13
|
+
# constructs this instead of `Dispatcher` when the booted domain's
|
|
14
14
|
# own `.world` declares Lambda routing (see loader.rb's own
|
|
15
15
|
# `dispatcher_class_for`). Matches `Dispatcher`'s public shape
|
|
16
|
-
# closely enough that everything built
|
|
16
|
+
# closely enough that everything built on top of a dispatcher —
|
|
17
17
|
# `Handle`, `AggregateDoor`, `Facade::Surface` — needs no changes
|
|
18
18
|
# at all: `Handle#run`'s entire contract is
|
|
19
19
|
# `@dispatcher.dispatch("#{fqn}.#{command}", **identity, **args).instance.state`,
|
|
20
20
|
# and both classes answer that identically.
|
|
21
21
|
#
|
|
22
|
-
#
|
|
23
|
-
# to a
|
|
24
|
-
# registry's own repositories are
|
|
22
|
+
# **Reads delegate, writes don't**. `query`/`reference_query` hand off
|
|
23
|
+
# to a real `Dispatcher` built over the same registry — since that
|
|
24
|
+
# registry's own repositories are already Lambda-backed
|
|
25
25
|
# (`Adapters::Lambda`, `persisted_by("Lambda")`, Phase 2's other
|
|
26
26
|
# half), the inherited query machinery (`QueryInterpreter`,
|
|
27
27
|
# `Ports::Query::InMemory.execute`) works completely unchanged, no
|
|
@@ -37,43 +37,58 @@ module Hecks
|
|
|
37
37
|
|
|
38
38
|
attr_reader :registry
|
|
39
39
|
|
|
40
|
-
def initialize(registry, region: "us-east-1")
|
|
40
|
+
def initialize(registry, region: "us-east-1", function: nil)
|
|
41
41
|
@registry = registry
|
|
42
42
|
# `File.basename(registry.root)`, not `bluebooks.keys.first` —
|
|
43
43
|
# matches `Adapters::Lambda`'s own function-name resolution
|
|
44
44
|
# exactly (see its own comment on why: one merged Lambda per
|
|
45
45
|
# deploy, not one per attached chapter, and `root` is the one
|
|
46
46
|
# signal every bluebook in this registry shares regardless of
|
|
47
|
-
# which one attached it) —
|
|
47
|
+
# which one attached it) — including that same adapter's own
|
|
48
48
|
# `ENV["DOMAIN_NAME"]`-first fix: `root` is always `/var/task`
|
|
49
49
|
# inside a deployed Lambda, giving "task" instead of the real
|
|
50
50
|
# domain name (a real, live AccessDeniedException on
|
|
51
51
|
# "hecks-task" caught this).
|
|
52
|
-
|
|
53
|
-
#
|
|
52
|
+
# `function:` — the `.world`'s own `dispatched_by("Lambda")`
|
|
53
|
+
# naming of which function this is, for a deployment whose stack
|
|
54
|
+
# name isn't `hecks-<domain>` (Client's own comment has the real
|
|
55
|
+
# case). Absent, the resolution above is unchanged.
|
|
56
|
+
@client = Adapters::Lambda::Client.new(domain: ENV["DOMAIN_NAME"] || File.basename(registry.root),
|
|
57
|
+
region: region, function: function)
|
|
58
|
+
# Read-side delegate only (see class comment) — never dispatched
|
|
54
59
|
# through; a real Dispatcher's own `query`/`reference_query`
|
|
55
60
|
# already resolve generically via `registry.repository(...)`,
|
|
56
61
|
# so building one here reuses that instead of duplicating it.
|
|
57
62
|
@local = Dispatcher.new(registry)
|
|
58
63
|
end
|
|
59
64
|
|
|
65
|
+
# Same deprecation as `Dispatcher#dispatch` — loose keyword facts warn;
|
|
66
|
+
# `to:`/`with:` do not.
|
|
60
67
|
def dispatch(verb, saga_correlation: nil, **args)
|
|
68
|
+
Dispatcher.deprecate_loose_facts(args.except(:to, :with))
|
|
69
|
+
dispatch_flat(verb, args.merge(saga_correlation: saga_correlation))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Same flat-facts wire form as `Dispatcher#dispatch_flat`.
|
|
73
|
+
def dispatch_flat(verb, args = {})
|
|
74
|
+
args = args.dup
|
|
75
|
+
saga_correlation = args.delete(:saga_correlation)
|
|
61
76
|
domain, aggregate_name, = Naming.split_verb(verb) ||
|
|
62
77
|
raise(UnknownVerb,
|
|
63
|
-
RefusalWording.
|
|
78
|
+
RefusalWording.render_site("UnknownVerb", "not_fully_qualified", verb: verb))
|
|
64
79
|
aggregate = @registry.bluebook(domain)&.aggregate(aggregate_name) ||
|
|
65
80
|
raise(UnknownVerb,
|
|
66
|
-
RefusalWording.
|
|
81
|
+
RefusalWording.render_site("UnknownVerb", "no_aggregate", domain: domain, aggregate: aggregate_name))
|
|
67
82
|
|
|
68
|
-
#
|
|
69
|
-
#
|
|
83
|
+
# Not every aggregate in a lambda-routed domain is itself
|
|
84
|
+
# lambda-bound — Member's real name->email rekey carries a
|
|
70
85
|
# `compute` rule (era_check.rb's own `check_compute_rules!`),
|
|
71
86
|
# which can only ever run against Postgres, permanently. Its
|
|
72
|
-
#
|
|
87
|
+
# own `.hecksagon` bind stays "Postgres" even when
|
|
73
88
|
# `dispatched_by("Lambda")` is on for everything else — checked
|
|
74
|
-
# here by real
|
|
89
|
+
# here by real capability (`Ports::Persistence::RemoteRuntime`,
|
|
75
90
|
# §1), not by comparing the adapter's own name to the string
|
|
76
|
-
# "Lambda" — a bind resolves to whatever adapter
|
|
91
|
+
# "Lambda" — a bind resolves to whatever adapter class actually
|
|
77
92
|
# backs it, and only a class shaped like "the real interpreter
|
|
78
93
|
# lives behind a call boundary" forwards here; anything else
|
|
79
94
|
# (Postgres, Memory, any future local adapter) falls through to
|
|
@@ -82,7 +97,7 @@ module Hecks
|
|
|
82
97
|
# all.
|
|
83
98
|
adapter_name = Ports::Persistence::BindingPolicy.resolve(@registry, domain, aggregate).adapter
|
|
84
99
|
unless @registry.adapter_class(adapter_name) <= Ports::Persistence::RemoteRuntime
|
|
85
|
-
return @local.
|
|
100
|
+
return @local.dispatch_flat(verb, args.merge(saga_correlation: saga_correlation))
|
|
86
101
|
end
|
|
87
102
|
|
|
88
103
|
response = @client.dispatch(verb, args)
|
|
@@ -90,14 +105,14 @@ module Hecks
|
|
|
90
105
|
refusal = response.fetch("refusals", []).find { |r| r["verb"] == verb }
|
|
91
106
|
raise RemoteRefusal, "#{verb} refused: #{refusal['error']}" if refusal
|
|
92
107
|
|
|
93
|
-
#
|
|
108
|
+
# This step's own mutations — `mutations` is one entry per
|
|
94
109
|
# replayed step (rust/host's rehydrate-and-replay design,
|
|
95
110
|
# Phase 1), so `.last` is exactly the step just dispatched.
|
|
96
111
|
# Matched by fully-qualified aggregate name, not just "the
|
|
97
|
-
# first mutation" — a command whose reaction
|
|
112
|
+
# first mutation" — a command whose reaction also mutates a
|
|
98
113
|
# different aggregate (a policy, a saga leg) puts more than
|
|
99
114
|
# one mutation in the same step, and the direct effect of
|
|
100
|
-
#
|
|
115
|
+
# this verb is the one this dispatch's own caller expects
|
|
101
116
|
# `.instance` to be.
|
|
102
117
|
fqn = "#{domain}::#{aggregate.hecks_name}"
|
|
103
118
|
mutation = response.fetch("mutations", []).last&.find { |m| m["aggregate"] == fqn } ||
|
|
@@ -113,9 +128,9 @@ module Hecks
|
|
|
113
128
|
def query(verb, **args) = @local.query(verb, **args)
|
|
114
129
|
def reference_query(verb, **args) = @local.reference_query(verb, **args)
|
|
115
130
|
|
|
116
|
-
#
|
|
131
|
+
# The full domain's event history, on every call — `{"read":
|
|
117
132
|
# true}` replays the whole journal (Phase 1's `dispatch::read`),
|
|
118
|
-
# so its own `events` array already
|
|
133
|
+
# so its own `events` array already is the complete log, the
|
|
119
134
|
# same thing `@registry.event_log` would answer for a local
|
|
120
135
|
# dispatch. Not cached: `AggregateDoor.events`/`Handle#events`
|
|
121
136
|
# are not called in this codebase's own hot paths today: if that
|