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,90 @@
|
|
|
1
|
+
Hecks.bluebook "Identity" do
|
|
2
|
+
vision "Authentication proves an identifier; the organization decides what that identity means."
|
|
3
|
+
# S13, ADR 0025 (coverage standard) — GENERIC, not `supporting`: an
|
|
4
|
+
# OIDC-shaped external identifier is the textbook generic subdomain
|
|
5
|
+
# (Vernon's own example of one), off-the-shelf and buyable —
|
|
6
|
+
# `Governance` (role/scope RBAC beside it) stays `supporting`
|
|
7
|
+
# instead, since ITS OWN model is bespoke to this codebase's actor/
|
|
8
|
+
# role/scope shape, not a drop-in replaceable concept the way an
|
|
9
|
+
# identity provider is.
|
|
10
|
+
generic
|
|
11
|
+
|
|
12
|
+
aggregate "Identity" do
|
|
13
|
+
description "A stable organizational identity, independent of how it was authenticated."
|
|
14
|
+
|
|
15
|
+
attribute :identity_id, IdentityId
|
|
16
|
+
|
|
17
|
+
identified_by :identity_id
|
|
18
|
+
|
|
19
|
+
value_object "IdentityId" do
|
|
20
|
+
attribute :value, String
|
|
21
|
+
invariant("an identity id is present") { !value.to_s.empty? }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# No natural key exists for "a newly recognized identity" — the
|
|
25
|
+
# caller mints identity_id via Ports::IdentityGeneration.uuid
|
|
26
|
+
# before dispatching (see spec/ports/identity_generation_spec.rb),
|
|
27
|
+
# the same pattern proven there against Pizzas.
|
|
28
|
+
command "Register" do
|
|
29
|
+
role "Identity registrar"
|
|
30
|
+
goal "Recognize an identity"
|
|
31
|
+
|
|
32
|
+
attribute :identity_id, IdentityId
|
|
33
|
+
emits "IdentityRegistered"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
aggregate "ExternalIdentifier" do
|
|
38
|
+
description "One authenticated (issuer, subject) pair, linked to the identity it proves control of."
|
|
39
|
+
|
|
40
|
+
reference_to Identity
|
|
41
|
+
attribute :key, ExternalIdentifierKey
|
|
42
|
+
attribute :issuer, Issuer
|
|
43
|
+
attribute :subject, Subject
|
|
44
|
+
|
|
45
|
+
identified_by :key
|
|
46
|
+
|
|
47
|
+
value_object "ExternalIdentifierKey" do
|
|
48
|
+
attribute :value, String
|
|
49
|
+
invariant("a key is present") { !value.to_s.empty? }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
value_object "Issuer" do
|
|
53
|
+
attribute :value, String
|
|
54
|
+
invariant("an issuer is present") { !value.to_s.empty? }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
value_object "Subject" do
|
|
58
|
+
attribute :value, String
|
|
59
|
+
invariant("a subject is present") { !value.to_s.empty? }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# `key` is deliberately not derived here — a caller-side concern
|
|
63
|
+
# (e.g. SHA256 of issuer+subject), same as any other identity
|
|
64
|
+
# field this domain never mints itself.
|
|
65
|
+
command "Link" do
|
|
66
|
+
role "Identity registrar"
|
|
67
|
+
goal "Associate an external authenticated subject with an identity"
|
|
68
|
+
|
|
69
|
+
reference_to Identity
|
|
70
|
+
attribute :key, ExternalIdentifierKey
|
|
71
|
+
attribute :issuer, Issuer
|
|
72
|
+
attribute :subject, Subject
|
|
73
|
+
|
|
74
|
+
emits "ExternalIdentifierLinked"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# THE OIDC-SHAPED LOOKUP — a token hands over `(issuer, subject)`,
|
|
78
|
+
# never the `key` this aggregate is actually identified by (`key`
|
|
79
|
+
# is a caller-side derivation, deliberately not reproduced here —
|
|
80
|
+
# see `Link`'s own comment). `identity` rides the row already,
|
|
81
|
+
# from `reference_to Identity` above, so a caller reads it straight
|
|
82
|
+
# off the match rather than needing a second query.
|
|
83
|
+
query "ResolvedBy" do
|
|
84
|
+
description "The external identifier linked to this exact (issuer, subject) pair, if any."
|
|
85
|
+
attribute :issuer, Issuer
|
|
86
|
+
attribute :subject, Subject
|
|
87
|
+
where(issuer: :issuer, subject: :subject)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"audience": "Compliance",
|
|
3
|
+
"scopes": [
|
|
4
|
+
{
|
|
5
|
+
"scope": "compliance:account_freeze_review.clear",
|
|
6
|
+
"verb": "Compliance::AccountFreezeReview.Clear",
|
|
7
|
+
"role": "Compliance officer"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"scope": "compliance:account_freeze_review.escalate",
|
|
11
|
+
"verb": "Compliance::AccountFreezeReview.Escalate",
|
|
12
|
+
"role": "Compliance officer"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"scope": "compliance:account_freeze_review.open",
|
|
16
|
+
"verb": "Compliance::AccountFreezeReview.Open",
|
|
17
|
+
"role": "System"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"scope": "compliance:box_surrender_review.clear",
|
|
21
|
+
"verb": "Compliance::BoxSurrenderReview.Clear",
|
|
22
|
+
"role": "Compliance officer"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"scope": "compliance:box_surrender_review.escalate",
|
|
26
|
+
"verb": "Compliance::BoxSurrenderReview.Escalate",
|
|
27
|
+
"role": "Compliance officer"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"scope": "compliance:box_surrender_review.open",
|
|
31
|
+
"verb": "Compliance::BoxSurrenderReview.Open",
|
|
32
|
+
"role": "System"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"roles": [
|
|
36
|
+
"Compliance officer",
|
|
37
|
+
"System"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require_relative "runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
# THE REGISTRY OF FRAMEWORK BLUEBOOKS — Governance, Identity, and
|
|
5
|
+
# whatever lands beside them in `lib/hecks/framework/bluebook/`:
|
|
6
|
+
# domain-agnostic chapters no single example owns, shared by reference
|
|
7
|
+
# rather than copied into every domain that wants one.
|
|
8
|
+
#
|
|
9
|
+
# UNDER `lib/`, not a top-level sibling — a real consumer (embryonaut)
|
|
10
|
+
# vendors ONLY `lib/` (`bin/vendor-hecks`, and this gem's own
|
|
11
|
+
# `hecks.gemspec`, both glob `lib/**/*`), so a `framework/`
|
|
12
|
+
# sitting beside `lib/` rather than inside it packaged and vendored
|
|
13
|
+
# cleanly but left `uses_framework` finding nothing at runtime —
|
|
14
|
+
# `Framework::ROOT` pointed at a directory that simply never shipped.
|
|
15
|
+
# Nothing in THIS repo's own suite caught it, since every spec here
|
|
16
|
+
# reads the working tree directly; only vendoring into a real consumer
|
|
17
|
+
# did. Same reasoning `lib/hecks/language/bluebook/` already
|
|
18
|
+
# settled for the self-hosted grammar's own `.bluebook` files —
|
|
19
|
+
# non-Ruby data a module owns lives inside `lib/` with the code that
|
|
20
|
+
# reads it, not beside it.
|
|
21
|
+
#
|
|
22
|
+
# DERIVED FROM THE DIRECTORY, not hand-listed a second time — the same
|
|
23
|
+
# reasoning `Assembly::CONTRACTS` gives for reading the language's own
|
|
24
|
+
# fields from a table instead of restating them: a member added here
|
|
25
|
+
# and forgotten in a list would be a member `uses_framework` could
|
|
26
|
+
# never find, and a hand-kept list is exactly the shape that goes
|
|
27
|
+
# stale in silence. `spec/corpus_spec.rb`'s own `FRAMEWORK_MEMBERS`
|
|
28
|
+
# glob is the precedent this mirrors.
|
|
29
|
+
#
|
|
30
|
+
# NAMED BY FILE STEM, capitalized — `governance.bluebook` holds
|
|
31
|
+
# `Hecks.bluebook "Governance"`, the same one-to-one spelling every
|
|
32
|
+
# other chapter in this codebase already keeps between its filename
|
|
33
|
+
# and its declared name.
|
|
34
|
+
module Framework
|
|
35
|
+
ROOT = File.expand_path("framework/bluebook", __dir__).freeze
|
|
36
|
+
|
|
37
|
+
def self.members
|
|
38
|
+
Dir.glob(File.join(ROOT, "*.bluebook")).to_h do |path|
|
|
39
|
+
[Naming.pascal(File.basename(path, ".bluebook")), path]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# LOADED FROM ITS OWN REAL PATH, always — never a copy. A domain
|
|
44
|
+
# booted through `Fuzzing::IsolatedBoot`'s tmp-directory copy still
|
|
45
|
+
# reaches the SAME `framework/bluebook/governance.bluebook` this
|
|
46
|
+
# constant points at, because `uses_framework` runs `Kernel.load`
|
|
47
|
+
# against `ROOT`, not against anything inside the copied domain
|
|
48
|
+
# directory — there is nothing here for a relocated copy to break,
|
|
49
|
+
# the way a symlink carried along with the copy would.
|
|
50
|
+
#
|
|
51
|
+
# ONLY THE BLUEBOOK — a framework member's own `.hecksagon`, if it
|
|
52
|
+
# has one, is NOT auto-loaded. Persistence is a WIRING decision, the
|
|
53
|
+
# same as any other aggregate's, and belongs to whoever is doing the
|
|
54
|
+
# deploying, not to a default baked into the framework member
|
|
55
|
+
# itself: a consuming app needs `Governance::RoleAssignment` durable
|
|
56
|
+
# (Postgres, say), and a framework member hard-coding Memory for its
|
|
57
|
+
# own specs would silently make that decision FOR every app that
|
|
58
|
+
# attaches it. The consuming app declares its OWN, SEPARATE
|
|
59
|
+
# `Hecks.hecksagon "Governance" do ... end` block for that — not
|
|
60
|
+
# binds folded into its own hecksagon, which would build but never
|
|
61
|
+
# resolve: `Ports::Persistence::BindingPolicy.resolve` looks up
|
|
62
|
+
# `registry.hecksagon` by the AGGREGATE's own domain name, not the
|
|
63
|
+
# domain doing the binding, the one invariant this runtime holds
|
|
64
|
+
# everywhere else too. See `examples/banking/bluebook/banking.hecksagon`
|
|
65
|
+
# for the pattern — a real `.hecksagon` file can hold more than one
|
|
66
|
+
# `Hecks.hecksagon` call, one per domain it wires.
|
|
67
|
+
# IDEMPOTENT, PER REGISTRY — `uses_framework` is now called more than
|
|
68
|
+
# once for the SAME member within a single boot (S8: a domain
|
|
69
|
+
# attaching Governance for its own role check, plus a framework
|
|
70
|
+
# sibling attaching it too, e.g. `banking.hecksagon`'s Identity
|
|
71
|
+
# block). `Kernel.load` always re-executes the file, unlike
|
|
72
|
+
# `require`, so a second call would re-run `Hecks.bluebook
|
|
73
|
+
# "Governance"` a second time — and the self-hosting meta-domain
|
|
74
|
+
# records every declaration as a real dispatched command against its
|
|
75
|
+
# own ledger, so a second `Declare` for the SAME aggregate is a real
|
|
76
|
+
# `AlreadyExists`, not a no-op. Skipped once the member's bluebook is
|
|
77
|
+
# already registered in THIS registry — the same chapter, not merely
|
|
78
|
+
# a same-named one from a stale prior boot.
|
|
79
|
+
def self.load!(name)
|
|
80
|
+
path = members.fetch(name.to_s) do
|
|
81
|
+
raise Runtime::WiringError,
|
|
82
|
+
"no framework member named #{name.inspect} — known: #{members.keys.sort.join(', ')}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
return if Hecks.current_registry.bluebook(name.to_s)
|
|
86
|
+
|
|
87
|
+
Kernel.load(path)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
# THE ONE PLACE THAT KNOWS HOW TO FREEZE A DOMAIN VALUE.
|
|
3
|
+
#
|
|
4
|
+
# Freezing here has been fixed four times in four places, each time by
|
|
5
|
+
# topping the container and leaving the contents: list attributes, the
|
|
6
|
+
# event log, query rows, and value objects. `.freeze` on a Hash stops a
|
|
7
|
+
# key being added or removed and NOTHING ELSE — the String, Array or
|
|
8
|
+
# Hash a field holds stays mutable, so a caller reaches straight
|
|
9
|
+
# through and edits in place. Each fix looked complete and none was.
|
|
10
|
+
#
|
|
11
|
+
# WHAT SHOULD BE FROZEN, and why it is not everything:
|
|
12
|
+
#
|
|
13
|
+
# A VALUE OBJECT has no identity to change over. `with` already
|
|
14
|
+
# answers a new one rather than mutating, so freezing it through is
|
|
15
|
+
# what it always claimed to be.
|
|
16
|
+
#
|
|
17
|
+
# AN EMITTED EVENT is a record of something that happened. A mutable
|
|
18
|
+
# audit trail is not one.
|
|
19
|
+
#
|
|
20
|
+
# A QUERY ROW is an answer, not a handle — mutating one edits nobody's
|
|
21
|
+
# state and silently disagrees with the store.
|
|
22
|
+
#
|
|
23
|
+
# AN INSTANCE'S STATE is NOT frozen, deliberately: the interpreter
|
|
24
|
+
# builds it up across a dispatch, and a command's whole job is to
|
|
25
|
+
# change it. Its VALUES are frozen; the holder is not.
|
|
26
|
+
#
|
|
27
|
+
# A COMMAND'S ARGUMENTS are not frozen either. They arrive from
|
|
28
|
+
# outside, are normalised and coerced on the way in, and the coerced
|
|
29
|
+
# result is what becomes a frozen value.
|
|
30
|
+
module Freezer
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# Numbers, symbols, nil and booleans are already immediate or frozen;
|
|
34
|
+
# a Value froze itself when it was built. What is left is the mutable
|
|
35
|
+
# trio, and each has to be WALKED rather than topped.
|
|
36
|
+
def deep(held)
|
|
37
|
+
case held
|
|
38
|
+
when Hash then held.each_value { |inner| deep(inner) }.freeze
|
|
39
|
+
when Array then held.each { |inner| deep(inner) }.freeze
|
|
40
|
+
when String then held.freeze
|
|
41
|
+
else held
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# The question a gate asks, rather than the act. Answers false for the
|
|
46
|
+
# FIRST thing that is reachable and mutable, which is what makes a
|
|
47
|
+
# failure message worth reading.
|
|
48
|
+
def deeply_frozen?(held) = unfrozen_within(held).nil?
|
|
49
|
+
|
|
50
|
+
# The path to the first mutable thing reachable from `held`, or nil.
|
|
51
|
+
# A path rather than a boolean because "something in this event is
|
|
52
|
+
# mutable" is not an actionable sentence.
|
|
53
|
+
def unfrozen_within(held, path = [])
|
|
54
|
+
return (path.empty? ? "(the value itself)" : path.join(".")) unless immune?(held) || held.frozen?
|
|
55
|
+
|
|
56
|
+
case held
|
|
57
|
+
when Hash then held.lazy.filter_map { |key, inner| unfrozen_within(inner, path + [key.to_s]) }.first
|
|
58
|
+
when Array then held.each_with_index.lazy.filter_map { |inner, i| unfrozen_within(inner, path + [i.to_s]) }.first
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Immediates are frozen in every Ruby that matters, but asking
|
|
63
|
+
# `frozen?` of them and trusting the answer has bitten enough people
|
|
64
|
+
# that it is worth being explicit.
|
|
65
|
+
def immune?(held) = held.nil? || held == true || held == false || held.is_a?(Numeric) || held.is_a?(Symbol)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Fuzzing
|
|
3
|
+
# A value that is DELIBERATELY THE WRONG SHAPE for the attribute it is handed
|
|
4
|
+
# to — the sibling of ValueGenerator, which only ever builds values that satisfy
|
|
5
|
+
# a declared type.
|
|
6
|
+
#
|
|
7
|
+
# That limit is why the fuzzer could not reach the space a hand-written
|
|
8
|
+
# adversarial corpus covers. spec/corpus/banking.json got there one step at a
|
|
9
|
+
# time, by hand : a value object where an Integer is declared, a Float where an
|
|
10
|
+
# Integer is declared, a numeral wearing quotes, an attribute missing entirely,
|
|
11
|
+
# an attribute the command never declared. One of those — a hash reaching a
|
|
12
|
+
# refusal message — is the exact shape that exposed refusal wording drift
|
|
13
|
+
# (a composite rendered with `inspect` at one site and JSON at another —
|
|
14
|
+
# Rendering's whole story), and no generated value
|
|
15
|
+
# could ever have produced it.
|
|
16
|
+
#
|
|
17
|
+
# These are expected to be REFUSED, and that is the point : a refusal is an
|
|
18
|
+
# answer, and its wording is pinned byte-for-byte. The bugs live in
|
|
19
|
+
# the sentence, not in the happy path.
|
|
20
|
+
module InvalidValueGenerator
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
# Each kind names a real confusion, not random noise. Ordered roughly by how
|
|
24
|
+
# often this codebase has actually been bitten by it.
|
|
25
|
+
KINDS = %i[
|
|
26
|
+
object_for_scalar
|
|
27
|
+
scalar_for_object
|
|
28
|
+
float_for_integer
|
|
29
|
+
numeral_string
|
|
30
|
+
array_for_scalar
|
|
31
|
+
boolean_for_string
|
|
32
|
+
null
|
|
33
|
+
].freeze
|
|
34
|
+
|
|
35
|
+
def corrupt(attribute, aggregate, random:)
|
|
36
|
+
kind = kinds_for(attribute, aggregate).sample(random: random)
|
|
37
|
+
build(kind, attribute, aggregate, random: random)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Only the confusions that MEAN anything for this attribute. Offering
|
|
41
|
+
# `scalar_for_object` for a plain Integer would just be a second spelling of
|
|
42
|
+
# `numeral_string`, and a kind that cannot be wrong for the attribute it is
|
|
43
|
+
# handed teaches the corpus nothing.
|
|
44
|
+
def kinds_for(attribute, aggregate)
|
|
45
|
+
value_object = aggregate.value_object(attribute.type.to_s)
|
|
46
|
+
return %i[scalar_for_object array_for_scalar null] if value_object
|
|
47
|
+
return %i[object_for_scalar float_for_integer numeral_string array_for_scalar null] if attribute.type.to_s == "Integer"
|
|
48
|
+
|
|
49
|
+
%i[object_for_scalar array_for_scalar boolean_for_string null]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def build(kind, attribute, aggregate, random:)
|
|
53
|
+
case kind
|
|
54
|
+
when :object_for_scalar then { "cents" => random.rand(1..1000) }
|
|
55
|
+
when :scalar_for_object then scalar_for(attribute, aggregate, random: random)
|
|
56
|
+
when :float_for_integer then random.rand(1.0..100.0).round(2)
|
|
57
|
+
when :numeral_string then random.rand(1..1000).to_s
|
|
58
|
+
when :array_for_scalar then [random.rand(1..10), random.rand(1..10)]
|
|
59
|
+
when :boolean_for_string then random.rand(2).zero?
|
|
60
|
+
else nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# A bare scalar where a value object is declared. A SINGLE-FIELD value object
|
|
65
|
+
# legitimately accepts one (that is the standing-in rule every domain relies
|
|
66
|
+
# on), so the interesting case is a value object with SEVERAL fields, where a
|
|
67
|
+
# scalar cannot stand for anything and the refusal has to say so.
|
|
68
|
+
def scalar_for(attribute, aggregate, random:)
|
|
69
|
+
value_object = aggregate.value_object(attribute.type.to_s)
|
|
70
|
+
return "a bare scalar" unless value_object && value_object.attributes.size == 1
|
|
71
|
+
|
|
72
|
+
# One field, so a scalar is legal — corrupt the FIELD's own type instead,
|
|
73
|
+
# which is still a shape the attribute cannot accept.
|
|
74
|
+
{ value_object.attributes.first.name.to_s => ["nested", "array"] }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# An attribute the command never declared. `refuse_unknown_arguments` is a
|
|
78
|
+
# real dispatch step (Vocabulary::AggregateDispatchOrder), and nothing
|
|
79
|
+
# generated had ever exercised it.
|
|
80
|
+
def undeclared_argument(random:)
|
|
81
|
+
name = %w[colour flavour rank note].sample(random: random)
|
|
82
|
+
[name, %w[red loud third scribbled].sample(random: random)]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "tmpdir"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Fuzzing
|
|
6
|
+
# A FRESH, IN-PROCESS ADAPTER FOR EVERY EPHEMERAL BOOT.
|
|
7
|
+
#
|
|
8
|
+
# Fuzzing/replay copies a domain to a tmpdir and boots from there
|
|
9
|
+
# specifically to get zero-history state — `rm_rf`ing the copy's own
|
|
10
|
+
# `data/` achieves that for a file-based adapter (Memory,
|
|
11
|
+
# SqlitePersistence, Heki), because copying the DIRECTORY copies the
|
|
12
|
+
# store. It achieves nothing for an adapter that lives outside the
|
|
13
|
+
# copied directory entirely — Postgres, named by a fixed connection
|
|
14
|
+
# string in `.world` (examples/pizzas/bluebook/pizzas.world, for
|
|
15
|
+
# instance). Copying the directory does not copy or isolate the
|
|
16
|
+
# DATABASE, so a "fresh" boot against a Postgres-bound domain would
|
|
17
|
+
# still see every record any other run, ever, wrote to it.
|
|
18
|
+
#
|
|
19
|
+
# So every `.hecksagon` in the copy gets its persistence binding
|
|
20
|
+
# rewritten to Memory before booting, and any `projected_by` bind
|
|
21
|
+
# dropped outright (optional — `Registry#read_repository` already
|
|
22
|
+
# falls back to the authoritative repository when none exists). The
|
|
23
|
+
# domain's own rules and shape are untouched ; only WHICH adapter this
|
|
24
|
+
# one ephemeral copy answers through changes. What the domain is
|
|
25
|
+
# bound to for real deployment is never touched — only this tmp copy.
|
|
26
|
+
module IsolatedBoot
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
def call(domain_path)
|
|
30
|
+
Dir.mktmpdir("hecks-fuzz") do |tmp|
|
|
31
|
+
copy = File.join(tmp, File.basename(domain_path))
|
|
32
|
+
copy_dereferencing(domain_path, copy)
|
|
33
|
+
FileUtils.rm_rf(File.join(copy, "data"))
|
|
34
|
+
rebind_to_memory!(copy)
|
|
35
|
+
yield copy
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# SYMLINKS ARE FOLLOWED, NOT COPIED. `FileUtils.cp_r` reproduces a
|
|
40
|
+
# symlink AS a symlink, and a RELATIVE one then points at nothing
|
|
41
|
+
# from a tmpdir — `lib/hecks/framework/bluebook/compliance
|
|
42
|
+
# .bluebook` is exactly that, a link to
|
|
43
|
+
# `examples/compliance/bluebook/compliance.bluebook`, so the whole
|
|
44
|
+
# framework domain failed to boot here with a LoadError naming a
|
|
45
|
+
# path under /var/folders that had never existed. Nothing about the
|
|
46
|
+
# domain was wrong; the copy was.
|
|
47
|
+
#
|
|
48
|
+
# `FileUtils.cp` follows a symlink and copies its CONTENT, which is
|
|
49
|
+
# what an isolated boot wants: the copy has to stand alone, since
|
|
50
|
+
# rebind_to_memory! rewrites files in it and must not reach back
|
|
51
|
+
# through a link into the real tree.
|
|
52
|
+
def copy_dereferencing(source, destination)
|
|
53
|
+
FileUtils.mkdir_p(destination)
|
|
54
|
+
Dir.glob(File.join(source, "**", "*"), File::FNM_DOTMATCH).each do |path|
|
|
55
|
+
next if [".", ".."].include?(File.basename(path))
|
|
56
|
+
|
|
57
|
+
target = File.join(destination, path.delete_prefix("#{source}/"))
|
|
58
|
+
if File.directory?(path)
|
|
59
|
+
FileUtils.mkdir_p(target)
|
|
60
|
+
else
|
|
61
|
+
FileUtils.mkdir_p(File.dirname(target))
|
|
62
|
+
FileUtils.cp(path, target)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def rebind_to_memory!(copy)
|
|
68
|
+
Dir.glob(File.join(copy, "**", "*.hecksagon")).each do |path|
|
|
69
|
+
# `persisted_by`/`projected_by` can be spelled two ways now —
|
|
70
|
+
# aggregate-scoped (`Banking::Customer.persisted_by("Heki")`,
|
|
71
|
+
# always parenthesised) and, since §0's domain-level default
|
|
72
|
+
# binds, a bare call at the hecksagon's own root
|
|
73
|
+
# (`persisted_by "Heki"`, no parens, no receiver). Both must
|
|
74
|
+
# be caught here or a domain that only declares the bare form
|
|
75
|
+
# keeps its real adapter under an "isolated" fuzz boot.
|
|
76
|
+
lines = File.readlines(path).reject { |line| line.match?(/\bprojected_by\s*\(?\s*"/) }
|
|
77
|
+
File.write(path, lines.join.gsub(/persisted_by\s*\(?\s*"[^"]+"\s*\)?/, 'persisted_by("Memory")'))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# THE SETTINGS, NOT JUST THE BIND — `WorldBuilder#method_missing`
|
|
81
|
+
# stores a settings block under BOTH "verb:adapter" and the bare
|
|
82
|
+
# "verb" (world_builder.rb:32-33), so a bind rewritten to Memory
|
|
83
|
+
# still falls back to whatever adapter's settings were declared
|
|
84
|
+
# bare — Postgres's `database:`, which Memory does not take and
|
|
85
|
+
# WiringError refuses on sight. Memory needs no settings at all
|
|
86
|
+
# (a domain with no .world boots it as the default), so the
|
|
87
|
+
# simplest correct fix is dropping .world from the copy entirely.
|
|
88
|
+
Dir.glob(File.join(copy, "**", "*.world")).each { |path| File.delete(path) }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|