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
|
@@ -35,16 +35,20 @@ module Hecks
|
|
|
35
35
|
attr_accessor :last_suite
|
|
36
36
|
|
|
37
37
|
# `Kernel.load`s one `.behaviors` file and returns its suite, with
|
|
38
|
-
#
|
|
38
|
+
# no test actually executed yet — the cheap half, split out so a
|
|
39
39
|
# caller that only needs to know what tests exist (the rspec shim,
|
|
40
40
|
# naming its `it`s at collection time) doesn't pay for running them
|
|
41
41
|
# until it actually wants to. `Behaviors.loading_path` is bound only
|
|
42
42
|
# for the duration of the load, and `last_suite` is reset to nil
|
|
43
|
-
#
|
|
43
|
+
# before it — a file that loads without ever calling
|
|
44
44
|
# `Hecks.behaviors` is unambiguously a parse error, never a stale
|
|
45
45
|
# suite from whatever loaded before it in a sweep (a real bug in a
|
|
46
46
|
# prior port of this idea: compared only against nil, so after the
|
|
47
47
|
# first successful file in a sweep it stayed non-nil forever).
|
|
48
|
+
# @param path [String] the `.behaviors` file's path
|
|
49
|
+
# @return [Behaviors::ParseResult] `suite` holding the built `BehaviorsSuite` and
|
|
50
|
+
# `parse_error` nil on success; `suite` nil and `parse_error` a String describing
|
|
51
|
+
# a raised exception, or the file loading without calling `Hecks.behaviors`
|
|
48
52
|
def parse(path)
|
|
49
53
|
path = File.expand_path(path)
|
|
50
54
|
previous_path = loading_path
|
|
@@ -66,6 +70,11 @@ module Hecks
|
|
|
66
70
|
end
|
|
67
71
|
|
|
68
72
|
# One `.behaviors` file → `FileResult`, every test actually run.
|
|
73
|
+
#
|
|
74
|
+
# @param path [String] the `.behaviors` file's path
|
|
75
|
+
# @return [Behaviors::FileResult] `parse_error` and empty `runs` on a parse
|
|
76
|
+
# failure; otherwise `parse_error` nil and `runs` one `Expectations::Result`
|
|
77
|
+
# per test
|
|
69
78
|
def run(path)
|
|
70
79
|
parsed = parse(path)
|
|
71
80
|
return FileResult.new(path: parsed.path, parse_error: parsed.parse_error, runs: []) if parsed.parse_error
|
|
@@ -78,12 +87,22 @@ module Hecks
|
|
|
78
87
|
# it actually found — a sweep that goes green without saying how
|
|
79
88
|
# much it looked at is indistinguishable from one that found
|
|
80
89
|
# nothing to look at.
|
|
90
|
+
#
|
|
91
|
+
# @param dir [String] the directory to search, recursively, for `.behaviors` files
|
|
92
|
+
# @return [Behaviors::SweepResult] `files_swept` the count found, `files` one
|
|
93
|
+
# `FileResult` per file, and `summary` the aggregate counts `summarize` returns
|
|
81
94
|
def run_all(dir)
|
|
82
95
|
files = Dir.glob(File.join(dir, "**", "*.behaviors"))
|
|
83
96
|
results = files.map { |path| run(path) }
|
|
84
97
|
SweepResult.new(root: dir, files_swept: files.size, files: results, summary: summarize(results))
|
|
85
98
|
end
|
|
86
99
|
|
|
100
|
+
# Tallies a sweep's results into counts by outcome.
|
|
101
|
+
#
|
|
102
|
+
# @param results [Array<Behaviors::FileResult>] the swept files' results
|
|
103
|
+
# @return [Hash{Symbol => Integer}] `:files` the file count, `:parse_errors` files
|
|
104
|
+
# that failed to parse, `:total` tests actually run across every file, `:passed`,
|
|
105
|
+
# `:failed` and `:errored` counting each `Expectations::Result#status`
|
|
87
106
|
def summarize(results)
|
|
88
107
|
runs = results.flat_map(&:runs)
|
|
89
108
|
{
|
data/lib/hecks/behaviors.rb
CHANGED
|
@@ -8,11 +8,19 @@ require_relative "behaviors/runner"
|
|
|
8
8
|
# lib/hecks/fuzzing.rb, the shape this file mirrors) is opt-in too.
|
|
9
9
|
module Hecks
|
|
10
10
|
class << self
|
|
11
|
-
# `Hecks.behaviors "Name" do ... end` — deliberately
|
|
11
|
+
# `Hecks.behaviors "Name" do ... end` — deliberately not routed
|
|
12
12
|
# through `collect` the way `bluebook`/`hecksagon`/`world` are: a
|
|
13
13
|
# behaviors suite is a test artifact a runner reads on demand, never
|
|
14
14
|
# a thing a live domain boot needs, so it has no business landing in
|
|
15
15
|
# `Runtime.current_registry`.
|
|
16
|
+
#
|
|
17
|
+
# @param name [String] the suite's declared name
|
|
18
|
+
# @yield the suite's body, evaluated against a `Behaviors::BehaviorsBuilder`
|
|
19
|
+
# @return [Behaviors::BehaviorsSuite] the built suite, also stashed as
|
|
20
|
+
# `Behaviors.last_suite`
|
|
21
|
+
# @raise [Behaviors::LoadOutsideRunner] if called outside `Behaviors.run(path)`
|
|
22
|
+
# @raise [Behaviors::Malformed] if the suite declares no `vision` or `loads`, or
|
|
23
|
+
# any of its `test` blocks is malformed
|
|
16
24
|
def behaviors(name, &)
|
|
17
25
|
path = Behaviors.loading_path or
|
|
18
26
|
raise Behaviors::LoadOutsideRunner,
|
|
@@ -3,15 +3,15 @@ require_relative "expression/ast_json"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Bluebook
|
|
6
|
-
# A
|
|
6
|
+
# A field read through a `reference_to`, held locally (S12, ADR 0025
|
|
7
7
|
# — "Consistency across aggregate boundaries") — `projects
|
|
8
8
|
# :customer_status, from: :"customer.status"` declares that this
|
|
9
|
-
# aggregate's own `:customer_status` is a
|
|
9
|
+
# aggregate's own `:customer_status` is a copy of the target's own
|
|
10
10
|
# `:status`, kept fresh by a rebuild sweep rather than read live at
|
|
11
|
-
# rule-evaluation time. `reference` names the
|
|
11
|
+
# rule-evaluation time. `reference` names the local reference
|
|
12
12
|
# attribute to walk through (`:customer`, minted by this
|
|
13
13
|
# aggregate's own `reference_to Customer`); `remote_field` names
|
|
14
|
-
# the
|
|
14
|
+
# the scalar on the target aggregate to copy.
|
|
15
15
|
ProjectedField = Struct.new(:name, :reference, :remote_field, keyword_init: true)
|
|
16
16
|
|
|
17
17
|
# A construct like every other: the aggregate carries its own identity
|
|
@@ -19,14 +19,14 @@ module Hecks
|
|
|
19
19
|
# everything declared on it. The chain is model objects end to end —
|
|
20
20
|
# reference resolution and hecks_fqn both walk it.
|
|
21
21
|
#
|
|
22
|
-
#
|
|
22
|
+
# The holding half, and nothing else. Every line below restates what
|
|
23
23
|
# `language/bluebook/aggregate.bluebook` already declares — the field
|
|
24
24
|
# list, said again as readers, again as constructor keywords, and
|
|
25
25
|
# again as an emission. That triplication is what a generator removes;
|
|
26
|
-
# `Behaviour::Aggregate` carries everything that is
|
|
26
|
+
# `Behaviour::Aggregate` carries everything that is not derivable from
|
|
27
27
|
# the declaration, so regenerating this file can never be lossy.
|
|
28
28
|
#
|
|
29
|
-
#
|
|
29
|
+
# Prototype: hand-written in the shape a generator would emit, to
|
|
30
30
|
# prove the seam before the generator exists. `bin/project_model`
|
|
31
31
|
# would own this file; behaviour/aggregate.rb stays hand-written.
|
|
32
32
|
class Aggregate
|
|
@@ -42,16 +42,16 @@ module Hecks
|
|
|
42
42
|
value_objects: many(:value_objects),
|
|
43
43
|
commands: many(:commands),
|
|
44
44
|
invariants: -> { invariants.map { |rule| Expression::AstJson.rule_row(rule) } },
|
|
45
|
-
# A
|
|
46
|
-
# 0025) — the aggregate's
|
|
45
|
+
# A precondition shared across commands, declared once (S10, ADR
|
|
46
|
+
# 0025) — the aggregate's own named `given`s, the declaration a
|
|
47
47
|
# referencing command's own (already-resolved) `givens` entry
|
|
48
48
|
# came from. Both sides of "declared once, referenced many"
|
|
49
|
-
# are real IR, the same shape a value object's
|
|
49
|
+
# are real IR, the same shape a value object's type and an
|
|
50
50
|
# attribute's own reference to it both are.
|
|
51
51
|
preconditions: -> { preconditions.map { |rule| Expression::AstJson.rule_row(rule) } },
|
|
52
|
-
# S12, ADR 0025 — deliberately
|
|
52
|
+
# S12, ADR 0025 — deliberately not folded into `attributes`:
|
|
53
53
|
# `EraGuard::ShapeDiff` only ever walks `attributes` to decide
|
|
54
|
-
# whether a
|
|
54
|
+
# whether a new field leaves an existing record with something
|
|
55
55
|
# genuinely absent, and a projected field's own absence story
|
|
56
56
|
# is different — a record predating the `projects` declaration
|
|
57
57
|
# is expected to be missing it until the rebuild sweep runs,
|
|
@@ -64,7 +64,7 @@ module Hecks
|
|
|
64
64
|
lifecycle: one(:lifecycle),
|
|
65
65
|
entities: many(:entities),
|
|
66
66
|
queries: many(:queries),
|
|
67
|
-
#
|
|
67
|
+
# Additive — every domain that declares no port emits `ports: []`,
|
|
68
68
|
# the same "regenerate deliberately" wire-format change
|
|
69
69
|
# ir_golden_spec.rb's own header describes; no existing key
|
|
70
70
|
# moves. See docs/decisions (rust/project/ports.rb) for the
|
|
@@ -80,6 +80,36 @@ module Hecks
|
|
|
80
80
|
# Assigns what the language declares, then hands off to the
|
|
81
81
|
# behaviour's own `settle` — derived identity, name indexes and
|
|
82
82
|
# owner stamping, none of which the declaration states.
|
|
83
|
+
#
|
|
84
|
+
# @param name [String, Symbol] the aggregate's declared name
|
|
85
|
+
# @param description [String, nil] the aggregate's declared prose description
|
|
86
|
+
# @param attributes [Array<Bluebook::Attribute>] the aggregate's declared fields
|
|
87
|
+
# @param value_objects [Array<Class>] the value object classes (`Bluebook::ValueObject`
|
|
88
|
+
# subclasses) declared on this aggregate
|
|
89
|
+
# @param commands [Array<Class>] the command classes (`Bluebook::Command` subclasses)
|
|
90
|
+
# declared on this aggregate
|
|
91
|
+
# @param invariants [Array<Bluebook::Invariant>] the aggregate-level rules that must
|
|
92
|
+
# always hold
|
|
93
|
+
# @param preconditions [Array<Bluebook::Given>] the aggregate's own named `given`s, a
|
|
94
|
+
# referencing command's own resolved `givens` entry can point back at
|
|
95
|
+
# @param projected_fields [Array<Bluebook::ProjectedField>] the declared `projects`
|
|
96
|
+
# fields, copied from another aggregate's own state by the rebuild sweep
|
|
97
|
+
# @param identified_by [String, Symbol, Array<String, Symbol>] the identity path(s)
|
|
98
|
+
# this aggregate is addressed by
|
|
99
|
+
# @param lifecycle [Bluebook::Lifecycle, nil] the aggregate's declared state machine,
|
|
100
|
+
# or `nil` if it declares none
|
|
101
|
+
# @param entities [Array<Class>] the entity classes (`Bluebook::Entity` subclasses)
|
|
102
|
+
# nested directly under this aggregate
|
|
103
|
+
# @param queries [Array<Bluebook::Query>] the queries declared directly on this
|
|
104
|
+
# aggregate
|
|
105
|
+
# @param policies [Array<Bluebook::Policy>] the reactions hoisted onto this aggregate
|
|
106
|
+
# from the chapter that assembled it
|
|
107
|
+
# @param ports [Array<Bluebook::DomainPort>] the aggregate-scoped ports attached after
|
|
108
|
+
# this aggregate was built
|
|
109
|
+
# @param reference_targets [Array<String>] the name of every aggregate this one points
|
|
110
|
+
# at with an aggregate-level `reference_to`
|
|
111
|
+
# @param provenance [Object, nil] the aggregate's declared canonical source, captured
|
|
112
|
+
# exactly as written, or `nil` if it declares none
|
|
83
113
|
def initialize(name:, description: nil, attributes: [], value_objects: [],
|
|
84
114
|
commands: [], invariants: [], preconditions: [], projected_fields: [], identified_by: [], lifecycle: nil,
|
|
85
115
|
entities: [], queries: [], policies: [], ports: [], reference_targets: [],
|
|
@@ -7,13 +7,20 @@ module Hecks
|
|
|
7
7
|
# is the part no field table can say: which construct holds which, and how a
|
|
8
8
|
# head's references become able to resolve.
|
|
9
9
|
#
|
|
10
|
-
# It
|
|
10
|
+
# It decides nothing. Whether a declaration is admissible was settled on the
|
|
11
11
|
# way in, by the language.
|
|
12
12
|
class AggregateAssembly
|
|
13
|
+
# @param row [Hash{Symbol => Object}] one declared aggregate's raw contract data,
|
|
14
|
+
# as `@declaration[:aggregates]` carries it
|
|
13
15
|
def initialize(row)
|
|
14
16
|
@row = row
|
|
15
17
|
end
|
|
16
18
|
|
|
19
|
+
# Builds the aggregate's whole owner-chain graph — its value objects, commands,
|
|
20
|
+
# entities, queries and lifecycle — from its raw declaration.
|
|
21
|
+
#
|
|
22
|
+
# @return [Bluebook::Aggregate] the built aggregate, with every reference stamped
|
|
23
|
+
# to resolve against it
|
|
17
24
|
def aggregate
|
|
18
25
|
shapes = Array(@row[:value_objects]).map { |shape| value_object(shape) }
|
|
19
26
|
commands = Array(@row[:commands]).map { |verb| Build.call("Command", verb) }
|
|
@@ -29,7 +36,7 @@ module Hecks
|
|
|
29
36
|
entities: entities,
|
|
30
37
|
queries: asks,
|
|
31
38
|
lifecycle: lifecycle_of(@row),
|
|
32
|
-
# A policy declared inside a head is
|
|
39
|
+
# A policy declared inside a head is hoisted onto the chapter by the
|
|
33
40
|
# builder, and `Aggregate#to_h` never carried it — so the language does
|
|
34
41
|
# not record which head a chapter-level policy was written in. The
|
|
35
42
|
# chapter holds them all, which is what `PolicyInterpreter` reads.
|
|
@@ -43,11 +50,11 @@ module Hecks
|
|
|
43
50
|
|
|
44
51
|
private
|
|
45
52
|
|
|
46
|
-
# What this head points at with an aggregate-level `reference_to`.
|
|
53
|
+
# What this head points at with an aggregate-level `reference_to`. Not the
|
|
47
54
|
# self-references its verbs carry — the first version read
|
|
48
55
|
# `commands.filter_map(&:references)` and gave Pizza two targets of "Pizza",
|
|
49
56
|
# its own verbs pointing at itself, where the builder recorded none. An
|
|
50
|
-
# aggregate-level `reference_to X` leaves a reference
|
|
57
|
+
# aggregate-level `reference_to X` leaves a reference attribute behind, so
|
|
51
58
|
# the attributes are where the answer is.
|
|
52
59
|
def reference_targets(fields)
|
|
53
60
|
fields.select(&:reference?).map { |field| field.type.target_name }
|
|
@@ -57,7 +64,7 @@ module Hecks
|
|
|
57
64
|
Build.call("ValueObject", row)
|
|
58
65
|
end
|
|
59
66
|
|
|
60
|
-
# S17, ADR 0026 —
|
|
67
|
+
# S17, ADR 0026 — recurses. "That is what `entity` is for, and
|
|
61
68
|
# `entity` is declared by the language and used zero times in it"
|
|
62
69
|
# — Dispatch, inside Handler, is the first use, so `row[:entities]`
|
|
63
70
|
# is built the same way `row` itself was reached: through this
|
|
@@ -75,7 +82,7 @@ module Hecks
|
|
|
75
82
|
|
|
76
83
|
# Every reference learns which head declares it, so it can reach the chapter
|
|
77
84
|
# and resolve. Deliberately across every list that can carry one — a
|
|
78
|
-
# reference the walk misses resolves to nil, and a nil target is
|
|
85
|
+
# reference the walk misses resolves to nil, and a nil target is skipped
|
|
79
86
|
# rather than refused, so the guarantee would go quiet instead of red.
|
|
80
87
|
def stamp_references(aggregate)
|
|
81
88
|
lists = [aggregate.attributes, *aggregate.commands.map(&:attributes), *aggregate.queries.map(&:attributes)]
|
|
@@ -84,7 +91,7 @@ module Hecks
|
|
|
84
91
|
lists.flatten.select(&:reference?).each { |field| field.type.declared_in = aggregate }
|
|
85
92
|
end
|
|
86
93
|
|
|
87
|
-
# S17, ADR 0026 — walks
|
|
94
|
+
# S17, ADR 0026 — walks nested entities too (Dispatch, inside
|
|
88
95
|
# Handler), not only an aggregate's own direct ones.
|
|
89
96
|
def entity_reference_lists(entities, lists)
|
|
90
97
|
entities.each do |piece|
|
|
@@ -95,7 +102,7 @@ module Hecks
|
|
|
95
102
|
end
|
|
96
103
|
end
|
|
97
104
|
|
|
98
|
-
#
|
|
105
|
+
# Three language fields, one IR object. `state_field`, `state_start` and
|
|
99
106
|
# `transitions` are separate declarations; the IR keeps one Lifecycle. The
|
|
100
107
|
# contract names them derived, and this is what derives them.
|
|
101
108
|
def lifecycle_of(row)
|
|
@@ -110,8 +117,8 @@ module Hecks
|
|
|
110
117
|
end
|
|
111
118
|
|
|
112
119
|
# A lifecycle holds [command, StateTransition] pairs and `to_h` expands one
|
|
113
|
-
# pair whose `from` is a list into several rows. Grouped back by command
|
|
114
|
-
#
|
|
120
|
+
# pair whose `from` is a list into several rows. Grouped back by command and
|
|
121
|
+
# target, because one declared pair has exactly one target and may have many
|
|
115
122
|
# froms — grouping by command alone would fuse two declarations that move the
|
|
116
123
|
# same verb to different states.
|
|
117
124
|
def transitions(rows)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Bluebook
|
|
3
3
|
class Assembly
|
|
4
|
-
#
|
|
4
|
+
# One way to build a construct, for every construct.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
# `
|
|
8
|
-
#
|
|
9
|
-
#
|
|
6
|
+
# Not a method per category — `value_object(row)`, `command(row)`,
|
|
7
|
+
# `policy(row)`, each gathering the same keywords the contract already
|
|
8
|
+
# names. This reads the contract instead, so adding a field to the
|
|
9
|
+
# language and forgetting to assemble it is caught by the coverage gate
|
|
10
10
|
# rather than by nobody.
|
|
11
11
|
#
|
|
12
12
|
# `make` is the only branch, and it is a real one: a construct that became a
|
|
@@ -16,6 +16,16 @@ module Hecks
|
|
|
16
16
|
module Build
|
|
17
17
|
module_function
|
|
18
18
|
|
|
19
|
+
# Builds one construct from its declared row, reading which keywords it takes
|
|
20
|
+
# and how to read each one off `Assembly.contract(category)`.
|
|
21
|
+
#
|
|
22
|
+
# @param category [String] the contract's category name, such as `"Aggregate"`
|
|
23
|
+
# or `"Command"`
|
|
24
|
+
# @param row [Hash{Symbol => Object}] the construct's own declared row
|
|
25
|
+
# @param extra [Hash{Symbol => Object}] keywords the caller supplies directly,
|
|
26
|
+
# such as already-built children the contract itself cannot derive
|
|
27
|
+
# @return [Object] the built construct: an instance for a category whose
|
|
28
|
+
# `make` is `:new`, or a class for one whose `make` is `:declare`
|
|
19
29
|
def call(category, row, extra = {})
|
|
20
30
|
contract = Assembly.contract(category)
|
|
21
31
|
keywords = contract.fields.to_h { |keyword, (key, reader)| [keyword, read(reader, row[key])] }
|
|
@@ -23,12 +33,23 @@ module Hecks
|
|
|
23
33
|
holder(contract).public_send(contract.make, **keywords, **extra)
|
|
24
34
|
end
|
|
25
35
|
|
|
36
|
+
# Resolves the class or module a contract's construct is built through.
|
|
37
|
+
#
|
|
38
|
+
# @param contract [Bluebook::Assembly::Contract] the category's field contract
|
|
39
|
+
# @return [Module] the holder that answers `contract.make` (`.new` or `.declare`)
|
|
40
|
+
# @raise [ArgumentError] if the contract names no holder to build through
|
|
26
41
|
def holder(contract)
|
|
27
42
|
contract.holder or raise ArgumentError, "#{contract} holds nothing that can be built"
|
|
28
43
|
end
|
|
29
44
|
|
|
30
45
|
# A reader is a Marks method, a list of them, or one of three spellings that
|
|
31
46
|
# need no decoding at all.
|
|
47
|
+
#
|
|
48
|
+
# @param reader [Symbol, Array, nil] the contract field's reader: `:plain`,
|
|
49
|
+
# `:identity`, `:flag`, `[:each, marks_method]`, `[:option, name]`, or a bare
|
|
50
|
+
# `Marks` method name
|
|
51
|
+
# @param value [Object] the raw declared value to read
|
|
52
|
+
# @return [Object] the value read through `reader`
|
|
32
53
|
def read(reader, value)
|
|
33
54
|
case reader
|
|
34
55
|
when :plain then value
|
|
@@ -5,14 +5,14 @@ module Hecks
|
|
|
5
5
|
# struct format itself (this file) plus, reopened in contracts.rb, the
|
|
6
6
|
# `CONTRACTS` table of one `Contract` per construct category.
|
|
7
7
|
class Assembly
|
|
8
|
-
#
|
|
9
|
-
# field needs no assembling is
|
|
8
|
+
# What a construct needs that the language cannot say, and how a claim that a
|
|
9
|
+
# field needs no assembling is checked.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
11
|
+
# Naming a field derived is a claim, and a claim needs a kind, not just a name
|
|
12
|
+
# in a list. A bare list of names is a promise with nobody holding it: the
|
|
13
|
+
# coverage gate only asks whether a field is accounted for, so `derived:
|
|
14
|
+
# %i[version]` would satisfy it while dropping a chapter's version in
|
|
15
|
+
# silence — the exact shape of every defect this arc has found.
|
|
16
16
|
#
|
|
17
17
|
# :parent the containment tree supplies it — a `*_id`, or one of
|
|
18
18
|
# the named pointers below. Checked against the name.
|
|
@@ -25,38 +25,70 @@ module Hecks
|
|
|
25
25
|
# :elsewhere not a fact about this construct at all. Allow-listed one
|
|
26
26
|
# by one, because it is the kind with no other check.
|
|
27
27
|
#
|
|
28
|
-
# Every one of those can
|
|
28
|
+
# Every one of those can fail. That is the whole difference.
|
|
29
29
|
Contract = Struct.new(:holder, :make, :fields, :derived, :rows, :reads, keyword_init: true) do
|
|
30
|
-
# How a declaration key is read back off a
|
|
30
|
+
# How a declaration key is read back off a row. Absent means the default —
|
|
31
31
|
# `text(row[key])`, a single cell — which is most of them ; present names the
|
|
32
32
|
# shape, because a list needs a reader per element and a folded field is
|
|
33
33
|
# gathered rather than fetched. Same pattern as `rows`, in the other
|
|
34
34
|
# direction: declare the exceptions, default the rest.
|
|
35
|
+
#
|
|
36
|
+
# @param key [String, Symbol] the field's declaration key, as `fields` names it
|
|
37
|
+
# @return [Symbol, Array, nil] the reader named in `reads` for `key`, or `nil`
|
|
38
|
+
# when the field reads through the default single-cell reader
|
|
35
39
|
def reader(key) = Hash(reads)[key.to_sym]
|
|
36
40
|
|
|
37
|
-
# How an appendable
|
|
41
|
+
# How an appendable list becomes rows the walk can offer. A list absent from
|
|
38
42
|
# here reads straight off the node ; one that is present names the shaper,
|
|
39
43
|
# because the IR keeps a shape the language does not — a transition whose
|
|
40
44
|
# `from` is a list is several rows, an append binds several fields at once,
|
|
41
45
|
# an open map is one row per entry.
|
|
46
|
+
#
|
|
47
|
+
# @param list [String, Symbol] the appendable list's field name
|
|
48
|
+
# @return [Symbol, nil] the shaper method named in `rows` for `list`, or `nil`
|
|
49
|
+
# when the list reads straight off the node instead
|
|
42
50
|
def shaper(list) = Hash(rows)[list.to_sym]
|
|
43
51
|
|
|
52
|
+
# Whether `field` is one this contract accounts for, stored or derived.
|
|
53
|
+
#
|
|
54
|
+
# @param field [Symbol] the field name to check
|
|
55
|
+
# @return [Boolean] whether `field` is a key of `fields` or of `derived`
|
|
44
56
|
def declares?(field) = fields.key?(field) || derived.key?(field)
|
|
45
57
|
|
|
58
|
+
# Names the kind of derived claim `field` makes, if any.
|
|
59
|
+
#
|
|
60
|
+
# @param field [Symbol] the derived field name to check
|
|
61
|
+
# @return [Symbol, Array, nil] the kind `derived` names for `field` — `:parent`,
|
|
62
|
+
# `:children`, `:elsewhere`, `:walk`, an `[:computed, method]` or `[:folded,
|
|
63
|
+
# object, member]` pair — or `nil` when `field` is not derived
|
|
46
64
|
def kind_of(field) = derived[field]
|
|
47
65
|
|
|
48
|
-
#
|
|
66
|
+
# The fields the walk supplies — every `derived: { field => :walk }` claim.
|
|
67
|
+
# The language declares them (`attribute :position, Position`) so the
|
|
68
|
+
# judge can order siblings, but no constructor takes one. This is the one
|
|
69
|
+
# place that fact is stated ; `Specializer` and `Model::Deviations` read it
|
|
70
|
+
# here rather than each keeping their own `%i[position]`.
|
|
71
|
+
#
|
|
72
|
+
# @return [Array<Symbol>] every field name whose derived kind is `:walk`
|
|
73
|
+
def walked = derived.select { |_field, kind| kind == :walk }.keys
|
|
74
|
+
|
|
75
|
+
# Where a folded field actually lives, as [object, member].
|
|
49
76
|
#
|
|
50
77
|
# `[:folded, :lifecycle, :field]` says the language's `state_field` is the
|
|
51
|
-
# `field` of the IR's one Lifecycle.
|
|
52
|
-
#
|
|
53
|
-
# drives
|
|
54
|
-
# reconstruction gathers the members back into the object on the way out.
|
|
78
|
+
# `field` of the IR's one Lifecycle. `Readings` would otherwise have to state
|
|
79
|
+
# that fact a second time as `node.lifecycle&.field` — saying it once here
|
|
80
|
+
# drives both directions instead: the walk reads the member on the way in, and
|
|
81
|
+
# the reconstruction gathers the members back into the object on the way out.
|
|
55
82
|
#
|
|
56
83
|
# A nil member means the fold has no single member to name — `rows` is a
|
|
57
84
|
# count of what `closed_set` and `members` hold between them, and `options`
|
|
58
85
|
# spreads across eight keys. Those keep their own code, and the gate still
|
|
59
86
|
# checks the object they name is real.
|
|
87
|
+
#
|
|
88
|
+
# @param field [Symbol] the derived field name to check
|
|
89
|
+
# @return [Array(Symbol, Symbol), Array(Array<Symbol>, nil), nil] the `[object,
|
|
90
|
+
# member]` pair the field folds into — `member` is `nil` when the fold has
|
|
91
|
+
# no single member to name — or `nil` when `field` does not fold at all
|
|
60
92
|
def folded(field)
|
|
61
93
|
kind = derived[field]
|
|
62
94
|
return nil unless kind.is_a?(Array) && kind.first == :folded
|
|
@@ -64,16 +96,20 @@ module Hecks
|
|
|
64
96
|
[kind[1], kind[2]]
|
|
65
97
|
end
|
|
66
98
|
|
|
67
|
-
#
|
|
99
|
+
# Computed means worked out, not merely answerable.
|
|
68
100
|
#
|
|
69
101
|
# Asking only whether the holder responds was too weak, and measurably so:
|
|
70
102
|
# `[:computed, :version]` passed, because `Bluebook` does answer to
|
|
71
103
|
# `version` — it just answers with what the constructor was handed. A field
|
|
72
|
-
# the constructor
|
|
104
|
+
# the constructor takes is stored, and calling it computed is how a chapter's
|
|
73
105
|
# version would have gone missing while the gate said yes.
|
|
74
106
|
#
|
|
75
|
-
# So a computed field is one the holder answers
|
|
107
|
+
# So a computed field is one the holder answers and the constructor does not
|
|
76
108
|
# accept. `query_name` qualifies (`Naming.snake(name)`) ; `version` cannot.
|
|
109
|
+
#
|
|
110
|
+
# @param method [Symbol] the field name to check
|
|
111
|
+
# @return [Boolean] whether `holder` answers `method` but its constructor does
|
|
112
|
+
# not accept it as a keyword
|
|
77
113
|
def computes?(method)
|
|
78
114
|
return false unless holder
|
|
79
115
|
return false unless answers?(method)
|
|
@@ -81,10 +117,22 @@ module Hecks
|
|
|
81
117
|
!accepts?(method)
|
|
82
118
|
end
|
|
83
119
|
|
|
120
|
+
# Whether `holder` answers `method`, checked the way that fits how it is
|
|
121
|
+
# built: `respond_to?` for a `:declare` holder (a class, queried live) and
|
|
122
|
+
# `method_defined?` for a `:new` holder (an instance method, checked without
|
|
123
|
+
# building one).
|
|
124
|
+
#
|
|
125
|
+
# @param method [Symbol] the method name to check
|
|
126
|
+
# @return [Boolean] whether `holder` answers `method`
|
|
84
127
|
def answers?(method)
|
|
85
128
|
make == :declare ? holder.respond_to?(method) : holder.method_defined?(method)
|
|
86
129
|
end
|
|
87
130
|
|
|
131
|
+
# Whether the holder's constructor takes `keyword` as a keyword argument.
|
|
132
|
+
#
|
|
133
|
+
# @param keyword [Symbol] the keyword to check
|
|
134
|
+
# @return [Boolean] whether the holder's constructor (`.declare` or
|
|
135
|
+
# `#initialize`) accepts `keyword` as an optional or required keyword
|
|
88
136
|
def accepts?(keyword)
|
|
89
137
|
builder = make == :declare ? holder.method(:declare) : holder.instance_method(:initialize)
|
|
90
138
|
|
|
@@ -92,22 +140,49 @@ module Hecks
|
|
|
92
140
|
end
|
|
93
141
|
end
|
|
94
142
|
|
|
95
|
-
# The fields that
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
|
|
143
|
+
# The fields that point at a parent without being spelled `*_id` — the one list,
|
|
144
|
+
# read by the assembly gate (a `:parent` claim), the model generator and
|
|
145
|
+
# QueryIR (a declared field the model composes instead of storing).
|
|
146
|
+
#
|
|
147
|
+
# aggregate, bluebook the bare parent link a creating command mints
|
|
148
|
+
# (ADR 0025) — exactly the `parent_key`s `Plan` reads
|
|
149
|
+
# off the language
|
|
150
|
+
# owner Entity's own text twin of that link, which is why
|
|
151
|
+
# Entity's contract claims `owner: :parent`
|
|
152
|
+
#
|
|
153
|
+
# `shape` and `handler` are not on this list — S17 (ADR 0026) made Member and
|
|
154
|
+
# Dispatch nested entities, so neither is a parent field any more the way
|
|
155
|
+
# `owner` is. `spec/assembly_spec` derives this set from `Plan` and the
|
|
156
|
+
# contracts directly and fails on any drift, rather than trusting this list to
|
|
157
|
+
# stay in sync with a second hand-written copy kept by the model generator.
|
|
158
|
+
PARENT_POINTERS = %i[aggregate bluebook owner].freeze
|
|
159
|
+
|
|
160
|
+
# Whether `field` is a parent pointer that does not end in `_id`.
|
|
161
|
+
#
|
|
162
|
+
# @param field [String, Symbol] the field name to check
|
|
163
|
+
# @return [Boolean] whether `field` ends with `_id`, or is one of
|
|
164
|
+
# `PARENT_POINTERS`
|
|
165
|
+
def self.parent_pointer?(field)
|
|
166
|
+
field.to_s.end_with?("_id") || PARENT_POINTERS.include?(field.to_sym)
|
|
167
|
+
end
|
|
99
168
|
|
|
100
169
|
# The only fields allowed to claim they describe something other than the
|
|
101
170
|
# construct they hang off, each with the reason spelled out.
|
|
102
171
|
#
|
|
103
172
|
# normalisations the canonical-form table belongs to the expression grammar.
|
|
104
173
|
# A chapter's rules are canonicalised on the way in, so the
|
|
105
|
-
# language models the table — but no chapter
|
|
174
|
+
# language models the table — but no chapter stores one, and
|
|
106
175
|
# `Bluebook#to_h` splices it in from Expression.
|
|
107
176
|
ELSEWHERE = {
|
|
108
177
|
Bluebook: %i[normalisations]
|
|
109
178
|
}.freeze
|
|
110
179
|
|
|
180
|
+
# Whether `field` is allow-listed as describing something other than
|
|
181
|
+
# `category`'s own construct — see `ELSEWHERE`.
|
|
182
|
+
#
|
|
183
|
+
# @param category [String, Symbol] the construct category name
|
|
184
|
+
# @param field [Symbol] the field name to check
|
|
185
|
+
# @return [Boolean] whether `field` is on `category`'s `ELSEWHERE` entry
|
|
111
186
|
def self.elsewhere?(category, field)
|
|
112
187
|
Array(ELSEWHERE[category.to_sym]).include?(field)
|
|
113
188
|
end
|