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
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Bluebook
|
|
3
|
-
#
|
|
3
|
+
# **Which regexes a bluebook may say**.
|
|
4
4
|
#
|
|
5
5
|
# A `pattern:` is a fact about a value, carried in a bluebook — declared
|
|
6
6
|
# data, not Ruby code, so it must not lean on what any one engine happens
|
|
7
7
|
# to accept. Regex engines disagree in two different ways :
|
|
8
8
|
#
|
|
9
|
-
#
|
|
9
|
+
# only a backtracking engine can match it — lookahead, lookbehind,
|
|
10
10
|
# backreferences, atomic groups, possessive quantifiers. None of these
|
|
11
11
|
# can be matched in linear time, and linear-time engines refuse them
|
|
12
12
|
# outright. Refused here for the same reason.
|
|
13
13
|
#
|
|
14
|
-
#
|
|
14
|
+
# every engine parses it and they mean different things — the dangerous
|
|
15
15
|
# half, because nothing errors. `\d` `\w` `\s` are ASCII in some engines
|
|
16
16
|
# and Unicode in others ; `[:digit:]` and friends flip the same way in
|
|
17
17
|
# the other direction. Both families are refused, and a domain spells
|
|
18
18
|
# the range it means.
|
|
19
19
|
#
|
|
20
20
|
# What remains — explicit ranges, alternation, quantifiers, anchors,
|
|
21
|
-
# groups — reads identically everywhere, with `^` and `$` as
|
|
21
|
+
# groups — reads identically everywhere, with `^` and `$` as line anchors
|
|
22
22
|
# (Ruby's reading). The evidence is spec/corpus/fixtures/patterns.json.
|
|
23
23
|
module PatternSubset
|
|
24
24
|
Rejection = Struct.new(:construct, :reason)
|
|
@@ -57,13 +57,13 @@ module Hecks
|
|
|
57
57
|
|
|
58
58
|
# nil when the pattern is admitted, a Rejection when it is not.
|
|
59
59
|
#
|
|
60
|
-
# A
|
|
60
|
+
# A character walk, deliberately plain : the subset is defined by this
|
|
61
61
|
# walk, and a cleverer spelling would hide what it admits. An escaped
|
|
62
|
-
# construct is a
|
|
62
|
+
# construct is a literal, not a violation — `\(\?=` is the three
|
|
63
63
|
# characters "(?=" and says nothing about lookahead — which is why this
|
|
64
64
|
# steps over each backslash pair rather than matching the pattern as a
|
|
65
65
|
# whole.
|
|
66
|
-
# One character walk is the subset's
|
|
66
|
+
# One character walk is the subset's definition (see the method
|
|
67
67
|
# comment above): each construct-check is a branch in a single
|
|
68
68
|
# ordered pass sharing `index`/`in_class`/`class_start`. Splitting
|
|
69
69
|
# the branches into separate methods would force those three cursor
|
|
@@ -72,10 +72,14 @@ module Hecks
|
|
|
72
72
|
# methods agreeing about a shared cursor" — exactly what the walk's
|
|
73
73
|
# own comment says a cleverer spelling would obscure.
|
|
74
74
|
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
75
|
+
#
|
|
76
|
+
# @param pattern [String, Symbol, #to_s] the declared `pattern:` regex source
|
|
77
|
+
# @return [Rejection, nil] the reason the pattern is refused, or `nil` if it is
|
|
78
|
+
# entirely within the portable subset
|
|
75
79
|
def validate(pattern)
|
|
76
80
|
chars = pattern.to_s.chars
|
|
77
81
|
index = 0
|
|
78
|
-
# A
|
|
82
|
+
# A character-class interior is a different alphabet : inside `[...]`,
|
|
79
83
|
# `*`, `+`, `?`, `(`, `?` are literal characters, not quantifiers or
|
|
80
84
|
# group syntax — `[*+]` means "a literal asterisk or plus". `]` is
|
|
81
85
|
# only the class's close when it isn't the first character after `[`
|
|
@@ -132,7 +136,7 @@ module Hecks
|
|
|
132
136
|
nil
|
|
133
137
|
end
|
|
134
138
|
|
|
135
|
-
#
|
|
139
|
+
# Spelled out, not derived from the key : these strings are the refusal
|
|
136
140
|
# a caller reads.
|
|
137
141
|
CONSTRUCTS = {
|
|
138
142
|
backreference: "backreference",
|
|
@@ -145,8 +149,17 @@ module Hecks
|
|
|
145
149
|
possessive: "possessive quantifier"
|
|
146
150
|
}.freeze
|
|
147
151
|
|
|
152
|
+
# Builds the rejection for one refused construct.
|
|
153
|
+
#
|
|
154
|
+
# @param key [Symbol] a key of `CONSTRUCTS`/`REASONS`, such as `:lookahead`
|
|
155
|
+
# @return [Rejection] the construct's name and the reason it is refused
|
|
148
156
|
def refuse(key) = Rejection.new(CONSTRUCTS.fetch(key), REASONS.fetch(key))
|
|
149
157
|
|
|
158
|
+
# Says whether a POSIX bracket class (`[:digit:]` and friends) starts at `index`.
|
|
159
|
+
#
|
|
160
|
+
# @param chars [Array<String>] the pattern, split into characters
|
|
161
|
+
# @param index [Integer] the position to check
|
|
162
|
+
# @return [Boolean] whether a POSIX bracket class starts at `index`
|
|
150
163
|
def posix_class_at?(chars, index)
|
|
151
164
|
return false unless chars[index] == "[" && chars[index + 1] == ":"
|
|
152
165
|
|
|
@@ -156,9 +169,13 @@ module Hecks
|
|
|
156
169
|
end
|
|
157
170
|
|
|
158
171
|
# A possessive quantifier is `*+`, `++`, `?+`, or a bounded `{n}`/{n,m}`
|
|
159
|
-
# immediately followed by `+` — only checked
|
|
172
|
+
# immediately followed by `+` — only checked outside a character class,
|
|
160
173
|
# where `*`, `+`, `?`, `{`, `}` are quantifier syntax rather than
|
|
161
174
|
# literal characters.
|
|
175
|
+
#
|
|
176
|
+
# @param chars [Array<String>] the pattern, split into characters
|
|
177
|
+
# @param index [Integer] the position to check
|
|
178
|
+
# @return [Boolean] whether a possessive quantifier starts at `index`
|
|
162
179
|
def possessive_at?(chars, index)
|
|
163
180
|
return true if %w[* + ?].include?(chars[index]) && chars[index + 1] == "+"
|
|
164
181
|
return false unless chars[index] == "{"
|
|
@@ -169,6 +186,11 @@ module Hecks
|
|
|
169
186
|
|
|
170
187
|
# Length of a `{n}` / `{n,}` / `{n,m}` bound starting at `index`, or nil
|
|
171
188
|
# if what's there isn't one.
|
|
189
|
+
#
|
|
190
|
+
# @param chars [Array<String>] the pattern, split into characters
|
|
191
|
+
# @param index [Integer] the position the bound is expected to start at
|
|
192
|
+
# @return [Integer, nil] the bound's length in characters, or `nil` if `index`
|
|
193
|
+
# does not start a `{n}`/`{n,}`/`{n,m}` bound
|
|
172
194
|
def bounded_quantifier_length(chars, index)
|
|
173
195
|
cursor = index + 1
|
|
174
196
|
digit_seen = false
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
1
|
+
# Generated — projected from the language's own Policy aggregate.
|
|
2
|
+
# Do not edit: the holding half is rendered, and Behaviour::Policy
|
|
3
3
|
# is where anything hand-written belongs.
|
|
4
4
|
require_relative "behaviour/policy"
|
|
5
5
|
|
|
@@ -10,28 +10,30 @@ module Hecks
|
|
|
10
10
|
include Behaviour::Policy
|
|
11
11
|
|
|
12
12
|
emits_ir(
|
|
13
|
-
name:
|
|
14
|
-
on_event:
|
|
15
|
-
trigger_command:
|
|
16
|
-
target_domain:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
name: :name,
|
|
14
|
+
on_event: :on_event,
|
|
15
|
+
trigger_command: :trigger_command,
|
|
16
|
+
target_domain: :target_domain,
|
|
17
|
+
expect_undelivered: :expect_undelivered,
|
|
18
|
+
where: :where,
|
|
19
|
+
for_each: :for_each,
|
|
20
|
+
with_spec: -> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } },
|
|
21
|
+
where_ast: -> { where_ast }
|
|
21
22
|
)
|
|
22
23
|
|
|
23
|
-
attr_reader :name, :on_event, :trigger_command, :target_domain, :where, :for_each, :with_spec
|
|
24
|
+
attr_reader :name, :on_event, :trigger_command, :target_domain, :expect_undelivered, :where, :for_each, :with_spec
|
|
24
25
|
|
|
25
|
-
#
|
|
26
|
+
# Aggregate, declared and deliberately off the wire
|
|
26
27
|
# the wire format is a pinned contract, and it does not carry
|
|
27
28
|
# where a policy was written before the builder hoisted it
|
|
28
29
|
attr_accessor :aggregate
|
|
29
30
|
|
|
30
|
-
def initialize(name:, on_event: nil, trigger_command: nil, target_domain: nil, where: nil, for_each: nil, with_spec: [], aggregate: nil)
|
|
31
|
+
def initialize(name:, on_event: nil, trigger_command: nil, target_domain: nil, expect_undelivered: false, where: nil, for_each: nil, with_spec: [], aggregate: nil)
|
|
31
32
|
@name = name.to_s
|
|
32
33
|
@on_event = on_event
|
|
33
34
|
@trigger_command = trigger_command
|
|
34
35
|
@target_domain = target_domain
|
|
36
|
+
@expect_undelivered = expect_undelivered.to_s == "true"
|
|
35
37
|
@where = where
|
|
36
38
|
@for_each = for_each
|
|
37
39
|
@with_spec = with_spec
|
|
@@ -3,8 +3,8 @@ require_relative "../ir"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Bluebook
|
|
6
|
-
# `compensates` — a
|
|
7
|
-
# one, naming the command that undoes
|
|
6
|
+
# `compensates` — a second `DispatchSpec`, shape-identical to this
|
|
7
|
+
# one, naming the command that undoes this dispatch specifically
|
|
8
8
|
# (see `ProcessManagerBuilder::HandlerBuilder#dispatch_impl`'s own
|
|
9
9
|
# comment). `nil` for a dispatch with nothing to undo (a pure
|
|
10
10
|
# bookkeeping mark, or one whose own effect is superseded by a later
|
|
@@ -14,7 +14,7 @@ module Hecks
|
|
|
14
14
|
# used" bar would refuse a second level speculatively.
|
|
15
15
|
DispatchSpec = Struct.new(:command_name, :with_spec, :compensates, keyword_init: true) do
|
|
16
16
|
# A Struct already answers to_h; including the mixin puts the
|
|
17
|
-
#
|
|
17
|
+
# declared emission ahead of Struct's own in the ancestry, which
|
|
18
18
|
# is what makes the shape data rather than a method body.
|
|
19
19
|
include Hecks::IR
|
|
20
20
|
|
|
@@ -39,36 +39,40 @@ module Hecks
|
|
|
39
39
|
|
|
40
40
|
# The compensation half of a procedure, as its own thing.
|
|
41
41
|
#
|
|
42
|
-
# A
|
|
43
|
-
# next. A
|
|
42
|
+
# A process manager coordinates: legs, states, an opinion about who goes
|
|
43
|
+
# next. A saga undoes: what makes the world good again when a leg it
|
|
44
44
|
# dispatched is refused. Two concepts, and the industry slurs them into one
|
|
45
45
|
# word — so here they are two objects, and a procedure either has a saga or
|
|
46
46
|
# does not.
|
|
47
47
|
#
|
|
48
48
|
# `undoes` is the ordered list of commands the compensation sends — a
|
|
49
|
-
#
|
|
49
|
+
# static preview, declaration order (`Behaviour::ProcessManager#saga`),
|
|
50
50
|
# not one instance's own runtime history. Per-dispatch compensation
|
|
51
51
|
# (`compensates`, on the step it compensates for) moved most of what
|
|
52
52
|
# a saga undoes off this leg's own hand-written body and onto
|
|
53
53
|
# whichever forward dispatch each one undoes — this reads every
|
|
54
|
-
# declared `compensates` across the
|
|
54
|
+
# declared `compensates` across the whole saga first, then whatever
|
|
55
55
|
# this leg's own hand-written body still lists, for compensation
|
|
56
|
-
# that isn't expressible as "undo command X."
|
|
56
|
+
# that isn't expressible as "undo command X." which of a declared
|
|
57
57
|
# `compensates` actually fires for one instance, and in what order
|
|
58
58
|
# (newest-first, completed-legs-only), is `SagaInterpreter`'s own
|
|
59
59
|
# dynamic `completed_compensations` — a per-instance runtime fact
|
|
60
60
|
# this declaration-only object could never hold.
|
|
61
61
|
#
|
|
62
|
-
#
|
|
62
|
+
# **Naming collision, once flagged, now resolved** — `command`'s own
|
|
63
63
|
# `corrects event, reverses: true` (docs/implemented/decisions/0036-
|
|
64
64
|
# corrects-is-an-appended-fact-not-a-rewrite.md) already claimed
|
|
65
|
-
# `reverses` for a different meaning: auto-deriving a command's
|
|
66
|
-
# corrective mutation from a past
|
|
67
|
-
# compensating leg from a past
|
|
65
|
+
# `reverses` for a different meaning: auto-deriving a command's own
|
|
66
|
+
# corrective mutation from a past event, not a saga's own
|
|
67
|
+
# compensating leg from a past dispatch. This feature keeps
|
|
68
68
|
# `reverses` reserved for `corrects` and uses `compensates` for
|
|
69
69
|
# per-dispatch saga compensation instead — a deliberate choice, not
|
|
70
70
|
# an accidental collision.
|
|
71
71
|
Saga = Struct.new(:trigger, :from_state, :to_state, :compensations, keyword_init: true) do
|
|
72
|
+
# Lists the commands this saga sends to undo a refused leg.
|
|
73
|
+
#
|
|
74
|
+
# @return [Array<String>] the name of each compensating command, in the order
|
|
75
|
+
# `compensations` lists them
|
|
72
76
|
def undoes = compensations.map(&:command_name)
|
|
73
77
|
|
|
74
78
|
def to_s = "#{trigger} → #{to_state} (#{undoes.join(', ')})"
|
|
@@ -78,10 +82,10 @@ module Hecks
|
|
|
78
82
|
# produced by `DSL::ProcessManagerBuilder` — its start/end events,
|
|
79
83
|
# correlation field, derived states, and handler rows. Its
|
|
80
84
|
# compensation half (`saga`, `Behaviour::ProcessManager#saga`) is
|
|
81
|
-
#
|
|
85
|
+
# derived from the handler answering `REFUSED`, below, not declared as
|
|
82
86
|
# its own construct.
|
|
83
87
|
class ProcessManager
|
|
84
|
-
# The
|
|
88
|
+
# The bluebook's name for this construct, asked the same way of a class
|
|
85
89
|
# that has crossed over and of an IR object that has not. Collapses into
|
|
86
90
|
# Construct when this one crosses.
|
|
87
91
|
# The trigger of a compensating leg. Not an event name — no aggregate
|
|
@@ -120,6 +124,15 @@ module Hecks
|
|
|
120
124
|
|
|
121
125
|
attr_reader :name, :correlates_by, :starts_on, :ends_on, :states, :handlers
|
|
122
126
|
|
|
127
|
+
# @param name [String, Symbol] the process manager's declared name
|
|
128
|
+
# @param correlates_by [Symbol, nil] the payload field a triggering event's
|
|
129
|
+
# instances are correlated by
|
|
130
|
+
# @param starts_on [String, nil] the event that starts a new instance
|
|
131
|
+
# @param ends_on [String, nil] the event that ends an instance
|
|
132
|
+
# @param states [Array<String>] the declared states this procedure's instances pass
|
|
133
|
+
# through
|
|
134
|
+
# @param handlers [Array<Bluebook::ProcessManagerHandler>] the declared handler rows,
|
|
135
|
+
# one per (event, state) leg
|
|
123
136
|
def initialize(name:, correlates_by: nil, starts_on: nil, ends_on: nil,
|
|
124
137
|
states: [], handlers: [])
|
|
125
138
|
@name = name.to_s
|
|
@@ -8,10 +8,15 @@ module Hecks
|
|
|
8
8
|
|
|
9
9
|
attr_reader :root
|
|
10
10
|
|
|
11
|
+
# @param root [String] the directory to search under
|
|
11
12
|
def initialize(root)
|
|
12
13
|
@root = File.expand_path(root)
|
|
13
14
|
end
|
|
14
15
|
|
|
16
|
+
# Walks `root` for every directory that holds `.bluebook` files.
|
|
17
|
+
#
|
|
18
|
+
# @return [Array<String>] the absolute path of each `bluebook` directory found,
|
|
19
|
+
# sorted; a directory named in `SKIPPED_DIRECTORIES` is pruned from the walk
|
|
15
20
|
def bluebook_directories
|
|
16
21
|
found = []
|
|
17
22
|
Find.find(root) do |path|
|
|
@@ -14,14 +14,31 @@ module Hecks
|
|
|
14
14
|
|
|
15
15
|
attr_reader :root, :discovery, :register
|
|
16
16
|
|
|
17
|
+
# Discovers and loads every domain under `root` in one call.
|
|
18
|
+
#
|
|
19
|
+
# @param root [String] the directory to search under
|
|
20
|
+
# @return [Bluebook::ProjectLoader] the loader, with every discovered domain
|
|
21
|
+
# booted and registered
|
|
17
22
|
def self.load(root) = new(root).load
|
|
18
23
|
|
|
24
|
+
# @param root [String] the directory to search under
|
|
25
|
+
# @param discovery [Bluebook::ProjectDiscovery] the discovery this loader walks
|
|
26
|
+
# `root` with
|
|
27
|
+
# @param register [Bluebook::ProjectRegister] the register booted domains are
|
|
28
|
+
# fed into
|
|
19
29
|
def initialize(root, discovery: ProjectDiscovery.new(root), register: ProjectRegister.new)
|
|
20
30
|
@root = File.expand_path(root)
|
|
21
31
|
@discovery = discovery
|
|
22
32
|
@register = register
|
|
23
33
|
end
|
|
24
34
|
|
|
35
|
+
# Boots every domain `discovery` finds under `root` and registers its declarations.
|
|
36
|
+
#
|
|
37
|
+
# @return [Bluebook::ProjectLoader] self
|
|
38
|
+
# @raise [Bluebook::ProjectRegister::MissingRealm] see `ProjectRegister#register`
|
|
39
|
+
# @raise [Bluebook::ProjectRegister::LatestMismatch] see `ProjectRegister#register`
|
|
40
|
+
# @raise [Bluebook::ProjectRegister::DuplicateFqn] see `ProjectRegister#register`
|
|
41
|
+
# @raise [Runtime::WiringError] see `ProjectRegister#register`
|
|
25
42
|
def load
|
|
26
43
|
discovery.bluebook_directories.each do |directory|
|
|
27
44
|
runtime = Runtime.boot(directory)
|
|
@@ -30,10 +47,33 @@ module Hecks
|
|
|
30
47
|
self
|
|
31
48
|
end
|
|
32
49
|
|
|
50
|
+
# Lists every registered entry.
|
|
51
|
+
#
|
|
52
|
+
# @return [Hash{String => Bluebook::ProjectRegister::Entry}] every registered
|
|
53
|
+
# entry, keyed by its FQN
|
|
33
54
|
def entries = register.entries
|
|
55
|
+
|
|
56
|
+
# Finds a registered entry by its fully-qualified verb.
|
|
57
|
+
#
|
|
58
|
+
# @param address [String, #to_s] the FQN to look up
|
|
59
|
+
# @return [Bluebook::ProjectRegister::Entry] the entry registered under `address`
|
|
60
|
+
# @raise [KeyError] if no entry is registered under `address`
|
|
34
61
|
def fetch(address) = register.fetch(address)
|
|
62
|
+
|
|
63
|
+
# Says whether an entry is registered under an address.
|
|
64
|
+
#
|
|
65
|
+
# @param address [String, #to_s] the FQN to check
|
|
66
|
+
# @return [Boolean] whether an entry is registered under `address`
|
|
35
67
|
def include?(address) = register.include?(address)
|
|
68
|
+
|
|
69
|
+
# Lists every registered command entry.
|
|
70
|
+
#
|
|
71
|
+
# @return [Array<Bluebook::ProjectRegister::Entry>] every registered command entry
|
|
36
72
|
def commands = register.commands
|
|
73
|
+
|
|
74
|
+
# Lists every registered query entry.
|
|
75
|
+
#
|
|
76
|
+
# @return [Array<Bluebook::ProjectRegister::Entry>] every registered query entry
|
|
37
77
|
def queries = register.queries
|
|
38
78
|
end
|
|
39
79
|
end
|
|
@@ -4,7 +4,14 @@ module Hecks
|
|
|
4
4
|
# declarations become public FQNs, but never searches or boots folders.
|
|
5
5
|
class ProjectRegister
|
|
6
6
|
Entry = Struct.new(:fqn, :source_directory, :dispatcher, :declared_verb, :domain_version, keyword_init: true) do
|
|
7
|
+
# Says whether this entry addresses a command.
|
|
8
|
+
#
|
|
9
|
+
# @return [Boolean] whether this entry's `fqn` addresses a command
|
|
7
10
|
def command? = fqn.command?
|
|
11
|
+
|
|
12
|
+
# Says whether this entry addresses a query.
|
|
13
|
+
#
|
|
14
|
+
# @return [Boolean] whether this entry's `fqn` addresses a query
|
|
8
15
|
def query? = fqn.query?
|
|
9
16
|
end
|
|
10
17
|
|
|
@@ -14,16 +21,53 @@ module Hecks
|
|
|
14
21
|
|
|
15
22
|
attr_reader :entries
|
|
16
23
|
|
|
24
|
+
# Starts an empty register, with no domains loaded yet.
|
|
17
25
|
def initialize
|
|
18
26
|
@entries = {}
|
|
19
27
|
@tenant_directories = Hash.new { |hash, key| hash[key] = [] }
|
|
20
28
|
end
|
|
21
29
|
|
|
30
|
+
# Finds a registered entry by its fully-qualified verb.
|
|
31
|
+
#
|
|
32
|
+
# @param address [String, #to_s] the FQN to look up, such as `"Pizzas.Pizza.Order"`
|
|
33
|
+
# @return [Bluebook::ProjectRegister::Entry] the entry registered under `address`
|
|
34
|
+
# @raise [KeyError] if no entry is registered under `address`
|
|
22
35
|
def fetch(address) = entries.fetch(address.to_s)
|
|
36
|
+
|
|
37
|
+
# Says whether an entry is registered under an address.
|
|
38
|
+
#
|
|
39
|
+
# @param address [String, #to_s] the FQN to check
|
|
40
|
+
# @return [Boolean] whether an entry is registered under `address`
|
|
23
41
|
def include?(address) = entries.key?(address.to_s)
|
|
42
|
+
|
|
43
|
+
# Lists every registered command entry.
|
|
44
|
+
#
|
|
45
|
+
# @return [Array<Bluebook::ProjectRegister::Entry>] every registered command entry
|
|
24
46
|
def commands = entries.values.select(&:command?)
|
|
47
|
+
|
|
48
|
+
# Lists every registered query entry.
|
|
49
|
+
#
|
|
50
|
+
# @return [Array<Bluebook::ProjectRegister::Entry>] every registered query entry
|
|
25
51
|
def queries = entries.values.select(&:query?)
|
|
26
52
|
|
|
53
|
+
# Registers every aggregate command/query and read model query a booted chapter
|
|
54
|
+
# declares, under its versioned and (if current) unversioned FQNs.
|
|
55
|
+
#
|
|
56
|
+
# @param bluebooks [Array<Bluebook::Chapter>] the booted chapters to register
|
|
57
|
+
# @param registry [Runtime::Registry] the registry `bluebooks` booted into
|
|
58
|
+
# @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
|
|
59
|
+
# dispatcher entries dispatch through
|
|
60
|
+
# @param directory [String] the source directory `bluebooks` was discovered under
|
|
61
|
+
# @return [Bluebook::ProjectRegister] self
|
|
62
|
+
# @raise [Bluebook::ProjectRegister::MissingRealm] if a bluebook's world declares no
|
|
63
|
+
# realm
|
|
64
|
+
# @raise [Bluebook::ProjectRegister::LatestMismatch] if a world's `latest` disagrees
|
|
65
|
+
# with its bluebook's own version
|
|
66
|
+
# @raise [Bluebook::ProjectRegister::DuplicateFqn] if an FQN this call would register
|
|
67
|
+
# is already registered
|
|
68
|
+
# @raise [Runtime::WiringError] if a second tenant registers the same on-disk
|
|
69
|
+
# directory while any of its aggregates binds to an adapter that is not
|
|
70
|
+
# `tenant_capable?`
|
|
27
71
|
def register(bluebooks, registry, dispatcher, directory)
|
|
28
72
|
bluebooks.each do |bluebook|
|
|
29
73
|
refuse_unless_safe_for_second_tenant!(bluebook, registry, directory)
|
|
@@ -86,15 +130,15 @@ module Hecks
|
|
|
86
130
|
end
|
|
87
131
|
end
|
|
88
132
|
|
|
89
|
-
#
|
|
133
|
+
# The actual "more than one tenant" moment — Runtime::TenantCheck's
|
|
90
134
|
# own header names this table as the one place real multitenancy
|
|
91
|
-
# happens: the
|
|
92
|
-
# `persisted_by` binding) registering a
|
|
93
|
-
# different realm, into this
|
|
94
|
-
#
|
|
135
|
+
# happens: the same on-disk directory (one domain, one
|
|
136
|
+
# `persisted_by` binding) registering a second time, under a
|
|
137
|
+
# different realm, into this same shared route table. A directory's
|
|
138
|
+
# first registration is never refused here — nothing shares its
|
|
95
139
|
# data yet, so a plain single-tenant deployment on an ordinary
|
|
96
140
|
# adapter (Postgres, no schema story) still boots exactly as
|
|
97
|
-
# before. Only the
|
|
141
|
+
# before. Only the second (and any later) registration of that
|
|
98
142
|
# same directory is refused, and refused before this call adds its
|
|
99
143
|
# routes to the table — so a leaking tenant's requests never
|
|
100
144
|
# become reachable through `Router#resolve` in the first place.
|
data/lib/hecks/bluebook/query.rb
CHANGED
|
@@ -6,16 +6,21 @@ module Hecks
|
|
|
6
6
|
# throughout this chapter reach for. See lib/hecks/bluebook.rb's own
|
|
7
7
|
# header for what `Bluebook` is as a whole.
|
|
8
8
|
module Bluebook
|
|
9
|
+
# Renders a captured Ruby value to its self-describing wire spelling.
|
|
10
|
+
#
|
|
11
|
+
# @param value [Object] any value an `emits_ir` field carries, such as a Symbol,
|
|
12
|
+
# String, Hash, Array, or literal
|
|
13
|
+
# @return [String] the value rendered through `Hecks::Literal.render`
|
|
9
14
|
def self.render_value(value) = Literal.render(value)
|
|
10
15
|
|
|
11
|
-
# A query — an
|
|
16
|
+
# A query — an ask, declared on an aggregate or on one of its entities.
|
|
12
17
|
#
|
|
13
|
-
# It crosses over as an
|
|
18
|
+
# It crosses over as an instance rather than a class, and the reason is worth
|
|
14
19
|
# stating because it is the boundary of the pattern. A query inherits its
|
|
15
20
|
# whole body from `QuerySpecification::Common::Options` — `wheres`,
|
|
16
21
|
# `order_by`, `limit`, `offset`, `cursor`,
|
|
17
22
|
# `authorization`, `null_semantics`, `inspection` — and those
|
|
18
|
-
# are
|
|
23
|
+
# are instance methods that the runtime and the SQLite adapter both read.
|
|
19
24
|
# Hoisting the declaration onto a metaclass would put the identity and the
|
|
20
25
|
# specification on opposite sides of the object.
|
|
21
26
|
#
|
|
@@ -40,6 +45,25 @@ module Hecks
|
|
|
40
45
|
|
|
41
46
|
attr_reader :name, :description, :attributes
|
|
42
47
|
|
|
48
|
+
# @param name [String, Symbol] the query's declared name
|
|
49
|
+
# @param description [String, nil] the query's declared prose description
|
|
50
|
+
# @param attributes [Array<Bluebook::Attribute>] the query's declared result fields
|
|
51
|
+
# @param wheres [Array<QuerySpecification::Common::WhereClause>] the declared filter
|
|
52
|
+
# conditions
|
|
53
|
+
# @param order_by [QuerySpecification::Common::OrderBy, nil] the declared sort field
|
|
54
|
+
# and direction, or `nil` for no explicit ordering
|
|
55
|
+
# @param limit [QuerySpecification::Common::LimitSpec, nil] the declared row limit,
|
|
56
|
+
# or `nil` for none
|
|
57
|
+
# @param offset [QuerySpecification::Common::OffsetSpec, nil] the declared row offset,
|
|
58
|
+
# or `nil` for none
|
|
59
|
+
# @param cursor [QuerySpecification::Common::CursorSpec, nil] the declared pagination
|
|
60
|
+
# cursor, or `nil` for none
|
|
61
|
+
# @param authorization [QuerySpecification::Common::AuthorizationSpec, nil] the declared
|
|
62
|
+
# authorization policy, or `nil` for none
|
|
63
|
+
# @param null_semantics [QuerySpecification::Common::NullSemantics, nil] how this query
|
|
64
|
+
# orders `nil` values; defaults to `NullSemantics.default` when omitted
|
|
65
|
+
# @param inspection [QuerySpecification::Common::InspectionSpec, nil] the declared
|
|
66
|
+
# inspection mode, or `nil` for none
|
|
43
67
|
def initialize(name:, description: nil, attributes: [], wheres: [],
|
|
44
68
|
order_by: nil, limit: nil, offset: nil, cursor: nil,
|
|
45
69
|
authorization: nil, null_semantics: nil,
|
|
@@ -54,11 +78,14 @@ module Hecks
|
|
|
54
78
|
@attributes = attributes
|
|
55
79
|
end
|
|
56
80
|
|
|
57
|
-
# A query's shape is
|
|
81
|
+
# A query's shape is not fixed — `extra_options_to_h` carries
|
|
58
82
|
# whatever options the specification layer grew (count, median,
|
|
59
83
|
# group_by, scope_to). Declared emission covers the settled part
|
|
60
84
|
# and `super` hands it over; the tail stays dynamic, which is the
|
|
61
85
|
# honest description of it.
|
|
86
|
+
#
|
|
87
|
+
# @return [Hash] the declared emission, merged with whatever `extra_options_to_h`
|
|
88
|
+
# the specification layer currently carries
|
|
62
89
|
def to_h = super.merge(extra_options_to_h)
|
|
63
90
|
end
|
|
64
91
|
end
|
|
@@ -4,11 +4,11 @@ module Hecks
|
|
|
4
4
|
module Bluebook
|
|
5
5
|
# A read model — an ask that gathers heads from more than one aggregate.
|
|
6
6
|
#
|
|
7
|
-
# Like a query it crosses over as an
|
|
7
|
+
# Like a query it crosses over as an instance, and for the same reason: its
|
|
8
8
|
# body is inherited from `QuerySpecification::ReadModel::Specification`, whose
|
|
9
9
|
# readers the runtime and the SQLite adapter both call on the object. It gains
|
|
10
10
|
# an identity and an owner ; it keeps the name it always had, because only a
|
|
11
|
-
#
|
|
11
|
+
# class ever had a competing answer for `name`.
|
|
12
12
|
class ReadModel < QuerySpecification::ReadModel::Specification
|
|
13
13
|
include Construct
|
|
14
14
|
|
|
@@ -29,11 +29,27 @@ module Hecks
|
|
|
29
29
|
attr_reader :name, :description, :reference_name, :reference_target, :aggregate_heads, :group_by,
|
|
30
30
|
:count, :median_field
|
|
31
31
|
|
|
32
|
-
# `reference_name:`/`reference_target:` are nil for a
|
|
32
|
+
# `reference_name:`/`reference_target:` are nil for a rootless read
|
|
33
33
|
# model (no `reference_to` declared) — `&.` throughout, rather than
|
|
34
|
-
#
|
|
34
|
+
# calling `.to_s`/`.to_sym` unconditionally, so
|
|
35
35
|
# `reference_target.nil?` stays a real, checkable fact for the
|
|
36
36
|
# interpreter instead of silently becoming `""`.
|
|
37
|
+
#
|
|
38
|
+
# @param name [String, Symbol] the read model's declared name
|
|
39
|
+
# @param description [String, nil] the read model's declared prose description
|
|
40
|
+
# @param reference_name [Symbol, String, nil] the local reference attribute this
|
|
41
|
+
# read model roots at, or `nil` for a rootless read model
|
|
42
|
+
# @param reference_target [String, Symbol, nil] the rooted aggregate's name, or
|
|
43
|
+
# `nil` for a rootless read model
|
|
44
|
+
# @param aggregate_heads [Array<Hash{Symbol => Object}>] the included aggregates, each
|
|
45
|
+
# a row with `:aggregate` (the aggregate's name), `:as` (the output field name) and
|
|
46
|
+
# `:many` (whether this head is a many-side join)
|
|
47
|
+
# @param group_by [Array<Hash{field: Symbol}>] the declared group-by fields, one row
|
|
48
|
+
# per field
|
|
49
|
+
# @param count [Boolean, nil] whether this read model reduces to a row count; `nil`
|
|
50
|
+
# when undeclared
|
|
51
|
+
# @param median_field [Symbol, nil] the field this read model reduces to the median
|
|
52
|
+
# of, or `nil` when undeclared
|
|
37
53
|
def initialize(name:, description: nil, reference_name: nil, reference_target: nil, aggregate_heads: [],
|
|
38
54
|
group_by: [], count: nil, median_field: nil, **)
|
|
39
55
|
super(joins: aggregate_heads, **)
|
|
@@ -47,11 +63,11 @@ module Hecks
|
|
|
47
63
|
# `group_by_fields` is the convenience reader everything but
|
|
48
64
|
# `to_h`/the Judge's own generic walk actually wants.
|
|
49
65
|
@group_by = group_by
|
|
50
|
-
#
|
|
66
|
+
# **Absent is not false**. `@count` stays nil rather than becoming
|
|
51
67
|
# `false` when undeclared — the same "if you declare it, declare
|
|
52
68
|
# something" reading `Lifecycle`'s own optional fields rely on
|
|
53
69
|
# (MetaValidator::Judge#setters skips a setter whose every
|
|
54
|
-
# source is `nil`, so an
|
|
70
|
+
# source is `nil`, so an always-false `@count` would dispatch
|
|
55
71
|
# `ReadModel.Count` on every read model that never wrote the
|
|
56
72
|
# word at all). `!!count` collapses a truthy DSL `true` (or a
|
|
57
73
|
# reconstructed `true`) to the same `true`/`nil` pair either path
|
|
@@ -60,15 +76,15 @@ module Hecks
|
|
|
60
76
|
@median_field = median_field&.to_sym
|
|
61
77
|
end
|
|
62
78
|
|
|
63
|
-
# `.to_sym` regardless of source — the
|
|
79
|
+
# `.to_sym` regardless of source — the direct builder path stores
|
|
64
80
|
# symbols, but `Reconstruction::Shapes#group_by_field` (the
|
|
65
|
-
# replayed-from-the-meta-domain path every
|
|
81
|
+
# replayed-from-the-meta-domain path every real boot actually
|
|
66
82
|
# goes through) reads the field back as a String, the same way
|
|
67
|
-
# `aggregate_heads`' own `:as` does. Row hash
|
|
83
|
+
# `aggregate_heads`' own `:as` does. Row hash keys built by
|
|
68
84
|
# `Value.materialize_unwrapped` are symbols (`attr.name`), so
|
|
69
85
|
# this has to be too, or `row[field]` in `nest` silently misses.
|
|
70
86
|
|
|
71
|
-
# `wheres`/`order_by`/`limit` are spelled explicitly here, the
|
|
87
|
+
# `wheres`/`order_by`/`limit` are spelled explicitly here, the same
|
|
72
88
|
# mechanism `Query#to_h` already uses (query.rb, read directly
|
|
73
89
|
# before this was written) — always present, `wheres` a (possibly
|
|
74
90
|
# empty) array and `order_by`/`limit` nil when undeclared, exactly
|
|
@@ -78,22 +94,26 @@ module Hecks
|
|
|
78
94
|
# than a reader needing to learn a second one. See this file's own
|
|
79
95
|
# `filtered_head_name` and language/bluebook/syntax.bluebook's
|
|
80
96
|
# `ReadModel` `where`/`order_by`/`limit` member rows for the history
|
|
81
|
-
# of why this
|
|
97
|
+
# of why this was narrower, and 2026-08-11's read-model where/
|
|
82
98
|
# order_by/limit task for why it stopped being.
|
|
83
99
|
# Same dynamic tail as a Query's, plus two collections whose
|
|
84
|
-
#
|
|
100
|
+
# rows are plain hashes rather than constructs — they are
|
|
85
101
|
# normalised here rather than by `many`, which recurses through
|
|
86
102
|
# `to_h` and would have nothing to call.
|
|
87
|
-
# `count`/`median_field` are
|
|
103
|
+
# `count`/`median_field` are absent (no key at all), not `nil`,
|
|
88
104
|
# when undeclared — unlike `group_by` (always a `[]`, never
|
|
89
|
-
# missing) they are the
|
|
90
|
-
# added that no
|
|
105
|
+
# missing) they are the first fields this construct has ever
|
|
106
|
+
# added that no older real corpus member could possibly carry, so
|
|
91
107
|
# merging them unconditionally would have put a `null` onto every
|
|
92
108
|
# existing read model's own wire shape (banking's
|
|
93
109
|
# `ComplianceDashboard`/`CustomerPortfolio`, the language's own
|
|
94
110
|
# `WholeBluebook`, ...) for a fact nothing about them changed —
|
|
95
111
|
# the exact `extra_options_to_h` reads for `cursor`/`offset`/etc,
|
|
96
112
|
# applied here for the same reason.
|
|
113
|
+
#
|
|
114
|
+
# @return [Hash] the declared emission, with `aggregate_heads`/`group_by` rows
|
|
115
|
+
# stringified, `count`/`median_field` merged in only when declared, and
|
|
116
|
+
# `extra_options_to_h`'s own dynamic tail merged last
|
|
97
117
|
def to_h
|
|
98
118
|
reductions = {}
|
|
99
119
|
reductions[:count] = true if @count
|