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
|
@@ -76,6 +76,11 @@
|
|
|
76
76
|
"verb": "Bluebook::Bluebook.Normalise",
|
|
77
77
|
"role": "Language"
|
|
78
78
|
},
|
|
79
|
+
{
|
|
80
|
+
"scope": "bluebook:bluebook.provide",
|
|
81
|
+
"verb": "Bluebook::Bluebook.Provide",
|
|
82
|
+
"role": "Language"
|
|
83
|
+
},
|
|
79
84
|
{
|
|
80
85
|
"scope": "bluebook:command.acts_on",
|
|
81
86
|
"verb": "Bluebook::Command.ActsOn",
|
data/lib/hecks/literal.rb
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
module Hecks
|
|
2
|
-
#
|
|
2
|
+
# The one spelling for a captured Ruby literal on the wire.
|
|
3
3
|
#
|
|
4
4
|
# Several `to_h` fields hold a value the author wrote in a bluebook —
|
|
5
5
|
# `where(status: { eq: "open" })`, `then_set append: { direction: { value:
|
|
6
6
|
# "credit" } }`, `dispatch ... with: { number: :source }`, an attribute's
|
|
7
|
-
# `default:` — as
|
|
7
|
+
# `default:` — as text, because the export has to stand on its own and a
|
|
8
8
|
# String field cannot say "this one was a Symbol". Every such field is the
|
|
9
9
|
# same question and now gets the same answer.
|
|
10
10
|
#
|
|
11
11
|
# It was three answers before, and they disagreed on both axes that matter.
|
|
12
|
-
# `Mutation#appended_fields` spelled a Symbol
|
|
13
|
-
# `inspect`; `QuerySpecification.render_value` spelled a Symbol with a
|
|
12
|
+
# `Mutation#appended_fields` spelled a Symbol bare and everything else
|
|
13
|
+
# `inspect`; `QuerySpecification.render_value` spelled a Symbol with a colon
|
|
14
14
|
# and everything else `to_s`. So `:amount` crossed as "amount" in a mutation
|
|
15
15
|
# and ":amount" in a saga binding — the same value, two spellings, and each
|
|
16
16
|
# reader had to know which field it was looking at. Worse, `to_s`/`inspect`
|
|
17
|
-
# on a Hash is Ruby's
|
|
17
|
+
# on a Hash is Ruby's own rendering, which moved under us: 3.3 writes
|
|
18
18
|
# `{:value=>"credit"}` and 3.4 writes `{value: "credit"}` for the identical
|
|
19
19
|
# Hash, so the wire format was silently pinned to an interpreter version.
|
|
20
20
|
#
|
|
21
|
-
#
|
|
21
|
+
# Self-describing is the rule, stated once here rather than inherited from
|
|
22
22
|
# whatever `inspect` happens to do: a symbol wears its colon, a string wears
|
|
23
23
|
# its quotes, a hash wears `{key: value}` braces, a list wears its brackets,
|
|
24
24
|
# and a number, a boolean and nil are bare. `read` is the exact inverse, and
|
|
25
25
|
# is the only thing that should ever take one of these strings apart.
|
|
26
|
-
# A
|
|
26
|
+
# A mutation source that reads the record's own state — `sets :positions,
|
|
27
27
|
# append: { knights: state(:knights) }`. A bare Symbol in a mutation
|
|
28
|
-
# source always names a command
|
|
28
|
+
# source always names a command argument (and imports it as one when
|
|
29
29
|
# the target's own field carries the same name — `CommandBuilder
|
|
30
30
|
# #resolve_append_fields!`); before this there was no way to say "the
|
|
31
31
|
# value this field already holds", so a command could not snapshot its
|
|
@@ -102,7 +102,7 @@ module Hecks
|
|
|
102
102
|
|
|
103
103
|
def read_array(raw) = split_items(raw[1..-2]).map { |item| read(item) }
|
|
104
104
|
|
|
105
|
-
# Split on the commas that are actually
|
|
105
|
+
# Split on the commas that are actually separators — never one inside a
|
|
106
106
|
# quoted string or a nested brace/bracket. Scanned rather than
|
|
107
107
|
# `String#split(", ")`, which tore `"a, b"` in half and lost the second
|
|
108
108
|
# field of anything nested.
|
data/lib/hecks/naming.rb
CHANGED
|
@@ -5,14 +5,14 @@ module Hecks
|
|
|
5
5
|
# two readers never invent two different spellings for the same
|
|
6
6
|
# derivation.
|
|
7
7
|
module Naming
|
|
8
|
-
#
|
|
8
|
+
# What separates the parts of a derived identity.
|
|
9
9
|
#
|
|
10
|
-
# An identity of several parts is their
|
|
10
|
+
# An identity of several parts is their join, and the join has to be spelled the
|
|
11
11
|
# same everywhere or two readers name two different records off one declaration.
|
|
12
12
|
# It was spelled three ways at once here — "::" for an aggregate under a
|
|
13
13
|
# chapter, "." for everything under an aggregate, "#" for the three that keyed
|
|
14
14
|
# off position — and each was written where it happened to be needed. Once the
|
|
15
|
-
# runtime derived the same identity, the runtime made a
|
|
15
|
+
# runtime derived the same identity, the runtime made a fourth, and a reference
|
|
16
16
|
# that resolved by string comparison found nothing.
|
|
17
17
|
IDENTITY_JOIN = ":".freeze
|
|
18
18
|
|
|
@@ -39,7 +39,7 @@ module Hecks
|
|
|
39
39
|
.downcase
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
#
|
|
42
|
+
# An identifier as a person would say it — `ATMCard` -> "ATM card",
|
|
43
43
|
# `AccrueInterest` -> "Accrue interest", `daily_limit` -> "Daily
|
|
44
44
|
# limit", `Back office` -> "Back office". The same two word-boundary
|
|
45
45
|
# splits `snake` uses, with a space instead of an underscore — plus
|
|
@@ -73,14 +73,14 @@ module Hecks
|
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
# The vowel-
|
|
76
|
+
# The vowel-letter heuristic — safe here for the same reason
|
|
77
77
|
# `Projections::Statements#article` gives: a construct name is a
|
|
78
78
|
# plain word, never "hour" or "university".
|
|
79
79
|
def a_or_an(word)
|
|
80
80
|
%w[a e i o u].include?(word.to_s[0].to_s.downcase) ? "an" : "a"
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
# The name a
|
|
83
|
+
# The name a collection of something takes.
|
|
84
84
|
#
|
|
85
85
|
# There were two of these and one was wrong. A read model's gathered heads
|
|
86
86
|
# derived their name with a bare `"#{snake(target)}s"`, so the meta-domain's
|
|
@@ -97,9 +97,9 @@ module Hecks
|
|
|
97
97
|
"#{word}s"
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
# `has_many`'s undo — the plural
|
|
100
|
+
# `has_many`'s undo — the plural written, back to the singular the target
|
|
101
101
|
# aggregate is actually named. Deliberately the crude half of a pair: `plural`
|
|
102
|
-
# above earns its precision (three suffix rules) because getting a
|
|
102
|
+
# above earns its precision (three suffix rules) because getting a collection
|
|
103
103
|
# name wrong reads as a typo forever ; this only ever recovers a name someone
|
|
104
104
|
# already wrote as a real aggregate, so "ies -> y, trailing s dropped" is the
|
|
105
105
|
# whole rule — enough for `has_many Invoices` to resolve to the aggregate
|
|
@@ -108,11 +108,11 @@ module Hecks
|
|
|
108
108
|
word = text.to_s
|
|
109
109
|
return "#{word[0..-4]}y" if word.length > 3 && word.end_with?("ies")
|
|
110
110
|
|
|
111
|
-
# `plural`'s
|
|
111
|
+
# `plural`'s own second rule adds "es" (not bare "s") after
|
|
112
112
|
# s/x/z/ch/sh — undone here the same way, or a word `plural`
|
|
113
113
|
# itself would have suffixed with "es" comes back missing its
|
|
114
114
|
# own trailing letter ("Boxes" -> "Boxe", not "Box") once this
|
|
115
|
-
# only ever knew how to drop a bare "s". Checked
|
|
115
|
+
# only ever knew how to drop a bare "s". Checked before the
|
|
116
116
|
# bare-"s" rule below: stripping "es" first and confirming what
|
|
117
117
|
# is left actually ends in one of those five shapes is what
|
|
118
118
|
# keeps an ordinary "-es" word (e.g. "Invoices" -> "Invoice")
|
|
@@ -143,19 +143,19 @@ module Hecks
|
|
|
143
143
|
text.include?(".") ? text.split(".", 2).last : text
|
|
144
144
|
end
|
|
145
145
|
|
|
146
|
-
#
|
|
146
|
+
# Domain, aggregate, then the REST dot-joined into one command path.
|
|
147
147
|
#
|
|
148
148
|
# The `::` boundary between domain and aggregate is unambiguous by
|
|
149
149
|
# construction — every caller here has already prefixed the domain
|
|
150
150
|
# itself (`PolicyInterpreter#deliver`, `SagaInterpreter#qualified`,
|
|
151
151
|
# `Router#dispatch`'s own rebuilt string) before this ever runs. A
|
|
152
|
-
#
|
|
152
|
+
# third `::` segment can still show up past that boundary: a bare
|
|
153
153
|
# `ScopedConstant` naming a port operation (`command_ref`'s own
|
|
154
154
|
# comment — `Aggregate::Port::Operation`, three colon-joined
|
|
155
|
-
# segments with no `.` of its own) only gets its
|
|
155
|
+
# segments with no `.` of its own) only gets its last `::` rewritten
|
|
156
156
|
# to `.` there, at DSL-build time, because nothing at that point
|
|
157
157
|
# knows yet whether the constant names a port operation or a
|
|
158
|
-
# domain-qualified command (`Domain::Aggregate::Command`, the
|
|
158
|
+
# domain-qualified command (`Domain::Aggregate::Command`, the other
|
|
159
159
|
# shape `command_ref` documents) — both are textually identical.
|
|
160
160
|
# Here, past the already-resolved domain boundary, any leftover
|
|
161
161
|
# `::` is unambiguous: it is that same rewrite artifact, and folding
|
|
@@ -179,19 +179,19 @@ module Hecks
|
|
|
179
179
|
end
|
|
180
180
|
|
|
181
181
|
# `trigger Account::Debit` / `dispatch Account::Debit` — a bare
|
|
182
|
-
#
|
|
182
|
+
# constant reference (`ConstShim`'s own `ScopedConstant`, S0b), not
|
|
183
183
|
# text (ADR 0025, "events and reactions" — command references become
|
|
184
|
-
# first-class). Ruby's `::` joins
|
|
184
|
+
# first-class). Ruby's `::` joins every segment the same way a
|
|
185
185
|
# constant path always does, but a command's own `hecks_fqn` joins
|
|
186
186
|
# its aggregate with `.` (`Construct#hecks_separator`'s default,
|
|
187
|
-
# only an
|
|
187
|
+
# only an aggregate overrides it to `::`) — so only the last `::`
|
|
188
188
|
# becomes a `.`; everything before it (the chapter, when a domain is
|
|
189
189
|
# spelled at all: `Banking::Account::Debit`) stays `::`-joined.
|
|
190
190
|
#
|
|
191
|
-
# A
|
|
191
|
+
# A string passes through unchanged, on purpose — legacy era text
|
|
192
192
|
# (S0a's own shadow-parsed spelling) already mixes `::` (domain) and
|
|
193
193
|
# `.` (command) correctly on its own, e.g. `"Banking::Account.Debit"`,
|
|
194
|
-
# and re-splitting that by content rather than by
|
|
194
|
+
# and re-splitting that by content rather than by type would corrupt
|
|
195
195
|
# it (its own last `::` sits between the domain and the aggregate,
|
|
196
196
|
# not the aggregate and the command). Only an actual constant object
|
|
197
197
|
# — never seen holding a `.` of its own — needs the rewrite at all.
|
|
@@ -216,25 +216,25 @@ module Hecks
|
|
|
216
216
|
|
|
217
217
|
# `transition Account::AccountDebited => "state"` / `starts_on
|
|
218
218
|
# Transfer::TransferRequested` / `ends_on Transfer::TransferSettled`
|
|
219
|
-
# — a process manager's
|
|
220
|
-
#
|
|
219
|
+
# — a process manager's own event references (ADR 0025, S6),
|
|
220
|
+
# deliberately not `event_ref` — found live, not assumed, wiring a
|
|
221
221
|
# real migrated corpus site into `bin/model_check` for the first
|
|
222
222
|
# time (2026-08-28): `SagaInterpreter#begin_saga`/`#advance_saga`
|
|
223
223
|
# match `pm.starts_on`/`pm.handler_for` against `event.name`, which
|
|
224
|
-
# `CommandRules::Emission#emit` stamps
|
|
224
|
+
# `CommandRules::Emission#emit` stamps bare — a command's own
|
|
225
225
|
# `emits AccountDebited` never carries its aggregate's name at all
|
|
226
226
|
# (unlike a policy's cross-aggregate `on`, matched instead by
|
|
227
227
|
# `Naming.demodulise(event.aggregate)` split apart from the bare
|
|
228
228
|
# name — `PolicyInterpreter#policies_for`). Handing a saga's own
|
|
229
|
-
# matcher the
|
|
229
|
+
# matcher the dotted `event_ref` form ("Account.AccountDebited")
|
|
230
230
|
# would silently name an event no command in the domain ever
|
|
231
231
|
# actually emits — caught by `bin/model_check`'s own `deaf_handler`/
|
|
232
232
|
# `deaf_trigger` findings the moment a real qualified corpus site
|
|
233
233
|
# existed to trip them, not by any unit test in isolation.
|
|
234
234
|
#
|
|
235
|
-
# A qualifier is still worth
|
|
235
|
+
# A qualifier is still worth writing (`Account::`) — the same
|
|
236
236
|
# provenance a reader gets from `trigger Account::Debit` — it is
|
|
237
|
-
# only not worth
|
|
237
|
+
# only not worth keeping: `demodulise` drops everything but the
|
|
238
238
|
# final segment, so `Account::AccountDebited` and a bare
|
|
239
239
|
# `AccountDebited` resolve to the identical stored string. A String
|
|
240
240
|
# passes through unchanged either way, exactly like `command_ref`'s
|
|
@@ -2,7 +2,7 @@ require_relative "../runtime/registry"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Ports
|
|
5
|
-
#
|
|
5
|
+
# Who can sign in, and what role do they hold — the domain-specific
|
|
6
6
|
# half of sign-in `Ports::Authentication` deliberately stays out of
|
|
7
7
|
# (see that port's own header: it only ever talks to the external
|
|
8
8
|
# provider, never this registry's own data). Resolved the same way
|
|
@@ -13,7 +13,7 @@ module Hecks
|
|
|
13
13
|
# Every domain shapes this differently — who its own "a person who
|
|
14
14
|
# can sign in" aggregate is, what admits them, what grants them
|
|
15
15
|
# access — so this port is pure delegation, same as every sibling
|
|
16
|
-
# port. The one piece of this that
|
|
16
|
+
# port. The one piece of this that is generic (a live Governance
|
|
17
17
|
# grant should win over whatever an aggregate's own role field
|
|
18
18
|
# says) lives on `Ports::Authorization#live_role_for` instead —
|
|
19
19
|
# a domain's adapter here calls that directly, rather than this
|
|
@@ -23,26 +23,82 @@ module Hecks
|
|
|
23
23
|
|
|
24
24
|
module_function
|
|
25
25
|
|
|
26
|
+
# Asks the domain's adapter for the session an already-resolved identity signs into.
|
|
27
|
+
#
|
|
28
|
+
# No adapter or spec double for this port ships in this repository, so every shape
|
|
29
|
+
# below other than `registry` is adapter-defined: the port forwards it untouched.
|
|
30
|
+
#
|
|
31
|
+
# @param registry [Runtime::Registry] the booted registry, used to resolve the adapter
|
|
32
|
+
# and handed on to it
|
|
33
|
+
# @param identity_id [Object] adapter-defined identity key, forwarded unchanged
|
|
34
|
+
# @return [Object] adapter-defined session representation
|
|
35
|
+
# @raise [Runtime::WiringError] if this port does not resolve to exactly one adapter
|
|
36
|
+
# (see `adapter`)
|
|
26
37
|
def session_for_identity(registry, identity_id:)
|
|
27
38
|
adapter(registry).session_for_identity(registry, identity_id: identity_id)
|
|
28
39
|
end
|
|
29
40
|
|
|
41
|
+
# Admits a new person, in whatever way this domain's adapter defines admission.
|
|
42
|
+
#
|
|
43
|
+
# The keywords carry the names of a verified sign-in (`Ports::Authentication.verify`
|
|
44
|
+
# answers `issuer`, `subject` and `email`), but nothing in this repository wires the
|
|
45
|
+
# two together, so their shapes here are adapter-defined.
|
|
46
|
+
#
|
|
47
|
+
# @param registry [Runtime::Registry] the booted registry, used to resolve the adapter
|
|
48
|
+
# and handed on to it
|
|
49
|
+
# @param email [Object] adapter-defined, forwarded unchanged; the person's email
|
|
50
|
+
# @param issuer [Object] adapter-defined, forwarded unchanged; the OIDC issuer that
|
|
51
|
+
# authenticated the person
|
|
52
|
+
# @param subject [Object] adapter-defined, forwarded unchanged; the OIDC subject the
|
|
53
|
+
# issuer vouches for
|
|
54
|
+
# @return [Object] adapter-defined representation of the newly admitted person
|
|
55
|
+
# @raise [Runtime::WiringError] if this port does not resolve to exactly one adapter
|
|
56
|
+
# (see `adapter`)
|
|
30
57
|
def provision(registry, email:, issuer:, subject:)
|
|
31
58
|
adapter(registry).provision(registry, email: email, issuer: issuer, subject: subject)
|
|
32
59
|
end
|
|
33
60
|
|
|
61
|
+
# Asks the domain's adapter which roles it can grant.
|
|
62
|
+
#
|
|
63
|
+
# @param registry [Runtime::Registry] the booted registry, used to resolve the adapter
|
|
64
|
+
# and handed on to it
|
|
65
|
+
# @return [Object] adapter-defined collection of grantable roles
|
|
66
|
+
# @raise [Runtime::WiringError] if this port does not resolve to exactly one adapter
|
|
67
|
+
# (see `adapter`)
|
|
34
68
|
def available_roles(registry)
|
|
35
69
|
adapter(registry).available_roles(registry)
|
|
36
70
|
end
|
|
37
71
|
|
|
72
|
+
# Grants a role to a person, by whatever means the domain's adapter records a grant.
|
|
73
|
+
#
|
|
74
|
+
# @param registry [Runtime::Registry] the booted registry, used to resolve the adapter
|
|
75
|
+
# and handed on to it
|
|
76
|
+
# @param email [Object] adapter-defined, forwarded unchanged; the person receiving the role
|
|
77
|
+
# @param role [Object] adapter-defined, forwarded unchanged; the role to grant
|
|
78
|
+
# @return [Object] adapter-defined representation of the grant
|
|
79
|
+
# @raise [Runtime::WiringError] if this port does not resolve to exactly one adapter
|
|
80
|
+
# (see `adapter`)
|
|
38
81
|
def grant(registry, email:, role:)
|
|
39
82
|
adapter(registry).grant(registry, email: email, role: role)
|
|
40
83
|
end
|
|
41
84
|
|
|
85
|
+
# Lists every person the domain's adapter knows about.
|
|
86
|
+
#
|
|
87
|
+
# @param registry [Runtime::Registry] the booted registry, used to resolve the adapter
|
|
88
|
+
# and handed on to it
|
|
89
|
+
# @return [Object] adapter-defined collection of people
|
|
90
|
+
# @raise [Runtime::WiringError] if this port does not resolve to exactly one adapter
|
|
91
|
+
# (see `adapter`)
|
|
42
92
|
def all_people(registry)
|
|
43
93
|
adapter(registry).all_people(registry)
|
|
44
94
|
end
|
|
45
95
|
|
|
96
|
+
# Finds the single adapter bound to this port, refusing an ambiguous wiring.
|
|
97
|
+
#
|
|
98
|
+
# @param registry [Runtime::Registry] the booted registry to search
|
|
99
|
+
# @return [Module] the adapter module or class implementing this port
|
|
100
|
+
# @raise [Runtime::WiringError] if no adapter, or more than one, implements this port,
|
|
101
|
+
# or the one that does has no Ruby implementation under `Hecks::Adapters`
|
|
46
102
|
def adapter(registry)
|
|
47
103
|
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
48
104
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Ports
|
|
3
3
|
module Agent
|
|
4
|
-
#
|
|
4
|
+
# Where a raw hash becomes a struct — the one place, so every
|
|
5
5
|
# adapter (the real `claude_code` one, and any scripted double
|
|
6
6
|
# standing in for it in a spec) is held to the identical shape.
|
|
7
7
|
# An adapter's whole job ends at "here is what came back, already
|
|
8
|
-
# JSON"; whether that hash is
|
|
8
|
+
# JSON"; whether that hash is usable is decided once, here, not
|
|
9
9
|
# re-decided per adapter.
|
|
10
10
|
#
|
|
11
11
|
# Every failure raises `ValidationError` — never lets a bare
|
|
@@ -18,16 +18,43 @@ module Hecks
|
|
|
18
18
|
|
|
19
19
|
module_function
|
|
20
20
|
|
|
21
|
+
# Validates an adapter's raw answer to `ask` into `Question` structs.
|
|
22
|
+
#
|
|
23
|
+
# @param raw [Hash{String => Object}] the adapter's parsed reply, expected to hold a
|
|
24
|
+
# `"questions"` array of `{"text" =>, "because" =>}` rows; any other shape is refused
|
|
25
|
+
# @return [Array<Ports::Agent::Question>] one struct per row, in the answer's order;
|
|
26
|
+
# `[]` for an empty `"questions"` array
|
|
27
|
+
# @raise [Ports::Agent::ValidationError] if `raw` is not a Hash, holds no
|
|
28
|
+
# `"questions"` array, or a row's `"text"` or `"because"` is missing or blank
|
|
21
29
|
def questions(raw)
|
|
22
30
|
rows(raw, "questions").map { |row| Question.new(text: text!(row, "text"), because: text!(row, "because")) }
|
|
23
31
|
end
|
|
24
32
|
|
|
33
|
+
# Validates an adapter's raw answer to `interpret` into `Proposal` structs.
|
|
34
|
+
#
|
|
35
|
+
# @param raw [Hash{String => Object}] the adapter's parsed reply, expected to hold a
|
|
36
|
+
# `"proposals"` array of `{"verb" =>, "rationale" =>, "arguments" =>}` rows
|
|
37
|
+
# @return [Array<Ports::Agent::Proposal>] one struct per row, its `arguments` an Array
|
|
38
|
+
# of `{name:, field:, value:}` Hashes (see `arguments!`); `[]` for an empty
|
|
39
|
+
# `"proposals"` array
|
|
40
|
+
# @raise [Ports::Agent::ValidationError] if `raw` is not a Hash, holds no
|
|
41
|
+
# `"proposals"` array, or a row has a verb that is not fully qualified, a blank
|
|
42
|
+
# `"rationale"`, or an argument row with no `"name"`
|
|
25
43
|
def proposals(raw)
|
|
26
44
|
rows(raw, "proposals").map do |row|
|
|
27
45
|
Proposal.new(verb: verb!(row), rationale: text!(row, "rationale"), arguments: arguments!(row))
|
|
28
46
|
end
|
|
29
47
|
end
|
|
30
48
|
|
|
49
|
+
# Validates an adapter's raw answer to `critique` into `Finding` structs.
|
|
50
|
+
#
|
|
51
|
+
# @param raw [Hash{String => Object}] the adapter's parsed reply, expected to hold a
|
|
52
|
+
# `"findings"` array of `{"kind" =>, "severity" =>, "subject" =>, "message" =>}` rows
|
|
53
|
+
# @return [Array<Ports::Agent::Finding>] one struct per row, with `kind` and `severity`
|
|
54
|
+
# as Symbols; `[]` for an empty `"findings"` array
|
|
55
|
+
# @raise [Ports::Agent::ValidationError] if `raw` is not a Hash, holds no
|
|
56
|
+
# `"findings"` array, or a row's kind is outside `CRITIQUE_KINDS`, its severity is
|
|
57
|
+
# outside `SEVERITIES`, or its `"subject"` or `"message"` is missing or blank
|
|
31
58
|
def findings(raw)
|
|
32
59
|
rows(raw, "findings").map do |row|
|
|
33
60
|
Finding.new(kind: kind!(row), severity: severity!(row), subject: text!(row, "subject"),
|
|
@@ -35,6 +62,14 @@ module Hecks
|
|
|
35
62
|
end
|
|
36
63
|
end
|
|
37
64
|
|
|
65
|
+
# Validates an adapter's raw answer to `suggest_name` into `Suggestion` structs.
|
|
66
|
+
#
|
|
67
|
+
# @param raw [Hash{String => Object}] the adapter's parsed reply, expected to hold a
|
|
68
|
+
# `"names"` array of `{"name" =>, "because" =>, "rejected" =>}` rows
|
|
69
|
+
# @return [Array<Ports::Agent::Suggestion>] one struct per row, its `rejected` an
|
|
70
|
+
# Array of Strings (`[]` when the row carries none); `[]` for an empty `"names"` array
|
|
71
|
+
# @raise [Ports::Agent::ValidationError] if `raw` is not a Hash, holds no `"names"`
|
|
72
|
+
# array, or a row's `"name"` or `"because"` is missing or blank
|
|
38
73
|
def suggestions(raw)
|
|
39
74
|
rows(raw, "names").map do |row|
|
|
40
75
|
Suggestion.new(name: text!(row, "name"), because: text!(row, "because"),
|
|
@@ -44,6 +79,13 @@ module Hecks
|
|
|
44
79
|
|
|
45
80
|
# ── shared checks, each named for what it refuses ───────────────
|
|
46
81
|
|
|
82
|
+
# Reads the array of rows an answer holds under one key, refusing any other shape.
|
|
83
|
+
#
|
|
84
|
+
# @param raw [Object] the adapter's reply; only a Hash is accepted
|
|
85
|
+
# @param key [String] the key the rows sit under, such as `"questions"`
|
|
86
|
+
# @return [Array<Object>] the rows exactly as the adapter gave them, unvalidated
|
|
87
|
+
# @raise [Ports::Agent::ValidationError] if `raw` is not a Hash, or `raw[key]` is
|
|
88
|
+
# not an Array
|
|
47
89
|
def rows(raw, key)
|
|
48
90
|
raise ValidationError, "expected a Hash back, got #{raw.class}: #{raw.inspect}" unless raw.is_a?(Hash)
|
|
49
91
|
|
|
@@ -53,6 +95,13 @@ module Hecks
|
|
|
53
95
|
raise ValidationError, "no #{key.inspect} array in the answer: #{raw.inspect}"
|
|
54
96
|
end
|
|
55
97
|
|
|
98
|
+
# Reads one required text field off a row, refusing a missing or blank one.
|
|
99
|
+
#
|
|
100
|
+
# @param row [Hash{String => Object}] one row of the adapter's answer
|
|
101
|
+
# @param key [String] the field to read, such as `"because"`
|
|
102
|
+
# @return [String] the field's value as a String, whitespace untouched
|
|
103
|
+
# @raise [Ports::Agent::ValidationError] if the field is nil, false, empty, or only
|
|
104
|
+
# whitespace
|
|
56
105
|
def text!(row, key)
|
|
57
106
|
value = row[key]
|
|
58
107
|
return value.to_s if value && !value.to_s.strip.empty?
|
|
@@ -60,12 +109,19 @@ module Hecks
|
|
|
60
109
|
raise ValidationError, "#{key.inspect} missing or blank in #{row.inspect}"
|
|
61
110
|
end
|
|
62
111
|
|
|
63
|
-
#
|
|
112
|
+
# Reads a proposal row's verb, refusing one that is not fully qualified.
|
|
113
|
+
#
|
|
114
|
+
# A verb the language could not even parse is an adapter fault,
|
|
64
115
|
# refused right here — the same `not_fully_qualified` shape the
|
|
65
116
|
# language's own grammar already refuses by, reused rather than
|
|
66
117
|
# reinvented. A verb naming a real category that turns out to
|
|
67
|
-
# describe the wrong fact is
|
|
118
|
+
# describe the wrong fact is not this port's business: that one
|
|
68
119
|
# dispatches, and `Interview::Session#offer` is what says no.
|
|
120
|
+
#
|
|
121
|
+
# @param row [Hash{String => Object}] one proposal row of the adapter's answer
|
|
122
|
+
# @return [String] the row's `"verb"`, shaped `Chapter::Aggregate.Command`
|
|
123
|
+
# @raise [Ports::Agent::ValidationError] if the verb is missing or does not match
|
|
124
|
+
# `VERB_PATTERN`
|
|
69
125
|
def verb!(row)
|
|
70
126
|
verb = row["verb"].to_s
|
|
71
127
|
return verb if VERB_PATTERN.match?(verb)
|
|
@@ -73,6 +129,12 @@ module Hecks
|
|
|
73
129
|
raise ValidationError, "#{verb.inspect} is not a fully-qualified verb (Chapter::Aggregate.Command)"
|
|
74
130
|
end
|
|
75
131
|
|
|
132
|
+
# Reads a finding row's kind, refusing one outside the closed critique vocabulary.
|
|
133
|
+
#
|
|
134
|
+
# @param row [Hash{String => Object}] one finding row of the adapter's answer
|
|
135
|
+
# @return [Symbol] the row's `"kind"`, a member of `CRITIQUE_KINDS`
|
|
136
|
+
# @raise [Ports::Agent::ValidationError] if the kind is missing or not in
|
|
137
|
+
# `CRITIQUE_KINDS`
|
|
76
138
|
def kind!(row)
|
|
77
139
|
kind = row["kind"].to_s.to_sym
|
|
78
140
|
return kind if CRITIQUE_KINDS.include?(kind)
|
|
@@ -80,6 +142,12 @@ module Hecks
|
|
|
80
142
|
raise ValidationError, "#{kind.inspect} is not a critique kind this port knows (#{CRITIQUE_KINDS.join(', ')})"
|
|
81
143
|
end
|
|
82
144
|
|
|
145
|
+
# Reads a finding row's severity, refusing anything but the two that exist.
|
|
146
|
+
#
|
|
147
|
+
# @param row [Hash{String => Object}] one finding row of the adapter's answer
|
|
148
|
+
# @return [Symbol] the row's `"severity"`, a member of `SEVERITIES`
|
|
149
|
+
# @raise [Ports::Agent::ValidationError] if the severity is missing or not in
|
|
150
|
+
# `SEVERITIES`
|
|
83
151
|
def severity!(row)
|
|
84
152
|
severity = row["severity"].to_s.to_sym
|
|
85
153
|
return severity if SEVERITIES.include?(severity)
|
|
@@ -87,9 +155,18 @@ module Hecks
|
|
|
87
155
|
raise ValidationError, "#{severity.inspect} is not a severity this port knows (#{SEVERITIES.join(', ')})"
|
|
88
156
|
end
|
|
89
157
|
|
|
90
|
-
#
|
|
91
|
-
#
|
|
158
|
+
# Normalises a proposal row's arguments into symbol-keyed, all-String rows.
|
|
159
|
+
#
|
|
160
|
+
# Rows shaped exactly as `Interview::Proposal::Argument` — a
|
|
161
|
+
# reference's `field` is legitimately blank (a reference is an
|
|
92
162
|
# id), so only `name` is required here.
|
|
163
|
+
#
|
|
164
|
+
# @param row [Hash{String => Object}] one proposal row of the adapter's answer
|
|
165
|
+
# @return [Array<Hash{Symbol => String}>] one `{name:, field:, value:}` Hash per
|
|
166
|
+
# argument, `field` and `value` being `""` when absent; `[]` when the row has no
|
|
167
|
+
# `"arguments"`
|
|
168
|
+
# @raise [Ports::Agent::ValidationError] if an argument row's `"name"` is missing or
|
|
169
|
+
# empty
|
|
93
170
|
def arguments!(row)
|
|
94
171
|
Array(row["arguments"]).map do |argument|
|
|
95
172
|
name = argument["name"].to_s
|