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
|
@@ -2,48 +2,65 @@ require_relative "../naming"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Projector
|
|
5
|
-
# A
|
|
5
|
+
# A bluebook, projected as its own usage documentation.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# ## What this is for
|
|
8
|
+
#
|
|
9
|
+
# A chapter in this corpus already contains everything a user of it
|
|
10
|
+
# needs: what each aggregate is (`description`), what each verb is
|
|
11
|
+
# for (`goal`) and who issues it (`role`), which states it moves
|
|
10
12
|
# between, what it refuses and in whose words (`given`, `invariant`,
|
|
11
|
-
# `ensures`), and what each list is worth reading (`description` on
|
|
12
|
-
# query). None of that reaches the person who has to
|
|
13
|
-
# They read the source, or they read a document somebody
|
|
14
|
-
# source and stopped updating.
|
|
13
|
+
# `ensures`), and what each list is worth reading (`description` on
|
|
14
|
+
# a query). None of that reaches the person who has to call the
|
|
15
|
+
# domain. They read the source, or they read a document somebody
|
|
16
|
+
# wrote beside the source and stopped updating.
|
|
15
17
|
#
|
|
16
18
|
# `docs/implemented/reference/` is the precedent and the contrast. `bin/reference`
|
|
17
19
|
# generates it from the language's own Syntax chapter, so the reference
|
|
18
20
|
# for the DSL cannot drift from the DSL. This is the same trick one level
|
|
19
|
-
# down: the usage document for a
|
|
21
|
+
# down: the usage document for a domain, generated from that domain, so it
|
|
20
22
|
# cannot drift from the domain either.
|
|
21
23
|
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
24
|
+
# ## Why a projection, not a generator script
|
|
25
|
+
#
|
|
26
|
+
# `Projector` is the repository's registry of "canonical IR in,
|
|
27
|
+
# external artifact out" (§30 of the implementation plan), and this
|
|
28
|
+
# is exactly that shape: one bluebook's IR in, markdown out, no
|
|
29
|
+
# runtime needed and no store touched. Registered as `:docs` beside
|
|
30
|
+
# `:ir`, and reachable the way every projector is —
|
|
31
|
+
# `Projector.call(:docs, bluebook: ...)`.
|
|
28
32
|
#
|
|
29
|
-
#
|
|
33
|
+
# And as a method, which is the half that makes it get used.
|
|
30
34
|
# `Facade::Surface` already installs a module per chapter carrying
|
|
31
35
|
# `vision` and `aggregates`; `docs` joins them, so a booted domain answers
|
|
32
36
|
# `QualityControl.docs` and an aggregate door answers
|
|
33
37
|
# `QualityControl::Bug.docs`. A document you have to remember a script for
|
|
34
38
|
# is a document nobody reads.
|
|
35
39
|
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
40
|
+
# ## What it deliberately does not do
|
|
41
|
+
#
|
|
42
|
+
# Invent. Every sentence below comes out of the chapter. Where a
|
|
43
|
+
# chapter says nothing — an aggregate with no `description`, a
|
|
44
|
+
# command with no `goal` — the document says nothing rather than
|
|
45
|
+
# filling the gap with a restatement of the name, because a
|
|
46
|
+
# generated paragraph that only rephrases an identifier teaches a
|
|
47
|
+
# reader to skim the ones that do not.
|
|
42
48
|
module DocsProjector
|
|
43
49
|
module_function
|
|
44
50
|
|
|
51
|
+
# Projects `bluebook` as its own usage documentation.
|
|
52
|
+
#
|
|
45
53
|
# `options[:heading]` sets the top heading level (default 1), so a
|
|
46
54
|
# caller splicing this into a larger document can push it down.
|
|
55
|
+
#
|
|
56
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to document
|
|
57
|
+
# @param options [Hash] optional inputs
|
|
58
|
+
# @option options [Integer, String] :heading the top heading level; defaults to 1
|
|
59
|
+
# @option options [String, Symbol, nil] :aggregate narrows the document to one
|
|
60
|
+
# aggregate, omitting the chapter header and closing sections
|
|
61
|
+
# @return [String] the document, as Markdown, ending in a newline
|
|
62
|
+
# @raise [Runtime::NotFound] if `options[:aggregate]` names no aggregate `bluebook`
|
|
63
|
+
# declares
|
|
47
64
|
def call(bluebook:, options: {})
|
|
48
65
|
depth = (options[:heading] || 1).to_i
|
|
49
66
|
only = options[:aggregate]
|
|
@@ -55,11 +72,18 @@ module Hecks
|
|
|
55
72
|
"#{out.compact.join("\n").rstrip}\n"
|
|
56
73
|
end
|
|
57
74
|
|
|
58
|
-
# A
|
|
75
|
+
# A name that names nothing is refused, not answered with an empty
|
|
59
76
|
# document. Shipped the other way first: `options[:aggregate]` that
|
|
60
77
|
# matched no head returned "" and exit 0, which is the silent-wrong-
|
|
61
78
|
# answer shape this repository has already been bitten by twice in the
|
|
62
79
|
# query engine. A misspelling should cost a sentence, not a puzzle.
|
|
80
|
+
#
|
|
81
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to look in
|
|
82
|
+
# @param only [String, Symbol, nil] narrows the result to the one aggregate
|
|
83
|
+
# named; nil answers every aggregate
|
|
84
|
+
# @return [Array<Bluebook::Aggregate>, Bluebook::Aggregate] every declared
|
|
85
|
+
# aggregate, or the single aggregate `only` names
|
|
86
|
+
# @raise [Runtime::NotFound] if `only` names no aggregate `bluebook` declares
|
|
63
87
|
def aggregates(bluebook, only)
|
|
64
88
|
return bluebook.aggregates unless only
|
|
65
89
|
|
|
@@ -69,13 +93,24 @@ module Hecks
|
|
|
69
93
|
"it declares #{bluebook.aggregates.map(&:hecks_name).sort.join(', ')}")
|
|
70
94
|
end
|
|
71
95
|
|
|
96
|
+
# Renders a Markdown heading line.
|
|
97
|
+
#
|
|
98
|
+
# @param depth [Integer] the heading level
|
|
99
|
+
# @param text [String] the heading text
|
|
100
|
+
# @return [String] a Markdown `#`-prefixed heading line
|
|
72
101
|
def h(depth, text) = "#{'#' * depth} #{text}"
|
|
73
102
|
|
|
74
103
|
# ── the chapter ───────────────────────────────────────────────────
|
|
75
104
|
|
|
105
|
+
# Renders the chapter-level header: its title, vision, classification,
|
|
106
|
+
# former name, and aggregate list.
|
|
107
|
+
#
|
|
108
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to document
|
|
109
|
+
# @param depth [Integer] the heading level for the chapter's own title
|
|
110
|
+
# @return [String] the header's Markdown source
|
|
76
111
|
def chapter_header(bluebook, depth)
|
|
77
112
|
out = [h(depth, bluebook.name), ""]
|
|
78
|
-
#
|
|
113
|
+
# The vision first and as a quote. It is the one sentence in a chapter
|
|
79
114
|
# written for somebody who does not know the domain yet.
|
|
80
115
|
out += ["> #{bluebook.vision}", ""] if bluebook.vision
|
|
81
116
|
out << "#{bluebook.classification.to_s.capitalize} domain." if bluebook.classification
|
|
@@ -86,13 +121,22 @@ module Hecks
|
|
|
86
121
|
out.join("\n")
|
|
87
122
|
end
|
|
88
123
|
|
|
124
|
+
# Builds a construct's own GitHub-style heading anchor.
|
|
125
|
+
#
|
|
126
|
+
# @param name [String, Symbol] the construct name to anchor
|
|
127
|
+
# @return [String] the construct's own GitHub-style heading anchor
|
|
89
128
|
def anchor(name) = Naming.snake(name).tr("_", "-")
|
|
90
129
|
|
|
91
|
-
#
|
|
130
|
+
# What happens without anybody asking — the part of a domain a caller
|
|
92
131
|
# cannot discover from any verb list, and the part most likely to surprise
|
|
93
132
|
# them. A policy means one dispatch causes another, sometimes into a
|
|
94
133
|
# different domain entirely; a saga means a sequence is being driven on
|
|
95
134
|
# their behalf and can end in more than one place.
|
|
135
|
+
#
|
|
136
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to document
|
|
137
|
+
# @param depth [Integer] the heading level for the "Reactions"/saga sections
|
|
138
|
+
# @return [String, nil] the closing's Markdown source, or nil if `bluebook`
|
|
139
|
+
# declares no policy and no process manager
|
|
96
140
|
def closing(bluebook, depth)
|
|
97
141
|
out = []
|
|
98
142
|
|
|
@@ -123,6 +167,12 @@ module Hecks
|
|
|
123
167
|
|
|
124
168
|
# ── one aggregate ─────────────────────────────────────────────────
|
|
125
169
|
|
|
170
|
+
# Documents one aggregate: its description, identity, references,
|
|
171
|
+
# attributes, lifecycle, verbs, queries, and nested entities.
|
|
172
|
+
#
|
|
173
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate to document
|
|
174
|
+
# @param depth [Integer] the heading level for the aggregate's own title
|
|
175
|
+
# @return [String] the aggregate's Markdown section
|
|
126
176
|
def aggregate_section(aggregate, depth)
|
|
127
177
|
out = [h(depth, aggregate.hecks_name), ""]
|
|
128
178
|
out += [aggregate.description, ""] if aggregate.description
|
|
@@ -142,10 +192,16 @@ module Hecks
|
|
|
142
192
|
out.compact.join("\n")
|
|
143
193
|
end
|
|
144
194
|
|
|
195
|
+
# Documents one entity nested under `aggregate`.
|
|
196
|
+
#
|
|
197
|
+
# @param aggregate [Bluebook::Aggregate] the entity's own owning aggregate
|
|
198
|
+
# @param entity [Bluebook::Entity] the entity to document
|
|
199
|
+
# @param depth [Integer] the heading level for the entity's own title
|
|
200
|
+
# @return [String] the entity's Markdown section
|
|
145
201
|
def entity_section(aggregate, entity, depth)
|
|
146
202
|
out = [h(depth, "#{entity.hecks_name} (within #{aggregate.hecks_name})"), ""]
|
|
147
203
|
out += [entity.description, ""] if entity.description
|
|
148
|
-
#
|
|
204
|
+
# The thing a caller gets wrong first. An entity has no door of its
|
|
149
205
|
# own: its verb is spelled through the aggregate that holds it, and
|
|
150
206
|
# the parent's id travels alongside the entity's own identity.
|
|
151
207
|
out << "Addressed through its holder — `#{aggregate.hecks_name}.#{entity.hecks_name}.<Verb>`, " \
|
|
@@ -161,6 +217,12 @@ module Hecks
|
|
|
161
217
|
|
|
162
218
|
# ── the shape ─────────────────────────────────────────────────────
|
|
163
219
|
|
|
220
|
+
# Renders a holder's own non-reference attributes as a Markdown table.
|
|
221
|
+
#
|
|
222
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the holder whose
|
|
223
|
+
# attributes to render
|
|
224
|
+
# @return [String, nil] the attribute/shape/rules table, or nil if `holder`
|
|
225
|
+
# declares no non-reference attribute
|
|
164
226
|
def attributes_table(holder)
|
|
165
227
|
attributes = holder.attributes.reject(&:reference?)
|
|
166
228
|
return nil if attributes.empty?
|
|
@@ -171,10 +233,15 @@ module Hecks
|
|
|
171
233
|
table(%w[attribute shape rules], rows)
|
|
172
234
|
end
|
|
173
235
|
|
|
174
|
-
# A
|
|
236
|
+
# A value object's fields, not its name. `commit` typed `CommitRef` tells
|
|
175
237
|
# a caller nothing; `{ value: String }` tells them what to send, which is
|
|
176
238
|
# the single most common thing to get wrong at this boundary — a bare
|
|
177
239
|
# scalar where an object is wanted.
|
|
240
|
+
#
|
|
241
|
+
# @param attribute [Bluebook::Attribute] the attribute to describe
|
|
242
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] `attribute`'s own holder
|
|
243
|
+
# @return [String] the attribute's shape: its value object's own fields, or its
|
|
244
|
+
# scalar type, with "list of" and "*(optional)*" applied as declared
|
|
178
245
|
def shape_of(attribute, holder)
|
|
179
246
|
value_object = value_object_for(attribute, holder)
|
|
180
247
|
inner =
|
|
@@ -187,6 +254,12 @@ module Hecks
|
|
|
187
254
|
attribute.optional? ? "#{shape} *(optional)*" : shape
|
|
188
255
|
end
|
|
189
256
|
|
|
257
|
+
# Lists an attribute's own rules: closed-set members, field patterns and
|
|
258
|
+
# defaults, invariants, and its own default.
|
|
259
|
+
#
|
|
260
|
+
# @param attribute [Bluebook::Attribute] the attribute to describe
|
|
261
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] `attribute`'s own holder
|
|
262
|
+
# @return [String] the attribute's rules, joined with "; "; `""` if it has none
|
|
190
263
|
def rules_of(attribute, holder)
|
|
191
264
|
value_object = value_object_for(attribute, holder)
|
|
192
265
|
rules = []
|
|
@@ -200,6 +273,11 @@ module Hecks
|
|
|
200
273
|
rules.empty? ? "" : rules.join("; ")
|
|
201
274
|
end
|
|
202
275
|
|
|
276
|
+
# Names a closed set's own members.
|
|
277
|
+
#
|
|
278
|
+
# @param value_object [Bluebook::ValueObject, nil] the value object to check
|
|
279
|
+
# @return [Array<Object>] every unique value across `value_object`'s own member
|
|
280
|
+
# rows, or `[]` if `value_object` is nil or not a closed set
|
|
203
281
|
def closed_members(value_object)
|
|
204
282
|
return [] unless value_object&.closed_set?
|
|
205
283
|
|
|
@@ -208,6 +286,12 @@ module Hecks
|
|
|
208
286
|
|
|
209
287
|
# An entity holds no value objects of its own — its argument types are
|
|
210
288
|
# declared on the aggregate above it.
|
|
289
|
+
#
|
|
290
|
+
# @param attribute [Bluebook::Attribute] the attribute whose type to resolve
|
|
291
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] `attribute`'s own holder
|
|
292
|
+
# @return [Bluebook::ValueObject, nil] the value object class `attribute`'s type
|
|
293
|
+
# names, found on `holder` or its own owning aggregate; nil if `attribute`'s
|
|
294
|
+
# type is not a value object
|
|
211
295
|
def value_object_for(attribute, holder)
|
|
212
296
|
scopes = [holder, holder.respond_to?(:hecks_owner) ? holder.hecks_owner : nil].compact
|
|
213
297
|
scopes.each do |scope|
|
|
@@ -221,6 +305,13 @@ module Hecks
|
|
|
221
305
|
|
|
222
306
|
# ── the machine ───────────────────────────────────────────────────
|
|
223
307
|
|
|
308
|
+
# Renders a holder's own lifecycle as a "starting state" sentence and a
|
|
309
|
+
# verb/from/to table.
|
|
310
|
+
#
|
|
311
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the holder to document
|
|
312
|
+
# @param depth [Integer] the heading level for the "Lifecycle" section
|
|
313
|
+
# @return [String, nil] the section's Markdown source, or nil if `holder`
|
|
314
|
+
# declares no lifecycle
|
|
224
315
|
def lifecycle_section(holder, depth)
|
|
225
316
|
lifecycle = holder.lifecycle or return nil
|
|
226
317
|
|
|
@@ -234,6 +325,13 @@ module Hecks
|
|
|
234
325
|
|
|
235
326
|
# ── the verbs ─────────────────────────────────────────────────────
|
|
236
327
|
|
|
328
|
+
# Documents every command a holder declares.
|
|
329
|
+
#
|
|
330
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the holder whose
|
|
331
|
+
# commands to document
|
|
332
|
+
# @param depth [Integer] the heading level for the "Verbs" section
|
|
333
|
+
# @return [String, nil] the section's Markdown source, or nil if `holder`
|
|
334
|
+
# declares no command
|
|
237
335
|
def verbs_section(holder, depth)
|
|
238
336
|
commands = holder.commands
|
|
239
337
|
return nil if commands.empty?
|
|
@@ -243,6 +341,13 @@ module Hecks
|
|
|
243
341
|
out.join("\n")
|
|
244
342
|
end
|
|
245
343
|
|
|
344
|
+
# Documents one command: its goal, role, arguments, refusals,
|
|
345
|
+
# guarantees, and emitted events.
|
|
346
|
+
#
|
|
347
|
+
# @param command [Bluebook::Command] the command to document
|
|
348
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] `command`'s own holder
|
|
349
|
+
# @param depth [Integer] the heading level for the command's own title
|
|
350
|
+
# @return [String] the command's Markdown section
|
|
246
351
|
def command_entry(command, holder, depth)
|
|
247
352
|
out = [h(depth, "#{command.hecks_name}#{' *(creates)*' if command.creates?}"), ""]
|
|
248
353
|
out += [command.goal, ""] if command.goal
|
|
@@ -270,6 +375,11 @@ module Hecks
|
|
|
270
375
|
# nothing to share with `command_entry`'s other sections, extracted
|
|
271
376
|
# only to keep that method to the one shape every section there
|
|
272
377
|
# follows: build a chunk, append it if non-empty.
|
|
378
|
+
#
|
|
379
|
+
# @param arguments [Array<Bluebook::Attribute>] the command's own arguments
|
|
380
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the arguments' own holder
|
|
381
|
+
# @return [Array<Array(String, String, String)>] one `[argument, shape, needed]`
|
|
382
|
+
# row per argument
|
|
273
383
|
def command_argument_rows(arguments, holder)
|
|
274
384
|
arguments.map do |attribute|
|
|
275
385
|
shape = attribute.reference? ? "id of a `#{attribute.type.target_name}`" : shape_of(attribute, holder)
|
|
@@ -277,11 +387,15 @@ module Hecks
|
|
|
277
387
|
end
|
|
278
388
|
end
|
|
279
389
|
|
|
280
|
-
#
|
|
390
|
+
# Every way this verb can say no, gathered from the three places a
|
|
281
391
|
# chapter states them — the lifecycle it is an edge of, its own
|
|
282
392
|
# `given`s, and the fact that a reference has to resolve. A caller
|
|
283
393
|
# reading only the argument list learns none of these, and they are
|
|
284
394
|
# most of what a domain is.
|
|
395
|
+
#
|
|
396
|
+
# @param command [Bluebook::Command] the command to gather refusals for
|
|
397
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] `command`'s own holder
|
|
398
|
+
# @return [Array<String>] every way `command` can refuse, stated as sentences
|
|
285
399
|
def refusals_of(command, holder)
|
|
286
400
|
refusals = []
|
|
287
401
|
|
|
@@ -304,6 +418,12 @@ module Hecks
|
|
|
304
418
|
|
|
305
419
|
# ── the reads ─────────────────────────────────────────────────────
|
|
306
420
|
|
|
421
|
+
# Renders a list of queries as one Markdown paragraph per query.
|
|
422
|
+
#
|
|
423
|
+
# @param queries [Array<Bluebook::Query>] the queries to document
|
|
424
|
+
# @param depth [Integer] the heading level for `title`
|
|
425
|
+
# @param title [String] the section's own heading text
|
|
426
|
+
# @return [String, nil] the section's Markdown source, or nil if `queries` is empty
|
|
307
427
|
def queries_section(queries, depth, title)
|
|
308
428
|
return nil if queries.empty?
|
|
309
429
|
|
|
@@ -320,6 +440,12 @@ module Hecks
|
|
|
320
440
|
out.join("\n")
|
|
321
441
|
end
|
|
322
442
|
|
|
443
|
+
# Renders a Markdown pipe table.
|
|
444
|
+
#
|
|
445
|
+
# @param headers [Array<String>] the column headers
|
|
446
|
+
# @param rows [Array<Array<String>>] each row's own cell values, matching
|
|
447
|
+
# `headers`' width
|
|
448
|
+
# @return [String] the rendered table, ending in a blank line
|
|
323
449
|
def table(headers, rows)
|
|
324
450
|
lines = ["| #{headers.join(' | ')} |", "|#{headers.map { '---' }.join('|')}|"]
|
|
325
451
|
rows.each { |row| lines << "| #{row.join(' | ')} |" }
|
|
@@ -5,31 +5,39 @@ module Hecks
|
|
|
5
5
|
module Projector
|
|
6
6
|
# Registry-wide serialization to Hash/JSON: bluebook IR
|
|
7
7
|
# (`call`/`json`), era-adapter lineage-capability flags (`lineage`),
|
|
8
|
-
# and translation edges in both their
|
|
8
|
+
# and translation edges in both their digest-relevant declared shape
|
|
9
9
|
# (`translation_hash`, what ApprovalDigest hashes) and their
|
|
10
|
-
# consumer-ready
|
|
10
|
+
# consumer-ready compiled shape with precompiled SQL attached
|
|
11
11
|
# (`translations`/`compiled_translation_aggregate`). Read directly by
|
|
12
12
|
# bin/ir, bin/project_rust, and the translation/audit approval digest.
|
|
13
13
|
module Exporter
|
|
14
14
|
module_function
|
|
15
15
|
|
|
16
|
+
# Exports every booted bluebook's own canonical IR.
|
|
17
|
+
#
|
|
18
|
+
# @param registry [Runtime::Registry] the booted registry to export
|
|
19
|
+
# @return [Hash{String => Hash}] each domain name, mapped to its bluebook's `to_h`
|
|
16
20
|
def call(registry)
|
|
17
21
|
registry.bluebooks.transform_values(&:to_h)
|
|
18
22
|
end
|
|
19
23
|
|
|
24
|
+
# Exports every booted bluebook's own canonical IR as JSON.
|
|
25
|
+
#
|
|
26
|
+
# @param registry [Runtime::Registry] the booted registry to export
|
|
27
|
+
# @return [String] `call`'s output, as pretty-printed JSON
|
|
20
28
|
def json(registry)
|
|
21
29
|
JSON.pretty_generate(call(registry))
|
|
22
30
|
end
|
|
23
31
|
|
|
24
|
-
# A
|
|
32
|
+
# A binding fact, deliberately not folded into `call`/`bluebook.to_h`
|
|
25
33
|
# above — the canonical IR is runtime-independent by design (ADR
|
|
26
|
-
# 0001: it describes what a bluebook
|
|
34
|
+
# 0001: it describes what a bluebook declares, never which adapter
|
|
27
35
|
# a deployment happens to bind it to), and "is this aggregate bound
|
|
28
36
|
# to a lineage-capable adapter" is exactly the kind of fact that
|
|
29
37
|
# answer can change per-deployment without the bluebook's own shape
|
|
30
38
|
# changing at all. Consumers that need it (bin/project_rust's own
|
|
31
39
|
# `ir.json` sidecar, rust/host's runtime era-aware seed overlay —
|
|
32
|
-
# dispatch.rs) merge this in as a
|
|
40
|
+
# dispatch.rs) merge this in as a separate top-level key, the same
|
|
33
41
|
# way `translations` already sits beside `call`'s output rather than
|
|
34
42
|
# inside it.
|
|
35
43
|
#
|
|
@@ -42,6 +50,12 @@ module Hecks
|
|
|
42
50
|
# already answers for a domain with nothing lineage-capable bound —
|
|
43
51
|
# `capable_aggregates: []` — rather than raising on an undefined
|
|
44
52
|
# constant.
|
|
53
|
+
# @param registry [Runtime::Registry] the booted registry `domain_name` is loaded in
|
|
54
|
+
# @param domain_name [String] the domain to check era-adapter lineage capability for
|
|
55
|
+
# @return [Hash{Symbol => Array<Hash{Symbol => String}>}] `:capable_aggregates`,
|
|
56
|
+
# each a `:name`/`:storage_name` Hash; empty when the era plugin is unloaded or
|
|
57
|
+
# nothing this domain binds is lineage-capable
|
|
58
|
+
# @raise [KeyError] if `domain_name` is not a loaded domain
|
|
45
59
|
def lineage(registry, domain_name)
|
|
46
60
|
return { capable_aggregates: [] } unless Ports::Persistence.plugin?(:era)
|
|
47
61
|
|
|
@@ -54,13 +68,13 @@ module Hecks
|
|
|
54
68
|
{ capable_aggregates: capable.map { |aggregate| { name: aggregate.name, storage_name: aggregate.storage_name } } }
|
|
55
69
|
end
|
|
56
70
|
|
|
57
|
-
# A
|
|
58
|
-
# aggregate's
|
|
71
|
+
# A binding fact, same shape/reasoning as `lineage` above: every
|
|
72
|
+
# aggregate's declared persistence adapter name (`persisted_by`),
|
|
59
73
|
# not part of the canonical bluebook shape `call` exports (ADR
|
|
60
74
|
# 0001 — the IR describes what's declared, never which adapter a
|
|
61
75
|
# deployment binds it to). Unlike `lineage`, this needs no era
|
|
62
76
|
# plugin — `BindingPolicy` is core, always loaded — and covers
|
|
63
|
-
#
|
|
77
|
+
# every aggregate, not just lineage-capable ones: `rust/host`
|
|
64
78
|
# (`ir.rs`'s own `refuse_unsupported_persistence_adapters`) reads
|
|
65
79
|
# this to refuse loudly, at boot, against a domain bound to an
|
|
66
80
|
# adapter it has no backend for (Heki, Memory, Sqlite, D1,
|
|
@@ -68,6 +82,13 @@ module Hecks
|
|
|
68
82
|
# today), rather than silently building up a second, disjoint
|
|
69
83
|
# history nothing but Rust ever reads while the real state stays
|
|
70
84
|
# wherever its own adapter actually wrote it.
|
|
85
|
+
# @param registry [Runtime::Registry] the booted registry `domain_name` is loaded in
|
|
86
|
+
# @param domain_name [String] the domain to export persistence bindings for
|
|
87
|
+
# @return [Hash{Symbol => Array<Hash{Symbol => Object}>}] `:aggregates`, each a
|
|
88
|
+
# `:name`/`:storage_name`/`:adapter` Hash
|
|
89
|
+
# @raise [KeyError] if `domain_name` is not a loaded domain
|
|
90
|
+
# @raise [Runtime::WiringError] if an aggregate has no authoritative bind, more
|
|
91
|
+
# than one, or a bind with a role this port does not support
|
|
71
92
|
def persistence(registry, domain_name)
|
|
72
93
|
bluebook = registry.bluebooks.fetch(domain_name)
|
|
73
94
|
aggregates = bluebook.aggregates.map do |aggregate|
|
|
@@ -78,7 +99,34 @@ module Hecks
|
|
|
78
99
|
{ aggregates: aggregates }
|
|
79
100
|
end
|
|
80
101
|
|
|
81
|
-
#
|
|
102
|
+
# A binding fact, same shape/reasoning as `lineage`/`persistence`
|
|
103
|
+
# above: which chapter this domain's role checks resolve against
|
|
104
|
+
# (`Registry#authorization_provider_for` — the domain's own chapter
|
|
105
|
+
# or a framework member it attaches that declares `provides
|
|
106
|
+
# "authorization"`), with that chapter's declared verbs qualified.
|
|
107
|
+
# `rust/host` (auth.rs) reads this instead of naming Governance.
|
|
108
|
+
# `{}` when nothing this domain attaches provides authorization.
|
|
109
|
+
# @param registry [Runtime::Registry] the booted registry `domain_name` is loaded in
|
|
110
|
+
# @param domain_name [String] the domain to export the authorization binding for
|
|
111
|
+
# @return [Hash{Symbol => String, nil}] `:provider` (name), `:grant`, `:assignments`
|
|
112
|
+
# (both provided-verb names), and `:assignment_aggregate` (`:assignments`' own
|
|
113
|
+
# leading aggregate name); `{}` if nothing this domain attaches provides
|
|
114
|
+
# authorization
|
|
115
|
+
def authorization(registry, domain_name)
|
|
116
|
+
provider = registry.authorization_provider_for(domain_name)
|
|
117
|
+
return {} unless provider
|
|
118
|
+
|
|
119
|
+
capability = Bluebook::Capabilities::AUTHORIZATION
|
|
120
|
+
assignments = provider.provided_verb(capability, :assignments)
|
|
121
|
+
{
|
|
122
|
+
provider: provider.name,
|
|
123
|
+
grant: provider.provided_verb(capability, :grant),
|
|
124
|
+
assignments: assignments,
|
|
125
|
+
assignment_aggregate: assignments&.split(".")&.first
|
|
126
|
+
}
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Translation IR, always as an array, with each aggregate's
|
|
82
130
|
# precompiled SQL attached (`compiled_translation_aggregate`) —
|
|
83
131
|
# this is the export a consumer embeds (`ir.json`'s `translations`
|
|
84
132
|
# key), never the bare digest-relevant shape `edge_digest` hashes
|
|
@@ -86,10 +134,19 @@ module Hecks
|
|
|
86
134
|
# separate). `values:` tables serialize as `[key, value]` pairs,
|
|
87
135
|
# never an object, because JSON object keys are always strings and
|
|
88
136
|
# a convert's keys are typed.
|
|
137
|
+
# @param registry [Runtime::Registry] the booted registry to export translations from
|
|
138
|
+
# @return [Array<Hash>] every registered translation, as `compiled_translation_hash`
|
|
139
|
+
# builds
|
|
89
140
|
def translations(registry)
|
|
90
141
|
registry.translations.map { |translation| compiled_translation_hash(translation) }
|
|
91
142
|
end
|
|
92
143
|
|
|
144
|
+
# Exports one translation, its aggregates' compiled SQL included.
|
|
145
|
+
#
|
|
146
|
+
# @param translation [Bluebook::Translation] the translation to export
|
|
147
|
+
# @return [Hash{Symbol => Object}] `:domain` (String), `:from`/`:to` (the era
|
|
148
|
+
# identifiers as declared), `:retired` (`Array<String>`), and `:aggregates`
|
|
149
|
+
# (each `compiled_translation_aggregate`'s own Hash)
|
|
93
150
|
def compiled_translation_hash(translation)
|
|
94
151
|
{
|
|
95
152
|
domain: translation.domain,
|
|
@@ -100,21 +157,29 @@ module Hecks
|
|
|
100
157
|
}
|
|
101
158
|
end
|
|
102
159
|
|
|
160
|
+
# Exports every registered translation as JSON.
|
|
161
|
+
#
|
|
162
|
+
# @param registry [Runtime::Registry] the booted registry to export translations from
|
|
163
|
+
# @return [String] `translations`' output, as pretty-printed JSON
|
|
103
164
|
def translations_json(registry)
|
|
104
165
|
JSON.pretty_generate(translations(registry))
|
|
105
166
|
end
|
|
106
167
|
|
|
107
|
-
#
|
|
108
|
-
#
|
|
168
|
+
# The digest-relevant shape — `ApprovalDigest.edge_digest` hashes
|
|
169
|
+
# exactly this, and only this, for exactly the reason `compiled_
|
|
109
170
|
# translation_aggregate` below must never be used for that
|
|
110
|
-
# purpose: a digest bound to the
|
|
111
|
-
#
|
|
171
|
+
# purpose: a digest bound to the compiled SQL, not just the
|
|
172
|
+
# declared rules, would invalidate an existing human approval the
|
|
112
173
|
# moment `Translation::RuleCompiler`'s own output format changed
|
|
113
174
|
# for any reason — a compiler refactor, a cosmetic SQL-formatting
|
|
114
175
|
# change — even when the declared rules an approver actually
|
|
115
|
-
# reviewed never changed at all. The approval binds to
|
|
116
|
-
#
|
|
176
|
+
# reviewed never changed at all. The approval binds to what was
|
|
177
|
+
# declared, not to what a particular compiler build happened to
|
|
117
178
|
# emit from it.
|
|
179
|
+
# @param translation [Bluebook::Translation] the translation to digest
|
|
180
|
+
# @return [Hash{Symbol => Object}] `:domain` (String), `:from`/`:to` (the era
|
|
181
|
+
# identifiers as declared), `:retired` (`Array<String>`), and `:aggregates`
|
|
182
|
+
# (each `translation_aggregate`'s own Hash)
|
|
118
183
|
def translation_hash(translation)
|
|
119
184
|
{
|
|
120
185
|
domain: translation.domain,
|
|
@@ -125,6 +190,14 @@ module Hecks
|
|
|
125
190
|
}
|
|
126
191
|
end
|
|
127
192
|
|
|
193
|
+
# Digests one aggregate's own declared translation rules.
|
|
194
|
+
#
|
|
195
|
+
# @param aggregate [Bluebook::TranslationAggregate] the aggregate's own
|
|
196
|
+
# translation rules to digest
|
|
197
|
+
# @return [Hash{Symbol => Object}] `:name` (String), `:was` (String, nil),
|
|
198
|
+
# `:renames` (`Hash{String => String}`), `:moves`/`:converts`/`:retypes`/
|
|
199
|
+
# `:computes`/`:rekeys`/`:backfills` (each an `Array<Hash>`), `:drops`
|
|
200
|
+
# (`Array<String>`)
|
|
128
201
|
def translation_aggregate(aggregate)
|
|
129
202
|
{
|
|
130
203
|
name: aggregate.name,
|
|
@@ -137,30 +210,27 @@ module Hecks
|
|
|
137
210
|
drops: aggregate.drops.map(&:to_s),
|
|
138
211
|
retypes: aggregate.retypes.map { |retype| { from: retype.from, to: retype.to } },
|
|
139
212
|
computes: aggregate.computes.map { |compute| { from: compute.from, to: compute.to, sql: compute.sql } },
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
# exported). Fixing this CHANGES every existing rekey/
|
|
149
|
-
# backfill edge's digest — any approval already recorded for
|
|
150
|
-
# one is invalidated by this fix and must be re-reviewed.
|
|
213
|
+
# **`rekeys`/`backfills` are digest-relevant too.** Without them, an
|
|
214
|
+
# edge carrying only a rekey (no compute) would bind its approval
|
|
215
|
+
# to nothing rekey-specific: any two rekey edges with otherwise-
|
|
216
|
+
# identical renames/moves/converts/drops/retypes/computes would
|
|
217
|
+
# produce the same digest regardless of what their `rekey sql:`
|
|
218
|
+
# actually said, letting a rekey's own SQL change without
|
|
219
|
+
# invalidating an existing approval. Same reasoning covers
|
|
220
|
+
# `backfills`.
|
|
151
221
|
rekeys: aggregate.rekeys.map { |rekey| { sql: rekey.sql } },
|
|
152
222
|
backfills: aggregate.backfills.map { |backfill| { name: backfill.name.to_s, default: backfill.default } }
|
|
153
223
|
}
|
|
154
224
|
end
|
|
155
225
|
|
|
156
|
-
#
|
|
226
|
+
# The export shape — `translation_aggregate`'s own digest-relevant
|
|
157
227
|
# fields, plus the precompiled SQL (`compiled_state_expression`/
|
|
158
228
|
# `compiled_id_expression`) a consumer embedding this JSON
|
|
159
229
|
# (rust/host's own boot-time mint) needs to execute the edge
|
|
160
|
-
# without compiling SQL itself. The
|
|
230
|
+
# without compiling SQL itself. The same call head_compiler.rb's
|
|
161
231
|
# own `compile_rules(declared)`/`id_case(guard, declared)` make at
|
|
162
232
|
# mint time, run here once at build/export time instead —
|
|
163
|
-
# `Translation::RuleCompiler` is the
|
|
233
|
+
# `Translation::RuleCompiler` is the one place this expression is
|
|
164
234
|
# built, called from both here and from head_compiler.rb's real
|
|
165
235
|
# per-mint assembly, so a consumer gets Ruby's own compiler's
|
|
166
236
|
# output verbatim, never a second, independently-authored SQL
|
|
@@ -176,6 +246,11 @@ module Hecks
|
|
|
176
246
|
# declared-rules shape, just without precompiled SQL to execute —
|
|
177
247
|
# consistent with there being no mint/audit machinery to run it
|
|
178
248
|
# against either.
|
|
249
|
+
# @param aggregate [Bluebook::TranslationAggregate] the aggregate's own
|
|
250
|
+
# translation rules to compile and export
|
|
251
|
+
# @return [Hash{Symbol => Object}] `translation_aggregate`'s own Hash, plus
|
|
252
|
+
# `:compiled_state_expression` (String) and `:compiled_id_expression`
|
|
253
|
+
# (String, nil) when the era persistence plugin is loaded
|
|
179
254
|
def compiled_translation_aggregate(aggregate)
|
|
180
255
|
return translation_aggregate(aggregate) unless Ports::Persistence.plugin?(:era)
|
|
181
256
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Projector
|
|
3
|
-
# The trivial case, on purpose: canonical IR projected
|
|
3
|
+
# The trivial case, on purpose: canonical IR projected as canonical
|
|
4
4
|
# IR. `Bluebook#to_h` already satisfies every §30 acceptance
|
|
5
5
|
# criterion on its own — no live runtime needed to call it, output is
|
|
6
6
|
# deterministic (the same golden fixtures `spec/ir_golden_spec.rb`
|
|
@@ -12,6 +12,12 @@ module Hecks
|
|
|
12
12
|
module IRProjector
|
|
13
13
|
module_function
|
|
14
14
|
|
|
15
|
+
# Projects `bluebook` as its own canonical IR.
|
|
16
|
+
#
|
|
17
|
+
# @param bluebook [Hecks::IR] the IR-emitting construct to project
|
|
18
|
+
# @param options [Hash] unused; accepted to satisfy the registry's call shape
|
|
19
|
+
# @return [Hash] `bluebook`'s canonical IR, as built by `Hecks::IR::Emits#to_h`
|
|
20
|
+
# @raise [Hecks::IR::Undeclared] if `bluebook` never declared its shape with `emits_ir`
|
|
15
21
|
def call(bluebook:, options: {}) = bluebook.to_h
|
|
16
22
|
end
|
|
17
23
|
end
|