hecks 1.3.0 → 1.5.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 +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -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 +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- 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/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -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 +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- 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 +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- 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 +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- 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 +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- 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 +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -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 +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- 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 +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- 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/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -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 +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- 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 +34 -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 +95 -6
- 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 +48 -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 +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -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 +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -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 +61 -15
- 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 +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- 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 +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -2,7 +2,7 @@ require_relative "../runtime/errors"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Facade
|
|
5
|
-
#
|
|
5
|
+
# **The CLI door** — where Facade meets a caller holding flat strings.
|
|
6
6
|
#
|
|
7
7
|
# `JsonDoor` beside this one translates for a caller holding parsed JSON:
|
|
8
8
|
# String keys, already-nested objects, real Integers. A command line has
|
|
@@ -12,7 +12,7 @@ module Hecks
|
|
|
12
12
|
# So this does the two things that turns into: rebuild the nesting, and
|
|
13
13
|
# give every leaf the type the chapter declared for it.
|
|
14
14
|
#
|
|
15
|
-
#
|
|
15
|
+
# The type comes from the projection, never from the value. A door that
|
|
16
16
|
# guessed — "99 looks like a number" — would send the Integer 99 for a
|
|
17
17
|
# version string of "99", and be wrong in a way nothing downstream could
|
|
18
18
|
# detect, because both are perfectly good arguments. `Projector::CliProjector`
|
|
@@ -21,8 +21,24 @@ module Hecks
|
|
|
21
21
|
module CliDoor
|
|
22
22
|
module_function
|
|
23
23
|
|
|
24
|
+
# Builds a verb's nested, typed argument Hash out of the `name=value` words on a
|
|
25
|
+
# command line.
|
|
26
|
+
#
|
|
24
27
|
# `["reference.value=BUG#1", "sequence.value=99"]` against a projected
|
|
25
28
|
# verb spec -> `{ reference: { value: "BUG#1" }, sequence: { value: 99 } }`
|
|
29
|
+
#
|
|
30
|
+
# @param spec [Hash{Symbol => Object}] one verb's entry from `Projector::CliProjector`;
|
|
31
|
+
# read for `:arguments` and the optional `:legacy_arguments`, each an Array of
|
|
32
|
+
# option Hashes with `:path`, `:type` and, for a list, `:list`
|
|
33
|
+
# @param pairs [Array<String>] the words after the verb, each `path=value`; a path
|
|
34
|
+
# may be the short form of a single-field value object (`reference` for
|
|
35
|
+
# `reference.value`)
|
|
36
|
+
# @return [Hash{Symbol => Object}] the arguments nested by path, each leaf cast to
|
|
37
|
+
# its declared type and each list option collected into an Array; `{}` for no pairs
|
|
38
|
+
# @raise [Runtime::NotFound] if a pair has no `=`, or names a path the verb does
|
|
39
|
+
# not take
|
|
40
|
+
# @raise [Runtime::TypeMismatch] if a value cannot be read as the declared Integer
|
|
41
|
+
# or Float
|
|
26
42
|
def arguments(spec, pairs)
|
|
27
43
|
# Legacy options are accepted but not printed in help. This lets an
|
|
28
44
|
# existing id=... aggregate invocation cross the new receiver boundary
|
|
@@ -46,6 +62,12 @@ module Hecks
|
|
|
46
62
|
end
|
|
47
63
|
end
|
|
48
64
|
|
|
65
|
+
# Cuts one command-line word at its first `=`, so a value may itself contain `=`.
|
|
66
|
+
#
|
|
67
|
+
# @param pair [String] one word, such as `"reference.value=BUG#1"`
|
|
68
|
+
# @return [Array(String, String)] the path and the value; the value is `""` for
|
|
69
|
+
# `"name="`
|
|
70
|
+
# @raise [Runtime::NotFound] if the word contains no `=`
|
|
49
71
|
def split(pair)
|
|
50
72
|
name, value = pair.split("=", 2)
|
|
51
73
|
raise Runtime::NotFound, "#{pair.inspect} is not name=value" if value.nil?
|
|
@@ -53,16 +75,32 @@ module Hecks
|
|
|
53
75
|
[name, value]
|
|
54
76
|
end
|
|
55
77
|
|
|
56
|
-
#
|
|
78
|
+
# Expands a bare value-object name into the full path of its only field.
|
|
79
|
+
#
|
|
80
|
+
# The short form, for the common case. Almost every value object in this
|
|
57
81
|
# corpus has exactly one field, so `reference=BUG#1` is unambiguous and
|
|
58
82
|
# is what anybody types. Expanded only when precisely one option starts
|
|
59
83
|
# with that prefix — two would be a guess, and a guess about which field
|
|
60
84
|
# a caller meant is worse than asking them to say.
|
|
85
|
+
#
|
|
86
|
+
# @param path [String] the path as typed, such as `"reference"`
|
|
87
|
+
# @param options [Hash{String => Hash}] the verb's options keyed by full path
|
|
88
|
+
# @return [String] the one full path beginning `"#{path}."`, or `path` unchanged when
|
|
89
|
+
# none or several do
|
|
61
90
|
def expand(path, options)
|
|
62
91
|
candidates = options.keys.select { |key| key.start_with?("#{path}.") }
|
|
63
92
|
candidates.length == 1 ? candidates.first : path
|
|
64
93
|
end
|
|
65
94
|
|
|
95
|
+
# Converts one command-line value to the type the chapter declares for its field.
|
|
96
|
+
#
|
|
97
|
+
# @param value [String] the text after the `=`
|
|
98
|
+
# @param type [String] the declared type name; `"Integer"`, `"Float"` and
|
|
99
|
+
# `"Boolean"` are converted, anything else leaves the value a String
|
|
100
|
+
# @return [Integer, Float, Boolean, String] the typed value; a Boolean is true only
|
|
101
|
+
# for `true`, `yes` or `1` in any letter case, and false for every other text
|
|
102
|
+
# @raise [Runtime::TypeMismatch] if the text does not parse as the declared Integer
|
|
103
|
+
# or Float
|
|
66
104
|
def cast(value, type)
|
|
67
105
|
case type
|
|
68
106
|
when "Integer" then Integer(value)
|
|
@@ -74,27 +112,36 @@ module Hecks
|
|
|
74
112
|
raise Runtime::TypeMismatch, "#{value.inspect} is not #{type} — the chapter declares this field as #{type}"
|
|
75
113
|
end
|
|
76
114
|
|
|
77
|
-
#
|
|
115
|
+
# Adds one element to a list argument, creating the list on first use.
|
|
116
|
+
#
|
|
117
|
+
# A list grows rather than overwrites, and getting this wrong is silent.
|
|
78
118
|
#
|
|
79
|
-
# `tags.value=framework tags.value=model-checker`
|
|
80
|
-
# twice
|
|
119
|
+
# `tags.value=framework tags.value=model-checker` sent through `bury`
|
|
120
|
+
# twice would store the second one alone — no refusal, no warning, one tag
|
|
81
121
|
# simply gone. That is the failure the interview named first: not the
|
|
82
122
|
# loud kind, the kind where a value is forgotten and the caller has no
|
|
83
123
|
# way to notice.
|
|
84
124
|
#
|
|
85
|
-
# A
|
|
125
|
+
# A list of one is still a list. `tags.value=flaky` produces
|
|
86
126
|
# `[{ value: "flaky" }]`, not `{ value: "flaky" }`, because the chapter
|
|
87
127
|
# declared a collection and a caller who sent one element did not
|
|
88
|
-
# thereby declare a different shape.
|
|
89
|
-
#
|
|
128
|
+
# thereby declare a different shape. A bare object must never reach a
|
|
129
|
+
# `list_of` attribute: everything downstream that walks
|
|
90
130
|
# it — a query's `contains`, a projection, the Postgres adapter's own
|
|
91
131
|
# array handling — is entitled to assume it can iterate.
|
|
92
132
|
#
|
|
93
|
-
#
|
|
133
|
+
# Multi-field elements are not supported here, deliberately. A flat
|
|
94
134
|
# command line has no way to say which `a.x=` goes with which `a.y=`,
|
|
95
135
|
# and inventing an index syntax would be a language nobody asked for.
|
|
96
136
|
# Every list in this corpus is a list of single-field value objects; a
|
|
97
137
|
# richer one is a job for `JsonDoor`, which has real nesting.
|
|
138
|
+
#
|
|
139
|
+
# @param hash [Hash{Symbol => Object}] the arguments built so far; mutated in place
|
|
140
|
+
# @param path [Array<String>] the option's path segments, at least two: the last
|
|
141
|
+
# names the element's field, the one before it names the list
|
|
142
|
+
# @param value [Integer, Float, Boolean, String] the cast value for the new element
|
|
143
|
+
# @return [Hash{Symbol => Object}] `hash`, with `{ leaf => value }` appended to the
|
|
144
|
+
# Array at the list's key
|
|
98
145
|
def append(hash, path, value)
|
|
99
146
|
*branches, leaf = path.map(&:to_sym)
|
|
100
147
|
holder = branches[0..-2].reduce(hash) { |node, key| node[key] ||= {} }
|
|
@@ -104,6 +151,13 @@ module Hecks
|
|
|
104
151
|
hash
|
|
105
152
|
end
|
|
106
153
|
|
|
154
|
+
# Sets one value at a nested path, creating each intermediate Hash on the way and
|
|
155
|
+
# overwriting whatever the leaf already held.
|
|
156
|
+
#
|
|
157
|
+
# @param hash [Hash{Symbol => Object}] the arguments built so far; mutated in place
|
|
158
|
+
# @param path [Array<String>] the option's path segments, outermost first
|
|
159
|
+
# @param value [Integer, Float, Boolean, String] the cast value to store at the leaf
|
|
160
|
+
# @return [Hash{Symbol => Object}] `hash`, with the value set under Symbol keys
|
|
107
161
|
def bury(hash, path, value)
|
|
108
162
|
*branches, leaf = path.map(&:to_sym)
|
|
109
163
|
target = branches.reduce(hash) { |node, key| node[key] ||= {} }
|
|
@@ -111,6 +165,11 @@ module Hecks
|
|
|
111
165
|
hash
|
|
112
166
|
end
|
|
113
167
|
|
|
168
|
+
# Words the refusal for an argument the verb does not take, listing what it does.
|
|
169
|
+
#
|
|
170
|
+
# @param path [String] the path as the caller typed it
|
|
171
|
+
# @param known [Array<String>] every path the verb accepts, in any order
|
|
172
|
+
# @return [String] a one-line message naming `path` and the sorted accepted paths
|
|
114
173
|
def unknown(path, known)
|
|
115
174
|
"no argument #{path.inspect} — this verb takes #{known.sort.join(', ')}"
|
|
116
175
|
end
|
|
@@ -7,23 +7,44 @@ require_relative "../ports/clock"
|
|
|
7
7
|
|
|
8
8
|
module Hecks
|
|
9
9
|
module Facade
|
|
10
|
-
#
|
|
10
|
+
# The runner behind a projected CLI.
|
|
11
11
|
#
|
|
12
|
-
# `Projector::CliProjector` answers what a domain's command line
|
|
12
|
+
# `Projector::CliProjector` answers what a domain's command line looks
|
|
13
13
|
# like; this is the twenty lines that parse against it and dispatch. It
|
|
14
14
|
# lives in `lib/` rather than in a `bin/` because more than one front door
|
|
15
15
|
# wants it — `bin/run` for whichever domain you are standing in, `bin/qc`
|
|
16
16
|
# pinned to the QA ledger — and a second copy of the parse-and-dispatch
|
|
17
17
|
# would be the exact duplication the projection exists to avoid.
|
|
18
18
|
#
|
|
19
|
-
#
|
|
19
|
+
# No IO. It answers `[text, status]` and never prints or exits, so a spec
|
|
20
20
|
# can call it without capturing streams or trapping SystemExit. The `bin/`
|
|
21
21
|
# scripts do the printing, the same division `Router` and `JsonDoor`
|
|
22
22
|
# already keep against HTTP.
|
|
23
23
|
module CliRunner
|
|
24
24
|
module_function
|
|
25
25
|
|
|
26
|
+
# Runs one command line against a booted domain: resolves the verb or question,
|
|
27
|
+
# parses its `name=value` arguments, dispatches or queries, and answers with the text
|
|
28
|
+
# to print.
|
|
29
|
+
#
|
|
26
30
|
# `[text, status]` — status 0 answered, 1 refused or misused.
|
|
31
|
+
#
|
|
32
|
+
# Only the first bluebook in the registry (the booted domain's own) is projected
|
|
33
|
+
# into verbs. `--help`, `-h`, `help` or an empty `argv` answers the usage text; a
|
|
34
|
+
# verb followed by `--help` answers that verb's help.
|
|
35
|
+
#
|
|
36
|
+
# @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain,
|
|
37
|
+
# as `Hecks.boot` returns it
|
|
38
|
+
# @param argv [Array<String>] the words after the program name: an optional `ask`,
|
|
39
|
+
# the verb or question name (bare or aggregate-qualified), then `path=value` pairs
|
|
40
|
+
# @param program [String] how the caller was invoked, echoed in usage and hints
|
|
41
|
+
# @return [Array(String, Integer)] the text to print and the exit status: 0 with
|
|
42
|
+
# usage, help, or pretty-printed JSON of the outcome; 1 with a message when the
|
|
43
|
+
# verb is unknown, an argument is wrong, a record is missing, or the domain refuses
|
|
44
|
+
# @raise [Runtime::WiringError] if a repository, or the clock adapter a `now`
|
|
45
|
+
# argument needs, cannot be resolved
|
|
46
|
+
# @raise [Runtime::StaleWrite] if concurrent writers beat the command through every
|
|
47
|
+
# retry
|
|
27
48
|
def call(runtime:, argv:, program: "bin/run")
|
|
28
49
|
bluebook = runtime.registry.bluebooks.values.first
|
|
29
50
|
cli = Projector.call(:cli, bluebook: bluebook, options: { program: program })
|
|
@@ -31,14 +52,14 @@ module Hecks
|
|
|
31
52
|
name = argv.first
|
|
32
53
|
return [cli[:usage], 0] if name.nil? || %w[--help -h help].include?(name)
|
|
33
54
|
|
|
34
|
-
# `ask`
|
|
55
|
+
# `ask` puts a question in its own namespace — a chapter may declare a
|
|
35
56
|
# command and a query of one name, and banking does.
|
|
36
57
|
asking = name == "ask"
|
|
37
58
|
argv = argv[1..] if asking
|
|
38
59
|
name = argv.first
|
|
39
60
|
return [cli[:usage], 1] if name.nil?
|
|
40
61
|
|
|
41
|
-
#
|
|
62
|
+
# Resolved through the alias map, so `pizzas create_pizza` and
|
|
42
63
|
# `pizzas order.create_pizza` reach the same verb — the aggregate is
|
|
43
64
|
# worth typing only when two of them declare the same word.
|
|
44
65
|
pool = asking ? cli[:questions] : cli[:verbs]
|
|
@@ -56,6 +77,26 @@ module Hecks
|
|
|
56
77
|
dispatch(runtime, spec, name, rest, program, asking)
|
|
57
78
|
end
|
|
58
79
|
|
|
80
|
+
# Parses one resolved verb's arguments, runs it as a query or a command, and turns
|
|
81
|
+
# the outcome, or the domain's refusal, into printable text.
|
|
82
|
+
#
|
|
83
|
+
# @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain
|
|
84
|
+
# @param spec [Hash{Symbol => Object}] the verb's entry from `Projector::CliProjector`;
|
|
85
|
+
# read for `:kind`, `:verb`, `:arguments`, `:receiver` and `:legacy_receiver`
|
|
86
|
+
# @param name [String] the verb as the caller typed it, echoed in the `--help` hint
|
|
87
|
+
# @param rest [Array<String>] the `path=value` words after the verb
|
|
88
|
+
# @param program [String] how the caller was invoked, echoed in the `--help` hint
|
|
89
|
+
# @param asking [Boolean] whether the verb was reached through `ask`, which only
|
|
90
|
+
# changes the wording of the hint
|
|
91
|
+
# @return [Array(String, Integer)] status 0 with pretty-printed JSON: the materialized
|
|
92
|
+
# rows for a query; `id`, `state` and event names for a command; `id` and events
|
|
93
|
+
# with payloads for a port operation. Status 1 with the message of a
|
|
94
|
+
# `Runtime::DOMAIN_REFUSALS` error, followed by a `--help` hint when it is a
|
|
95
|
+
# `Runtime::NotFound` or `Runtime::TypeMismatch`
|
|
96
|
+
# @raise [Runtime::WiringError] if a repository, or the clock adapter a `now`
|
|
97
|
+
# argument needs, cannot be resolved
|
|
98
|
+
# @raise [Runtime::StaleWrite] if concurrent writers beat the command through every
|
|
99
|
+
# retry
|
|
59
100
|
def dispatch(runtime, spec, name, rest, program, asking)
|
|
60
101
|
args = stamp_time(runtime, spec, CliDoor.arguments(spec, rest))
|
|
61
102
|
|
|
@@ -64,34 +105,36 @@ module Hecks
|
|
|
64
105
|
return [JSON.pretty_generate(rows.map { |row| JsonDoor.materialize(row) }), 0]
|
|
65
106
|
end
|
|
66
107
|
|
|
67
|
-
#
|
|
108
|
+
# The answer is scoped to what was asked. `bin/run`'s step-list form
|
|
68
109
|
# reports the whole store because a corpus run is judged on all of it;
|
|
69
110
|
# somebody who issued one verb wants that verb's outcome, and against a
|
|
70
111
|
# Postgres-backed domain the full dump is every record there has been.
|
|
71
112
|
request = CommandRequest.normalize(args, receiver: spec[:receiver],
|
|
72
113
|
legacy_receiver: spec[:legacy_receiver])
|
|
73
|
-
handle = runtime.
|
|
114
|
+
handle = runtime.dispatch_flat(spec[:verb], request)
|
|
74
115
|
return [JSON.pretty_generate(answered(handle)), 0] if handle.state.nil?
|
|
75
116
|
|
|
76
117
|
[JSON.pretty_generate(id: handle.id,
|
|
77
118
|
state: JsonDoor.materialize(handle.state),
|
|
78
119
|
events: handle.events.map(&:name)), 0]
|
|
79
120
|
rescue Runtime::NotFound, Runtime::TypeMismatch => e
|
|
80
|
-
# A
|
|
121
|
+
# A bad argument and a missing record both land here, and both want the
|
|
81
122
|
# same next step: read what the verb actually takes.
|
|
82
123
|
["#{e.message}\n\n #{program} #{'ask ' if asking}#{name} --help", 1]
|
|
83
124
|
rescue *Runtime::DOMAIN_REFUSALS => e
|
|
84
|
-
#
|
|
125
|
+
# **The refusal is the product** — the chapter's own sentence, verbatim.
|
|
85
126
|
[e.message, 1]
|
|
86
127
|
end
|
|
87
128
|
|
|
88
|
-
#
|
|
129
|
+
# Supplies the current time for a verb that takes a `now` argument the caller left out.
|
|
130
|
+
#
|
|
131
|
+
# The clock, filled in at the door.
|
|
89
132
|
#
|
|
90
133
|
# A staleness rule needs the time, and the sublanguage cannot ask for it
|
|
91
134
|
# — a `given` that read the clock would judge the same record differently
|
|
92
135
|
# on two runs, and every replay, audit and fuzz oracle here assumes it
|
|
93
|
-
# does not. So `now` stays an
|
|
94
|
-
# the question becomes who types it.
|
|
136
|
+
# does not. So `now` stays an argument the predicate merely reads, and
|
|
137
|
+
# the question becomes who types it. Left to the caller, that is:
|
|
95
138
|
#
|
|
96
139
|
# qa/quality_control target.claim id=QC held_by.value=me \
|
|
97
140
|
# now.value=$(date +%s) window.value=900
|
|
@@ -99,20 +142,20 @@ module Hecks
|
|
|
99
142
|
# which is a shell incantation in front of every claim, and one an agent
|
|
100
143
|
# gets wrong by pasting a stale number.
|
|
101
144
|
#
|
|
102
|
-
#
|
|
145
|
+
# At the door, not in the runtime, and the distinction is load-bearing.
|
|
103
146
|
# `Ports::IdentityGeneration` reasons the same question through for a
|
|
104
147
|
# minted uuid and lands on "the value is baked into the caller's args at
|
|
105
|
-
# the first live dispatch". A clock consulted
|
|
148
|
+
# the first live dispatch". A clock consulted inside the interpreter
|
|
106
149
|
# would not have that property — a recorded corpus step replayed
|
|
107
150
|
# tomorrow would quietly get tomorrow's time, and the fuzzer's oracle and
|
|
108
151
|
# the adapter-agreement gate both compare runs of exactly that shape. Here
|
|
109
152
|
# it fills only what a person or an agent is typing, and `runtime.dispatch`
|
|
110
153
|
# is left alone.
|
|
111
154
|
#
|
|
112
|
-
#
|
|
155
|
+
# An explicit value always wins, so a spec or a caller reproducing a
|
|
113
156
|
# moment says so and is believed. This only supplies what was omitted.
|
|
114
157
|
#
|
|
115
|
-
#
|
|
158
|
+
# By name, which is the one uncomfortable part. `now` is a plausible
|
|
116
159
|
# domain word and nothing declares that it means the clock. It is
|
|
117
160
|
# tolerable because this is a convenience layer rather than semantics —
|
|
118
161
|
# the verb's own help says the argument exists, dispatch is unchanged,
|
|
@@ -120,6 +163,17 @@ module Hecks
|
|
|
120
163
|
# declaration in the chapter (`attribute :now, Instant, from: :clock`),
|
|
121
164
|
# which is a language change: DSL, IR, the self-hosted grammar and its
|
|
122
165
|
# goldens. Worth doing; not worth smuggling in here.
|
|
166
|
+
#
|
|
167
|
+
# @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain,
|
|
168
|
+
# whose registry names the clock adapter
|
|
169
|
+
# @param spec [Hash{Symbol => Object}] the verb's projected entry; only an option whose
|
|
170
|
+
# `:path` starts `"now."` makes this method act
|
|
171
|
+
# @param args [Hash{Symbol => Object}] the parsed arguments; not mutated
|
|
172
|
+
# @return [Hash{Symbol => Object}] `args` itself when the verb takes no `now` or the
|
|
173
|
+
# caller gave one; otherwise a copy with `now: { value: seconds }`, the clock port's
|
|
174
|
+
# current time in whole seconds
|
|
175
|
+
# @raise [Runtime::WiringError] if the time is needed and zero or several adapters
|
|
176
|
+
# implement the clock port
|
|
123
177
|
def stamp_time(runtime, spec, args)
|
|
124
178
|
return args unless spec[:arguments].any? { |argument| argument[:path].start_with?("now.") }
|
|
125
179
|
return args if args.key?(:now)
|
|
@@ -127,12 +181,15 @@ module Hecks
|
|
|
127
181
|
args.merge(now: { value: Ports::Clock.now(runtime.registry) })
|
|
128
182
|
end
|
|
129
183
|
|
|
130
|
-
#
|
|
184
|
+
# Shapes a port operation's outcome for printing: the record asked about, and each
|
|
185
|
+
# event with its full payload.
|
|
186
|
+
#
|
|
187
|
+
# A port operation has no state, and its payload is the entire point.
|
|
131
188
|
#
|
|
132
189
|
# A command answers with the record it changed, so naming the events is
|
|
133
190
|
# enough — the interesting part is in `state`. A port operation changes
|
|
134
191
|
# no record: it asked something outside and came back with what was
|
|
135
|
-
# said, and that lives
|
|
192
|
+
# said, and that lives only in the event payload. Reporting names alone
|
|
136
193
|
# would print `SpecsCompleted` and drop the spec output on the floor.
|
|
137
194
|
#
|
|
138
195
|
# This is what makes a projected CLI usable as somebody's only door. An
|
|
@@ -140,13 +197,19 @@ module Hecks
|
|
|
140
197
|
# it asks the port and reads the answer, and if the answer is a bare
|
|
141
198
|
# event name then the door leads nowhere and it needs a shell after all.
|
|
142
199
|
#
|
|
143
|
-
#
|
|
200
|
+
# Both endings come back the same way, and the status stays 0 for both.
|
|
144
201
|
# A refusal here is not a misuse — `IssueStillOpen` and `SuiteFailed`
|
|
145
202
|
# are answers the caller asked for, correctly delivered. Exit 1 is for
|
|
146
203
|
# "you typed something wrong", and conflating the two would have a
|
|
147
204
|
# scripted agent treat a healthy no as a broken call.
|
|
205
|
+
#
|
|
206
|
+
# @param handle [Runtime::Dispatcher::Result, Runtime::RemoteDispatcher::Result] the
|
|
207
|
+
# dispatch result, which for a port operation has a nil `state`
|
|
208
|
+
# @return [Hash{Symbol => Object}] `:id`, the result's id or else the first event's
|
|
209
|
+
# (nil when there is neither), and `:events`, an Array of `{ name:, payload: }`
|
|
210
|
+
# Hashes with each payload materialized to plain data
|
|
148
211
|
def answered(handle)
|
|
149
|
-
#
|
|
212
|
+
# The ID comes off the event, because a port operation hydrates no
|
|
150
213
|
# instance and the handle's own `id` is nil by design. The event knows
|
|
151
214
|
# which record was asked about — it was stamped with it — and printing
|
|
152
215
|
# `null` beside a payload that plainly says `SW-TOOL` would read as a
|
|
@@ -157,15 +220,27 @@ module Hecks
|
|
|
157
220
|
end }
|
|
158
221
|
end
|
|
159
222
|
|
|
160
|
-
#
|
|
223
|
+
# Words the answer to a verb or question that does not exist, suggesting up to five
|
|
224
|
+
# names that start the same way.
|
|
225
|
+
#
|
|
226
|
+
# A near miss is worth more than a list. Somebody who typed
|
|
161
227
|
# `bug.discovr` wants one line, not eighty-seven of them.
|
|
162
228
|
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
229
|
+
# Ranked by shared prefix, not by substring. Substring matching finds
|
|
230
|
+
# nothing for the commonest typo of all — a dropped
|
|
165
231
|
# letter, `order.create_piza`, which is a substring of nothing. Prefix
|
|
166
232
|
# length survives an error anywhere after it, which is where errors are.
|
|
233
|
+
#
|
|
234
|
+
# @param cli [Hash{Symbol => Object}] the `Projector::CliProjector` projection; read
|
|
235
|
+
# for `cli[:names]`, the alias maps under `:command` and `:question`
|
|
236
|
+
# @param name [String] what the caller typed
|
|
237
|
+
# @param asking [Boolean] whether it was typed after `ask`, which picks the question
|
|
238
|
+
# names and the wording
|
|
239
|
+
# @param program [String] how the caller was invoked, echoed in the closing hint
|
|
240
|
+
# @return [String] a multi-line message; candidates must share a prefix of at least
|
|
241
|
+
# half of `name`'s length, and never fewer than three characters
|
|
167
242
|
def unknown(cli, name, asking, program)
|
|
168
|
-
#
|
|
243
|
+
# **Both spellings are candidates**. A caller who typed the qualified
|
|
169
244
|
# form with a typo — `order.create_piza` — shares no prefix with the
|
|
170
245
|
# short name `create_pizza`, so pooling only one of them suggests
|
|
171
246
|
# nothing for half the mistakes anybody makes.
|
|
@@ -181,6 +256,12 @@ module Hecks
|
|
|
181
256
|
lines.join("\n")
|
|
182
257
|
end
|
|
183
258
|
|
|
259
|
+
# Counts how many leading characters two names have in common.
|
|
260
|
+
#
|
|
261
|
+
# @param one [String] a candidate name
|
|
262
|
+
# @param other [String] the name to compare it with
|
|
263
|
+
# @return [Integer] the length of the common prefix, from 0 up to the shorter
|
|
264
|
+
# name's length
|
|
184
265
|
def shared_prefix(one, other)
|
|
185
266
|
length = [one.length, other.length].min
|
|
186
267
|
(0...length).find { |index| one[index] != other[index] } || length
|
|
@@ -16,6 +16,29 @@ module Hecks
|
|
|
16
16
|
module CommandRequest
|
|
17
17
|
module_function
|
|
18
18
|
|
|
19
|
+
# Splits one external request into routing (`to:`) and facts (`with:`), accepting
|
|
20
|
+
# either the explicit envelope or a flat Hash, and refuses a receiver that does
|
|
21
|
+
# not fit the kind of command being called.
|
|
22
|
+
#
|
|
23
|
+
# Keys are symbolized at every depth first, so a parsed JSON body and a Ruby Hash
|
|
24
|
+
# are treated alike. In a flat Hash everything but `to` (or the legacy receiver
|
|
25
|
+
# keys) is a fact.
|
|
26
|
+
#
|
|
27
|
+
# @param input [Hash, Object] the request, with String or Symbol keys; anything
|
|
28
|
+
# that is not a Hash is refused
|
|
29
|
+
# @param receiver [Symbol, nil] the kind of receiver the command takes:
|
|
30
|
+
# `:aggregate` (an identity), `:entity` (a Hash of `aggregate:` and `entity:`
|
|
31
|
+
# identities), or `nil` for a command that takes none, such as a creating command
|
|
32
|
+
# @param legacy_receiver [Symbol, String, Hash{Symbol => Symbol, String}, nil] where
|
|
33
|
+
# a flat request without `to` may carry its receiver instead: one key name (such
|
|
34
|
+
# as `:id`) for an `:aggregate` receiver, or `{ aggregate: key, entity: key }` for
|
|
35
|
+
# an `:entity` receiver; `nil` accepts no legacy spelling
|
|
36
|
+
# @return [Hash{Symbol => Object}] `{ with: facts }`, plus `to:` holding the route
|
|
37
|
+
# whenever `receiver` is not `nil`; ready to pass to `Dispatcher#dispatch_flat`
|
|
38
|
+
# @raise [Runtime::TypeMismatch] if `input` or its `with:` is not a Hash, if an
|
|
39
|
+
# explicit envelope carries keys other than `to:` and `with:`, or if the route
|
|
40
|
+
# is missing, blank, malformed, or given to a command that takes no receiver
|
|
41
|
+
# @raise [ArgumentError] if `receiver` is not `nil`, `:aggregate` or `:entity`
|
|
19
42
|
def normalize(input, receiver:, legacy_receiver: nil)
|
|
20
43
|
request = symbolize(input)
|
|
21
44
|
raise Runtime::TypeMismatch, "a command request must be a hash" unless request.is_a?(Hash)
|