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
data/lib/hecks/bench/workload.rb
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
require "json"
|
|
2
|
-
|
|
3
|
-
module Hecks
|
|
4
|
-
module Bench
|
|
5
|
-
# A fixed command sequence for one example domain, identical for every runtime.
|
|
6
|
-
#
|
|
7
|
-
# `setup` runs once, untimed, before a run starts. `cycle(n)` answers the timed
|
|
8
|
-
# commands for the n-th unit of work. Every cycle names its own records by `n`, so
|
|
9
|
-
# cycles never collide and no command is ever refused: a refusal would mean the
|
|
10
|
-
# benchmark measured a rejection path rather than the work it claims.
|
|
11
|
-
#
|
|
12
|
-
# Steps are the same `{"verb", "args"}` shape `bin/run` and the corpus scripts use.
|
|
13
|
-
# Ruby dispatches them through `dispatch_flat` and the Rust binary reads them as
|
|
14
|
-
# JSON lines, so both runtimes see byte-identical input.
|
|
15
|
-
class Workload
|
|
16
|
-
# One command to dispatch.
|
|
17
|
-
#
|
|
18
|
-
# @!attribute verb
|
|
19
|
-
# @return [String] the fully qualified command, e.g. `"Pizzas::Order.CreatePizza"`
|
|
20
|
-
# @!attribute args
|
|
21
|
-
# @return [Hash{String => Object}] the command's arguments, string-keyed throughout
|
|
22
|
-
Step = Struct.new(:verb, :args) do
|
|
23
|
-
# Renders the step as one line of `rust --serve` input.
|
|
24
|
-
#
|
|
25
|
-
# @return [String] the step as compact JSON with no trailing newline
|
|
26
|
-
def to_json_line
|
|
27
|
-
JSON.generate("verb" => verb, "args" => args)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Gives the arguments as `dispatch_flat` takes them.
|
|
31
|
-
#
|
|
32
|
-
# @return [Hash{Symbol => Object}] `args` with only its top-level keys symbolized,
|
|
33
|
-
# the way the corpus replay hands them over
|
|
34
|
-
def ruby_args
|
|
35
|
-
args.transform_keys(&:to_sym)
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# @return [String] the example domain's directory name, also its Cargo feature
|
|
40
|
-
attr_reader :name
|
|
41
|
-
|
|
42
|
-
# @return [String] the absolute path of the example domain directory
|
|
43
|
-
attr_reader :domain_path
|
|
44
|
-
|
|
45
|
-
# @return [Array<Step>] the commands run once, untimed, before any cycle
|
|
46
|
-
attr_reader :setup
|
|
47
|
-
|
|
48
|
-
# Builds a workload.
|
|
49
|
-
#
|
|
50
|
-
# @param name [String] the example domain's directory name, e.g. `"pizzas"`
|
|
51
|
-
# @param setup [Array<Step>] the untimed preparation commands
|
|
52
|
-
# @param cycle [#call] takes a cycle number and returns that cycle's `Array<Step>`
|
|
53
|
-
def initialize(name:, setup:, cycle:)
|
|
54
|
-
@name = name
|
|
55
|
-
@domain_path = File.expand_path("../../../examples/#{name}", __dir__)
|
|
56
|
-
@setup = setup
|
|
57
|
-
@cycle = cycle
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Gives the timed commands for one unit of work.
|
|
61
|
-
#
|
|
62
|
-
# @param number [Integer] the zero-based cycle number, unique across warmup and
|
|
63
|
-
# measurement so record names never repeat
|
|
64
|
-
# @return [Array<Step>] the commands in dispatch order
|
|
65
|
-
def cycle(number) = @cycle.call(number)
|
|
66
|
-
|
|
67
|
-
# Gives how many commands each cycle dispatches.
|
|
68
|
-
#
|
|
69
|
-
# @return [Integer] the length of every cycle
|
|
70
|
-
def commands_per_cycle = cycle(0).size
|
|
71
|
-
|
|
72
|
-
# Lists the workloads `bin/bench` knows.
|
|
73
|
-
#
|
|
74
|
-
# @return [Hash{String => Workload}] each workload keyed by its domain name
|
|
75
|
-
def self.all
|
|
76
|
-
{ "pizzas" => pizzas, "banking" => banking }
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Looks up one workload.
|
|
80
|
-
#
|
|
81
|
-
# @param name [String] a key of `.all`
|
|
82
|
-
# @return [Workload] the workload for `name`
|
|
83
|
-
# @raise [ArgumentError] if there is no workload with that name
|
|
84
|
-
def self.fetch(name)
|
|
85
|
-
all.fetch(name) do
|
|
86
|
-
raise ArgumentError, "unknown domain #{name.inspect} — one of #{all.keys.join(', ')}"
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# A pizza is created, topped twice and bought: four commands and two aggregate updates.
|
|
91
|
-
#
|
|
92
|
-
# @return [Workload] the pizzas workload
|
|
93
|
-
def self.pizzas
|
|
94
|
-
new(name: "pizzas", setup: [], cycle: lambda { |n|
|
|
95
|
-
pizza = "pizza-#{n}"
|
|
96
|
-
[
|
|
97
|
-
Step.new("Pizzas::Order.CreatePizza",
|
|
98
|
-
{ "name" => { "value" => pizza },
|
|
99
|
-
"pizza" => { "price_cents" => { "cents" => 1200 }, "size" => { "value" => "large" } } }),
|
|
100
|
-
Step.new("Pizzas::Order.AddTopping",
|
|
101
|
-
{ "name" => pizza, "topping" => { "value" => "Basil" }, "amount" => { "value" => 3 } }),
|
|
102
|
-
Step.new("Pizzas::Order.AddTopping",
|
|
103
|
-
{ "name" => pizza, "topping" => { "value" => "Olive" }, "amount" => { "value" => 2 } }),
|
|
104
|
-
Step.new("Pizzas::Order.Purchase",
|
|
105
|
-
{ "name" => pizza, "customer_name" => { "value" => "Chris" },
|
|
106
|
-
"amount" => { "cents" => 1200 } })
|
|
107
|
-
]
|
|
108
|
-
})
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# An account is opened, credited twice and debited once, all for one registered customer.
|
|
112
|
-
#
|
|
113
|
-
# @return [Workload] the banking workload
|
|
114
|
-
def self.banking
|
|
115
|
-
register = Step.new("Banking::Customer.Register",
|
|
116
|
-
{ "reference" => { "value" => "CUST-0001" },
|
|
117
|
-
"name" => { "given" => "Ada", "family" => "Lovelace" },
|
|
118
|
-
"email" => { "address" => "ada@example.com" } })
|
|
119
|
-
new(name: "banking", setup: [register], cycle: lambda { |n|
|
|
120
|
-
number = { "value" => "acct-#{n}" }
|
|
121
|
-
money = ->(cents) { { "cents" => cents, "currency" => "USD" } }
|
|
122
|
-
[
|
|
123
|
-
Step.new("Banking::Account.Open",
|
|
124
|
-
{ "number" => number, "kind" => { "name" => "current" },
|
|
125
|
-
"daily_limit" => { "cents" => 50_000 }, "customer" => "CUST-0001" }),
|
|
126
|
-
Step.new("Banking::Account.Credit",
|
|
127
|
-
{ "number" => number, "amount" => money.call(10_000), "narrative" => { "text" => "Deposit" } }),
|
|
128
|
-
Step.new("Banking::Account.Credit",
|
|
129
|
-
{ "number" => number, "amount" => money.call(5000), "narrative" => { "text" => "Deposit" } }),
|
|
130
|
-
Step.new("Banking::Account.Debit",
|
|
131
|
-
{ "number" => number, "amount" => money.call(2500), "narrative" => { "text" => "Groceries" } })
|
|
132
|
-
]
|
|
133
|
-
})
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
end
|
data/lib/hecks/bench.rb
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
module Hecks
|
|
2
|
-
# The throughput and latency harness `bin/bench` drives.
|
|
3
|
-
#
|
|
4
|
-
# It dispatches a fixed, valid command workload against the Ruby runtime on each
|
|
5
|
-
# persistence adapter and against the native Rust binary, and reports
|
|
6
|
-
# commands per second plus p50 and p99 latency for each. Not required by
|
|
7
|
-
# `lib/hecks.rb` on purpose: a booted domain never needs it.
|
|
8
|
-
#
|
|
9
|
-
# ## What is measured
|
|
10
|
-
#
|
|
11
|
-
# Every runtime is driven the same way: one caller, one command at a time, each
|
|
12
|
-
# waiting for the previous answer. The Ruby side times `dispatch_flat` in-process.
|
|
13
|
-
# The Rust side times a full round trip over the pipes of `rust --serve`. See
|
|
14
|
-
# `docs/benchmarks.md` for what that does and does not compare.
|
|
15
|
-
module Bench
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
require_relative "bench/stats"
|
|
20
|
-
require_relative "bench/workload"
|
|
21
|
-
require_relative "bench/environment"
|
|
22
|
-
require_relative "bench/postgres_probe"
|
|
23
|
-
require_relative "bench/run"
|
|
24
|
-
require_relative "bench/ruby_runner"
|
|
25
|
-
require_relative "bench/rust_runner"
|
|
26
|
-
require_relative "bench/suite"
|
|
27
|
-
require_relative "bench/report"
|
|
28
|
-
require_relative "bench/cli"
|
data/lib/hecks/codemod.rb
DELETED
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
require_relative "bluebook/meta_validator"
|
|
2
|
-
require_relative "corpus"
|
|
3
|
-
|
|
4
|
-
module Hecks
|
|
5
|
-
# Shared machinery for a codemod that migrates real `.bluebook` source.
|
|
6
|
-
#
|
|
7
|
-
# ## Why this module exists
|
|
8
|
-
#
|
|
9
|
-
# For migrating source once a DSL builder change turns a required
|
|
10
|
-
# declaration into an optional, redundant one — pulled out of
|
|
11
|
-
# `bin/codemod_implicit_append_fields` (the first one built), which
|
|
12
|
-
# needed three real, hard-won fixes before it could be trusted: a
|
|
13
|
-
# process-lifetime AST cache with no invalidation, a batch-revert
|
|
14
|
-
# granularity that let one unsafe candidate sink every other safe one
|
|
15
|
-
# sharing its boot, and (in the spine the codemod migrates for, not
|
|
16
|
-
# here) an append-at-end insertion that only round-tripped correctly
|
|
17
|
-
# for whichever field happened to be last. None of those are guessable
|
|
18
|
-
# in advance; they only surface by actually running a real edit
|
|
19
|
-
# against real self-hosted code. This module is that lesson, kept —
|
|
20
|
-
# the next codemod plugs in two rule-specific procs (`find_candidates`,
|
|
21
|
-
# `apply_candidate`) and inherits the boot/safety-net machinery rather
|
|
22
|
-
# than rediscovering it.
|
|
23
|
-
#
|
|
24
|
-
# ## The three-step contract
|
|
25
|
-
#
|
|
26
|
-
# **A codemod is not pattern-matching alone**. Deciding "is this line safe
|
|
27
|
-
# to delete" means knowing what the runtime would resolve it to — so
|
|
28
|
-
# every codemod built on this module follows the same three steps:
|
|
29
|
-
# 1. Boot the real domain (or the self-hosted meta-domain) and read
|
|
30
|
-
# its IR to find candidates — provided by the caller's own
|
|
31
|
-
# `find_candidates`, since the actual redundancy rule is specific
|
|
32
|
-
# to whichever spine change this migration serves.
|
|
33
|
-
# 2. Locate and remove each candidate's own source text — the
|
|
34
|
-
# caller's own `apply_candidate`.
|
|
35
|
-
# 3. Re-boot from the edited text and diff the full IR export
|
|
36
|
-
# against the pre-edit export. Byte-identical -> keep. Anything
|
|
37
|
-
# else (including a raised exception — the self-hosted
|
|
38
|
-
# meta-domain dispatches itself into being, S14, so a bad edit
|
|
39
|
-
# can surface as a runtime refusal, not just a differing export)
|
|
40
|
-
# -> revert and report skipped, never silently guessed past.
|
|
41
|
-
#
|
|
42
|
-
# ## Design-time checklist for a future codemod
|
|
43
|
-
#
|
|
44
|
-
# For the spine change a future codemod migrates corpus text for —
|
|
45
|
-
# both items below are real bugs this module's own first use found,
|
|
46
|
-
# not hypothetical:
|
|
47
|
-
# - Does the resolved value get inserted into an order-sensitive
|
|
48
|
-
# list (the exported IR is array-order-sensitive throughout)? If
|
|
49
|
-
# so, the spine's own insertion must preserve the original
|
|
50
|
-
# position, not just append — an append-at-end insertion only
|
|
51
|
-
# round-trips correctly for a value that already happened to be
|
|
52
|
-
# last.
|
|
53
|
-
# - Does resolution depend on another construct already being
|
|
54
|
-
# declared (the aggregate/entity a `sets`/`append:` field resolves
|
|
55
|
-
# against)? If a creator command can be declared before the
|
|
56
|
-
# construct it creates (real, live: `command "Handler"` before
|
|
57
|
-
# `entity "Handler"`, one file down), one-pass resolution
|
|
58
|
-
# genuinely cannot see it yet — not a codemod bug, a structural
|
|
59
|
-
# limit worth naming rather than working around.
|
|
60
|
-
module Codemod
|
|
61
|
-
ROOT = File.expand_path("../..", __dir__)
|
|
62
|
-
|
|
63
|
-
EXAMPLE_ROOTS = Corpus.members(:example, root: ROOT).map(&:path)
|
|
64
|
-
META_FILES = (Dir.glob(File.join(ROOT, "lib/hecks/grammar/*.bluebook")) +
|
|
65
|
-
Dir.glob(File.join(ROOT, "lib/hecks/framework/bluebook/*.bluebook")) +
|
|
66
|
-
Dir.glob(File.join(ROOT, "lib/hecks/language/bluebook/**/*.bluebook"))).sort
|
|
67
|
-
|
|
68
|
-
# The same lightweight path `spec/spec_helper.rb`'s own
|
|
69
|
-
# `boot_in_memory` uses — `Hecks.with_registry` satisfies
|
|
70
|
-
# `Hecks.bluebook`'s own `collect`'s "loaded outside a boot" check
|
|
71
|
-
# without `Hecks.boot`'s full era-check/adapter-wiring path, which
|
|
72
|
-
# needs a live Postgres connection for `compliance` and would make
|
|
73
|
-
# every codemod depend on a database it has no reason to touch — a
|
|
74
|
-
# codemod only ever reads a chapter's own declared IR, never a
|
|
75
|
-
# stored record.
|
|
76
|
-
PERSISTENCE_PORT = File.join(ROOT, "lib/hecks/ports/persistence.port")
|
|
77
|
-
EXTRACTION_PORT = File.join(ROOT, "lib/hecks/ports/extraction.port")
|
|
78
|
-
MEMORY_ADAPTER = File.join(ROOT, "lib/hecks/adapters/driven/memory.adapter")
|
|
79
|
-
PRISM_ADAPTER = File.join(ROOT, "lib/hecks/adapters/driven/prism.adapter")
|
|
80
|
-
|
|
81
|
-
# Exports a booted registry's canonical IR as JSON, for a before/after diff.
|
|
82
|
-
#
|
|
83
|
-
# @param registry [Runtime::Registry] the booted registry to export
|
|
84
|
-
# @return [String] the pretty-printed JSON IR export
|
|
85
|
-
def self.export_json(registry) = Hecks::Projector::Exporter.json(registry)
|
|
86
|
-
|
|
87
|
-
# `Hecks::Adapters::Prism` caches a file's parsed AST for the
|
|
88
|
-
# life of the process, keyed by path — fine for every existing
|
|
89
|
-
# caller (a file loads once per process: one `bin/ir` run, one
|
|
90
|
-
# rspec worker), but a codemod legitimately reloads the same path
|
|
91
|
-
# after editing it, and a stale cached tree reports a
|
|
92
|
-
# `given`/`ensures` block at its old line number, which no longer
|
|
93
|
-
# matches the freshly re-executed file's own `block.source_location`
|
|
94
|
-
# — surfacing as "did not survive extraction" on a perfectly valid
|
|
95
|
-
# file. `Prism.forget` is the real invalidation API this module's
|
|
96
|
-
# own first use motivated (found here, fixed at the source rather
|
|
97
|
-
# than left as a private `TREES.clear` poke from outside).
|
|
98
|
-
#
|
|
99
|
-
# @param path [String, Array<String>] a single `.bluebook` file, a directory
|
|
100
|
-
# to glob every `.bluebook` file from, or an explicit list of file paths
|
|
101
|
-
# @return [Runtime::Registry] a fresh registry with every file loaded and judged
|
|
102
|
-
def self.load_bluebook(path)
|
|
103
|
-
paths = if path.is_a?(Array)
|
|
104
|
-
path
|
|
105
|
-
elsif File.directory?(path)
|
|
106
|
-
Dir.glob(File.join(path, "*.bluebook"))
|
|
107
|
-
else
|
|
108
|
-
[path]
|
|
109
|
-
end
|
|
110
|
-
paths.each { |file| Hecks::Adapters::Prism.forget(file) }
|
|
111
|
-
|
|
112
|
-
registry = Hecks::Runtime::Registry.new
|
|
113
|
-
Hecks.with_registry(registry) do
|
|
114
|
-
Kernel.load(PERSISTENCE_PORT)
|
|
115
|
-
Kernel.load(EXTRACTION_PORT)
|
|
116
|
-
Kernel.load(MEMORY_ADAPTER)
|
|
117
|
-
Kernel.load(PRISM_ADAPTER)
|
|
118
|
-
Hecks::Bluebook::MetaValidator.defer { paths.each { |file| Kernel.load(file) } }
|
|
119
|
-
Hecks::Bluebook::MetaValidator.judge_deferred!(registry)
|
|
120
|
-
end
|
|
121
|
-
registry
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# `forget_all`, not a single `forget` — the meta-domain is nine
|
|
125
|
-
# files (`MetaValidator::GRAMMAR_FILES`) merged into one registry,
|
|
126
|
-
# and a caller here (the codemod runner) doesn't generally know in
|
|
127
|
-
# advance which one it just edited.
|
|
128
|
-
# @return [String] the freshly re-derived meta-domain's canonical IR, as JSON
|
|
129
|
-
def self.boot_meta
|
|
130
|
-
Hecks::Adapters::Prism.forget_all
|
|
131
|
-
Hecks::Bluebook::MetaValidator.instance_variable_set(:@grammar_registry, nil)
|
|
132
|
-
export_json(Hecks::Bluebook::MetaValidator.grammar_registry)
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Forces the self-hosted meta-domain to re-derive from its current source,
|
|
136
|
-
# discarding any cached parse trees or memoized registry.
|
|
137
|
-
#
|
|
138
|
-
# @return [Runtime::Registry] the freshly re-derived meta-domain's registry
|
|
139
|
-
def self.meta_registry
|
|
140
|
-
Hecks::Adapters::Prism.forget_all
|
|
141
|
-
Hecks::Bluebook::MetaValidator.instance_variable_set(:@grammar_registry, nil)
|
|
142
|
-
Hecks::Bluebook::MetaValidator.grammar_registry
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Walks every aggregate (and every nested entity, recursively)
|
|
146
|
-
# across every chapter in a booted registry, yielding [owning
|
|
147
|
-
# construct, command] pairs — `construct` is whichever
|
|
148
|
-
# Aggregate/Entity actually owns the command, the same distinction
|
|
149
|
-
# `AggregateBuilder#command` vs `EntityBuilder#command` already
|
|
150
|
-
# draws. Generic enough for any rule that needs to walk real
|
|
151
|
-
# commands, not specific to the attribute-redundancy rule.
|
|
152
|
-
#
|
|
153
|
-
# @param registry [Runtime::Registry] a booted registry
|
|
154
|
-
# @yield [construct, command] every command in the registry, once per aggregate
|
|
155
|
-
# and once per nested entity
|
|
156
|
-
# @yieldparam construct [Bluebook::Aggregate, Bluebook::Entity] whichever
|
|
157
|
-
# aggregate or entity actually owns `command`
|
|
158
|
-
# @yieldparam command [Bluebook::Command] the command
|
|
159
|
-
# @return [Hash{String => Bluebook::Chapter}] `registry.bluebooks`, unchanged
|
|
160
|
-
def self.each_command(registry)
|
|
161
|
-
registry.bluebooks.each_value do |chapter|
|
|
162
|
-
chapter.aggregates.each do |aggregate|
|
|
163
|
-
walk = lambda do |construct|
|
|
164
|
-
construct.commands.each { |command| yield construct, command }
|
|
165
|
-
construct.entities.each(&walk) if construct.respond_to?(:entities)
|
|
166
|
-
end
|
|
167
|
-
walk.call(aggregate)
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
# Finds one construct's own attribute by name.
|
|
173
|
-
#
|
|
174
|
-
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct to search
|
|
175
|
-
# @param name [String, Symbol, #to_s] the attribute's declared name
|
|
176
|
-
# @return [Bluebook::Attribute, nil] the matching attribute, or nil if `construct`
|
|
177
|
-
# declares no attribute named `name`
|
|
178
|
-
def self.owner_attribute(construct, name)
|
|
179
|
-
construct.attributes.find { |attr| attr.name.to_s == name.to_s }
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# A list attribute's own element construct — the value object or
|
|
183
|
-
# entity `list_of(...)` names, resolved by `hecks_name` the same way
|
|
184
|
-
# `AttributeCollector#resolve_identity_field!` already does. Shared
|
|
185
|
-
# because "what does this list actually hold" is a question any
|
|
186
|
-
# append-shaped rule needs answered, not just this one.
|
|
187
|
-
#
|
|
188
|
-
# Nothing in the DSL stops a value object and an entity nested under
|
|
189
|
-
# the same aggregate from sharing a `hecks_name` — they are declared
|
|
190
|
-
# through two separate collections with no cross-kind uniqueness
|
|
191
|
-
# check between them. A silent value-objects-before-entities pick
|
|
192
|
-
# would resolve such a pair the same way every time regardless of
|
|
193
|
-
# which one `list_field` actually names, so a genuine collision is
|
|
194
|
-
# refused instead, the same "never first-wins silently" call
|
|
195
|
-
# `EntityBuilder#install_closed_sets!` already makes for a same-named
|
|
196
|
-
# closed set.
|
|
197
|
-
#
|
|
198
|
-
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct
|
|
199
|
-
# declaring `list_field`
|
|
200
|
-
# @param list_field [String, Symbol, #to_s] the name of the `list_of(...)` attribute
|
|
201
|
-
# @return [Bluebook::Entity, Bluebook::ValueObject, nil] the construct
|
|
202
|
-
# `list_field` holds a list of, or nil if `list_field` names no attribute,
|
|
203
|
-
# isn't a list, or names no known value object or entity
|
|
204
|
-
# @raise [RuntimeError] if a value object and an entity both match `list_field`'s type
|
|
205
|
-
def self.element_construct_for(construct, list_field)
|
|
206
|
-
list_attr = owner_attribute(construct, list_field)
|
|
207
|
-
return nil unless list_attr&.list?
|
|
208
|
-
|
|
209
|
-
value_objects = construct.respond_to?(:value_objects) ? construct.value_objects : []
|
|
210
|
-
entities = construct.respond_to?(:entities) ? construct.entities : []
|
|
211
|
-
matches = (value_objects + entities).select { |c| c.hecks_name.to_s == list_attr.type.to_s }
|
|
212
|
-
|
|
213
|
-
if matches.size > 1
|
|
214
|
-
raise "#{construct.hecks_name}##{list_field} names #{list_attr.type}, held by both a value " \
|
|
215
|
-
"object and an entity — ambiguous, cannot resolve which one the list holds"
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
matches.first
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
# Either a raised exception or a differing export counts as unsafe
|
|
222
|
-
# — see the module header on why the meta-domain specifically can
|
|
223
|
-
# raise. Returns [value_or_nil, error_message_or_nil].
|
|
224
|
-
#
|
|
225
|
-
# @yield the risky boot/export step to run
|
|
226
|
-
# @return [Array(Object, nil), Array(nil, String)] `[the block's result, nil]`
|
|
227
|
-
# on success, or `[nil, "ExceptionClass: message"]` if the block raises
|
|
228
|
-
# any StandardError
|
|
229
|
-
def self.safely
|
|
230
|
-
[yield, nil]
|
|
231
|
-
rescue StandardError => e
|
|
232
|
-
[nil, "#{e.class}: #{e.message}"]
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# **The reusable runner** — every real bug fix this module carries lives
|
|
236
|
-
# here, not in a caller's own script. A caller supplies:
|
|
237
|
-
#
|
|
238
|
-
# find_candidates: ->(registry) { [...] }
|
|
239
|
-
# Given a booted registry, return every candidate this rule
|
|
240
|
-
# could migrate. A candidate is whatever shape the caller wants
|
|
241
|
-
# — `apply_candidate` is the only other thing that reads it.
|
|
242
|
-
#
|
|
243
|
-
# apply_candidate: ->(text, candidate) { [new_text, changed_bool] }
|
|
244
|
-
# Given one file's current text and one candidate, return the
|
|
245
|
-
# edited text and whether a match was actually found/removed —
|
|
246
|
-
# `false` (text unchanged) when the candidate doesn't apply to
|
|
247
|
-
# this file, which is how the meta-domain's multi-file search
|
|
248
|
-
# below finds the right one without the caller needing to know
|
|
249
|
-
# which file a candidate lives in ahead of time.
|
|
250
|
-
#
|
|
251
|
-
# label: ->(candidate) { "..." }
|
|
252
|
-
# One-line description for the results report.
|
|
253
|
-
class Runner
|
|
254
|
-
# @param find_candidates [Proc] `->(registry) { [...] }` — given a booted
|
|
255
|
-
# registry, returns every candidate this rule could migrate; a candidate's
|
|
256
|
-
# shape is the caller's own, read back only by `apply_candidate`
|
|
257
|
-
# @param apply_candidate [Proc] `->(text, candidate) { [new_text, changed_bool] }`
|
|
258
|
-
# — given one file's current text and one candidate, returns the edited text
|
|
259
|
-
# and whether a match was found; `false` means the candidate doesn't apply to
|
|
260
|
-
# this file
|
|
261
|
-
# @param label [Proc] `->(candidate) { "..." }` — a one-line description of a
|
|
262
|
-
# candidate, for the results report
|
|
263
|
-
def initialize(find_candidates:, apply_candidate:, label:)
|
|
264
|
-
@find_candidates = find_candidates
|
|
265
|
-
@apply_candidate = apply_candidate
|
|
266
|
-
@label = label
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
# Runs this rule's codemod across every example domain and the meta-domain.
|
|
270
|
-
#
|
|
271
|
-
# @param dry_run [Boolean] when true, every safe edit is still written and
|
|
272
|
-
# reverified, then reverted rather than kept
|
|
273
|
-
# @return [Hash{Symbol => Array}] `:applied` (`Array<Hash{file: String,
|
|
274
|
-
# candidates: Array<String>}>`), `:skipped` (`Array<Hash{file: String,
|
|
275
|
-
# reason: String, candidates: Array<String>}>`), and `:clean`
|
|
276
|
-
# (`Array<String>` of domain directories or `"meta-domain"` with no candidates)
|
|
277
|
-
def run(dry_run: false)
|
|
278
|
-
results = { applied: [], skipped: [], clean: [] }
|
|
279
|
-
run_example_domains(results, dry_run)
|
|
280
|
-
run_meta_domain(results, dry_run)
|
|
281
|
-
results
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
# Prints `run`'s results to stdout.
|
|
285
|
-
#
|
|
286
|
-
# @param results [Hash{Symbol => Array}] a `run` result
|
|
287
|
-
# @param dry_run [Boolean] whether this was a dry run, for the report's own heading
|
|
288
|
-
# @return [void]
|
|
289
|
-
def report(results, dry_run:)
|
|
290
|
-
puts "== results (#{dry_run ? 'DRY RUN — nothing written' : 'applied'}) =="
|
|
291
|
-
puts "clean (no candidates): #{results[:clean].join(', ')}" unless results[:clean].empty?
|
|
292
|
-
results[:applied].each { |r| puts "APPLIED #{r[:file]}: #{r[:candidates].join(', ')}" }
|
|
293
|
-
results[:skipped].each { |r| puts "SKIPPED #{r[:file]} (#{r[:reason]}): #{Array(r[:candidates]).join(', ')}" }
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
private
|
|
297
|
-
|
|
298
|
-
def apply_many(text, candidates)
|
|
299
|
-
applied = []
|
|
300
|
-
candidates.each do |c|
|
|
301
|
-
text, changed = @apply_candidate.call(text, c)
|
|
302
|
-
applied << c if changed
|
|
303
|
-
end
|
|
304
|
-
[text, applied]
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
# The per-candidate write/verify/revert sequence below is one
|
|
308
|
-
# coherent unit (see the comment ahead of the candidates.each
|
|
309
|
-
# loop for why it can't batch) — splitting it across methods just
|
|
310
|
-
# to satisfy the line count would scatter state four ways for no
|
|
311
|
-
# readability gain.
|
|
312
|
-
# rubocop:disable-next Metrics/AbcSize
|
|
313
|
-
# rubocop:disable-next Metrics/BlockLength
|
|
314
|
-
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
315
|
-
# rubocop:disable-next Metrics/MethodLength
|
|
316
|
-
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
317
|
-
def run_example_domains(results, dry_run)
|
|
318
|
-
Codemod::EXAMPLE_ROOTS.each do |domain_dir|
|
|
319
|
-
bluebook_files = Dir.glob(File.join(domain_dir, "bluebook", "*.bluebook"))
|
|
320
|
-
next if bluebook_files.empty?
|
|
321
|
-
|
|
322
|
-
before_json = Codemod.export_json(Codemod.load_bluebook(bluebook_files))
|
|
323
|
-
candidates = @find_candidates.call(Codemod.load_bluebook(bluebook_files))
|
|
324
|
-
|
|
325
|
-
if candidates.empty?
|
|
326
|
-
results[:clean] << domain_dir
|
|
327
|
-
next
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
live = bluebook_files.to_h { |file| [file, File.read(file)] }
|
|
331
|
-
applied_by_file = Hash.new { |hash, file| hash[file] = [] }
|
|
332
|
-
|
|
333
|
-
candidates.each do |candidate|
|
|
334
|
-
target_file = bluebook_files.find { |file| apply_many(live[file], [candidate]).last.any? }
|
|
335
|
-
unless target_file
|
|
336
|
-
results[:skipped] << { file: domain_dir, reason: "no matching source line", candidates: [@label.call(candidate)] }
|
|
337
|
-
next
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
original_text = live[target_file]
|
|
341
|
-
text, = apply_many(original_text, [candidate])
|
|
342
|
-
live[target_file] = text
|
|
343
|
-
File.write(target_file, text)
|
|
344
|
-
after_json, error = Codemod.safely do
|
|
345
|
-
Codemod.export_json(Codemod.load_bluebook(bluebook_files))
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
if after_json == before_json && !dry_run
|
|
349
|
-
applied_by_file[target_file] << candidate
|
|
350
|
-
elsif after_json == before_json
|
|
351
|
-
live[target_file] = original_text
|
|
352
|
-
File.write(target_file, original_text)
|
|
353
|
-
Codemod.safely { Codemod.load_bluebook(bluebook_files) }
|
|
354
|
-
applied_by_file[target_file] << candidate
|
|
355
|
-
else
|
|
356
|
-
live[target_file] = original_text
|
|
357
|
-
File.write(target_file, original_text)
|
|
358
|
-
Codemod.safely { Codemod.load_bluebook(bluebook_files) }
|
|
359
|
-
reason = error ? "reboot raised after edit (#{error}) — reverted" : "IR changed after edit — reverted"
|
|
360
|
-
results[:skipped] << { file: target_file, reason: reason, candidates: [@label.call(candidate)] }
|
|
361
|
-
end
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
applied_by_file.each do |file, applied|
|
|
365
|
-
results[:applied] << { file: file, candidates: applied.map(&@label) }
|
|
366
|
-
end
|
|
367
|
-
end
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
# Per-candidate, not one batched write-then-verify — the
|
|
371
|
-
# meta-domain is one shared registry (SyntaxBoot dispatches it
|
|
372
|
-
# into itself, S14), so a single unsafe candidate among many would
|
|
373
|
-
# otherwise sink every other, genuinely safe candidate in the same
|
|
374
|
-
# run: this module's first real run found exactly that (25
|
|
375
|
-
# candidates, one dispatch-time break, all 25 reverted as a batch
|
|
376
|
-
# before this per-candidate loop existed). `before_meta` stays the
|
|
377
|
-
# fixed reference throughout — a truly safe candidate never
|
|
378
|
-
# changes the IR, so the running state's own export should always
|
|
379
|
-
# still equal the pristine original after each kept edit, by
|
|
380
|
-
# definition, no moving target needed.
|
|
381
|
-
# Same shape and same reason as run_example_domains just above (see
|
|
382
|
-
# its own comment) — the write/verify/revert sequence, per
|
|
383
|
-
# candidate (this method's own comment explains why it cannot
|
|
384
|
-
# batch), is one coherent unit; splitting it would scatter
|
|
385
|
-
# before_meta/live_meta/applied_by_file state across methods for
|
|
386
|
-
# no gain.
|
|
387
|
-
# rubocop:disable-next Metrics/AbcSize
|
|
388
|
-
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
389
|
-
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
390
|
-
def run_meta_domain(results, dry_run)
|
|
391
|
-
before_meta = Codemod.boot_meta
|
|
392
|
-
meta_candidates = @find_candidates.call(Codemod.meta_registry)
|
|
393
|
-
|
|
394
|
-
if meta_candidates.empty?
|
|
395
|
-
results[:clean] << "meta-domain"
|
|
396
|
-
return
|
|
397
|
-
end
|
|
398
|
-
|
|
399
|
-
live_meta = Codemod::META_FILES.to_h { |f| [f, File.read(f)] }
|
|
400
|
-
applied_by_file = Hash.new { |h, k| h[k] = [] }
|
|
401
|
-
|
|
402
|
-
meta_candidates.each do |candidate|
|
|
403
|
-
target_file = Codemod::META_FILES.find { |f| apply_many(live_meta[f], [candidate]).last.any? }
|
|
404
|
-
unless target_file
|
|
405
|
-
results[:skipped] << { file: "meta-domain", reason: "no matching source line", candidates: [@label.call(candidate)] }
|
|
406
|
-
next
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
text, = apply_many(live_meta[target_file], [candidate])
|
|
410
|
-
original_text = live_meta[target_file]
|
|
411
|
-
live_meta[target_file] = text
|
|
412
|
-
Codemod::META_FILES.each { |f| File.write(f, live_meta[f]) }
|
|
413
|
-
|
|
414
|
-
# **Dry run still verifies** — see run_example_domains' own
|
|
415
|
-
# comment; the edit is always written and rebooted for real,
|
|
416
|
-
# then always reverted afterward when dry-run (whether or not
|
|
417
|
-
# it was safe) so the next candidate is judged against the
|
|
418
|
-
# true original state, never an accumulated hypothetical.
|
|
419
|
-
after_meta, error = Codemod.safely { Codemod.boot_meta }
|
|
420
|
-
|
|
421
|
-
if after_meta == before_meta && !dry_run
|
|
422
|
-
applied_by_file[target_file] << candidate
|
|
423
|
-
elsif after_meta == before_meta # dry-run, safe — revert, but count as applied
|
|
424
|
-
live_meta[target_file] = original_text
|
|
425
|
-
File.write(target_file, original_text)
|
|
426
|
-
Codemod.safely { Codemod.boot_meta }
|
|
427
|
-
applied_by_file[target_file] << candidate
|
|
428
|
-
else
|
|
429
|
-
live_meta[target_file] = original_text
|
|
430
|
-
File.write(target_file, original_text)
|
|
431
|
-
Codemod.safely { Codemod.boot_meta } # restore cache/memoized state to the reverted text
|
|
432
|
-
reason = error ? "reboot raised (#{error})" : "IR changed"
|
|
433
|
-
results[:skipped] << { file: "meta-domain", reason: reason, candidates: [@label.call(candidate)] }
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
applied_by_file.each do |file, candidates|
|
|
438
|
-
results[:applied] << { file: file, candidates: candidates.map(&@label) }
|
|
439
|
-
end
|
|
440
|
-
end
|
|
441
|
-
end
|
|
442
|
-
end
|
|
443
|
-
end
|