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,82 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Bluebook
|
|
3
|
+
# The callable catalogue for a discovered project. It knows how Bluebook
|
|
4
|
+
# declarations become public FQNs, but never searches or boots folders.
|
|
5
|
+
class ProjectRegister
|
|
6
|
+
Entry = Struct.new(:fqn, :source_directory, :dispatcher, :declared_verb, :domain_version, keyword_init: true) do
|
|
7
|
+
def command? = fqn.command?
|
|
8
|
+
def query? = fqn.query?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class DuplicateFqn < StandardError; end
|
|
12
|
+
class MissingRealm < StandardError; end
|
|
13
|
+
class LatestMismatch < StandardError; end
|
|
14
|
+
|
|
15
|
+
attr_reader :entries
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
@entries = {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def fetch(address) = entries.fetch(address.to_s)
|
|
22
|
+
def include?(address) = entries.key?(address.to_s)
|
|
23
|
+
def commands = entries.values.select(&:command?)
|
|
24
|
+
def queries = entries.values.select(&:query?)
|
|
25
|
+
|
|
26
|
+
def register(bluebooks, registry, dispatcher, directory)
|
|
27
|
+
bluebooks.each do |bluebook|
|
|
28
|
+
world = registry.world(bluebook.name)
|
|
29
|
+
realm = world&.realm
|
|
30
|
+
raise MissingRealm, "#{bluebook.name} in #{directory} has no world realm" if realm.to_s.empty?
|
|
31
|
+
if world.latest && bluebook.version && world.latest != bluebook.version
|
|
32
|
+
raise LatestMismatch,
|
|
33
|
+
"#{bluebook.name} in #{directory} declares latest #{world.latest.inspect}, not #{bluebook.version.inspect}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
bluebook.aggregates.each do |aggregate|
|
|
37
|
+
aggregate.commands.each do |command|
|
|
38
|
+
add(Fqn.command(realm: realm, domain: bluebook.name, version: bluebook.version,
|
|
39
|
+
aggregate: aggregate.hecks_name, command: command.hecks_name), directory, dispatcher, command.hecks_name, bluebook.version) if bluebook.version
|
|
40
|
+
add(Fqn.command(realm: realm, domain: bluebook.name, aggregate: aggregate.hecks_name,
|
|
41
|
+
command: command.hecks_name), directory, dispatcher, command.hecks_name, bluebook.version) if current?(
|
|
42
|
+
bluebook, world
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
aggregate.queries.each do |query|
|
|
46
|
+
name = Naming.snake(query.name)
|
|
47
|
+
add(Fqn.query(realm: realm, domain: bluebook.name, version: bluebook.version,
|
|
48
|
+
aggregate: aggregate.hecks_name, query: name), directory, dispatcher, query.name, bluebook.version) if bluebook.version
|
|
49
|
+
add(Fqn.query(realm: realm, domain: bluebook.name, aggregate: aggregate.hecks_name,
|
|
50
|
+
query: name), directory, dispatcher, query.name, bluebook.version) if current?(bluebook, world)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
bluebook.read_models.each do |model|
|
|
54
|
+
add(Fqn.query(realm: realm, domain: bluebook.name, version: bluebook.version,
|
|
55
|
+
query: model.query_name), directory, dispatcher, model.query_name, bluebook.version) if bluebook.version
|
|
56
|
+
add(Fqn.query(realm: realm, domain: bluebook.name, query: model.query_name),
|
|
57
|
+
directory, dispatcher, model.query_name, bluebook.version) if current?(bluebook, world)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def current?(bluebook, world)
|
|
66
|
+
bluebook.version.nil? || world.latest == bluebook.version
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def add(fqn, directory, dispatcher, declared_verb, domain_version)
|
|
70
|
+
existing = entries[fqn.to_s]
|
|
71
|
+
if existing
|
|
72
|
+
raise DuplicateFqn, "#{fqn} is declared in both #{existing.source_directory} and #{directory}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
entries[fqn.to_s] = Entry.new(
|
|
76
|
+
fqn: fqn, source_directory: directory, dispatcher: dispatcher,
|
|
77
|
+
declared_verb: declared_verb, domain_version: domain_version
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require_relative "behaviour/query"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Bluebook
|
|
5
|
+
def self.render_value(value) = Literal.render(value)
|
|
6
|
+
|
|
7
|
+
# A query — an ASK, declared on an aggregate or on one of its entities.
|
|
8
|
+
#
|
|
9
|
+
# It crosses over as an INSTANCE rather than a class, and the reason is worth
|
|
10
|
+
# stating because it is the boundary of the pattern. A query inherits its
|
|
11
|
+
# whole body from `QuerySpecification::Common::Options` — `wheres`,
|
|
12
|
+
# `order_by`, `limit`, `offset`, `cursor`,
|
|
13
|
+
# `authorization`, `null_semantics`, `inspection` — and those
|
|
14
|
+
# are INSTANCE methods that the runtime and the SQLite adapter both read.
|
|
15
|
+
# Hoisting the declaration onto a metaclass would put the identity and the
|
|
16
|
+
# specification on opposite sides of the object.
|
|
17
|
+
#
|
|
18
|
+
# And nothing is lost, because `Class#name` was the only reason a construct
|
|
19
|
+
# ever needed a second word for its own name. An instance answers `name`
|
|
20
|
+
# truthfully. So a query gains an owner and an identity — which is what the
|
|
21
|
+
# graph is for — and keeps the name it always had.
|
|
22
|
+
class Query < QuerySpecification::Common::Options
|
|
23
|
+
include Construct
|
|
24
|
+
|
|
25
|
+
include Hecks::IR
|
|
26
|
+
include Behaviour::Query
|
|
27
|
+
|
|
28
|
+
emits_ir(
|
|
29
|
+
name: :name,
|
|
30
|
+
description: :description,
|
|
31
|
+
attributes: many(:attributes),
|
|
32
|
+
wheres: many(:wheres),
|
|
33
|
+
order_by: one(:order_by),
|
|
34
|
+
limit: one(:limit)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
attr_reader :name, :description, :attributes
|
|
38
|
+
|
|
39
|
+
def initialize(name:, description: nil, attributes: [], wheres: [],
|
|
40
|
+
order_by: nil, limit: nil, offset: nil, cursor: nil,
|
|
41
|
+
authorization: nil, null_semantics: nil,
|
|
42
|
+
inspection: nil)
|
|
43
|
+
null_semantics ||= QuerySpecification::Common::NullSemantics.default
|
|
44
|
+
super(wheres: wheres, order_by: order_by, limit: limit, offset: offset, cursor: cursor,
|
|
45
|
+
authorization: authorization,
|
|
46
|
+
null_semantics: null_semantics, inspection: inspection)
|
|
47
|
+
@name = name.to_s
|
|
48
|
+
@hecks_name = @name
|
|
49
|
+
@description = description
|
|
50
|
+
@attributes = attributes
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A query's shape is NOT fixed — `extra_options_to_h` carries
|
|
54
|
+
# whatever options the specification layer grew (count, median,
|
|
55
|
+
# group_by, scope_to). Declared emission covers the settled part
|
|
56
|
+
# and `super` hands it over; the tail stays dynamic, which is the
|
|
57
|
+
# honest description of it.
|
|
58
|
+
def to_h = super.merge(extra_options_to_h)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
require_relative "behaviour/read_model"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Bluebook
|
|
5
|
+
# A read model — an ask that gathers heads from more than one aggregate.
|
|
6
|
+
#
|
|
7
|
+
# Like a query it crosses over as an INSTANCE, and for the same reason: its
|
|
8
|
+
# body is inherited from `QuerySpecification::ReadModel::Specification`, whose
|
|
9
|
+
# readers the runtime and the SQLite adapter both call on the object. It gains
|
|
10
|
+
# an identity and an owner ; it keeps the name it always had, because only a
|
|
11
|
+
# CLASS ever had a competing answer for `name`.
|
|
12
|
+
class ReadModel < QuerySpecification::ReadModel::Specification
|
|
13
|
+
include Construct
|
|
14
|
+
|
|
15
|
+
include Hecks::IR
|
|
16
|
+
include Behaviour::ReadModel
|
|
17
|
+
|
|
18
|
+
emits_ir(
|
|
19
|
+
name: :name,
|
|
20
|
+
description: :description,
|
|
21
|
+
reference_name: :reference_name,
|
|
22
|
+
reference_target: :reference_target,
|
|
23
|
+
query_name: :query_name,
|
|
24
|
+
wheres: many(:wheres),
|
|
25
|
+
order_by: one(:order_by),
|
|
26
|
+
limit: one(:limit)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
attr_reader :name, :description, :reference_name, :reference_target, :aggregate_heads, :group_by,
|
|
30
|
+
:count, :median_field
|
|
31
|
+
|
|
32
|
+
# `reference_name:`/`reference_target:` are nil for a ROOTLESS read
|
|
33
|
+
# model (no `reference_to` declared) — `&.` throughout, rather than
|
|
34
|
+
# the `.to_s`/`.to_sym` this used to require unconditionally, so
|
|
35
|
+
# `reference_target.nil?` stays a real, checkable fact for the
|
|
36
|
+
# interpreter instead of silently becoming `""`.
|
|
37
|
+
def initialize(name:, description: nil, reference_name: nil, reference_target: nil, aggregate_heads: [],
|
|
38
|
+
group_by: [], count: nil, median_field: nil, **options)
|
|
39
|
+
super(joins: aggregate_heads, **options)
|
|
40
|
+
@name = name.to_s
|
|
41
|
+
@hecks_name = @name
|
|
42
|
+
@description = description
|
|
43
|
+
@reference_name = reference_name&.to_sym
|
|
44
|
+
@reference_target = reference_target&.to_s
|
|
45
|
+
@aggregate_heads = aggregate_heads
|
|
46
|
+
# Hash rows (`{field: :agg}`), same shape as `aggregate_heads` —
|
|
47
|
+
# `group_by_fields` is the convenience reader everything but
|
|
48
|
+
# `to_h`/the Judge's own generic walk actually wants.
|
|
49
|
+
@group_by = group_by
|
|
50
|
+
# ABSENT IS NOT FALSE. `@count` stays nil rather than becoming
|
|
51
|
+
# `false` when undeclared — the same "if you declare it, declare
|
|
52
|
+
# something" reading `Lifecycle`'s own optional fields rely on
|
|
53
|
+
# (MetaValidator::Judge#setters skips a setter whose every
|
|
54
|
+
# source is `nil`, so an ALWAYS-false `@count` would dispatch
|
|
55
|
+
# `ReadModel.Count` on every read model that never wrote the
|
|
56
|
+
# word at all). `!!count` collapses a truthy DSL `true` (or a
|
|
57
|
+
# reconstructed `true`) to the same `true`/`nil` pair either path
|
|
58
|
+
# produces — never `false`.
|
|
59
|
+
@count = count ? true : nil
|
|
60
|
+
@median_field = median_field&.to_sym
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# `.to_sym` regardless of source — the DIRECT builder path stores
|
|
64
|
+
# symbols, but `Reconstruction::Shapes#group_by_field` (the
|
|
65
|
+
# replayed-from-the-meta-domain path every REAL boot actually
|
|
66
|
+
# goes through) reads the field back as a String, the same way
|
|
67
|
+
# `aggregate_heads`' own `:as` does. Row hash KEYS built by
|
|
68
|
+
# `Value.materialize_unwrapped` are symbols (`attr.name`), so
|
|
69
|
+
# this has to be too, or `row[field]` in `nest` silently misses.
|
|
70
|
+
|
|
71
|
+
# `wheres`/`order_by`/`limit` are spelled explicitly here, the SAME
|
|
72
|
+
# mechanism `Query#to_h` already uses (query.rb, read directly
|
|
73
|
+
# before this was written) — always present, `wheres` a (possibly
|
|
74
|
+
# empty) array and `order_by`/`limit` nil when undeclared, exactly
|
|
75
|
+
# like a Query's. `extra_options_to_h` still excludes these three by
|
|
76
|
+
# name (options.rb), so nothing here double-spells them; the two
|
|
77
|
+
# constructs now share one encoding for the same three words rather
|
|
78
|
+
# than a reader needing to learn a second one. See this file's own
|
|
79
|
+
# `filtered_head_name` and language/bluebook/syntax.bluebook's
|
|
80
|
+
# `ReadModel` `where`/`order_by`/`limit` member rows for the history
|
|
81
|
+
# of why this WAS narrower, and 2026-08-11's read-model where/
|
|
82
|
+
# order_by/limit task for why it stopped being.
|
|
83
|
+
# Same dynamic tail as a Query's, plus two collections whose
|
|
84
|
+
# ROWS are plain hashes rather than constructs — they are
|
|
85
|
+
# normalised here rather than by `many`, which recurses through
|
|
86
|
+
# `to_h` and would have nothing to call.
|
|
87
|
+
# `count`/`median_field` are ABSENT (no key at all), not `nil`,
|
|
88
|
+
# when undeclared — unlike `group_by` (always a `[]`, never
|
|
89
|
+
# missing) they are the FIRST fields this construct has ever
|
|
90
|
+
# added that no OLDER real corpus member could possibly carry, so
|
|
91
|
+
# merging them unconditionally would have put a `null` onto every
|
|
92
|
+
# existing read model's own wire shape (banking's
|
|
93
|
+
# `ComplianceDashboard`/`CustomerPortfolio`, the language's own
|
|
94
|
+
# `WholeBluebook`, ...) for a fact nothing about them changed —
|
|
95
|
+
# the exact `extra_options_to_h` reads for `cursor`/`offset`/etc,
|
|
96
|
+
# applied here for the same reason.
|
|
97
|
+
def to_h
|
|
98
|
+
reductions = {}
|
|
99
|
+
reductions[:count] = true if @count
|
|
100
|
+
reductions[:median_field] = @median_field.to_s if @median_field
|
|
101
|
+
super
|
|
102
|
+
.merge(aggregate_heads: @aggregate_heads.map { |head| head.merge(as: head[:as].to_s) })
|
|
103
|
+
.merge(group_by: @group_by.map { |row| row.merge(field: row[:field].to_s) })
|
|
104
|
+
.merge(reductions)
|
|
105
|
+
.merge(extra_options_to_h)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Bluebook
|
|
3
|
+
# An attribute that points at another aggregate's head.
|
|
4
|
+
#
|
|
5
|
+
# This used to be the STRING `"Reference<Customer>"`, minted by
|
|
6
|
+
# `AggregateBuilder#reference_to` and `CommandBuilder#cross_reference` from
|
|
7
|
+
# a real constant that had just been handed in, and then parsed back apart
|
|
8
|
+
# by a regex in the command interpreter, by string equality in the read-model
|
|
9
|
+
# interpreter and the SQLite adapter, and by `delete_prefix` in the bluebook
|
|
10
|
+
# builder. Five readers of a spelling one writer invented.
|
|
11
|
+
#
|
|
12
|
+
# It holds the TARGET instead, and answers `resolve` with the target's
|
|
13
|
+
# Aggregate. Resolution is LAZY and deliberately so: `reference_to
|
|
14
|
+
# Customer` may name an aggregate declared lower in the file — banking's
|
|
15
|
+
# Account points at Customer and survives only because Customer happens to
|
|
16
|
+
# be written above — so the edge cannot be resolved at declaration time.
|
|
17
|
+
# It resolves through the chapter's own IR (`Bluebook#aggregate`),
|
|
18
|
+
# which is scoped by construction : the lookup cannot walk anywhere but
|
|
19
|
+
# this chapter's declared heads, so a same-named aggregate in another
|
|
20
|
+
# loaded domain is unreachable rather than defended against.
|
|
21
|
+
#
|
|
22
|
+
# `to_s` still spells `"Reference<Customer>"`, because `Attribute#to_h` is
|
|
23
|
+
# part of the pinned byte-for-byte export contract. IR objects in
|
|
24
|
+
# the graph, strings in the export.
|
|
25
|
+
class Reference
|
|
26
|
+
attr_reader :target_name
|
|
27
|
+
|
|
28
|
+
# The Aggregate whose declaration carries this reference — the way
|
|
29
|
+
# up to the chapter, stamped once every sibling has been read.
|
|
30
|
+
attr_accessor :declared_in
|
|
31
|
+
|
|
32
|
+
def initialize(target_name)
|
|
33
|
+
@target_name = Naming.demodulise(target_name).to_s
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The Aggregate this points at, or nil when the target belongs to
|
|
37
|
+
# ANOTHER domain — a cross-domain target may legitimately not be loaded,
|
|
38
|
+
# the same reading `across` policies get.
|
|
39
|
+
def resolve
|
|
40
|
+
unless declared_in
|
|
41
|
+
raise DSL::Malformed,
|
|
42
|
+
"#{self} cannot say which aggregate declares it, so it cannot " \
|
|
43
|
+
"resolve — a reference that resolves to nothing is checked " \
|
|
44
|
+
"against nothing"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
declared_in.hecks_owner&.aggregate(@target_name)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The IR spelling, the one the export carries.
|
|
51
|
+
def to_s = "Reference<#{@target_name}>"
|
|
52
|
+
def inspect = "#<Reference #{@target_name}>"
|
|
53
|
+
|
|
54
|
+
# VALUE EQUALITY, not identity — without this, two references to
|
|
55
|
+
# the SAME target, parsed from two SEPARATE bluebook reads (era
|
|
56
|
+
# N's own boot and a held era's own shadow reconstruction,
|
|
57
|
+
# coverage_check.rb's own comparison), are different objects and
|
|
58
|
+
# compare unequal by Ruby's default `==`. `EraGuard::ShapeDiff
|
|
59
|
+
# #diff_type`'s `held_type != current_type` check then reads as
|
|
60
|
+
# true for EVERY reference_to attribute on EVERY mint, regardless
|
|
61
|
+
# of whether the reference actually changed — a real refusal for
|
|
62
|
+
# attributes nothing about. `declared_in` (which aggregate carries
|
|
63
|
+
# this reference) is deliberately excluded: the same reference
|
|
64
|
+
# attribute exists once, but its `held_type`/`current_type` come
|
|
65
|
+
# from separately-parsed bluebooks with structurally different
|
|
66
|
+
# (if same-shaped) owning aggregates, and `declared_in` is a
|
|
67
|
+
# cross-reference for `resolve`, not part of what this attribute
|
|
68
|
+
# itself IS.
|
|
69
|
+
def ==(other) = other.is_a?(Reference) && target_name == other.target_name
|
|
70
|
+
alias eql? ==
|
|
71
|
+
def hash = [self.class, target_name].hash
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
require_relative "synthesizer"
|
|
2
|
+
require "tmpdir"
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Bluebook
|
|
7
|
+
# BOOTS A REAL BLUEBOOK AND ACTUALLY DISPATCHES AGAINST IT — the
|
|
8
|
+
# sibling `bin/model_check` never had: that tool proves a bluebook
|
|
9
|
+
# is STRUCTURALLY sound (no dead states, no unreachable transitions)
|
|
10
|
+
# without ever running a single command; this proves it WORKS,
|
|
11
|
+
# catching exactly the class of bug static analysis structurally
|
|
12
|
+
# cannot see. Built and proved inside `bin/interview`'s own `smoke`
|
|
13
|
+
# command, where it first found two real bugs — a lifecycle field
|
|
14
|
+
# colliding with an explicit attribute, and a non-creating command
|
|
15
|
+
# missing `Command.ActsOn` — that reloaded clean, checked clean,
|
|
16
|
+
# and only broke once something actually dispatched against them.
|
|
17
|
+
#
|
|
18
|
+
# SYNTHESIZED ARGS, NEVER RANDOM (`Synthesizer`) — a String becomes
|
|
19
|
+
# a fixed marker, a closed set uses its own first admitted member, a
|
|
20
|
+
# reference reuses whatever this same run already minted for that
|
|
21
|
+
# target. ONE call per declared command, ONE call per declared
|
|
22
|
+
# report, per created root; every failure is collected and reported
|
|
23
|
+
# together rather than stopping at the first.
|
|
24
|
+
#
|
|
25
|
+
# WHAT THIS CANNOT CATCH: a call that dispatches cleanly but answers
|
|
26
|
+
# WRONG — the read-model join bug this exact tool's own first user
|
|
27
|
+
# hit is the textbook case, a query that silently returned an empty
|
|
28
|
+
# array rather than raising. Smoke-testing proves nothing crashes;
|
|
29
|
+
# it does not prove the answer is the right one.
|
|
30
|
+
#
|
|
31
|
+
# A FAILURE HERE IS NOT NECESSARILY A DOMAIN BUG, either — it may
|
|
32
|
+
# just be `Synthesizer`'s own naive values (0, an empty list,
|
|
33
|
+
# "smoke-test") not satisfying a real invariant this simple
|
|
34
|
+
# generator was never built to reason about (a positive amount, a
|
|
35
|
+
# non-empty topping list, an email pattern). Confirmed against
|
|
36
|
+
# Banking and Pizzas: both real domains, both showing exactly that
|
|
37
|
+
# class of failure, neither one an actual bug. Building a
|
|
38
|
+
# synthesizer that respects arbitrary declared invariants is real
|
|
39
|
+
# constraint-aware generation — a separate, larger tool than this.
|
|
40
|
+
module SmokeTest
|
|
41
|
+
Failure = Struct.new(:domain, :aggregate, :command, :error, keyword_init: true) do
|
|
42
|
+
def to_s = "#{domain}::#{aggregate}.#{command}: #{error}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module_function
|
|
46
|
+
|
|
47
|
+
# `dir` is any real, bootable hecks app directory — a saved
|
|
48
|
+
# domain, or a throwaway one a caller rendered into a temp dir
|
|
49
|
+
# for exactly this purpose. Plain `Hecks.boot`, not
|
|
50
|
+
# `Router.boot` — proven all night, and it works against a
|
|
51
|
+
# domain with framework-attached chapters (`uses_framework
|
|
52
|
+
# "Governance"`, say) that `Router.boot` refuses outright: those
|
|
53
|
+
# chapters carry no `.world` of their own, which `Router`'s
|
|
54
|
+
# per-domain realm resolution requires and a plain boot never
|
|
55
|
+
# did. Reports are reachable the identical way regardless of
|
|
56
|
+
# boot path — `dispatcher.query("Domain.report_name", **args)`
|
|
57
|
+
# is the SAME `Dispatcher#query` either boot hands back; Router
|
|
58
|
+
# was never actually required for this, only its OWN namespace
|
|
59
|
+
# SUGAR (`Domain.report_name(...)` as a bare method call) was.
|
|
60
|
+
#
|
|
61
|
+
# NEVER BOOTS `dir`'S OWN REAL BINDINGS — measured, not a
|
|
62
|
+
# precaution taken on spec: pointed at `examples/pizzas` (a real,
|
|
63
|
+
# persistent store carrying real accumulated records), a
|
|
64
|
+
# synthesized `CreatePizza` collided with an actual pre-existing
|
|
65
|
+
# record literally named "smoke-test" — a coincidence this time,
|
|
66
|
+
# data pollution the next. `isolate!` copies only the `.bluebook`
|
|
67
|
+
# (and `.world`, for its realm) into a throwaway directory and
|
|
68
|
+
# deliberately leaves the real `.hecksagon` behind — with none
|
|
69
|
+
# present, `Ports::Persistence::BindingPolicy.default_binding`
|
|
70
|
+
# binds every aggregate to Memory on its own, no settings needed.
|
|
71
|
+
# Whatever `dir` is really bound to in production is never
|
|
72
|
+
# touched.
|
|
73
|
+
# `install_facade: false` — this only ever dispatches by FQN string
|
|
74
|
+
# (below), so it never needs the `Widget::Item.Add(...)` Ruby
|
|
75
|
+
# sugar `Hecks.boot` installs by default. Skipping it matters
|
|
76
|
+
# here specifically: that sugar lands as a BARE global constant on
|
|
77
|
+
# `Object` per domain and per aggregate name, with no scoping and
|
|
78
|
+
# no cleanup — and this tool boots throwaway domains under
|
|
79
|
+
# whatever generic names the caller's `.bluebook` happens to use
|
|
80
|
+
# ("Widget", "Item", "Tag" in this tool's own specs). Installing
|
|
81
|
+
# the facade would leak those names into the rest of the process —
|
|
82
|
+
# measured, not hypothetical: it once left a stale `Widget` constant
|
|
83
|
+
# that corrupted an unrelated spec's own unrelated use of the same
|
|
84
|
+
# bare name.
|
|
85
|
+
def call(dir)
|
|
86
|
+
Dir.mktmpdir("hecks-smoke-") do |scratch|
|
|
87
|
+
isolate!(dir, scratch)
|
|
88
|
+
dispatcher = Hecks.boot(scratch, install_facade: false)
|
|
89
|
+
domain = dispatcher.registry.bluebooks.keys.first
|
|
90
|
+
next [] unless domain
|
|
91
|
+
|
|
92
|
+
smoke_domain(dispatcher, domain)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# ONLY THE `.bluebook`, NEVER `.world` EITHER — a real world's own
|
|
97
|
+
# settings (`persisted_by("Heki") { dir "..." }`) are keyed to the
|
|
98
|
+
# REAL adapter it names, not to Memory; copied verbatim, they
|
|
99
|
+
# apply to the wrong binding and refuse with a `WiringError`
|
|
100
|
+
# ("Memory does not declare :dir") — measured, not assumed, the
|
|
101
|
+
# first time this ran against Banking. A plain `Hecks.boot`
|
|
102
|
+
# needs no realm to address anything (unlike `Router.boot`), so
|
|
103
|
+
# leaving the world out entirely is the correct minimal isolation,
|
|
104
|
+
# not a workaround. `translations/*.bluebook` also deliberately
|
|
105
|
+
# excluded — those are earlier, superseded versions of the same
|
|
106
|
+
# domain; smoke-testing the current declaration is the point, not
|
|
107
|
+
# its whole history.
|
|
108
|
+
def isolate!(dir, scratch)
|
|
109
|
+
source = Adapters::Folder.new.bluebook_directory(dir)
|
|
110
|
+
target = File.join(scratch, "bluebook")
|
|
111
|
+
FileUtils.mkdir_p(target)
|
|
112
|
+
Dir.glob(File.join(source, "*.bluebook")).each { |file| FileUtils.cp(file, target) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# AGGREGATES WALKED IN DECLARATION ORDER, on purpose — so whatever
|
|
116
|
+
# a LATER aggregate's own shape needs from an EARLIER one (a
|
|
117
|
+
# `TripItem` needing a real `PackingItem`, say) already exists by
|
|
118
|
+
# the time that aggregate's own turn comes, the same reason
|
|
119
|
+
# `Synthesizer#args_for`'s `created` map is threaded through in
|
|
120
|
+
# this same order.
|
|
121
|
+
def smoke_domain(dispatcher, domain)
|
|
122
|
+
chapter = dispatcher.registry.bluebook(domain)
|
|
123
|
+
return [] unless chapter
|
|
124
|
+
|
|
125
|
+
created = {}
|
|
126
|
+
failures = []
|
|
127
|
+
|
|
128
|
+
chapter.aggregates.each do |aggregate|
|
|
129
|
+
creating, noncreating = aggregate.commands.partition(&:creates?)
|
|
130
|
+
|
|
131
|
+
creating.each do |command|
|
|
132
|
+
args = Synthesizer.args_for(chapter, aggregate, command, created)
|
|
133
|
+
begin
|
|
134
|
+
result = dispatcher.dispatch("#{domain}::#{aggregate.name}.#{command.hecks_name}", **args)
|
|
135
|
+
created[aggregate.name] = result.instance.id
|
|
136
|
+
rescue StandardError => e
|
|
137
|
+
failures << Failure.new(domain: domain, aggregate: aggregate.name, command: command.hecks_name,
|
|
138
|
+
error: "#{e.class}: #{e.message}")
|
|
139
|
+
next
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
noncreating.each do |nc_command|
|
|
143
|
+
nc_args = Synthesizer.args_for(chapter, aggregate, nc_command, created).merge(id: created[aggregate.name])
|
|
144
|
+
dispatcher.dispatch("#{domain}::#{aggregate.name}.#{nc_command.hecks_name}", **nc_args)
|
|
145
|
+
rescue StandardError => e
|
|
146
|
+
failures << Failure.new(domain: domain, aggregate: aggregate.name, command: nc_command.hecks_name,
|
|
147
|
+
error: "#{e.class}: #{e.message}")
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
chapter.read_models.each do |model|
|
|
153
|
+
root_id = created[model.reference_target]
|
|
154
|
+
next unless root_id # nothing was created for this root — nothing to smoke-test yet, not a failure
|
|
155
|
+
|
|
156
|
+
dispatcher.query("#{domain}.#{model.query_name}", model.reference_name => root_id)
|
|
157
|
+
rescue StandardError => e
|
|
158
|
+
failures << Failure.new(domain: domain, aggregate: model.name, command: "report(#{model.name})",
|
|
159
|
+
error: "#{e.class}: #{e.message}")
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
failures
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Bluebook
|
|
3
|
+
# ONE SYNTHESIZED ARGUMENT PER DECLARED ATTRIBUTE — never a random
|
|
4
|
+
# guess. Reads only real `IR` (`Aggregate`, `Command`,
|
|
5
|
+
# `Attribute`, `ValueObject`), so it works against ANY
|
|
6
|
+
# loaded bluebook, not one particular domain: a String becomes a
|
|
7
|
+
# fixed marker; an Integer becomes 0; a closed set uses ITS OWN
|
|
8
|
+
# first admitted member (never a value a real `one_of` would
|
|
9
|
+
# legitimately refuse); a reference uses whatever id the CALLER
|
|
10
|
+
# already knows for that target (`created`), so a command whose
|
|
11
|
+
# own shape depends on an earlier-created record — a `TripItem`
|
|
12
|
+
# needing a real `PackingItem`, say — gets something real to work
|
|
13
|
+
# with instead of a guess that would only ever fail.
|
|
14
|
+
#
|
|
15
|
+
# Extracted from `bin/interview`'s own smoke test, where this first
|
|
16
|
+
# got built and proved — the logic never actually knew anything
|
|
17
|
+
# about the Interview domain or any one app; it only ever read IR.
|
|
18
|
+
module Synthesizer
|
|
19
|
+
# THE ONLY TYPE NAMES THAT ARE EVER TRULY SCALAR — anything else
|
|
20
|
+
# a value object's own field names is ANOTHER value object,
|
|
21
|
+
# nested (`Pizza.price_cents: Price`, itself wrapping `cents:
|
|
22
|
+
# Integer` — a real, ordinary shape, not an edge case). Missing
|
|
23
|
+
# this the first time round meant a nested field silently
|
|
24
|
+
# synthesized as the bare string `"smoke-test"` instead of the
|
|
25
|
+
# real nested structure a command actually declares — caught
|
|
26
|
+
# only by testing against Pizzas instead of re-testing against
|
|
27
|
+
# the one simpler domain this was first built for, where every
|
|
28
|
+
# value object happened to be single-field and primitive-typed.
|
|
29
|
+
# THE SAME LIST `Attribute::PRIMITIVES` holds, referenced rather
|
|
30
|
+
# than repeated. It was a second, byte-identical copy — and only
|
|
31
|
+
# the Attribute one is held to `vocabulary.bluebook` by
|
|
32
|
+
# spec/vocabulary_conformance_spec, so this copy could drift from
|
|
33
|
+
# the language and nothing would say so.
|
|
34
|
+
PRIMITIVES = Attribute::PRIMITIVES
|
|
35
|
+
|
|
36
|
+
module_function
|
|
37
|
+
|
|
38
|
+
# `chapter`/`aggregate` are the real `Bluebook`/`Aggregate`
|
|
39
|
+
# the command belongs to — needed to resolve a value object type
|
|
40
|
+
# name back to its own declared shape. `created` maps an
|
|
41
|
+
# aggregate NAME to a real id already minted for it earlier in
|
|
42
|
+
# the same run; a reference whose target isn't in there yet gets
|
|
43
|
+
# a placeholder instead of failing outright, since the caller may
|
|
44
|
+
# not care about that particular argument's real value.
|
|
45
|
+
def args_for(chapter, aggregate, command, created = {})
|
|
46
|
+
command.attributes.to_h do |attribute|
|
|
47
|
+
if attribute.reference?
|
|
48
|
+
[attribute.name, created.fetch(attribute.type.target_name, "smoke-test-id")]
|
|
49
|
+
else
|
|
50
|
+
[attribute.name, value_for(chapter, aggregate, attribute.type)]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# A value object's OWN synthesized value — its first admitted
|
|
56
|
+
# member if it's a closed set (the one value guaranteed not to be
|
|
57
|
+
# refused), otherwise one synthesized scalar per declared field.
|
|
58
|
+
# Falls back to searching every aggregate in the chapter, the
|
|
59
|
+
# same tolerance `bin/interview shape`'s own lookup already
|
|
60
|
+
# needed: a value object referenced by name doesn't have to be
|
|
61
|
+
# declared on the SAME aggregate using it.
|
|
62
|
+
def value_for(chapter, aggregate, type_name)
|
|
63
|
+
value_object = aggregate.value_object(type_name) ||
|
|
64
|
+
chapter.aggregates.filter_map { |a| a.value_object(type_name) }.first
|
|
65
|
+
return "smoke-test" unless value_object
|
|
66
|
+
|
|
67
|
+
return value_object.members.first.to_h if value_object.respond_to?(:closed_set?) && value_object.closed_set? && value_object.members.any?
|
|
68
|
+
|
|
69
|
+
value_object.attributes.to_h { |field| [field.name, field_value_for(chapter, aggregate, field.type)] }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# A single field's own synthesized value — a plain scalar if its
|
|
73
|
+
# type is one of the true primitives, otherwise that type names
|
|
74
|
+
# ANOTHER value object, resolved the identical way `value_for`
|
|
75
|
+
# resolves any other one (its own closed set or its own nested
|
|
76
|
+
# fields, however deep that nesting actually goes).
|
|
77
|
+
def field_value_for(chapter, aggregate, type_name)
|
|
78
|
+
PRIMITIVES.include?(type_name.to_s) ? scalar_for(type_name) : value_for(chapter, aggregate, type_name)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# A bare primitive's own synthesized value. Never used for a
|
|
82
|
+
# closed set's OWN field (that's `value_for`'s job, reading the
|
|
83
|
+
# set's real first member) — only for a plain scalar field with
|
|
84
|
+
# no declared vocabulary to respect.
|
|
85
|
+
def scalar_for(primitive)
|
|
86
|
+
case primitive.to_s
|
|
87
|
+
when "Integer" then 0
|
|
88
|
+
when "Float" then 0.0
|
|
89
|
+
when "TrueClass", "FalseClass" then false
|
|
90
|
+
else "smoke-test"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|