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
|
@@ -27,17 +27,31 @@ module Hecks
|
|
|
27
27
|
|
|
28
28
|
attr_reader :aggregate, :path
|
|
29
29
|
|
|
30
|
+
# Names the optional persistence capabilities `Ports::Persistence::AppendOnly` may rely on.
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<Symbol>] `[:atomic_put]`
|
|
30
33
|
def persistence_capabilities = [:atomic_put]
|
|
31
34
|
|
|
35
|
+
# Opens (creating if absent) the database file and its aggregate, journal, event, saga
|
|
36
|
+
# and outbox tables.
|
|
37
|
+
#
|
|
38
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate whose table this adapter owns
|
|
39
|
+
# @param settings [Hash{Symbol, String => Object}] world settings for the binding:
|
|
40
|
+
# `database` (file path, default `data/<table>.db`) and `domain` (scopes saga rows,
|
|
41
|
+
# default the aggregate's name), each read under a Symbol or a String key
|
|
42
|
+
# @param root [String, nil] directory a relative `database` path resolves against; nil
|
|
43
|
+
# means the process working directory
|
|
44
|
+
# @raise [LoadError] if the `sqlite3` gem is not installed
|
|
45
|
+
# @raise [SQLite3::Exception] if the file cannot be opened or a table cannot be created
|
|
32
46
|
def initialize(aggregate:, settings: {}, root: nil)
|
|
33
|
-
#
|
|
47
|
+
# **Lazy, on purpose** — a domain that never wires Sqlite should never
|
|
34
48
|
# need the gem installed. `require "hecks"` alone must not
|
|
35
49
|
# force a database client library nobody asked for.
|
|
36
50
|
require "sqlite3"
|
|
37
51
|
|
|
38
52
|
@aggregate = aggregate
|
|
39
53
|
@path = resolve_path(settings, root)
|
|
40
|
-
#
|
|
54
|
+
# The optional saga-persistence capability's own scoping column
|
|
41
55
|
# (§2/§4) — falls back to the aggregate's own name for a
|
|
42
56
|
# directly-instantiated adapter (specs), same fallback shape
|
|
43
57
|
# Postgres's own @domain already uses.
|
|
@@ -67,18 +81,33 @@ module Hecks
|
|
|
67
81
|
create_outbox_table!
|
|
68
82
|
end
|
|
69
83
|
|
|
70
|
-
#
|
|
84
|
+
# Runs the block inside one SQLite transaction, joining an already-open one.
|
|
85
|
+
#
|
|
86
|
+
# **Re-entrant on purpose** — `atomic_put` opens its own transaction
|
|
71
87
|
# and `Interpreting#run_dispatch_order` opens one around the whole
|
|
72
88
|
# save+emit pair; SQLite3 refuses a BEGIN inside a BEGIN, so the
|
|
73
89
|
# inner call joins the outer one instead. Same shape Postgres uses.
|
|
90
|
+
#
|
|
91
|
+
# @yield the writes to commit together; an exception raised inside rolls the
|
|
92
|
+
# outermost transaction back
|
|
93
|
+
# @return [Object] the block's own result
|
|
94
|
+
# @raise [SQLite3::Exception] if `BEGIN`, a statement inside the block, or `COMMIT` fails
|
|
74
95
|
def transaction(&)
|
|
75
96
|
return yield if @db.transaction_active?
|
|
76
97
|
|
|
77
98
|
@db.transaction(&)
|
|
78
99
|
end
|
|
79
100
|
|
|
101
|
+
# Names the aggregate's table; the journal table and outbox rows are keyed off it.
|
|
102
|
+
#
|
|
103
|
+
# @return [String] the aggregate's snake_case storage name, unquoted
|
|
80
104
|
def table = @aggregate.storage_name
|
|
81
105
|
|
|
106
|
+
# Reads the current row for one aggregate identity.
|
|
107
|
+
#
|
|
108
|
+
# @param id [String, Object] the aggregate identity, bound as `id.to_s`
|
|
109
|
+
# @return [Runtime::Instance, nil] the decoded record, or nil when no row has that id
|
|
110
|
+
# @raise [SQLite3::Exception] if the statement fails
|
|
82
111
|
def find(id)
|
|
83
112
|
row = @db.get_first_row("SELECT * FROM #{quoted_table} WHERE id = ?", [id.to_s])
|
|
84
113
|
return nil unless row
|
|
@@ -86,9 +115,19 @@ module Hecks
|
|
|
86
115
|
Runtime::Instance.new(aggregate: @aggregate, id: row["id"], state: decode(row))
|
|
87
116
|
end
|
|
88
117
|
|
|
89
|
-
#
|
|
118
|
+
# Lists every stored record, ordered by id unless an ordering attribute is given.
|
|
119
|
+
#
|
|
120
|
+
# order_by is a runtime value — see postgres.rb's own all for the
|
|
90
121
|
# full reasoning; whitelisted the identical way before it ever
|
|
91
122
|
# reaches order_expression.
|
|
123
|
+
#
|
|
124
|
+
# @param order_by [String, Symbol, nil] attribute (or dotted value-object path) to sort
|
|
125
|
+
# by; nil orders by id alone
|
|
126
|
+
# @param direction [Symbol, String] `:asc` or `:desc`, case-insensitive; anything else
|
|
127
|
+
# sorts ascending
|
|
128
|
+
# @return [Array<Runtime::Instance>] the decoded records, `[]` when the table is empty
|
|
129
|
+
# @raise [Runtime::WiringError] if `order_by` names no attribute of the aggregate
|
|
130
|
+
# @raise [SQLite3::Exception] if the statement fails
|
|
92
131
|
def all(order_by: nil, direction: :asc)
|
|
93
132
|
order_sql = "ORDER BY id"
|
|
94
133
|
if order_by
|
|
@@ -107,21 +146,38 @@ module Hecks
|
|
|
107
146
|
end
|
|
108
147
|
end
|
|
109
148
|
|
|
149
|
+
# Counts the rows in the aggregate's table, deleted records excluded.
|
|
150
|
+
#
|
|
151
|
+
# @return [Integer] number of current records
|
|
152
|
+
# @raise [SQLite3::Exception] if the statement fails
|
|
110
153
|
def count = @db.get_first_value("SELECT COUNT(*) FROM #{quoted_table}").to_i
|
|
111
154
|
|
|
155
|
+
# Inserts one journal row, outside any transaction of its own.
|
|
156
|
+
#
|
|
157
|
+
# @param entry [Ports::Persistence::Entry] the save or delete to journal; `state` is
|
|
158
|
+
# encoded through the state codec and `mirrors` stored as JSON, or NULL when nil
|
|
159
|
+
# @return [Ports::Persistence::Entry] the same `entry`
|
|
160
|
+
# @raise [SQLite3::Exception] if the insert fails
|
|
112
161
|
def append(entry)
|
|
113
162
|
@db.execute(
|
|
114
163
|
"INSERT INTO #{quoted_entry_table} (aggregate_id, operation, state, mirrors) VALUES (?, ?, ?, ?)",
|
|
115
|
-
# `mirrors` (unlike `state`) is a
|
|
164
|
+
# `mirrors` (unlike `state`) is a nullable column — an absent
|
|
116
165
|
# mirrors hash must bind a real SQL NULL, not the four-character
|
|
117
166
|
# JSON text `"null"` (`JSON.generate(nil)`), or a future `IS NULL`
|
|
118
167
|
# check against it would never match. Same guard `postgres_era.rb`
|
|
119
168
|
# already uses for its own journal's `mirrors` column.
|
|
120
|
-
[entry.id, entry.operation, JSON.generate(
|
|
169
|
+
[entry.id, entry.operation, JSON.generate(Ports::Persistence::StateCodec.encode(@aggregate, entry.state)),
|
|
170
|
+
entry.mirrors && JSON.generate(entry.mirrors)]
|
|
121
171
|
)
|
|
122
172
|
entry
|
|
123
173
|
end
|
|
124
174
|
|
|
175
|
+
# Replaces or deletes the aggregate's row for one journal entry.
|
|
176
|
+
#
|
|
177
|
+
# @param entry [Ports::Persistence::Entry] the save or delete to materialize
|
|
178
|
+
# @return [Runtime::Instance, Array] for a save, a new instance over the entry's state;
|
|
179
|
+
# for a delete, the `DELETE` statement's empty result rows
|
|
180
|
+
# @raise [SQLite3::Exception] if the statement fails
|
|
125
181
|
def project(entry)
|
|
126
182
|
return @db.execute("DELETE FROM #{quoted_table} WHERE id = ?", [entry.id]) if entry.delete?
|
|
127
183
|
|
|
@@ -137,24 +193,41 @@ module Hecks
|
|
|
137
193
|
instance
|
|
138
194
|
end
|
|
139
195
|
|
|
196
|
+
# Reads the whole journal back in append order, for `AppendOnly#recover!` to replay.
|
|
197
|
+
#
|
|
198
|
+
# @return [Array<Ports::Persistence::Entry>] every journalled entry, state decoded
|
|
199
|
+
# through the state codec and `mirrors` parsed with String keys (nil when none were
|
|
200
|
+
# stored); a NULL `operation` reads as `"save"`; `[]` when nothing has been appended
|
|
201
|
+
# @raise [SQLite3::Exception] if the statement fails
|
|
202
|
+
# @raise [JSON::ParserError] if a stored `state` or `mirrors` value is not valid JSON
|
|
140
203
|
def entries
|
|
141
204
|
@db.execute("SELECT aggregate_id, operation, state, mirrors FROM #{quoted_entry_table} ORDER BY sequence").map do |row|
|
|
142
205
|
state = JSON.parse(row["state"])
|
|
143
206
|
Ports::Persistence::Entry.new(
|
|
144
207
|
operation: row["operation"] || "save",
|
|
145
208
|
id: row["aggregate_id"],
|
|
146
|
-
state: state
|
|
209
|
+
state: Ports::Persistence::StateCodec.decode(@aggregate, state),
|
|
147
210
|
mirrors: row["mirrors"] && JSON.parse(row["mirrors"])
|
|
148
211
|
)
|
|
149
212
|
end
|
|
150
213
|
end
|
|
151
214
|
|
|
215
|
+
# Deletes every row of the aggregate's table and its journal; events, saga rows and
|
|
216
|
+
# outbox rows are left in place.
|
|
217
|
+
#
|
|
218
|
+
# @return [Adapters::Sqlite] self
|
|
219
|
+
# @raise [SQLite3::Exception] if a statement fails
|
|
152
220
|
def reset!
|
|
153
221
|
@db.execute("DELETE FROM #{quoted_table}")
|
|
154
222
|
@db.execute("DELETE FROM #{quoted_entry_table}")
|
|
155
223
|
self
|
|
156
224
|
end
|
|
157
225
|
|
|
226
|
+
# Journals and replaces an instance's current state in one transaction.
|
|
227
|
+
#
|
|
228
|
+
# @param instance [Runtime::Instance] the instance to store
|
|
229
|
+
# @return [Runtime::Instance] a new instance over a shallow copy of the saved state
|
|
230
|
+
# @raise [SQLite3::Exception] if either statement fails; the transaction is rolled back
|
|
158
231
|
def save(instance)
|
|
159
232
|
entry = Ports::Persistence::Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
|
|
160
233
|
transaction do
|
|
@@ -163,9 +236,17 @@ module Hecks
|
|
|
163
236
|
end
|
|
164
237
|
end
|
|
165
238
|
|
|
239
|
+
# Stores an entry and reports whether it inserted, replaced or conflicted.
|
|
240
|
+
#
|
|
166
241
|
# The outcome lookup, journal append and snapshot replacement share one
|
|
167
242
|
# SQLite transaction. The runtime performs no preliminary find; this
|
|
168
243
|
# adapter-native operation owns both concurrency and outcome reporting.
|
|
244
|
+
#
|
|
245
|
+
# @param entry [Ports::Persistence::Entry] the save to store
|
|
246
|
+
# @param insert_only [Boolean] when true, an existing row is left untouched
|
|
247
|
+
# @return [Symbol] `:inserted`, `:replaced`, or `:conflicted` when `insert_only` met an
|
|
248
|
+
# existing row and nothing was written
|
|
249
|
+
# @raise [SQLite3::Exception] if a statement fails; the transaction is rolled back
|
|
169
250
|
def atomic_put(entry, insert_only: false)
|
|
170
251
|
status = nil
|
|
171
252
|
transaction do
|
|
@@ -181,6 +262,12 @@ module Hecks
|
|
|
181
262
|
status
|
|
182
263
|
end
|
|
183
264
|
|
|
265
|
+
# Journals a delete and removes the row, whether or not a row exists. The two
|
|
266
|
+
# statements share a transaction only when the caller has one open.
|
|
267
|
+
#
|
|
268
|
+
# @param id [String, Object] the aggregate identity, journalled as `id.to_s`
|
|
269
|
+
# @return [Boolean] always true
|
|
270
|
+
# @raise [SQLite3::Exception] if either statement fails
|
|
184
271
|
def delete(id)
|
|
185
272
|
entry = Ports::Persistence::Entry.new(operation: "delete", id: id.to_s, state: nil)
|
|
186
273
|
append(entry)
|
|
@@ -188,6 +275,11 @@ module Hecks
|
|
|
188
275
|
true
|
|
189
276
|
end
|
|
190
277
|
|
|
278
|
+
# Inserts an emitted event into the database's shared `events` table.
|
|
279
|
+
#
|
|
280
|
+
# @param event [Runtime::Event] the emitted event; `payload` is stored as JSON
|
|
281
|
+
# @return [Array] the insert's empty result rows; callers ignore it
|
|
282
|
+
# @raise [SQLite3::Exception] if the insert fails
|
|
191
283
|
def record_event(event)
|
|
192
284
|
@db.execute(
|
|
193
285
|
"INSERT INTO events (name, aggregate, aggregate_id, payload, occurred_at) VALUES (?, ?, ?, ?, ?)",
|
|
@@ -195,6 +287,12 @@ module Hecks
|
|
|
195
287
|
)
|
|
196
288
|
end
|
|
197
289
|
|
|
290
|
+
# Reads back every recorded event in insertion order — the database file's whole
|
|
291
|
+
# `events` table, not only this aggregate's rows.
|
|
292
|
+
#
|
|
293
|
+
# @return [Array<Runtime::Event>] the stored events, `payload` parsed with Symbol keys
|
|
294
|
+
# and `occurred_at` as stored; `[]` when none are recorded
|
|
295
|
+
# @raise [SQLite3::Exception] if the statement fails
|
|
198
296
|
def events
|
|
199
297
|
@db.execute("SELECT * FROM events ORDER BY id").map do |row|
|
|
200
298
|
Runtime::Event.new(
|
|
@@ -207,26 +305,22 @@ module Hecks
|
|
|
207
305
|
end
|
|
208
306
|
end
|
|
209
307
|
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
#
|
|
213
|
-
# SQLite's `resolve_path` defaults to one `.db` file PER
|
|
214
|
-
# AGGREGATE unless a domain shares one `database` setting across
|
|
215
|
-
# its aggregates — since saga persistence resolves through
|
|
216
|
-
# whichever adapter instance backs the domain's FIRST aggregate
|
|
217
|
-
# (`Registry#saga_persistence`), this table ends up living inside
|
|
218
|
-
# THAT one aggregate's own file by default. Correct and durable
|
|
219
|
-
# either way; a domain that wants an obviously-named saga store
|
|
220
|
-
# already gets one by sharing `database` across its aggregates,
|
|
221
|
-
# the recommended, common case.
|
|
222
|
-
# THE OUTBOX — see `Runtime::Outbox`. Rows land in the SAME
|
|
308
|
+
# Inserts new outbox rows as pending, skipping any whose `delivery_id` already exists.
|
|
309
|
+
#
|
|
310
|
+
# The outbox — see `Runtime::Outbox`. Rows land in the same
|
|
223
311
|
# database as this aggregate (the only way the enqueue shares the
|
|
224
312
|
# save's transaction), keyed by the aggregate's storage name so an
|
|
225
313
|
# adapter instance only ever reads back its own rows even when
|
|
226
314
|
# several aggregates share one file. `INSERT OR IGNORE` on the
|
|
227
|
-
#
|
|
315
|
+
# unique delivery_id makes a re-enqueue of the same (event,
|
|
228
316
|
# consumer) a no-op; `outbox_claim`'s `WHERE status = 'pending'`
|
|
229
317
|
# is the compare-and-set that lets exactly one relay win a row.
|
|
318
|
+
#
|
|
319
|
+
# @param rows [Array<Runtime::Outbox::Row>] rows to enqueue; each accepted row has its
|
|
320
|
+
# `id` and `status` assigned in place. `row.aggregate` is stored as given
|
|
321
|
+
# @return [Array<Runtime::Outbox::Row>] the rows actually inserted, `[]` when every one
|
|
322
|
+
# was a duplicate
|
|
323
|
+
# @raise [SQLite3::Exception] if an insert fails
|
|
230
324
|
def outbox_enqueue(rows)
|
|
231
325
|
rows.filter_map do |row|
|
|
232
326
|
@db.execute(
|
|
@@ -243,6 +337,12 @@ module Hecks
|
|
|
243
337
|
end
|
|
244
338
|
end
|
|
245
339
|
|
|
340
|
+
# Claims a pending outbox row with a compare-and-set update, counting the attempt.
|
|
341
|
+
#
|
|
342
|
+
# @param id [Integer] the row id `outbox_enqueue` assigned
|
|
343
|
+
# @return [Boolean] true when the row was pending and is now claimed; false when it is
|
|
344
|
+
# unknown or another claimer got there first
|
|
345
|
+
# @raise [SQLite3::Exception] if the update fails
|
|
246
346
|
def outbox_claim(id)
|
|
247
347
|
@db.execute(
|
|
248
348
|
"UPDATE hecks_outbox SET status = 'claimed', attempts = attempts + 1, claimed_at = ? " \
|
|
@@ -252,6 +352,15 @@ module Hecks
|
|
|
252
352
|
@db.changes == 1
|
|
253
353
|
end
|
|
254
354
|
|
|
355
|
+
# Records a delivery outcome and its settle time on an outbox row, whatever status it
|
|
356
|
+
# held.
|
|
357
|
+
#
|
|
358
|
+
# @param id [Integer] the row id `outbox_enqueue` assigned
|
|
359
|
+
# @param status [String, Symbol] the new status, one of `Runtime::Outbox::STATUSES`;
|
|
360
|
+
# not validated here
|
|
361
|
+
# @param error [String, nil] the failure description, or nil to store NULL
|
|
362
|
+
# @return [Boolean] true when exactly one row was updated; false when no row has `id`
|
|
363
|
+
# @raise [SQLite3::Exception] if the update fails
|
|
255
364
|
def outbox_settle(id, status:, error: nil)
|
|
256
365
|
@db.execute(
|
|
257
366
|
"UPDATE hecks_outbox SET status = ?, error = ?, settled_at = ? WHERE id = ?",
|
|
@@ -260,6 +369,13 @@ module Hecks
|
|
|
260
369
|
@db.changes == 1
|
|
261
370
|
end
|
|
262
371
|
|
|
372
|
+
# Lists the outbox rows whose `aggregate` column equals this adapter's `table`, in
|
|
373
|
+
# enqueue order.
|
|
374
|
+
#
|
|
375
|
+
# @param status [String, Symbol, nil] only rows with this status; nil lists every row
|
|
376
|
+
# @return [Array<Runtime::Outbox::Row>] the matching rows, `event` parsed with Symbol
|
|
377
|
+
# keys; `[]` when none match
|
|
378
|
+
# @raise [SQLite3::Exception] if the statement fails
|
|
263
379
|
def outbox_rows(status: nil)
|
|
264
380
|
sql = "SELECT * FROM hecks_outbox WHERE aggregate = ?"
|
|
265
381
|
binds = [table]
|
|
@@ -270,6 +386,31 @@ module Hecks
|
|
|
270
386
|
@db.execute("#{sql} ORDER BY id", binds).map { |row| outbox_row(row) }
|
|
271
387
|
end
|
|
272
388
|
|
|
389
|
+
# Replaces one saga instance's checkpoint, keyed by domain, process manager and
|
|
390
|
+
# correlation.
|
|
391
|
+
#
|
|
392
|
+
# ── the optional saga-persistence capability (§2) — reuses the
|
|
393
|
+
# DDL every SQLite-backed aggregate table already lives beside
|
|
394
|
+
# (`create_saga_table!`, `Sqlite::SchemaBuilder`, shared with D1).
|
|
395
|
+
# SQLite's `resolve_path` defaults to one `.db` file per
|
|
396
|
+
# aggregate unless a domain shares one `database` setting across
|
|
397
|
+
# its aggregates — since saga persistence resolves through
|
|
398
|
+
# whichever adapter instance backs the domain's first aggregate
|
|
399
|
+
# (`Registry#saga_persistence`), this table ends up living inside
|
|
400
|
+
# that one aggregate's own file by default. Correct and durable
|
|
401
|
+
# either way; a domain that wants an obviously-named saga store
|
|
402
|
+
# already gets one by sharing `database` across its aggregates,
|
|
403
|
+
# the recommended, common case.
|
|
404
|
+
#
|
|
405
|
+
# @param process_manager [String, Symbol] the process manager's name
|
|
406
|
+
# @param correlation [String, Object] the instance's correlation value, stored as
|
|
407
|
+
# `correlation.to_s`
|
|
408
|
+
# @param state [String, Symbol] the saga's current state name
|
|
409
|
+
# @param memory [Hash] the saga's memory; must be JSON-serializable
|
|
410
|
+
# @param completed_compensations [Array] the ledger of completed compensable legs; must
|
|
411
|
+
# be JSON-serializable
|
|
412
|
+
# @return [Array] the statement's empty result rows; callers ignore it
|
|
413
|
+
# @raise [SQLite3::Exception] if the statement fails
|
|
273
414
|
def save_saga(process_manager:, correlation:, state:, memory:, completed_compensations: [])
|
|
274
415
|
@db.execute(
|
|
275
416
|
"INSERT OR REPLACE INTO hecks_saga_instances (domain, process_manager, correlation, state, memory, " \
|
|
@@ -279,6 +420,13 @@ module Hecks
|
|
|
279
420
|
)
|
|
280
421
|
end
|
|
281
422
|
|
|
423
|
+
# Removes a finished saga instance's checkpoint; a missing row is not an error.
|
|
424
|
+
#
|
|
425
|
+
# @param process_manager [String, Symbol] the process manager's name
|
|
426
|
+
# @param correlation [String, Object] the instance's correlation value, matched as
|
|
427
|
+
# `correlation.to_s`
|
|
428
|
+
# @return [Array] the statement's empty result rows; callers ignore it
|
|
429
|
+
# @raise [SQLite3::Exception] if the statement fails
|
|
282
430
|
def delete_saga(process_manager:, correlation:)
|
|
283
431
|
@db.execute(
|
|
284
432
|
"DELETE FROM hecks_saga_instances WHERE domain = ? AND process_manager = ? AND correlation = ?",
|
|
@@ -286,6 +434,18 @@ module Hecks
|
|
|
286
434
|
)
|
|
287
435
|
end
|
|
288
436
|
|
|
437
|
+
# Yields every checkpointed saga instance of this adapter's domain, for
|
|
438
|
+
# `Registry#rehydrate_sagas!` to restore at boot.
|
|
439
|
+
#
|
|
440
|
+
# @yieldparam process_manager [String] the process manager's name
|
|
441
|
+
# @yieldparam correlation [String] the instance's correlation value
|
|
442
|
+
# @yieldparam state [String] the saga's state name
|
|
443
|
+
# @yieldparam memory [Hash{Symbol => Object}] the saga's memory, Symbol keys at every depth
|
|
444
|
+
# @yieldparam completed_compensations [Array] the completed-compensation ledger, `[]`
|
|
445
|
+
# when the column is NULL
|
|
446
|
+
# @return [Enumerator, Array<Hash>] an enumerator over the same five values when no
|
|
447
|
+
# block is given; otherwise the raw result rows
|
|
448
|
+
# @raise [SQLite3::Exception] if the statement fails
|
|
289
449
|
def each_saga
|
|
290
450
|
return enum_for(:each_saga) unless block_given?
|
|
291
451
|
|
|
@@ -14,7 +14,7 @@ end
|
|
|
14
14
|
require_relative "driven/memory"
|
|
15
15
|
require_relative "driven/sqlite"
|
|
16
16
|
require_relative "driven/postgres"
|
|
17
|
-
# `PostgresEra` is
|
|
17
|
+
# `PostgresEra` is not `require_relative`d here — ADR 0033 moved it, and
|
|
18
18
|
# the rest of the era/lineage/translation subsystem, behind a loadable
|
|
19
19
|
# persistence plugin (`hecks/ports/persistence/plugins/era`) rather than
|
|
20
20
|
# requiring every app to carry it whether or not anything ever binds
|
|
@@ -22,10 +22,10 @@ require_relative "driven/postgres"
|
|
|
22
22
|
# that makes that genuinely load-nothing-until-asked instead of pushing
|
|
23
23
|
# an explicit `require` onto every one of the ~15 generic `bin/*` tools
|
|
24
24
|
# that boot an arbitrary checked-in domain (several of which — pizzas,
|
|
25
|
-
# compliance, chess, roster — bind PostgresEra): the
|
|
25
|
+
# compliance, chess, roster — bind PostgresEra): the first real
|
|
26
26
|
# `Adapters.const_get("PostgresEra")` (`registry/verification.rb`'s own
|
|
27
27
|
# adapter resolution, unchanged) transparently `require`s the plugin
|
|
28
|
-
# entry point, which defines the constant
|
|
28
|
+
# entry point, which defines the constant and calls `register_plugin`
|
|
29
29
|
# as its own side effect (`plugins/era.rb`'s last line) — the exact
|
|
30
30
|
# same effect an explicit `require "hecks/ports/persistence/plugins/
|
|
31
31
|
# era"` has, just deferred to the moment something is actually asked
|
|
@@ -43,7 +43,7 @@ require_relative "driven/mock_stripe_adapter"
|
|
|
43
43
|
require_relative "driven/secure_random_identity"
|
|
44
44
|
require_relative "driven/system_clock"
|
|
45
45
|
# `SequentialIdentity` — the deterministic identity_generation test
|
|
46
|
-
# double — is
|
|
46
|
+
# double — is not required here on purpose. It lives at
|
|
47
47
|
# spec/fixtures/sequential_identity.{adapter,rb}, loaded explicitly by
|
|
48
48
|
# whichever spec wants it: this file's `require_relative` list is what
|
|
49
49
|
# `Folder#load_library`'s `.adapter` glob backs, and `.adapter` DSL
|
|
@@ -4,19 +4,19 @@ require "rack"
|
|
|
4
4
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Adapters
|
|
7
|
-
#
|
|
8
|
-
# rather than code the domain reaches
|
|
7
|
+
# **The driving side** — code an outside caller reaches in through,
|
|
8
|
+
# rather than code the domain reaches out through. Every existing
|
|
9
9
|
# file under `adapters/driven/` is the latter: a store or reader a
|
|
10
|
-
# `persisted_by`/`port` binding resolves
|
|
10
|
+
# `persisted_by`/`port` binding resolves to, called by this
|
|
11
11
|
# framework's own runtime. Nothing under this repository has ever
|
|
12
|
-
# been the mirror image before — code that receives a request
|
|
12
|
+
# been the mirror image before — code that receives a request from
|
|
13
13
|
# the outside world and turns it into a dispatch — so this is the
|
|
14
14
|
# first entry, and the directory itself is new.
|
|
15
15
|
module Driving
|
|
16
|
-
# A
|
|
16
|
+
# **A GitHub webhook receiver, transport only** — the same split
|
|
17
17
|
# `Hecks::Adapters::GithubChecks` (qa/adapters/github_checks.rb,
|
|
18
|
-
# this class's own
|
|
19
|
-
#
|
|
18
|
+
# this class's own pull-side sibling) already draws for itself:
|
|
19
|
+
# this file owns proving a request really came from GitHub and
|
|
20
20
|
# unwrapping GitHub's own webhook envelope (`X-GitHub-Event`, the
|
|
21
21
|
# JSON body, GitHub's own automatic `ping` check) — never which
|
|
22
22
|
# commands to dispatch about what it finds inside. That is exactly
|
|
@@ -26,10 +26,10 @@ module Hecks
|
|
|
26
26
|
# in `qa/adapters/github_ci_webhook.rb`, the subclass of this file
|
|
27
27
|
# that actually knows what a `QualityControl::Clearance` is.
|
|
28
28
|
#
|
|
29
|
-
# A
|
|
29
|
+
# A plain rack app (`#call(env)`) — no Sinatra, no Rails — the same
|
|
30
30
|
# shape `Hecks::Forms::App` (lib/hecks/forms/app.rb) already
|
|
31
31
|
# established for the one other HTTP-facing surface this library
|
|
32
|
-
# ships. `rack` is a
|
|
32
|
+
# ships. `rack` is a lazy Gemfile dependency for exactly the reason
|
|
33
33
|
# that file's own header gives: this file is never required by
|
|
34
34
|
# `require "hecks"` (nothing under `adapters.rb`'s own eager
|
|
35
35
|
# `adapters/driven` load names it — see that file's own header),
|
|
@@ -37,9 +37,9 @@ module Hecks
|
|
|
37
37
|
# `rack` installed, the same "opt in by requiring the file at all"
|
|
38
38
|
# contract `hecks/forms.rb` already has for `Forms::App`.
|
|
39
39
|
#
|
|
40
|
-
#
|
|
40
|
+
# **Subclass responsibility**: implement `#handle_event(event, action,
|
|
41
41
|
# payload)`, returning `[http_status, response_body_hash]`. Called
|
|
42
|
-
#
|
|
42
|
+
# only after the signature has verified and the body has parsed as
|
|
43
43
|
# JSON — a subclass never has to re-check either. `event` is
|
|
44
44
|
# GitHub's own `X-GitHub-Event` header value ("check_suite",
|
|
45
45
|
# "check_run", "pull_request", ...); `action` is the payload's own
|
|
@@ -51,7 +51,7 @@ module Hecks
|
|
|
51
51
|
# subclass at all; there is nothing domain-specific to decide
|
|
52
52
|
# about it.
|
|
53
53
|
class GithubWebhook
|
|
54
|
-
#
|
|
54
|
+
# **Refused, loudly** — the same shape a domain refusal already takes
|
|
55
55
|
# everywhere else in this codebase (`Runtime::DOMAIN_REFUSALS`,
|
|
56
56
|
# `Forms::App`'s own `{error:, message:}` JSON body for a bad
|
|
57
57
|
# command). A request that cannot prove it came from GitHub gets
|
|
@@ -60,20 +60,20 @@ module Hecks
|
|
|
60
60
|
# exactly like success in a log nobody re-reads.
|
|
61
61
|
class InvalidSignature < StandardError; end
|
|
62
62
|
|
|
63
|
-
#
|
|
63
|
+
# **The body did not even parse** — distinct from a signature refusal:
|
|
64
64
|
# this body genuinely came from whoever signed it (checked
|
|
65
|
-
#
|
|
65
|
+
# first, before parsing ever runs — see `#call`), and simply
|
|
66
66
|
# is not JSON. Still refused, never guessed at.
|
|
67
67
|
class MalformedPayload < StandardError; end
|
|
68
68
|
|
|
69
69
|
SIGNATURE_HEADER = "HTTP_X_HUB_SIGNATURE_256".freeze
|
|
70
70
|
EVENT_HEADER = "HTTP_X_GITHUB_EVENT".freeze
|
|
71
71
|
|
|
72
|
-
# `secret:`
|
|
72
|
+
# `secret:` has no default, on purpose — the same rule
|
|
73
73
|
# `GoogleAuthentication`'s own header states for its own
|
|
74
74
|
# `ENV.fetch`, restated here because the consequence is worse for
|
|
75
75
|
# a webhook: an unverified signature check is not "half
|
|
76
|
-
# configured", it is
|
|
76
|
+
# configured", it is no verification at all, silently accepting
|
|
77
77
|
# anything claiming to be GitHub. A caller passes the real
|
|
78
78
|
# secret explicitly — from `ENV.fetch("GITHUB_WEBHOOK_SECRET")`
|
|
79
79
|
# or wherever it keeps one — rather than this class reaching into
|
|
@@ -107,8 +107,8 @@ module Hecks
|
|
|
107
107
|
|
|
108
108
|
private
|
|
109
109
|
|
|
110
|
-
#
|
|
111
|
-
# the moment it finds the first mismatching byte, so how
|
|
110
|
+
# Constant-time compare, not `==`. A byte-by-byte `==` returns
|
|
111
|
+
# the moment it finds the first mismatching byte, so how long
|
|
112
112
|
# that took leaks how many leading bytes of a forged signature
|
|
113
113
|
# were already right to anyone timing the response — GitHub's
|
|
114
114
|
# own webhook documentation calls this out by name and recommends
|
data/lib/hecks/behaviors/dsl.rb
CHANGED
|
@@ -59,7 +59,7 @@ module Hecks
|
|
|
59
59
|
# nothing and passes whenever dispatch doesn't raise; `count:` on a
|
|
60
60
|
# command and `emits:` on a query are each read by neither runner
|
|
61
61
|
# (Expectations#run_command/#run_query), so they're accepted here
|
|
62
|
-
# and then silently ignored at run time. Checking both at
|
|
62
|
+
# and then silently ignored at run time. Checking both at build
|
|
63
63
|
# time, not in the runners, makes them errors on the file that
|
|
64
64
|
# wrote them rather than green checks nobody questions.
|
|
65
65
|
def validate_expect!
|
|
@@ -95,7 +95,7 @@ module Hecks
|
|
|
95
95
|
|
|
96
96
|
def vision(text) = @vision = text
|
|
97
97
|
|
|
98
|
-
# Relative to
|
|
98
|
+
# Relative to this `.behaviors` file, never to the filesystem's cwd
|
|
99
99
|
# or a same-stem convention — scope is a fact this file declares,
|
|
100
100
|
# not one a runner infers.
|
|
101
101
|
def loads(*paths)
|