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
data/lib/hecks/projector.rb
CHANGED
|
@@ -13,28 +13,31 @@ module Hecks
|
|
|
13
13
|
# working, golden-tested example of the shape every future projector
|
|
14
14
|
# (`:rust`, `:ul`, `:openid`, ...) is meant to follow.
|
|
15
15
|
#
|
|
16
|
-
# The unit is
|
|
16
|
+
# The unit is one bluebook's IR, not a whole booted registry — matching
|
|
17
17
|
# every real projection target (Rust/UL/OIDC all project one domain at
|
|
18
18
|
# a time), and deliberately narrower than `Exporter.call`'s own
|
|
19
19
|
# multi-domain shape.
|
|
20
|
-
# THREE KINDS OF "PROJECT", TOLD APART BY WHAT THEY NEED AS INPUT.
|
|
21
|
-
# Only the first belongs in this registry.
|
|
22
20
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
21
|
+
# ## Three kinds of "project"
|
|
22
|
+
#
|
|
23
|
+
# Told apart by what they need as input. Only the first belongs in this
|
|
24
|
+
# registry.
|
|
25
|
+
#
|
|
26
|
+
# a projection takes a chapter's declaration and answers something
|
|
27
|
+
# that describes the domain: its IR, its storage shape, an OIDC scope
|
|
25
28
|
# manifest, the parser's keyword table, the reference pages. Inert,
|
|
26
29
|
# derived, and runnable against any chapter that carries what it
|
|
27
30
|
# declares it needs. These are what `register` holds.
|
|
28
31
|
#
|
|
29
|
-
#
|
|
30
|
-
# something that
|
|
32
|
+
# an export takes a declaration and its bindings and answers
|
|
33
|
+
# something that is the domain, running elsewhere — rust/project.rb's
|
|
31
34
|
# generated crate, the WASM artifact, the SAM template
|
|
32
35
|
# bin/project_deploy renders. It needs the `.world`/`.hecksagon` a
|
|
33
36
|
# projection never looks at, because a running system has to know how
|
|
34
37
|
# it is wired. That is the whole reason bin/project_deploy cannot use
|
|
35
38
|
# this protocol: `call(bluebook:, options:)` has no channel for it.
|
|
36
39
|
#
|
|
37
|
-
#
|
|
40
|
+
# a state projection takes records — a domain after dispatch — and is
|
|
38
41
|
# a read-model question wearing the same word.
|
|
39
42
|
# `bin/expression_projection` is the one of these: its operators are
|
|
40
43
|
# not declared anywhere, they are what exists after
|
|
@@ -42,8 +45,10 @@ module Hecks
|
|
|
42
45
|
# dispatches. Converting it into this registry would be a category
|
|
43
46
|
# error, however much its name suggests otherwise.
|
|
44
47
|
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
48
|
+
# ## One word, three other meanings
|
|
49
|
+
#
|
|
50
|
+
# Worth naming too, because grepping "projection" turns all of these up
|
|
51
|
+
# and none is the above:
|
|
47
52
|
#
|
|
48
53
|
# Ports::Projection read-model catch-up, events folded into state
|
|
49
54
|
# bin/project forces that catch-up by hand
|
|
@@ -61,14 +66,33 @@ module Hecks
|
|
|
61
66
|
# deliberately unguarded: a spec re-registering a stub under the same
|
|
62
67
|
# name between examples is the ordinary case, not a footgun to fence
|
|
63
68
|
# against.
|
|
69
|
+
# Adds `projector` to the registry under `name`, replacing anything
|
|
70
|
+
# already registered there.
|
|
71
|
+
#
|
|
72
|
+
# @param name [String, Symbol] the key `projector` is looked up by (converted to a symbol)
|
|
73
|
+
# @param projector [Module, Class, #call] anything answering `call(bluebook:, options:)`
|
|
74
|
+
# @return [void]
|
|
64
75
|
def register(name, projector)
|
|
65
76
|
registry[name.to_sym] = projector
|
|
66
77
|
end
|
|
67
78
|
|
|
79
|
+
# Runs the projector registered under `name` against `bluebook`,
|
|
80
|
+
# after refusing a construct it does not admit.
|
|
81
|
+
#
|
|
68
82
|
# `bluebook:` is kept as the keyword because it is the shipped
|
|
69
83
|
# spelling and every existing caller uses it — but what it accepts is
|
|
70
84
|
# any construct that emits IR, and `admits!` is what decides whether
|
|
71
|
-
#
|
|
85
|
+
# this target can actually take the one handed over.
|
|
86
|
+
#
|
|
87
|
+
# @param name [String, Symbol] the registered projector's key
|
|
88
|
+
# @param bluebook [Bluebook::Behaviour::Chapter, Hecks::IR] the chapter or
|
|
89
|
+
# IR-emitting construct to project
|
|
90
|
+
# @param options [Hash] projector-specific options, passed through unchanged
|
|
91
|
+
# @return [Object] whatever the projector's own `call` returns: typically a
|
|
92
|
+
# `Hash`/`String` artifact, or a `Hash{String => String}` file tree
|
|
93
|
+
# @raise [UnknownProjector] if no projector is registered under `name`
|
|
94
|
+
# @raise [WrongConstruct] if `bluebook` lacks a capability or aggregate the
|
|
95
|
+
# projector requires
|
|
72
96
|
def call(name, bluebook:, options: {})
|
|
73
97
|
projector = registry.fetch(name.to_sym) do
|
|
74
98
|
raise UnknownProjector, "no projector registered for #{name.inspect} — registered: #{registered.sort.inspect}"
|
|
@@ -77,16 +101,27 @@ module Hecks
|
|
|
77
101
|
projector.call(bluebook: bluebook, options: options)
|
|
78
102
|
end
|
|
79
103
|
|
|
80
|
-
#
|
|
81
|
-
#
|
|
104
|
+
# Refuses `construct` if it lacks a capability or declared aggregate
|
|
105
|
+
# `projector` requires. A projection names the capabilities it needs;
|
|
106
|
+
# this is what enforces that, before the projector runs.
|
|
82
107
|
#
|
|
83
|
-
#
|
|
108
|
+
# One check covers both shapes. An ordinary construct includes its
|
|
84
109
|
# capabilities and a class-shaped one — Command, Entity, ValueObject
|
|
85
|
-
# —
|
|
110
|
+
# — extends them, and `is_a?` consults the singleton chain, so it
|
|
86
111
|
# answers for an extended module as readily as an included one. This
|
|
87
112
|
# started as two checks on the assumption it would not; a spec
|
|
88
113
|
# asserting the assumption failed, which is the only reason the
|
|
89
114
|
# redundant half was noticed.
|
|
115
|
+
#
|
|
116
|
+
# @param name [String, Symbol] the projector's registered key, used in the message
|
|
117
|
+
# when refusing
|
|
118
|
+
# @param projector [Module, Class, #call] the target being checked; consulted for
|
|
119
|
+
# `projection_requires` and `projection_declares` when it answers them
|
|
120
|
+
# @param construct [Bluebook::Behaviour::Chapter, Hecks::IR] the chapter or
|
|
121
|
+
# IR-emitting construct offered to the projector
|
|
122
|
+
# @return [void]
|
|
123
|
+
# @raise [WrongConstruct] if `construct` lacks a required capability, or the
|
|
124
|
+
# chapter it is declares no aggregate the projector needs
|
|
90
125
|
def admits!(name, projector, construct)
|
|
91
126
|
needed = projector.respond_to?(:projection_requires) ? projector.projection_requires : []
|
|
92
127
|
missing = needed.reject { |capability| capable?(construct, capability) }
|
|
@@ -105,18 +140,38 @@ module Hecks
|
|
|
105
140
|
"#{construct.name} declares no such aggregate."
|
|
106
141
|
end
|
|
107
142
|
|
|
143
|
+
# Tells whether `construct` has the capability `admits!` requires of it.
|
|
144
|
+
#
|
|
145
|
+
# @param construct [Bluebook::Behaviour::Chapter, Hecks::IR] the construct to check
|
|
146
|
+
# @param capability [Module] the capability module to check for
|
|
147
|
+
# @return [Boolean] true if `construct` is a `capability`
|
|
108
148
|
def capable?(construct, capability) = construct.is_a?(capability)
|
|
109
149
|
|
|
110
|
-
# What
|
|
150
|
+
# What kind of artifact a registered target emits — asked of the
|
|
111
151
|
# projection rather than inferred from what it returned.
|
|
152
|
+
#
|
|
153
|
+
# @param name [String, Symbol] the registered projector's key
|
|
154
|
+
# @return [Symbol] `:files` for a path => contents tree, `:artifact` (the
|
|
155
|
+
# default, including for an unregistered `name`) for a single Hash or String
|
|
112
156
|
def emits_for(name)
|
|
113
157
|
projector = registry.fetch(name.to_sym) { return :artifact }
|
|
114
158
|
projector.respond_to?(:projection_emits) ? projector.projection_emits : :artifact
|
|
115
159
|
end
|
|
116
160
|
|
|
161
|
+
# Tells whether a projector is registered under `name`.
|
|
162
|
+
#
|
|
163
|
+
# @param name [String, Symbol] the key to look up
|
|
164
|
+
# @return [Boolean] true if a projector is registered under `name`
|
|
117
165
|
def registered?(name) = registry.key?(name.to_sym)
|
|
166
|
+
|
|
167
|
+
# Lists every key currently registered.
|
|
168
|
+
#
|
|
169
|
+
# @return [Array<Symbol>] every key currently registered
|
|
118
170
|
def registered = registry.keys
|
|
119
171
|
|
|
172
|
+
# Gives the live registry, initializing it on first use.
|
|
173
|
+
#
|
|
174
|
+
# @return [Hash{Symbol => Module, Class, #call}] the live key => projector registry
|
|
120
175
|
def registry
|
|
121
176
|
@registry ||= {}
|
|
122
177
|
end
|
|
@@ -126,13 +181,17 @@ module Hecks
|
|
|
126
181
|
# (`:oidc`). Both resolve here, so the constant form is added
|
|
127
182
|
# surface rather than a replacement — every `Projector.call(:ir, ...)`
|
|
128
183
|
# written before this existed keeps working untouched.
|
|
184
|
+
#
|
|
185
|
+
# @param target [String, Symbol, #projection_key] a registered key, or a target
|
|
186
|
+
# that declared its own key with `Target#projects_as`
|
|
187
|
+
# @return [String, Symbol] the key to call the target under
|
|
129
188
|
def key_for(target)
|
|
130
189
|
return target.projection_key if target.respond_to?(:projection_key) && target.projection_key
|
|
131
190
|
|
|
132
191
|
target
|
|
133
192
|
end
|
|
134
193
|
|
|
135
|
-
#
|
|
194
|
+
# Writing is the caller's choice, not the projector's. A projector
|
|
136
195
|
# returns an artifact and never touches disk, which is what lets
|
|
137
196
|
# spec/projector_spec.rb compare `:ir`'s output against a golden
|
|
138
197
|
# fixture without a tmpdir. `out:` is the only thing that writes.
|
|
@@ -141,6 +200,11 @@ module Hecks
|
|
|
141
200
|
# than from inspecting the artifact. A Hash of path => contents and a
|
|
142
201
|
# Hash that simply happens to hold strings are the same object to
|
|
143
202
|
# Ruby; only the projection knows which it meant.
|
|
203
|
+
#
|
|
204
|
+
# @param artifact [Hash, String] the projector's output; a file tree when `as: :files`
|
|
205
|
+
# @param out [String] the path to write to; a directory when `as: :files`
|
|
206
|
+
# @param as [Symbol] `:files` to write a tree, anything else to write one file
|
|
207
|
+
# @return [String, Array<String>] the path written, or every path written when `as: :files`
|
|
144
208
|
def write(artifact, out, as: :artifact)
|
|
145
209
|
return write_tree(artifact, out) if as == :files
|
|
146
210
|
|
|
@@ -150,6 +214,10 @@ module Hecks
|
|
|
150
214
|
|
|
151
215
|
# Answers the paths written, in the order given — so a caller can
|
|
152
216
|
# report what happened without re-deriving it from the tree.
|
|
217
|
+
#
|
|
218
|
+
# @param files [Hash{String => String}] relative path => contents
|
|
219
|
+
# @param directory [String] the directory to write the tree under
|
|
220
|
+
# @return [Array<String>] each file's full written path, in `files`' order
|
|
153
221
|
def write_tree(files, directory)
|
|
154
222
|
require "fileutils"
|
|
155
223
|
files.map do |relative, contents|
|
|
@@ -178,7 +246,7 @@ Hecks::Projector.register(:docs, Hecks::Projector::DocsProjector)
|
|
|
178
246
|
Hecks::Projector.register(:narrate, Hecks::Projector::NarrateProjector)
|
|
179
247
|
Hecks::Projector.register(:cli, Hecks::Projector::CliProjector)
|
|
180
248
|
|
|
181
|
-
# The
|
|
249
|
+
# The targets are required from lib/hecks.rb, immediately after this
|
|
182
250
|
# file — deliberately not from here. A target requires this file (it
|
|
183
251
|
# needs `Target` and the registry), so requiring them back from here
|
|
184
252
|
# would close a genuine `circular require considered harmful` loop. Ruby
|
data/lib/hecks/query_ir.rb
CHANGED
|
@@ -4,7 +4,7 @@ require_relative "projections/model"
|
|
|
4
4
|
require_relative "fuzzing/properties"
|
|
5
5
|
|
|
6
6
|
module Hecks
|
|
7
|
-
#
|
|
7
|
+
# The shared core behind `bin/query_ir` (a text CLI) and
|
|
8
8
|
# `bin/hecks_query_ir_mcp` (an MCP server exposing the same two
|
|
9
9
|
# queries as tools) — one implementation, two front ends, the same
|
|
10
10
|
# reason `Hecks::Codemod` exists once rather than per-script.
|
|
@@ -14,7 +14,7 @@ module Hecks
|
|
|
14
14
|
Codemod = Hecks::Codemod
|
|
15
15
|
Deviations = Hecks::Projections::Model::Deviations
|
|
16
16
|
|
|
17
|
-
#
|
|
17
|
+
# The same mapping spec/model_shape_conformance_spec.rb's own
|
|
18
18
|
# MODEL_CONSTRUCTS holds — kept here rather than shared from the
|
|
19
19
|
# spec (a spec file is not a library other code should require),
|
|
20
20
|
# matching Deviations' own doc comment: "the generator and the gate
|
|
@@ -37,6 +37,11 @@ module Hecks
|
|
|
37
37
|
|
|
38
38
|
module_function
|
|
39
39
|
|
|
40
|
+
# Reads the fields the language declares for one construct kind, from the grammar itself.
|
|
41
|
+
#
|
|
42
|
+
# @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
|
|
43
|
+
# @return [Array<Symbol>] the attribute names the self-hosted meta-domain
|
|
44
|
+
# grammar declares for `name`
|
|
40
45
|
def meta_declared(name)
|
|
41
46
|
Hecks::Bluebook::MetaValidator.grammar_registry
|
|
42
47
|
.bluebook("Bluebook").aggregate(name).attributes.map(&:name)
|
|
@@ -44,9 +49,14 @@ module Hecks
|
|
|
44
49
|
|
|
45
50
|
# The real structural diff between what a Ruby IR class emits
|
|
46
51
|
# (Class.ir_spec.keys) and what the self-hosted meta-domain declares
|
|
47
|
-
# for it — the
|
|
52
|
+
# for it — the same comparison spec/model_shape_conformance_spec.rb
|
|
48
53
|
# makes, reusing its own Deviations data so this can never silently
|
|
49
54
|
# drift from what that gate actually checks.
|
|
55
|
+
#
|
|
56
|
+
# @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
|
|
57
|
+
# @return [Hash{Symbol => Object}] `:name`, `:declared`, `:emitted`, plus
|
|
58
|
+
# `:missing_from_ruby` and `:unaccounted_in_ruby` — each an `Array<Symbol>`
|
|
59
|
+
# @raise [ArgumentError] if `name` is not a `CONSTRUCTS` key
|
|
50
60
|
def construct_diff(name)
|
|
51
61
|
klass = CONSTRUCTS.fetch(name) do
|
|
52
62
|
raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}"
|
|
@@ -54,8 +64,8 @@ module Hecks
|
|
|
54
64
|
declared = meta_declared(name)
|
|
55
65
|
emitted = klass.ir_spec.keys
|
|
56
66
|
|
|
57
|
-
accounted = declared.reject { |field| Deviations
|
|
58
|
-
Deviations
|
|
67
|
+
accounted = declared.reject { |field| Deviations.parent_ref?(field) } -
|
|
68
|
+
Deviations.judge_only(name) -
|
|
59
69
|
Deviations.folded(name).values.flatten -
|
|
60
70
|
Deviations.off_the_wire(name) -
|
|
61
71
|
Deviations.dynamic_tail(name) -
|
|
@@ -72,30 +82,38 @@ module Hecks
|
|
|
72
82
|
missing_from_ruby: accounted - emitted, unaccounted_in_ruby: unaccounted }
|
|
73
83
|
end
|
|
74
84
|
|
|
85
|
+
# Diffs one or every construct kind at once.
|
|
86
|
+
#
|
|
87
|
+
# @param names [Array<String>] `CONSTRUCTS` keys to diff, every construct when empty
|
|
88
|
+
# @return [Array<Hash>] one `construct_diff` result per name
|
|
75
89
|
def constructs(names = [])
|
|
76
90
|
targets = names.empty? ? CONSTRUCTS.keys : names
|
|
77
91
|
targets.map { |name| construct_diff(name) }
|
|
78
92
|
end
|
|
79
93
|
|
|
80
|
-
# Every given/ensures/invariant
|
|
94
|
+
# Every given/ensures/invariant declaration reachable from a booted
|
|
81
95
|
# registry, walked recursively — every owner's own `.preconditions`/
|
|
82
96
|
# `.invariants` (block-declared rules), every value object's own
|
|
83
97
|
# `.invariants`, and every command's own `.givens`/`.ensures`
|
|
84
|
-
# (`.givens` too, not just `.ensures` — a command's own
|
|
98
|
+
# (`.givens` too, not just `.ensures` — a command's own local
|
|
85
99
|
# `given("x") { block }` not yet hoisted to its owner, round 4's own
|
|
86
100
|
# starting shape, would otherwise be invisible).
|
|
87
101
|
#
|
|
88
|
-
#
|
|
102
|
+
# Not keyed by object identity (a real, hard-won correction — see
|
|
89
103
|
# the comment on `duplicates`' own dedup below for why: a bare
|
|
90
104
|
# `given("x")` reference and its owner's own block declaration are
|
|
91
|
-
# the
|
|
92
|
-
# (S14 — every bluebook is judged by dispatching its
|
|
105
|
+
# the same Ruby object at DSL build time, but `MetaValidator.call`
|
|
106
|
+
# (S14 — every bluebook is judged by dispatching its own IR into the
|
|
93
107
|
# self-hosted grammar, then reconstructed via `Assembly.call` from
|
|
94
108
|
# flat rows) rebuilds the whole graph fresh from there. By the time
|
|
95
|
-
# any caller reads `chapter.aggregates`,
|
|
109
|
+
# any caller reads `chapter.aggregates`, every given/invariant is
|
|
96
110
|
# already a distinct object, whether it was block-declared or
|
|
97
111
|
# bare-referenced — object identity carries no signal past that
|
|
98
112
|
# point, for any construct, not just this one).
|
|
113
|
+
#
|
|
114
|
+
# @param registry [Runtime::Registry] the booted registry to walk
|
|
115
|
+
# @param chapter_name [String, nil] one chapter to walk, every booted chapter when nil
|
|
116
|
+
# @return [Array<Rule>]
|
|
99
117
|
def collect_rules(registry, chapter_name = nil)
|
|
100
118
|
rules = []
|
|
101
119
|
chapters = chapter_name ? [registry.bluebook(chapter_name)] : registry.bluebooks.values
|
|
@@ -114,7 +132,7 @@ module Hecks
|
|
|
114
132
|
rules
|
|
115
133
|
end
|
|
116
134
|
|
|
117
|
-
#
|
|
135
|
+
# The recursive walk `collect_rules` drives — pulled out of that method
|
|
118
136
|
# (pure extraction, identical traversal and Rule shapes) as its own
|
|
119
137
|
# named, self-recursive method rather than a lambda closing over the
|
|
120
138
|
# same locals. `rules` is the one piece of state every call shares —
|
|
@@ -147,17 +165,21 @@ module Hecks
|
|
|
147
165
|
end
|
|
148
166
|
private_class_method :walk_construct_rules
|
|
149
167
|
|
|
150
|
-
# A rule's
|
|
168
|
+
# A rule's owner — the construct path a "(declared)" location names
|
|
151
169
|
# directly, or (for a command-level `.givens`/`.ensures` entry) the
|
|
152
170
|
# path with its trailing `.CommandName` segment stripped. Two rules
|
|
153
|
-
# sharing an owner are the
|
|
171
|
+
# sharing an owner are the same declaration read twice (an owner's
|
|
154
172
|
# own precondition, and a command under it referencing that
|
|
155
173
|
# precondition by name) — not two independent ones.
|
|
156
174
|
#
|
|
157
|
-
#
|
|
175
|
+
# Public, not a `duplicates`-only internal — `bin/codemod_hoist_
|
|
158
176
|
# local_givens` reads it directly to group `collect_rules`' own
|
|
159
177
|
# output by owner itself, the same reading `duplicates`' own
|
|
160
178
|
# `declaration_count` makes.
|
|
179
|
+
#
|
|
180
|
+
# @param location [String] a `Rule#location`
|
|
181
|
+
# @return [String] the owning construct's path, with `" (declared)"` or a trailing
|
|
182
|
+
# `.CommandName` segment stripped
|
|
161
183
|
def owner_of(location)
|
|
162
184
|
return location.sub(/ \(declared\)\z/, "") if location.end_with?(" (declared)")
|
|
163
185
|
|
|
@@ -167,27 +189,33 @@ module Hecks
|
|
|
167
189
|
# Grouped by (kind, description, canonical), not canonical text
|
|
168
190
|
# alone — a generic one-liner like `!value.to_s.empty?` legitimately
|
|
169
191
|
# recurs dozens of times for unrelated fields; the real signal is
|
|
170
|
-
# the
|
|
192
|
+
# the same rule (same description, same predicate), which is also
|
|
171
193
|
# exactly what the given/invariant reference mechanism itself
|
|
172
194
|
# resolves on.
|
|
173
195
|
#
|
|
174
|
-
#
|
|
196
|
+
# Deduped by owner, not object identity (`collect_rules`' own
|
|
175
197
|
# comment has the full story — identity is gone by the time this
|
|
176
198
|
# reads the registry). Within a group, every command-level rule
|
|
177
|
-
# whose
|
|
199
|
+
# whose owner already has its own "(declared)" entry in the same
|
|
178
200
|
# group is just that declaration read again through a reference —
|
|
179
201
|
# `Account.Open`/`Account.Credit`/etc. all naming `Account`'s own
|
|
180
|
-
# `given("customer is active")` count as Account's
|
|
202
|
+
# `given("customer is active")` count as Account's one declaration,
|
|
181
203
|
# not nine. A command-level rule with no matching owner declaration
|
|
182
|
-
# (a
|
|
204
|
+
# (a local, not-yet-hoisted `given("x") { block }`) counts as its
|
|
183
205
|
# own standalone declaration — two different commands independently
|
|
184
|
-
# writing the identical local predicate
|
|
206
|
+
# writing the identical local predicate is two declarations, a real
|
|
185
207
|
# hoisting opportunity. A group is reported only when it adds up to
|
|
186
|
-
#
|
|
208
|
+
# more than one real declaration this way.
|
|
187
209
|
#
|
|
188
210
|
# `domains: []` means "the self-hosted meta-domain only" — pass real
|
|
189
211
|
# domain directories explicitly to include them, or `nil` (the
|
|
190
212
|
# default) for meta-domain plus every real example.
|
|
213
|
+
#
|
|
214
|
+
# @param domains [Array<String>, nil] domain root directories to scan; every real
|
|
215
|
+
# example (`Codemod::EXAMPLE_ROOTS`) when nil, none beyond the meta-domain when `[]`
|
|
216
|
+
# @param include_meta [Boolean] whether to also scan the self-hosted meta-domain
|
|
217
|
+
# @return [Array<Hash>] one entry per duplicate group: `:kind`, `:description`,
|
|
218
|
+
# `:canonical`, and `:locations` (`Array<String>`)
|
|
191
219
|
def duplicates(domains: nil, include_meta: true)
|
|
192
220
|
domains ||= Codemod::EXAMPLE_ROOTS
|
|
193
221
|
all_rules = []
|
|
@@ -210,48 +238,48 @@ module Hecks
|
|
|
210
238
|
end
|
|
211
239
|
end
|
|
212
240
|
|
|
213
|
-
# `given`
|
|
241
|
+
# `given` only (not `invariant`/`ensures`) also covers cross-entity
|
|
214
242
|
# coverage — one piece's own entity-level declaration, shared with
|
|
215
|
-
# any
|
|
243
|
+
# any other piece nested under the same root aggregate (real corpus
|
|
216
244
|
# this closes: SafeDepositBox's own `Visit`/`KeyIssuance`, two
|
|
217
245
|
# different pieces on one head). A rule owned by a nested entity
|
|
218
246
|
# (its own owner path has more than one segment) is covered when
|
|
219
|
-
#
|
|
220
|
-
# aggregate — not just under its
|
|
247
|
+
# some "(declared)" entry exists anywhere under that same root
|
|
248
|
+
# aggregate — not just under its own exact owner — matching the
|
|
221
249
|
# DSL's own pool, threaded unchanged through an aggregate's whole
|
|
222
250
|
# entity tree (`AggregateBuilder#entity`'s own comment).
|
|
223
251
|
#
|
|
224
|
-
# A
|
|
225
|
-
# close:
|
|
252
|
+
# A known, accepted gap this does not (and structurally cannot)
|
|
253
|
+
# close: chapter-wide given sharing (`AggregateBuilder#given`'s own
|
|
226
254
|
# bare form, `docs/implemented/resolution-rules/chapter-given.md`) — `Account`,
|
|
227
255
|
# `SafeDepositBox`, and `OnboardingCase` each still show as their
|
|
228
|
-
# own "(declared)" owner here even
|
|
256
|
+
# own "(declared)" owner here even after `SafeDepositBox`/
|
|
229
257
|
# `OnboardingCase` were converted to bare chapter-wide references,
|
|
230
|
-
# because a
|
|
231
|
-
# aggregate's `@named_givens` — the
|
|
258
|
+
# because a referenced given still write-throughs into its own
|
|
259
|
+
# aggregate's `@named_givens` — the same reason `collect_rules`'
|
|
232
260
|
# own top comment already gives for why object identity carries no
|
|
233
|
-
# signal past a bluebook's own build: the
|
|
261
|
+
# signal past a bluebook's own build: the exported IR cannot tell
|
|
234
262
|
# "I declared this myself" apart from "I referenced someone else's
|
|
235
263
|
# declaration," because by the time anything reads `chapter.
|
|
236
264
|
# aggregates`, both look identical. Closing this would mean reading
|
|
237
|
-
#
|
|
265
|
+
# source text (bare `given(desc)` vs. block `given(desc) { ... }`),
|
|
238
266
|
# not the built IR this query is deliberately built on — a
|
|
239
267
|
# different, source-level tool, not a fix to this one. Treat a
|
|
240
268
|
# still-flagged group naming multiple aggregates as "verify by
|
|
241
|
-
# hand whether this is
|
|
269
|
+
# hand whether this is already a chapter-wide reference before
|
|
242
270
|
# assuming it's fresh duplication," not as an automatic signal
|
|
243
271
|
# either way.
|
|
244
272
|
#
|
|
245
|
-
#
|
|
273
|
+
# The identical gap, one level down: chapter-wide entity-scoped
|
|
246
274
|
# sharing (`EntityBuilder#given`'s own bare form,
|
|
247
275
|
# `docs/implemented/resolution-rules/chapter-entity-given.md`) hits this same wall for
|
|
248
276
|
# the same structural reason — `SafeDepositBox.Visit` still shows
|
|
249
277
|
# as its own "(declared)" owner here even after becoming a bare
|
|
250
278
|
# reference to `Account.LedgerEntry`'s declaration, because a piece
|
|
251
279
|
# resolving a chapter-wide reference still write-throughs the
|
|
252
|
-
# resolved `Given` into its own `@named_givens` (so
|
|
280
|
+
# resolved `Given` into its own `@named_givens` (so its own
|
|
253
281
|
# commands can read it back locally without a second hop). This is
|
|
254
|
-
# not a
|
|
282
|
+
# not a new limitation this feature introduces — it is the exact
|
|
255
283
|
# same IR-cannot-distinguish-declared-from-referenced fact, one
|
|
256
284
|
# scope wider. `bin/query_ir duplicates` confirms this directly:
|
|
257
285
|
# `Account.LedgerEntry (declared)` and `SafeDepositBox.Visit
|
|
@@ -260,6 +288,9 @@ module Hecks
|
|
|
260
288
|
# case above, before assuming a group naming two pieces under
|
|
261
289
|
# different aggregates is fresh duplication rather than an already-
|
|
262
290
|
# resolved chapter-wide reference.
|
|
291
|
+
#
|
|
292
|
+
# @param rules [Array<Rule>] one duplicate-key group's own rules
|
|
293
|
+
# @return [Integer] how many of `rules` are real, independent declarations
|
|
263
294
|
def declaration_count(rules)
|
|
264
295
|
declared = rules.select { |r| r.location.end_with?(" (declared)") }
|
|
265
296
|
declared_owners = declared.to_set { |r| owner_of(r.location) }
|
|
@@ -278,10 +309,12 @@ module Hecks
|
|
|
278
309
|
end
|
|
279
310
|
private_class_method :declaration_count
|
|
280
311
|
|
|
281
|
-
#
|
|
312
|
+
# Shared text formatting — both `bin/query_ir` (a text CLI) and
|
|
282
313
|
# `bin/hecks_query_ir_mcp` (an MCP tool result, itself a text
|
|
283
314
|
# block) want the identical human-readable rendering; only the
|
|
284
|
-
#
|
|
315
|
+
# outer framing differs (plain stdout vs. a JSON-RPC content array).
|
|
316
|
+
# @param diffs [Array<Hash>] `constructs`' own output
|
|
317
|
+
# @return [String] the human-readable rendering
|
|
285
318
|
def format_constructs(diffs)
|
|
286
319
|
diffs.map do |diff|
|
|
287
320
|
lines = ["== #{diff[:name]} =="]
|
|
@@ -303,31 +336,37 @@ module Hecks
|
|
|
303
336
|
end.join("\n\n")
|
|
304
337
|
end
|
|
305
338
|
|
|
306
|
-
#
|
|
307
|
-
# two `MetaValidator::Reconstruction` methods
|
|
339
|
+
# **One hand-typed construct-name per reconstruction method** — the only
|
|
340
|
+
# two `MetaValidator::Reconstruction` methods not driven generically
|
|
308
341
|
# through `Assembly::Contracts`' own table (its own header explains
|
|
309
342
|
# why: `aggregate(row)`/`entity(row)` predate the table and were
|
|
310
343
|
# never migrated). `impact_preview`'s own touchpoint 4 is checked
|
|
311
|
-
#
|
|
344
|
+
# only for these two — every other construct is read generically, so
|
|
312
345
|
# asking "does Command's own reconstruction method mention this
|
|
313
346
|
# field" is a question with no method to check.
|
|
314
347
|
RECONSTRUCTION_METHODS = { "Aggregate" => :aggregate, "Entity" => :entity }.freeze
|
|
315
348
|
|
|
316
|
-
#
|
|
349
|
+
# The six touchpoints `.claude/skills/bluebook-construct-creator/
|
|
317
350
|
# SKILL.md` walks in prose, checked structurally instead of by hand
|
|
318
|
-
# — for a construct/field pair
|
|
351
|
+
# — for a construct/field pair not yet fully propagated (typically
|
|
319
352
|
# mid-round, deciding what's left), or as a sanity check before the
|
|
320
353
|
# final gate sweep of a round already believed done. Every check
|
|
321
|
-
# here is
|
|
354
|
+
# here is best-effort and advisory, not a gate: a `false` does not
|
|
322
355
|
# always mean "not yet done" (a field can be legitimately exempt —
|
|
323
356
|
# `Deviations`' own named categories, `GUARANTEED_BY_CONSTRUCTION`,
|
|
324
357
|
# or `META_DOMAIN_KNOWN_GAPS`, the last of which lives in
|
|
325
|
-
# spec/fuzzing/meta_domain_coverage_spec.rb, a
|
|
358
|
+
# spec/fuzzing/meta_domain_coverage_spec.rb, a spec file this
|
|
326
359
|
# module deliberately never requires — see `CONSTRUCTS`' own
|
|
327
360
|
# comment on the same principle). Read the touchpoint's own
|
|
328
361
|
# existing gate (`model_shape_conformance_spec.rb`,
|
|
329
362
|
# `assembly_spec.rb`, `meta_domain_coverage_spec.rb`) before trusting
|
|
330
363
|
# a `false` here as a real gap.
|
|
364
|
+
# @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
|
|
365
|
+
# @param field [String, Symbol] the declared field to check propagation for
|
|
366
|
+
# @return [Hash{Symbol => Object}] `:name`, `:field`, and `:touchpoints` — an
|
|
367
|
+
# `Array<Hash>` of `:touchpoint` (String) and `:present` (Boolean, or nil when the
|
|
368
|
+
# touchpoint does not apply to `name`)
|
|
369
|
+
# @raise [ArgumentError] if `name` is not a `CONSTRUCTS` key
|
|
331
370
|
def impact_preview(name, field)
|
|
332
371
|
CONSTRUCTS.fetch(name) { raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}" }
|
|
333
372
|
field = field.to_s
|
|
@@ -365,7 +404,7 @@ module Hecks
|
|
|
365
404
|
# the method's own body ends at the next line indented no deeper
|
|
366
405
|
# than its own `def` — the same boundary Ruby itself uses, read back
|
|
367
406
|
# textually because there is no live AST here, only a file to grep a
|
|
368
|
-
# slice of. `nil` (not `false`) for every
|
|
407
|
+
# slice of. `nil` (not `false`) for every other construct — this
|
|
369
408
|
# touchpoint genuinely does not apply to them (`RECONSTRUCTION_
|
|
370
409
|
# METHODS` only names the two hand-typed methods), and collapsing
|
|
371
410
|
# "does not apply" into "not done" would misreport a construct that
|
|
@@ -402,6 +441,10 @@ module Hecks
|
|
|
402
441
|
end
|
|
403
442
|
private_class_method :rust_mentions?
|
|
404
443
|
|
|
444
|
+
# Renders one field's touchpoint checklist as text.
|
|
445
|
+
#
|
|
446
|
+
# @param preview [Hash] `impact_preview`'s own output
|
|
447
|
+
# @return [String] the human-readable rendering
|
|
405
448
|
def format_impact_preview(preview)
|
|
406
449
|
lines = ["== #{preview[:name]}##{preview[:field]} =="]
|
|
407
450
|
preview[:touchpoints].each do |t|
|
|
@@ -421,6 +464,10 @@ module Hecks
|
|
|
421
464
|
lines.join("\n")
|
|
422
465
|
end
|
|
423
466
|
|
|
467
|
+
# Renders the duplicate-rule groups as text.
|
|
468
|
+
#
|
|
469
|
+
# @param groups [Array<Hash>] `duplicates`' own output
|
|
470
|
+
# @return [String] the human-readable rendering
|
|
424
471
|
def format_duplicates(groups)
|
|
425
472
|
return "no duplicate given/invariant/ensures rule found" if groups.empty?
|
|
426
473
|
|
|
@@ -7,10 +7,16 @@ module Hecks
|
|
|
7
7
|
# vocabulary — and .render_value, the wire-rendering entry point
|
|
8
8
|
# shared by every literal-bearing spec struct.
|
|
9
9
|
module QuerySpecification
|
|
10
|
+
# The vocabulary every query-shaped construct shares — the clause structs
|
|
11
|
+
# (`WhereClause`, `OrderBy`, `LimitSpec`...), the `DSL` mixin that parses
|
|
12
|
+
# them, and the comparison and null rules every engine answers by. It is
|
|
13
|
+
# its own namespace so a plain `Bluebook::Query` and a
|
|
14
|
+
# `ReadModel::Specification` say one thing the same way rather than each
|
|
15
|
+
# carrying a copy.
|
|
10
16
|
module Common
|
|
11
17
|
# `none_in_state`, vendored addition not (yet) upstream hecks
|
|
12
|
-
# (migration plan task 4): a
|
|
13
|
-
# `where ref: { none_in_state: "Claim:held" }` holds true when
|
|
18
|
+
# (migration plan task 4): a cross-aggregate anti-join comparator --
|
|
19
|
+
# `where ref: { none_in_state: "Claim:held" }` holds true when no
|
|
14
20
|
# record in the named aggregate, keyed by this record's own field
|
|
15
21
|
# value, is currently in the named state. plan.bluebook's own
|
|
16
22
|
# description: "a keyed point lookup (HashMap hit), never a scan" --
|
|
@@ -20,10 +26,20 @@ module Hecks
|
|
|
20
26
|
COMPARATORS = Hecks::Vocabulary.symbols("QueryComparator")
|
|
21
27
|
end
|
|
22
28
|
|
|
29
|
+
# Renders a literal captured in a query clause as its self-describing
|
|
30
|
+
# wire spelling, for a spec struct's `to_h`.
|
|
31
|
+
#
|
|
23
32
|
# The specification structs' own name for the one wire spelling — see
|
|
24
|
-
# Hecks::Literal
|
|
33
|
+
# `Hecks::Literal`, which every other `to_h`-bound literal field
|
|
25
34
|
# shares. Kept as a word here because the structs below read better
|
|
26
35
|
# saying what they are doing than naming the module that does it.
|
|
36
|
+
#
|
|
37
|
+
# @param value [nil, Symbol, String, StateRef, Boolean, Integer, Float, Hash, Array] the
|
|
38
|
+
# literal as the bluebook author wrote it; Hash values and Array elements are
|
|
39
|
+
# rendered recursively
|
|
40
|
+
# @return [String] the wire text: a Symbol keeps its colon, a String its double quotes,
|
|
41
|
+
# and a number, a boolean and `nil` are bare
|
|
42
|
+
# @raise [ArgumentError] if `value`, or a value nested in it, is of any other class
|
|
27
43
|
def self.render_value(value) = Literal.render(value)
|
|
28
44
|
end
|
|
29
45
|
end
|