hecks 0.2.0 → 0.3.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 +5 -5
- data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
- data/lib/hecks/adapters/driven/claude_code.rb +127 -0
- data/lib/hecks/adapters/driven/d1.adapter +12 -0
- data/lib/hecks/adapters/driven/d1.rb +403 -0
- data/lib/hecks/adapters/driven/folder.adapter +3 -0
- data/lib/hecks/adapters/driven/folder.rb +199 -0
- data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
- data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
- data/lib/hecks/adapters/driven/governance_authorization.rb +62 -0
- data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
- data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
- data/lib/hecks/adapters/driven/heki.adapter +4 -0
- data/lib/hecks/adapters/driven/heki.rb +148 -0
- data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
- data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
- data/lib/hecks/adapters/driven/lambda.adapter +4 -0
- data/lib/hecks/adapters/driven/lambda.rb +131 -0
- data/lib/hecks/adapters/driven/memory.adapter +3 -0
- data/lib/hecks/adapters/driven/memory.rb +103 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
- data/lib/hecks/adapters/driven/postgres/codec.rb +80 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
- data/lib/hecks/adapters/driven/postgres.adapter +5 -0
- data/lib/hecks/adapters/driven/postgres.rb +391 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
- data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
- data/lib/hecks/adapters/driven/prism.adapter +3 -0
- data/lib/hecks/adapters/driven/prism.rb +80 -0
- data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
- data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
- data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
- data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
- data/lib/hecks/adapters/driven/sqlite.rb +285 -0
- data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +14 -0
- data/lib/hecks/adapters/driven.rb +36 -0
- data/lib/hecks/adapters.rb +6 -0
- data/lib/hecks/behaviors/dsl.rb +96 -0
- data/lib/hecks/behaviors/expectations.rb +280 -0
- data/lib/hecks/behaviors/ir.rb +31 -0
- data/lib/hecks/behaviors/rspec.rb +42 -0
- data/lib/hecks/behaviors/runner.rb +96 -0
- data/lib/hecks/behaviors.rb +25 -0
- data/lib/hecks/bluebook/aggregate.rb +108 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
- data/lib/hecks/bluebook/assembly/build.rb +48 -0
- data/lib/hecks/bluebook/assembly/contract.rb +112 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
- data/lib/hecks/bluebook/assembly/marks.rb +227 -0
- data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
- data/lib/hecks/bluebook/assembly.rb +78 -0
- data/lib/hecks/bluebook/attribute.rb +96 -0
- data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
- data/lib/hecks/bluebook/behaviour/command.rb +116 -0
- data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
- data/lib/hecks/bluebook/behaviour/query.rb +10 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
- data/lib/hecks/bluebook/chapter.rb +68 -0
- data/lib/hecks/bluebook/command.rb +124 -0
- data/lib/hecks/bluebook/domain_port.rb +102 -0
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
- data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
- data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
- data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
- data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
- data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
- data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
- data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
- data/lib/hecks/bluebook/dsl.rb +45 -0
- data/lib/hecks/bluebook/entity.rb +103 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
- data/lib/hecks/bluebook/expression/projection.json +170 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
- data/lib/hecks/bluebook/expression/resolver.rb +630 -0
- data/lib/hecks/bluebook/expression.rb +13 -0
- data/lib/hecks/bluebook/hexagon.rb +60 -0
- data/lib/hecks/bluebook/lifecycle.rb +42 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
- data/lib/hecks/bluebook/meta_validator.rb +452 -0
- data/lib/hecks/bluebook/model_check.rb +318 -0
- data/lib/hecks/bluebook/pattern_subset.rb +120 -0
- data/lib/hecks/bluebook/policy.rb +41 -0
- data/lib/hecks/bluebook/process_manager.rb +86 -0
- data/lib/hecks/bluebook/project_discovery.rb +30 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +82 -0
- data/lib/hecks/bluebook/query.rb +61 -0
- data/lib/hecks/bluebook/read_model.rb +109 -0
- data/lib/hecks/bluebook/reference.rb +74 -0
- data/lib/hecks/bluebook/smoke_test.rb +166 -0
- data/lib/hecks/bluebook/synthesizer.rb +95 -0
- data/lib/hecks/bluebook/translation.rb +92 -0
- data/lib/hecks/bluebook/value_object.rb +58 -0
- data/lib/hecks/bluebook.rb +74 -0
- data/lib/hecks/codemod.rb +342 -0
- data/lib/hecks/construct.rb +71 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
- data/lib/hecks/deploy/oidc.json +18 -0
- data/lib/hecks/doc/reference.rb +389 -0
- data/lib/hecks/embryonaut_bluebook.rb +75 -0
- data/lib/hecks/facade/cli_door.rb +116 -0
- data/lib/hecks/facade/cli_runner.rb +190 -0
- data/lib/hecks/facade/command_request.rb +105 -0
- data/lib/hecks/facade/handle.rb +173 -0
- data/lib/hecks/facade/json_door.rb +154 -0
- data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
- data/lib/hecks/facade/surface/chapter.rb +107 -0
- data/lib/hecks/facade/surface.rb +48 -0
- data/lib/hecks/facade.rb +44 -0
- data/lib/hecks/forms/app.rb +300 -0
- data/lib/hecks/forms/command_form_renderer.rb +113 -0
- data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
- data/lib/hecks/forms/field_renderer.rb +170 -0
- data/lib/hecks/forms/field_shape.rb +232 -0
- data/lib/hecks/forms/html.rb +53 -0
- data/lib/hecks/forms/index_renderer.rb +35 -0
- data/lib/hecks/forms/page.rb +157 -0
- data/lib/hecks/forms/params.rb +132 -0
- data/lib/hecks/forms/query_form_renderer.rb +114 -0
- data/lib/hecks/forms/record_renderer.rb +115 -0
- data/lib/hecks/forms/record_table.rb +63 -0
- data/lib/hecks/forms/reference_options.rb +30 -0
- data/lib/hecks/forms/value_object_shape.rb +46 -0
- data/lib/hecks/forms.rb +54 -0
- data/lib/hecks/fqn.rb +94 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
- data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
- data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
- data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
- data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
- data/lib/hecks/framework/oidc.json +39 -0
- data/lib/hecks/framework.rb +90 -0
- data/lib/hecks/freezer.rb +67 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
- data/lib/hecks/fuzzing/properties.rb +1146 -0
- data/lib/hecks/fuzzing/replay.rb +635 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
- data/lib/hecks/fuzzing/value_generator.rb +177 -0
- data/lib/hecks/fuzzing.rb +12 -0
- data/lib/hecks/grammar/evolve.rb +291 -0
- data/lib/hecks/grammar/expression.bluebook +417 -0
- data/lib/hecks/grammar/expression_operators.json +1152 -0
- data/lib/hecks/grammar/grammar.hecksagon +20 -0
- data/lib/hecks/grammar/oidc.json +69 -0
- data/lib/hecks/grammar/translation.bluebook +203 -0
- data/lib/hecks/grammar.rb +178 -0
- data/lib/hecks/ir.rb +126 -0
- data/lib/hecks/language/adapter.bluebook +116 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
- data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
- data/lib/hecks/language/bluebook/command.bluebook +437 -0
- data/lib/hecks/language/bluebook/entity.bluebook +316 -0
- data/lib/hecks/language/bluebook/policy.bluebook +172 -0
- data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
- data/lib/hecks/language/bluebook/projection.bluebook +267 -0
- data/lib/hecks/language/bluebook/query.bluebook +245 -0
- data/lib/hecks/language/bluebook/shape.bluebook +292 -0
- data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
- data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
- data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
- data/lib/hecks/language/oidc.json +313 -0
- data/lib/hecks/language/port.bluebook +92 -0
- data/lib/hecks/language/translation/translation.bluebook +110 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
- data/lib/hecks/language/world/wiring.bluebook +62 -0
- data/lib/hecks/language/world/world.bluebook +84 -0
- data/lib/hecks/literal.rb +125 -0
- data/lib/hecks/naming.rb +121 -0
- data/lib/hecks/ports/access_control.port +4 -0
- data/lib/hecks/ports/access_control.rb +62 -0
- data/lib/hecks/ports/agent/answers.rb +104 -0
- data/lib/hecks/ports/agent.port +4 -0
- data/lib/hecks/ports/agent.rb +167 -0
- data/lib/hecks/ports/authentication.port +4 -0
- data/lib/hecks/ports/authentication.rb +50 -0
- data/lib/hecks/ports/authorization.port +4 -0
- data/lib/hecks/ports/authorization.rb +59 -0
- data/lib/hecks/ports/clock.port +4 -0
- data/lib/hecks/ports/clock.rb +62 -0
- data/lib/hecks/ports/extraction.port +4 -0
- data/lib/hecks/ports/extraction.rb +37 -0
- data/lib/hecks/ports/identity_assignment.port +4 -0
- data/lib/hecks/ports/identity_assignment.rb +45 -0
- data/lib/hecks/ports/identity_generation.port +4 -0
- data/lib/hecks/ports/identity_generation.rb +49 -0
- data/lib/hecks/ports/identity_resolution.port +4 -0
- data/lib/hecks/ports/identity_resolution.rb +40 -0
- data/lib/hecks/ports/loading.port +4 -0
- data/lib/hecks/ports/loading.rb +13 -0
- data/lib/hecks/ports/persistence/append_only.rb +102 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
- data/lib/hecks/ports/persistence/execution.rb +18 -0
- data/lib/hecks/ports/persistence/lineage.rb +277 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
- data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
- data/lib/hecks/ports/persistence.port +4 -0
- data/lib/hecks/ports/persistence.rb +39 -0
- data/lib/hecks/ports/projection.port +4 -0
- data/lib/hecks/ports/projection.rb +76 -0
- data/lib/hecks/ports/query/in_memory.rb +60 -0
- data/lib/hecks/ports/query/ordering.rb +41 -0
- data/lib/hecks/ports/query.rb +36 -0
- data/lib/hecks/ports.rb +27 -0
- data/lib/hecks/projections/diagrams.rb +413 -0
- data/lib/hecks/projections/ir.rb +18 -0
- data/lib/hecks/projections/model/deviations.rb +98 -0
- data/lib/hecks/projections/model.rb +145 -0
- data/lib/hecks/projections/oidc.rb +110 -0
- data/lib/hecks/projections/parser_table.rb +159 -0
- data/lib/hecks/projections/reference.rb +38 -0
- data/lib/hecks/projections/shape.rb +31 -0
- data/lib/hecks/projections/statements.rb +110 -0
- data/lib/hecks/projections/vocabulary.rb +100 -0
- data/lib/hecks/projections.rb +32 -0
- data/lib/hecks/projector/cli_projector.rb +437 -0
- data/lib/hecks/projector/docs_projector.rb +321 -0
- data/lib/hecks/projector/exporter.rb +141 -0
- data/lib/hecks/projector/ir_projector.rb +18 -0
- data/lib/hecks/projector/narrate_projector.rb +243 -0
- data/lib/hecks/projector/target.rb +97 -0
- data/lib/hecks/projector.rb +186 -0
- data/lib/hecks/query_ir.rb +392 -0
- data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
- data/lib/hecks/query_specification/common/comparators.rb +25 -0
- data/lib/hecks/query_specification/common/comparison.rb +174 -0
- data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
- data/lib/hecks/query_specification/common/dsl.rb +58 -0
- data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
- data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
- data/lib/hecks/query_specification/common/null_policy.rb +80 -0
- data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
- data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
- data/lib/hecks/query_specification/common/options.rb +32 -0
- data/lib/hecks/query_specification/common/order_by.rb +9 -0
- data/lib/hecks/query_specification/common/specification.rb +10 -0
- data/lib/hecks/query_specification/common/where_clause.rb +9 -0
- data/lib/hecks/query_specification/field_path.rb +89 -0
- data/lib/hecks/query_specification/hop_path.rb +130 -0
- data/lib/hecks/query_specification/read_model/specification.rb +18 -0
- data/lib/hecks/query_specification.rb +14 -0
- data/lib/hecks/rendering.rb +48 -0
- data/lib/hecks/router/namespace_installer.rb +157 -0
- data/lib/hecks/router.rb +70 -0
- data/lib/hecks/runtime/caller.rb +50 -0
- data/lib/hecks/runtime/capability_graph.rb +44 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
- data/lib/hecks/runtime/command_interpreter.rb +413 -0
- data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
- data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
- data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
- data/lib/hecks/runtime/command_rules/emission.rb +34 -0
- data/lib/hecks/runtime/command_rules/references.rb +181 -0
- data/lib/hecks/runtime/command_rules.rb +28 -0
- data/lib/hecks/runtime/dependency_planning.rb +231 -0
- data/lib/hecks/runtime/dispatcher.rb +286 -0
- data/lib/hecks/runtime/entity_element.rb +253 -0
- data/lib/hecks/runtime/entity_interpreter.rb +228 -0
- data/lib/hecks/runtime/era_check.rb +136 -0
- data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
- data/lib/hecks/runtime/era_guard.rb +193 -0
- data/lib/hecks/runtime/era_tamper.rb +61 -0
- data/lib/hecks/runtime/errors.rb +100 -0
- data/lib/hecks/runtime/event.rb +51 -0
- data/lib/hecks/runtime/identity.rb +128 -0
- data/lib/hecks/runtime/instance.rb +120 -0
- data/lib/hecks/runtime/interpreting.rb +69 -0
- data/lib/hecks/runtime/loader.rb +143 -0
- data/lib/hecks/runtime/policy_interpreter.rb +366 -0
- data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
- data/lib/hecks/runtime/query_interpreter.rb +254 -0
- data/lib/hecks/runtime/reaction_invocation.rb +245 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
- data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
- data/lib/hecks/runtime/reference_hop.rb +99 -0
- data/lib/hecks/runtime/refusal_wording.rb +124 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
- data/lib/hecks/runtime/registry/verification.rb +158 -0
- data/lib/hecks/runtime/registry.rb +218 -0
- data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
- data/lib/hecks/runtime/routing.rb +96 -0
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
- data/lib/hecks/runtime/saga_interpreter.rb +301 -0
- data/lib/hecks/runtime/storage_shape.rb +120 -0
- data/lib/hecks/runtime/tenant_check.rb +84 -0
- data/lib/hecks/runtime/tenant_scope.rb +55 -0
- data/lib/hecks/runtime/value/admission.rb +112 -0
- data/lib/hecks/runtime/value/coercion.rb +500 -0
- data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
- data/lib/hecks/runtime/value.rb +125 -0
- data/lib/hecks/runtime.rb +95 -0
- data/lib/hecks/translation/audit/approval_digest.rb +31 -0
- data/lib/hecks/translation/audit/layer_one.rb +37 -0
- data/lib/hecks/translation/audit/layer_two.rb +77 -0
- data/lib/hecks/translation/audit/unfed_report.rb +42 -0
- data/lib/hecks/translation/audit.rb +70 -0
- data/lib/hecks/translation/reattest.rb +72 -0
- data/lib/hecks/translation/rule_compiler.rb +120 -0
- data/lib/hecks/translation/scaffold/differ.rb +183 -0
- data/lib/hecks/translation/scaffold/renderer.rb +41 -0
- data/lib/hecks/translation/scaffold/writer.rb +28 -0
- data/lib/hecks/translation/scaffold.rb +30 -0
- data/lib/hecks/translation.rb +11 -0
- data/lib/hecks/version.rb +16 -0
- data/lib/hecks/vocabulary.rb +213 -0
- data/lib/hecks.rb +118 -6
- metadata +401 -222
- data/bin/hecks +0 -7
- data/bin/hecks-package +0 -65
- data/bin/hecks_console +0 -12
- data/bin/hecks_serverless +0 -6
- data/lib/cli/build.rb +0 -14
- data/lib/cli/command_runner.rb +0 -28
- data/lib/cli/console.rb +0 -10
- data/lib/cli/generate.rb +0 -37
- data/lib/cli/hecks-cli.rb +0 -27
- data/lib/cli/test.rb +0 -57
- data/lib/console/commands.rb +0 -8
- data/lib/console/hecks-console.rb +0 -1
- data/lib/packager/README.md +0 -0
- data/lib/packager/app_runner.rb +0 -21
- data/lib/packager/args.rb +0 -26
- data/lib/packager/compatibility/fixnum.rb +0 -6
- data/lib/packager/hecks.rb +0 -39
- data/lib/packager/query_runner.rb +0 -21
- data/lib/packager/resources/Dockerfile +0 -11
- data/lib/packager/resources/app_binary +0 -7
- data/lib/packager/resources/bundle_config +0 -3
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
- data/lib/serverless/Domain +0 -32
- data/lib/serverless/cli.rb +0 -75
- data/lib/serverless/resources/command_name.js +0 -5
- data/lib/serverless/resources/environment.js +0 -7
- data/lib/serverless/resources/handler.js.tt +0 -28
- data/lib/serverless/resources/run_binary.js +0 -22
- data/lib/serverless/resources/serverless.yml +0 -20
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
require_relative "../projector"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
# A DOMAIN'S OWN SHAPE, PROJECTED AS MERMAID DIAGRAMS — the same
|
|
6
|
+
# trick `Projections::Reference`/`DocsProjector` already play for
|
|
7
|
+
# prose, one level further: a diagram generated FROM the
|
|
8
|
+
# declaration can't drift from it the way a hand-drawn one
|
|
9
|
+
# inevitably does, because there is no second copy to forget to
|
|
10
|
+
# update.
|
|
11
|
+
#
|
|
12
|
+
# MERMAID, NOT GRAPHVIZ (the two considered) — every diagram type
|
|
13
|
+
# below has a Mermaid form purpose-built for exactly what the
|
|
14
|
+
# underlying construct already is (a `lifecycle` IS a state
|
|
15
|
+
# machine, `has_many`/`belongs_to` already speaks in cardinality,
|
|
16
|
+
# `emits`/`trigger` already IS a directed graph), and the output is
|
|
17
|
+
# plain text that renders natively wherever this project's own docs
|
|
18
|
+
# already live — GitHub markdown, this repo's generated docs, Claude
|
|
19
|
+
# Artifacts — with no build step and no external binary. Graphviz's
|
|
20
|
+
# DOT format needs an actual render step (a `dot` binary, or a WASM
|
|
21
|
+
# port) to become anything viewable, which is a real dependency this
|
|
22
|
+
# repository's own discipline (see rust/parser's Cargo.toml: "no
|
|
23
|
+
# dependency earns its way past std") would rather not take just to
|
|
24
|
+
# draw a diagram.
|
|
25
|
+
#
|
|
26
|
+
# FOUR DIAGRAM KINDS, one file each per domain except lifecycles
|
|
27
|
+
# (one per lifecycle-bearing construct, since that's how a reader
|
|
28
|
+
# actually reaches for it — looking at ONE aggregate's states, not
|
|
29
|
+
# every aggregate's at once):
|
|
30
|
+
#
|
|
31
|
+
# <Name>_lifecycle.mmd stateDiagram-v2 one per lifecycle
|
|
32
|
+
# relationships.mmd erDiagram the whole domain's has_many/
|
|
33
|
+
# has_one/belongs_to/reference_to
|
|
34
|
+
# dispatch.mmd flowchart the whole domain's command
|
|
35
|
+
# emits -> policy trigger chains
|
|
36
|
+
# roles.mmd flowchart every role that issues a
|
|
37
|
+
# command, wired to every
|
|
38
|
+
# command it issues
|
|
39
|
+
# ports.mmd flowchart every port operation, which
|
|
40
|
+
# aggregate exposes it, which
|
|
41
|
+
# aggregate it routes to: (if
|
|
42
|
+
# any), and what it emits
|
|
43
|
+
# read_models.mmd flowchart every read_model, and every
|
|
44
|
+
# aggregate it's assembled
|
|
45
|
+
# from — the read-side
|
|
46
|
+
# complement to relationships.mmd
|
|
47
|
+
# <Name>_surface.mmd flowchart one per aggregate/entity that
|
|
48
|
+
# declares at least one command
|
|
49
|
+
# or query — everything you can
|
|
50
|
+
# DO to it and ASK about it,
|
|
51
|
+
# in one place
|
|
52
|
+
#
|
|
53
|
+
# CONSTRUCT NAMES (aggregate/entity/command/event) ARE USED BARE,
|
|
54
|
+
# UNSANITIZED, as Mermaid node/entity ids — safe because this
|
|
55
|
+
# language's own word grammar only ever admits simple CamelCase/
|
|
56
|
+
# snake_case identifiers there (confirmed: no space or punctuation
|
|
57
|
+
# appears in any real aggregate/command/event name across the corpus
|
|
58
|
+
# this projects from). A `role:` STRING IS FREE TEXT, though — the
|
|
59
|
+
# real corpus already has "Back office"/"Vault officer"/"Branch
|
|
60
|
+
# clerk" — so `roles.mmd` is the one diagram here that sanitizes a
|
|
61
|
+
# name into an id (`role_id`) while keeping the real string as the
|
|
62
|
+
# node's own displayed label.
|
|
63
|
+
module Diagrams
|
|
64
|
+
extend Hecks::Projector::Target
|
|
65
|
+
|
|
66
|
+
projects_as :diagrams, emits: :files
|
|
67
|
+
|
|
68
|
+
module_function
|
|
69
|
+
|
|
70
|
+
def call(bluebook:, options: {})
|
|
71
|
+
files = {}
|
|
72
|
+
|
|
73
|
+
holders_with_lifecycle(bluebook).each do |holder|
|
|
74
|
+
files["#{holder.hecks_name}_lifecycle.mmd"] = lifecycle_diagram(bluebook, holder)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if (diagram = relationship_diagram(bluebook))
|
|
78
|
+
files["relationships.mmd"] = diagram
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if (diagram = dispatch_diagram(bluebook))
|
|
82
|
+
files["dispatch.mmd"] = diagram
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if (diagram = roles_diagram(bluebook))
|
|
86
|
+
files["roles.mmd"] = diagram
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if (diagram = ports_diagram(bluebook))
|
|
90
|
+
files["ports.mmd"] = diagram
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if (diagram = read_model_diagram(bluebook))
|
|
94
|
+
files["read_models.mmd"] = diagram
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
holders(bluebook).each do |holder|
|
|
98
|
+
next if holder.commands.empty? && holder.queries.empty?
|
|
99
|
+
|
|
100
|
+
files["#{holder.hecks_name}_surface.mmd"] = surface_diagram(bluebook, holder)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
files
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# ── shared ────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
# AN ENTITY CAN CARRY ITS OWN LIFECYCLE, RELATIONSHIP, OR COMMAND
|
|
109
|
+
# TOO — its own `lifecycle`/`reference_to`/`command` block,
|
|
110
|
+
# addressed through its holding aggregate the same way
|
|
111
|
+
# `DocsProjector` already treats an aggregate and its entities
|
|
112
|
+
# alike. Walking both here means a domain's entity gaining any of
|
|
113
|
+
# these needs no change to this file.
|
|
114
|
+
def holders(bluebook)
|
|
115
|
+
bluebook.aggregates.flat_map { |aggregate| [aggregate, *aggregate.entities] }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def holders_with_lifecycle(bluebook) = holders(bluebook).select(&:lifecycle)
|
|
119
|
+
|
|
120
|
+
# `chapter_name` DRIVES THE RE-RUN HINT ALWAYS — that's the one
|
|
121
|
+
# argument `bin/project_diagrams` actually takes, regardless of
|
|
122
|
+
# which single aggregate/entity `subject` happens to name. Passing
|
|
123
|
+
# the wrong one here once already produced a real, committed
|
|
124
|
+
# `Order_lifecycle.mmd` telling a reader to run
|
|
125
|
+
# `bin/project_diagrams <domain-path> Order` — a chapter name
|
|
126
|
+
# Hecks.boot has never heard of.
|
|
127
|
+
def header(chapter_name, subject)
|
|
128
|
+
<<~HEADER
|
|
129
|
+
%% GENERATED by bin/project_diagrams from #{subject} — DO NOT EDIT BY HAND.
|
|
130
|
+
%% Re-run `bin/project_diagrams <domain-path> #{chapter_name}` after any change.
|
|
131
|
+
HEADER
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# ── lifecycle -> stateDiagram-v2 ─────────────────────────────────
|
|
135
|
+
|
|
136
|
+
def lifecycle_diagram(bluebook, holder)
|
|
137
|
+
lifecycle = holder.lifecycle
|
|
138
|
+
edges = lifecycle.transitions.flat_map do |command_name, transition|
|
|
139
|
+
Array(transition.from).map { |from_state| " #{from_state} --> #{transition.target}: #{command_name}" }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
subject = "#{holder.hecks_name}'s own declared lifecycle (field: #{lifecycle.field})"
|
|
143
|
+
<<~MERMAID
|
|
144
|
+
#{header(bluebook.name, subject)}stateDiagram-v2
|
|
145
|
+
[*] --> #{lifecycle.default}
|
|
146
|
+
#{edges.join("\n")}
|
|
147
|
+
MERMAID
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# ── relationships -> erDiagram ───────────────────────────────────
|
|
151
|
+
|
|
152
|
+
# STANDARD CROW'S-FOOT READING, the same convention every ORM's own
|
|
153
|
+
# ERD generator (Rails' erd gem included) already uses:
|
|
154
|
+
# `has_many`/`has_one` are read from the OWNING side — one Holder
|
|
155
|
+
# relates to many/one Target. `belongs_to`/`reference_to` are read
|
|
156
|
+
# from the TARGET's side instead — one Target can be pointed at by
|
|
157
|
+
# MANY Holders — because a bare reference carries no promise about
|
|
158
|
+
# how many holders point back at it; "many" is the honest default
|
|
159
|
+
# absent a declared uniqueness rule this language doesn't expose.
|
|
160
|
+
# `optional?` only ever softens the side that can genuinely be
|
|
161
|
+
# absent (a nilable reference, an empty has_one) — never the
|
|
162
|
+
# crow's-foot "many" marker, which is a structural fact independent
|
|
163
|
+
# of any one instance's optionality.
|
|
164
|
+
def relationship_diagram(bluebook)
|
|
165
|
+
edges = holders(bluebook).flat_map do |holder|
|
|
166
|
+
holder.attributes.select(&:reference?).map { |attribute| relationship_edge(holder, attribute) }
|
|
167
|
+
end
|
|
168
|
+
return nil if edges.empty?
|
|
169
|
+
|
|
170
|
+
subject = "#{bluebook.name}'s own declared reference_to/belongs_to/has_many/has_one attributes"
|
|
171
|
+
"#{header(bluebook.name, subject)}erDiagram\n#{edges.join("\n")}\n"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def relationship_edge(holder, attribute)
|
|
175
|
+
target = attribute.type.target_name
|
|
176
|
+
case attribute.relationship
|
|
177
|
+
when "has_many"
|
|
178
|
+
%( #{holder.hecks_name} ||--o{ #{target} : "#{attribute.name}")
|
|
179
|
+
when "has_one"
|
|
180
|
+
%( #{holder.hecks_name} ||--#{attribute.optional? ? 'o|' : '||'} #{target} : "#{attribute.name}")
|
|
181
|
+
when "belongs_to", "reference_to"
|
|
182
|
+
%( #{target} #{attribute.optional? ? '|o' : '||'}--o{ #{holder.hecks_name} : "#{attribute.name}")
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# ── dispatch -> flowchart ─────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
# A COMMAND NODE, STADIUM-SHAPED (`(["..."])`); AN EVENT NODE,
|
|
189
|
+
# HEXAGONAL (`{{"..."}}`) — one visual vocabulary for "a thing
|
|
190
|
+
# someone does" versus "a fact that happened", matching the
|
|
191
|
+
# language's own verb/event distinction. Command ids are qualified
|
|
192
|
+
# by their owning aggregate (`cmd_Order_Purchase`) since two
|
|
193
|
+
# aggregates may share a command name; event ids are bare
|
|
194
|
+
# (`evt_PizzaCreated`) since an event is this domain's own
|
|
195
|
+
# addressing key, the same way `policy.on_event` reaches it.
|
|
196
|
+
def dispatch_diagram(bluebook)
|
|
197
|
+
lines = []
|
|
198
|
+
|
|
199
|
+
holders(bluebook).each do |holder|
|
|
200
|
+
holder.commands.each do |command|
|
|
201
|
+
command.emits.each { |event| lines << emits_edge(holder, command, event) }
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
bluebook.policies.each { |policy| lines << trigger_edge(policy) }
|
|
206
|
+
|
|
207
|
+
lines.compact!
|
|
208
|
+
return nil if lines.empty?
|
|
209
|
+
|
|
210
|
+
subject = "#{bluebook.name}'s own declared commands' emits and policies' on/trigger"
|
|
211
|
+
"#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def emits_edge(holder, command, event)
|
|
215
|
+
%( #{command_node(holder.hecks_name, command.hecks_name)} -->|emits| #{event_node(event)})
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# `on_event` IS SOMETIMES AGGREGATE-QUALIFIED
|
|
219
|
+
# (`"Account.AccountFrozen"`) AND SOMETIMES BARE
|
|
220
|
+
# (`"CustomerSuspended"`) in the real corpus — `emits` never is,
|
|
221
|
+
# so this always matches against the bare tail, the same
|
|
222
|
+
# normalization a reader has to do by eye today.
|
|
223
|
+
#
|
|
224
|
+
# A TRIGGER CROSSING INTO ANOTHER DOMAIN (`policy.target_domain`)
|
|
225
|
+
# still draws — the target command just has no incoming `emits`
|
|
226
|
+
# edge of its own here, which honestly shows "dispatch continues
|
|
227
|
+
# elsewhere" rather than silently dropping the edge. The label
|
|
228
|
+
# names which domain, so that's not a dead end on the page either.
|
|
229
|
+
def trigger_edge(policy)
|
|
230
|
+
bare_event = policy.on_event.to_s.split(".").last
|
|
231
|
+
aggregate_name, command_name = policy.trigger_command.to_s.split(".", 2)
|
|
232
|
+
label = policy.target_domain ? "triggers in #{policy.target_domain}" : "triggers"
|
|
233
|
+
%( #{event_node(bare_event)} -->|#{label}| #{command_node(aggregate_name, command_name)})
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def command_node(aggregate_name, command_name)
|
|
237
|
+
%(cmd_#{aggregate_name}_#{command_name}(["#{aggregate_name}.#{command_name}"]))
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def event_node(event_name) = %(evt_#{event_name}{{"#{event_name}"}})
|
|
241
|
+
|
|
242
|
+
# ── roles -> flowchart ────────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
# WHO ISSUES WHAT, ACROSS THE WHOLE DOMAIN — data no existing
|
|
245
|
+
# projection draws at all today (the reference pages' own
|
|
246
|
+
# `command_entry` only ever prints a command's role as a single
|
|
247
|
+
# line of prose, never assembled across commands). A command with
|
|
248
|
+
# no declared `role` draws nothing — there is no fact to state.
|
|
249
|
+
# Circle-shaped so a role reads as "who" beside `dispatch.mmd`'s
|
|
250
|
+
# stadium ("what someone does") and hexagon ("what happened").
|
|
251
|
+
def roles_diagram(bluebook)
|
|
252
|
+
lines = holders(bluebook).flat_map do |holder|
|
|
253
|
+
holder.commands.select(&:role).map { |command| role_edge(holder, command) }
|
|
254
|
+
end
|
|
255
|
+
return nil if lines.empty?
|
|
256
|
+
|
|
257
|
+
subject = "#{bluebook.name}'s own declared command roles"
|
|
258
|
+
"#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def role_edge(holder, command)
|
|
262
|
+
%( #{role_node(command.role)} -->|issues| #{command_node(holder.hecks_name, command.hecks_name)})
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def role_node(role_name) = %(#{role_id(role_name)}((#{role_name})))
|
|
266
|
+
|
|
267
|
+
# A ROLE NAME IS FREE TEXT ("Back office", "Vault officer") —
|
|
268
|
+
# unlike every other name this file uses as a bare id, this one
|
|
269
|
+
# has to be sanitized to become a legal Mermaid identifier. The
|
|
270
|
+
# real string still appears as the node's own label
|
|
271
|
+
# (`role_node`); only the id is mangled.
|
|
272
|
+
def role_id(role_name) = "role_#{role_name.to_s.gsub(/[^A-Za-z0-9]+/, '_')}"
|
|
273
|
+
|
|
274
|
+
# ── ports -> flowchart ───────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
# A PORT OPERATION IS A BOUNDARY TRANSLATION, NOT A VERB OR A FACT —
|
|
277
|
+
# its own reference page says so plainly ("the builder behind it
|
|
278
|
+
# defines no `given` or `sets`, so an operation cannot read
|
|
279
|
+
# aggregate state or mutate a record itself"), so it gets a third
|
|
280
|
+
# shape, a trapezoid, beside `dispatch.mmd`'s stadium/hexagon
|
|
281
|
+
# vocabulary. An aggregate drawn as a `to:` target is a cylinder —
|
|
282
|
+
# state landing somewhere, the same reason a data store gets one
|
|
283
|
+
# in an ordinary flowchart.
|
|
284
|
+
#
|
|
285
|
+
# TWO EDGE KINDS PER OPERATION: a dotted "exposes" edge from the
|
|
286
|
+
# aggregate the port hangs off (always present — a port always
|
|
287
|
+
# belongs to exactly one aggregate), and a solid "to:" edge to
|
|
288
|
+
# whichever aggregate the operation itself names as its receiver
|
|
289
|
+
# (present only when `to:` is declared — PR #351's own real
|
|
290
|
+
# addition; before it, this data didn't exist to draw at all).
|
|
291
|
+
# `emits` reuses `dispatch.mmd`'s own `event_node` unchanged — the
|
|
292
|
+
# same fact, reached from a different direction.
|
|
293
|
+
#
|
|
294
|
+
# `bluebook.aggregates`, NOT the shared `holders` — unlike a
|
|
295
|
+
# lifecycle/relationship/command, a port belongs to an AGGREGATE
|
|
296
|
+
# only; an entity has no `ports` method at all (confirmed: calling
|
|
297
|
+
# it raises, it isn't just always empty), so walking entities here
|
|
298
|
+
# the way every other diagram in this file does would crash on
|
|
299
|
+
# the first entity-bearing domain.
|
|
300
|
+
def ports_diagram(bluebook)
|
|
301
|
+
lines = bluebook.aggregates.flat_map do |holder|
|
|
302
|
+
holder.ports.flat_map { |port| port.operations.map { |operation| port_edges(holder, port, operation) } }
|
|
303
|
+
end.flatten
|
|
304
|
+
|
|
305
|
+
return nil if lines.empty?
|
|
306
|
+
|
|
307
|
+
subject = "#{bluebook.name}'s own declared port operations (which aggregate exposes each, its to:, and its emits)"
|
|
308
|
+
"#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def port_edges(holder, port, operation)
|
|
312
|
+
op = port_operation_node(holder.hecks_name, port.name, operation.hecks_name)
|
|
313
|
+
edges = [" #{holder.hecks_name}[(#{holder.hecks_name})] -.->|exposes| #{op}"]
|
|
314
|
+
edges << " #{op} -->|to: #{operation.to}| #{operation.to}[(#{operation.to})]" if operation.to
|
|
315
|
+
operation.emits.each { |event| edges << " #{op} -->|emits| #{event_node(event)}" }
|
|
316
|
+
edges
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def port_operation_node(aggregate_name, port_name, operation_name)
|
|
320
|
+
id = "op_#{aggregate_name}_#{port_name}_#{operation_name}"
|
|
321
|
+
%(#{id}[/"#{port_name}.#{operation_name}"/])
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# ── read models -> flowchart ─────────────────────────────────────
|
|
325
|
+
|
|
326
|
+
# THE READ-SIDE COMPLEMENT TO `relationships.mmd` — that diagram
|
|
327
|
+
# shows how aggregates reference each other for WRITES
|
|
328
|
+
# (`has_many`/`belongs_to`/`reference_to`); this shows how a
|
|
329
|
+
# `read_model` ASSEMBLES data for READS, from
|
|
330
|
+
# `aggregate_heads` — the same list `where`/`group_by`/`order_by`
|
|
331
|
+
# all operate over, and the one fact every read_model has
|
|
332
|
+
# regardless of whether it's rooted (`reference_target`) or
|
|
333
|
+
# gathers heads with no root at all (a rootless read model, real
|
|
334
|
+
# in the corpus: `AccountsByKind`).
|
|
335
|
+
#
|
|
336
|
+
# A READ MODEL IS A SUBROUTINE SHAPE (`[[...]]`, "a predefined
|
|
337
|
+
# process") — a fourth shape, beside `ports.mmd`'s trapezoid and
|
|
338
|
+
# `dispatch.mmd`'s stadium/hexagon: not a verb, not a fact, not a
|
|
339
|
+
# boundary translation, but a standing, reusable view. Every
|
|
340
|
+
# aggregate it draws from is a cylinder — the same "state lands
|
|
341
|
+
# somewhere" shape `ports.mmd`'s `to:` target already uses, and
|
|
342
|
+
# the same bare id, so an aggregate feeding several read_models
|
|
343
|
+
# (real in banking: `Account` feeds four) merges into one node
|
|
344
|
+
# across the whole diagram.
|
|
345
|
+
#
|
|
346
|
+
# THE LABEL NAMES THE SHAPE OF THE ANSWER, NOT JUST THE NAME —
|
|
347
|
+
# `(count)`/`(median: field)` for the two real aggregations in the
|
|
348
|
+
# corpus, nothing appended for an ordinary row-returning
|
|
349
|
+
# read_model. Still MVP scope: `where`/`group_by`/`order_by`
|
|
350
|
+
# aren't drawn at all yet — real facts, not invented, just not
|
|
351
|
+
# this diagram's job yet.
|
|
352
|
+
def read_model_diagram(bluebook)
|
|
353
|
+
lines = bluebook.read_models.flat_map { |read_model| read_model_edges(read_model) }
|
|
354
|
+
return nil if lines.empty?
|
|
355
|
+
|
|
356
|
+
subject = "#{bluebook.name}'s own declared read_models and the aggregates each is assembled from"
|
|
357
|
+
"#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def read_model_edges(read_model)
|
|
361
|
+
shape = read_model.to_h
|
|
362
|
+
node = %(rm_#{shape[:name]}[["#{read_model_label(shape)}"]])
|
|
363
|
+
Array(shape[:aggregate_heads]).map do |head|
|
|
364
|
+
# QUOTED, NOT BARE — an edge label containing `[` or `]`
|
|
365
|
+
# (`accounts[]`, marking the "many" side) breaks Mermaid's own
|
|
366
|
+
# `|label|` parser outright if left unquoted: it reads the
|
|
367
|
+
# `[` as the START OF A NEW NODE SHAPE mid-label, not text.
|
|
368
|
+
# Confirmed live against the real parser before this quoting
|
|
369
|
+
# existed — every OTHER edge label in this file happens to be
|
|
370
|
+
# a bare word or already-quoted string, so this is the one
|
|
371
|
+
# spot that needed it.
|
|
372
|
+
label = head[:many] ? "#{head[:as]}[]" : head[:as]
|
|
373
|
+
%( #{head[:aggregate]}[(#{head[:aggregate]})] -->|"#{label}"| #{node})
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def read_model_label(shape)
|
|
378
|
+
return "#{shape[:name]} (count)" if shape[:count]
|
|
379
|
+
return "#{shape[:name]} (median: #{shape[:median_field]})" if shape[:median_field]
|
|
380
|
+
|
|
381
|
+
shape[:name]
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# ── surface -> flowchart ─────────────────────────────────────────
|
|
385
|
+
|
|
386
|
+
# "WHAT CAN I DO TO THIS, WHAT CAN I ASK ABOUT IT" — one file per
|
|
387
|
+
# holder, unlike every other diagram here: `dispatch.mmd` already
|
|
388
|
+
# shows a command's own onward reaction chain, but never an
|
|
389
|
+
# aggregate's own FULL command/query menu in one place, and
|
|
390
|
+
# `roles.mmd` shows who issues a command without saying what else
|
|
391
|
+
# that same aggregate answers. This is the one diagram meant to
|
|
392
|
+
# be read starting from the aggregate, not from a verb or a fact.
|
|
393
|
+
#
|
|
394
|
+
# A QUERY IS A DIAMOND — a fifth shape, beside `dispatch.mmd`'s
|
|
395
|
+
# stadium/hexagon, `ports.mmd`'s trapezoid, and `read_models.mmd`'s
|
|
396
|
+
# subroutine: a question with an answer, not a verb that changes
|
|
397
|
+
# anything. Command edges are solid ("does"); query edges are
|
|
398
|
+
# dotted ("asks") — the same solid/dotted split `ports.mmd`
|
|
399
|
+
# already uses for "routes to:" versus "exposes".
|
|
400
|
+
def surface_diagram(bluebook, holder)
|
|
401
|
+
lines = holder.commands.map { |command| " #{holder.hecks_name}[(#{holder.hecks_name})] -->|does| #{command_node(holder.hecks_name, command.hecks_name)}" }
|
|
402
|
+
lines += holder.queries.map { |query| " #{holder.hecks_name}[(#{holder.hecks_name})] -.->|asks| #{query_node(holder.hecks_name, query.hecks_name)}" }
|
|
403
|
+
|
|
404
|
+
subject = "#{holder.hecks_name}'s own declared commands and queries"
|
|
405
|
+
"#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def query_node(aggregate_name, query_name)
|
|
409
|
+
%(qry_#{aggregate_name}_#{query_name}{"#{aggregate_name}.#{query_name}"})
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require_relative "../projector"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
# The canonical IR, as a constant. The implementation already existed
|
|
6
|
+
# and is already golden-tested (`Projector::IRProjector`, registered
|
|
7
|
+
# as `:ir`) — this only gives it the constant spelling every other
|
|
8
|
+
# target has, by re-registering the SAME module under the same key.
|
|
9
|
+
#
|
|
10
|
+
# Deliberately not a new implementation: two things named `IR` that
|
|
11
|
+
# each rendered IR their own way is exactly the drift this namespace
|
|
12
|
+
# exists to avoid.
|
|
13
|
+
IR = Projector::IRProjector
|
|
14
|
+
|
|
15
|
+
IR.extend(Projector::Target)
|
|
16
|
+
IR.projects_as :ir, requires: Hecks::IR
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Projections
|
|
3
|
+
module Model
|
|
4
|
+
# HOW THE MODEL'S SHAPE DIFFERS FROM THE LANGUAGE'S, and why.
|
|
5
|
+
#
|
|
6
|
+
# Every construct's `emits_ir` restates what the grammar declares —
|
|
7
|
+
# and the two legitimately differ, in seven ways. These lived only
|
|
8
|
+
# as prose in Ruby comments until spec/model_shape_conformance_spec
|
|
9
|
+
# made them checkable; they live HERE so the generator and the gate
|
|
10
|
+
# read one source rather than two that must agree.
|
|
11
|
+
#
|
|
12
|
+
# A reason is carried beside each entry because the generated file
|
|
13
|
+
# will CARRY it: a deviation is exactly the kind of thing whose
|
|
14
|
+
# explanation must survive regeneration, and the only way it can is
|
|
15
|
+
# to be emitted rather than typed into the output.
|
|
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
|
+
# What the model holds that the grammar declares elsewhere: the
|
|
31
|
+
# containment edges, stated in syntax.bluebook's Keyword rows as
|
|
32
|
+
# `context` -> `opens`.
|
|
33
|
+
CONTAINED = {
|
|
34
|
+
"Bluebook" => %i[aggregates read_models policies process_managers],
|
|
35
|
+
"Aggregate" => %i[commands entities queries value_objects],
|
|
36
|
+
# S17, ADR 0026 — an entity may nest further entities now
|
|
37
|
+
# (`Dispatch`, inside `Handler`) — same containment edge as
|
|
38
|
+
# Aggregate's own `entities`, one level down.
|
|
39
|
+
"Entity" => %i[commands entities queries],
|
|
40
|
+
"ValueObject" => %i[members],
|
|
41
|
+
"ProcessManager" => %i[handlers]
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
# One model field gathered from several declared ones —
|
|
45
|
+
# contracts.rb's own `[:folded, ...]` shape, as the pair it is.
|
|
46
|
+
FOLDED = {
|
|
47
|
+
"Aggregate" => { lifecycle: %i[state_field state_start transitions] },
|
|
48
|
+
"Entity" => { lifecycle: %i[state_field state_start transitions] },
|
|
49
|
+
"Query" => { order_by: %i[order_field order_way] }
|
|
50
|
+
}.freeze
|
|
51
|
+
|
|
52
|
+
# The inverse of a fold, and it had no name at all: one declared
|
|
53
|
+
# field opening into several the model holds apart.
|
|
54
|
+
UNPACKED = {
|
|
55
|
+
"ReadModel" => { options: %i[wheres order_by limit] }
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
58
|
+
# Model-only, each for its own reason rather than by oversight.
|
|
59
|
+
COMPUTED = {
|
|
60
|
+
"Bluebook" => { ir_version: "the EMISSION's own version, not the domain's",
|
|
61
|
+
canonical_form: "the normalisation table every reader needs beside the IR" },
|
|
62
|
+
"ValueObject" => { closed_set: "an empty one_of and no one_of are otherwise indistinguishable" },
|
|
63
|
+
"Aggregate" => { ports: "declared in the hecksagon, attached after the aggregate exists" }
|
|
64
|
+
}.freeze
|
|
65
|
+
|
|
66
|
+
# DECLARED, AND DELIBERATELY NOT EMITTED. The category that had no
|
|
67
|
+
# home anywhere before — each of these was a comment and nothing
|
|
68
|
+
# more.
|
|
69
|
+
OFF_THE_WIRE = {
|
|
70
|
+
"Policy" => { aggregate: "the wire format is a pinned contract, and it does not carry " \
|
|
71
|
+
"where a policy was written before the builder hoisted it" },
|
|
72
|
+
"Bluebook" => { formerly_known_as: "a rename's old name is a fact about the source",
|
|
73
|
+
normalisations: "the normalisation table rides on canonical_form instead" },
|
|
74
|
+
"ValueObject" => { rows: "the language's name for a closed set's members; emitted as `members`" }
|
|
75
|
+
}.freeze
|
|
76
|
+
|
|
77
|
+
# Emitted by `to_h`'s own merge rather than by `emits_ir` — the
|
|
78
|
+
# two constructs whose shape is genuinely not fixed, because the
|
|
79
|
+
# query specification layer grew options after them.
|
|
80
|
+
DYNAMIC_TAIL = {
|
|
81
|
+
"Query" => %i[options],
|
|
82
|
+
"ReadModel" => %i[options group_by aggregate_heads count median_field]
|
|
83
|
+
}.freeze
|
|
84
|
+
|
|
85
|
+
module_function
|
|
86
|
+
|
|
87
|
+
# The tables that carry a reason answer with names only when the
|
|
88
|
+
# caller wants the set rather than the explanations.
|
|
89
|
+
def off_the_wire(name) = OFF_THE_WIRE.fetch(name, {}).keys
|
|
90
|
+
def computed(name) = COMPUTED.fetch(name, {}).keys
|
|
91
|
+
def contained(name) = CONTAINED.fetch(name, [])
|
|
92
|
+
def folded(name) = FOLDED.fetch(name, {})
|
|
93
|
+
def unpacked(name) = UNPACKED.fetch(name, {})
|
|
94
|
+
def dynamic_tail(name) = DYNAMIC_TAIL.fetch(name, [])
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
require_relative "model/deviations"
|
|
2
|
+
require_relative "../projector"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Projections
|
|
6
|
+
# THE MODEL CLASSES, PROJECTED FROM THE LANGUAGE THAT DECLARES THEM.
|
|
7
|
+
#
|
|
8
|
+
# A construct's HOLDING half — its readers, its emission, a
|
|
9
|
+
# constructor that assigns declared fields and hands off — restates
|
|
10
|
+
# what `bluebook.bluebook` already says, three times over in Ruby.
|
|
11
|
+
# This renders it instead.
|
|
12
|
+
#
|
|
13
|
+
# ONLY THE HOLDING HALF. `Behaviour::X` is hand-written and permanent,
|
|
14
|
+
# and `settle` is the seam: everything a declaration cannot state
|
|
15
|
+
# lives behind it, so regenerating can never be lossy. That property
|
|
16
|
+
# was established construct by construct before any of this was
|
|
17
|
+
# written — the chapter looked generatable and was not, until its
|
|
18
|
+
# `@hecks_root`, ports table and child stamping moved behind `settle`.
|
|
19
|
+
#
|
|
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
|
+
# instances, what a constructor's defaults are, or how a value is
|
|
23
|
+
# coerced on the way in. Those are facts about Ruby, not about
|
|
24
|
+
# bluebooks, so they are declared here rather than pretended into the
|
|
25
|
+
# language — and keeping them in one table is what would let a second
|
|
26
|
+
# host swap this file rather than edit thirteen.
|
|
27
|
+
module Model
|
|
28
|
+
extend Projector::Target
|
|
29
|
+
|
|
30
|
+
projects_as :model, declares: "Bluebook", emits: :files
|
|
31
|
+
|
|
32
|
+
# PER-CONSTRUCT RUBY FACTS. `coerce` is the only fiddly column: a
|
|
33
|
+
# declared field arrives as whatever the builder handed over, and
|
|
34
|
+
# each construct has always normalised its own on the way in.
|
|
35
|
+
HOST = {
|
|
36
|
+
"Policy" => {
|
|
37
|
+
construct: "Policy",
|
|
38
|
+
file: "policy.rb",
|
|
39
|
+
behaviour: "Behaviour::Policy",
|
|
40
|
+
readers: %i[name on_event trigger_command target_domain where for_each with_spec],
|
|
41
|
+
accessors: %i[aggregate],
|
|
42
|
+
defaults: { name: nil, on_event: "nil", trigger_command: "nil",
|
|
43
|
+
target_domain: "nil", where: "nil", for_each: "nil",
|
|
44
|
+
with_spec: "[]", aggregate: "nil" },
|
|
45
|
+
coerce: { name: ".to_s", aggregate: "&.to_s" },
|
|
46
|
+
# A LIST OF BINDINGS IS NOT A SCALAR ON THE WIRE. Every other
|
|
47
|
+
# field emits as itself; this one has to render the way
|
|
48
|
+
# `DispatchSpec`'s own `with_spec` does — keys to strings, and
|
|
49
|
+
# `render_value` KEEPING the leading colon on a Symbol, because
|
|
50
|
+
# a binding that reads an event field and one that supplies a
|
|
51
|
+
# literal string are otherwise indistinguishable once written
|
|
52
|
+
# down (see `MetaValidator::Readings`' own note on exactly that).
|
|
53
|
+
renders: { with_spec: "-> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } }" },
|
|
54
|
+
settles: false
|
|
55
|
+
}
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
58
|
+
module_function
|
|
59
|
+
|
|
60
|
+
def call(bluebook:, options: {})
|
|
61
|
+
HOST.to_h { |name, host| [host.fetch(:file), render(bluebook, name, host)] }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def render(bluebook, name, host)
|
|
65
|
+
<<~RUBY
|
|
66
|
+
# GENERATED — projected from the language's own #{name} aggregate.
|
|
67
|
+
# DO NOT EDIT: the holding half is rendered, and #{host.fetch(:behaviour)}
|
|
68
|
+
# is where anything hand-written belongs.
|
|
69
|
+
require_relative "behaviour/#{File.basename(host.fetch(:file), ".rb")}"
|
|
70
|
+
|
|
71
|
+
module Hecks
|
|
72
|
+
module Bluebook
|
|
73
|
+
class #{name}
|
|
74
|
+
include Hecks::IR
|
|
75
|
+
include #{host.fetch(:behaviour)}
|
|
76
|
+
|
|
77
|
+
#{indent(emits(bluebook, name), 6)}
|
|
78
|
+
|
|
79
|
+
#{indent(readers(host), 6)}
|
|
80
|
+
|
|
81
|
+
#{indent(constructor(host), 6)}
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
RUBY
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# The emission, keyed as the model spells it and sourced as the
|
|
89
|
+
# language declares it.
|
|
90
|
+
def emits(bluebook, name)
|
|
91
|
+
fields = emitted_fields(bluebook, name)
|
|
92
|
+
renders = HOST.fetch(name).fetch(:renders, {})
|
|
93
|
+
width = fields.map { |f| f.to_s.length }.max.to_i
|
|
94
|
+
|
|
95
|
+
"emits_ir(\n" +
|
|
96
|
+
fields.map { |f| " #{"#{f}:".ljust(width + 1)} #{renders.fetch(f, ":#{f}")}" }.join(",\n") +
|
|
97
|
+
"\n)"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# WHAT THE CONSTRUCT EMITS: what the language declares, less every
|
|
101
|
+
# deviation the tables account for. The generator and
|
|
102
|
+
# spec/model_shape_conformance_spec compute this the same way, from
|
|
103
|
+
# the same tables, which is the point of the tables being in lib.
|
|
104
|
+
def emitted_fields(bluebook, name)
|
|
105
|
+
bluebook.aggregate(name).attributes.map(&:name)
|
|
106
|
+
.reject { |f| Deviations::PARENT_REF.call(f) } -
|
|
107
|
+
Deviations::JUDGE_ONLY -
|
|
108
|
+
Deviations.off_the_wire(name) -
|
|
109
|
+
Deviations.dynamic_tail(name) -
|
|
110
|
+
Deviations.folded(name).values.flatten -
|
|
111
|
+
Deviations.unpacked(name).keys
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def readers(host)
|
|
115
|
+
lines = ["attr_reader #{host.fetch(:readers).map { |r| ":#{r}" }.join(', ')}"]
|
|
116
|
+
accessors = host.fetch(:accessors, [])
|
|
117
|
+
return lines.join("\n") if accessors.empty?
|
|
118
|
+
|
|
119
|
+
# A declared field the model deliberately does not emit still
|
|
120
|
+
# needs a reader, and the REASON it is off the wire is carried
|
|
121
|
+
# here rather than typed in — a comment that survives
|
|
122
|
+
# regeneration is one the generator writes.
|
|
123
|
+
reasons = Deviations::OFF_THE_WIRE.fetch(host.fetch(:construct, ""), {})
|
|
124
|
+
(lines + accessors.map { |a|
|
|
125
|
+
why = reasons[a]
|
|
126
|
+
(why ? "\n# #{a.upcase}, DECLARED AND DELIBERATELY OFF THE WIRE\n# #{wrap(why)}\n" : "") +
|
|
127
|
+
"attr_accessor :#{a}"
|
|
128
|
+
}).join("\n")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def constructor(host)
|
|
132
|
+
args = host.fetch(:defaults).map { |f, d| d ? "#{f}: #{d}" : "#{f}:" }.join(", ")
|
|
133
|
+
body = host.fetch(:defaults).keys.map { |f|
|
|
134
|
+
" @#{f} = #{f}#{host.fetch(:coerce, {})[f]}"
|
|
135
|
+
}
|
|
136
|
+
body << "\n settle" if host.fetch(:settles, true)
|
|
137
|
+
|
|
138
|
+
"def initialize(#{args})\n#{body.join("\n")}\nend"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def indent(text, by) = text.lines.map { |l| l.strip.empty? ? l : (" " * by) + l }.join
|
|
142
|
+
def wrap(text) = text.scan(/.{1,62}(?:\s|$)/).map(&:strip).join("\n# ")
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|