hecks 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +5 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +4 -4
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +18 -18
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +23 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +101 -87
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +170 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +180 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +140 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +37 -18
- data/lib/hecks/fuzzing/form_census.rb +35 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +62 -62
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +19 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +9 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +154 -49
- data/lib/hecks/fuzzing/properties/guards.rb +42 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +22 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +24 -24
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +64 -40
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +114 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +10 -10
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +141 -112
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +35 -35
- 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 +58 -31
- data/lib/hecks/fuzzing/sequence_generator.rb +111 -23
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +23 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +4 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +49 -18
- data/lib/hecks/fuzzing/value_generator.rb +14 -14
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +25 -25
- data/lib/hecks/ports/access_control.rb +58 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +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 +28 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +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 +57 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +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 +42 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +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 +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +3 -3
- data/lib/hecks/projections/glossary/markdown.rb +3 -3
- data/lib/hecks/projections/glossary/mermaid.rb +1 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +5 -5
- data/lib/hecks/projections/glossary.rb +15 -15
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +11 -11
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +57 -13
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +42 -63
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +35 -35
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +151 -67
- data/lib/hecks/runtime/entity_interpreter.rb +108 -82
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +55 -64
- data/lib/hecks/runtime/reaction_invocation.rb +20 -20
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +45 -28
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -153
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +64 -64
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +318 -127
- data/lib/hecks/runtime/value/entity_list_coercion.rb +34 -34
- data/lib/hecks/runtime/value.rb +21 -21
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +22 -2
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
require_relative "../projector"
|
|
2
|
+
require_relative "../runtime/refusal_wording"
|
|
3
|
+
require_relative "vocabulary"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Projections
|
|
7
|
+
# **The language's closed sets, projected into the Rust kernel** — one
|
|
8
|
+
# `rust/src/kernel/vocab/<table>.rs` per Vocabulary table, each a
|
|
9
|
+
# `pub enum` plus `match`-based accessors with no wildcard arm, so a
|
|
10
|
+
# row the chapter gains is a compile error at every exhaustive match
|
|
11
|
+
# over it rather than a silent gap.
|
|
12
|
+
#
|
|
13
|
+
# Projector.call(:rust_vocabulary, bluebook: <the Bluebook chapter>)
|
|
14
|
+
# # => { "vocab/mod.rs" => "...", "vocab/refusal_template.rs" => "...", ... }
|
|
15
|
+
#
|
|
16
|
+
# Paths are relative to rust/src/kernel/; bin/project_rust_vocabulary
|
|
17
|
+
# writes them. This generalizes the old bin/project_refusal_wording,
|
|
18
|
+
# which read Runtime::RefusalWording::TEMPLATES — a hand-typed copy of
|
|
19
|
+
# the RefusalTemplate rows. This reads the chapter itself.
|
|
20
|
+
#
|
|
21
|
+
# **Only TABLES the kernel needs**. Each entry in TABLES names its enum
|
|
22
|
+
# and its generated extras; a table not listed is not projected. The
|
|
23
|
+
# enum names that differ from the table name keep existing Rust call
|
|
24
|
+
# sites (`RefusalSite`) and the dispatch-step names D1 consumes
|
|
25
|
+
# (`AggregateStep`, `EntityStep`).
|
|
26
|
+
#
|
|
27
|
+
# TYPED REFUSAL ARGUMENTS. The templates table also reads
|
|
28
|
+
# RefusalSiteArgument: every site gets a `<Variant>Args` struct whose
|
|
29
|
+
# fields are exactly the site's declared arguments (a `&str` per
|
|
30
|
+
# scalar, a `&[&str]` per list) and a `render_args` that formats each
|
|
31
|
+
# one by its row. `render` itself stays private to the generated
|
|
32
|
+
# module, so no call site can pass an argument list by hand — leaving
|
|
33
|
+
# one out does not compile. The generated test pins every site's
|
|
34
|
+
# `render_args` output (empty, single and multiple unsorted lists)
|
|
35
|
+
# against Runtime::RefusalWording.render_with, computed here in Ruby.
|
|
36
|
+
module RustVocabulary
|
|
37
|
+
extend Projector::Target
|
|
38
|
+
|
|
39
|
+
projects_as :rust_vocabulary, declares: "Vocabulary", emits: :files
|
|
40
|
+
|
|
41
|
+
GENERATOR = "bin/project_rust_vocabulary".freeze
|
|
42
|
+
SOURCE = "lib/hecks/language/bluebook/vocabulary.bluebook".freeze
|
|
43
|
+
|
|
44
|
+
# table name => enum name, variant-naming fields, file stem, kind.
|
|
45
|
+
# `:order` tables also get `ORDER` and `position`; `:templates`
|
|
46
|
+
# gets `render`, the typed `<Variant>Args` (read off the `arguments`
|
|
47
|
+
# table) and the placeholder and argument-rendering tests.
|
|
48
|
+
TABLES = {
|
|
49
|
+
"RefusalTemplate" => { enum: "RefusalSite", variant_from: %w[refusal site], file: "refusal_template",
|
|
50
|
+
kind: :templates, arguments: "RefusalSiteArgument" },
|
|
51
|
+
"QueryComparator" => { enum: "QueryComparator", variant_from: %w[name], file: "query_comparator", kind: :set },
|
|
52
|
+
"FieldHint" => { enum: "FieldHint", variant_from: %w[name], file: "field_hint", kind: :set },
|
|
53
|
+
"AggregateDispatchOrder" => { enum: "AggregateStep", variant_from: %w[step], file: "aggregate_dispatch_order",
|
|
54
|
+
kind: :order },
|
|
55
|
+
"EntityDispatchOrder" => { enum: "EntityStep", variant_from: %w[step], file: "entity_dispatch_order",
|
|
56
|
+
kind: :order }
|
|
57
|
+
}.freeze
|
|
58
|
+
|
|
59
|
+
RUST_KEYWORDS = %w[as async await break const continue crate dyn else enum extern false fn for if impl in let loop
|
|
60
|
+
match mod move mut pub ref return self static struct super trait true type unsafe use where
|
|
61
|
+
while abstract become box do final macro override priv typeof unsized virtual yield try].freeze
|
|
62
|
+
|
|
63
|
+
module_function
|
|
64
|
+
|
|
65
|
+
def call(bluebook:, options: {}) = render(bluebook)
|
|
66
|
+
|
|
67
|
+
def render(bluebook)
|
|
68
|
+
tables = Vocabulary.tables(bluebook)
|
|
69
|
+
files = TABLES.to_h do |table, spec|
|
|
70
|
+
rows = tables.fetch(table) { raise ArgumentError, "Vocabulary declares no #{table} table" }
|
|
71
|
+
arguments = spec[:arguments] && tables.fetch(spec[:arguments]) do
|
|
72
|
+
raise ArgumentError, "Vocabulary declares no #{spec[:arguments]} table"
|
|
73
|
+
end
|
|
74
|
+
["vocab/#{spec[:file]}.rs", table_file(table, spec, rows, arguments)]
|
|
75
|
+
end
|
|
76
|
+
{ "vocab/mod.rs" => mod_file }.merge(files)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def header(table)
|
|
80
|
+
<<~RUST
|
|
81
|
+
// GENERATED by #{GENERATOR} (Hecks::Projections::RustVocabulary,
|
|
82
|
+
// lib/hecks/projections/rust_vocabulary.rb) from Vocabulary::#{table}
|
|
83
|
+
// (#{SOURCE}).
|
|
84
|
+
// Do not hand-edit — re-run #{GENERATOR} instead.
|
|
85
|
+
RUST
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def mod_file
|
|
89
|
+
mods = TABLES.values.map { |spec| "pub mod #{spec[:file]};" }.join("\n")
|
|
90
|
+
exports = TABLES.values.map { |spec| "pub use #{spec[:file]}::#{spec[:enum]};" }.join("\n")
|
|
91
|
+
<<~RUST
|
|
92
|
+
// GENERATED by #{GENERATOR} (Hecks::Projections::RustVocabulary,
|
|
93
|
+
// lib/hecks/projections/rust_vocabulary.rb) from the Vocabulary
|
|
94
|
+
// aggregate (#{SOURCE}).
|
|
95
|
+
// Do not hand-edit — re-run #{GENERATOR} instead.
|
|
96
|
+
//
|
|
97
|
+
// One module per projected table. Every enum below is matched
|
|
98
|
+
// exhaustively by its own accessors — no wildcard arm — so a row the
|
|
99
|
+
// language gains fails to compile until it is handled.
|
|
100
|
+
|
|
101
|
+
#{mods}
|
|
102
|
+
|
|
103
|
+
#{exports}
|
|
104
|
+
RUST
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def table_file(table, spec, rows, argument_rows = nil)
|
|
108
|
+
variants = rows.map { |row| variant_name(row, spec[:variant_from]) }
|
|
109
|
+
duplicates = variants.tally.select { |_, count| count > 1 }.keys
|
|
110
|
+
raise ArgumentError, "duplicate #{spec[:enum]} variant(s): #{duplicates.join(', ')}" if duplicates.any?
|
|
111
|
+
|
|
112
|
+
enum = spec[:enum]
|
|
113
|
+
fields = rows.first.keys
|
|
114
|
+
by_site = spec[:kind] == :templates ? site_arguments(rows, argument_rows) : nil
|
|
115
|
+
body = [
|
|
116
|
+
header(table),
|
|
117
|
+
"#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]",
|
|
118
|
+
"pub enum #{enum} {",
|
|
119
|
+
variants.map { |variant| " #{variant}," },
|
|
120
|
+
"}",
|
|
121
|
+
"",
|
|
122
|
+
"impl #{enum} {",
|
|
123
|
+
constants(enum, variants, spec[:kind]),
|
|
124
|
+
fields.map { |field| accessor(enum, field, variants, rows) },
|
|
125
|
+
from_name(enum, spec[:kind]),
|
|
126
|
+
extras(enum, variants, spec[:kind]),
|
|
127
|
+
"}",
|
|
128
|
+
"",
|
|
129
|
+
by_site ? argument_types(enum, variants, rows, by_site) : [],
|
|
130
|
+
tests(enum, spec[:kind], variants, rows, by_site)
|
|
131
|
+
]
|
|
132
|
+
"#{body.flatten.join("\n").rstrip}\n"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def constants(enum, variants, kind)
|
|
136
|
+
list = variants.map { |variant| " #{enum}::#{variant}," }
|
|
137
|
+
if kind == :order
|
|
138
|
+
[" /// The declared step order, first to last.",
|
|
139
|
+
" pub const ORDER: [#{enum}; #{variants.size}] = [", list, " ];", ""]
|
|
140
|
+
else
|
|
141
|
+
[" /// Every row, in declared order.",
|
|
142
|
+
" pub const ALL: &'static [#{enum}] = &[", list, " ];", ""]
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def accessor(enum, field, variants, rows)
|
|
147
|
+
arms = variants.zip(rows).map { |variant, row| " #{enum}::#{variant} => #{rust_string(row.fetch(field))}," }
|
|
148
|
+
[" /// The row's declared `#{field}`.",
|
|
149
|
+
" pub fn #{accessor_name(field)}(&self) -> &'static str {",
|
|
150
|
+
" match self {",
|
|
151
|
+
arms,
|
|
152
|
+
" }",
|
|
153
|
+
" }",
|
|
154
|
+
""]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Built on the enum's own list and name accessor, not a string
|
|
158
|
+
# match, so it needs no wildcard arm either.
|
|
159
|
+
def from_name(enum, kind)
|
|
160
|
+
list = kind == :order ? "ORDER" : "ALL"
|
|
161
|
+
field = kind == :templates ? "site" : name_field(kind)
|
|
162
|
+
[" /// The row whose `#{field}` is `name`, if any.",
|
|
163
|
+
" pub fn from_name(name: &str) -> Option<#{enum}> {",
|
|
164
|
+
" #{enum}::#{list}.iter().copied().find(|row| row.#{accessor_name(field)}() == name)",
|
|
165
|
+
" }",
|
|
166
|
+
""]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def extras(enum, variants, kind)
|
|
170
|
+
case kind
|
|
171
|
+
when :order
|
|
172
|
+
arms = variants.each_with_index.map { |variant, index| " #{enum}::#{variant} => #{index}," }
|
|
173
|
+
[" /// This step's index in `ORDER`.",
|
|
174
|
+
" pub fn position(&self) -> usize {",
|
|
175
|
+
" match self {",
|
|
176
|
+
arms,
|
|
177
|
+
" }",
|
|
178
|
+
" }"]
|
|
179
|
+
when :templates
|
|
180
|
+
[" /// `RefusalWording.substitute`: replace every `{key}` marker, in the",
|
|
181
|
+
" /// order given. A template is read, never evaluated. Private: call",
|
|
182
|
+
" /// sites go through a site's typed `<Variant>Args::render_args`,",
|
|
183
|
+
" /// which formats and supplies every declared argument.",
|
|
184
|
+
" fn render(&self, values: &[(&str, &str)]) -> String {",
|
|
185
|
+
" let mut text = self.template().to_string();",
|
|
186
|
+
" for (key, value) in values {",
|
|
187
|
+
" text = text.replace(&format!(\"{{{key}}}\"), value);",
|
|
188
|
+
" }",
|
|
189
|
+
" text",
|
|
190
|
+
" }"]
|
|
191
|
+
else
|
|
192
|
+
[]
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# [refusal, site] => that site's argument rows, in declared order —
|
|
197
|
+
# refused unless they name exactly the template's own placeholders,
|
|
198
|
+
# in the order each first appears, with only known formatting rules.
|
|
199
|
+
def site_arguments(rows, argument_rows)
|
|
200
|
+
grouped = argument_rows.group_by { |row| [row.fetch("refusal"), row.fetch("site")] }
|
|
201
|
+
orphans = grouped.keys - rows.map { |row| [row.fetch("refusal"), row.fetch("site")] }
|
|
202
|
+
raise ArgumentError, "RefusalSiteArgument rows name no RefusalTemplate: #{orphans.inspect}" if orphans.any?
|
|
203
|
+
|
|
204
|
+
rows.to_h do |row|
|
|
205
|
+
key = [row.fetch("refusal"), row.fetch("site")]
|
|
206
|
+
specs = grouped.fetch(key, [])
|
|
207
|
+
wants = row.fetch("template").scan(/\{(\w+)\}/).flatten.uniq
|
|
208
|
+
names = specs.map { |spec| spec.fetch("argument") }
|
|
209
|
+
unless names == wants
|
|
210
|
+
raise ArgumentError, "RefusalSiteArgument for #{key.join('/')} declares #{names.inspect}; " \
|
|
211
|
+
"its template's placeholders are #{wants.inspect}"
|
|
212
|
+
end
|
|
213
|
+
specs.each { |spec| check_rule!(key, spec) }
|
|
214
|
+
[key, specs]
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def check_rule!(key, spec)
|
|
219
|
+
where = "#{key.join('/')}.#{spec.fetch('argument')}"
|
|
220
|
+
unless Runtime::RefusalWording::SHAPES.include?(spec.fetch("shape"))
|
|
221
|
+
raise ArgumentError, "#{where}: unknown shape #{spec['shape'].inspect}"
|
|
222
|
+
end
|
|
223
|
+
unless Runtime::RefusalWording::QUOTINGS.include?(spec.fetch("quoting"))
|
|
224
|
+
raise ArgumentError, "#{where}: unknown quoting #{spec['quoting'].inspect}"
|
|
225
|
+
end
|
|
226
|
+
return if %w[true false].include?(spec.fetch("sorted"))
|
|
227
|
+
|
|
228
|
+
raise ArgumentError, "#{where}: sorted must be \"true\" or \"false\""
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def argument_types(enum, variants, rows, by_site)
|
|
232
|
+
support = [
|
|
233
|
+
"/// Ruby's `#inspect` of a name, as a refusal quotes it: `{:?}` on a",
|
|
234
|
+
"/// `&str`, the quoting every kernel call site used before",
|
|
235
|
+
"/// RefusalSiteArgument existed.",
|
|
236
|
+
"fn quoted(text: &str) -> String {",
|
|
237
|
+
" format!(\"{text:?}\")",
|
|
238
|
+
"}",
|
|
239
|
+
"",
|
|
240
|
+
"/// A list argument, written the way its RefusalSiteArgument row says:",
|
|
241
|
+
"/// sorted first (before quoting), each item quoted, then joined; an",
|
|
242
|
+
"/// empty list reads `when_empty`.",
|
|
243
|
+
"fn list(items: &[&str], sorted: bool, inspect: bool, separator: &str, when_empty: &str) -> String {",
|
|
244
|
+
" let mut items = items.to_vec();",
|
|
245
|
+
" if sorted {",
|
|
246
|
+
" items.sort_unstable();",
|
|
247
|
+
" }",
|
|
248
|
+
" if items.is_empty() {",
|
|
249
|
+
" return when_empty.to_string();",
|
|
250
|
+
" }",
|
|
251
|
+
" let write = |item: &&str| if inspect { quoted(item) } else { item.to_string() };",
|
|
252
|
+
" items.iter().map(write).collect::<Vec<_>>().join(separator)",
|
|
253
|
+
"}",
|
|
254
|
+
""
|
|
255
|
+
]
|
|
256
|
+
support + variants.zip(rows).flat_map do |variant, row|
|
|
257
|
+
args_struct(enum, variant, row, by_site.fetch([row.fetch("refusal"), row.fetch("site")]))
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def args_struct(enum, variant, row, specs)
|
|
262
|
+
fields = specs.flat_map do |spec|
|
|
263
|
+
type = spec.fetch("shape") == "list" ? "&'a [&'a str]" : "&'a str"
|
|
264
|
+
[" /// #{rule_reading(spec)}", " pub #{rust_field(spec.fetch('argument'))}: #{type},"]
|
|
265
|
+
end
|
|
266
|
+
locals = specs.filter_map { |spec| formatted_local(spec) }
|
|
267
|
+
pairs = specs.map do |spec|
|
|
268
|
+
name = spec.fetch("argument")
|
|
269
|
+
value = formatted_local(spec) ? "#{local_name(name)}.as_str()" : "self.#{rust_field(name)}"
|
|
270
|
+
" (#{rust_string(name)}, #{value}),"
|
|
271
|
+
end
|
|
272
|
+
["/// `#{enum}::#{variant}`'s arguments — `RefusalWording.render_site(" \
|
|
273
|
+
"#{rust_string(row.fetch('refusal'))}, #{rust_string(row.fetch('site'))}, ...)`.",
|
|
274
|
+
"#[derive(Debug, Clone, Copy)]",
|
|
275
|
+
"pub struct #{variant}Args<'a> {",
|
|
276
|
+
fields,
|
|
277
|
+
"}",
|
|
278
|
+
"",
|
|
279
|
+
"impl #{variant}Args<'_> {",
|
|
280
|
+
" /// The site's wording, every argument formatted by its declared row.",
|
|
281
|
+
" pub fn render_args(&self) -> String {",
|
|
282
|
+
locals.map { |line| " #{line}" },
|
|
283
|
+
" #{enum}::#{variant}.render(&[",
|
|
284
|
+
pairs,
|
|
285
|
+
" ])",
|
|
286
|
+
" }",
|
|
287
|
+
"}",
|
|
288
|
+
""]
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def formatted_local(spec)
|
|
292
|
+
name = spec.fetch("argument")
|
|
293
|
+
field = "self.#{rust_field(name)}"
|
|
294
|
+
if spec.fetch("shape") == "list"
|
|
295
|
+
"let #{local_name(name)} = list(#{field}, #{spec.fetch('sorted')}, #{spec.fetch('quoting') == 'inspect'}, " \
|
|
296
|
+
"#{rust_string(spec.fetch('separator'))}, #{rust_string(spec.fetch('when_empty'))});"
|
|
297
|
+
elsif spec.fetch("quoting") == "inspect"
|
|
298
|
+
"let #{local_name(name)} = quoted(#{field});"
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def rule_reading(spec)
|
|
303
|
+
quoting = spec.fetch("quoting") == "inspect" ? ", quoted" : ""
|
|
304
|
+
return "scalar#{quoting}" unless spec.fetch("shape") == "list"
|
|
305
|
+
|
|
306
|
+
sorted = spec.fetch("sorted") == "true" ? ", sorted" : ""
|
|
307
|
+
"list#{sorted}#{quoting}, joined #{spec.fetch('separator').inspect}, empty reads #{spec.fetch('when_empty').inspect}"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def local_name(argument) = "#{argument}_text"
|
|
311
|
+
|
|
312
|
+
def rust_field(argument) = RUST_KEYWORDS.include?(argument) ? "r##{argument}" : argument
|
|
313
|
+
|
|
314
|
+
def tests(enum, kind, variants = [], rows = [], by_site = nil)
|
|
315
|
+
list = kind == :order ? "ORDER" : "ALL"
|
|
316
|
+
name = accessor_name(kind == :templates ? "site" : name_field(kind))
|
|
317
|
+
lines = ["#[cfg(test)]",
|
|
318
|
+
"mod tests {",
|
|
319
|
+
" use super::*;",
|
|
320
|
+
""]
|
|
321
|
+
unless kind == :templates
|
|
322
|
+
lines += [" #[test]",
|
|
323
|
+
" fn every_row_is_found_by_its_own_name() {",
|
|
324
|
+
" for row in #{enum}::#{list}.iter() {",
|
|
325
|
+
" assert_eq!(#{enum}::from_name(row.#{name}()), Some(*row));",
|
|
326
|
+
" }",
|
|
327
|
+
" }",
|
|
328
|
+
""]
|
|
329
|
+
end
|
|
330
|
+
lines += order_test(enum) if kind == :order
|
|
331
|
+
if kind == :templates
|
|
332
|
+
lines += placeholder_test(enum)
|
|
333
|
+
lines += [""] + render_args_test(variants, rows, by_site)
|
|
334
|
+
end
|
|
335
|
+
lines.pop while lines.last == ""
|
|
336
|
+
lines + ["}"]
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def order_test(enum)
|
|
340
|
+
[" #[test]",
|
|
341
|
+
" fn position_is_the_index_in_order() {",
|
|
342
|
+
" for (index, step) in #{enum}::ORDER.iter().enumerate() {",
|
|
343
|
+
" assert_eq!(step.position(), index);",
|
|
344
|
+
" }",
|
|
345
|
+
" }",
|
|
346
|
+
""]
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Placeholders are read off each template's own text, never a second
|
|
350
|
+
# hand-kept list.
|
|
351
|
+
def placeholder_test(enum)
|
|
352
|
+
[" #[test]",
|
|
353
|
+
" fn every_site_renders_with_no_leftover_placeholder() {",
|
|
354
|
+
" for site in #{enum}::ALL {",
|
|
355
|
+
" let template = site.template();",
|
|
356
|
+
" let mut keys: Vec<String> = Vec::new();",
|
|
357
|
+
" let mut current: Option<String> = None;",
|
|
358
|
+
" for ch in template.chars() {",
|
|
359
|
+
" match (ch, current.as_mut()) {",
|
|
360
|
+
" ('{', _) => current = Some(String::new()),",
|
|
361
|
+
" ('}', Some(key)) => {",
|
|
362
|
+
" keys.push(key.clone());",
|
|
363
|
+
" current = None;",
|
|
364
|
+
" }",
|
|
365
|
+
" (_, Some(key)) => key.push(ch),",
|
|
366
|
+
" (_, None) => {}",
|
|
367
|
+
" }",
|
|
368
|
+
" }",
|
|
369
|
+
" let dummies: Vec<(String, String)> = keys.iter().map(|k| (k.clone(), format!(\"<{k}>\"))).collect();",
|
|
370
|
+
" let pairs: Vec<(&str, &str)> = dummies.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();",
|
|
371
|
+
" let rendered = site.render(&pairs);",
|
|
372
|
+
" assert!(",
|
|
373
|
+
" !rendered.contains('{') && !rendered.contains('}'),",
|
|
374
|
+
" \"{site:?} left an unrendered placeholder behind: {rendered:?}\"",
|
|
375
|
+
" );",
|
|
376
|
+
" }",
|
|
377
|
+
" }"]
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# THE RUBY ORACLE, PINNED. Every site renders through its typed
|
|
381
|
+
# `render_args` for each edge case a list argument has — empty, one
|
|
382
|
+
# item, several out of order — and must equal what
|
|
383
|
+
# Runtime::RefusalWording.render_with answers for the same values,
|
|
384
|
+
# computed here at generation time. Scalars carry a `"` so quoting
|
|
385
|
+
# is compared too.
|
|
386
|
+
def render_args_test(variants, rows, by_site)
|
|
387
|
+
asserts = variants.zip(rows).flat_map do |variant, row|
|
|
388
|
+
specs = by_site.fetch([row.fetch("refusal"), row.fetch("site")])
|
|
389
|
+
argument_cases(specs).map do |arguments|
|
|
390
|
+
expected = Runtime::RefusalWording.render_with(row.fetch("template"), specs, arguments)
|
|
391
|
+
fields = specs.map do |spec|
|
|
392
|
+
value = arguments.fetch(spec.fetch("argument").to_sym)
|
|
393
|
+
rust_value = value.is_a?(Array) ? "&[#{value.map { |item| rust_string(item) }.join(', ')}]" : rust_string(value)
|
|
394
|
+
"#{rust_field(spec.fetch('argument'))}: #{rust_value}"
|
|
395
|
+
end
|
|
396
|
+
[" assert_eq!(",
|
|
397
|
+
" #{variant}Args { #{fields.join(', ')} }.render_args(),",
|
|
398
|
+
" #{rust_string(expected)}",
|
|
399
|
+
" );"]
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
[" #[test]",
|
|
403
|
+
" fn render_args_matches_ruby_render_site() {",
|
|
404
|
+
asserts,
|
|
405
|
+
" }"].flatten
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
LIST_CASES = [[], ["only \"one\""], %w[zeta alpha mid]].freeze
|
|
409
|
+
|
|
410
|
+
def argument_cases(specs)
|
|
411
|
+
cases = specs.any? { |spec| spec.fetch("shape") == "list" } ? LIST_CASES : [nil]
|
|
412
|
+
cases.map do |items|
|
|
413
|
+
specs.to_h do |spec|
|
|
414
|
+
name = spec.fetch("argument")
|
|
415
|
+
[name.to_sym, spec.fetch("shape") == "list" ? items : "#{name} \"x\""]
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def name_field(kind) = kind == :order ? "step" : "name"
|
|
421
|
+
|
|
422
|
+
# `refusal` is a Rust-safe word but reads ambiguously beside the
|
|
423
|
+
# kernel's own `Refusal` enum; the accessor says what it answers.
|
|
424
|
+
def accessor_name(field) = field == "refusal" ? "refusal_class" : field
|
|
425
|
+
|
|
426
|
+
def variant_name(row, fields)
|
|
427
|
+
name = fields.map { |field| pascal(row.fetch(field)) }.join
|
|
428
|
+
raise ArgumentError, "#{name.inspect} is not a Rust variant name" unless name.match?(/\A[A-Z][A-Za-z0-9]*\z/)
|
|
429
|
+
|
|
430
|
+
name
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def pascal(text) = text.to_s.split("_").map { |part| part[0].to_s.upcase + part[1..].to_s }.join
|
|
434
|
+
|
|
435
|
+
# A Rust string literal: only `\`, `"` and newlines need escaping in
|
|
436
|
+
# the text any vocabulary row holds.
|
|
437
|
+
def rust_string(text)
|
|
438
|
+
escaped = text.to_s.gsub("\\") { "\\\\" }.gsub('"') { "\\\"" }.gsub("\n") { "\\n" }
|
|
439
|
+
"\"#{escaped}\""
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
@@ -3,11 +3,11 @@ require_relative "../projector"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Projections
|
|
6
|
-
#
|
|
6
|
+
# The storage shape of one bluebook — the same structural form
|
|
7
7
|
# `StorageShape.mint_hash` hashes to name an era, which is what makes
|
|
8
8
|
# a bump/no-bump question answerable by diffing two of these.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
10
|
+
# The retrofit, and the point of it: `Runtime::StorageShape.project`
|
|
11
11
|
# was written long before this framework existed, and it already
|
|
12
12
|
# takes exactly one bluebook, already returns a plain Hash, already
|
|
13
13
|
# touches no disk and no live runtime — it even already uses the
|
|
@@ -2,7 +2,7 @@ require_relative "../projector"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Projections
|
|
5
|
-
# A
|
|
5
|
+
# A domain's own declared facts, projected as plain english sentences
|
|
6
6
|
# — one atomic, independently-checkable statement per fact, not a
|
|
7
7
|
# flowing document (`Projections::Reference`/`DocsProjector` already
|
|
8
8
|
# do that job well; this is a flat list a caller can iterate, diff,
|
|
@@ -10,25 +10,25 @@ module Hecks
|
|
|
10
10
|
# a sanity check that the domain still says what someone thinks it
|
|
11
11
|
# says).
|
|
12
12
|
#
|
|
13
|
-
#
|
|
13
|
+
# **Never invents a sentence from nothing** — `DocsProjector`'s own
|
|
14
14
|
# discipline, held here too: a relationship's sentence is built
|
|
15
|
-
#
|
|
15
|
+
# mechanically from its own declared shape (holder, target,
|
|
16
16
|
# relationship kind — the same facts `Projections::Diagrams`'s own
|
|
17
|
-
# relationship_edge already reads), and an invariant's sentence
|
|
17
|
+
# relationship_edge already reads), and an invariant's sentence is
|
|
18
18
|
# the domain author's own `description`, capitalized and
|
|
19
19
|
# punctuated, never paraphrased. If a fact has no author-written
|
|
20
20
|
# description and no unambiguous mechanical phrasing, it doesn't
|
|
21
21
|
# get a sentence here — a wrong sentence is worse than a missing
|
|
22
22
|
# one.
|
|
23
23
|
#
|
|
24
|
-
#
|
|
24
|
+
# Reachable the same way every projection already is, no new facade
|
|
25
25
|
# wiring needed: `Pizzas.project(Projections::Statements)` —
|
|
26
26
|
# `Facade::Surface::Chapter#project`'s own comment already settled
|
|
27
27
|
# this ("anything genuinely needing the graph is a projector, and a
|
|
28
28
|
# projector is given it"). `bin/statements` is a thin, optional
|
|
29
29
|
# convenience for reaching the same call from a shell.
|
|
30
30
|
#
|
|
31
|
-
# MVP
|
|
31
|
+
# MVP scope: "has many"/"has a"/"belongs to"/"references" sentences
|
|
32
32
|
# for every list or relationship attribute, and every invariant's
|
|
33
33
|
# own description (aggregate-level and every nested value object's),
|
|
34
34
|
# verbatim. Lifecycle transitions and command given/ensures read the
|
|
@@ -50,7 +50,7 @@ module Hecks
|
|
|
50
50
|
attribute_statements(holder) + invariant_statements(holder)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
# "MANY"
|
|
53
|
+
# "MANY" reads the same way regardless of what's behind it — a
|
|
54
54
|
# `has_many` relationship to another aggregate and a plain
|
|
55
55
|
# `list_of(Topping)` value-object attribute are the same idea to
|
|
56
56
|
# someone reading the domain in English ("an Order has many
|
|
@@ -77,17 +77,17 @@ module Hecks
|
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
# "
|
|
80
|
+
# "An account", not "A ACCOUNT" — every subject/object noun here is
|
|
81
81
|
# a bare construct name (`ATMCard`, `ExternalTransfer`, `Account`),
|
|
82
82
|
# never free text, so the ordinary "starts with a vowel LETTER"
|
|
83
83
|
# heuristic is safe: this language's own naming never produces the
|
|
84
84
|
# English exceptions that heuristic gets wrong (an "hour", a
|
|
85
85
|
# "university") because a construct name is always spelled as a
|
|
86
86
|
# plain word, never an abbreviation read letter-by-letter or a
|
|
87
|
-
# word starting with a consonant
|
|
87
|
+
# word starting with a consonant letter but a vowel sound.
|
|
88
88
|
def article(word) = word.to_s.match?(/\A[AEIOUaeiou]/) ? "An" : "A"
|
|
89
89
|
|
|
90
|
-
#
|
|
90
|
+
# Invariants live in two places — directly on the holder (an
|
|
91
91
|
# aggregate-level rule, checked after every command) and on every
|
|
92
92
|
# value object nested inside it — `DocsProjector#rules_of`'s own
|
|
93
93
|
# `value_object_for` lookup is the precedent for walking both.
|
|
@@ -97,7 +97,7 @@ module Hecks
|
|
|
97
97
|
(own + nested).map { |invariant| invariant_statement(invariant) }
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
#
|
|
100
|
+
# The domain author's own words, capitalized and punctuated —
|
|
101
101
|
# nothing else. `invariant("a pizza is named")` already reads as
|
|
102
102
|
# a sentence; this is the entire transformation.
|
|
103
103
|
def invariant_statement(invariant)
|
|
@@ -2,20 +2,20 @@ require_relative "../projector"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Projections
|
|
5
|
-
#
|
|
5
|
+
# **The closed sets, projected** — lib/hecks/vocabulary.rb rendered
|
|
6
6
|
# from whichever chapter declares a `Vocabulary` aggregate, which in
|
|
7
7
|
# practice is the language projecting its own tables.
|
|
8
8
|
#
|
|
9
9
|
# Projector.call(:vocabulary, bluebook: <the Bluebook chapter>)
|
|
10
10
|
#
|
|
11
|
-
# A
|
|
11
|
+
# A projection rather than a bin/ script, and that correction is the
|
|
12
12
|
# point of it. This was first written as its own program with its own
|
|
13
13
|
# call shape, beside `bin/project_parser_table`, `bin/reference` and
|
|
14
14
|
# `bin/expression_projection` — four tools doing "canonical IR in,
|
|
15
15
|
# external artifact out" four different ways, which is the exact
|
|
16
16
|
# thing the projector registry exists to stop.
|
|
17
17
|
#
|
|
18
|
-
# Reads the chapter's
|
|
18
|
+
# Reads the chapter's judged IR, so what is written out is what the
|
|
19
19
|
# language actually holds rather than what a builder happened to
|
|
20
20
|
# produce.
|
|
21
21
|
module Vocabulary
|
|
@@ -24,10 +24,10 @@ module Hecks
|
|
|
24
24
|
projects_as :vocabulary, declares: "Vocabulary"
|
|
25
25
|
|
|
26
26
|
HEADER = <<~RUBY.freeze
|
|
27
|
-
#
|
|
27
|
+
# Generated — projected from the language's own Vocabulary aggregate
|
|
28
28
|
# (lib/hecks/language/bluebook/vocabulary.bluebook).
|
|
29
29
|
#
|
|
30
|
-
#
|
|
30
|
+
# Do not edit. spec/vocabulary_table_spec.rb re-projects this in memory
|
|
31
31
|
# and refuses a diff, so an edit here fails the ordinary suite.
|
|
32
32
|
#
|
|
33
33
|
# Plain data on purpose — no requires, no dependency on the model —
|
|
@@ -42,9 +42,9 @@ module Hecks
|
|
|
42
42
|
# has nothing to vary.
|
|
43
43
|
def call(bluebook:, options: {}) = render(bluebook)
|
|
44
44
|
|
|
45
|
-
#
|
|
45
|
+
# Full rows, not just the first field of each.
|
|
46
46
|
#
|
|
47
|
-
# Most vocabularies
|
|
47
|
+
# Most vocabularies are one-field lists and the field is the term.
|
|
48
48
|
# Several are not: `Comparison` declares the algebra each operator
|
|
49
49
|
# computes with, `RefusalTemplate` an error plus its key and text.
|
|
50
50
|
# Taking the first field of those produced a list of thirty-nine
|
|
@@ -69,8 +69,8 @@ module Hecks
|
|
|
69
69
|
#{rows.join(",\n")}
|
|
70
70
|
}.freeze
|
|
71
71
|
|
|
72
|
-
#
|
|
73
|
-
# one-field vocabulary is the whole of it. Derived
|
|
72
|
+
# The terms — the first field of each row, which for a
|
|
73
|
+
# one-field vocabulary is the whole of it. Derived once and
|
|
74
74
|
# frozen rather than mapped per call: these are constant
|
|
75
75
|
# tables, and a constant that allocates a new array every
|
|
76
76
|
# time it is read is not one.
|
data/lib/hecks/projections.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require_relative "projector"
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
|
-
#
|
|
4
|
+
# The targets a domain can be projected into, as constants rather than
|
|
5
5
|
# bare symbols — `Pizzas.project(Projections::OIDC)`.
|
|
6
6
|
#
|
|
7
7
|
# A constant is worth the namespace for two reasons a Symbol cannot
|
|
@@ -10,9 +10,9 @@ module Hecks
|
|
|
10
10
|
# carry its own documentation and defaults.
|
|
11
11
|
#
|
|
12
12
|
# Namespaced rather than top-level because `IR` is already taken —
|
|
13
|
-
# `Hecks::Bluebook` is the
|
|
13
|
+
# `Hecks::Bluebook` is the model (`Bluebook::Command`, and the
|
|
14
14
|
# chapter class itself).
|
|
15
|
-
# `Projections::IR` is the projection
|
|
15
|
+
# `Projections::IR` is the projection of that model, a different thing
|
|
16
16
|
# that would be genuinely confusing under the same bare name.
|
|
17
17
|
#
|
|
18
18
|
# `include Hecks::Projections` gets the short spelling where the
|
|
@@ -26,6 +26,8 @@ require_relative "projections/shape"
|
|
|
26
26
|
require_relative "projections/oidc"
|
|
27
27
|
require_relative "projections/vocabulary"
|
|
28
28
|
require_relative "projections/parser_table"
|
|
29
|
+
require_relative "projections/bootstrap_table"
|
|
30
|
+
require_relative "projections/rust_vocabulary"
|
|
29
31
|
require_relative "projections/reference"
|
|
30
32
|
require_relative "projections/model"
|
|
31
33
|
require_relative "projections/diagrams"
|