hecks 0.2.0 → 1.0.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 +427 -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 +101 -0
- data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
- data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
- data/lib/hecks/adapters/driven/heki.adapter +4 -0
- data/lib/hecks/adapters/driven/heki.rb +179 -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 +145 -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 +88 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
- data/lib/hecks/adapters/driven/postgres.adapter +5 -0
- data/lib/hecks/adapters/driven/postgres.rb +438 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +17 -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 +80 -0
- data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
- data/lib/hecks/adapters/driven/sqlite.rb +306 -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 +54 -0
- data/lib/hecks/adapters.rb +6 -0
- data/lib/hecks/behaviors/dsl.rb +125 -0
- data/lib/hecks/behaviors/expectations.rb +340 -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 +438 -0
- data/lib/hecks/bluebook/assembly/marks.rb +228 -0
- data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
- data/lib/hecks/bluebook/assembly.rb +91 -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 +68 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -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 +78 -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 +1018 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +714 -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 +430 -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 +135 -0
- data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -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 +173 -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 +228 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +117 -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 +123 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
- data/lib/hecks/bluebook/expression/projection.json +218 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
- data/lib/hecks/bluebook/expression/resolver.rb +781 -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 +621 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -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 +542 -0
- data/lib/hecks/bluebook/model_check.rb +445 -0
- data/lib/hecks/bluebook/pattern_subset.rb +184 -0
- data/lib/hecks/bluebook/policy.rb +41 -0
- data/lib/hecks/bluebook/process_manager.rb +128 -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 +66 -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 +410 -0
- data/lib/hecks/embryonaut_bluebook.rb +75 -0
- data/lib/hecks/facade/cli_door.rb +119 -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 +166 -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 +341 -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 +177 -0
- data/lib/hecks/forms/field_shape.rb +232 -0
- data/lib/hecks/forms/html.rb +84 -0
- data/lib/hecks/forms/index_renderer.rb +35 -0
- data/lib/hecks/forms/page.rb +157 -0
- data/lib/hecks/forms/params.rb +161 -0
- data/lib/hecks/forms/port_argument.rb +46 -0
- data/lib/hecks/forms/query_form_renderer.rb +114 -0
- data/lib/hecks/forms/record_renderer.rb +119 -0
- data/lib/hecks/forms/record_table.rb +68 -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 +145 -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/bounded_exhaustive_expressions.rb +527 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
- data/lib/hecks/fuzzing/properties.rb +1192 -0
- data/lib/hecks/fuzzing/replay.rb +668 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
- data/lib/hecks/fuzzing/value_generator.rb +200 -0
- data/lib/hecks/fuzzing.rb +12 -0
- data/lib/hecks/grammar/evolve.rb +324 -0
- data/lib/hecks/grammar/expression.bluebook +420 -0
- data/lib/hecks/grammar/expression_operators.json +1648 -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 +471 -0
- data/lib/hecks/language/bluebook/entity.bluebook +392 -0
- data/lib/hecks/language/bluebook/policy.bluebook +189 -0
- data/lib/hecks/language/bluebook/process_manager.bluebook +381 -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 +448 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -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 +333 -0
- data/lib/hecks/language/port.bluebook +120 -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 +174 -0
- data/lib/hecks/ports/access_control.port +9 -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 +8 -0
- data/lib/hecks/ports/agent.rb +167 -0
- data/lib/hecks/ports/authentication.port +6 -0
- data/lib/hecks/ports/authentication.rb +50 -0
- data/lib/hecks/ports/authorization.port +7 -0
- data/lib/hecks/ports/authorization.rb +62 -0
- data/lib/hecks/ports/clock.port +5 -0
- data/lib/hecks/ports/clock.rb +62 -0
- data/lib/hecks/ports/extraction.port +5 -0
- data/lib/hecks/ports/extraction.rb +37 -0
- data/lib/hecks/ports/identity_assignment.port +5 -0
- data/lib/hecks/ports/identity_assignment.rb +45 -0
- data/lib/hecks/ports/identity_generation.port +5 -0
- data/lib/hecks/ports/identity_generation.rb +49 -0
- data/lib/hecks/ports/identity_resolution.port +5 -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 +138 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
- data/lib/hecks/ports/persistence/execution.rb +23 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
- data/lib/hecks/ports/persistence/plugin.rb +54 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
- data/lib/hecks/ports/persistence/plugins/era.rb +48 -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 +96 -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 +642 -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 +44 -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 +446 -0
- data/lib/hecks/projector/docs_projector.rb +321 -0
- data/lib/hecks/projector/exporter.rb +158 -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 +411 -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 +92 -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 +107 -0
- data/lib/hecks/query_specification/hop_path.rb +132 -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/aggregate_lock.rb +45 -0
- data/lib/hecks/runtime/boot_gates.rb +41 -0
- data/lib/hecks/runtime/caller.rb +66 -0
- data/lib/hecks/runtime/capability_graph.rb +44 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
- data/lib/hecks/runtime/command_interpreter.rb +497 -0
- data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
- data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
- data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
- data/lib/hecks/runtime/command_rules/emission.rb +34 -0
- data/lib/hecks/runtime/command_rules/references.rb +189 -0
- data/lib/hecks/runtime/command_rules.rb +28 -0
- data/lib/hecks/runtime/dependency_planning.rb +245 -0
- data/lib/hecks/runtime/dispatcher.rb +301 -0
- data/lib/hecks/runtime/entity_element.rb +253 -0
- data/lib/hecks/runtime/entity_interpreter.rb +299 -0
- data/lib/hecks/runtime/errors.rb +122 -0
- data/lib/hecks/runtime/event.rb +51 -0
- data/lib/hecks/runtime/identity.rb +156 -0
- data/lib/hecks/runtime/instance.rb +175 -0
- data/lib/hecks/runtime/interpreting.rb +90 -0
- data/lib/hecks/runtime/loader.rb +184 -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 +286 -0
- data/lib/hecks/runtime/reaction_invocation.rb +253 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +341 -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 +126 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
- data/lib/hecks/runtime/registry/verification.rb +246 -0
- data/lib/hecks/runtime/registry.rb +279 -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 +503 -0
- data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -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 +130 -0
- data/lib/hecks/runtime/value/coercion.rb +565 -0
- data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
- data/lib/hecks/runtime/value.rb +125 -0
- data/lib/hecks/runtime.rb +107 -0
- data/lib/hecks/storehouse.rb +632 -0
- data/lib/hecks/version.rb +16 -0
- data/lib/hecks/vocabulary.rb +218 -0
- data/lib/hecks.rb +123 -6
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
- metadata +412 -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,340 @@
|
|
|
1
|
+
require_relative "ir"
|
|
2
|
+
require_relative "../runtime/loader"
|
|
3
|
+
require_relative "../runtime/errors"
|
|
4
|
+
require_relative "../runtime/value"
|
|
5
|
+
require_relative "../runtime/reaction_invocation"
|
|
6
|
+
require_relative "../ports/persistence/binding_policy"
|
|
7
|
+
|
|
8
|
+
# Hecks::Behaviors::Expectations
|
|
9
|
+
#
|
|
10
|
+
# One test case, start to finish: take the suite's own runtime (booted
|
|
11
|
+
# once for exactly what its `loads` names, reset to nothing between
|
|
12
|
+
# tests — `runtime_for`), replay `setup` dispatches, dispatch (or query)
|
|
13
|
+
# the command under test, check `expect`. Split out of
|
|
14
|
+
# runner.rb the same way the file-count/sweep concern is split from a
|
|
15
|
+
# single test's own execution.
|
|
16
|
+
#
|
|
17
|
+
# TWO DELIBERATE DEVIATIONS from a prior port of this same idea (read
|
|
18
|
+
# before writing this, not reinvented):
|
|
19
|
+
#
|
|
20
|
+
# 1. A `setup` refusal and a refusal from the command under test are
|
|
21
|
+
# caught in SEPARATE rescue scopes. The prior port wrapped the whole
|
|
22
|
+
# test body — setups included — in one `rescue *REFUSAL_CLASSES`, so a
|
|
23
|
+
# broken setup could spuriously satisfy `expect refused: "..."` if its
|
|
24
|
+
# own refusal happened to match the expected substring. Here, any
|
|
25
|
+
# domain refusal during `setup` is unconditionally an ERROR — the
|
|
26
|
+
# example never got to the situation it claims to test.
|
|
27
|
+
#
|
|
28
|
+
# 2. `emits:` is read off a `registry.event_log` DIFF around the tested
|
|
29
|
+
# dispatch, not off `Result#events` alone. `Result#events` only holds
|
|
30
|
+
# the events the OUTERMOST dispatch announced; a policy's own cascade
|
|
31
|
+
# reenters through the same `Dispatcher#dispatch` (`PolicyInterpreter
|
|
32
|
+
# #deliver` → `door.reenter` → `dispatch`), and every dispatch's
|
|
33
|
+
# events — outer and reentrant alike — land in the one shared
|
|
34
|
+
# `registry.event_log`, in order (`CommandRules::Emission#emit`,
|
|
35
|
+
# `PortOperationInterpreter#emit`/`#call`). Diffing that log's length
|
|
36
|
+
# before/after the tested dispatch is the one read that actually sees
|
|
37
|
+
# a cascade — this DSL has no `kind: :cascade` because cascades are
|
|
38
|
+
# always on and `emits:` is expected to see them.
|
|
39
|
+
module Hecks
|
|
40
|
+
module Behaviors
|
|
41
|
+
module Expectations
|
|
42
|
+
module_function
|
|
43
|
+
|
|
44
|
+
REFUSAL_CLASSES = Hecks::Runtime::DOMAIN_REFUSALS
|
|
45
|
+
SPECIAL_KEYS = %i[ok refused emits count].freeze
|
|
46
|
+
|
|
47
|
+
def run_one(test, suite, runtime: nil)
|
|
48
|
+
runtime ||= runtime_for(suite)
|
|
49
|
+
runtime.registry.reset_runtime_state!
|
|
50
|
+
bluebooks = runtime.registry.bluebooks.values
|
|
51
|
+
|
|
52
|
+
current_setup = nil
|
|
53
|
+
begin
|
|
54
|
+
test.setups.each do |setup|
|
|
55
|
+
current_setup = setup
|
|
56
|
+
dispatch_command(runtime, qualify(setup.command, nil, bluebooks, kind: :command), setup.args)
|
|
57
|
+
end
|
|
58
|
+
rescue *REFUSAL_CLASSES => e
|
|
59
|
+
return error_result(test, "setup #{current_setup&.command.inspect} refused: #{e.message}")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
run_tested(test, runtime, bluebooks)
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
error_result(test, "#{e.class}: #{e.message}")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# ONE BOOT PER SUITE, NOT PER TEST. The isolation a test needs is a
|
|
68
|
+
# runtime with nothing in it — and a boot of the same files gives
|
|
69
|
+
# exactly that back for the price of `Registry#reset_runtime_state!`
|
|
70
|
+
# instead of ~2s of loading, verifying and era-checking the same
|
|
71
|
+
# bluebook again (chess: 76 behaviours, 155s of which was booting
|
|
72
|
+
# `chess.bluebook` 76 times). Keyed by the suite's own `loads` AND
|
|
73
|
+
# their mtimes, so an edited bluebook boots fresh on the next test
|
|
74
|
+
# rather than running against a stale one — the property a watch
|
|
75
|
+
# loop or a long rspec session actually relies on. `runtime:` lets
|
|
76
|
+
# a caller that already holds a booted runtime (a spec, a REPL)
|
|
77
|
+
# hand it in; it is reset the same way.
|
|
78
|
+
RUNTIMES = {}
|
|
79
|
+
RUNTIMES_LOCK = Mutex.new
|
|
80
|
+
private_constant :RUNTIMES, :RUNTIMES_LOCK
|
|
81
|
+
|
|
82
|
+
def runtime_for(suite)
|
|
83
|
+
files = Array(suite.loads).map { |path| File.expand_path(path) }
|
|
84
|
+
key = files.map { |file| [file, File.exist?(file) ? File.mtime(file).to_f : nil] }
|
|
85
|
+
|
|
86
|
+
RUNTIMES_LOCK.synchronize do
|
|
87
|
+
RUNTIMES[key] ||= boot_and_guard(files)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# `reset_runtime_state!` only drops repository OBJECTS between
|
|
92
|
+
# tests (registry.rb) — sufficient isolation for `Memory`, whose
|
|
93
|
+
# `@records` is a plain per-instance ivar, so a fresh object really
|
|
94
|
+
# is a fresh store. Against anything else (Sqlite, Postgres) the
|
|
95
|
+
# rows themselves stay put: tests leak into each other, and a
|
|
96
|
+
# suite booted against a domain's REAL hecksagon writes to a real
|
|
97
|
+
# database. Refusing that wiring here, at boot, is the same shape
|
|
98
|
+
# of guard `BindingPolicy` already applies to a missing bind — the
|
|
99
|
+
# project's identity is refusing bad wiring up front, not
|
|
100
|
+
# discovering it mid-suite.
|
|
101
|
+
def boot_and_guard(files)
|
|
102
|
+
runtime = Hecks::Runtime::Loader.boot_files(files, install_facade: false)
|
|
103
|
+
guard_memory_only!(runtime)
|
|
104
|
+
runtime
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def guard_memory_only!(runtime)
|
|
108
|
+
runtime.registry.bluebooks.each_value do |bluebook|
|
|
109
|
+
bluebook.aggregates.each do |aggregate|
|
|
110
|
+
bind = Ports::Persistence::BindingPolicy.resolve(runtime.registry, bluebook.name, aggregate)
|
|
111
|
+
next if bind.adapter == Ports::Persistence::DEFAULT_ADAPTER
|
|
112
|
+
|
|
113
|
+
raise Malformed,
|
|
114
|
+
"#{bluebook.name}::#{aggregate.hecks_name} is persisted_by " \
|
|
115
|
+
"#{bind.adapter.inspect}, not #{Ports::Persistence::DEFAULT_ADAPTER.inspect} — " \
|
|
116
|
+
"a behaviors suite's `loads` must resolve every aggregate to an in-memory " \
|
|
117
|
+
"binding, or tests leak state into each other and write to a real database. " \
|
|
118
|
+
"Load a Memory-bound sibling hecksagon instead of the domain's real one — see " \
|
|
119
|
+
"examples/pizzas/bluebook/pizzas.behaviors's own `loads` comment for the pattern."
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def run_tested(test, runtime, bluebooks)
|
|
125
|
+
verb = qualify(test.tests_command, test.on_aggregate, bluebooks, kind: test.kind)
|
|
126
|
+
|
|
127
|
+
if test.query?
|
|
128
|
+
run_query(test, runtime, verb)
|
|
129
|
+
else
|
|
130
|
+
run_command(test, runtime, verb)
|
|
131
|
+
end
|
|
132
|
+
rescue *REFUSAL_CLASSES => e
|
|
133
|
+
check_refusal(test, e)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def run_command(test, runtime, verb)
|
|
137
|
+
before = runtime.registry.event_log.length
|
|
138
|
+
result = dispatch_command(runtime, verb, test.input)
|
|
139
|
+
|
|
140
|
+
return fail_result(test, "expected refused: #{test.expect[:refused].inspect} but dispatch succeeded") if test.expect.key?(:refused)
|
|
141
|
+
|
|
142
|
+
if (expected_emits = test.expect[:emits])
|
|
143
|
+
actual = runtime.registry.event_log[before..].map(&:name)
|
|
144
|
+
return fail_result(test, "expected emits: #{expected_emits.inspect}, got #{actual.inspect}") unless actual == expected_emits
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
check_ok(test) || check_fields(test, settled_state(runtime, verb, result)) || pass_result(test)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# A field expectation reads the aggregate AS IT STANDS once the
|
|
151
|
+
# dispatch and its whole cascade have run — the same "cascades are
|
|
152
|
+
# always on" reading `emits:` already commits to. `Result#state` is
|
|
153
|
+
# the wrong source for that: it snapshots the instance the OUTER
|
|
154
|
+
# dispatch saved, and a policy's own reentrant dispatch (a ply
|
|
155
|
+
# advancing off a Moved event, a move count bumping) hydrates and
|
|
156
|
+
# saves a FRESH record afterward — so a field the cascade wrote
|
|
157
|
+
# read back stale (found live: `expect move_count: 1` got 0 while
|
|
158
|
+
# `emits:` saw MoveCountBumped in the same test). The repository
|
|
159
|
+
# holds the settled record; read it back by the id the dispatch
|
|
160
|
+
# itself answered with.
|
|
161
|
+
def settled_state(runtime, verb, result)
|
|
162
|
+
return result.state || {} unless result.respond_to?(:id) && result.id
|
|
163
|
+
|
|
164
|
+
domain, rest = verb.split("::", 2)
|
|
165
|
+
aggregate_name = rest.to_s.split(".", 2).first
|
|
166
|
+
aggregate = runtime.registry.bluebook(domain)&.aggregate(aggregate_name)
|
|
167
|
+
return result.state || {} unless aggregate
|
|
168
|
+
|
|
169
|
+
record = runtime.registry.repository(domain, aggregate).find(result.id)
|
|
170
|
+
record ? record.state : (result.state || {})
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# A behaviors test writes a dispatch the way the guide's own chess
|
|
174
|
+
# examples do — receiver identity and command facts side by side
|
|
175
|
+
# (`label: "g", id: "wn", to: { file: 2, rank: 2 }`) — and since
|
|
176
|
+
# #335 the dispatcher's own `to:` keyword is the ROUTING envelope,
|
|
177
|
+
# so forwarding those kwargs loose collides the moment a domain
|
|
178
|
+
# declares a command fact named `to` (chess does: every Move's own
|
|
179
|
+
# destination). Found live: every such test failed with "to: does
|
|
180
|
+
# not recognize file, rank" while this guide promised the spelling
|
|
181
|
+
# works. `ReactionInvocation.build` is #335's own seam for turning
|
|
182
|
+
# mixed facts into the strict envelope — identities lifted into
|
|
183
|
+
# `to:`, declared facts into `with:` — so a behaviors dispatch now
|
|
184
|
+
# goes through the exact same separation a policy's projection
|
|
185
|
+
# does. A verb that resolves to no command (a port operation —
|
|
186
|
+
# "Pizzas::Order.PaymentGateway.Receive") keeps the loose
|
|
187
|
+
# passthrough: its own input already spells the port form's
|
|
188
|
+
# `to:`/`with:`, which the dispatcher's port branch reads directly.
|
|
189
|
+
def dispatch_command(runtime, verb, args)
|
|
190
|
+
invocation = begin
|
|
191
|
+
Runtime::ReactionInvocation.build(registry: runtime.registry, verb: verb,
|
|
192
|
+
projected: args, explicit: true)
|
|
193
|
+
rescue Runtime::UnknownVerb
|
|
194
|
+
nil
|
|
195
|
+
end
|
|
196
|
+
return runtime.dispatch(verb, **args) unless invocation
|
|
197
|
+
|
|
198
|
+
if invocation.key?(:to)
|
|
199
|
+
runtime.dispatch(verb, to: invocation[:to], with: invocation[:with])
|
|
200
|
+
else
|
|
201
|
+
runtime.dispatch(verb, with: invocation[:with])
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def run_query(test, runtime, verb)
|
|
206
|
+
rows = runtime.query(verb, **test.input)
|
|
207
|
+
|
|
208
|
+
return fail_result(test, "expected refused: #{test.expect[:refused].inspect} but the query succeeded") if test.expect.key?(:refused)
|
|
209
|
+
|
|
210
|
+
if (expected = test.expect[:count])
|
|
211
|
+
count = if rows.is_a?(Array)
|
|
212
|
+
rows.size
|
|
213
|
+
else
|
|
214
|
+
(rows.nil? ? 0 : 1)
|
|
215
|
+
end
|
|
216
|
+
return fail_result(test, "expected count: #{expected}, got #{count}") if count != expected
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
return check_ok(test) || pass_result(test) unless field_expectations?(test)
|
|
220
|
+
|
|
221
|
+
row = query_row(test, rows)
|
|
222
|
+
return row if row.is_a?(Result)
|
|
223
|
+
|
|
224
|
+
check_ok(test) || check_fields(test, row) || pass_result(test)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def field_expectations?(test)
|
|
228
|
+
test.expect.keys.any? { |key| !SPECIAL_KEYS.include?(key) }
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# A field expectation on a query names one row's shape, so it only
|
|
232
|
+
# makes sense once the query has settled on exactly one — the same
|
|
233
|
+
# reason `expect status: "sold"` on a multi-row answer would be
|
|
234
|
+
# ambiguous about which row it's describing. `check_fields` itself
|
|
235
|
+
# stays row-shaped (it already is, for `run_command`'s settled
|
|
236
|
+
# state); this picks which row it reads.
|
|
237
|
+
def query_row(test, rows)
|
|
238
|
+
return rows unless rows.is_a?(Array)
|
|
239
|
+
|
|
240
|
+
case rows.size
|
|
241
|
+
when 1 then rows.first
|
|
242
|
+
when 0 then fail_result(test, "expect names a field, but the query returned no rows")
|
|
243
|
+
else fail_result(test, "expect names a field, but the query returned #{rows.size} rows — " \
|
|
244
|
+
"narrow it with `input`/`expect count: 1` first")
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def check_ok(test)
|
|
249
|
+
return unless test.expect.key?(:ok)
|
|
250
|
+
|
|
251
|
+
expected = test.expect[:ok]
|
|
252
|
+
return if expected == true || expected.nil?
|
|
253
|
+
|
|
254
|
+
fail_result(test, "expect ok: only accepts true — got #{expected.inspect}")
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def check_fields(test, state)
|
|
258
|
+
test.expect.each do |key, expected|
|
|
259
|
+
next if SPECIAL_KEYS.include?(key)
|
|
260
|
+
|
|
261
|
+
unless state.key?(key) || state.key?(key.to_s)
|
|
262
|
+
return fail_result(test, "expect #{key}: names no field on the tested aggregate — " \
|
|
263
|
+
"valid expect keys are ok:, refused:, emits:, count: (queries only), " \
|
|
264
|
+
"or a real field name")
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
actual = normalize(state.key?(key) ? state[key] : state[key.to_s])
|
|
268
|
+
exp = normalize(expected)
|
|
269
|
+
return fail_result(test, "expected #{key}: #{expected.inspect}, got #{actual.inspect}") unless actual == exp
|
|
270
|
+
end
|
|
271
|
+
nil
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def check_refusal(test, error)
|
|
275
|
+
expected = test.expect[:refused]
|
|
276
|
+
return error_result(test, "unexpected refusal (#{error.class}): #{error.message}") unless expected
|
|
277
|
+
|
|
278
|
+
msg = error.message.to_s
|
|
279
|
+
# hecks's given/ensures refusals render "Command refused —
|
|
280
|
+
# <description>" (RefusalWording) ; a behaviors file names just
|
|
281
|
+
# the description — end_with?/include? bridges the prefix.
|
|
282
|
+
return pass_result(test) if msg == expected || msg.end_with?(expected) || msg.include?(expected)
|
|
283
|
+
|
|
284
|
+
fail_result(test, "expected refused: #{expected.inspect}, got #{msg.inspect}")
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# The real corpus this DSL is proven against writes VO-typed
|
|
288
|
+
# `expect` values both ways — bare (`expect kind: "bishop"`) and
|
|
289
|
+
# wrapped (`expect kind: { value: "bishop" }`). A live record's
|
|
290
|
+
# field always comes back as a `Hecks::Runtime::Value`;
|
|
291
|
+
# normalizing BOTH sides to the same bare-scalar-or-plain-hash
|
|
292
|
+
# shape is the one comparison that accepts either spelling.
|
|
293
|
+
def normalize(value)
|
|
294
|
+
return Hecks::Runtime::Value.materialize_unwrapped(value) if value.is_a?(Hecks::Runtime::Value)
|
|
295
|
+
return normalize(value[:value]) if value.is_a?(Hash) && value.keys == [:value]
|
|
296
|
+
|
|
297
|
+
value
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# A bare `tests`/`setup` command name carries no domain — `loads`
|
|
301
|
+
# can name more than one bluebook, so resolution searches every
|
|
302
|
+
# aggregate across every bluebook the suite booted for the one that
|
|
303
|
+
# actually declares the command. `on:` (when given, only ever on
|
|
304
|
+
# the TESTED command — `setup` never receives it, see the DSL
|
|
305
|
+
# contract) narrows the search to one aggregate by name instead of
|
|
306
|
+
# searching all of them.
|
|
307
|
+
def qualify(command, on_aggregate, bluebooks, kind:)
|
|
308
|
+
return command.to_s if command.to_s.include?(".")
|
|
309
|
+
|
|
310
|
+
members = kind == :query ? :queries : :commands
|
|
311
|
+
pairs =
|
|
312
|
+
if on_aggregate
|
|
313
|
+
bluebooks.filter_map { |bb| (agg = bb.aggregate(on_aggregate)) && [bb, agg] }
|
|
314
|
+
else
|
|
315
|
+
bluebooks.flat_map { |bb| bb.aggregates.map { |agg| [bb, agg] } }
|
|
316
|
+
end
|
|
317
|
+
candidates = pairs.select { |_, agg| agg.public_send(members).any? { |m| m.hecks_name == command.to_s } }
|
|
318
|
+
|
|
319
|
+
case candidates.size
|
|
320
|
+
when 0
|
|
321
|
+
raise ArgumentError, "no aggregate among #{bluebooks.map(&:name).inspect} declares a #{kind} " \
|
|
322
|
+
"named #{command.inspect} — say `on:` if it's ambiguous, or check the spelling"
|
|
323
|
+
when 1
|
|
324
|
+
bluebook, aggregate = candidates.first
|
|
325
|
+
"#{bluebook.name}::#{aggregate.name}.#{command}"
|
|
326
|
+
else
|
|
327
|
+
owners = candidates.map { |bb, agg| "#{bb.name}::#{agg.name}" }
|
|
328
|
+
raise ArgumentError, "#{command.inspect} is declared on more than one aggregate (#{owners.join(', ')}) " \
|
|
329
|
+
"— say `on:` to disambiguate, or use the dotted FQN"
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def pass_result(test) = Result.new(description: test.description, status: :pass, message: nil)
|
|
334
|
+
def fail_result(test, message) = Result.new(description: test.description, status: :fail, message: message)
|
|
335
|
+
def error_result(test, message) = Result.new(description: test.description, status: :error, message: message)
|
|
336
|
+
|
|
337
|
+
Result = Struct.new(:description, :status, :message, keyword_init: true)
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Hecks::Behaviors::{TestSetup,TestCase,BehaviorsSuite}
|
|
2
|
+
#
|
|
3
|
+
# The `.behaviors` authoring surface's own IR — plain Structs, holding
|
|
4
|
+
# exactly what `Hecks.behaviors "Name" do ... end` collected. Deliberately
|
|
5
|
+
# NOT part of `Hecks::IR`/`emits_ir` — a behaviors suite is never
|
|
6
|
+
# collected into a domain Registry (see `Hecks.behaviors`), never
|
|
7
|
+
# dispatched through MetaValidator, and carries none of the self-hosted
|
|
8
|
+
# round-trip machinery a real bluebook construct does. It is a test
|
|
9
|
+
# artifact a runner reads on demand, not a domain a boot needs.
|
|
10
|
+
module Hecks
|
|
11
|
+
module Behaviors
|
|
12
|
+
TestSetup = Struct.new(:command, :args, keyword_init: true)
|
|
13
|
+
|
|
14
|
+
TestCase = Struct.new(:description, :tests_command, :on_aggregate, :kind,
|
|
15
|
+
:setups, :input, :expect, keyword_init: true) do
|
|
16
|
+
# An already-dotted tests_command is a literal FQN; otherwise `on:`
|
|
17
|
+
# composes with the domain name resolved once setups/the tested
|
|
18
|
+
# dispatch actually run (see Expectations — the domain isn't known
|
|
19
|
+
# until `loads` is booted, so it's a runtime concern, not a field
|
|
20
|
+
# here).
|
|
21
|
+
def dotted? = tests_command.to_s.include?(".")
|
|
22
|
+
|
|
23
|
+
def query? = kind == :query
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# `loads` — absolute paths, already resolved against the `.behaviors`
|
|
27
|
+
# file's own directory by the DSL (`BehaviorsBuilder#loads`). `path` is
|
|
28
|
+
# the `.behaviors` file itself, kept for error messages.
|
|
29
|
+
BehaviorsSuite = Struct.new(:name, :vision, :loads, :tests, :path, keyword_init: true)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require_relative "../behaviors"
|
|
2
|
+
|
|
3
|
+
# Hecks::Behaviors::RSpec.describe_file(path) — the shim a consumer's
|
|
4
|
+
# `bundle exec rspec` uses to run `.behaviors` files as ordinary examples,
|
|
5
|
+
# one `it` per test, named by the test's own description string. Same
|
|
6
|
+
# shape `spec/guides_spec.rb` uses for doctested guides: the file is
|
|
7
|
+
# PARSED at collection time (cheap — `Behaviors.parse`, no test actually
|
|
8
|
+
# run yet, just enough to know the `it` names), and each test's own
|
|
9
|
+
# `Expectations.run_one` runs lazily inside its own `it`, exactly when
|
|
10
|
+
# rspec actually executes it.
|
|
11
|
+
#
|
|
12
|
+
# require "hecks/behaviors/rspec"
|
|
13
|
+
#
|
|
14
|
+
# Dir.glob("bluebook/**/*.behaviors").each do |path|
|
|
15
|
+
# Hecks::Behaviors::RSpec.describe_file(path)
|
|
16
|
+
# end
|
|
17
|
+
module Hecks
|
|
18
|
+
module Behaviors
|
|
19
|
+
module RSpec
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
def describe_file(path)
|
|
23
|
+
parsed = Behaviors.parse(path)
|
|
24
|
+
|
|
25
|
+
::RSpec.describe(File.basename(path)) do
|
|
26
|
+
if parsed.parse_error
|
|
27
|
+
it "loads without a parse error" do
|
|
28
|
+
raise parsed.parse_error
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
parsed.suite.tests.each do |test|
|
|
32
|
+
it(test.description) do
|
|
33
|
+
run = Expectations.run_one(test, parsed.suite)
|
|
34
|
+
raise run.message if run.status != :pass
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require_relative "expectations"
|
|
2
|
+
|
|
3
|
+
# Hecks::Behaviors.run(path) / .run_all(dir)
|
|
4
|
+
#
|
|
5
|
+
# Discovery + aggregation. `run_one` (expectations.rb) is the impure edge
|
|
6
|
+
# that actually touches a live runtime; this file finds `.behaviors`
|
|
7
|
+
# files, loads them, and maps their tests through it.
|
|
8
|
+
module Hecks
|
|
9
|
+
module Behaviors
|
|
10
|
+
FileResult = Struct.new(:path, :parse_error, :runs, keyword_init: true)
|
|
11
|
+
SweepResult = Struct.new(:root, :files_swept, :files, :summary, keyword_init: true)
|
|
12
|
+
ParseResult = Struct.new(:path, :suite, :parse_error, keyword_init: true)
|
|
13
|
+
|
|
14
|
+
class LoadOutsideRunner < StandardError; end
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# The `.behaviors` file currently being `Kernel.load`ed, if any —
|
|
18
|
+
# bound only for the duration of that load (`run`'s own `ensure`),
|
|
19
|
+
# never left set. `Hecks.behaviors` (lib/hecks/behaviors.rb)
|
|
20
|
+
# reads this to resolve its own `loads` line relative to the right
|
|
21
|
+
# file, and to refuse a `.behaviors` file loaded any other way.
|
|
22
|
+
attr_accessor :loading_path
|
|
23
|
+
|
|
24
|
+
# The suite the most recent `Hecks.behaviors` call built — read
|
|
25
|
+
# once, right after `Kernel.load`, by `run` below, which resets it
|
|
26
|
+
# to nil before every load so a file that loads without calling
|
|
27
|
+
# `Hecks.behaviors` at all is never confused for a stale previous
|
|
28
|
+
# suite (a real bug in a prior port of this idea: compared only
|
|
29
|
+
# against nil, so after the first successful file in a sweep it
|
|
30
|
+
# stayed non-nil forever).
|
|
31
|
+
attr_accessor :last_suite
|
|
32
|
+
|
|
33
|
+
# `Kernel.load`s one `.behaviors` file and returns its suite, with
|
|
34
|
+
# NO test actually executed yet — the cheap half, split out so a
|
|
35
|
+
# caller that only needs to know what tests exist (the rspec shim,
|
|
36
|
+
# naming its `it`s at collection time) doesn't pay for running them
|
|
37
|
+
# until it actually wants to. `Behaviors.loading_path` is bound only
|
|
38
|
+
# for the duration of the load, and `last_suite` is reset to nil
|
|
39
|
+
# BEFORE it — a file that loads without ever calling
|
|
40
|
+
# `Hecks.behaviors` is unambiguously a parse error, never a stale
|
|
41
|
+
# suite from whatever loaded before it in a sweep (a real bug in a
|
|
42
|
+
# prior port of this idea: compared only against nil, so after the
|
|
43
|
+
# first successful file in a sweep it stayed non-nil forever).
|
|
44
|
+
def parse(path)
|
|
45
|
+
path = File.expand_path(path)
|
|
46
|
+
previous_path = loading_path
|
|
47
|
+
self.loading_path = path
|
|
48
|
+
self.last_suite = nil
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
Kernel.load(path)
|
|
52
|
+
rescue StandardError, ScriptError => e
|
|
53
|
+
return ParseResult.new(path: path, suite: nil, parse_error: "#{e.class}: #{e.message}")
|
|
54
|
+
ensure
|
|
55
|
+
self.loading_path = previous_path
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
suite = last_suite
|
|
59
|
+
return ParseResult.new(path: path, suite: nil, parse_error: "file loaded but called no Hecks.behaviors") unless suite
|
|
60
|
+
|
|
61
|
+
ParseResult.new(path: path, suite: suite, parse_error: nil)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# One `.behaviors` file → `FileResult`, every test actually run.
|
|
65
|
+
def run(path)
|
|
66
|
+
parsed = parse(path)
|
|
67
|
+
return FileResult.new(path: parsed.path, parse_error: parsed.parse_error, runs: []) if parsed.parse_error
|
|
68
|
+
|
|
69
|
+
runs = parsed.suite.tests.map { |test| Expectations.run_one(test, parsed.suite) }
|
|
70
|
+
FileResult.new(path: parsed.path, parse_error: nil, runs: runs)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Sweeps every `.behaviors` file under `dir`. Reports the file count
|
|
74
|
+
# it actually found — a sweep that goes green without saying how
|
|
75
|
+
# much it looked at is indistinguishable from one that found
|
|
76
|
+
# nothing to look at.
|
|
77
|
+
def run_all(dir)
|
|
78
|
+
files = Dir.glob(File.join(dir, "**", "*.behaviors"))
|
|
79
|
+
results = files.map { |path| run(path) }
|
|
80
|
+
SweepResult.new(root: dir, files_swept: files.size, files: results, summary: summarize(results))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def summarize(results)
|
|
84
|
+
runs = results.flat_map(&:runs)
|
|
85
|
+
{
|
|
86
|
+
files: results.size,
|
|
87
|
+
parse_errors: results.count(&:parse_error),
|
|
88
|
+
total: runs.size,
|
|
89
|
+
passed: runs.count { |r| r.status == :pass },
|
|
90
|
+
failed: runs.count { |r| r.status == :fail },
|
|
91
|
+
errored: runs.count { |r| r.status == :error }
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require_relative "behaviors/dsl"
|
|
2
|
+
require_relative "behaviors/expectations"
|
|
3
|
+
require_relative "behaviors/runner"
|
|
4
|
+
|
|
5
|
+
# The `.behaviors` toolkit `bin/behaviors` and a consumer's own rspec shim
|
|
6
|
+
# (`hecks/behaviors/rspec`) drive. Not required by lib/hecks.rb on
|
|
7
|
+
# purpose — a booted domain never needs it, the same reason `Fuzzing` (see
|
|
8
|
+
# lib/hecks/fuzzing.rb, the shape this file mirrors) is opt-in too.
|
|
9
|
+
module Hecks
|
|
10
|
+
class << self
|
|
11
|
+
# `Hecks.behaviors "Name" do ... end` — deliberately NOT routed
|
|
12
|
+
# through `collect` the way `bluebook`/`hecksagon`/`world` are: a
|
|
13
|
+
# behaviors suite is a test artifact a runner reads on demand, never
|
|
14
|
+
# a thing a live domain boot needs, so it has no business landing in
|
|
15
|
+
# `Runtime.current_registry`.
|
|
16
|
+
def behaviors(name, &)
|
|
17
|
+
path = Behaviors.loading_path or
|
|
18
|
+
raise Behaviors::LoadOutsideRunner,
|
|
19
|
+
"Hecks.behaviors called outside Hecks::Behaviors.run(path) — " \
|
|
20
|
+
"a .behaviors file is only ever loaded by the behaviors runner"
|
|
21
|
+
|
|
22
|
+
Behaviors.last_suite = Behaviors::BehaviorsBuilder.build(name, source_path: path, &)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
require_relative "behaviour/aggregate"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Bluebook
|
|
5
|
+
# A FIELD READ THROUGH A `reference_to`, HELD LOCALLY (S12, ADR 0025
|
|
6
|
+
# — "Consistency across aggregate boundaries") — `projects
|
|
7
|
+
# :customer_status, from: :"customer.status"` declares that this
|
|
8
|
+
# aggregate's own `:customer_status` is a COPY of the target's own
|
|
9
|
+
# `:status`, kept fresh by a rebuild sweep rather than read live at
|
|
10
|
+
# rule-evaluation time. `reference` names the LOCAL reference
|
|
11
|
+
# attribute to walk through (`:customer`, minted by this
|
|
12
|
+
# aggregate's own `reference_to Customer`); `remote_field` names
|
|
13
|
+
# the SCALAR on the target aggregate to copy.
|
|
14
|
+
ProjectedField = Struct.new(:name, :reference, :remote_field, keyword_init: true)
|
|
15
|
+
|
|
16
|
+
# A construct like every other: the aggregate carries its own identity
|
|
17
|
+
# and sits in the owner chain between its chapter (a Bluebook) and
|
|
18
|
+
# everything declared on it. The chain is model objects end to end —
|
|
19
|
+
# reference resolution and hecks_fqn both walk it.
|
|
20
|
+
#
|
|
21
|
+
# THE HOLDING HALF, and nothing else. Every line below restates what
|
|
22
|
+
# `language/bluebook/aggregate.bluebook` already declares — the field
|
|
23
|
+
# list, said again as readers, again as constructor keywords, and
|
|
24
|
+
# again as an emission. That triplication is what a generator removes;
|
|
25
|
+
# `Behaviour::Aggregate` carries everything that is NOT derivable from
|
|
26
|
+
# the declaration, so regenerating this file can never be lossy.
|
|
27
|
+
#
|
|
28
|
+
# PROTOTYPE: hand-written in the shape a generator would emit, to
|
|
29
|
+
# prove the seam before the generator exists. `bin/project_model`
|
|
30
|
+
# would own this file; behaviour/aggregate.rb stays hand-written.
|
|
31
|
+
class Aggregate
|
|
32
|
+
include Construct
|
|
33
|
+
include Hecks::IR
|
|
34
|
+
include Behaviour::Aggregate
|
|
35
|
+
|
|
36
|
+
emits_ir(
|
|
37
|
+
name: :name,
|
|
38
|
+
description: :description,
|
|
39
|
+
identified_by: :identity_paths,
|
|
40
|
+
attributes: many(:attributes),
|
|
41
|
+
value_objects: many(:value_objects),
|
|
42
|
+
commands: many(:commands),
|
|
43
|
+
invariants: -> { invariants.map { |rule| { description: rule.description, canonical: rule.canonical } } },
|
|
44
|
+
# A PRECONDITION SHARED ACROSS COMMANDS, DECLARED ONCE (S10, ADR
|
|
45
|
+
# 0025) — the aggregate's OWN named `given`s, the declaration a
|
|
46
|
+
# referencing command's own (already-resolved) `givens` entry
|
|
47
|
+
# came from. Both sides of "declared once, referenced many"
|
|
48
|
+
# are real IR, the same shape a value object's TYPE and an
|
|
49
|
+
# attribute's own reference to it both are.
|
|
50
|
+
preconditions: -> { preconditions.map { |rule| { description: rule.description, canonical: rule.canonical } } },
|
|
51
|
+
# S12, ADR 0025 — deliberately NOT folded into `attributes`:
|
|
52
|
+
# `EraGuard::ShapeDiff` only ever walks `attributes` to decide
|
|
53
|
+
# whether a NEW field leaves an existing record with something
|
|
54
|
+
# genuinely absent, and a projected field's own absence story
|
|
55
|
+
# is different — a record predating the `projects` declaration
|
|
56
|
+
# is expected to be missing it until the rebuild sweep runs,
|
|
57
|
+
# not a shape drift a translation needs to explain.
|
|
58
|
+
projected_fields: -> {
|
|
59
|
+
projected_fields.map { |field|
|
|
60
|
+
{ name: field.name.to_s, reference: field.reference.to_s, remote_field: field.remote_field.to_s }
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
lifecycle: one(:lifecycle),
|
|
64
|
+
entities: many(:entities),
|
|
65
|
+
queries: many(:queries),
|
|
66
|
+
# ADDITIVE — every domain that declares no port emits `ports: []`,
|
|
67
|
+
# the same "regenerate deliberately" wire-format change
|
|
68
|
+
# ir_golden_spec.rb's own header describes; no existing key
|
|
69
|
+
# moves. See docs/decisions (rust/project/ports.rb) for the
|
|
70
|
+
# first reader of this.
|
|
71
|
+
ports: many(:ports),
|
|
72
|
+
provenance: :provenance
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
attr_reader :name, :description, :attributes, :value_objects, :commands, :invariants, :preconditions,
|
|
76
|
+
:projected_fields, :identified_by, :identity_paths, :identity_heads, :lifecycle,
|
|
77
|
+
:entities, :queries, :policies, :ports, :reference_targets, :provenance
|
|
78
|
+
|
|
79
|
+
# Assigns what the language declares, then hands off to the
|
|
80
|
+
# behaviour's own `settle` — derived identity, name indexes and
|
|
81
|
+
# owner stamping, none of which the declaration states.
|
|
82
|
+
def initialize(name:, description: nil, attributes: [], value_objects: [],
|
|
83
|
+
commands: [], invariants: [], preconditions: [], projected_fields: [], identified_by: [], lifecycle: nil,
|
|
84
|
+
entities: [], queries: [], policies: [], ports: [], reference_targets: [],
|
|
85
|
+
provenance: nil)
|
|
86
|
+
@name = name.to_s
|
|
87
|
+
@hecks_name = @name
|
|
88
|
+
@description = description
|
|
89
|
+
@attributes = attributes
|
|
90
|
+
@value_objects = value_objects
|
|
91
|
+
@commands = commands
|
|
92
|
+
@invariants = invariants
|
|
93
|
+
@preconditions = preconditions
|
|
94
|
+
@projected_fields = projected_fields
|
|
95
|
+
@identified_by = identified_by
|
|
96
|
+
@lifecycle = lifecycle
|
|
97
|
+
@entities = entities
|
|
98
|
+
@queries = queries
|
|
99
|
+
@policies = policies
|
|
100
|
+
@ports = ports
|
|
101
|
+
@reference_targets = reference_targets
|
|
102
|
+
@provenance = provenance
|
|
103
|
+
|
|
104
|
+
settle
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|