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,32 +1,19 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Projections
|
|
3
3
|
module Model
|
|
4
|
-
#
|
|
4
|
+
# How the model's shape differs from the language's, and why.
|
|
5
5
|
#
|
|
6
6
|
# Every construct's `emits_ir` restates what the grammar declares —
|
|
7
7
|
# and the two legitimately differ, in seven ways. These lived only
|
|
8
8
|
# as prose in Ruby comments until spec/model_shape_conformance_spec
|
|
9
|
-
# made them checkable; they live
|
|
9
|
+
# made them checkable; they live here so the generator and the gate
|
|
10
10
|
# read one source rather than two that must agree.
|
|
11
11
|
#
|
|
12
12
|
# A reason is carried beside each entry because the generated file
|
|
13
|
-
# will
|
|
13
|
+
# will carry it: a deviation is exactly the kind of thing whose
|
|
14
14
|
# explanation must survive regeneration, and the only way it can is
|
|
15
15
|
# to be emitted rather than typed into the output.
|
|
16
16
|
module Deviations
|
|
17
|
-
# The grammar is relational — a Command points UP at its
|
|
18
|
-
# Aggregate — where the model composes. An explicit `as:` still
|
|
19
|
-
# keeps its `_id` (Command's own `entity_id`, kept as data); the
|
|
20
|
-
# parent link itself mints bare now (ADR 0025) — `aggregate` or
|
|
21
|
-
# `bluebook`, whichever this category's creating command declares
|
|
22
|
-
# first — so both spellings are checked. Entity spells its own
|
|
23
|
-
# (separate, non-colliding) text twin of the parent link `owner`.
|
|
24
|
-
PARENT_REF = ->(field) { field.to_s.match?(/_id\z/) || %i[owner aggregate bluebook].include?(field) }
|
|
25
|
-
|
|
26
|
-
# The judge's own field, never the model's — contracts.rb already
|
|
27
|
-
# says so with `derived: { position: :walk }`.
|
|
28
|
-
JUDGE_ONLY = %i[position].freeze
|
|
29
|
-
|
|
30
17
|
# What the model holds that the grammar declares elsewhere: the
|
|
31
18
|
# containment edges, stated in syntax.bluebook's Keyword rows as
|
|
32
19
|
# `context` -> `opens`.
|
|
@@ -64,7 +51,7 @@ module Hecks
|
|
|
64
51
|
"Policy" => { where_ast: "the structured form of `where`, derived from the same text at emission" }
|
|
65
52
|
}.freeze
|
|
66
53
|
|
|
67
|
-
#
|
|
54
|
+
# **Declared, and deliberately not emitted**. The category that had no
|
|
68
55
|
# home anywhere before — each of these was a comment and nothing
|
|
69
56
|
# more.
|
|
70
57
|
OFF_THE_WIRE = {
|
|
@@ -85,13 +72,71 @@ module Hecks
|
|
|
85
72
|
|
|
86
73
|
module_function
|
|
87
74
|
|
|
75
|
+
# The grammar is relational — a Command points up at its
|
|
76
|
+
# Aggregate — where the model composes. An explicit `as:` still
|
|
77
|
+
# keeps its `_id` (Command's own `entity_id`, kept as data); the
|
|
78
|
+
# parent link itself mints bare now (ADR 0025) — `aggregate` or
|
|
79
|
+
# `bluebook`, whichever this category's creating command declares
|
|
80
|
+
# first. Entity spells its own (separate, non-colliding) text twin
|
|
81
|
+
# of the parent link `owner`. Not restated here: the one list is
|
|
82
|
+
# `Assembly::PARENT_POINTERS`, which the assembly gate reads too.
|
|
83
|
+
# Tells whether `field` is a parent pointer, minted bare rather than
|
|
84
|
+
# declared like an ordinary field.
|
|
85
|
+
#
|
|
86
|
+
# @param field [String, Symbol] the field name to check
|
|
87
|
+
# @return [Boolean] true if `field` is a parent pointer (`Assembly::PARENT_POINTERS`,
|
|
88
|
+
# or any name ending `_id`)
|
|
89
|
+
def parent_ref?(field) = Hecks::Bluebook::Assembly.parent_pointer?(field)
|
|
90
|
+
|
|
91
|
+
# The judge's own fields, never the model's — read off the
|
|
92
|
+
# category's contract (`derived: { position: :walk }`), not restated.
|
|
93
|
+
#
|
|
94
|
+
# @param name [String] the construct's name, an `Assembly::CONTRACTS` key
|
|
95
|
+
# @return [Array<Symbol>] fields the assembly judge derives by walking, for this construct
|
|
96
|
+
# @raise [KeyError] if `name` has no assembly contract
|
|
97
|
+
def judge_only(name) = Hecks::Bluebook::Assembly.contract(name).walked
|
|
98
|
+
|
|
88
99
|
# The tables that carry a reason answer with names only when the
|
|
89
100
|
# caller wants the set rather than the explanations.
|
|
101
|
+
#
|
|
102
|
+
# @param name [String] the construct's name, an `OFF_THE_WIRE` key
|
|
103
|
+
# @return [Array<Symbol>] fields declared but deliberately not emitted for `name`;
|
|
104
|
+
# empty if `name` has none
|
|
90
105
|
def off_the_wire(name) = OFF_THE_WIRE.fetch(name, {}).keys
|
|
106
|
+
|
|
107
|
+
# Names `name`'s model-only fields.
|
|
108
|
+
#
|
|
109
|
+
# @param name [String] the construct's name, a `COMPUTED` key
|
|
110
|
+
# @return [Array<Symbol>] model-only fields computed rather than declared for
|
|
111
|
+
# `name`; empty if `name` has none
|
|
91
112
|
def computed(name) = COMPUTED.fetch(name, {}).keys
|
|
113
|
+
|
|
114
|
+
# Names `name`'s containment edges.
|
|
115
|
+
#
|
|
116
|
+
# @param name [String] the construct's name, a `CONTAINED` key
|
|
117
|
+
# @return [Array<Symbol>] fields the model holds that the grammar declares as
|
|
118
|
+
# containment edges instead, for `name`; empty if `name` has none
|
|
92
119
|
def contained(name) = CONTAINED.fetch(name, [])
|
|
120
|
+
|
|
121
|
+
# Names `name`'s folded fields and what each gathers.
|
|
122
|
+
#
|
|
123
|
+
# @param name [String] the construct's name, a `FOLDED` key
|
|
124
|
+
# @return [Hash{Symbol => Array<Symbol>}] each folded field for `name`, mapped to
|
|
125
|
+
# the declared fields it gathers; empty if `name` has none
|
|
93
126
|
def folded(name) = FOLDED.fetch(name, {})
|
|
127
|
+
|
|
128
|
+
# Names `name`'s unpacked fields and what each opens into.
|
|
129
|
+
#
|
|
130
|
+
# @param name [String] the construct's name, an `UNPACKED` key
|
|
131
|
+
# @return [Hash{Symbol => Array<Symbol>}] each declared field for `name` that opens
|
|
132
|
+
# into several model fields, mapped to those fields; empty if `name` has none
|
|
94
133
|
def unpacked(name) = UNPACKED.fetch(name, {})
|
|
134
|
+
|
|
135
|
+
# Names `name`'s dynamic-tail fields.
|
|
136
|
+
#
|
|
137
|
+
# @param name [String] the construct's name, a `DYNAMIC_TAIL` key
|
|
138
|
+
# @return [Array<Symbol>] fields for `name` emitted by `to_h`'s own merge rather
|
|
139
|
+
# than by `emits_ir`; empty if `name` has none
|
|
95
140
|
def dynamic_tail(name) = DYNAMIC_TAIL.fetch(name, [])
|
|
96
141
|
end
|
|
97
142
|
end
|
|
@@ -3,22 +3,22 @@ require_relative "../projector"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Projections
|
|
6
|
-
#
|
|
6
|
+
# The model classes, projected from the language that declares them.
|
|
7
7
|
#
|
|
8
|
-
# A construct's
|
|
8
|
+
# A construct's holding half — its readers, its emission, a
|
|
9
9
|
# constructor that assigns declared fields and hands off — restates
|
|
10
10
|
# what `bluebook.bluebook` already says, three times over in Ruby.
|
|
11
11
|
# This renders it instead.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
13
|
+
# **Only the holding half**. `Behaviour::X` is hand-written and permanent,
|
|
14
14
|
# and `settle` is the seam: everything a declaration cannot state
|
|
15
15
|
# lives behind it, so regenerating can never be lossy. That property
|
|
16
16
|
# was established construct by construct before any of this was
|
|
17
17
|
# written — the chapter looked generatable and was not, until its
|
|
18
18
|
# `@hecks_root`, ports table and child stamping moved behind `settle`.
|
|
19
19
|
#
|
|
20
|
-
#
|
|
21
|
-
# it does not state which constructs are Ruby
|
|
20
|
+
# The `HOST` manifest is the honest part. The grammar states the fields;
|
|
21
|
+
# it does not state which constructs are Ruby classes rather than
|
|
22
22
|
# instances, what a constructor's defaults are, or how a value is
|
|
23
23
|
# coerced on the way in. Those are facts about Ruby, not about
|
|
24
24
|
# bluebooks, so they are declared here rather than pretended into the
|
|
@@ -29,7 +29,7 @@ module Hecks
|
|
|
29
29
|
|
|
30
30
|
projects_as :model, declares: "Bluebook", emits: :files
|
|
31
31
|
|
|
32
|
-
#
|
|
32
|
+
# **Per-construct Ruby facts**. `coerce` is the only fiddly column: a
|
|
33
33
|
# declared field arrives as whatever the builder handed over, and
|
|
34
34
|
# each construct has always normalised its own on the way in.
|
|
35
35
|
HOST = {
|
|
@@ -37,21 +37,25 @@ module Hecks
|
|
|
37
37
|
construct: "Policy",
|
|
38
38
|
file: "policy.rb",
|
|
39
39
|
behaviour: "Behaviour::Policy",
|
|
40
|
-
readers: %i[name on_event trigger_command target_domain where for_each with_spec],
|
|
40
|
+
readers: %i[name on_event trigger_command target_domain expect_undelivered where for_each with_spec],
|
|
41
41
|
accessors: %i[aggregate],
|
|
42
42
|
defaults: { name: nil, on_event: "nil", trigger_command: "nil",
|
|
43
|
-
target_domain: "nil", where: "nil", for_each: "nil",
|
|
43
|
+
target_domain: "nil", expect_undelivered: "false", where: "nil", for_each: "nil",
|
|
44
44
|
with_spec: "[]", aggregate: "nil" },
|
|
45
|
-
|
|
46
|
-
#
|
|
45
|
+
# A flag on the wire is a boolean — the language holds it as
|
|
46
|
+
# text ("true"), the builder hands over `true`, and a
|
|
47
|
+
# reconstruction hands back whichever it read; all three land
|
|
48
|
+
# as the same `true`/`false`.
|
|
49
|
+
coerce: { name: ".to_s", aggregate: "&.to_s", expect_undelivered: ".to_s == \"true\"" },
|
|
50
|
+
# A list of bindings is not a scalar on the wire. Every other
|
|
47
51
|
# field emits as itself; this one has to render the way
|
|
48
52
|
# `DispatchSpec`'s own `with_spec` does — keys to strings, and
|
|
49
|
-
# `render_value`
|
|
53
|
+
# `render_value` keeping the leading colon on a Symbol, because
|
|
50
54
|
# a binding that reads an event field and one that supplies a
|
|
51
55
|
# literal string are otherwise indistinguishable once written
|
|
52
56
|
# down (see `MetaValidator::Readings`' own note on exactly that).
|
|
53
57
|
renders: { with_spec: "-> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } }",
|
|
54
|
-
#
|
|
58
|
+
# Computed (Deviations::COMPUTED["Policy"]): the structured
|
|
55
59
|
# form of `where`, a pure function of that text, the same
|
|
56
60
|
# `ast` every rule row carries beside its `canonical`.
|
|
57
61
|
where_ast: "-> { where_ast }" },
|
|
@@ -61,14 +65,30 @@ module Hecks
|
|
|
61
65
|
|
|
62
66
|
module_function
|
|
63
67
|
|
|
68
|
+
# Renders every `HOST`-listed construct's holding half.
|
|
69
|
+
#
|
|
70
|
+
# @param bluebook [Bluebook::Chapter] the "Bluebook" chapter itself (the language
|
|
71
|
+
# describing its own constructs), not a domain being projected
|
|
72
|
+
# @param options [Hash{Symbol => Object}] ignored; present to satisfy the
|
|
73
|
+
# `Projector::Target` calling convention
|
|
74
|
+
# @return [Hash{String => String}] each construct's output filename mapped to its
|
|
75
|
+
# rendered Ruby source
|
|
64
76
|
def call(bluebook:, options: {})
|
|
65
77
|
HOST.to_h { |name, host| [host.fetch(:file), render(bluebook, name, host)] }
|
|
66
78
|
end
|
|
67
79
|
|
|
80
|
+
# Renders one construct's class: emission, readers and constructor, wrapped in
|
|
81
|
+
# the generated-file header and namespace.
|
|
82
|
+
#
|
|
83
|
+
# @param bluebook [Bluebook::Chapter] the "Bluebook" chapter the construct's own
|
|
84
|
+
# attributes are read from
|
|
85
|
+
# @param name [String] the construct's name, a key into `HOST` (`"Policy"`, ...)
|
|
86
|
+
# @param host [Hash{Symbol => Object}] `name`'s `HOST` entry
|
|
87
|
+
# @return [String] the rendered class source, ready to write to `host[:file]`
|
|
68
88
|
def render(bluebook, name, host)
|
|
69
89
|
<<~RUBY
|
|
70
|
-
#
|
|
71
|
-
#
|
|
90
|
+
# Generated — projected from the language's own #{name} aggregate.
|
|
91
|
+
# Do not edit: the holding half is rendered, and #{host.fetch(:behaviour)}
|
|
72
92
|
# is where anything hand-written belongs.
|
|
73
93
|
require_relative "behaviour/#{File.basename(host.fetch(:file), '.rb')}"
|
|
74
94
|
|
|
@@ -91,9 +111,14 @@ module Hecks
|
|
|
91
111
|
|
|
92
112
|
# The emission, keyed as the model spells it and sourced as the
|
|
93
113
|
# language declares it.
|
|
94
|
-
# A
|
|
114
|
+
# A computed field (Deviations::COMPUTED) is emitted too — it is
|
|
95
115
|
# model-only by definition, so it rides after the declared fields
|
|
96
|
-
# and
|
|
116
|
+
# and must have a `renders` entry, there being nothing to `send`.
|
|
117
|
+
#
|
|
118
|
+
# @param bluebook [Bluebook::Chapter] the "Bluebook" chapter the construct's own
|
|
119
|
+
# attributes are read from
|
|
120
|
+
# @param name [String] the construct's name, a key into `HOST`
|
|
121
|
+
# @return [String] the rendered `emits_ir(...)` call, one field per line
|
|
97
122
|
def emits(bluebook, name)
|
|
98
123
|
fields = emitted_fields(bluebook, name) + Deviations.computed(name)
|
|
99
124
|
renders = HOST.fetch(name).fetch(:renders, {})
|
|
@@ -102,37 +127,54 @@ module Hecks
|
|
|
102
127
|
"emits_ir(\n#{fields.map { |f| " #{"#{f}:".ljust(width + 1)} #{renders.fetch(f, ":#{f}")}" }.join(",\n")}\n)"
|
|
103
128
|
end
|
|
104
129
|
|
|
105
|
-
#
|
|
130
|
+
# What the construct emits: what the language declares, less every
|
|
106
131
|
# deviation the tables account for. The generator and
|
|
107
132
|
# spec/model_shape_conformance_spec compute this the same way, from
|
|
108
133
|
# the same tables, which is the point of the tables being in lib.
|
|
134
|
+
#
|
|
135
|
+
# @param bluebook [Bluebook::Chapter] the "Bluebook" chapter the construct's own
|
|
136
|
+
# attributes are read from
|
|
137
|
+
# @param name [String] the construct's name, an aggregate on `bluebook`
|
|
138
|
+
# @return [Array<Symbol>] the declared attribute names, minus every field
|
|
139
|
+
# `Deviations` marks as parent-ref, judge-only, off-the-wire, dynamic-tail,
|
|
140
|
+
# folded, or unpacked
|
|
109
141
|
def emitted_fields(bluebook, name)
|
|
110
142
|
bluebook.aggregate(name).attributes.map(&:name)
|
|
111
|
-
.reject { |f| Deviations
|
|
112
|
-
Deviations
|
|
143
|
+
.reject { |f| Deviations.parent_ref?(f) } -
|
|
144
|
+
Deviations.judge_only(name) -
|
|
113
145
|
Deviations.off_the_wire(name) -
|
|
114
146
|
Deviations.dynamic_tail(name) -
|
|
115
147
|
Deviations.folded(name).values.flatten -
|
|
116
148
|
Deviations.unpacked(name).keys
|
|
117
149
|
end
|
|
118
150
|
|
|
151
|
+
# Renders the `attr_reader` line for every emitted field, plus an `attr_accessor`
|
|
152
|
+
# for each field the model deliberately keeps off the wire.
|
|
153
|
+
#
|
|
154
|
+
# @param host [Hash{Symbol => Object}] the construct's `HOST` entry
|
|
155
|
+
# @return [String] the rendered reader/accessor lines, one construct's worth
|
|
119
156
|
def readers(host)
|
|
120
157
|
lines = ["attr_reader #{host.fetch(:readers).map { |r| ":#{r}" }.join(', ')}"]
|
|
121
158
|
accessors = host.fetch(:accessors, [])
|
|
122
159
|
return lines.join("\n") if accessors.empty?
|
|
123
160
|
|
|
124
161
|
# A declared field the model deliberately does not emit still
|
|
125
|
-
# needs a reader, and the
|
|
162
|
+
# needs a reader, and the reason it is off the wire is carried
|
|
126
163
|
# here rather than typed in — a comment that survives
|
|
127
164
|
# regeneration is one the generator writes.
|
|
128
165
|
reasons = Deviations::OFF_THE_WIRE.fetch(host.fetch(:construct, ""), {})
|
|
129
166
|
(lines + accessors.map do |a|
|
|
130
167
|
why = reasons[a]
|
|
131
|
-
(why ? "\n# #{a.
|
|
168
|
+
(why ? "\n# #{a.to_s.capitalize}, declared and deliberately off the wire\n# #{wrap(why)}\n" : "") +
|
|
132
169
|
"attr_accessor :#{a}"
|
|
133
170
|
end).join("\n")
|
|
134
171
|
end
|
|
135
172
|
|
|
173
|
+
# Renders the `initialize` that assigns every declared field, coerced as `HOST`
|
|
174
|
+
# states, and calls `settle` unless the construct opts out.
|
|
175
|
+
#
|
|
176
|
+
# @param host [Hash{Symbol => Object}] the construct's `HOST` entry
|
|
177
|
+
# @return [String] the rendered `def initialize ... end` block
|
|
136
178
|
def constructor(host)
|
|
137
179
|
args = host.fetch(:defaults).map { |f, d| d ? "#{f}: #{d}" : "#{f}:" }.join(", ")
|
|
138
180
|
body = host.fetch(:defaults).keys.map do |f|
|
|
@@ -143,7 +185,19 @@ module Hecks
|
|
|
143
185
|
"def initialize(#{args})\n#{body.join("\n")}\nend"
|
|
144
186
|
end
|
|
145
187
|
|
|
188
|
+
# Indents every non-blank line of a rendered block, for nesting it inside the
|
|
189
|
+
# class body.
|
|
190
|
+
#
|
|
191
|
+
# @param text [String] the block to indent
|
|
192
|
+
# @param by [Integer] how many spaces to prefix each non-blank line with
|
|
193
|
+
# @return [String] the indented text
|
|
146
194
|
def indent(text, by) = text.lines.map { |l| l.strip.empty? ? l : (" " * by) + l }.join
|
|
195
|
+
|
|
196
|
+
# Wraps prose into `# `-prefixed comment lines, for a reader-declared reason
|
|
197
|
+
# rendered back into the generated file.
|
|
198
|
+
#
|
|
199
|
+
# @param text [String] the prose to wrap
|
|
200
|
+
# @return [String] the wrapped text, its lines joined by `"\n# "`
|
|
147
201
|
def wrap(text) = text.scan(/.{1,62}(?:\s|$)/).map(&:strip).join("\n# ")
|
|
148
202
|
end
|
|
149
203
|
end
|
|
@@ -3,16 +3,17 @@ require_relative "../projector"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Projections
|
|
6
|
-
#
|
|
6
|
+
# An OIDC client/scope manifest derived from a domain's own IR: what
|
|
7
7
|
# an identity provider has to know about this domain before it can
|
|
8
8
|
# issue a token that means anything here.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
10
|
+
# ## The artifact half of something already half-built
|
|
11
|
+
#
|
|
12
|
+
# `spec/oidc_projection_spec.rb` covers the integration half — verified
|
|
12
13
|
# claims in, `IdentityResolution.resolve` → `Authorization.holds_role?`
|
|
13
14
|
# → a dispatch scoped by `Hecks.as_caller(role:)`. That half enforces
|
|
14
15
|
# a role per command. It just had no way to say, up front and as data,
|
|
15
|
-
#
|
|
16
|
+
# which role each command wants — every answer came from asking the
|
|
16
17
|
# live runtime one dispatch at a time.
|
|
17
18
|
#
|
|
18
19
|
# This is that catalogue, and the two are checked against each other
|
|
@@ -24,12 +25,13 @@ module Hecks
|
|
|
24
25
|
# `Ports::Authorization.holds_role?` compares against a real
|
|
25
26
|
# `Governance::RoleAssignment`.
|
|
26
27
|
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
28
|
+
# ## Roles come from the commands, not from governance
|
|
29
|
+
#
|
|
30
|
+
# A command's own `role "Compliance officer"` is in the bluebook IR
|
|
29
31
|
# (`Command#role`), whereas `uses_framework "Governance"` is
|
|
30
32
|
# declared in the `.hecksagon` — which `call(bluebook:, options:)`
|
|
31
33
|
# cannot see at all. Reading the commands is both the only thing
|
|
32
|
-
# available here
|
|
34
|
+
# available here and the more accurate source: it says what each
|
|
33
35
|
# command actually demands, not merely which role vocabulary the
|
|
34
36
|
# application happened to mount.
|
|
35
37
|
#
|
|
@@ -47,6 +49,13 @@ module Hecks
|
|
|
47
49
|
# `audience:` overrides the domain name, for the ordinary case
|
|
48
50
|
# where the IdP's registered audience is a URL rather than a bare
|
|
49
51
|
# chapter name.
|
|
52
|
+
#
|
|
53
|
+
# @param bluebook [Bluebook::Chapter] the chapter being projected
|
|
54
|
+
# @param options [Hash{Symbol => Object}] `:audience` (String, Symbol, nil)
|
|
55
|
+
# overrides the manifest's `"audience"` value
|
|
56
|
+
# @return [Hash{String => Object}] `"audience"` (String), `"scopes"` (see
|
|
57
|
+
# `scopes_for`) and `"roles"` (`Array<String>`, every distinct declared role,
|
|
58
|
+
# sorted)
|
|
50
59
|
def call(bluebook:, options: {})
|
|
51
60
|
scopes = scopes_for(bluebook)
|
|
52
61
|
|
|
@@ -67,11 +76,22 @@ module Hecks
|
|
|
67
76
|
# through `Dispatcher#dispatch`'s dotted `Entity.Command` routing
|
|
68
77
|
# is real and callable, so a manifest that never names it can
|
|
69
78
|
# never grant a client a scope for it either.
|
|
79
|
+
#
|
|
80
|
+
# @param bluebook [Bluebook::Chapter] the chapter being projected
|
|
81
|
+
# @return [Array<Hash{String => Object}>] every command's scope entry (see
|
|
82
|
+
# `command_scopes`), sorted by `"scope"`
|
|
70
83
|
def scopes_for(bluebook)
|
|
71
84
|
bluebook.aggregates.flat_map { |aggregate| aggregate_scopes(bluebook, aggregate) }
|
|
72
85
|
.sort_by { |scope| scope["scope"] }
|
|
73
86
|
end
|
|
74
87
|
|
|
88
|
+
# Projects one aggregate's own commands and every entity nested inside it.
|
|
89
|
+
#
|
|
90
|
+
# @param bluebook [Bluebook::Chapter] the aggregate's owning chapter, for the
|
|
91
|
+
# verb and scope prefixes
|
|
92
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate being projected
|
|
93
|
+
# @return [Array<Hash{String => Object}>] the aggregate's and its entities'
|
|
94
|
+
# scope entries (see `command_scopes`)
|
|
75
95
|
def aggregate_scopes(bluebook, aggregate)
|
|
76
96
|
verb_prefix = "#{bluebook.name}::#{aggregate.hecks_name}"
|
|
77
97
|
scope_prefix = "#{Naming.snake(bluebook.name)}:#{Naming.snake(aggregate.hecks_name)}"
|
|
@@ -84,6 +104,14 @@ module Hecks
|
|
|
84
104
|
# inside `Handler`), so this recurses the same way `Chapter#verbs`
|
|
85
105
|
# now does. The verb and scope prefixes grow in lockstep, each
|
|
86
106
|
# `.`-joined the same way its own kind already was.
|
|
107
|
+
#
|
|
108
|
+
# @param entity [Bluebook::Entity] the entity being projected
|
|
109
|
+
# @param verb_prefix [String] the enclosing aggregate or entity's own verb
|
|
110
|
+
# prefix, extended with this entity's name
|
|
111
|
+
# @param scope_prefix [String] the enclosing aggregate or entity's own scope
|
|
112
|
+
# prefix, extended with this entity's snake-cased name
|
|
113
|
+
# @return [Array<Hash{String => Object}>] this entity's and its nested entities'
|
|
114
|
+
# scope entries (see `command_scopes`)
|
|
87
115
|
def entity_scopes(entity, verb_prefix, scope_prefix)
|
|
88
116
|
verb_prefix = "#{verb_prefix}.#{entity.hecks_name}"
|
|
89
117
|
scope_prefix = "#{scope_prefix}.#{Naming.snake(entity.hecks_name)}"
|
|
@@ -93,9 +121,18 @@ module Hecks
|
|
|
93
121
|
end
|
|
94
122
|
|
|
95
123
|
# `banking:account.open` — the shape an OIDC scope is conventionally
|
|
96
|
-
# spelled in, and snake_cased through the
|
|
124
|
+
# spelled in, and snake_cased through the same `Naming.snake` the
|
|
97
125
|
# facade uses to name a command's own door method, so a scope and
|
|
98
126
|
# the Ruby call that satisfies it cannot drift apart.
|
|
127
|
+
#
|
|
128
|
+
# @param commands [Array<Bluebook::Command>] the commands to project
|
|
129
|
+
# @param verb_prefix [String] the owning aggregate or entity's fully-qualified
|
|
130
|
+
# verb prefix
|
|
131
|
+
# @param scope_prefix [String] the owning aggregate or entity's snake-cased
|
|
132
|
+
# scope prefix
|
|
133
|
+
# @return [Array<Hash{String => Object}>] one entry per command: `"scope"`
|
|
134
|
+
# (String), `"verb"` (String), `"role"` (String, `nil` if the command
|
|
135
|
+
# declares none)
|
|
99
136
|
def command_scopes(commands, verb_prefix, scope_prefix)
|
|
100
137
|
commands.map do |command|
|
|
101
138
|
{
|
|
@@ -2,12 +2,12 @@ require_relative "../projector"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Projections
|
|
5
|
-
#
|
|
6
|
-
# Syntax aggregate — "the parser's grammar knowledge is
|
|
5
|
+
# The Rust parser's keyword table, projected from the chapter's own
|
|
6
|
+
# Syntax aggregate — "the parser's grammar knowledge is derived from
|
|
7
7
|
# hecks's self-description, not hand-typed a second time", which
|
|
8
8
|
# is the anti-drift idea the whole Rust-parser plan rests on.
|
|
9
9
|
#
|
|
10
|
-
# A
|
|
10
|
+
# A registered target now, rather than a module living inside its own
|
|
11
11
|
# bin/ script. It was already a projection in everything but call
|
|
12
12
|
# shape; this only stops it being a fifth way of spelling one.
|
|
13
13
|
module ParserTable
|
|
@@ -16,25 +16,47 @@ module Hecks
|
|
|
16
16
|
|
|
17
17
|
module_function
|
|
18
18
|
|
|
19
|
+
# Projects the Rust parser's keyword and closed-set tables for one chapter.
|
|
20
|
+
#
|
|
21
|
+
# @param bluebook [Bluebook::Chapter] the chapter to project; the language's own
|
|
22
|
+
# for the ordinary `bin/project_parser_table` run
|
|
23
|
+
# @param options [Hash{Symbol => Object}] ignored; present to satisfy the
|
|
24
|
+
# `Projector::Target` calling convention
|
|
25
|
+
# @return [String] the rendered Rust source
|
|
19
26
|
def call(bluebook:, options: {}) = render(bluebook)
|
|
20
27
|
|
|
21
28
|
module_function
|
|
22
29
|
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
30
|
+
# The chapter is handed over, not reached for — reaching for the grammar
|
|
31
|
+
# registry directly would pin the projection to one chapter, the language's
|
|
32
|
+
# own, and the projector protocol already hands over the chapter as an
|
|
33
|
+
# argument, at no cost.
|
|
34
|
+
#
|
|
35
|
+
# @param bluebook [Bluebook::Chapter] the chapter to read the Syntax aggregate
|
|
36
|
+
# from
|
|
37
|
+
# @return [Bluebook::Aggregate, nil] the chapter's `"Syntax"` aggregate, `nil` if
|
|
38
|
+
# it declares none
|
|
27
39
|
def syntax(bluebook) = bluebook.aggregate("Syntax")
|
|
28
40
|
|
|
29
41
|
# Every cell as text — exactly spec/syntax_conformance_spec.rb's own
|
|
30
42
|
# `rows` helper, reused rather than re-derived: a member's fields decode
|
|
31
43
|
# back through typed literal decoding on the way out of reconstruction,
|
|
32
44
|
# and this reads it back as what was written.
|
|
45
|
+
#
|
|
46
|
+
# @param bluebook [Bluebook::Chapter] the chapter to read from
|
|
47
|
+
# @param name [String] the closed-set value object's name, such as `"Context"`
|
|
48
|
+
# @return [Array<Hash{Symbol => String}>] one row per declared member, every
|
|
49
|
+
# field stringified
|
|
33
50
|
def rows(bluebook, name)
|
|
34
51
|
syntax(bluebook).value_objects.find { |vo| vo.hecks_name == name }
|
|
35
52
|
.members.map { |row| row.to_h.transform_values(&:to_s) }
|
|
36
53
|
end
|
|
37
54
|
|
|
55
|
+
# Reads one closed set's member names, for a Rust `&[&str]` constant.
|
|
56
|
+
#
|
|
57
|
+
# @param bluebook [Bluebook::Chapter] the chapter to read from
|
|
58
|
+
# @param name [String] the closed-set value object's name
|
|
59
|
+
# @return [Array<String>] every declared member's `:name` field
|
|
38
60
|
def closed_set_members(bluebook, name) = rows(bluebook, name).map { |row| row[:name] }
|
|
39
61
|
|
|
40
62
|
KEYWORD_FIELDS = %i[word context body inner opens fills status was resolves_via disambiguator].freeze
|
|
@@ -45,18 +67,34 @@ module Hecks
|
|
|
45
67
|
# plain ASCII (a word, a context name, a digit, "true"/"false"), so this
|
|
46
68
|
# only has to be safe against the two characters Rust string literals
|
|
47
69
|
# themselves reserve.
|
|
70
|
+
#
|
|
71
|
+
# @param value [Object] the field value to render; stringified before escaping
|
|
72
|
+
# @return [String] a quoted Rust string literal
|
|
48
73
|
def rust_string(value) = "\"#{value.to_s.gsub('\\', '\\\\\\\\').gsub('"', '\\"')}\""
|
|
49
74
|
|
|
75
|
+
# Renders one `KEYWORDS` table entry.
|
|
76
|
+
#
|
|
77
|
+
# @param row [Hash{Symbol => String}] one keyword row, keyed by `KEYWORD_FIELDS`
|
|
78
|
+
# @return [String] one `KeywordRow { ... },` line
|
|
50
79
|
def keyword_row(row)
|
|
51
80
|
fields = KEYWORD_FIELDS.map { |field| rust_string(row[field]) }
|
|
52
81
|
" KeywordRow { #{KEYWORD_FIELDS.zip(fields).map { |name, value| "#{name}: #{value}" }.join(', ')} },"
|
|
53
82
|
end
|
|
54
83
|
|
|
84
|
+
# Renders one `ARGUMENTS` table entry.
|
|
85
|
+
#
|
|
86
|
+
# @param row [Hash{Symbol => String}] one argument row, keyed by `ARGUMENT_FIELDS`
|
|
87
|
+
# @return [String] one `ArgumentRow { ... },` line
|
|
55
88
|
def argument_row(row)
|
|
56
89
|
fields = ARGUMENT_FIELDS.map { |field| rust_string(row[field]) }
|
|
57
90
|
" ArgumentRow { #{ARGUMENT_FIELDS.zip(fields).map { |name, value| "#{name}: #{value}" }.join(', ')} },"
|
|
58
91
|
end
|
|
59
92
|
|
|
93
|
+
# Renders one `pub static NAME: &[&str]` constant.
|
|
94
|
+
#
|
|
95
|
+
# @param name [String] the Rust constant's name
|
|
96
|
+
# @param values [Array<String>] the constant's member strings
|
|
97
|
+
# @return [String] the rendered `pub static` declaration
|
|
60
98
|
def const_str_array(name, values)
|
|
61
99
|
lines = values.map { |value| " #{rust_string(value)}," }
|
|
62
100
|
"pub static #{name}: &[&str] = &[\n#{lines.join("\n")}\n];\n"
|
|
@@ -64,12 +102,15 @@ module Hecks
|
|
|
64
102
|
|
|
65
103
|
# S14, ADR 0026 — Keyword/Argument are genuine entities of Syntax
|
|
66
104
|
# now, dispatched (not merely declared) so their own `status`
|
|
67
|
-
# really is a lifecycle. `SyntaxBoot.call` reads the
|
|
105
|
+
# really is a lifecycle. `SyntaxBoot.call` reads the still-static
|
|
68
106
|
# seed rows (`KeywordSeed`/`ArgumentSeed`), dispatches each one
|
|
69
|
-
# through the real admission/lifecycle door, and hands back
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
107
|
+
# through the real admission/lifecycle door, and hands back rows in
|
|
108
|
+
# the same shape `rows` reads for every other closed set here —
|
|
109
|
+
# symbol keys, string values, `status` included — so nothing else
|
|
110
|
+
# in this file needs to change.
|
|
111
|
+
#
|
|
112
|
+
# @param bluebook [Bluebook::Chapter] the chapter to project
|
|
113
|
+
# @return [String] the rendered `KEYWORDS`/`ARGUMENTS`/closed-set Rust source
|
|
73
114
|
def render(bluebook)
|
|
74
115
|
table = Hecks::Bluebook::MetaValidator::SyntaxBoot.call
|
|
75
116
|
keywords = table[:keywords]
|
|
@@ -3,18 +3,18 @@ require_relative "../projector"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Projections
|
|
6
|
-
#
|
|
6
|
+
# The DSL reference pages, projected from the chapter's own Syntax
|
|
7
7
|
# aggregate — the tables come from the declaration, the prose is
|
|
8
8
|
# preserved from whatever is already committed.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
10
|
+
# The first projection that emits a tree. `emits: :files` says so,
|
|
11
11
|
# and the framework writes the map rather than inferring one: a Hash
|
|
12
12
|
# of path => contents and a Hash that merely holds strings are the
|
|
13
13
|
# same object to Ruby, so only the projection can know which it
|
|
14
14
|
# meant. This is the case the output contract was designed for and
|
|
15
15
|
# deliberately left unimplemented until something real needed it.
|
|
16
16
|
#
|
|
17
|
-
#
|
|
17
|
+
# Not pure, and it cannot be. `Doc::Reference.pages` reads the
|
|
18
18
|
# committed pages to harvest their prose, so the existing directory
|
|
19
19
|
# is an input — passed as `from:` rather than assumed, so the
|
|
20
20
|
# projection never reaches for a path of its own choosing.
|
|
@@ -25,6 +25,18 @@ module Hecks
|
|
|
25
25
|
|
|
26
26
|
module_function
|
|
27
27
|
|
|
28
|
+
# Renders one reference page per DSL keyword context, carrying prose
|
|
29
|
+
# over from whatever is already committed under `options[:from]`.
|
|
30
|
+
#
|
|
31
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
|
|
32
|
+
# Syntax aggregate to render pages from; unused beyond admission, since the
|
|
33
|
+
# keyword table this reads comes from the global `Syntax` grammar
|
|
34
|
+
# @param options [Hash] must include `:from`
|
|
35
|
+
# @option options [String] :from directory holding the already-committed
|
|
36
|
+
# reference pages, read to harvest their prose
|
|
37
|
+
# @return [Hash{String => String}] each page's filename => its rendered
|
|
38
|
+
# Markdown, including `"index.md"`
|
|
39
|
+
# @raise [ArgumentError] if `options[:from]` is missing or falsy
|
|
28
40
|
def call(bluebook:, options: {})
|
|
29
41
|
from = options[:from] or
|
|
30
42
|
raise ArgumentError,
|