hecks 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +61 -15
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
data/lib/hecks/corpus.rb
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
require_relative "fuzzing/target_capabilities"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
# **The corpus, discovered** — every place in this repo that holds a real
|
|
5
|
+
# domain, named once.
|
|
6
|
+
#
|
|
7
|
+
# One table here, rather than each consumer spelling out its own copy —
|
|
8
|
+
# spec/corpus_spec.rb, spec/model_check_spec.rb, bin/model_check,
|
|
9
|
+
# spec/parser_parity_spec.rb, bin/fuzz, Fuzzing::CombinationMiner all did,
|
|
10
|
+
# and the copies had already drifted: only the model checker saw
|
|
11
|
+
# `qa/bluebook`, only parser parity saw `spec/fixtures`, and nothing but
|
|
12
|
+
# bin/fuzz's own sweep ever saw `qa/stress_domains`. Each consumer names
|
|
13
|
+
# the `KINDS` it walks rather than re-deriving where those kinds live.
|
|
14
|
+
#
|
|
15
|
+
# Plain Dir/File only — bin/ scripts require this before (or without)
|
|
16
|
+
# booting anything.
|
|
17
|
+
module Corpus
|
|
18
|
+
ROOT = File.expand_path("../..", __dir__).freeze
|
|
19
|
+
|
|
20
|
+
Member = Struct.new(:stem, :kind, :path)
|
|
21
|
+
|
|
22
|
+
# **One domain per directory** — its bluebooks sit in `<dir>/bluebook/`
|
|
23
|
+
# or directly in `<dir>` (see `bluebook_files`). Stemmed by directory.
|
|
24
|
+
DIRECTORY_KINDS = {
|
|
25
|
+
example: "examples/*",
|
|
26
|
+
stress: "qa/stress_domains/*",
|
|
27
|
+
semantics: "spec/corpus/semantics/domains/*",
|
|
28
|
+
# A PACKAGE VENDORED VIA `uses_embryonaut_bluebook` — same real
|
|
29
|
+
# mechanism `uses_framework` is (`lib/hecks/embryonaut_bluebook.rb`'s
|
|
30
|
+
# own header: "same shape as Framework"), one level further out: not
|
|
31
|
+
# a chapter shipped inside THIS gem's own `lib/hecks/framework/
|
|
32
|
+
# bluebook/` (the `:framework` FILE_KIND, below), but a directory
|
|
33
|
+
# nested inside the CONSUMING example's own checkout
|
|
34
|
+
# (`<domain>/vendor/embryonaut_bluebooks/<name>/bluebook/`,
|
|
35
|
+
# EmbryonautBluebook.load!'s own resolution path) — hence its own
|
|
36
|
+
# DIRECTORY_KIND rather than reuse of `:framework`'s shape. First
|
|
37
|
+
# real member: docs/decisions/0058's own
|
|
38
|
+
# examples/embryonaut_vendoring_demo/vendor/embryonaut_bluebooks/widgets.
|
|
39
|
+
vendored: "examples/*/vendor/embryonaut_bluebooks/*"
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
# **One chapter per file**. Stemmed by the path below the glob's fixed
|
|
43
|
+
# prefix, so a nested fixture keeps its subdirectory (`eras/base`)
|
|
44
|
+
# and never collides with a same-named file elsewhere in the kind.
|
|
45
|
+
FILE_KINDS = {
|
|
46
|
+
grammar: "lib/hecks/grammar/*.bluebook",
|
|
47
|
+
framework: "lib/hecks/framework/bluebook/*.bluebook",
|
|
48
|
+
qa: "qa/bluebook/*.bluebook",
|
|
49
|
+
language: "lib/hecks/language/**/*.bluebook",
|
|
50
|
+
deploy: "lib/hecks/deploy/bluebook/*.bluebook",
|
|
51
|
+
tenancy: "lib/hecks/tenancy/bluebook/*.bluebook",
|
|
52
|
+
fixture: "spec/fixtures/**/*.bluebook"
|
|
53
|
+
}.freeze
|
|
54
|
+
|
|
55
|
+
KINDS = (DIRECTORY_KINDS.keys + FILE_KINDS.keys).freeze
|
|
56
|
+
|
|
57
|
+
# Where a bluebook the sweep does not boot goes instead. Not a filter:
|
|
58
|
+
# nothing leaves `sweepable_domains` without naming the check that owns
|
|
59
|
+
# it, and spec/corpus_accounting_spec.rb proves each destination exists
|
|
60
|
+
# and actually exercises what is routed to it.
|
|
61
|
+
#
|
|
62
|
+
# check: :named_in — `destination` names every routed file
|
|
63
|
+
# (`names: :each_file`) or the given text
|
|
64
|
+
# check: :gitignored — `destination` holds the ignore rule `names`,
|
|
65
|
+
# and nothing matching is committed
|
|
66
|
+
# check: :gap — no check exercises these yet. Listed so the
|
|
67
|
+
# gap is visible; the accounting spec keeps it
|
|
68
|
+
# pending and fails the moment one appears.
|
|
69
|
+
#
|
|
70
|
+
# Ordered — a bluebook belongs to the first route it matches, so a
|
|
71
|
+
# specific destination sits above the catch-all for its shape.
|
|
72
|
+
Route = Struct.new(:pattern, :check, :destination, :names, :why)
|
|
73
|
+
|
|
74
|
+
ROUTES = [
|
|
75
|
+
Route.new(%r{\Atmp/}, :gitignored, ".gitignore", "tmp/",
|
|
76
|
+
"scratch output — generated and mined candidate domains, fuzz failures — never committed"),
|
|
77
|
+
Route.new(%r{/\.aws-sam/}, :gitignored, ".gitignore", ".aws-sam/",
|
|
78
|
+
"SAM build output — a vendored copy of real sources, never committed"),
|
|
79
|
+
Route.new(%r{/data/eras/}, :gitignored, ".gitignore", "**/data/eras/",
|
|
80
|
+
"era snapshots a file adapter writes at runtime, never committed"),
|
|
81
|
+
Route.new(%r{\Arust/}, :named_in, "rust/parser/tests/gates.rs", :each_file,
|
|
82
|
+
"the Rust parser's own fixtures, each loaded by its gate tests"),
|
|
83
|
+
Route.new(%r{/translations/}, :named_in, "spec/translation/committed_edges_spec.rb", "bluebook/translations",
|
|
84
|
+
"translation edges, not chapters; each must load, chain era to era, and end at the storage " \
|
|
85
|
+
"shape its bluebook declares today"),
|
|
86
|
+
Route.new(%r{\Alib/hecks/forms/examples/}, :named_in, "spec/forms/app_spec.rb", :each_file,
|
|
87
|
+
"a Forms presentation config wearing the .bluebook extension"),
|
|
88
|
+
Route.new(%r{\Aspec/fixtures/eras/}, :named_in, "spec/runtime/storage_shape_spec.rb", "fixtures/eras",
|
|
89
|
+
"deliberately conflicting versions of one domain; each must classify as the verdict " \
|
|
90
|
+
"its filename declares (bump_* / same_*)"),
|
|
91
|
+
Route.new(%r{\Aspec/fixtures/model_check/}, :named_in, "spec/model_check_spec.rb", :each_file,
|
|
92
|
+
"domains broken on purpose; each must produce exactly the finding kinds it is built to trigger"),
|
|
93
|
+
# bin/fuzz sweeps it too, once Fuzzing::Replay coerces value-object
|
|
94
|
+
# args before recomputing givens — today its oracle reads raw args
|
|
95
|
+
# while dispatch coerces, so checkout_fixture's VO-reading given
|
|
96
|
+
# reads as wrongly admitted.
|
|
97
|
+
Route.new(%r{\Aspec/fixtures/rust_host/}, :named_in, "rust/host/src/web.rs", "checkout_fixture",
|
|
98
|
+
"the Rust host's checkout fixture, pinned by its web and /api tests")
|
|
99
|
+
].freeze
|
|
100
|
+
|
|
101
|
+
module_function
|
|
102
|
+
|
|
103
|
+
# Lists corpus members of the given kinds (every kind, by default).
|
|
104
|
+
#
|
|
105
|
+
# @param kinds [Array<Symbol>] corpus kinds to include, from `KINDS`; every kind
|
|
106
|
+
# when empty
|
|
107
|
+
# @param root [String] repository root to search under
|
|
108
|
+
# @return [Array<Member>] matching members
|
|
109
|
+
# @raise [ArgumentError] if `kinds` names a kind not in `KINDS`
|
|
110
|
+
def members(*kinds, root: ROOT)
|
|
111
|
+
kinds = KINDS if kinds.empty?
|
|
112
|
+
kinds.flat_map do |kind|
|
|
113
|
+
if (glob = DIRECTORY_KINDS[kind])
|
|
114
|
+
Dir.glob(File.join(root, glob)).select { |path| File.directory?(path) }.sort
|
|
115
|
+
.map { |dir| Member.new(File.basename(dir), kind, dir) }
|
|
116
|
+
elsif (glob = FILE_KINDS[kind])
|
|
117
|
+
prefix = File.join(root, glob[/\A[^*]*/].chomp("/"))
|
|
118
|
+
Dir.glob(File.join(root, glob))
|
|
119
|
+
.map { |file| Member.new(file.delete_prefix("#{prefix}/").delete_suffix(".bluebook"), kind, file) }
|
|
120
|
+
else
|
|
121
|
+
raise ArgumentError, "unknown corpus kind #{kind.inspect} — known: #{KINDS.join(', ')}"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# What a boot loads for a member: a directory kind's bluebook
|
|
127
|
+
# directory, a file kind's own file. `nil` for a directory holding no
|
|
128
|
+
# bluebook at all.
|
|
129
|
+
#
|
|
130
|
+
# @param member [Member] the corpus member
|
|
131
|
+
# @return [String, nil] the path to boot, or nil when a directory member holds
|
|
132
|
+
# no bluebook
|
|
133
|
+
def source_of(member)
|
|
134
|
+
DIRECTORY_KINDS.key?(member.kind) ? bluebook_dir(member.path) : member.path
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Where a domain path keeps its bluebooks — `<domain>/bluebook/*.bluebook`
|
|
138
|
+
# (every example and stress domain), or the directory itself
|
|
139
|
+
# (`qa/bluebook`). `nil` when neither holds a bluebook.
|
|
140
|
+
#
|
|
141
|
+
# @param domain_path [String] path to a domain directory
|
|
142
|
+
# @return [Array<String>, nil] `.bluebook` file paths found, or nil when none
|
|
143
|
+
def bluebook_files(domain_path)
|
|
144
|
+
[File.join(domain_path, "bluebook"), domain_path].each do |dir|
|
|
145
|
+
files = Dir[File.join(dir, "*.bluebook")]
|
|
146
|
+
return files unless files.empty?
|
|
147
|
+
end
|
|
148
|
+
nil
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# The directory holding `domain_path`'s bluebooks.
|
|
152
|
+
#
|
|
153
|
+
# @param domain_path [String] path to a domain directory
|
|
154
|
+
# @return [String, nil] directory of the first `.bluebook` file found, or nil
|
|
155
|
+
# when `domain_path` holds none
|
|
156
|
+
def bluebook_dir(domain_path)
|
|
157
|
+
files = bluebook_files(domain_path)
|
|
158
|
+
files && File.dirname(files.first)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# The route a repo-relative path takes instead of the sweep — the
|
|
162
|
+
# first one it matches — or `nil` when the sweep boots it.
|
|
163
|
+
#
|
|
164
|
+
# @param relative_path [String] a corpus member's path, relative to the repo root
|
|
165
|
+
# @return [Route, nil] the first matching route, or nil when the sweep boots it
|
|
166
|
+
def route_for(relative_path)
|
|
167
|
+
ROUTES.find { |route| route.pattern.match?(relative_path) }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# What bin/model_check and spec/model_check_spec.rb walk — every kind,
|
|
171
|
+
# less the language (examined as one judged chapter, not file by file)
|
|
172
|
+
# and deploy chapters (the SAM projector's own inputs), and less any
|
|
173
|
+
# member a route already sends to a destination of its own: the
|
|
174
|
+
# broken-on-purpose model_check fixtures must produce their findings
|
|
175
|
+
# there, so a clean-corpus gate here would be the wrong check for them.
|
|
176
|
+
MODEL_CHECK_KINDS = %i[example grammar framework vendored qa stress fixture].freeze
|
|
177
|
+
|
|
178
|
+
# Every corpus member `bin/model_check` and `spec/model_check_spec.rb` walk.
|
|
179
|
+
#
|
|
180
|
+
# @param root [String] repository root to search under
|
|
181
|
+
# @return [Array<Member>] members of `MODEL_CHECK_KINDS`, less any routed elsewhere
|
|
182
|
+
def model_check_members(root: ROOT)
|
|
183
|
+
members(*MODEL_CHECK_KINDS, root: root).reject { |member| route_for(member.path.delete_prefix("#{root}/")) }
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# **The ledger sweeps itself** — its own chapter is a domain like any
|
|
187
|
+
# other, and the one rotation member that is neither an example nor a
|
|
188
|
+
# stress domain.
|
|
189
|
+
ROTATION_LEDGER = { "quality_control" => "qa/bluebook" }.freeze
|
|
190
|
+
|
|
191
|
+
# What the QA rotation is made of — every example and stress domain
|
|
192
|
+
# this repository owns, plus the ledger, as `reference => repo-relative
|
|
193
|
+
# path`: exactly the shape `Target.path` is stored in.
|
|
194
|
+
#
|
|
195
|
+
# Derived, because the hand-kept version silently went stale.
|
|
196
|
+
# `bin/qa_seed_targets` carried a literal list naming three of the
|
|
197
|
+
# thirteen stress domains; the other ten were authored, argued for in
|
|
198
|
+
# their own NOTES.md, several promoted by `bin/qa_generated_domains
|
|
199
|
+
# --promote` — and never swept once, because a `Target` row is what
|
|
200
|
+
# puts a domain in the rotation and nothing tied that list to the
|
|
201
|
+
# corpus. Promotion only ever printed the `target.identify` line for a
|
|
202
|
+
# human to run.
|
|
203
|
+
#
|
|
204
|
+
# @param root [String] repository root to search under
|
|
205
|
+
# @return [Hash{String => String}] each rotation member's stem/reference mapped
|
|
206
|
+
# to its repo-relative path
|
|
207
|
+
def rotation_targets(root: ROOT)
|
|
208
|
+
members(:example, :stress, root: root)
|
|
209
|
+
.to_h { |member| [member.stem, member.path.delete_prefix("#{root}/")] }
|
|
210
|
+
.merge(ROTATION_LEDGER)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Every bootable domain in the project, not a hand-kept list — any
|
|
214
|
+
# directory holding a `.bluebook` no route sends elsewhere, a
|
|
215
|
+
# `bluebook/` folder standing for the domain directory around it.
|
|
216
|
+
#
|
|
217
|
+
# @param root [String] repository root to search under
|
|
218
|
+
# @return [Array<String>] absolute paths of every sweepable domain directory
|
|
219
|
+
def sweepable_domains(root = ROOT)
|
|
220
|
+
Dir.chdir(root) do
|
|
221
|
+
Dir.glob("**/*.bluebook")
|
|
222
|
+
.reject { |path| route_for(path) }
|
|
223
|
+
.map { |path| File.dirname(path) }
|
|
224
|
+
.map { |dir| File.basename(dir) == "bluebook" ? File.dirname(dir) : dir }
|
|
225
|
+
.uniq.sort
|
|
226
|
+
.map { |dir| File.join(root, dir) }
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# The domain directory a member stands for, spelled the way
|
|
231
|
+
# `sweepable_domains` spells it: a `bluebook/` folder is its parent.
|
|
232
|
+
#
|
|
233
|
+
# @param member [Member] the corpus member
|
|
234
|
+
# @return [String] the member's owning domain directory path
|
|
235
|
+
def domain_dir_of(member)
|
|
236
|
+
dir = File.directory?(member.path) ? member.path : File.dirname(member.path)
|
|
237
|
+
File.basename(dir) == "bluebook" ? File.dirname(dir) : dir
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# ── The Rust-facing corpus ─────────────────────────────────────────
|
|
241
|
+
#
|
|
242
|
+
# Every Rust-facing list (the fuzz bridge, the codegen drift check,
|
|
243
|
+
# rust coverage, codegen parity) reads `rust/Cargo.toml`'s `[features]`
|
|
244
|
+
# as its one source, rather than being typed out by hand per consumer
|
|
245
|
+
# and drifting from it — hand-typed, fuzzing once saw only 8 of 20
|
|
246
|
+
# features. The `rust/src/generated/` modules split into buckets, and
|
|
247
|
+
# spec/corpus_rust_spec.rb proves every Cargo feature and every
|
|
248
|
+
# generated module lands in exactly one of them:
|
|
249
|
+
#
|
|
250
|
+
# rust_domains an in-repo domain directory with a Cargo feature
|
|
251
|
+
# of its own: fuzzed, regenerated, coverage- and
|
|
252
|
+
# parity-checked
|
|
253
|
+
# framework chapters no feature and no merged.rs; written as a side
|
|
254
|
+
# effect of every `uses_framework` domain's regen
|
|
255
|
+
# RUST_ELSEWHERE a feature with no in-repo domain directory, and
|
|
256
|
+
# the check that owns it instead
|
|
257
|
+
GENERATED_DIR = "rust/src/generated".freeze
|
|
258
|
+
RUST_DOMAIN_KINDS = %i[example stress fixture].freeze
|
|
259
|
+
|
|
260
|
+
RustDomain = Struct.new(:feature, :dir, :kind)
|
|
261
|
+
|
|
262
|
+
# `check: :named_in` — `destination` must name `names`.
|
|
263
|
+
# `check: :external` — no in-repo domain directory may carry the name;
|
|
264
|
+
# the day one does, it belongs in rust_domains.
|
|
265
|
+
Elsewhere = Struct.new(:check, :destination, :names, :why)
|
|
266
|
+
|
|
267
|
+
RUST_ELSEWHERE = {
|
|
268
|
+
"meta" => Elsewhere.new(:named_in, "spec/codegen_parity_spec.rb", "bluebook_language",
|
|
269
|
+
"the self-hosted grammar (lib/hecks/language), not a domain directory — every " \
|
|
270
|
+
"bin/project_rust run rewrites it (so the drift check diffs it), codegen parity " \
|
|
271
|
+
"checks it as bluebook_language, and there is no directory to fuzz"),
|
|
272
|
+
"embryonaut" => Elsewhere.new(:external, "~/Projects/embryonautfoundersapp", "embryonaut",
|
|
273
|
+
"an external product's domain — its bluebook, regeneration and parity are owed " \
|
|
274
|
+
"by its own repo; here bin/rust_coverage checks only the committed snapshot")
|
|
275
|
+
}.freeze
|
|
276
|
+
|
|
277
|
+
# **Shrink-only**. A generated module `bin/rust_coverage` still reports a
|
|
278
|
+
# gap for. `bin/corpus --rust-coverage` requires each of these to
|
|
279
|
+
# still fail, so an entry that starts passing breaks the build until
|
|
280
|
+
# it is deleted here.
|
|
281
|
+
RUST_COVERAGE_PENDING = {}.freeze
|
|
282
|
+
|
|
283
|
+
# The Cargo `[features]` table's raw text.
|
|
284
|
+
#
|
|
285
|
+
# @param root [String] repository root to search under
|
|
286
|
+
# @return [String] the table's text, or `""` when `rust/Cargo.toml` has none
|
|
287
|
+
def cargo_features_table(root: ROOT)
|
|
288
|
+
File.read(File.join(root, "rust/Cargo.toml"))[Fuzzing::TargetCapabilities::FEATURES_TABLE] || ""
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Every feature name Cargo declares with no dependency array of its own.
|
|
292
|
+
#
|
|
293
|
+
# @param root [String] repository root to search under
|
|
294
|
+
# @return [Array<String>] feature names, in file order
|
|
295
|
+
def cargo_features(root: ROOT)
|
|
296
|
+
cargo_features_table(root: root).scan(/^(\w+)\s*=\s*\[\]/).flatten
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# The feature `bin/project_rust` last wrote as Cargo's `default`.
|
|
300
|
+
#
|
|
301
|
+
# @param root [String] repository root to search under
|
|
302
|
+
# @return [String, nil] the default feature's name, or nil when Cargo.toml
|
|
303
|
+
# declares none
|
|
304
|
+
def cargo_default(root: ROOT)
|
|
305
|
+
cargo_features_table(root: root)[/^default\s*=\s*\["(\w+)"\]/, 1]
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Every in-repo domain directory whose name is a Cargo feature, sorted
|
|
309
|
+
# by path. When the module is already generated, its metadata.rs stamp
|
|
310
|
+
# decides which directory it came from — a directory name alone is not
|
|
311
|
+
# enough: spec/fixtures/qa_discover_external_domains vendors a second
|
|
312
|
+
# `examples/pizzas` that no Cargo feature was ever generated from.
|
|
313
|
+
#
|
|
314
|
+
# @param root [String] repository root to search under
|
|
315
|
+
# @return [Array<RustDomain>] each Rust-facing domain, sorted by directory path
|
|
316
|
+
def rust_domains(root: ROOT)
|
|
317
|
+
features = cargo_features(root: root)
|
|
318
|
+
members(*RUST_DOMAIN_KINDS, root: root)
|
|
319
|
+
.map { |member| [domain_dir_of(member), member.kind] }
|
|
320
|
+
.uniq(&:first)
|
|
321
|
+
.select { |dir, _| rust_domain_dir?(dir, features, root) }
|
|
322
|
+
.sort_by(&:first)
|
|
323
|
+
.map { |dir, kind| RustDomain.new(File.basename(dir).downcase, dir, kind) }
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Tells whether `dir` is the real source directory for its Cargo feature.
|
|
327
|
+
#
|
|
328
|
+
# @param dir [String] a candidate domain directory
|
|
329
|
+
# @param features [Array<String>] known Cargo feature names
|
|
330
|
+
# @param root [String] repository root `dir` is rooted under
|
|
331
|
+
# @return [Boolean]
|
|
332
|
+
def rust_domain_dir?(dir, features, root)
|
|
333
|
+
feature = File.basename(dir).downcase
|
|
334
|
+
source = generated_source(feature, root: root)
|
|
335
|
+
features.include?(feature) && (source.nil? || source == dir.delete_prefix("#{root}/"))
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Where a generated module came from, read off the stamp bin/project_rust
|
|
339
|
+
# writes into its metadata.rs — `examples/pizzas`, `/abs/path/embryonaut`,
|
|
340
|
+
# `the self-hosted language (lib/hecks/language/bluebook)`, with any
|
|
341
|
+
# ` (uses_framework "X")` or ` (uses_embryonaut_bluebook "X")` suffix
|
|
342
|
+
# dropped. `nil` when not generated.
|
|
343
|
+
SOURCE_STAMP = %r{
|
|
344
|
+
GENERATED\ by\ bin/project_rust\ —\ (.+?)
|
|
345
|
+
(?:\ \((?:uses_framework|uses_embryonaut_bluebook)\ "\w+"\))?
|
|
346
|
+
's\ own\ canonical\ IR,
|
|
347
|
+
}x
|
|
348
|
+
|
|
349
|
+
# Where a generated module came from, read off the stamp `bin/project_rust`
|
|
350
|
+
# writes into its metadata.rs.
|
|
351
|
+
#
|
|
352
|
+
# @param module_name [String] a generated module's name
|
|
353
|
+
# @param root [String] repository root to search under
|
|
354
|
+
# @return [String, nil] the source path the stamp names, or nil when the module
|
|
355
|
+
# is not generated
|
|
356
|
+
def generated_source(module_name, root: ROOT)
|
|
357
|
+
metadata = File.join(root, GENERATED_DIR, module_name, "metadata.rs")
|
|
358
|
+
File.file?(metadata) ? File.read(metadata)[SOURCE_STAMP, 1] : nil
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Every module directory under `rust/src/generated`.
|
|
362
|
+
#
|
|
363
|
+
# @param root [String] repository root to search under
|
|
364
|
+
# @return [Array<String>] generated module names, sorted
|
|
365
|
+
def generated_modules(root: ROOT)
|
|
366
|
+
Dir.children(File.join(root, GENERATED_DIR)).select { |name| File.directory?(File.join(root, GENERATED_DIR, name)) }.sort
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# Tells whether `feature` has already been generated (has a merged.rs).
|
|
370
|
+
#
|
|
371
|
+
# @param feature [String] a Cargo feature / domain name
|
|
372
|
+
# @param root [String] repository root to search under
|
|
373
|
+
# @return [Boolean]
|
|
374
|
+
def generated?(feature, root: ROOT)
|
|
375
|
+
File.file?(File.join(root, GENERATED_DIR, feature, "merged.rs"))
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# Chapters attached via `kind` (`:framework` or `:vendored`) that
|
|
379
|
+
# `bin/project_rust` writes a module for as a side effect of the
|
|
380
|
+
# attaching domain — a module, no merged.rs of its own. Both kinds
|
|
381
|
+
# share this shape: `bin/project_rust`'s own generated module name is
|
|
382
|
+
# always `chapter_name.downcase` (rust/src/generated/mod.rs's own
|
|
383
|
+
# convention), and for either kind that downcased name is always the
|
|
384
|
+
# original lowercase directory stem again — confirmed live for a
|
|
385
|
+
# framework member and for a vendored package alike (a vendored
|
|
386
|
+
# package's own bluebook stem doesn't have to equal its declared
|
|
387
|
+
# chapter name the way a framework member's does — `EmbryonautBluebook.
|
|
388
|
+
# load!` resolves it as `Naming.pascal(dir_name)`, e.g. "widgets" ->
|
|
389
|
+
# "Widgets" — but downcasing a `Naming.pascal`-derived name always
|
|
390
|
+
# returns the original lowercase stem again).
|
|
391
|
+
#
|
|
392
|
+
# @param kind [Symbol] :framework or :vendored
|
|
393
|
+
# @param root [String] repository root to search under
|
|
394
|
+
# @return [Array<String>] stems with a generated module but no merged.rs
|
|
395
|
+
# of their own
|
|
396
|
+
def rust_side_chapters(kind, root: ROOT)
|
|
397
|
+
modules = generated_modules(root: root)
|
|
398
|
+
members(kind, root: root).map(&:stem)
|
|
399
|
+
.select { |stem| modules.include?(stem) && !generated?(stem, root: root) }
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def rust_framework_chapters(root: ROOT)
|
|
403
|
+
rust_side_chapters(:framework, root: root)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def rust_vendored_chapters(root: ROOT)
|
|
407
|
+
rust_side_chapters(:vendored, root: root)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# THE REGENERATION ORDER the drift check runs. Sorted by path, so
|
|
411
|
+
# which domain runs last — and so wins Cargo's `default`, mod.rs's cfg
|
|
412
|
+
# comments and the shared framework modules' attribution stamp — is a
|
|
413
|
+
# fact of the sorted list, not a hand-picked order: today that is
|
|
414
|
+
# `has_many_fixture`. spec/corpus_rust_spec.rb pins last == default.
|
|
415
|
+
#
|
|
416
|
+
# @param root [String] repository root to search under
|
|
417
|
+
# @return [Array<RustDomain>] already-generated Rust domains, in regeneration order
|
|
418
|
+
def rust_regen_order(root: ROOT)
|
|
419
|
+
rust_domains(root: root).select { |domain| generated?(domain.feature, root: root) }
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# The chapter a bluebook declares — `Hecks.bluebook "<Name>"`, read
|
|
423
|
+
# off the file rather than guessed from its name (a grammar chapter's
|
|
424
|
+
# file is named after its role, `aggregate.bluebook`, while its
|
|
425
|
+
# chapter is always "Bluebook"). Scans the whole file: a framework
|
|
426
|
+
# member's header comment can run past any fixed line cap.
|
|
427
|
+
#
|
|
428
|
+
# @param bluebook_path [String, Array<String>] a `.bluebook` file path, or an
|
|
429
|
+
# array whose first element is used
|
|
430
|
+
# @return [String, nil] the declared chapter name, or nil when the file never
|
|
431
|
+
# declares one
|
|
432
|
+
def chapter_name_of(bluebook_path)
|
|
433
|
+
bluebook_path = Array(bluebook_path).first
|
|
434
|
+
header = File.foreach(bluebook_path).find { |line| line =~ /\A\s*Hecks\.bluebook\s+"([^"]+)"/ }
|
|
435
|
+
header && Regexp.last_match(1)
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
end
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
# The Deploy domain, bound so it boots standing alone.
|
|
2
2
|
Hecks.hecksagon "Deploy" do
|
|
3
3
|
persisted_by "Memory"
|
|
4
|
+
|
|
5
|
+
# THE IMPERATIVE HALF, BEHIND A REAL PORT — `bin/project_tenant`
|
|
6
|
+
# dispatches this `asks` once `Tenant.Declare` has already validated
|
|
7
|
+
# the request; `TenantProvisioner` (lib/hecks/adapters/driven/
|
|
8
|
+
# tenant_provisioner.rb) writes the overlay file and answers or
|
|
9
|
+
# refuses. `database`/`adapter`/`directory` are declared here, not on
|
|
10
|
+
# `Tenant` itself — CLI-only facts this validated request never
|
|
11
|
+
# needed to hold as its own state. `directory` is the real
|
|
12
|
+
# filesystem path being provisioned — NOT re-derivable from `Tenant`'s
|
|
13
|
+
# own `domain` (its declared NAME, e.g. "Scratch", never a path).
|
|
14
|
+
Deploy::Tenant.port "TenantProvisioning" do
|
|
15
|
+
asks "Provision", to: Tenant do
|
|
16
|
+
attribute :database, DatabaseName
|
|
17
|
+
attribute :adapter, AdapterName
|
|
18
|
+
attribute :directory, DirectoryPath
|
|
19
|
+
answers "TenantProvisioned"
|
|
20
|
+
refuses "ProvisioningRefused"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
4
23
|
end
|