hecks 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +60 -2
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/postgres_era.adapter +5 -0
- data/lib/hecks/adapters/driven/prism.rb +4 -4
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +51 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -3
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +122 -86
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +301 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -20
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +361 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +124 -0
- data/lib/hecks/fuzzing/form_census.rb +199 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +226 -38
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
- data/lib/hecks/fuzzing/properties/guards.rb +129 -26
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
- data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +84 -39
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +224 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +676 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +39 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
- data/lib/hecks/fuzzing/value_generator.rb +66 -14
- data/lib/hecks/fuzzing.rb +11 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +365 -15
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +89 -21
- data/lib/hecks/ports/access_control.rb +58 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +160 -58
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +44 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +338 -83
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +29 -6
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +250 -0
- data/lib/hecks/projections/glossary/markdown.rb +105 -0
- data/lib/hecks/projections/glossary/mermaid.rb +110 -0
- data/lib/hecks/projections/glossary/page.css +271 -0
- data/lib/hecks/projections/glossary/page.js +72 -0
- data/lib/hecks/projections/glossary/sections.rb +17 -0
- data/lib/hecks/projections/glossary/sentences.rb +205 -0
- data/lib/hecks/projections/glossary.rb +214 -286
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +15 -22
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +132 -24
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +57 -13
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +64 -67
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +94 -19
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +282 -46
- data/lib/hecks/runtime/entity_interpreter.rb +160 -69
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +111 -56
- data/lib/hecks/runtime/reaction_invocation.rb +76 -9
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -112
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +56 -27
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -88
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +93 -56
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +379 -226
- data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
- data/lib/hecks/runtime/value.rb +28 -23
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +207 -5
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +43 -2
data/lib/hecks/codemod.rb
CHANGED
|
@@ -1,49 +1,50 @@
|
|
|
1
1
|
require_relative "bluebook/meta_validator"
|
|
2
|
+
require_relative "corpus"
|
|
2
3
|
|
|
3
4
|
module Hecks
|
|
4
|
-
#
|
|
5
|
+
# Shared machinery for a codemod that migrates real `.bluebook` source
|
|
5
6
|
# once a DSL builder change makes some previously-required declaration
|
|
6
7
|
# optional/redundant — pulled out of `bin/codemod_implicit_append_fields`
|
|
7
8
|
# (the first one built), which needed three real, hard-won fixes before
|
|
8
9
|
# it could be trusted: a process-lifetime AST cache with no
|
|
9
10
|
# invalidation, a batch-revert granularity that let one unsafe
|
|
10
11
|
# candidate sink every other safe one sharing its boot, and (in the
|
|
11
|
-
#
|
|
12
|
+
# spine the codemod migrates for, not here) an append-at-end insertion
|
|
12
13
|
# that only round-tripped correctly for whichever field happened to be
|
|
13
14
|
# last. None of those are guessable in advance; they only surface by
|
|
14
15
|
# actually running a real edit against real self-hosted code. This
|
|
15
|
-
# module is that lesson, kept — the
|
|
16
|
+
# module is that lesson, kept — the next codemod plugs in two rule-
|
|
16
17
|
# specific procs (`find_candidates`, `apply_candidate`) and inherits
|
|
17
18
|
# the boot/safety-net machinery rather than rediscovering it.
|
|
18
19
|
#
|
|
19
|
-
# A
|
|
20
|
-
# to delete" means knowing what the
|
|
20
|
+
# **A codemod is not pattern-matching alone**. Deciding "is this line safe
|
|
21
|
+
# to delete" means knowing what the runtime would resolve it to — so
|
|
21
22
|
# every codemod built on this module follows the same three steps:
|
|
22
23
|
# 1. Boot the real domain (or the self-hosted meta-domain) and read
|
|
23
|
-
# its IR to find
|
|
24
|
+
# its IR to find candidates — provided by the caller's own
|
|
24
25
|
# `find_candidates`, since the actual redundancy rule is specific
|
|
25
26
|
# to whichever spine change this migration serves.
|
|
26
27
|
# 2. Locate and remove each candidate's own source text — the
|
|
27
28
|
# caller's own `apply_candidate`.
|
|
28
29
|
# 3. Re-boot from the edited text and diff the full IR export
|
|
29
30
|
# against the pre-edit export. Byte-identical -> keep. Anything
|
|
30
|
-
# else (including a
|
|
31
|
-
# meta-domain
|
|
31
|
+
# else (including a raised exception — the self-hosted
|
|
32
|
+
# meta-domain dispatches itself into being, S14, so a bad edit
|
|
32
33
|
# can surface as a runtime refusal, not just a differing export)
|
|
33
|
-
# -> revert and report
|
|
34
|
+
# -> revert and report skipped, never silently guessed past.
|
|
34
35
|
#
|
|
35
|
-
#
|
|
36
|
-
# corpus text for — both items below are real bugs
|
|
36
|
+
# Design-time checklist for the spine change a future codemod migrates
|
|
37
|
+
# corpus text for — both items below are real bugs this module's own
|
|
37
38
|
# first use found, not hypothetical:
|
|
38
|
-
# - Does the resolved value get inserted into an
|
|
39
|
+
# - Does the resolved value get inserted into an order-sensitive
|
|
39
40
|
# list (the exported IR is array-order-sensitive throughout)? If
|
|
40
|
-
# so, the spine's own insertion must preserve the
|
|
41
|
+
# so, the spine's own insertion must preserve the original
|
|
41
42
|
# position, not just append — an append-at-end insertion only
|
|
42
43
|
# round-trips correctly for a value that already happened to be
|
|
43
44
|
# last.
|
|
44
|
-
# - Does resolution depend on
|
|
45
|
+
# - Does resolution depend on another construct already being
|
|
45
46
|
# declared (the aggregate/entity a `sets`/`append:` field resolves
|
|
46
|
-
# against)? If a creator command can be declared
|
|
47
|
+
# against)? If a creator command can be declared before the
|
|
47
48
|
# construct it creates (real, live: `command "Handler"` before
|
|
48
49
|
# `entity "Handler"`, one file down), one-pass resolution
|
|
49
50
|
# genuinely cannot see it yet — not a codemod bug, a structural
|
|
@@ -51,16 +52,16 @@ module Hecks
|
|
|
51
52
|
module Codemod
|
|
52
53
|
ROOT = File.expand_path("../..", __dir__)
|
|
53
54
|
|
|
54
|
-
EXAMPLE_ROOTS =
|
|
55
|
+
EXAMPLE_ROOTS = Corpus.members(:example, root: ROOT).map(&:path)
|
|
55
56
|
META_FILES = (Dir.glob(File.join(ROOT, "lib/hecks/grammar/*.bluebook")) +
|
|
56
57
|
Dir.glob(File.join(ROOT, "lib/hecks/framework/bluebook/*.bluebook")) +
|
|
57
58
|
Dir.glob(File.join(ROOT, "lib/hecks/language/bluebook/**/*.bluebook"))).sort
|
|
58
59
|
|
|
59
|
-
#
|
|
60
|
+
# The same lightweight path `spec/spec_helper.rb`'s own
|
|
60
61
|
# `boot_in_memory` uses — `Hecks.with_registry` satisfies
|
|
61
62
|
# `Hecks.bluebook`'s own `collect`'s "loaded outside a boot" check
|
|
62
63
|
# without `Hecks.boot`'s full era-check/adapter-wiring path, which
|
|
63
|
-
# needs a
|
|
64
|
+
# needs a live Postgres connection for `compliance` and would make
|
|
64
65
|
# every codemod depend on a database it has no reason to touch — a
|
|
65
66
|
# codemod only ever reads a chapter's own declared IR, never a
|
|
66
67
|
# stored record.
|
|
@@ -72,11 +73,11 @@ module Hecks
|
|
|
72
73
|
def self.export_json(registry) = Hecks::Projector::Exporter.json(registry)
|
|
73
74
|
|
|
74
75
|
# `Hecks::Adapters::Prism` caches a file's parsed AST for the
|
|
75
|
-
# life of the
|
|
76
|
+
# life of the process, keyed by path — fine for every existing
|
|
76
77
|
# caller (a file loads once per process: one `bin/ir` run, one
|
|
77
|
-
# rspec worker), but a codemod legitimately reloads the
|
|
78
|
+
# rspec worker), but a codemod legitimately reloads the same path
|
|
78
79
|
# after editing it, and a stale cached tree reports a
|
|
79
|
-
# `given`/`ensures` block at its
|
|
80
|
+
# `given`/`ensures` block at its old line number, which no longer
|
|
80
81
|
# matches the freshly re-executed file's own `block.source_location`
|
|
81
82
|
# — surfacing as "did not survive extraction" on a perfectly valid
|
|
82
83
|
# file. `Prism.forget` is the real invalidation API this module's
|
|
@@ -104,10 +105,10 @@ module Hecks
|
|
|
104
105
|
registry
|
|
105
106
|
end
|
|
106
107
|
|
|
107
|
-
# `forget_all`, not a single `forget` — the meta-domain is
|
|
108
|
+
# `forget_all`, not a single `forget` — the meta-domain is nine
|
|
108
109
|
# files (`MetaValidator::GRAMMAR_FILES`) merged into one registry,
|
|
109
110
|
# and a caller here (the codemod runner) doesn't generally know in
|
|
110
|
-
# advance which
|
|
111
|
+
# advance which one it just edited.
|
|
111
112
|
def self.boot_meta
|
|
112
113
|
Hecks::Adapters::Prism.forget_all
|
|
113
114
|
Hecks::Bluebook::MetaValidator.instance_variable_set(:@grammar_registry, nil)
|
|
@@ -123,7 +124,7 @@ module Hecks
|
|
|
123
124
|
# Walks every aggregate (and every nested entity, recursively)
|
|
124
125
|
# across every chapter in a booted registry, yielding [owning
|
|
125
126
|
# construct, command] pairs — `construct` is whichever
|
|
126
|
-
# Aggregate/Entity actually
|
|
127
|
+
# Aggregate/Entity actually owns the command, the same distinction
|
|
127
128
|
# `AggregateBuilder#command` vs `EntityBuilder#command` already
|
|
128
129
|
# draws. Generic enough for any rule that needs to walk real
|
|
129
130
|
# commands, not specific to the attribute-redundancy rule.
|
|
@@ -143,7 +144,7 @@ module Hecks
|
|
|
143
144
|
construct.attributes.find { |attr| attr.name.to_s == name.to_s }
|
|
144
145
|
end
|
|
145
146
|
|
|
146
|
-
# A
|
|
147
|
+
# A list attribute's own element construct — the value object or
|
|
147
148
|
# entity `list_of(...)` names, resolved by `hecks_name` the same way
|
|
148
149
|
# `AttributeCollector#resolve_identity_field!` already does. Shared
|
|
149
150
|
# because "what does this list actually hold" is a question any
|
|
@@ -157,7 +158,7 @@ module Hecks
|
|
|
157
158
|
pool.find { |c| c.hecks_name.to_s == list_attr.type.to_s }
|
|
158
159
|
end
|
|
159
160
|
|
|
160
|
-
# Either a raised exception
|
|
161
|
+
# Either a raised exception or a differing export counts as unsafe
|
|
161
162
|
# — see the module header on why the meta-domain specifically can
|
|
162
163
|
# raise. Returns [value_or_nil, error_message_or_nil].
|
|
163
164
|
def self.safely
|
|
@@ -166,11 +167,11 @@ module Hecks
|
|
|
166
167
|
[nil, "#{e.class}: #{e.message}"]
|
|
167
168
|
end
|
|
168
169
|
|
|
169
|
-
#
|
|
170
|
+
# **The reusable runner** — every real bug fix this module carries lives
|
|
170
171
|
# here, not in a caller's own script. A caller supplies:
|
|
171
172
|
#
|
|
172
173
|
# find_candidates: ->(registry) { [...] }
|
|
173
|
-
# Given a booted registry, return every
|
|
174
|
+
# Given a booted registry, return every candidate this rule
|
|
174
175
|
# could migrate. A candidate is whatever shape the caller wants
|
|
175
176
|
# — `apply_candidate` is the only other thing that reads it.
|
|
176
177
|
#
|
|
@@ -178,7 +179,7 @@ module Hecks
|
|
|
178
179
|
# Given one file's current text and one candidate, return the
|
|
179
180
|
# edited text and whether a match was actually found/removed —
|
|
180
181
|
# `false` (text unchanged) when the candidate doesn't apply to
|
|
181
|
-
#
|
|
182
|
+
# this file, which is how the meta-domain's multi-file search
|
|
182
183
|
# below finds the right one without the caller needing to know
|
|
183
184
|
# which file a candidate lives in ahead of time.
|
|
184
185
|
#
|
|
@@ -279,10 +280,10 @@ module Hecks
|
|
|
279
280
|
end
|
|
280
281
|
end
|
|
281
282
|
|
|
282
|
-
#
|
|
283
|
-
# meta-domain is
|
|
283
|
+
# Per-candidate, not one batched write-then-verify — the
|
|
284
|
+
# meta-domain is one shared registry (SyntaxBoot dispatches it
|
|
284
285
|
# into itself, S14), so a single unsafe candidate among many would
|
|
285
|
-
# otherwise sink every
|
|
286
|
+
# otherwise sink every other, genuinely safe candidate in the same
|
|
286
287
|
# run: this module's first real run found exactly that (25
|
|
287
288
|
# candidates, one dispatch-time break, all 25 reverted as a batch
|
|
288
289
|
# before this per-candidate loop existed). `before_meta` stays the
|
|
@@ -291,8 +292,8 @@ module Hecks
|
|
|
291
292
|
# still equal the pristine original after each kept edit, by
|
|
292
293
|
# definition, no moving target needed.
|
|
293
294
|
# Same shape and same reason as run_example_domains just above (see
|
|
294
|
-
# its own comment) — the write/verify/revert sequence,
|
|
295
|
-
#
|
|
295
|
+
# its own comment) — the write/verify/revert sequence, per
|
|
296
|
+
# candidate (this method's own comment explains why it cannot
|
|
296
297
|
# batch), is one coherent unit; splitting it would scatter
|
|
297
298
|
# before_meta/live_meta/applied_by_file state across methods for
|
|
298
299
|
# no gain.
|
|
@@ -323,7 +324,7 @@ module Hecks
|
|
|
323
324
|
live_meta[target_file] = text
|
|
324
325
|
Codemod::META_FILES.each { |f| File.write(f, live_meta[f]) }
|
|
325
326
|
|
|
326
|
-
#
|
|
327
|
+
# **Dry run still verifies** — see run_example_domains' own
|
|
327
328
|
# comment; the edit is always written and rebooted for real,
|
|
328
329
|
# then always reverted afterward when dry-run (whether or not
|
|
329
330
|
# it was safe) so the next candidate is judged against the
|
data/lib/hecks/construct.rb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
# The invisible field a built construct carries.
|
|
3
3
|
#
|
|
4
|
-
# A construct is a
|
|
4
|
+
# A construct is a record with an owner chain — the chapter (Bluebook)
|
|
5
5
|
# owns its aggregates, an aggregate owns everything declared on it — and the
|
|
6
6
|
# bluebook identity is carried in its own field, under a `hecks_` prefix that
|
|
7
7
|
# no domain attribute can collide with. Invisible means exactly that: not an
|
|
8
8
|
# attribute, not a key in `to_h`, not a reader on instances. Framework
|
|
9
9
|
# metadata about the construct, not part of the domain it describes.
|
|
10
10
|
#
|
|
11
|
-
# The identity is
|
|
11
|
+
# The identity is computed by walking owners rather than stamped, so nothing
|
|
12
12
|
# has to be re-stamped when a chapter is assembled after its aggregates:
|
|
13
13
|
#
|
|
14
14
|
# Pizzas the chapter — no owner
|
|
@@ -17,7 +17,7 @@ module Hecks
|
|
|
17
17
|
#
|
|
18
18
|
# That spelling is not invented here. It is the id `MetaValidator::Judge`
|
|
19
19
|
# already mints in `#identify`, so a construct and the meta-domain's
|
|
20
|
-
# record
|
|
20
|
+
# record of that construct carry the same identity, and there is no
|
|
21
21
|
# translation table between them to be quietly wrong in.
|
|
22
22
|
#
|
|
23
23
|
# Usage:
|
|
@@ -37,7 +37,7 @@ module Hecks
|
|
|
37
37
|
attr_writer :hecks_name
|
|
38
38
|
|
|
39
39
|
# A chapter is the only construct that legitimately has no owner. Everything
|
|
40
|
-
# else is
|
|
40
|
+
# else is declared in something, so a missing owner is an unstamped construct
|
|
41
41
|
# rather than a top — see hecks_fqn.
|
|
42
42
|
attr_writer :hecks_root
|
|
43
43
|
|
|
@@ -47,11 +47,11 @@ module Hecks
|
|
|
47
47
|
def hecks_name = @hecks_name
|
|
48
48
|
|
|
49
49
|
# How this construct joins its owner. An aggregate is a member of its
|
|
50
|
-
# chapter's namespace (`::`) ; everything else is declared
|
|
50
|
+
# chapter's namespace (`::`) ; everything else is declared on its owner
|
|
51
51
|
# (`.`). Overridden by Aggregate, defaulted here for every other construct.
|
|
52
52
|
def hecks_separator = "."
|
|
53
53
|
|
|
54
|
-
#
|
|
54
|
+
# Refuses rather than guesses. A construct with no owner and no claim to be a
|
|
55
55
|
# chapter has simply not been stamped yet — entity commands are in that state
|
|
56
56
|
# while entities are still IR objects — and answering the bare name would be a
|
|
57
57
|
# plausible half-truth that no test would notice. That shape of falsehood is
|
data/lib/hecks/corpus.rb
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
require_relative "fuzzing/target_capabilities"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
# **The corpus, discovered** — every place in this repo that holds a real
|
|
5
|
+
# domain, named once.
|
|
6
|
+
#
|
|
7
|
+
# This used to be spelled out separately by every consumer that walks
|
|
8
|
+
# it — spec/corpus_spec.rb, spec/model_check_spec.rb, bin/model_check,
|
|
9
|
+
# spec/parser_parity_spec.rb, bin/fuzz, Fuzzing::CombinationMiner — and
|
|
10
|
+
# the copies had already drifted: only the model checker saw
|
|
11
|
+
# `qa/bluebook`, only parser parity saw `spec/fixtures`, and nothing but
|
|
12
|
+
# bin/fuzz's own sweep ever saw `qa/stress_domains`. One table here, and
|
|
13
|
+
# each consumer names the KINDS it walks rather than re-deriving where
|
|
14
|
+
# those kinds live.
|
|
15
|
+
#
|
|
16
|
+
# Plain Dir/File only — bin/ scripts require this before (or without)
|
|
17
|
+
# booting anything.
|
|
18
|
+
module Corpus
|
|
19
|
+
ROOT = File.expand_path("../..", __dir__).freeze
|
|
20
|
+
|
|
21
|
+
Member = Struct.new(:stem, :kind, :path)
|
|
22
|
+
|
|
23
|
+
# **One domain per directory** — its bluebooks sit in `<dir>/bluebook/`
|
|
24
|
+
# or directly in `<dir>` (see `bluebook_files`). Stemmed by directory.
|
|
25
|
+
DIRECTORY_KINDS = {
|
|
26
|
+
example: "examples/*",
|
|
27
|
+
stress: "qa/stress_domains/*",
|
|
28
|
+
semantics: "spec/corpus/semantics/domains/*"
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
# **One chapter per file**. Stemmed by the path below the glob's fixed
|
|
32
|
+
# prefix, so a nested fixture keeps its subdirectory (`eras/base`)
|
|
33
|
+
# and never collides with a same-named file elsewhere in the kind.
|
|
34
|
+
FILE_KINDS = {
|
|
35
|
+
grammar: "lib/hecks/grammar/*.bluebook",
|
|
36
|
+
framework: "lib/hecks/framework/bluebook/*.bluebook",
|
|
37
|
+
qa: "qa/bluebook/*.bluebook",
|
|
38
|
+
language: "lib/hecks/language/**/*.bluebook",
|
|
39
|
+
deploy: "lib/hecks/deploy/bluebook/*.bluebook",
|
|
40
|
+
fixture: "spec/fixtures/**/*.bluebook"
|
|
41
|
+
}.freeze
|
|
42
|
+
|
|
43
|
+
KINDS = (DIRECTORY_KINDS.keys + FILE_KINDS.keys).freeze
|
|
44
|
+
|
|
45
|
+
# Where a bluebook the sweep does not boot goes instead. Not a filter:
|
|
46
|
+
# nothing leaves `sweepable_domains` without naming the check that owns
|
|
47
|
+
# it, and spec/corpus_accounting_spec.rb proves each destination exists
|
|
48
|
+
# and actually exercises what is routed to it.
|
|
49
|
+
#
|
|
50
|
+
# check: :named_in — `destination` names every routed file
|
|
51
|
+
# (`names: :each_file`) or the given text
|
|
52
|
+
# check: :gitignored — `destination` holds the ignore rule `names`,
|
|
53
|
+
# and nothing matching is committed
|
|
54
|
+
# check: :gap — no check exercises these yet. Listed so the
|
|
55
|
+
# gap is visible; the accounting spec keeps it
|
|
56
|
+
# pending and fails the moment one appears.
|
|
57
|
+
#
|
|
58
|
+
# Ordered — a bluebook belongs to the first route it matches, so a
|
|
59
|
+
# specific destination sits above the catch-all for its shape.
|
|
60
|
+
Route = Struct.new(:pattern, :check, :destination, :names, :why)
|
|
61
|
+
|
|
62
|
+
ROUTES = [
|
|
63
|
+
Route.new(%r{\Atmp/}, :gitignored, ".gitignore", "tmp/",
|
|
64
|
+
"scratch output — generated and mined candidate domains, fuzz failures — never committed"),
|
|
65
|
+
Route.new(%r{/\.aws-sam/}, :gitignored, ".gitignore", ".aws-sam/",
|
|
66
|
+
"SAM build output — a vendored copy of real sources, never committed"),
|
|
67
|
+
Route.new(%r{/data/eras/}, :gitignored, ".gitignore", "**/data/eras/",
|
|
68
|
+
"era snapshots a file adapter writes at runtime, never committed"),
|
|
69
|
+
Route.new(%r{\Arust/}, :named_in, "rust/parser/tests/gates.rs", :each_file,
|
|
70
|
+
"the Rust parser's own fixtures, each loaded by its gate tests"),
|
|
71
|
+
Route.new(%r{/translations/}, :named_in, "spec/translation/committed_edges_spec.rb", "bluebook/translations",
|
|
72
|
+
"translation edges, not chapters; each must load, chain era to era, and end at the storage " \
|
|
73
|
+
"shape its bluebook declares today"),
|
|
74
|
+
Route.new(%r{\Alib/hecks/forms/examples/}, :named_in, "spec/forms/app_spec.rb", :each_file,
|
|
75
|
+
"a Forms presentation config wearing the .bluebook extension"),
|
|
76
|
+
Route.new(%r{\Aspec/fixtures/eras/}, :named_in, "spec/runtime/storage_shape_spec.rb", "fixtures/eras",
|
|
77
|
+
"deliberately conflicting versions of one domain; each must classify as the verdict " \
|
|
78
|
+
"its filename declares (bump_* / same_*)"),
|
|
79
|
+
Route.new(%r{\Aspec/fixtures/model_check/}, :named_in, "spec/model_check_spec.rb", :each_file,
|
|
80
|
+
"domains broken on purpose; each must produce exactly the finding kinds it is built to trigger"),
|
|
81
|
+
# bin/fuzz sweeps it too, once Fuzzing::Replay coerces value-object
|
|
82
|
+
# args before recomputing givens — today its oracle reads raw args
|
|
83
|
+
# while dispatch coerces, so checkout_fixture's VO-reading given
|
|
84
|
+
# reads as wrongly admitted.
|
|
85
|
+
Route.new(%r{\Aspec/fixtures/rust_host/}, :named_in, "rust/host/src/web.rs", "checkout_fixture",
|
|
86
|
+
"the Rust host's checkout fixture, pinned by its web and /api tests")
|
|
87
|
+
].freeze
|
|
88
|
+
|
|
89
|
+
module_function
|
|
90
|
+
|
|
91
|
+
def members(*kinds, root: ROOT)
|
|
92
|
+
kinds = KINDS if kinds.empty?
|
|
93
|
+
kinds.flat_map do |kind|
|
|
94
|
+
if (glob = DIRECTORY_KINDS[kind])
|
|
95
|
+
Dir.glob(File.join(root, glob)).select { |path| File.directory?(path) }.sort
|
|
96
|
+
.map { |dir| Member.new(File.basename(dir), kind, dir) }
|
|
97
|
+
elsif (glob = FILE_KINDS[kind])
|
|
98
|
+
prefix = File.join(root, glob[/\A[^*]*/].chomp("/"))
|
|
99
|
+
Dir.glob(File.join(root, glob))
|
|
100
|
+
.map { |file| Member.new(file.delete_prefix("#{prefix}/").delete_suffix(".bluebook"), kind, file) }
|
|
101
|
+
else
|
|
102
|
+
raise ArgumentError, "unknown corpus kind #{kind.inspect} — known: #{KINDS.join(', ')}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# What a boot loads for a member: a directory kind's bluebook
|
|
108
|
+
# directory, a file kind's own file. `nil` for a directory holding no
|
|
109
|
+
# bluebook at all.
|
|
110
|
+
def source_of(member)
|
|
111
|
+
DIRECTORY_KINDS.key?(member.kind) ? bluebook_dir(member.path) : member.path
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Where a domain path keeps its bluebooks — `<domain>/bluebook/*.bluebook`
|
|
115
|
+
# (every example and stress domain), or the directory itself
|
|
116
|
+
# (`qa/bluebook`). `nil` when neither holds a bluebook.
|
|
117
|
+
def bluebook_files(domain_path)
|
|
118
|
+
[File.join(domain_path, "bluebook"), domain_path].each do |dir|
|
|
119
|
+
files = Dir[File.join(dir, "*.bluebook")]
|
|
120
|
+
return files unless files.empty?
|
|
121
|
+
end
|
|
122
|
+
nil
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def bluebook_dir(domain_path)
|
|
126
|
+
files = bluebook_files(domain_path)
|
|
127
|
+
files && File.dirname(files.first)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# The route a repo-relative path takes instead of the sweep — the
|
|
131
|
+
# first one it matches — or `nil` when the sweep boots it.
|
|
132
|
+
def route_for(relative_path)
|
|
133
|
+
ROUTES.find { |route| route.pattern.match?(relative_path) }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# What bin/model_check and spec/model_check_spec.rb walk — every kind,
|
|
137
|
+
# less the language (examined as one judged chapter, not file by file)
|
|
138
|
+
# and deploy chapters (the SAM projector's own inputs), and less any
|
|
139
|
+
# member a route already sends to a destination of its own: the
|
|
140
|
+
# broken-on-purpose model_check fixtures must produce their findings
|
|
141
|
+
# there, so a clean-corpus gate here would be the wrong check for them.
|
|
142
|
+
MODEL_CHECK_KINDS = %i[example grammar framework qa stress fixture].freeze
|
|
143
|
+
|
|
144
|
+
def model_check_members(root: ROOT)
|
|
145
|
+
members(*MODEL_CHECK_KINDS, root: root).reject { |member| route_for(member.path.delete_prefix("#{root}/")) }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# **The ledger sweeps itself** — its own chapter is a domain like any
|
|
149
|
+
# other, and the one rotation member that is neither an example nor a
|
|
150
|
+
# stress domain.
|
|
151
|
+
ROTATION_LEDGER = { "quality_control" => "qa/bluebook" }.freeze
|
|
152
|
+
|
|
153
|
+
# What the QA rotation is made of — every example and stress domain
|
|
154
|
+
# this repository owns, plus the ledger, as `reference => repo-relative
|
|
155
|
+
# path`: exactly the shape `Target.path` is stored in.
|
|
156
|
+
#
|
|
157
|
+
# Derived, because the hand-kept version silently went stale.
|
|
158
|
+
# `bin/qa_seed_targets` carried a literal list naming three of the
|
|
159
|
+
# thirteen stress domains; the other ten were authored, argued for in
|
|
160
|
+
# their own NOTES.md, several promoted by `bin/qa_generated_domains
|
|
161
|
+
# --promote` — and never swept once, because a `Target` row is what
|
|
162
|
+
# puts a domain in the rotation and nothing tied that list to the
|
|
163
|
+
# corpus. Promotion only ever printed the `target.identify` line for a
|
|
164
|
+
# human to run.
|
|
165
|
+
def rotation_targets(root: ROOT)
|
|
166
|
+
members(:example, :stress, root: root)
|
|
167
|
+
.to_h { |member| [member.stem, member.path.delete_prefix("#{root}/")] }
|
|
168
|
+
.merge(ROTATION_LEDGER)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Every bootable domain in the project, not a hand-kept list — any
|
|
172
|
+
# directory holding a `.bluebook` no route sends elsewhere, a
|
|
173
|
+
# `bluebook/` folder standing for the domain directory around it.
|
|
174
|
+
def sweepable_domains(root = ROOT)
|
|
175
|
+
Dir.chdir(root) do
|
|
176
|
+
Dir.glob("**/*.bluebook")
|
|
177
|
+
.reject { |path| route_for(path) }
|
|
178
|
+
.map { |path| File.dirname(path) }
|
|
179
|
+
.map { |dir| File.basename(dir) == "bluebook" ? File.dirname(dir) : dir }
|
|
180
|
+
.uniq.sort
|
|
181
|
+
.map { |dir| File.join(root, dir) }
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# The domain directory a member stands for, spelled the way
|
|
186
|
+
# `sweepable_domains` spells it: a `bluebook/` folder is its parent.
|
|
187
|
+
def domain_dir_of(member)
|
|
188
|
+
dir = File.directory?(member.path) ? member.path : File.dirname(member.path)
|
|
189
|
+
File.basename(dir) == "bluebook" ? File.dirname(dir) : dir
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# ── The Rust-facing corpus ─────────────────────────────────────────
|
|
193
|
+
#
|
|
194
|
+
# Every Rust-facing list (the fuzz bridge, the codegen drift check,
|
|
195
|
+
# rust coverage, codegen parity) used to be typed out by hand, and
|
|
196
|
+
# each had drifted from `rust/Cargo.toml`'s `[features]`: fuzzing saw
|
|
197
|
+
# 8 of 20 features. These read the one source instead. The
|
|
198
|
+
# `rust/src/generated/` modules split into buckets, and
|
|
199
|
+
# spec/corpus_rust_spec.rb proves every Cargo feature and every
|
|
200
|
+
# generated module lands in exactly one of them:
|
|
201
|
+
#
|
|
202
|
+
# rust_domains an in-repo domain directory with a Cargo feature
|
|
203
|
+
# of its own: fuzzed, regenerated, coverage- and
|
|
204
|
+
# parity-checked
|
|
205
|
+
# framework chapters no feature and no merged.rs; written as a side
|
|
206
|
+
# effect of every `uses_framework` domain's regen
|
|
207
|
+
# RUST_ELSEWHERE a feature with no in-repo domain directory, and
|
|
208
|
+
# the check that owns it instead
|
|
209
|
+
GENERATED_DIR = "rust/src/generated".freeze
|
|
210
|
+
RUST_DOMAIN_KINDS = %i[example stress fixture].freeze
|
|
211
|
+
|
|
212
|
+
RustDomain = Struct.new(:feature, :dir, :kind)
|
|
213
|
+
|
|
214
|
+
# `check: :named_in` — `destination` must name `names`.
|
|
215
|
+
# `check: :external` — no in-repo domain directory may carry the name;
|
|
216
|
+
# the day one does, it belongs in rust_domains.
|
|
217
|
+
Elsewhere = Struct.new(:check, :destination, :names, :why)
|
|
218
|
+
|
|
219
|
+
RUST_ELSEWHERE = {
|
|
220
|
+
"meta" => Elsewhere.new(:named_in, "spec/codegen_parity_spec.rb", "bluebook_language",
|
|
221
|
+
"the self-hosted grammar (lib/hecks/language), not a domain directory — every " \
|
|
222
|
+
"bin/project_rust run rewrites it (so the drift check diffs it), codegen parity " \
|
|
223
|
+
"checks it as bluebook_language, and there is no directory to fuzz"),
|
|
224
|
+
"embryonaut" => Elsewhere.new(:external, "~/Projects/embryonautfoundersapp", "embryonaut",
|
|
225
|
+
"an external product's domain — its bluebook, regeneration and parity are owed " \
|
|
226
|
+
"by its own repo; here bin/rust_coverage checks only the committed snapshot")
|
|
227
|
+
}.freeze
|
|
228
|
+
|
|
229
|
+
# **Shrink-only**. A generated module `bin/rust_coverage` still reports a
|
|
230
|
+
# gap for. `bin/corpus --rust-coverage` requires each of these to
|
|
231
|
+
# still fail, so an entry that starts passing breaks the build until
|
|
232
|
+
# it is deleted here.
|
|
233
|
+
RUST_COVERAGE_PENDING = {}.freeze
|
|
234
|
+
|
|
235
|
+
def cargo_features_table(root: ROOT)
|
|
236
|
+
File.read(File.join(root, "rust/Cargo.toml"))[Fuzzing::TargetCapabilities::FEATURES_TABLE] || ""
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def cargo_features(root: ROOT)
|
|
240
|
+
cargo_features_table(root: root).scan(/^(\w+)\s*=\s*\[\]/).flatten
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# The feature `bin/project_rust` last wrote as Cargo's `default`.
|
|
244
|
+
def cargo_default(root: ROOT)
|
|
245
|
+
cargo_features_table(root: root)[/^default\s*=\s*\["(\w+)"\]/, 1]
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Every in-repo domain directory whose name is a Cargo feature, sorted
|
|
249
|
+
# by path. When the module is already generated, its metadata.rs stamp
|
|
250
|
+
# decides which directory it came from — a directory name alone is not
|
|
251
|
+
# enough: spec/fixtures/qa_discover_external_domains vendors a second
|
|
252
|
+
# `examples/pizzas` that no Cargo feature was ever generated from.
|
|
253
|
+
def rust_domains(root: ROOT)
|
|
254
|
+
features = cargo_features(root: root)
|
|
255
|
+
members(*RUST_DOMAIN_KINDS, root: root)
|
|
256
|
+
.map { |member| [domain_dir_of(member), member.kind] }
|
|
257
|
+
.uniq(&:first)
|
|
258
|
+
.select { |dir, _| rust_domain_dir?(dir, features, root) }
|
|
259
|
+
.sort_by(&:first)
|
|
260
|
+
.map { |dir, kind| RustDomain.new(File.basename(dir).downcase, dir, kind) }
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def rust_domain_dir?(dir, features, root)
|
|
264
|
+
feature = File.basename(dir).downcase
|
|
265
|
+
source = generated_source(feature, root: root)
|
|
266
|
+
features.include?(feature) && (source.nil? || source == dir.delete_prefix("#{root}/"))
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Where a generated module came from, read off the stamp bin/project_rust
|
|
270
|
+
# writes into its metadata.rs — `examples/pizzas`, `/abs/path/embryonaut`,
|
|
271
|
+
# `the self-hosted language (lib/hecks/language/bluebook)`, with any
|
|
272
|
+
# ` (uses_framework "X")` suffix dropped. `nil` when not generated.
|
|
273
|
+
SOURCE_STAMP = %r{GENERATED by bin/project_rust — (.+?)(?: \(uses_framework "\w+"\))?'s own canonical IR,}
|
|
274
|
+
|
|
275
|
+
def generated_source(module_name, root: ROOT)
|
|
276
|
+
metadata = File.join(root, GENERATED_DIR, module_name, "metadata.rs")
|
|
277
|
+
File.file?(metadata) ? File.read(metadata)[SOURCE_STAMP, 1] : nil
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def generated_modules(root: ROOT)
|
|
281
|
+
Dir.children(File.join(root, GENERATED_DIR)).select { |name| File.directory?(File.join(root, GENERATED_DIR, name)) }.sort
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def generated?(feature, root: ROOT)
|
|
285
|
+
File.file?(File.join(root, GENERATED_DIR, feature, "merged.rs"))
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Framework members `bin/project_rust` writes a module for as a side
|
|
289
|
+
# effect of some `uses_framework` domain — a module, no merged.rs.
|
|
290
|
+
def rust_framework_chapters(root: ROOT)
|
|
291
|
+
modules = generated_modules(root: root)
|
|
292
|
+
members(:framework, root: root).map(&:stem)
|
|
293
|
+
.select { |stem| modules.include?(stem) && !generated?(stem, root: root) }
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# The regeneration order the drift check runs. Sorted by path, so
|
|
297
|
+
# which domain runs last — and so wins Cargo's `default`, mod.rs's cfg
|
|
298
|
+
# comments and the shared framework modules' attribution stamp — is a
|
|
299
|
+
# fact of the sorted list, not a hand-picked order. (The old hand list
|
|
300
|
+
# put waybill last; PR #667 moved to the sort, which makes
|
|
301
|
+
# has_many_fixture last.) spec/corpus_rust_spec.rb pins last == default.
|
|
302
|
+
def rust_regen_order(root: ROOT)
|
|
303
|
+
rust_domains(root: root).select { |domain| generated?(domain.feature, root: root) }
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# The chapter a bluebook declares — `Hecks.bluebook "<Name>"`, read
|
|
307
|
+
# off the file rather than guessed from its name (a grammar chapter's
|
|
308
|
+
# file is named after its role, `aggregate.bluebook`, while its
|
|
309
|
+
# chapter is always "Bluebook"). Scans the whole file: a framework
|
|
310
|
+
# member's header comment can run past any fixed line cap.
|
|
311
|
+
def chapter_name_of(bluebook_path)
|
|
312
|
+
bluebook_path = Array(bluebook_path).first
|
|
313
|
+
header = File.foreach(bluebook_path).find { |line| line =~ /\A\s*Hecks\.bluebook\s+"([^"]+)"/ }
|
|
314
|
+
header && Regexp.last_match(1)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Deprecation warnings this gem raises about its own API. Not a singleton
|
|
2
|
+
# method on `Hecks` itself: the self-hosted language declares every word
|
|
3
|
+
# that module answers (spec/syntax_conformance_spec.rb), and a warning
|
|
4
|
+
# about an old call shape is not a word of the language — callers name
|
|
5
|
+
# `Hecks::Deprecation.call` in full.
|
|
6
|
+
module Hecks
|
|
7
|
+
# One warning per call site, naming the caller's line — never a line
|
|
8
|
+
# inside this gem. A deprecated shape usually reaches the runtime through
|
|
9
|
+
# a forwarding door (`Hecks::Router`, a namespace shortcut, a
|
|
10
|
+
# `RemoteDispatcher`), so the site reported is the first frame outside
|
|
11
|
+
# `lib/hecks`, which is the line a reader can actually change.
|
|
12
|
+
#
|
|
13
|
+
# Hecks::Deprecation.call(:legacy_dispatch_args, "pass facts in with:")
|
|
14
|
+
# # warns once: "spec/foo_spec.rb:12: warning: pass facts in with:"
|
|
15
|
+
#
|
|
16
|
+
# Per key, in this order:
|
|
17
|
+
# `allowing(key) { ... }` — suppressed for the block, this thread only
|
|
18
|
+
# (the spec that tests a deprecation itself)
|
|
19
|
+
# `raise_on!(key)` — raises `Hecks::Deprecation::Error` (the
|
|
20
|
+
# test suite, so no spec reintroduces it);
|
|
21
|
+
# with a block, only where the block says so,
|
|
22
|
+
# given the site, so a suite can raise on
|
|
23
|
+
# new sites while a known worklist warns
|
|
24
|
+
# HECKS_SILENCE_DEPRECATIONS=1 — silent
|
|
25
|
+
# otherwise — `Kernel#warn`, once per key and call site
|
|
26
|
+
module Deprecation
|
|
27
|
+
class Error < StandardError
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
LIB_ROOT = File.expand_path(__dir__)
|
|
31
|
+
LIB_ENTRY = File.expand_path("../hecks.rb", __dir__)
|
|
32
|
+
|
|
33
|
+
EVERYWHERE = ->(_site) { true }
|
|
34
|
+
|
|
35
|
+
@seen = {}
|
|
36
|
+
@raising = {}
|
|
37
|
+
@mutex = Mutex.new
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
# { key => where it raises instead of warning }.
|
|
41
|
+
attr_reader :raising
|
|
42
|
+
|
|
43
|
+
def raise_on!(*keys, &where)
|
|
44
|
+
keys.each { |key| @raising[key] = where || EVERYWHERE }
|
|
45
|
+
keys
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def call(key, message)
|
|
49
|
+
return if allowed?(key)
|
|
50
|
+
|
|
51
|
+
uplevel, site = external_frame
|
|
52
|
+
raise Error, "#{site}: #{message}" if @raising[key]&.call(site)
|
|
53
|
+
return if ENV["HECKS_SILENCE_DEPRECATIONS"] == "1"
|
|
54
|
+
return unless first_sighting?(key, site)
|
|
55
|
+
|
|
56
|
+
Kernel.warn(message, uplevel: uplevel)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def allowing(key)
|
|
60
|
+
allowed = Thread.current[:hecks_allowed_deprecations] ||= []
|
|
61
|
+
allowed.push(key)
|
|
62
|
+
yield
|
|
63
|
+
ensure
|
|
64
|
+
allowed.pop
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Forget every site already warned about — for specs only.
|
|
68
|
+
def reset! = @mutex.synchronize { @seen.clear }
|
|
69
|
+
|
|
70
|
+
# The first caller frame outside this gem — where a deprecated call
|
|
71
|
+
# was written.
|
|
72
|
+
def external?(frame)
|
|
73
|
+
path = frame.absolute_path || frame.path
|
|
74
|
+
!(path.start_with?("<internal:") || path == LIB_ENTRY || path.start_with?("#{LIB_ROOT}/"))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def allowed?(key) = Array(Thread.current[:hecks_allowed_deprecations]).include?(key)
|
|
80
|
+
|
|
81
|
+
def first_sighting?(key, site)
|
|
82
|
+
@mutex.synchronize { @seen.key?([key, site]) ? false : (@seen[[key, site]] = true) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# `uplevel:` counts from the frame that calls `Kernel.warn` — `call`,
|
|
86
|
+
# this method's caller — and `caller_locations(1)` starts at that
|
|
87
|
+
# same frame, so the index found here is the uplevel to pass.
|
|
88
|
+
def external_frame
|
|
89
|
+
frames = caller_locations(1)
|
|
90
|
+
index = frames.index { |frame| external?(frame) } || (frames.size - 1)
|
|
91
|
+
[index, "#{frames[index].path}:#{frames[index].lineno}"]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|