hecks 0.2.0 → 0.3.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 +5 -5
- data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
- data/lib/hecks/adapters/driven/claude_code.rb +127 -0
- data/lib/hecks/adapters/driven/d1.adapter +12 -0
- data/lib/hecks/adapters/driven/d1.rb +403 -0
- data/lib/hecks/adapters/driven/folder.adapter +3 -0
- data/lib/hecks/adapters/driven/folder.rb +199 -0
- data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
- data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
- data/lib/hecks/adapters/driven/governance_authorization.rb +62 -0
- data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
- data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
- data/lib/hecks/adapters/driven/heki.adapter +4 -0
- data/lib/hecks/adapters/driven/heki.rb +148 -0
- data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
- data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
- data/lib/hecks/adapters/driven/lambda.adapter +4 -0
- data/lib/hecks/adapters/driven/lambda.rb +131 -0
- data/lib/hecks/adapters/driven/memory.adapter +3 -0
- data/lib/hecks/adapters/driven/memory.rb +103 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
- data/lib/hecks/adapters/driven/postgres/codec.rb +80 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
- data/lib/hecks/adapters/driven/postgres.adapter +5 -0
- data/lib/hecks/adapters/driven/postgres.rb +391 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
- data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
- data/lib/hecks/adapters/driven/prism.adapter +3 -0
- data/lib/hecks/adapters/driven/prism.rb +80 -0
- data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
- data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
- data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
- data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
- data/lib/hecks/adapters/driven/sqlite.rb +285 -0
- data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +14 -0
- data/lib/hecks/adapters/driven.rb +36 -0
- data/lib/hecks/adapters.rb +6 -0
- data/lib/hecks/behaviors/dsl.rb +96 -0
- data/lib/hecks/behaviors/expectations.rb +280 -0
- data/lib/hecks/behaviors/ir.rb +31 -0
- data/lib/hecks/behaviors/rspec.rb +42 -0
- data/lib/hecks/behaviors/runner.rb +96 -0
- data/lib/hecks/behaviors.rb +25 -0
- data/lib/hecks/bluebook/aggregate.rb +108 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
- data/lib/hecks/bluebook/assembly/build.rb +48 -0
- data/lib/hecks/bluebook/assembly/contract.rb +112 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
- data/lib/hecks/bluebook/assembly/marks.rb +227 -0
- data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
- data/lib/hecks/bluebook/assembly.rb +78 -0
- data/lib/hecks/bluebook/attribute.rb +96 -0
- data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
- data/lib/hecks/bluebook/behaviour/command.rb +116 -0
- data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
- data/lib/hecks/bluebook/behaviour/query.rb +10 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
- data/lib/hecks/bluebook/chapter.rb +68 -0
- data/lib/hecks/bluebook/command.rb +124 -0
- data/lib/hecks/bluebook/domain_port.rb +102 -0
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
- data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
- data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
- data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
- data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
- data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
- data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
- data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
- data/lib/hecks/bluebook/dsl.rb +45 -0
- data/lib/hecks/bluebook/entity.rb +103 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
- data/lib/hecks/bluebook/expression/projection.json +170 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
- data/lib/hecks/bluebook/expression/resolver.rb +630 -0
- data/lib/hecks/bluebook/expression.rb +13 -0
- data/lib/hecks/bluebook/hexagon.rb +60 -0
- data/lib/hecks/bluebook/lifecycle.rb +42 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
- data/lib/hecks/bluebook/meta_validator.rb +452 -0
- data/lib/hecks/bluebook/model_check.rb +318 -0
- data/lib/hecks/bluebook/pattern_subset.rb +120 -0
- data/lib/hecks/bluebook/policy.rb +41 -0
- data/lib/hecks/bluebook/process_manager.rb +86 -0
- data/lib/hecks/bluebook/project_discovery.rb +30 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +82 -0
- data/lib/hecks/bluebook/query.rb +61 -0
- data/lib/hecks/bluebook/read_model.rb +109 -0
- data/lib/hecks/bluebook/reference.rb +74 -0
- data/lib/hecks/bluebook/smoke_test.rb +166 -0
- data/lib/hecks/bluebook/synthesizer.rb +95 -0
- data/lib/hecks/bluebook/translation.rb +92 -0
- data/lib/hecks/bluebook/value_object.rb +58 -0
- data/lib/hecks/bluebook.rb +74 -0
- data/lib/hecks/codemod.rb +342 -0
- data/lib/hecks/construct.rb +71 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
- data/lib/hecks/deploy/oidc.json +18 -0
- data/lib/hecks/doc/reference.rb +389 -0
- data/lib/hecks/embryonaut_bluebook.rb +75 -0
- data/lib/hecks/facade/cli_door.rb +116 -0
- data/lib/hecks/facade/cli_runner.rb +190 -0
- data/lib/hecks/facade/command_request.rb +105 -0
- data/lib/hecks/facade/handle.rb +173 -0
- data/lib/hecks/facade/json_door.rb +154 -0
- data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
- data/lib/hecks/facade/surface/chapter.rb +107 -0
- data/lib/hecks/facade/surface.rb +48 -0
- data/lib/hecks/facade.rb +44 -0
- data/lib/hecks/forms/app.rb +300 -0
- data/lib/hecks/forms/command_form_renderer.rb +113 -0
- data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
- data/lib/hecks/forms/field_renderer.rb +170 -0
- data/lib/hecks/forms/field_shape.rb +232 -0
- data/lib/hecks/forms/html.rb +53 -0
- data/lib/hecks/forms/index_renderer.rb +35 -0
- data/lib/hecks/forms/page.rb +157 -0
- data/lib/hecks/forms/params.rb +132 -0
- data/lib/hecks/forms/query_form_renderer.rb +114 -0
- data/lib/hecks/forms/record_renderer.rb +115 -0
- data/lib/hecks/forms/record_table.rb +63 -0
- data/lib/hecks/forms/reference_options.rb +30 -0
- data/lib/hecks/forms/value_object_shape.rb +46 -0
- data/lib/hecks/forms.rb +54 -0
- data/lib/hecks/fqn.rb +94 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
- data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
- data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
- data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
- data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
- data/lib/hecks/framework/oidc.json +39 -0
- data/lib/hecks/framework.rb +90 -0
- data/lib/hecks/freezer.rb +67 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
- data/lib/hecks/fuzzing/properties.rb +1146 -0
- data/lib/hecks/fuzzing/replay.rb +635 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
- data/lib/hecks/fuzzing/value_generator.rb +177 -0
- data/lib/hecks/fuzzing.rb +12 -0
- data/lib/hecks/grammar/evolve.rb +291 -0
- data/lib/hecks/grammar/expression.bluebook +417 -0
- data/lib/hecks/grammar/expression_operators.json +1152 -0
- data/lib/hecks/grammar/grammar.hecksagon +20 -0
- data/lib/hecks/grammar/oidc.json +69 -0
- data/lib/hecks/grammar/translation.bluebook +203 -0
- data/lib/hecks/grammar.rb +178 -0
- data/lib/hecks/ir.rb +126 -0
- data/lib/hecks/language/adapter.bluebook +116 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
- data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
- data/lib/hecks/language/bluebook/command.bluebook +437 -0
- data/lib/hecks/language/bluebook/entity.bluebook +316 -0
- data/lib/hecks/language/bluebook/policy.bluebook +172 -0
- data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
- data/lib/hecks/language/bluebook/projection.bluebook +267 -0
- data/lib/hecks/language/bluebook/query.bluebook +245 -0
- data/lib/hecks/language/bluebook/shape.bluebook +292 -0
- data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
- data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
- data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
- data/lib/hecks/language/oidc.json +313 -0
- data/lib/hecks/language/port.bluebook +92 -0
- data/lib/hecks/language/translation/translation.bluebook +110 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
- data/lib/hecks/language/world/wiring.bluebook +62 -0
- data/lib/hecks/language/world/world.bluebook +84 -0
- data/lib/hecks/literal.rb +125 -0
- data/lib/hecks/naming.rb +121 -0
- data/lib/hecks/ports/access_control.port +4 -0
- data/lib/hecks/ports/access_control.rb +62 -0
- data/lib/hecks/ports/agent/answers.rb +104 -0
- data/lib/hecks/ports/agent.port +4 -0
- data/lib/hecks/ports/agent.rb +167 -0
- data/lib/hecks/ports/authentication.port +4 -0
- data/lib/hecks/ports/authentication.rb +50 -0
- data/lib/hecks/ports/authorization.port +4 -0
- data/lib/hecks/ports/authorization.rb +59 -0
- data/lib/hecks/ports/clock.port +4 -0
- data/lib/hecks/ports/clock.rb +62 -0
- data/lib/hecks/ports/extraction.port +4 -0
- data/lib/hecks/ports/extraction.rb +37 -0
- data/lib/hecks/ports/identity_assignment.port +4 -0
- data/lib/hecks/ports/identity_assignment.rb +45 -0
- data/lib/hecks/ports/identity_generation.port +4 -0
- data/lib/hecks/ports/identity_generation.rb +49 -0
- data/lib/hecks/ports/identity_resolution.port +4 -0
- data/lib/hecks/ports/identity_resolution.rb +40 -0
- data/lib/hecks/ports/loading.port +4 -0
- data/lib/hecks/ports/loading.rb +13 -0
- data/lib/hecks/ports/persistence/append_only.rb +102 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
- data/lib/hecks/ports/persistence/execution.rb +18 -0
- data/lib/hecks/ports/persistence/lineage.rb +277 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
- data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
- data/lib/hecks/ports/persistence.port +4 -0
- data/lib/hecks/ports/persistence.rb +39 -0
- data/lib/hecks/ports/projection.port +4 -0
- data/lib/hecks/ports/projection.rb +76 -0
- data/lib/hecks/ports/query/in_memory.rb +60 -0
- data/lib/hecks/ports/query/ordering.rb +41 -0
- data/lib/hecks/ports/query.rb +36 -0
- data/lib/hecks/ports.rb +27 -0
- data/lib/hecks/projections/diagrams.rb +413 -0
- data/lib/hecks/projections/ir.rb +18 -0
- data/lib/hecks/projections/model/deviations.rb +98 -0
- data/lib/hecks/projections/model.rb +145 -0
- data/lib/hecks/projections/oidc.rb +110 -0
- data/lib/hecks/projections/parser_table.rb +159 -0
- data/lib/hecks/projections/reference.rb +38 -0
- data/lib/hecks/projections/shape.rb +31 -0
- data/lib/hecks/projections/statements.rb +110 -0
- data/lib/hecks/projections/vocabulary.rb +100 -0
- data/lib/hecks/projections.rb +32 -0
- data/lib/hecks/projector/cli_projector.rb +437 -0
- data/lib/hecks/projector/docs_projector.rb +321 -0
- data/lib/hecks/projector/exporter.rb +141 -0
- data/lib/hecks/projector/ir_projector.rb +18 -0
- data/lib/hecks/projector/narrate_projector.rb +243 -0
- data/lib/hecks/projector/target.rb +97 -0
- data/lib/hecks/projector.rb +186 -0
- data/lib/hecks/query_ir.rb +392 -0
- data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
- data/lib/hecks/query_specification/common/comparators.rb +25 -0
- data/lib/hecks/query_specification/common/comparison.rb +174 -0
- data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
- data/lib/hecks/query_specification/common/dsl.rb +58 -0
- data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
- data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
- data/lib/hecks/query_specification/common/null_policy.rb +80 -0
- data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
- data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
- data/lib/hecks/query_specification/common/options.rb +32 -0
- data/lib/hecks/query_specification/common/order_by.rb +9 -0
- data/lib/hecks/query_specification/common/specification.rb +10 -0
- data/lib/hecks/query_specification/common/where_clause.rb +9 -0
- data/lib/hecks/query_specification/field_path.rb +89 -0
- data/lib/hecks/query_specification/hop_path.rb +130 -0
- data/lib/hecks/query_specification/read_model/specification.rb +18 -0
- data/lib/hecks/query_specification.rb +14 -0
- data/lib/hecks/rendering.rb +48 -0
- data/lib/hecks/router/namespace_installer.rb +157 -0
- data/lib/hecks/router.rb +70 -0
- data/lib/hecks/runtime/caller.rb +50 -0
- data/lib/hecks/runtime/capability_graph.rb +44 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
- data/lib/hecks/runtime/command_interpreter.rb +413 -0
- data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
- data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
- data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
- data/lib/hecks/runtime/command_rules/emission.rb +34 -0
- data/lib/hecks/runtime/command_rules/references.rb +181 -0
- data/lib/hecks/runtime/command_rules.rb +28 -0
- data/lib/hecks/runtime/dependency_planning.rb +231 -0
- data/lib/hecks/runtime/dispatcher.rb +286 -0
- data/lib/hecks/runtime/entity_element.rb +253 -0
- data/lib/hecks/runtime/entity_interpreter.rb +228 -0
- data/lib/hecks/runtime/era_check.rb +136 -0
- data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
- data/lib/hecks/runtime/era_guard.rb +193 -0
- data/lib/hecks/runtime/era_tamper.rb +61 -0
- data/lib/hecks/runtime/errors.rb +100 -0
- data/lib/hecks/runtime/event.rb +51 -0
- data/lib/hecks/runtime/identity.rb +128 -0
- data/lib/hecks/runtime/instance.rb +120 -0
- data/lib/hecks/runtime/interpreting.rb +69 -0
- data/lib/hecks/runtime/loader.rb +143 -0
- data/lib/hecks/runtime/policy_interpreter.rb +366 -0
- data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
- data/lib/hecks/runtime/query_interpreter.rb +254 -0
- data/lib/hecks/runtime/reaction_invocation.rb +245 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
- data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
- data/lib/hecks/runtime/reference_hop.rb +99 -0
- data/lib/hecks/runtime/refusal_wording.rb +124 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
- data/lib/hecks/runtime/registry/verification.rb +158 -0
- data/lib/hecks/runtime/registry.rb +218 -0
- data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
- data/lib/hecks/runtime/routing.rb +96 -0
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
- data/lib/hecks/runtime/saga_interpreter.rb +301 -0
- data/lib/hecks/runtime/storage_shape.rb +120 -0
- data/lib/hecks/runtime/tenant_check.rb +84 -0
- data/lib/hecks/runtime/tenant_scope.rb +55 -0
- data/lib/hecks/runtime/value/admission.rb +112 -0
- data/lib/hecks/runtime/value/coercion.rb +500 -0
- data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
- data/lib/hecks/runtime/value.rb +125 -0
- data/lib/hecks/runtime.rb +95 -0
- data/lib/hecks/translation/audit/approval_digest.rb +31 -0
- data/lib/hecks/translation/audit/layer_one.rb +37 -0
- data/lib/hecks/translation/audit/layer_two.rb +77 -0
- data/lib/hecks/translation/audit/unfed_report.rb +42 -0
- data/lib/hecks/translation/audit.rb +70 -0
- data/lib/hecks/translation/reattest.rb +72 -0
- data/lib/hecks/translation/rule_compiler.rb +120 -0
- data/lib/hecks/translation/scaffold/differ.rb +183 -0
- data/lib/hecks/translation/scaffold/renderer.rb +41 -0
- data/lib/hecks/translation/scaffold/writer.rb +28 -0
- data/lib/hecks/translation/scaffold.rb +30 -0
- data/lib/hecks/translation.rb +11 -0
- data/lib/hecks/version.rb +16 -0
- data/lib/hecks/vocabulary.rb +213 -0
- data/lib/hecks.rb +118 -6
- metadata +401 -222
- data/bin/hecks +0 -7
- data/bin/hecks-package +0 -65
- data/bin/hecks_console +0 -12
- data/bin/hecks_serverless +0 -6
- data/lib/cli/build.rb +0 -14
- data/lib/cli/command_runner.rb +0 -28
- data/lib/cli/console.rb +0 -10
- data/lib/cli/generate.rb +0 -37
- data/lib/cli/hecks-cli.rb +0 -27
- data/lib/cli/test.rb +0 -57
- data/lib/console/commands.rb +0 -8
- data/lib/console/hecks-console.rb +0 -1
- data/lib/packager/README.md +0 -0
- data/lib/packager/app_runner.rb +0 -21
- data/lib/packager/args.rb +0 -26
- data/lib/packager/compatibility/fixnum.rb +0 -6
- data/lib/packager/hecks.rb +0 -39
- data/lib/packager/query_runner.rb +0 -21
- data/lib/packager/resources/Dockerfile +0 -11
- data/lib/packager/resources/app_binary +0 -7
- data/lib/packager/resources/bundle_config +0 -3
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
- data/lib/serverless/Domain +0 -32
- data/lib/serverless/cli.rb +0 -75
- data/lib/serverless/resources/command_name.js +0 -5
- data/lib/serverless/resources/environment.js +0 -7
- data/lib/serverless/resources/handler.js.tt +0 -28
- data/lib/serverless/resources/run_binary.js +0 -22
- data/lib/serverless/resources/serverless.yml +0 -20
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
require "set"
|
|
2
|
+
require_relative "bluebook"
|
|
3
|
+
require_relative "codemod"
|
|
4
|
+
require_relative "projections/model"
|
|
5
|
+
require_relative "fuzzing/properties"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
# THE SHARED CORE behind `bin/query_ir` (a text CLI) and
|
|
9
|
+
# `bin/hecks_query_ir_mcp` (an MCP server exposing the same two
|
|
10
|
+
# queries as tools) — one implementation, two front ends, the same
|
|
11
|
+
# reason `Hecks::Codemod` exists once rather than per-script.
|
|
12
|
+
# Every method here returns structured data (Hashes/Arrays/Structs),
|
|
13
|
+
# never formatted text — formatting is each front end's own job.
|
|
14
|
+
module QueryIR
|
|
15
|
+
Codemod = Hecks::Codemod
|
|
16
|
+
Deviations = Hecks::Projections::Model::Deviations
|
|
17
|
+
|
|
18
|
+
# THE SAME MAPPING spec/model_shape_conformance_spec.rb's own
|
|
19
|
+
# MODEL_CONSTRUCTS holds — kept here rather than shared from the
|
|
20
|
+
# spec (a spec file is not a library other code should require),
|
|
21
|
+
# matching Deviations' own doc comment: "the generator and the gate
|
|
22
|
+
# read one source" — Deviations is that one source; this table is
|
|
23
|
+
# small and genuinely construct-list bookkeeping, not a rule that
|
|
24
|
+
# can drift silently the way a Deviations entry could.
|
|
25
|
+
CONSTRUCTS = {
|
|
26
|
+
"Bluebook" => Hecks::Bluebook::Chapter,
|
|
27
|
+
"Aggregate" => Hecks::Bluebook::Aggregate,
|
|
28
|
+
"Command" => Hecks::Bluebook::Command,
|
|
29
|
+
"Entity" => Hecks::Bluebook::Entity,
|
|
30
|
+
"ValueObject" => Hecks::Bluebook::ValueObject,
|
|
31
|
+
"Policy" => Hecks::Bluebook::Policy,
|
|
32
|
+
"Query" => Hecks::Bluebook::Query,
|
|
33
|
+
"ReadModel" => Hecks::Bluebook::ReadModel,
|
|
34
|
+
"ProcessManager" => Hecks::Bluebook::ProcessManager
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
Rule = Struct.new(:kind, :description, :canonical, :location, keyword_init: true)
|
|
38
|
+
|
|
39
|
+
module_function
|
|
40
|
+
|
|
41
|
+
def meta_declared(name)
|
|
42
|
+
Hecks::Bluebook::MetaValidator.grammar_registry
|
|
43
|
+
.bluebook("Bluebook").aggregate(name).attributes.map(&:name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The real structural diff between what a Ruby IR class emits
|
|
47
|
+
# (Class.ir_spec.keys) and what the self-hosted meta-domain declares
|
|
48
|
+
# for it — the SAME comparison spec/model_shape_conformance_spec.rb
|
|
49
|
+
# makes, reusing its own Deviations data so this can never silently
|
|
50
|
+
# drift from what that gate actually checks.
|
|
51
|
+
def construct_diff(name)
|
|
52
|
+
klass = CONSTRUCTS.fetch(name) {
|
|
53
|
+
raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}"
|
|
54
|
+
}
|
|
55
|
+
declared = meta_declared(name)
|
|
56
|
+
emitted = klass.ir_spec.keys
|
|
57
|
+
|
|
58
|
+
accounted = declared.reject { |field| Deviations::PARENT_REF.call(field) } -
|
|
59
|
+
Deviations::JUDGE_ONLY -
|
|
60
|
+
Deviations.folded(name).values.flatten -
|
|
61
|
+
Deviations.off_the_wire(name) -
|
|
62
|
+
Deviations.dynamic_tail(name) -
|
|
63
|
+
Deviations.unpacked(name).keys
|
|
64
|
+
|
|
65
|
+
unaccounted = emitted -
|
|
66
|
+
declared -
|
|
67
|
+
Deviations.contained(name) -
|
|
68
|
+
Deviations.folded(name).keys -
|
|
69
|
+
Deviations.computed(name) -
|
|
70
|
+
Deviations.unpacked(name).values.flatten
|
|
71
|
+
|
|
72
|
+
{ name: name, declared: declared, emitted: emitted,
|
|
73
|
+
missing_from_ruby: accounted - emitted, unaccounted_in_ruby: unaccounted }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def constructs(names = [])
|
|
77
|
+
targets = names.empty? ? CONSTRUCTS.keys : names
|
|
78
|
+
targets.map { |name| construct_diff(name) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Every given/ensures/invariant DECLARATION reachable from a booted
|
|
82
|
+
# registry, walked recursively — every owner's own `.preconditions`/
|
|
83
|
+
# `.invariants` (block-declared rules), every value object's own
|
|
84
|
+
# `.invariants`, and every command's own `.givens`/`.ensures`
|
|
85
|
+
# (`.givens` too, not just `.ensures` — a command's own LOCAL
|
|
86
|
+
# `given("x") { block }` not yet hoisted to its owner, round 4's own
|
|
87
|
+
# starting shape, would otherwise be invisible).
|
|
88
|
+
#
|
|
89
|
+
# NOT keyed by object identity (a real, hard-won correction — see
|
|
90
|
+
# the comment on `duplicates`' own dedup below for why: a bare
|
|
91
|
+
# `given("x")` reference and its owner's own block declaration are
|
|
92
|
+
# the SAME Ruby object at DSL build time, but `MetaValidator.call`
|
|
93
|
+
# (S14 — every bluebook is judged by dispatching its OWN IR into the
|
|
94
|
+
# self-hosted grammar, then reconstructed via `Assembly.call` from
|
|
95
|
+
# flat rows) rebuilds the whole graph fresh from there. By the time
|
|
96
|
+
# any caller reads `chapter.aggregates`, EVERY given/invariant is
|
|
97
|
+
# already a distinct object, whether it was block-declared or
|
|
98
|
+
# bare-referenced — object identity carries no signal past that
|
|
99
|
+
# point, for any construct, not just this one).
|
|
100
|
+
def collect_rules(registry, chapter_name = nil)
|
|
101
|
+
rules = []
|
|
102
|
+
chapters = chapter_name ? [registry.bluebook(chapter_name)] : registry.bluebooks.values
|
|
103
|
+
|
|
104
|
+
walk_construct = lambda do |construct, path|
|
|
105
|
+
(construct.respond_to?(:preconditions) ? construct.preconditions : []).each do |rule|
|
|
106
|
+
rules << Rule.new(kind: "given", description: rule.description, canonical: rule.canonical,
|
|
107
|
+
location: "#{path} (declared)")
|
|
108
|
+
end
|
|
109
|
+
(construct.respond_to?(:invariants) ? construct.invariants : []).each do |rule|
|
|
110
|
+
rules << Rule.new(kind: "invariant", description: rule.description, canonical: rule.canonical,
|
|
111
|
+
location: "#{path} (declared)")
|
|
112
|
+
end
|
|
113
|
+
construct.commands.each do |command|
|
|
114
|
+
command.givens.each do |rule|
|
|
115
|
+
rules << Rule.new(kind: "given", description: rule.description, canonical: rule.canonical,
|
|
116
|
+
location: "#{path}.#{command.hecks_name}")
|
|
117
|
+
end
|
|
118
|
+
command.ensures.each do |rule|
|
|
119
|
+
rules << Rule.new(kind: "ensures", description: rule.description, canonical: rule.canonical,
|
|
120
|
+
location: "#{path}.#{command.hecks_name}")
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
construct.entities.each { |piece|
|
|
124
|
+
walk_construct.call(piece, "#{path}.#{piece.hecks_name}")
|
|
125
|
+
} if construct.respond_to?(:entities)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
chapters.each do |chapter|
|
|
129
|
+
chapter.aggregates.each do |aggregate|
|
|
130
|
+
walk_construct.call(aggregate, aggregate.hecks_name)
|
|
131
|
+
aggregate.value_objects.each do |vo|
|
|
132
|
+
vo.invariants.each do |rule|
|
|
133
|
+
rules << Rule.new(kind: "invariant", description: rule.description, canonical: rule.canonical,
|
|
134
|
+
location: "#{aggregate.hecks_name}::#{vo.hecks_name} (declared)")
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
rules
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# A rule's OWNER — the construct path a "(declared)" location names
|
|
143
|
+
# directly, or (for a command-level `.givens`/`.ensures` entry) the
|
|
144
|
+
# path with its trailing `.CommandName` segment stripped. Two rules
|
|
145
|
+
# sharing an owner are the SAME declaration read twice (an owner's
|
|
146
|
+
# own precondition, and a command under it referencing that
|
|
147
|
+
# precondition by name) — not two independent ones.
|
|
148
|
+
#
|
|
149
|
+
# PUBLIC, not a `duplicates`-only internal — `bin/codemod_hoist_
|
|
150
|
+
# local_givens` reads it directly to group `collect_rules`' own
|
|
151
|
+
# output by owner itself, the same reading `duplicates`' own
|
|
152
|
+
# `declaration_count` makes.
|
|
153
|
+
def owner_of(location)
|
|
154
|
+
return location.sub(/ \(declared\)\z/, "") if location.end_with?(" (declared)")
|
|
155
|
+
|
|
156
|
+
location.rpartition(".").first
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Grouped by (kind, description, canonical), not canonical text
|
|
160
|
+
# alone — a generic one-liner like `!value.to_s.empty?` legitimately
|
|
161
|
+
# recurs dozens of times for unrelated fields; the real signal is
|
|
162
|
+
# the SAME RULE (same description, same predicate), which is also
|
|
163
|
+
# exactly what the given/invariant reference mechanism itself
|
|
164
|
+
# resolves on.
|
|
165
|
+
#
|
|
166
|
+
# DEDUPED BY OWNER, not object identity (`collect_rules`' own
|
|
167
|
+
# comment has the full story — identity is gone by the time this
|
|
168
|
+
# reads the registry). Within a group, every command-level rule
|
|
169
|
+
# whose OWNER already has its own "(declared)" entry in the same
|
|
170
|
+
# group is just that declaration read again through a reference —
|
|
171
|
+
# `Account.Open`/`Account.Credit`/etc. all naming `Account`'s own
|
|
172
|
+
# `given("customer is active")` count as Account's ONE declaration,
|
|
173
|
+
# not nine. A command-level rule with no matching owner declaration
|
|
174
|
+
# (a LOCAL, not-yet-hoisted `given("x") { block }`) counts as its
|
|
175
|
+
# own standalone declaration — two different commands independently
|
|
176
|
+
# writing the identical local predicate IS two declarations, a real
|
|
177
|
+
# hoisting opportunity. A group is reported only when it adds up to
|
|
178
|
+
# MORE than one real declaration this way.
|
|
179
|
+
#
|
|
180
|
+
# `domains: []` means "the self-hosted meta-domain only" — pass real
|
|
181
|
+
# domain directories explicitly to include them, or `nil` (the
|
|
182
|
+
# default) for meta-domain plus every real example.
|
|
183
|
+
def duplicates(domains: nil, include_meta: true)
|
|
184
|
+
domains ||= Codemod::EXAMPLE_ROOTS
|
|
185
|
+
all_rules = []
|
|
186
|
+
all_rules.concat(collect_rules(Codemod.meta_registry)) if include_meta
|
|
187
|
+
|
|
188
|
+
domains.each do |domain_dir|
|
|
189
|
+
bluebook_files = Dir.glob(File.join(domain_dir, "bluebook", "*.bluebook"))
|
|
190
|
+
next if bluebook_files.empty?
|
|
191
|
+
|
|
192
|
+
registry = Codemod.load_bluebook(bluebook_files)
|
|
193
|
+
all_rules.concat(collect_rules(registry))
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
all_rules.group_by { |r| [r.kind, r.description, r.canonical] }
|
|
197
|
+
.map { |key, rules| [key, rules, declaration_count(rules)] }
|
|
198
|
+
.select { |_, _, count| count > 1 }
|
|
199
|
+
.map { |(kind, description, canonical), rules, _|
|
|
200
|
+
{ kind: kind, description: description, canonical: canonical,
|
|
201
|
+
locations: rules.map(&:location) }
|
|
202
|
+
}
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# `given` ONLY (not `invariant`/`ensures`) also covers CROSS-ENTITY
|
|
206
|
+
# coverage — one piece's own entity-level declaration, shared with
|
|
207
|
+
# any OTHER piece nested under the SAME root aggregate (real corpus
|
|
208
|
+
# this closes: SafeDepositBox's own `Visit`/`KeyIssuance`, two
|
|
209
|
+
# different pieces on one head). A rule owned by a nested entity
|
|
210
|
+
# (its own owner path has more than one segment) is covered when
|
|
211
|
+
# SOME "(declared)" entry exists ANYWHERE under that SAME root
|
|
212
|
+
# aggregate — not just under its OWN exact owner — matching the
|
|
213
|
+
# DSL's own pool, threaded unchanged through an aggregate's whole
|
|
214
|
+
# entity tree (`AggregateBuilder#entity`'s own comment).
|
|
215
|
+
#
|
|
216
|
+
# A KNOWN, ACCEPTED GAP this does NOT (and structurally cannot)
|
|
217
|
+
# close: CHAPTER-WIDE given sharing (`AggregateBuilder#given`'s own
|
|
218
|
+
# bare form, `docs/implemented/resolution-rules/chapter-given.md`) — `Account`,
|
|
219
|
+
# `SafeDepositBox`, and `OnboardingCase` each still show as their
|
|
220
|
+
# own "(declared)" owner here even AFTER `SafeDepositBox`/
|
|
221
|
+
# `OnboardingCase` were converted to bare chapter-wide references,
|
|
222
|
+
# because a REFERENCED given still write-throughs into its own
|
|
223
|
+
# aggregate's `@named_givens` — the SAME reason `collect_rules`'
|
|
224
|
+
# own top comment already gives for why object identity carries no
|
|
225
|
+
# signal past a bluebook's own build: the EXPORTED IR cannot tell
|
|
226
|
+
# "I declared this myself" apart from "I referenced someone else's
|
|
227
|
+
# declaration," because by the time anything reads `chapter.
|
|
228
|
+
# aggregates`, both look identical. Closing this would mean reading
|
|
229
|
+
# SOURCE TEXT (bare `given(desc)` vs. block `given(desc) { ... }`),
|
|
230
|
+
# not the built IR this query is deliberately built on — a
|
|
231
|
+
# different, source-level tool, not a fix to this one. Treat a
|
|
232
|
+
# still-flagged group naming multiple aggregates as "verify by
|
|
233
|
+
# hand whether this is ALREADY a chapter-wide reference before
|
|
234
|
+
# assuming it's fresh duplication," not as an automatic signal
|
|
235
|
+
# either way.
|
|
236
|
+
def declaration_count(rules)
|
|
237
|
+
declared = rules.select { |r| r.location.end_with?(" (declared)") }
|
|
238
|
+
declared_owners = declared.map { |r| owner_of(r.location) }.to_set
|
|
239
|
+
declared_given_roots = declared.select { |r| r.kind == "given" }
|
|
240
|
+
.map { |r| owner_of(r.location).split(".").first }.to_set
|
|
241
|
+
|
|
242
|
+
standalone = rules.reject do |r|
|
|
243
|
+
owner = owner_of(r.location)
|
|
244
|
+
next true if declared_owners.include?(owner)
|
|
245
|
+
next true if r.kind == "given" && owner.include?(".") && declared_given_roots.include?(owner.split(".").first)
|
|
246
|
+
|
|
247
|
+
false
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
declared_owners.size + standalone.size
|
|
251
|
+
end
|
|
252
|
+
private_class_method :declaration_count
|
|
253
|
+
|
|
254
|
+
# SHARED TEXT FORMATTING — both `bin/query_ir` (a text CLI) and
|
|
255
|
+
# `bin/hecks_query_ir_mcp` (an MCP tool result, itself a text
|
|
256
|
+
# block) want the identical human-readable rendering; only the
|
|
257
|
+
# OUTER framing differs (plain stdout vs. a JSON-RPC content array).
|
|
258
|
+
def format_constructs(diffs)
|
|
259
|
+
diffs.map do |diff|
|
|
260
|
+
lines = ["== #{diff[:name]} =="]
|
|
261
|
+
lines << " emits: #{diff[:emitted].join(', ')}"
|
|
262
|
+
lines << " declares: #{diff[:declared].join(', ')}"
|
|
263
|
+
if diff[:missing_from_ruby].empty? && diff[:unaccounted_in_ruby].empty?
|
|
264
|
+
lines << " clean — every declared field is emitted (or a named deviation), nothing emitted is undeclared"
|
|
265
|
+
else
|
|
266
|
+
lines << " MISSING FROM RUBY (declared, not emitted, not a named deviation): #{diff[:missing_from_ruby].join(', ')}" unless diff[:missing_from_ruby].empty?
|
|
267
|
+
lines << " UNACCOUNTED IN RUBY (emitted, not declared, not a named deviation): #{diff[:unaccounted_in_ruby].join(', ')}" unless diff[:unaccounted_in_ruby].empty?
|
|
268
|
+
end
|
|
269
|
+
lines.join("\n")
|
|
270
|
+
end.join("\n\n")
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# ONE HAND-TYPED CONSTRUCT-NAME PER RECONSTRUCTION METHOD — the only
|
|
274
|
+
# two `MetaValidator::Reconstruction` methods NOT driven generically
|
|
275
|
+
# through `Assembly::Contracts`' own table (its own header explains
|
|
276
|
+
# why: `aggregate(row)`/`entity(row)` predate the table and were
|
|
277
|
+
# never migrated). `impact_preview`'s own touchpoint 4 is checked
|
|
278
|
+
# ONLY for these two — every other construct is read generically, so
|
|
279
|
+
# asking "does Command's own reconstruction method mention this
|
|
280
|
+
# field" is a question with no method to check.
|
|
281
|
+
RECONSTRUCTION_METHODS = { "Aggregate" => :aggregate, "Entity" => :entity }.freeze
|
|
282
|
+
|
|
283
|
+
# THE SIX TOUCHPOINTS `.claude/skills/bluebook-construct-creator/
|
|
284
|
+
# SKILL.md` walks in prose, checked structurally instead of by hand
|
|
285
|
+
# — for a construct/field pair NOT yet fully propagated (typically
|
|
286
|
+
# mid-round, deciding what's left), or as a sanity check before the
|
|
287
|
+
# final gate sweep of a round already believed done. Every check
|
|
288
|
+
# here is BEST-EFFORT and ADVISORY, not a gate: a `false` does not
|
|
289
|
+
# always mean "not yet done" (a field can be legitimately exempt —
|
|
290
|
+
# `Deviations`' own named categories, `GUARANTEED_BY_CONSTRUCTION`,
|
|
291
|
+
# or `META_DOMAIN_KNOWN_GAPS`, the last of which lives in
|
|
292
|
+
# spec/fuzzing/meta_domain_coverage_spec.rb, a SPEC file this
|
|
293
|
+
# module deliberately never requires — see `CONSTRUCTS`' own
|
|
294
|
+
# comment on the same principle). Read the touchpoint's own
|
|
295
|
+
# existing gate (`model_shape_conformance_spec.rb`,
|
|
296
|
+
# `assembly_spec.rb`, `meta_domain_coverage_spec.rb`) before trusting
|
|
297
|
+
# a `false` here as a real gap.
|
|
298
|
+
def impact_preview(name, field)
|
|
299
|
+
CONSTRUCTS.fetch(name) { raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}" }
|
|
300
|
+
field = field.to_s
|
|
301
|
+
|
|
302
|
+
{
|
|
303
|
+
name: name,
|
|
304
|
+
field: field,
|
|
305
|
+
touchpoints: [
|
|
306
|
+
{ touchpoint: "meta-domain grammar declares it", present: meta_declared(name).map(&:to_s).include?(field) },
|
|
307
|
+
{ touchpoint: "docs/resolution-rules/ names it", present: resolution_rule_mentions?(field) },
|
|
308
|
+
{ touchpoint: "Assembly::Contracts consumes it", present: contract_consumes?(name, field) },
|
|
309
|
+
{ touchpoint: "Reconstruction's hand-typed method reads it", present: reconstruction_reads?(name, field) },
|
|
310
|
+
{ touchpoint: "fuzzer FEATURE_COVERAGE/GUARANTEED_BY_CONSTRUCTION claims it", present: fuzzer_claims?(name, field) },
|
|
311
|
+
{ touchpoint: "Rust mirror (rust/parser/src/parse/*.rs) mentions it", present: rust_mentions?(field) }
|
|
312
|
+
]
|
|
313
|
+
}
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def resolution_rule_mentions?(field)
|
|
317
|
+
paths = Dir.glob(File.join(Codemod::ROOT, "docs/resolution-rules/*.md")) +
|
|
318
|
+
Dir.glob(File.join(Codemod::ROOT, "docs/implemented/resolution-rules/*.md"))
|
|
319
|
+
paths.any? { |path| File.read(path).include?(field) }
|
|
320
|
+
end
|
|
321
|
+
private_class_method :resolution_rule_mentions?
|
|
322
|
+
|
|
323
|
+
def contract_consumes?(name, field)
|
|
324
|
+
contract = Hecks::Bluebook::Assembly.contract(name)
|
|
325
|
+
contract.fields.key?(field.to_sym) || contract.derived.key?(field.to_sym)
|
|
326
|
+
rescue KeyError
|
|
327
|
+
false
|
|
328
|
+
end
|
|
329
|
+
private_class_method :contract_consumes?
|
|
330
|
+
|
|
331
|
+
# `Method#source_location` finds where the hand-typed method starts;
|
|
332
|
+
# the method's own body ends at the next line indented no deeper
|
|
333
|
+
# than its own `def` — the same boundary Ruby itself uses, read back
|
|
334
|
+
# textually because there is no live AST here, only a file to grep a
|
|
335
|
+
# slice of. `nil` (not `false`) for every OTHER construct — this
|
|
336
|
+
# touchpoint genuinely does not apply to them (`RECONSTRUCTION_
|
|
337
|
+
# METHODS` only names the two hand-typed methods), and collapsing
|
|
338
|
+
# "does not apply" into "not done" would misreport a construct that
|
|
339
|
+
# was never supposed to have this touchpoint at all.
|
|
340
|
+
def reconstruction_reads?(name, field)
|
|
341
|
+
method_name = RECONSTRUCTION_METHODS[name]
|
|
342
|
+
return nil unless method_name
|
|
343
|
+
|
|
344
|
+
file, start_line = Hecks::Bluebook::MetaValidator::Reconstruction.instance_method(method_name).source_location
|
|
345
|
+
lines = File.readlines(file)
|
|
346
|
+
def_line = lines[start_line - 1]
|
|
347
|
+
indent = def_line[/\A\s*/]
|
|
348
|
+
|
|
349
|
+
body = lines[start_line..].take_while { |line| line.strip.empty? || line[/\A\s*/].size > indent.size || !line.lstrip.start_with?("def ") }
|
|
350
|
+
body.join.include?("#{field}:")
|
|
351
|
+
end
|
|
352
|
+
private_class_method :reconstruction_reads?
|
|
353
|
+
|
|
354
|
+
def fuzzer_claims?(name, field)
|
|
355
|
+
key = "#{name}##{field}"
|
|
356
|
+
Hecks::Fuzzing::Properties::FEATURE_COVERAGE.values.flatten.include?(key) ||
|
|
357
|
+
Hecks::Fuzzing::Properties::GUARANTEED_BY_CONSTRUCTION.key?(key)
|
|
358
|
+
end
|
|
359
|
+
private_class_method :fuzzer_claims?
|
|
360
|
+
|
|
361
|
+
def rust_mentions?(field)
|
|
362
|
+
Dir.glob(File.join(Codemod::ROOT, "rust/parser/src/parse/*.rs")).any? { |path| File.read(path).include?(field) }
|
|
363
|
+
end
|
|
364
|
+
private_class_method :rust_mentions?
|
|
365
|
+
|
|
366
|
+
def format_impact_preview(preview)
|
|
367
|
+
lines = ["== #{preview[:name]}##{preview[:field]} =="]
|
|
368
|
+
preview[:touchpoints].each do |t|
|
|
369
|
+
mark = t[:present].nil? ? "n/a" : (t[:present] ? "yes" : "NOT YET")
|
|
370
|
+
lines << " [#{mark.rjust(7)}] #{t[:touchpoint]}"
|
|
371
|
+
end
|
|
372
|
+
done = preview[:touchpoints].count { |t| t[:present] == true }
|
|
373
|
+
total = preview[:touchpoints].count { |t| !t[:present].nil? }
|
|
374
|
+
lines << ""
|
|
375
|
+
lines << "#{done}/#{total} applicable touchpoint(s) show signs of this field — advisory, not a gate; " \
|
|
376
|
+
"a NOT YET can be a legitimate exemption (Deviations, GUARANTEED_BY_CONSTRUCTION, or a spec-only " \
|
|
377
|
+
"META_DOMAIN_KNOWN_GAPS entry this module deliberately never reads)."
|
|
378
|
+
lines.join("\n")
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def format_duplicates(groups)
|
|
382
|
+
return "no duplicate given/invariant/ensures rule found" if groups.empty?
|
|
383
|
+
|
|
384
|
+
body = groups.map do |group|
|
|
385
|
+
["== #{group[:kind]}: #{group[:description].inspect} — #{group[:canonical]} ==",
|
|
386
|
+
*group[:locations].map { |loc| " #{loc}" }].join("\n")
|
|
387
|
+
end.join("\n\n")
|
|
388
|
+
|
|
389
|
+
"#{body}\n\n#{groups.size} duplicate group(s), #{groups.sum { |g| g[:locations].size }} declarations total"
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require_relative "../../literal"
|
|
2
|
+
require_relative "../../vocabulary"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module QuerySpecification
|
|
6
|
+
module Common
|
|
7
|
+
# `none_in_state`, vendored addition not (yet) upstream hecks
|
|
8
|
+
# (migration plan task 4): a CROSS-AGGREGATE ANTI-JOIN comparator --
|
|
9
|
+
# `where ref: { none_in_state: "Claim:held" }` holds true when NO
|
|
10
|
+
# record in the named aggregate, keyed by this record's own field
|
|
11
|
+
# value, is currently in the named state. plan.bluebook's own
|
|
12
|
+
# description: "a keyed point lookup (HashMap hit), never a scan" --
|
|
13
|
+
# a real, deliberate, pre-existing feature (WhereOp::NoneInState in
|
|
14
|
+
# the old Rust runtime), not invented here -- see Runtime::
|
|
15
|
+
# QueryInterpreter#holds?'s own comment for the evaluation side.
|
|
16
|
+
COMPARATORS = Hecks::Vocabulary.symbols("QueryComparator")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The specification structs' own name for the one wire spelling — see
|
|
20
|
+
# Hecks::Literal, which every other `to_h`-bound literal field now
|
|
21
|
+
# shares. Kept as a word here because the structs below read better
|
|
22
|
+
# saying what they are doing than naming the module that does it.
|
|
23
|
+
def self.render_value(value) = Literal.render(value)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
require_relative "null_policy"
|
|
2
|
+
require_relative "../../runtime/value"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module QuerySpecification
|
|
6
|
+
module Common
|
|
7
|
+
# THE ONE COMPARATOR TABLE. There were two, near-identical copies of
|
|
8
|
+
# this — `Ports::Query::InMemory` (the path a Memory- or Heki-backed
|
|
9
|
+
# aggregate query actually runs) and `Runtime::QueryInterpreter`
|
|
10
|
+
# (entity/sub-list queries, and any adapter implementing no `:query`)
|
|
11
|
+
# — plus a third partial reading of `comparable` inside
|
|
12
|
+
# `SqlQueryBuilder#query_value`.
|
|
13
|
+
#
|
|
14
|
+
# Two copies of one rule is not a style complaint here; it has cost
|
|
15
|
+
# real bugs twice already, both recorded in the comments those files
|
|
16
|
+
# carried. `none_in_state` was added to one copy only, so an ordinary
|
|
17
|
+
# Memory query's `none_in_state` clause fell to the `else` branch and
|
|
18
|
+
# silently excluded every row. And `comparable` DIVERGED without
|
|
19
|
+
# anyone noticing: one copy took the FIRST numeric member of a value
|
|
20
|
+
# object, the other only unwrapped when there was EXACTLY ONE — so a
|
|
21
|
+
# value object with two numeric members compared as a number on one
|
|
22
|
+
# path and as a whole Hash on the other.
|
|
23
|
+
#
|
|
24
|
+
# The callers still differ in how they REACH a value — one takes a
|
|
25
|
+
# registry argument, the other closes over `@registry`; one digs the
|
|
26
|
+
# field through `FieldPath`, the other is handed it — so they keep
|
|
27
|
+
# their own resolution and share only the comparison itself.
|
|
28
|
+
module Comparison
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# A value object compared as a scalar, when which scalar is meant
|
|
32
|
+
# is not in doubt. Exactly one numeric member is unambiguous; a
|
|
33
|
+
# single-member value object is unambiguous whatever its type.
|
|
34
|
+
# Anything else is returned UNCHANGED rather than guessed at — two
|
|
35
|
+
# numeric members give no reason to prefer either, and picking the
|
|
36
|
+
# first silently compares a field the author never named.
|
|
37
|
+
#
|
|
38
|
+
# A declaration naming an ambiguous value object is refused when
|
|
39
|
+
# the bluebook loads (`AggregateBuilder`'s own query-field seal),
|
|
40
|
+
# so this branch is a backstop rather than the primary guard: which
|
|
41
|
+
# member is meant is knowable at declaration time, and a refusal
|
|
42
|
+
# naming the candidates is worth more than any runtime reading.
|
|
43
|
+
def comparable(value)
|
|
44
|
+
value = value.to_h if value.is_a?(Runtime::Value)
|
|
45
|
+
return value unless value.is_a?(Hash)
|
|
46
|
+
|
|
47
|
+
numerics = value.values.select { |field| field.is_a?(Numeric) }
|
|
48
|
+
return numerics.first if numerics.size == 1
|
|
49
|
+
return value.values.first if value.size == 1
|
|
50
|
+
|
|
51
|
+
value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Which members a value object offers a scalar comparison, for a
|
|
55
|
+
# refusal that can name them. Empty when the value object is
|
|
56
|
+
# unambiguous — nothing to report.
|
|
57
|
+
def ambiguous_members(value_object)
|
|
58
|
+
numerics = value_object.attributes.select { |a| NUMERIC_TYPES.include?(a.type.to_s) }
|
|
59
|
+
return [] if numerics.size == 1 || value_object.attributes.size == 1
|
|
60
|
+
|
|
61
|
+
value_object.attributes.map(&:name)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
NUMERIC_TYPES = %w[Integer Float Numeric].freeze
|
|
65
|
+
|
|
66
|
+
def holds?(operation, held, want, registry: nil)
|
|
67
|
+
# A NULL SATISFIES NO COMPARISON — NullPolicy.unmatchable? owns
|
|
68
|
+
# the rule and the reasoning, including why `none_in_state` is
|
|
69
|
+
# exempt from it.
|
|
70
|
+
return false if NullPolicy.unmatchable?(operation, held, want)
|
|
71
|
+
|
|
72
|
+
case operation.to_s
|
|
73
|
+
when "eq" then held == want
|
|
74
|
+
when "ne" then held != want
|
|
75
|
+
when "lt" then ordered?(held, want) && held < want
|
|
76
|
+
when "lte" then ordered?(held, want) && held <= want
|
|
77
|
+
when "gt" then ordered?(held, want) && held > want
|
|
78
|
+
when "gte" then ordered?(held, want) && held >= want
|
|
79
|
+
when "in" then any_member_in?(held, want)
|
|
80
|
+
when "contains" then contains?(held, want)
|
|
81
|
+
when "none_in_state" then none_in_state?(held, want, registry)
|
|
82
|
+
else
|
|
83
|
+
# Every declared comparator has a `when` above (Vocabulary::
|
|
84
|
+
# QueryComparator, held equal to this list by
|
|
85
|
+
# spec/vocabulary_table_spec) — this is not a real runtime
|
|
86
|
+
# path today, only a backstop against the day a tenth
|
|
87
|
+
# comparator reaches the grammar and this method doesn't
|
|
88
|
+
# grow to match. Silently reading an unrecognized comparator
|
|
89
|
+
# as `eq` is exactly the failure this table already shipped
|
|
90
|
+
# once (see this file's own header) — an operator this
|
|
91
|
+
# method can't evaluate must refuse, not guess.
|
|
92
|
+
raise Runtime::WiringError, "no comparator handles #{operation.to_s.inspect} — add one before declaring it"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# gt/gte/lt/lte are numeric-only and silently false otherwise — a
|
|
97
|
+
# where-clause never raises the way a given does, and that contract
|
|
98
|
+
# predates the extraction (lt was already exactly this permissive).
|
|
99
|
+
def ordered?(held, want) = held.is_a?(Numeric) && want.is_a?(Numeric)
|
|
100
|
+
|
|
101
|
+
# `in` reads a comma-separated list — a real Array survives
|
|
102
|
+
# untouched (a bluebook's own in-process value, before any wire
|
|
103
|
+
# serialisation), each element unwrapped the same way a scalar
|
|
104
|
+
# field is. This is `in`'s reading of ITS ARGUMENT (a caller may
|
|
105
|
+
# legitimately pass "a,b,c" meaning "any of these") — unrelated to
|
|
106
|
+
# `contains`, which reads the STORED field. See `contains?`.
|
|
107
|
+
def members(value)
|
|
108
|
+
return value.map { |element| comparable(element).to_s } if value.is_a?(Array)
|
|
109
|
+
|
|
110
|
+
value.to_s.split(",").map(&:strip)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# A folded reference hop asks whether the locally-held identity is
|
|
114
|
+
# among the matching target identities. A has_many relationship holds
|
|
115
|
+
# several identities, so the same question becomes an intersection:
|
|
116
|
+
# does ANY held identity occur in the wanted set? Scalar `in` retains
|
|
117
|
+
# its existing one-candidate behavior.
|
|
118
|
+
def any_member_in?(held, want)
|
|
119
|
+
wanted = members(want)
|
|
120
|
+
candidates = held.is_a?(Array) ? held : [held]
|
|
121
|
+
|
|
122
|
+
candidates.any? { |candidate| wanted.include?(comparable(candidate).to_s) }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# `contains` means two different things depending on what is HELD —
|
|
126
|
+
# real ELEMENT membership for a `list_of` field (a genuine Array
|
|
127
|
+
# arrives already, one element one member, nothing to split), and
|
|
128
|
+
# plain SUBSTRING for anything else. It used to fall through to
|
|
129
|
+
# `members`' comma-split for the scalar case too, silently reading
|
|
130
|
+
# a free-text field's own comma as a separator — which the SQL
|
|
131
|
+
# side's `instr`/`position` never did, so the two disagreed the
|
|
132
|
+
# moment a scalar's real content held a comma. Matching SQL's
|
|
133
|
+
# substring reading here keeps every engine answering `contains`
|
|
134
|
+
# identically for the same declared field.
|
|
135
|
+
def contains?(held, want)
|
|
136
|
+
return members(held).include?(want.to_s) if held.is_a?(Array)
|
|
137
|
+
|
|
138
|
+
held.to_s.include?(want.to_s)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# A CROSS-AGGREGATE ANTI-JOIN — `where ref: { none_in_state:
|
|
142
|
+
# "Claim:held" }` holds when NO record in the named aggregate,
|
|
143
|
+
# keyed by this record's own field value, is in the named state.
|
|
144
|
+
# No registry — no way to look the target up — reads as "not
|
|
145
|
+
# excluded", the same graceful default a missing record already
|
|
146
|
+
# falls back to. The target is searched by bare name across every
|
|
147
|
+
# loaded domain; ambiguity (two domains declaring one name) picks
|
|
148
|
+
# the first match rather than refusing, since a where-clause never
|
|
149
|
+
# raises (see `ordered?`).
|
|
150
|
+
def none_in_state?(held, want, registry)
|
|
151
|
+
return true unless registry
|
|
152
|
+
|
|
153
|
+
aggregate_name, state = want.to_s.split(":", 2)
|
|
154
|
+
target = find_aggregate_by_name(registry, aggregate_name)
|
|
155
|
+
return true unless target
|
|
156
|
+
|
|
157
|
+
target_domain, target_ir = target
|
|
158
|
+
record = registry.repository(target_domain, target_ir).find(held)
|
|
159
|
+
return true unless record
|
|
160
|
+
|
|
161
|
+
comparable(record.state[:state]) != state
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def find_aggregate_by_name(registry, name)
|
|
165
|
+
registry.bluebooks.each do |domain, bluebook|
|
|
166
|
+
aggregate = bluebook.aggregates.find { |a| a.hecks_name == name }
|
|
167
|
+
return [domain, aggregate] if aggregate
|
|
168
|
+
end
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require_relative "authorization_spec"
|
|
2
|
+
require_relative "comparators"
|
|
3
|
+
require_relative "cursor_spec"
|
|
4
|
+
require_relative "inspection_spec"
|
|
5
|
+
require_relative "limit_spec"
|
|
6
|
+
require_relative "null_semantics"
|
|
7
|
+
require_relative "offset_spec"
|
|
8
|
+
require_relative "order_by"
|
|
9
|
+
require_relative "where_clause"
|
|
10
|
+
|
|
11
|
+
module Hecks
|
|
12
|
+
module QuerySpecification
|
|
13
|
+
module Common
|
|
14
|
+
module DSL
|
|
15
|
+
# RENAMED FROM `where`/`order_by`/`limit`/`offset`/`authorize`
|
|
16
|
+
# (all below) — item #13's full metaprogrammed dispatch (slice
|
|
17
|
+
# 4c). A SHARED mixin, same shape `attribute_impl` proved in
|
|
18
|
+
# slice 3: ONE renamed method each, both Query and ReadModel
|
|
19
|
+
# Keyword rows name it in `calls:`. `where`/`order_by` are
|
|
20
|
+
# bootstrap-reachable (every core chapter's own `read_model`
|
|
21
|
+
# filters its roster with them), so both are in
|
|
22
|
+
# BOOTSTRAP_CALLS_FALLBACK for the ReadModel context; `limit`/
|
|
23
|
+
# `offset`/`authorize` are not (checked directly).
|
|
24
|
+
def where_impl(clauses)
|
|
25
|
+
@wheres ||= []
|
|
26
|
+
clauses.each do |field, value|
|
|
27
|
+
op, operand = split_comparator(value)
|
|
28
|
+
@wheres << WhereClause.new(field: field, op: op, value: operand)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def order_by_impl(field, direction = :asc)
|
|
33
|
+
@order_by = OrderBy.new(field: field, direction: direction)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def limit_impl(value) = @limit = LimitSpec.new(value: value)
|
|
37
|
+
def offset_impl(value) = @offset = OffsetSpec.new(value: value)
|
|
38
|
+
def cursor(value) = @cursor = CursorSpec.new(value: value)
|
|
39
|
+
def authorize_impl(policy, tenant: nil) = @authorization = AuthorizationSpec.new(policy: policy, tenant: tenant)
|
|
40
|
+
def nulls(mode) = @null_semantics = NullSemantics.new(mode: mode)
|
|
41
|
+
def inspect_query(mode = :sql) = @inspection = InspectionSpec.new(mode: mode)
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def split_comparator(value)
|
|
46
|
+
return [:eq, value] unless value.is_a?(Hash)
|
|
47
|
+
|
|
48
|
+
op, operand = value.first
|
|
49
|
+
unless value.size == 1 && COMPARATORS.include?(op.to_sym)
|
|
50
|
+
raise ArgumentError,
|
|
51
|
+
"unknown comparator #{value.inspect} — expected one of #{COMPARATORS.join(', ')}"
|
|
52
|
+
end
|
|
53
|
+
[op.to_sym, operand]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|