hecks 2.7.0 → 2.9.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/exe/hecks +11 -0
- data/lib/hecks/adapters/driven/claude_code.rb +4 -104
- data/lib/hecks/adapters/driven/d1.rb +44 -237
- data/lib/hecks/adapters/driven/folder.rb +17 -76
- data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
- data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
- data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
- data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
- data/lib/hecks/adapters/driven/heki.rb +13 -42
- data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
- data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
- data/lib/hecks/adapters/driven/lambda.rb +10 -84
- data/lib/hecks/adapters/driven/local_storage.rb +5 -40
- data/lib/hecks/adapters/driven/memory.rb +18 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
- data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
- data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
- data/lib/hecks/adapters/driven/postgres.rb +145 -134
- data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
- data/lib/hecks/adapters/driven/prism.rb +7 -30
- data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
- data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
- data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
- data/lib/hecks/adapters/driven/sqlite.rb +128 -65
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
- data/lib/hecks/adapters/driven.rb +5 -36
- data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
- data/lib/hecks/behaviors/dsl.rb +7 -25
- data/lib/hecks/behaviors/expectations.rb +32 -142
- data/lib/hecks/behaviors/ir.rb +7 -20
- data/lib/hecks/behaviors/rspec.rb +6 -17
- data/lib/hecks/behaviors/runner.rb +10 -34
- data/lib/hecks/behaviors.rb +4 -8
- data/lib/hecks/bluebook/aggregate.rb +9 -75
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
- data/lib/hecks/bluebook/assembly/build.rb +4 -15
- data/lib/hecks/bluebook/assembly/contract.rb +15 -136
- data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
- data/lib/hecks/bluebook/assembly/marks.rb +31 -223
- data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
- data/lib/hecks/bluebook/assembly.rb +6 -45
- data/lib/hecks/bluebook/attribute.rb +12 -61
- data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
- data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
- data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
- data/lib/hecks/bluebook/behaviour/command.rb +14 -66
- data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
- data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
- data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
- data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
- data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
- data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
- data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
- data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
- data/lib/hecks/bluebook/capabilities.rb +17 -49
- data/lib/hecks/bluebook/chapter.rb +10 -48
- data/lib/hecks/bluebook/command.rb +23 -81
- data/lib/hecks/bluebook/domain_port.rb +14 -63
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
- data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
- data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
- data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
- data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
- data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
- data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
- data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
- data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
- data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
- data/lib/hecks/bluebook/entity.rb +13 -71
- data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
- data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
- data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
- data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
- data/lib/hecks/bluebook/expression/resolver.rb +37 -660
- data/lib/hecks/bluebook/hexagon.rb +5 -28
- data/lib/hecks/bluebook/lifecycle.rb +5 -16
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
- data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
- data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
- data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
- data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
- data/lib/hecks/bluebook/meta_validator.rb +79 -349
- data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
- data/lib/hecks/bluebook/model_check.rb +102 -600
- data/lib/hecks/bluebook/pattern_subset.rb +11 -69
- data/lib/hecks/bluebook/process_manager.rb +13 -83
- data/lib/hecks/bluebook/project_loader.rb +3 -39
- data/lib/hecks/bluebook/project_register.rb +12 -60
- data/lib/hecks/bluebook/query.rb +9 -48
- data/lib/hecks/bluebook/read_model.rb +57 -81
- data/lib/hecks/bluebook/reference.rb +7 -41
- data/lib/hecks/bluebook/smoke_test.rb +8 -98
- data/lib/hecks/bluebook/synthesizer.rb +13 -62
- data/lib/hecks/bluebook/translation.rb +26 -62
- data/lib/hecks/bluebook/value_object.rb +11 -36
- data/lib/hecks/bluebook.rb +5 -22
- data/lib/hecks/cache_dir.rb +84 -0
- data/lib/hecks/cli/document.rb +86 -0
- data/lib/hecks/cli/ir.rb +45 -0
- data/lib/hecks/cli/mcp.rb +31 -0
- data/lib/hecks/cli/mcp_door.rb +366 -0
- data/lib/hecks/cli/model_check.rb +203 -0
- data/lib/hecks/cli/project_cli.rb +90 -0
- data/lib/hecks/cli/project_diagrams.rb +36 -0
- data/lib/hecks/cli/run.rb +173 -0
- data/lib/hecks/cli/smoke_test.rb +45 -0
- data/lib/hecks/cli/stores.rb +53 -0
- data/lib/hecks/cli.rb +160 -0
- data/lib/hecks/construct.rb +8 -34
- data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
- data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
- data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
- data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
- data/lib/hecks/embryonaut_bluebook.rb +4 -71
- data/lib/hecks/facade/cli_door.rb +24 -106
- data/lib/hecks/facade/cli_runner.rb +28 -172
- data/lib/hecks/facade/command_request.rb +13 -33
- data/lib/hecks/facade/handle.rb +34 -121
- data/lib/hecks/facade/json_door.rb +31 -146
- data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
- data/lib/hecks/facade/surface/chapter.rb +10 -54
- data/lib/hecks/facade/surface.rb +8 -23
- data/lib/hecks/facade.rb +10 -25
- data/lib/hecks/forms/app.rb +15 -70
- data/lib/hecks/forms/command_form_renderer.rb +10 -29
- data/lib/hecks/forms/field_renderer.rb +17 -156
- data/lib/hecks/forms/field_shape.rb +30 -222
- data/lib/hecks/forms/html.rb +12 -47
- data/lib/hecks/forms/index_renderer.rb +1 -4
- data/lib/hecks/forms/page.rb +3 -9
- data/lib/hecks/forms/params.rb +14 -60
- data/lib/hecks/forms/port_argument.rb +2 -19
- data/lib/hecks/forms/query_form_renderer.rb +4 -79
- data/lib/hecks/forms/record_renderer.rb +8 -75
- data/lib/hecks/forms/record_table.rb +7 -50
- data/lib/hecks/forms/reference_options.rb +3 -30
- data/lib/hecks/forms/value_object_shape.rb +8 -35
- data/lib/hecks/forms.rb +5 -33
- data/lib/hecks/fqn.rb +5 -12
- data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
- data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
- data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
- data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
- data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
- data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
- data/lib/hecks/framework.rb +11 -80
- data/lib/hecks/freezer.rb +5 -39
- data/lib/hecks/grammar/expression.bluebook +15 -40
- data/lib/hecks/grammar/translation.bluebook +3 -6
- data/lib/hecks/grammar.rb +16 -41
- data/lib/hecks/ir.rb +14 -83
- data/lib/hecks/language/adapter.bluebook +2 -7
- data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
- data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
- data/lib/hecks/language/bluebook/command.bluebook +27 -144
- data/lib/hecks/language/bluebook/entity.bluebook +24 -97
- data/lib/hecks/language/bluebook/policy.bluebook +16 -67
- data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
- data/lib/hecks/language/bluebook/projection.bluebook +126 -60
- data/lib/hecks/language/bluebook/query.bluebook +12 -43
- data/lib/hecks/language/bluebook/shape.bluebook +23 -73
- data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
- data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
- data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
- data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
- data/lib/hecks/language/oidc.json +35 -0
- data/lib/hecks/language/port.bluebook +4 -17
- data/lib/hecks/language/translation/translation.bluebook +3 -9
- data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
- data/lib/hecks/language/world/wiring.bluebook +1 -3
- data/lib/hecks/language/world/world.bluebook +5 -7
- data/lib/hecks/literal.rb +8 -50
- data/lib/hecks/mcp_door_scope.rb +95 -0
- data/lib/hecks/mcp_stdio_guard.rb +4 -41
- data/lib/hecks/naming.rb +29 -137
- data/lib/hecks/ports/access_control.rb +7 -23
- data/lib/hecks/ports/agent/answers.rb +5 -23
- data/lib/hecks/ports/agent.rb +47 -172
- data/lib/hecks/ports/authentication.rb +12 -39
- data/lib/hecks/ports/authorization.rb +16 -43
- data/lib/hecks/ports/clock.rb +5 -45
- data/lib/hecks/ports/extraction.rb +7 -12
- data/lib/hecks/ports/identity_assignment.rb +8 -29
- data/lib/hecks/ports/identity_generation.rb +6 -29
- data/lib/hecks/ports/identity_resolution.rb +2 -10
- data/lib/hecks/ports/key_vault.rb +9 -45
- data/lib/hecks/ports/persistence/append_only.rb +62 -65
- data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
- data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
- data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
- data/lib/hecks/ports/persistence/plugin.rb +13 -42
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
- data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
- data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
- data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
- data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
- data/lib/hecks/ports/persistence/state_codec.rb +23 -103
- data/lib/hecks/ports/persistence.rb +7 -14
- data/lib/hecks/ports/projection.rb +42 -84
- data/lib/hecks/ports/query/in_memory.rb +10 -58
- data/lib/hecks/ports/query/ordering.rb +11 -32
- data/lib/hecks/ports/query.rb +14 -37
- data/lib/hecks/ports.rb +2 -8
- data/lib/hecks/projections/bootstrap_table.rb +11 -43
- data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
- data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
- data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
- data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
- data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
- data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
- data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
- data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
- data/lib/hecks/projections/deploy/fargate.rb +35 -202
- data/lib/hecks/projections/deploy/lambda.rb +151 -679
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
- data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
- data/lib/hecks/projections/deploy/preview/script.rb +2 -14
- data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
- data/lib/hecks/projections/deploy/preview/template.rb +2 -58
- data/lib/hecks/projections/deploy/preview.rb +2 -67
- data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
- data/lib/hecks/projections/deploy/scripts.rb +4 -57
- data/lib/hecks/projections/deploy/shared.rb +47 -146
- data/lib/hecks/projections/deploy/smoke.rb +4 -61
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
- data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
- data/lib/hecks/projections/deploy/template_diff.rb +5 -57
- data/lib/hecks/projections/diagrams.rb +20 -582
- data/lib/hecks/projections/glossary/html.rb +14 -130
- data/lib/hecks/projections/glossary/markdown.rb +8 -69
- data/lib/hecks/projections/glossary/mermaid.rb +9 -66
- data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
- data/lib/hecks/projections/glossary/sentences.rb +14 -159
- data/lib/hecks/projections/glossary.rb +30 -222
- data/lib/hecks/projections/ir.rb +3 -10
- data/lib/hecks/projections/model/deviations.rb +15 -42
- data/lib/hecks/projections/model.rb +12 -51
- data/lib/hecks/projections/oidc.rb +12 -56
- data/lib/hecks/projections/parser_table.rb +9 -28
- data/lib/hecks/projections/reference.rb +9 -16
- data/lib/hecks/projections/rust_vocabulary.rb +12 -250
- data/lib/hecks/projections/shape.rb +3 -29
- data/lib/hecks/projections/statements.rb +7 -86
- data/lib/hecks/projections/vocabulary.rb +6 -22
- data/lib/hecks/projections.rb +2 -16
- data/lib/hecks/projector/cli_projector.rb +60 -375
- data/lib/hecks/projector/docs_projector.rb +10 -201
- data/lib/hecks/projector/exporter.rb +46 -138
- data/lib/hecks/projector/ir_projector.rb +2 -9
- data/lib/hecks/projector/narrate_projector.rb +15 -189
- data/lib/hecks/projector/target.rb +13 -76
- data/lib/hecks/projector.rb +30 -121
- data/lib/hecks/query_specification/common/comparators.rb +5 -26
- data/lib/hecks/query_specification/common/comparison.rb +35 -143
- data/lib/hecks/query_specification/common/dsl.rb +8 -24
- data/lib/hecks/query_specification/common/null_policy.rb +20 -70
- data/lib/hecks/query_specification/common/options.rb +2 -6
- data/lib/hecks/query_specification/common/where_clause.rb +2 -10
- data/lib/hecks/query_specification/field_path.rb +37 -77
- data/lib/hecks/query_specification/hop_path.rb +12 -76
- data/lib/hecks/release/gem_pin.rb +2 -26
- data/lib/hecks/release/runner/ci_publisher.rb +93 -0
- data/lib/hecks/release/runner/commands.rb +66 -0
- data/lib/hecks/release/runner/console.rb +52 -0
- data/lib/hecks/release/runner/gem_publisher.rb +43 -0
- data/lib/hecks/release/runner/git.rb +48 -0
- data/lib/hecks/release/runner/npm_publisher.rb +75 -0
- data/lib/hecks/release/runner/preflight.rb +104 -0
- data/lib/hecks/release/runner/published.rb +55 -0
- data/lib/hecks/release/runner/tagger.rb +80 -0
- data/lib/hecks/release/runner.rb +215 -0
- data/lib/hecks/rendering.rb +4 -26
- data/lib/hecks/router/namespace_installer.rb +12 -36
- data/lib/hecks/router.rb +4 -8
- data/lib/hecks/runtime/aggregate_lock.rb +8 -33
- data/lib/hecks/runtime/boot_gates.rb +3 -18
- data/lib/hecks/runtime/caller.rb +19 -59
- data/lib/hecks/runtime/capability_graph.rb +5 -21
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
- data/lib/hecks/runtime/command_interpreter.rb +125 -307
- data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
- data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
- data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
- data/lib/hecks/runtime/command_rules/emission.rb +8 -21
- data/lib/hecks/runtime/command_rules/references.rb +23 -202
- data/lib/hecks/runtime/dependency_planning.rb +30 -102
- data/lib/hecks/runtime/dispatcher.rb +82 -241
- data/lib/hecks/runtime/entity_element.rb +40 -462
- data/lib/hecks/runtime/entity_interpreter.rb +65 -220
- data/lib/hecks/runtime/errors.rb +32 -115
- data/lib/hecks/runtime/event.rb +3 -25
- data/lib/hecks/runtime/identity.rb +18 -154
- data/lib/hecks/runtime/instance.rb +18 -150
- data/lib/hecks/runtime/interpreting.rb +11 -70
- data/lib/hecks/runtime/invocation.rb +28 -164
- data/lib/hecks/runtime/loader.rb +48 -186
- data/lib/hecks/runtime/outbox.rb +38 -261
- data/lib/hecks/runtime/policy_interpreter.rb +38 -200
- data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
- data/lib/hecks/runtime/query_interpreter.rb +45 -187
- data/lib/hecks/runtime/reaction_invocation.rb +36 -138
- data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
- data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
- data/lib/hecks/runtime/reference_hop.rb +8 -81
- data/lib/hecks/runtime/refusal_wording.rb +8 -89
- data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
- data/lib/hecks/runtime/registry/verification.rb +52 -225
- data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
- data/lib/hecks/runtime/registry.rb +103 -244
- data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
- data/lib/hecks/runtime/routing.rb +4 -15
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
- data/lib/hecks/runtime/saga_interpreter.rb +79 -263
- data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
- data/lib/hecks/runtime/tenant_check.rb +6 -68
- data/lib/hecks/runtime/tenant_scope.rb +3 -28
- data/lib/hecks/runtime/value/admission.rb +16 -67
- data/lib/hecks/runtime/value/coercion.rb +155 -641
- data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
- data/lib/hecks/runtime/value.rb +12 -84
- data/lib/hecks/runtime.rb +17 -65
- data/lib/hecks/storehouse.rb +146 -567
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
- data/lib/hecks/vendoring/git_environment.rb +2 -8
- data/lib/hecks/vendoring/git_source.rb +4 -10
- data/lib/hecks/vendoring.rb +7 -46
- data/lib/hecks/version.rb +3 -14
- data/lib/hecks/vocabulary.rb +5 -0
- data/lib/hecks.rb +32 -82
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
- metadata +30 -59
- data/lib/hecks/bench/cli.rb +0 -74
- data/lib/hecks/bench/environment.rb +0 -100
- data/lib/hecks/bench/postgres_probe.rb +0 -41
- data/lib/hecks/bench/report.rb +0 -116
- data/lib/hecks/bench/ruby_runner.rb +0 -151
- data/lib/hecks/bench/run.rb +0 -55
- data/lib/hecks/bench/rust_runner.rb +0 -165
- data/lib/hecks/bench/stats.rb +0 -75
- data/lib/hecks/bench/suite.rb +0 -172
- data/lib/hecks/bench/workload.rb +0 -137
- data/lib/hecks/bench.rb +0 -28
- data/lib/hecks/codemod.rb +0 -443
- data/lib/hecks/corpus.rb +0 -479
- data/lib/hecks/doc/reference.rb +0 -585
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
- data/lib/hecks/fuzzing/combination_miner.rb +0 -178
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
- data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
- data/lib/hecks/fuzzing/differential.rb +0 -192
- data/lib/hecks/fuzzing/domain_generator.rb +0 -871
- data/lib/hecks/fuzzing/era_boundary.rb +0 -154
- data/lib/hecks/fuzzing/form_census.rb +0 -285
- data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
- data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
- data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
- data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
- data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
- data/lib/hecks/fuzzing/properties/guards.rb +0 -398
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
- data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
- data/lib/hecks/fuzzing/properties/querying.rb +0 -260
- data/lib/hecks/fuzzing/properties.rb +0 -267
- data/lib/hecks/fuzzing/qa_settings.rb +0 -164
- data/lib/hecks/fuzzing/replay.rb +0 -930
- data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
- data/lib/hecks/fuzzing/self_consistency.rb +0 -825
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
- data/lib/hecks/fuzzing/shrinker.rb +0 -309
- data/lib/hecks/fuzzing/structural_skips.rb +0 -53
- data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
- data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
- data/lib/hecks/fuzzing/value_generator.rb +0 -340
- data/lib/hecks/fuzzing.rb +0 -23
- data/lib/hecks/grammar/evolve.rb +0 -524
- data/lib/hecks/query_ir.rb +0 -482
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
module Hecks
|
|
2
|
-
module Fuzzing
|
|
3
|
-
# A failing step list, made small enough to read.
|
|
4
|
-
#
|
|
5
|
-
# `bin/fuzz` always shrank its findings; `bin/qa_sweep` — the loop that
|
|
6
|
-
# actually finds the bugs — never did, so the ledger filled with
|
|
7
|
-
# demonstrations like "seed 25, 25 steps, step 16" and a human cut
|
|
8
|
-
# BUG#40 down to its four real steps by hand. This is `bin/fuzz`'s
|
|
9
|
-
# shrinker lifted out of that script so both callers share it, and
|
|
10
|
-
# made cheaper at the same time: a sweep's candidate check can cost a
|
|
11
|
-
# Rust subprocess (or a real Postgres round trip), where `bin/fuzz`'s
|
|
12
|
-
# only ever cost an in-process replay.
|
|
13
|
-
#
|
|
14
|
-
# ## Ownership of "same finding"
|
|
15
|
-
#
|
|
16
|
-
# The caller owns it. `call` is handed a block that
|
|
17
|
-
# answers true when a candidate step list still reproduces what the
|
|
18
|
-
# original did — `bin/fuzz` compares its own verdict signature,
|
|
19
|
-
# `bin/qa_sweep` compares `Shrinker.signature` of one mode's
|
|
20
|
-
# divergences. This module never replays anything itself, so it has
|
|
21
|
-
# no idea which engine, adapter or comparison it is minimizing for.
|
|
22
|
-
#
|
|
23
|
-
# ## Two passes, in order
|
|
24
|
-
#
|
|
25
|
-
# 1. Steps, chunks first. Removing one step at a time (what
|
|
26
|
-
# `bin/fuzz` did) costs O(n²) candidate checks on a sequence
|
|
27
|
-
# where most steps are irrelevant. Delta-debugging style: try
|
|
28
|
-
# removing halves, then quarters, … then single steps, keeping
|
|
29
|
-
# any removal that still reproduces; a single-step pass repeats
|
|
30
|
-
# until it changes nothing, so the result is 1-minimal (no one
|
|
31
|
-
# remaining step can be dropped).
|
|
32
|
-
# 2. Arguments, inside each surviving step. Unchanged from `bin/fuzz`
|
|
33
|
-
# (spec/bin_fuzz_spec.rb pins its accumulation contract): drop
|
|
34
|
-
# one key at a time from the step's current args, keep it dropped
|
|
35
|
-
# only while the finding still reproduces.
|
|
36
|
-
#
|
|
37
|
-
# ## Budget
|
|
38
|
-
#
|
|
39
|
-
# A budget, because a sweep has other targets waiting. `budget:` caps
|
|
40
|
-
# how many candidate checks one call may spend (nil = unbounded, the
|
|
41
|
-
# `bin/fuzz` behaviour). When it runs out the best candidate found so
|
|
42
|
-
# far is returned — every accepted candidate reproduced, so a partial
|
|
43
|
-
# shrink is still a correct, just less small, demonstration.
|
|
44
|
-
module Shrinker
|
|
45
|
-
Result = Struct.new(:steps, :attempts, :exhausted, keyword_init: true)
|
|
46
|
-
|
|
47
|
-
module_function
|
|
48
|
-
|
|
49
|
-
# Shrinks `steps` to the smallest step list the block still accepts as
|
|
50
|
-
# reproducing the original finding — pass 1 (steps), then pass 2
|
|
51
|
-
# (arguments), as this module's own header describes.
|
|
52
|
-
#
|
|
53
|
-
# @param steps [Array<Hash>] the step list to shrink, each step a command,
|
|
54
|
-
# query, or read-model hash as `SequenceGenerator` produces
|
|
55
|
-
# @param budget [Integer, nil] maximum candidate checks to spend; nil for
|
|
56
|
-
# unbounded
|
|
57
|
-
# @yield [candidate] tests whether a candidate step list still reproduces
|
|
58
|
-
# the finding being shrunk
|
|
59
|
-
# @yieldparam candidate [Array<Hash>] a step-dropped or argument-trimmed
|
|
60
|
-
# copy of `steps`
|
|
61
|
-
# @yieldreturn [Boolean] true if `candidate` still reproduces the finding
|
|
62
|
-
# @return [Fuzzing::Shrinker::Result] `steps:` the shrunk step list,
|
|
63
|
-
# `attempts:` how many candidate checks were spent, `exhausted:` whether
|
|
64
|
-
# the budget ran out before shrinking finished
|
|
65
|
-
# @raise [ArgumentError] if no block is given
|
|
66
|
-
def call(steps, budget: nil, &reproduces)
|
|
67
|
-
raise ArgumentError, "Shrinker.call needs a block answering whether a candidate reproduces" unless reproduces
|
|
68
|
-
|
|
69
|
-
meter = Meter.new(budget)
|
|
70
|
-
current = drop_steps(steps.dup, meter, &reproduces)
|
|
71
|
-
current = drop_arguments(current, meter, &reproduces)
|
|
72
|
-
Result.new(steps: current, attempts: meter.used, exhausted: meter.exhausted?)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Runs pass 1 of the shrink: delta-debugging removal of step chunks,
|
|
76
|
-
# halving the chunk size each full pass until single steps are tried
|
|
77
|
-
# and nothing more can be dropped.
|
|
78
|
-
#
|
|
79
|
-
# @param steps [Array<Hash>] the step list to shrink
|
|
80
|
-
# @param meter [Fuzzing::Shrinker::Meter] the shared budget meter
|
|
81
|
-
# @yield [candidate] tests whether a candidate step list still reproduces
|
|
82
|
-
# the finding being shrunk
|
|
83
|
-
# @yieldparam candidate [Array<Hash>] `steps` with one chunk removed
|
|
84
|
-
# @yieldreturn [Boolean] true if `candidate` still reproduces the finding
|
|
85
|
-
# @return [Array<Hash>] the smallest step list pass 1 could reach, or the
|
|
86
|
-
# best found so far if the budget ran out first
|
|
87
|
-
def drop_steps(steps, meter, &reproduces)
|
|
88
|
-
current = steps
|
|
89
|
-
chunk = [current.length / 2, 1].max
|
|
90
|
-
loop do
|
|
91
|
-
changed = false
|
|
92
|
-
index = 0
|
|
93
|
-
while index < current.length
|
|
94
|
-
return current if meter.exhausted?
|
|
95
|
-
|
|
96
|
-
candidate = current[0...index] + (current[(index + chunk)..] || [])
|
|
97
|
-
if candidate.empty?
|
|
98
|
-
index += chunk
|
|
99
|
-
next
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
if meter.try { reproduces.call(candidate) }
|
|
103
|
-
current = candidate
|
|
104
|
-
changed = true
|
|
105
|
-
else
|
|
106
|
-
index += chunk
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
if chunk > 1
|
|
111
|
-
chunk = [chunk / 2, 1].max
|
|
112
|
-
elsif !changed
|
|
113
|
-
break
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
current
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Runs pass 2 of the shrink: drops one argument key at a time from
|
|
120
|
-
# each surviving step's args, keeping the drop only while the
|
|
121
|
-
# finding still reproduces.
|
|
122
|
-
#
|
|
123
|
-
# See the module header's pass 2 — `args` is read by whichever
|
|
124
|
-
# spelling the step actually carries (`key?` first, never `||`,
|
|
125
|
-
# which cannot tell a stored `false` from an absent key).
|
|
126
|
-
#
|
|
127
|
-
# @param steps [Array<Hash>] the step list, already step-shrunk by `drop_steps`
|
|
128
|
-
# @param meter [Fuzzing::Shrinker::Meter] the shared budget meter
|
|
129
|
-
# @yield [candidate] tests whether a candidate step list still reproduces
|
|
130
|
-
# the finding being shrunk
|
|
131
|
-
# @yieldparam candidate [Array<Hash>] `steps` with one step's one argument
|
|
132
|
-
# key dropped
|
|
133
|
-
# @yieldreturn [Boolean] true if `candidate` still reproduces the finding
|
|
134
|
-
# @return [Array<Hash>] `steps` with every argument key `drop_arguments`
|
|
135
|
-
# accepted dropped
|
|
136
|
-
def drop_arguments(steps, meter, &reproduces)
|
|
137
|
-
steps.each_index do |position|
|
|
138
|
-
original = args_of(steps[position])
|
|
139
|
-
next unless original.is_a?(Hash)
|
|
140
|
-
|
|
141
|
-
original.each_key do |key|
|
|
142
|
-
return steps if meter.exhausted?
|
|
143
|
-
|
|
144
|
-
step = steps[position]
|
|
145
|
-
trimmed = args_of(step).reject { |name, _| name == key }
|
|
146
|
-
candidate = steps.map(&:dup)
|
|
147
|
-
candidate[position] = step.merge("args" => trimmed)
|
|
148
|
-
steps = candidate if meter.try { reproduces.call(candidate) }
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
steps
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# Reads a step's argument hash, whichever key spelling it uses.
|
|
155
|
-
#
|
|
156
|
-
# @param step [Hash] one step, string- or symbol-keyed
|
|
157
|
-
# @return [Hash, nil] the step's `"args"` value if the step is string-keyed,
|
|
158
|
-
# else its `:args` value (nil if neither key is present)
|
|
159
|
-
def args_of(step)
|
|
160
|
-
step.key?("args") ? step["args"] : step[:args]
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# Which finding this is, as a set of stable strings — the identity a
|
|
164
|
-
# shrink candidate has to keep. The `field` alone is too loose for
|
|
165
|
-
# the comparisons that carry lists: "refusals differ" on a 3-step
|
|
166
|
-
# candidate could be a different refusal split than the one the
|
|
167
|
-
# original seed found, and a shrinker that accepted it would hand
|
|
168
|
-
# back a demonstration of the wrong bug. So:
|
|
169
|
-
#
|
|
170
|
-
# refusals/queries/dry_runs/reactions — the field plus every verb
|
|
171
|
-
# (or query) named in the symmetric difference of the two sides;
|
|
172
|
-
# instances — the field plus the aggregate of every top-level key
|
|
173
|
-
# whose two sides disagree (the `#id` suffix dropped);
|
|
174
|
-
# a crash/process finding — the field plus the exception class
|
|
175
|
-
# leading its detail;
|
|
176
|
-
# anything else (a property name, a self-consistency axis) — the
|
|
177
|
-
# field, which already names the finding.
|
|
178
|
-
#
|
|
179
|
-
# `reproduces?` holds when a candidate's signature contains the
|
|
180
|
-
# original's: removing steps may add a second divergence, but it
|
|
181
|
-
# must never lose the one being demonstrated.
|
|
182
|
-
LIST_FIELDS = %w[refusals queries dry_runs reactions].freeze
|
|
183
|
-
|
|
184
|
-
CRASH_FIELDS = %w[crash process generator_crash].freeze
|
|
185
|
-
|
|
186
|
-
# Computes which finding a set of divergences is — the stable identity
|
|
187
|
-
# a shrink candidate has to keep. See this method's own preceding
|
|
188
|
-
# comment for what each field kind contributes.
|
|
189
|
-
#
|
|
190
|
-
# @param divergences [Array<Hash>] divergence entries, each at least
|
|
191
|
-
# `{field:, detail:}` plus whichever mode-specific keys carry the two
|
|
192
|
-
# compared sides
|
|
193
|
-
# @return [Set<String>] stable identity strings: each divergence's `field`,
|
|
194
|
-
# plus `detail_keys`' own per-field detail strings
|
|
195
|
-
def signature(divergences)
|
|
196
|
-
divergences.each_with_object(Set.new) do |divergence, keys|
|
|
197
|
-
field = divergence[:field].to_s
|
|
198
|
-
keys << field
|
|
199
|
-
keys.merge(detail_keys(field, divergence))
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
# Extracts the finer-grained identity strings one divergence's field
|
|
204
|
-
# contributes beyond the field name itself.
|
|
205
|
-
#
|
|
206
|
-
# @param field [String] the divergence's `field`, stringified
|
|
207
|
-
# @param divergence [Hash] the divergence entry `field` came from
|
|
208
|
-
# @return [Array<String>] extra identity strings for `field`'s own kind
|
|
209
|
-
# (list, `instances`, or crash/process); empty for any other field
|
|
210
|
-
def detail_keys(field, divergence)
|
|
211
|
-
left, right = divergence.except(:field, :detail).values.select { |v| v.is_a?(Array) || v.is_a?(Hash) }
|
|
212
|
-
if LIST_FIELDS.include?(field) then list_keys(field, left, right)
|
|
213
|
-
elsif field == "instances" then instance_keys(left, right)
|
|
214
|
-
elsif CRASH_FIELDS.include?(field) && divergence[:detail]
|
|
215
|
-
["#{field}:#{divergence[:detail].to_s[/\A\w+(?:::\w+)*/]}"]
|
|
216
|
-
else []
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# Names the rows present on one side of a list-field divergence and not
|
|
221
|
-
# the other.
|
|
222
|
-
#
|
|
223
|
-
# @param field [String] the divergence's field name, prefixed onto each key
|
|
224
|
-
# @param left [Array, nil] one side's list; a non-Array yields no keys
|
|
225
|
-
# @param right [Array, nil] the other side's list; a non-Array yields no keys
|
|
226
|
-
# @return [Array<String>] `"field:name"` for every row in the symmetric
|
|
227
|
-
# difference of `left` and `right`; empty unless both are arrays
|
|
228
|
-
def list_keys(field, left, right)
|
|
229
|
-
return [] unless left.is_a?(Array) && right.is_a?(Array)
|
|
230
|
-
|
|
231
|
-
((left - right) + (right - left)).map { |row| "#{field}:#{named(row)}" }
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
# Names the top-level keys the two sides of an `instances` divergence
|
|
235
|
-
# disagree on.
|
|
236
|
-
#
|
|
237
|
-
# Instance keys are `Aggregate#id` on the wire — the id is whatever
|
|
238
|
-
# the generator minted, so it names the record, not the finding;
|
|
239
|
-
# keeping it would pin every creating step.
|
|
240
|
-
#
|
|
241
|
-
# @param left [Hash, nil] one side's instance snapshot; a non-Hash yields no keys
|
|
242
|
-
# @param right [Hash, nil] the other side's instance snapshot; a non-Hash
|
|
243
|
-
# yields no keys
|
|
244
|
-
# @return [Array<String>] `"instances:Aggregate"` for every top-level key
|
|
245
|
-
# the two sides disagree on, its `#id` suffix dropped; empty unless both
|
|
246
|
-
# are hashes
|
|
247
|
-
def instance_keys(left, right)
|
|
248
|
-
return [] unless left.is_a?(Hash) && right.is_a?(Hash)
|
|
249
|
-
|
|
250
|
-
(left.keys | right.keys).reject { |key| left[key] == right[key] }
|
|
251
|
-
.map { |key| "instances:#{key.to_s.split('#').first}" }
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
# Checks that a candidate's own divergences still demonstrate the
|
|
255
|
-
# finding being shrunk.
|
|
256
|
-
#
|
|
257
|
-
# @param original_signature [Set<String>] the finding's own signature, as
|
|
258
|
-
# returned by `signature`
|
|
259
|
-
# @param divergences [Array<Hash>] the candidate's own divergence entries,
|
|
260
|
-
# in `signature`'s own shape
|
|
261
|
-
# @return [Boolean] true if `divergences` is non-empty and its signature is
|
|
262
|
-
# a superset of `original_signature`
|
|
263
|
-
def reproduces?(original_signature, divergences)
|
|
264
|
-
!divergences.empty? && original_signature.subset?(signature(divergences))
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
# Names one row from a list-field divergence, for `list_keys`.
|
|
268
|
-
#
|
|
269
|
-
# @param row [Object] one item from a list-field divergence's array;
|
|
270
|
-
# typically a Hash carrying a `verb`, `query`, or `policy` key (string
|
|
271
|
-
# or symbol)
|
|
272
|
-
# @return [String] `row`'s `verb`/`query`/`policy` value, stringified, if
|
|
273
|
-
# `row` is a Hash carrying one of those keys; otherwise `row.to_s`
|
|
274
|
-
def named(row)
|
|
275
|
-
return row.to_s unless row.is_a?(Hash)
|
|
276
|
-
|
|
277
|
-
key = [%w[verb query policy], %i[verb query policy]].flatten.find { |name| row.key?(name) }
|
|
278
|
-
(key ? row[key] : row).to_s
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
# Counts candidate checks against the budget.
|
|
282
|
-
class Meter
|
|
283
|
-
attr_reader :used
|
|
284
|
-
|
|
285
|
-
# @param budget [Integer, nil] maximum candidate checks to allow; nil for unbounded
|
|
286
|
-
def initialize(budget)
|
|
287
|
-
@budget = budget
|
|
288
|
-
@used = 0
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
# Reports whether this meter's budget has been fully spent.
|
|
292
|
-
#
|
|
293
|
-
# @return [Boolean] true if a budget is set and every allotted check has
|
|
294
|
-
# been used
|
|
295
|
-
def exhausted? = !@budget.nil? && @used >= @budget
|
|
296
|
-
|
|
297
|
-
# Spends one candidate check against the budget and runs the block.
|
|
298
|
-
#
|
|
299
|
-
# @yield runs the candidate check being counted
|
|
300
|
-
# @yieldreturn [Object] the block's own result
|
|
301
|
-
# @return [Object] whatever the block returns
|
|
302
|
-
def try
|
|
303
|
-
@used += 1
|
|
304
|
-
yield
|
|
305
|
-
end
|
|
306
|
-
end
|
|
307
|
-
end
|
|
308
|
-
end
|
|
309
|
-
end
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
module Hecks
|
|
2
|
-
module Fuzzing
|
|
3
|
-
# What each query verb the differential comparison skipped was hiding.
|
|
4
|
-
#
|
|
5
|
-
# `Differential.manifest_partition` drops a named query or read model
|
|
6
|
-
# from both sides of the Ruby/Rust comparison only when the binary's own
|
|
7
|
-
# manifest.json declares it `generated: false`. The drop is correct, but
|
|
8
|
-
# a sweep that dropped every `offset`/`cursor` ask would still read as
|
|
9
|
-
# "agreed across all steps". `bin/qa_sweep` therefore logs one Check per
|
|
10
|
-
# sweep naming every skipped verb and the construct family that caused
|
|
11
|
-
# it, and marks it Surprised when a family is outside
|
|
12
|
-
# `QualityControlDials::STRUCTURAL_REFUSAL_BOUNDARY`.
|
|
13
|
-
#
|
|
14
|
-
# The construct comes straight from the manifest entry, written by the
|
|
15
|
-
# generator branch that made the skip (`Projector::SkipReason`), rather
|
|
16
|
-
# than guessed from the Ruby declaration's `to_h` keys, which is coarser
|
|
17
|
-
# than codegen's own decision and can drift from it.
|
|
18
|
-
module StructuralSkips
|
|
19
|
-
module_function
|
|
20
|
-
|
|
21
|
-
# Attributes each skipped verb to the construct family its manifest
|
|
22
|
-
# entry names.
|
|
23
|
-
#
|
|
24
|
-
# One entry per skipped verb: `{ verb:, constructs: [...] }`, sorted by
|
|
25
|
-
# verb so the printed observation is stable across seeds and runs. A
|
|
26
|
-
# verb the manifest doesn't declare answers `unknown`, which no
|
|
27
|
-
# boundary admits.
|
|
28
|
-
#
|
|
29
|
-
# @param gaps [Fuzzing::RustGapManifest] the binary's not-generated manifest entries
|
|
30
|
-
# @param verbs [Array<String>] the skipped query/read-model verbs to attribute
|
|
31
|
-
# @return [Array<Hash{Symbol => Object}>] one `{ verb: String, constructs: Array<String> }`
|
|
32
|
-
# per verb, in sorted-verb order
|
|
33
|
-
def attribute(gaps, verbs)
|
|
34
|
-
verbs.sort.map do |verb|
|
|
35
|
-
entry = gaps.not_generated(verb)
|
|
36
|
-
{ verb: verb, constructs: entry ? [entry.fetch("construct")] : %w[unknown] }
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Selects the attributed entries whose constructs are not all inside `boundary`.
|
|
41
|
-
#
|
|
42
|
-
# @param attributed [Array<Hash{Symbol => Object}>] entries as returned by `attribute`
|
|
43
|
-
# @param boundary [Array<String, Symbol>] the construct families the documented codegen
|
|
44
|
-
# boundary admits
|
|
45
|
-
# @return [Array<Hash{Symbol => Object}>] the subset of `attributed` naming an
|
|
46
|
-
# unadmitted construct, or no construct at all
|
|
47
|
-
def outside_boundary(attributed, boundary)
|
|
48
|
-
admitted = boundary.map(&:to_s)
|
|
49
|
-
attributed.select { |entry| entry[:constructs].empty? || (entry[:constructs] - admitted).any? }
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
module Hecks
|
|
2
|
-
module Fuzzing
|
|
3
|
-
# How hard one sweep fuzzes, as a function of `Target.clean_streak`
|
|
4
|
-
# (qa/bluebook/quality_control.bluebook — that attribute's own comment
|
|
5
|
-
# says what the streak means). The sibling of `RotationPriority`: the
|
|
6
|
-
# ledger holds the number, `QualityControlDials::WIDENING_TIERS` holds
|
|
7
|
-
# the policy, and this module is where the two meet — a pure function
|
|
8
|
-
# of both, nothing else read.
|
|
9
|
-
#
|
|
10
|
-
# Why this is not in `bin/qa_sweep` any more. It was — `WIDENING_TIERS`
|
|
11
|
-
# and `widen_for_streak` lived at the top of that script, which meant
|
|
12
|
-
# policy data lived in a script (unlike every other dial, which lives
|
|
13
|
-
# in the bluebook a human edits and reviews) and duplicated itself as
|
|
14
|
-
# prose in SKILL.md. Now the table is a dial, this is the one reader,
|
|
15
|
-
# and `bin/qa_sweep` calls it the way it already calls
|
|
16
|
-
# `RotationPriority.pick`.
|
|
17
|
-
#
|
|
18
|
-
# **Pure, deliberately** — the same discipline `RotationPriority` keeps:
|
|
19
|
-
# same streak in, same `[seeds, steps]` out, every time, which is what
|
|
20
|
-
# lets a human predict what a given sweep is about to do before it
|
|
21
|
-
# runs one. `tiers:` defaults to the dial but is a plain argument, so
|
|
22
|
-
# a unit spec can pass its own table with no ledger boot at all and a
|
|
23
|
-
# `bin/qa_sweep` run against a ledger that declares no dials (an
|
|
24
|
-
# isolated spec's own fixture) can fall back the same way it already
|
|
25
|
-
# does for `ADVERSARIAL_FRACTION`.
|
|
26
|
-
module SweepDepth
|
|
27
|
-
module_function
|
|
28
|
-
|
|
29
|
-
# The fallback table, for a boot with no `QualityControlDials` at all
|
|
30
|
-
# — the same shape and the same three rows the dial ships with, so a
|
|
31
|
-
# dial-less ledger fuzzes exactly as a dialled one does by default.
|
|
32
|
-
# Never read when the dial exists; `bin/qa_sweep` passes the dial in.
|
|
33
|
-
DEFAULT_TIERS = [
|
|
34
|
-
{ upto: 4, seeds: 10, steps: 25 },
|
|
35
|
-
{ upto: 19, seeds: 25, steps: 50 },
|
|
36
|
-
{ upto: Float::INFINITY, seeds: 50, steps: 100 }
|
|
37
|
-
].freeze
|
|
38
|
-
|
|
39
|
-
# `[seeds, steps]` for one streak. The first row whose `upto` the
|
|
40
|
-
# streak does not exceed wins — rows are read in order, so the table
|
|
41
|
-
# must be ascending, and the last row's `Float::INFINITY` is what
|
|
42
|
-
# makes it the ceiling rather than a gap.
|
|
43
|
-
#
|
|
44
|
-
# @param streak [Integer] `Target.clean_streak`, the number of consecutive clean sweeps
|
|
45
|
-
# @param tiers [Array<Hash{Symbol => Numeric}>] ascending `{ upto:, seeds:, steps: }` rows;
|
|
46
|
-
# defaults to `DEFAULT_TIERS`
|
|
47
|
-
# @return [Array(Integer, Integer)] `[seeds, steps]` for the first row `streak` fits under
|
|
48
|
-
# @raise [ArgumentError] if `streak` is negative
|
|
49
|
-
# @raise [ArgumentError] if no row in `tiers` covers `streak` (the last row must declare
|
|
50
|
-
# `upto: Float::INFINITY`)
|
|
51
|
-
def for_streak(streak, tiers: DEFAULT_TIERS)
|
|
52
|
-
raise ArgumentError, "streak must not be negative" if streak.negative?
|
|
53
|
-
|
|
54
|
-
tier = tiers.find { |row| streak <= row[:upto] }
|
|
55
|
-
raise ArgumentError, "no tier covers a streak of #{streak} — the last row must be upto: Float::INFINITY" unless tier
|
|
56
|
-
|
|
57
|
-
[tier[:seeds], tier[:steps]]
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
module Hecks
|
|
2
|
-
module Fuzzing
|
|
3
|
-
# What a sweep target can actually be checked for, read off the
|
|
4
|
-
# filesystem — never off a stored list.
|
|
5
|
-
#
|
|
6
|
-
# `bin/qa_sweep` does not decide its comparison modes with inline
|
|
7
|
-
# conditionals — "is there a Cargo feature named after this directory?
|
|
8
|
-
# then `:differential`, else `:ruby_only`", a separate hand-typed abort
|
|
9
|
-
# for `--persistence-parity` ("does any .hecksagon bind PostgresEra?"),
|
|
10
|
-
# and so on for every further mode the practice adds (era boundary,
|
|
11
|
-
# concurrency, a WASM front) — because each such inline `if` is a
|
|
12
|
-
# policy decision hiding in a script. This module is those decisions
|
|
13
|
-
# as data: `MODE_REQUIREMENTS` says which capabilities each mode
|
|
14
|
-
# needs, `infer` says which capabilities a target's own directory
|
|
15
|
-
# actually has, and `resolve` is the one rule that joins them —
|
|
16
|
-
# `modes_to_run = enabled ∩ eligible`.
|
|
17
|
-
#
|
|
18
|
-
# ## Inference decides; a stored list only records
|
|
19
|
-
#
|
|
20
|
-
# `Target.capabilities` (qa/bluebook/quality_control.bluebook, once
|
|
21
|
-
# PR-1's era lands) is written by the runner from exactly this
|
|
22
|
-
# inference at release time so `Target.EligibleFor(mode)` can audit
|
|
23
|
-
# the rotation from the ledger alone — but the runner re-infers every
|
|
24
|
-
# sweep, because a stored list that lags yesterday's Cargo feature is
|
|
25
|
-
# precisely the "quiet divergence" (the chapter's own opening comment)
|
|
26
|
-
# this whole practice exists to hunt. Nothing here ever reads the
|
|
27
|
-
# ledger.
|
|
28
|
-
#
|
|
29
|
-
# ## Regex provenance
|
|
30
|
-
#
|
|
31
|
-
# Every regex is one the harness already owned, moved here rather than
|
|
32
|
-
# re-derived, and each one's provenance is named beside it so a
|
|
33
|
-
# future edit to the original site is a visible drift, not a silent
|
|
34
|
-
# one: the Cargo feature line (`RustConformanceHelpers#build_rust_for`),
|
|
35
|
-
# the PostgresEra binding (`bin/qa_sweep`'s old `POSTGRES_ERA_BINDING`),
|
|
36
|
-
# the translations glob (`IsolatedBoot#strip_translations!`).
|
|
37
|
-
module TargetCapabilities
|
|
38
|
-
module_function
|
|
39
|
-
|
|
40
|
-
# `RustConformanceHelpers#build_rust_for`'s own test, scoped to the
|
|
41
|
-
# `[features]` table the way `bin/project_rust`'s Cargo sync scopes
|
|
42
|
-
# its own lookup (a `[package] name = "rust"` line must never read
|
|
43
|
-
# as a feature named `rust`).
|
|
44
|
-
FEATURES_TABLE = /^\[features\](?:\n(?!\[).*)*$/
|
|
45
|
-
|
|
46
|
-
# `bin/qa_sweep`'s former `POSTGRES_ERA_BINDING` — both spellings
|
|
47
|
-
# `IsolatedBoot#rewrite_bindings!` has to catch: aggregate-scoped
|
|
48
|
-
# (`Directory::Member.persisted_by("PostgresEra")`) and the bare
|
|
49
|
-
# domain-level default (`persisted_by "PostgresEra"`).
|
|
50
|
-
POSTGRES_ERA_BINDING = /persisted_by\s*\(?\s*"PostgresEra"/
|
|
51
|
-
|
|
52
|
-
# A local variable assigned the bare literal `"PostgresEra"` on a line of
|
|
53
|
-
# its own (`adapter = "PostgresEra"`). Only the whole-line literal counts:
|
|
54
|
-
# a ternary or any other right-hand side is not a PostgresEra binding.
|
|
55
|
-
POSTGRES_ERA_VARIABLE = /^\s*([a-z_]\w*)\s*=\s*"PostgresEra"\s*(?:#.*)?$/
|
|
56
|
-
|
|
57
|
-
# `HecksagonBuilder#uses_framework "X"` — captures which member a
|
|
58
|
-
# hecksagon attaches, whatever its name. Whether that member answers
|
|
59
|
-
# a role check is then read off its own declaration (`provides
|
|
60
|
-
# "authorization"`, via `Framework.providers_of`), the same rule
|
|
61
|
-
# `Registry#authorization_provider_for` applies at runtime — never a
|
|
62
|
-
# match on the literal "Governance".
|
|
63
|
-
FRAMEWORK_ATTACHED = /uses_framework\s*\(?\s*"([^"]+)"/
|
|
64
|
-
|
|
65
|
-
# A command-level `role "..."` — the only construct
|
|
66
|
-
# `refuse_role_mismatch` ever has anything to check a caller against.
|
|
67
|
-
ROLE_GATED = /^\s*role\s+"/
|
|
68
|
-
|
|
69
|
-
# `authorize :vault_access, tenant: :branch_code`
|
|
70
|
-
# (examples/banking/bluebook/safe_deposit_boxes.bluebook) — the one
|
|
71
|
-
# `tenant:` spelling the corpus has.
|
|
72
|
-
TENANT_SCOPED = /\btenant:/
|
|
73
|
-
|
|
74
|
-
PROCESS_MANAGER = /^\s*process_manager\s+"/
|
|
75
|
-
|
|
76
|
-
# Which capabilities each mode needs before it can say anything true
|
|
77
|
-
# about a target. An empty list means "any target at all" — every
|
|
78
|
-
# domain boots under Memory, so Ruby-only properties and the
|
|
79
|
-
# self-consistency pass are always answerable. `ruby_only` is listed
|
|
80
|
-
# requirement-free on purpose and then excluded by `resolve` whenever
|
|
81
|
-
# `differential` resolved too: they are the same seat, and a compiled
|
|
82
|
-
# Rust binary is strictly the better occupant (item 1 of the
|
|
83
|
-
# detection plan folded the Ruby-only property battery into the
|
|
84
|
-
# differential seat, so nothing is lost by the exclusion).
|
|
85
|
-
#
|
|
86
|
-
# The four `false`-by-default modes in `QualityControlDials::MODES`
|
|
87
|
-
# (`adapter_parity_postgres`, `era_boundary`, `concurrency`,
|
|
88
|
-
# `wasm_front`) are named here with their requirements even though
|
|
89
|
-
# nothing runs them yet — so `resolved modes:` can already say, per
|
|
90
|
-
# target, which of them would be eligible the day a human flips the
|
|
91
|
-
# dial, and so flipping it is a one-line data change rather than a
|
|
92
|
-
# code change plus a data change.
|
|
93
|
-
MODE_REQUIREMENTS = {
|
|
94
|
-
differential: %w[rust],
|
|
95
|
-
ruby_only: [],
|
|
96
|
-
self_consistency: [],
|
|
97
|
-
properties_in_differential: %w[rust],
|
|
98
|
-
structural_skip_report: %w[rust],
|
|
99
|
-
adapter_parity_sqlite: %w[sqlite],
|
|
100
|
-
persistence_parity: %w[postgres_era],
|
|
101
|
-
adapter_parity_postgres: %w[postgres_era],
|
|
102
|
-
era_boundary: %w[translations postgres_era],
|
|
103
|
-
concurrency: %w[postgres_era],
|
|
104
|
-
wasm_front: %w[rust]
|
|
105
|
-
}.freeze
|
|
106
|
-
|
|
107
|
-
# Which of those modes `bin/qa_sweep` can actually run today.
|
|
108
|
-
# `MODE_REQUIREMENTS` above says what a mode needs; this says what
|
|
109
|
-
# exists to do it, and the two are not the same. Conflating them is
|
|
110
|
-
# how `qa/settings.yml` came to enable `wasm_front` and
|
|
111
|
-
# `adapter_parity_postgres` with nothing behind either — no seat, no
|
|
112
|
-
# check folded into the seed loop, no `MODE_EXPECTATIONS` entry — while
|
|
113
|
-
# `resolved modes:` still printed them per target, so the sweep
|
|
114
|
-
# advertised coverage it never performed. A mode named in
|
|
115
|
-
# `MODE_REQUIREMENTS` but absent here is one this practice wants and has
|
|
116
|
-
# not built: `bin/qa_sweep` refuses to start when a dial or `--modes`
|
|
117
|
-
# enables it, rather than resolving it into a line nobody can act on.
|
|
118
|
-
# `spec/qa_sweep_runnable_modes_spec.rb` keeps this list honest from
|
|
119
|
-
# both sides by grepping the runner itself.
|
|
120
|
-
RUNNABLE_MODES = %i[differential ruby_only self_consistency properties_in_differential
|
|
121
|
-
structural_skip_report adapter_parity_sqlite persistence_parity
|
|
122
|
-
era_boundary concurrency].freeze
|
|
123
|
-
|
|
124
|
-
# Modes that name a separate, expensive pass of their own rather than
|
|
125
|
-
# an extra check folded into the ordinary per-seed loop —
|
|
126
|
-
# `bin/qa_sweep` runs these only when asked by name (`--modes
|
|
127
|
-
# persistence_parity`, or its older alias `--persistence-parity`) or
|
|
128
|
-
# as `--all`'s own second wave, never silently inside a plain
|
|
129
|
-
# single-target sweep (the seed cap `PERSISTENCE_PARITY_SEED_CAP`
|
|
130
|
-
# exists because that pass pays for real Postgres I/O per dispatch).
|
|
131
|
-
DEFERRED_MODES = %i[persistence_parity adapter_parity_postgres era_boundary concurrency].freeze
|
|
132
|
-
|
|
133
|
-
# Reads a target directory to find out which capabilities it actually has.
|
|
134
|
-
#
|
|
135
|
-
# Sorted, plain strings — comma-joined by the runner into the
|
|
136
|
-
# `Target.Release(capabilities:)` value object and printed verbatim
|
|
137
|
-
# on the `resolved modes:` line, so the same spelling is what a
|
|
138
|
-
# human reads, what `--all` parses back, and what the ledger stores.
|
|
139
|
-
#
|
|
140
|
-
# @param domain_path [String] filesystem path to the target domain's directory
|
|
141
|
-
# @param rust_dir [String] path to the Rust project root, checked for a matching
|
|
142
|
-
# Cargo feature; defaults to this repo's own `rust/` directory
|
|
143
|
-
# @return [Array<String>] the target's capabilities, sorted; a subset of
|
|
144
|
-
# `sqlite`, `rust`, `postgres_era`, `translations`, `governance`, `role_gated`,
|
|
145
|
-
# `tenant`, `sagas`
|
|
146
|
-
def infer(domain_path, rust_dir: File.expand_path("../../../rust", __dir__))
|
|
147
|
-
capabilities = %w[sqlite]
|
|
148
|
-
capabilities << "rust" if rust_feature?(domain_path, rust_dir)
|
|
149
|
-
capabilities << "postgres_era" if postgres_era_bound?(domain_path)
|
|
150
|
-
capabilities << "translations" if Dir.glob(File.join(domain_path, "**", "translations", "*.bluebook")).any?
|
|
151
|
-
capabilities << "governance" if authorization_attached?(domain_path)
|
|
152
|
-
capabilities << "role_gated" if any_file?(domain_path, "*.bluebook", ROLE_GATED)
|
|
153
|
-
capabilities << "tenant" if any_file?(domain_path, "*.bluebook", TENANT_SCOPED)
|
|
154
|
-
capabilities << "sagas" if any_file?(domain_path, "*.bluebook", PROCESS_MANAGER)
|
|
155
|
-
capabilities.sort
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# Answers whether `mode` can run against a target with `capabilities`.
|
|
159
|
-
#
|
|
160
|
-
# @param mode [Symbol, String] a key of `MODE_REQUIREMENTS`, such as `:differential`
|
|
161
|
-
# @param capabilities [Array<String>] the target's capabilities, as `infer` returns
|
|
162
|
-
# @return [Boolean] whether `capabilities` covers every capability `mode` requires
|
|
163
|
-
# @raise [ArgumentError] if `mode` names no entry in `MODE_REQUIREMENTS`
|
|
164
|
-
def eligible?(mode, capabilities)
|
|
165
|
-
required = MODE_REQUIREMENTS.fetch(mode.to_sym) { raise ArgumentError, "unknown sweep mode #{mode.inspect}" }
|
|
166
|
-
(required - capabilities).empty?
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
# The one rule. `enabled` is whatever the dial (or `--modes`) turned
|
|
170
|
-
# on, in the dial's own declaration order — that order is preserved
|
|
171
|
-
# so the printed line reads the same way the dial does. Then the
|
|
172
|
-
# single exclusion named on `MODE_REQUIREMENTS`.
|
|
173
|
-
#
|
|
174
|
-
# @param enabled [Array<Symbol, String>] modes turned on, in the dial's own
|
|
175
|
-
# declaration order
|
|
176
|
-
# @param capabilities [Array<String>] the target's capabilities, as `infer` returns
|
|
177
|
-
# @return [Array<Symbol>] `enabled`'s eligible modes, in `enabled`'s order, with
|
|
178
|
-
# `:ruby_only` dropped whenever `:differential` is also eligible
|
|
179
|
-
def resolve(enabled, capabilities)
|
|
180
|
-
resolved = enabled.map(&:to_sym).select { |mode| eligible?(mode, capabilities) }
|
|
181
|
-
resolved.delete(:ruby_only) if resolved.include?(:differential)
|
|
182
|
-
resolved
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
# Answers whether `rust_dir`'s Cargo.toml declares a feature named after
|
|
186
|
-
# `domain_path`'s own directory.
|
|
187
|
-
#
|
|
188
|
-
# @param domain_path [String] filesystem path to the target domain's directory;
|
|
189
|
-
# its basename, lowercased, is the feature name looked up
|
|
190
|
-
# @param rust_dir [String] path to the Rust project root (holds `Cargo.toml`)
|
|
191
|
-
# @return [Boolean] whether a matching Cargo feature is declared; false if
|
|
192
|
-
# `rust_dir` has no `Cargo.toml`
|
|
193
|
-
def rust_feature?(domain_path, rust_dir)
|
|
194
|
-
cargo_toml = File.join(rust_dir, "Cargo.toml")
|
|
195
|
-
return false unless File.file?(cargo_toml)
|
|
196
|
-
|
|
197
|
-
feature = File.basename(domain_path).downcase
|
|
198
|
-
features = File.read(cargo_toml)[FEATURES_TABLE] || ""
|
|
199
|
-
features.match?(/^#{Regexp.escape(feature)}\s*=\s*\[\]/)
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
# Answers whether any `.hecksagon` under `domain_path` binds PostgresEra,
|
|
203
|
-
# either with the literal name or through a local variable that the same
|
|
204
|
-
# file assigned `"PostgresEra"` and then passed to `persisted_by`.
|
|
205
|
-
#
|
|
206
|
-
# @param domain_path [String] filesystem path to the target domain's directory
|
|
207
|
-
# @return [Boolean] whether a PostgresEra `persisted_by` binding is found
|
|
208
|
-
def postgres_era_bound?(domain_path)
|
|
209
|
-
Dir.glob(File.join(domain_path, "**", "*.hecksagon")).any? do |path|
|
|
210
|
-
text = File.read(path)
|
|
211
|
-
text.match?(POSTGRES_ERA_BINDING) || postgres_era_variable_bound?(text)
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# Answers whether `text` passes a variable assigned `"PostgresEra"` to `persisted_by`.
|
|
216
|
-
#
|
|
217
|
-
# @param text [String] the contents of one `.hecksagon` file
|
|
218
|
-
# @return [Boolean] whether some `persisted_by` call names such a variable
|
|
219
|
-
def postgres_era_variable_bound?(text)
|
|
220
|
-
text.scan(POSTGRES_ERA_VARIABLE).flatten.any? do |name|
|
|
221
|
-
text.match?(/persisted_by\s*\(?\s*#{Regexp.escape(name)}(?!\w)/)
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
# Answers whether any `.hecksagon` under `domain_path` attaches a member
|
|
226
|
-
# that provides authorization.
|
|
227
|
-
#
|
|
228
|
-
# The capability label stays "governance" — it is the value
|
|
229
|
-
# `bin/qa_sweep` writes into the QualityControl ledger's
|
|
230
|
-
# `Target.capabilities`, and renaming it is a ledger change, not
|
|
231
|
-
# part of dropping the name check.
|
|
232
|
-
#
|
|
233
|
-
# @param domain_path [String] filesystem path to the target domain's directory
|
|
234
|
-
# @return [Boolean] whether the domain attaches a member that provides the
|
|
235
|
-
# `Bluebook::Capabilities::AUTHORIZATION` capability
|
|
236
|
-
def authorization_attached?(domain_path)
|
|
237
|
-
attached = Dir.glob(File.join(domain_path, "**", "*.hecksagon"))
|
|
238
|
-
.flat_map { |path| File.read(path).scan(FRAMEWORK_ATTACHED).flatten }.uniq
|
|
239
|
-
providers = Framework.providers_of(Bluebook::Capabilities::AUTHORIZATION)
|
|
240
|
-
attached.intersect?(providers)
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
# Answers whether any file under `domain_path` matching `glob` contains `pattern`.
|
|
244
|
-
#
|
|
245
|
-
# @param domain_path [String] filesystem path to the target domain's directory
|
|
246
|
-
# @param glob [String] a `Dir.glob` pattern relative to `domain_path`, such as
|
|
247
|
-
# `"*.bluebook"`
|
|
248
|
-
# @param pattern [Regexp] the pattern each matching file's contents is tested against
|
|
249
|
-
# @return [Boolean] whether any matching file's contents match `pattern`
|
|
250
|
-
def any_file?(domain_path, glob, pattern)
|
|
251
|
-
Dir.glob(File.join(domain_path, "**", glob)).any? { |path| File.read(path).match?(pattern) }
|
|
252
|
-
end
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
end
|