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,306 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
require_relative "sql_query_builder"
|
|
5
|
+
require_relative "sqlite/schema_builder"
|
|
6
|
+
require_relative "sqlite/codec"
|
|
7
|
+
require_relative "../../ports/persistence/append_only"
|
|
8
|
+
require_relative "../../query_specification/common/null_policy"
|
|
9
|
+
require_relative "../../query_specification/common/order_by"
|
|
10
|
+
require_relative "../../runtime/errors"
|
|
11
|
+
require_relative "../../runtime/event"
|
|
12
|
+
require_relative "../../runtime/instance"
|
|
13
|
+
|
|
14
|
+
module Hecks
|
|
15
|
+
module Adapters
|
|
16
|
+
# The SQLite store: one table per aggregate head, an append-only entry
|
|
17
|
+
# table beside it. The DDL lives in sqlite/schema_builder.rb, the
|
|
18
|
+
# column codec in sqlite/codec.rb, and the query compilation is the
|
|
19
|
+
# shared SqlQueryBuilder — this file supplies only SQLite's dialect.
|
|
20
|
+
class Sqlite
|
|
21
|
+
include SqlQueryBuilder
|
|
22
|
+
include SchemaBuilder
|
|
23
|
+
include Codec
|
|
24
|
+
|
|
25
|
+
SQL_TYPES = { "Integer" => "INTEGER", "Float" => "REAL" }.freeze
|
|
26
|
+
|
|
27
|
+
attr_reader :aggregate, :path
|
|
28
|
+
|
|
29
|
+
def persistence_capabilities = [:atomic_put]
|
|
30
|
+
|
|
31
|
+
def initialize(aggregate:, settings: {}, root: nil)
|
|
32
|
+
# LAZY, ON PURPOSE — a domain that never wires Sqlite should never
|
|
33
|
+
# need the gem installed. `require "hecks"` alone must not
|
|
34
|
+
# force a database client library nobody asked for.
|
|
35
|
+
require "sqlite3"
|
|
36
|
+
|
|
37
|
+
@aggregate = aggregate
|
|
38
|
+
@path = resolve_path(settings, root)
|
|
39
|
+
# THE OPTIONAL saga-persistence capability's own scoping column
|
|
40
|
+
# (§2/§4) — falls back to the aggregate's own name for a
|
|
41
|
+
# directly-instantiated adapter (specs), same fallback shape
|
|
42
|
+
# Postgres's own @domain already uses.
|
|
43
|
+
@domain = (
|
|
44
|
+
if settings.key?(:domain)
|
|
45
|
+
settings[:domain]
|
|
46
|
+
elsif settings.key?("domain")
|
|
47
|
+
settings["domain"]
|
|
48
|
+
else
|
|
49
|
+
aggregate.name
|
|
50
|
+
end
|
|
51
|
+
).to_s
|
|
52
|
+
|
|
53
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
|
54
|
+
@db = SQLite3::Database.new(@path)
|
|
55
|
+
@db.results_as_hash = true
|
|
56
|
+
# The append is the recovery commit point. Keep SQLite's fsync policy
|
|
57
|
+
# explicit instead of inheriting a process-wide pragma choice.
|
|
58
|
+
@db.execute("PRAGMA synchronous = FULL")
|
|
59
|
+
|
|
60
|
+
create_aggregate_table!
|
|
61
|
+
create_entry_table!
|
|
62
|
+
ensure_entry_operation_column!
|
|
63
|
+
ensure_entry_mirrors_column!
|
|
64
|
+
create_event_table!
|
|
65
|
+
create_saga_table!
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def table = @aggregate.storage_name
|
|
69
|
+
|
|
70
|
+
def find(id)
|
|
71
|
+
row = @db.get_first_row("SELECT * FROM #{quoted_table} WHERE id = ?", [id.to_s])
|
|
72
|
+
return nil unless row
|
|
73
|
+
|
|
74
|
+
Runtime::Instance.new(aggregate: @aggregate, id: row["id"], state: decode(row))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# order_by IS A RUNTIME VALUE — see postgres.rb's own all for the
|
|
78
|
+
# full reasoning; whitelisted the identical way before it ever
|
|
79
|
+
# reaches order_expression.
|
|
80
|
+
def all(order_by: nil, direction: :asc)
|
|
81
|
+
order_sql = "ORDER BY id"
|
|
82
|
+
if order_by
|
|
83
|
+
name = order_by.to_s.split(".").first
|
|
84
|
+
raise Runtime::WiringError, "#{@aggregate.name} has no attribute #{order_by.inspect} to order by" unless @aggregate.lifecycle&.field.to_s == name || @aggregate.attribute(name)
|
|
85
|
+
|
|
86
|
+
spec = QuerySpecification::Common::OrderBy.new(field: order_by, direction: direction)
|
|
87
|
+
order_sql = "ORDER BY #{order_clause(spec, nil)}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
@db.execute("SELECT * FROM #{quoted_table} #{order_sql}").map do |row|
|
|
91
|
+
Runtime::Instance.new(aggregate: @aggregate, id: row["id"], state: decode(row))
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def count = @db.get_first_value("SELECT COUNT(*) FROM #{quoted_table}").to_i
|
|
96
|
+
|
|
97
|
+
def append(entry)
|
|
98
|
+
@db.execute(
|
|
99
|
+
"INSERT INTO #{quoted_entry_table} (aggregate_id, operation, state, mirrors) VALUES (?, ?, ?, ?)",
|
|
100
|
+
# `mirrors` (unlike `state`) is a NULLABLE column — an absent
|
|
101
|
+
# mirrors hash must bind a real SQL NULL, not the four-character
|
|
102
|
+
# JSON text `"null"` (`JSON.generate(nil)`), or a future `IS NULL`
|
|
103
|
+
# check against it would never match. Same guard `postgres_era.rb`
|
|
104
|
+
# already uses for its own journal's `mirrors` column.
|
|
105
|
+
[entry.id, entry.operation, JSON.generate(entry.state), entry.mirrors && JSON.generate(entry.mirrors)]
|
|
106
|
+
)
|
|
107
|
+
entry
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def project(entry)
|
|
111
|
+
return @db.execute("DELETE FROM #{quoted_table} WHERE id = ?", [entry.id]) if entry.delete?
|
|
112
|
+
|
|
113
|
+
instance = Runtime::Instance.new(aggregate: @aggregate, id: entry.id, state: entry.state)
|
|
114
|
+
columns = (["id"] + persisted_fields.map { |field| field[:name].to_s }).map { |c| quote_ident(c) }
|
|
115
|
+
values = [instance.id.to_s] + persisted_fields.map { |field| encode_field(field, instance[field[:name]]) }
|
|
116
|
+
slots = Array.new(columns.size, "?").join(", ")
|
|
117
|
+
|
|
118
|
+
@db.execute(
|
|
119
|
+
"INSERT OR REPLACE INTO #{quoted_table} (#{columns.join(', ')}) VALUES (#{slots})",
|
|
120
|
+
values
|
|
121
|
+
)
|
|
122
|
+
instance
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def entries
|
|
126
|
+
@db.execute("SELECT aggregate_id, operation, state, mirrors FROM #{quoted_entry_table} ORDER BY sequence").map do |row|
|
|
127
|
+
state = JSON.parse(row["state"])
|
|
128
|
+
Ports::Persistence::Entry.new(
|
|
129
|
+
operation: row["operation"] || "save",
|
|
130
|
+
id: row["aggregate_id"],
|
|
131
|
+
state: state&.transform_keys(&:to_sym),
|
|
132
|
+
mirrors: row["mirrors"] && JSON.parse(row["mirrors"])
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def reset!
|
|
138
|
+
@db.execute("DELETE FROM #{quoted_table}")
|
|
139
|
+
@db.execute("DELETE FROM #{quoted_entry_table}")
|
|
140
|
+
self
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def save(instance)
|
|
144
|
+
entry = Ports::Persistence::Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
|
|
145
|
+
append(entry)
|
|
146
|
+
project(entry)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# The outcome lookup, journal append and snapshot replacement share one
|
|
150
|
+
# SQLite transaction. The runtime performs no preliminary find; this
|
|
151
|
+
# adapter-native operation owns both concurrency and outcome reporting.
|
|
152
|
+
def atomic_put(entry, insert_only: false)
|
|
153
|
+
status = nil
|
|
154
|
+
@db.transaction do
|
|
155
|
+
exists = !@db.get_first_value("SELECT 1 FROM #{quoted_table} WHERE id = ?", [entry.id.to_s]).nil?
|
|
156
|
+
if insert_only && exists
|
|
157
|
+
status = :conflicted
|
|
158
|
+
next
|
|
159
|
+
end
|
|
160
|
+
status = exists ? :replaced : :inserted
|
|
161
|
+
append(entry)
|
|
162
|
+
project(entry)
|
|
163
|
+
end
|
|
164
|
+
status
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def delete(id)
|
|
168
|
+
entry = Ports::Persistence::Entry.new(operation: "delete", id: id.to_s, state: nil)
|
|
169
|
+
append(entry)
|
|
170
|
+
project(entry)
|
|
171
|
+
true
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def record_event(event)
|
|
175
|
+
@db.execute(
|
|
176
|
+
"INSERT INTO events (name, aggregate, aggregate_id, payload, occurred_at) VALUES (?, ?, ?, ?, ?)",
|
|
177
|
+
[event.name, event.aggregate, event.id.to_s, JSON.generate(event.payload), event.occurred_at]
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def events
|
|
182
|
+
@db.execute("SELECT * FROM events ORDER BY id").map do |row|
|
|
183
|
+
Runtime::Event.new(
|
|
184
|
+
name: row["name"],
|
|
185
|
+
aggregate: row["aggregate"],
|
|
186
|
+
id: row["aggregate_id"],
|
|
187
|
+
payload: JSON.parse(row["payload"], symbolize_names: true),
|
|
188
|
+
occurred_at: row["occurred_at"]
|
|
189
|
+
)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# ── the OPTIONAL saga-persistence capability (§2) — reuses the
|
|
194
|
+
# DDL every SQLite-backed aggregate table already lives beside
|
|
195
|
+
# (`create_saga_table!`, `Sqlite::SchemaBuilder`, shared with D1).
|
|
196
|
+
# SQLite's `resolve_path` defaults to one `.db` file PER
|
|
197
|
+
# AGGREGATE unless a domain shares one `database` setting across
|
|
198
|
+
# its aggregates — since saga persistence resolves through
|
|
199
|
+
# whichever adapter instance backs the domain's FIRST aggregate
|
|
200
|
+
# (`Registry#saga_persistence`), this table ends up living inside
|
|
201
|
+
# THAT one aggregate's own file by default. Correct and durable
|
|
202
|
+
# either way; a domain that wants an obviously-named saga store
|
|
203
|
+
# already gets one by sharing `database` across its aggregates,
|
|
204
|
+
# the recommended, common case.
|
|
205
|
+
def save_saga(process_manager:, correlation:, state:, memory:, completed_compensations: [])
|
|
206
|
+
@db.execute(
|
|
207
|
+
"INSERT OR REPLACE INTO hecks_saga_instances (domain, process_manager, correlation, state, memory, completed_compensations) " \
|
|
208
|
+
"VALUES (?, ?, ?, ?, ?, ?)",
|
|
209
|
+
[@domain, process_manager.to_s, correlation.to_s, state.to_s, JSON.generate(memory), JSON.generate(completed_compensations)]
|
|
210
|
+
)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def delete_saga(process_manager:, correlation:)
|
|
214
|
+
@db.execute(
|
|
215
|
+
"DELETE FROM hecks_saga_instances WHERE domain = ? AND process_manager = ? AND correlation = ?",
|
|
216
|
+
[@domain, process_manager.to_s, correlation.to_s]
|
|
217
|
+
)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def each_saga
|
|
221
|
+
return enum_for(:each_saga) unless block_given?
|
|
222
|
+
|
|
223
|
+
@db.execute(
|
|
224
|
+
"SELECT process_manager, correlation, state, memory, completed_compensations FROM hecks_saga_instances WHERE domain = ?",
|
|
225
|
+
[@domain]
|
|
226
|
+
).each do |row|
|
|
227
|
+
yield row["process_manager"], row["correlation"], row["state"],
|
|
228
|
+
JSON.parse(row["memory"], symbolize_names: true),
|
|
229
|
+
JSON.parse(row["completed_compensations"] || "[]", symbolize_names: true)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
private
|
|
234
|
+
|
|
235
|
+
# ── SqlQueryBuilder's dialect hooks ─────────────────────────────
|
|
236
|
+
|
|
237
|
+
def select_list = "*"
|
|
238
|
+
def from_relation = quoted_table
|
|
239
|
+
def dialect_name = "SQLite"
|
|
240
|
+
def empty_in_clause = "0"
|
|
241
|
+
|
|
242
|
+
def placeholder(binds, value)
|
|
243
|
+
binds << value
|
|
244
|
+
"?"
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def contains_clause(expression, placeholder)
|
|
248
|
+
"instr(#{expression}, #{placeholder}) > 0"
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def list_contains_clause(column, member, placeholder)
|
|
252
|
+
target = member.empty? ? "json_each.value" : "json_extract(json_each.value, '$.#{member}')"
|
|
253
|
+
"EXISTS (SELECT 1 FROM json_each(#{quote_ident(column)}) WHERE #{target} = #{placeholder})"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def plain_column(name) = quote_ident(name)
|
|
257
|
+
|
|
258
|
+
def nested_expression(name, path, member)
|
|
259
|
+
json_path = path.empty? ? "$.#{member || 'value'}" : "$.#{path.join('.')}"
|
|
260
|
+
"json_extract(#{quote_ident(name)}, '#{json_path}')"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# SQLite has no bare OFFSET — LIMIT -1 is its own documented
|
|
264
|
+
# unbounded spelling, exactly for this case.
|
|
265
|
+
def unbounded_limit = " LIMIT -1"
|
|
266
|
+
|
|
267
|
+
def order_clause(order_by, policy)
|
|
268
|
+
QuerySpecification::Common::NullPolicy.sql_order(query_expression(order_by.field), order_by.direction, policy)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def execute_query(sql, binds)
|
|
272
|
+
@db.execute(sql, binds).map { |row| Runtime::Instance.new(aggregate: @aggregate, id: row["id"], state: decode(row)) }
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# ── the rest of the dialect ─────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
def quote_ident(name)
|
|
278
|
+
%("#{name.to_s.gsub('"', '""')}")
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def quoted_table = quote_ident(table)
|
|
282
|
+
def entry_table = "#{table}_entries"
|
|
283
|
+
def quoted_entry_table = quote_ident(entry_table)
|
|
284
|
+
|
|
285
|
+
def resolve_path(settings, root)
|
|
286
|
+
declared =
|
|
287
|
+
if settings.key?(:database)
|
|
288
|
+
settings[:database]
|
|
289
|
+
elsif settings.key?("database")
|
|
290
|
+
settings["database"]
|
|
291
|
+
else
|
|
292
|
+
"data/#{table}.db"
|
|
293
|
+
end
|
|
294
|
+
return declared if declared.start_with?("/")
|
|
295
|
+
|
|
296
|
+
File.join(root || Dir.pwd, declared)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Port-specific bindings share SQLite's storage mechanics but advertise
|
|
301
|
+
# only one operational contract each.
|
|
302
|
+
class SqlitePersistence < Sqlite; end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
require_relative "sqlite/projection"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Adapters
|
|
3
|
+
# The real `clock` fulfillment — the machine's own time, in seconds.
|
|
4
|
+
#
|
|
5
|
+
# Whole seconds on purpose. The sublanguage does integer arithmetic, a
|
|
6
|
+
# staleness window is measured in minutes, and sub-second precision in a
|
|
7
|
+
# durable ledger is precision nobody reads and every diff has to ignore.
|
|
8
|
+
module SystemClock
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def now = Time.now.to_i
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Adapters
|
|
3
|
+
# The driven side — every store or reader an adapter declaration can
|
|
4
|
+
# bind. A small adapter is one file beside this one; sqlite, postgres
|
|
5
|
+
# and heki keep a directory for their supporting cast. Each `.adapter`
|
|
6
|
+
# file alongside is the DSL declaration the grammar bootstrap and the
|
|
7
|
+
# Folder adapter load as data.
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require_relative "driven/memory"
|
|
12
|
+
require_relative "driven/sqlite"
|
|
13
|
+
require_relative "driven/postgres"
|
|
14
|
+
# `PostgresEra` is NOT `require_relative`d here — ADR 0033 moved it, and
|
|
15
|
+
# the rest of the era/lineage/translation subsystem, behind a loadable
|
|
16
|
+
# persistence plugin (`hecks/ports/persistence/plugins/era`) rather than
|
|
17
|
+
# requiring every app to carry it whether or not anything ever binds
|
|
18
|
+
# `persisted_by "PostgresEra"`. `Module#autoload` is the one-line seam
|
|
19
|
+
# that makes that genuinely load-nothing-until-asked instead of pushing
|
|
20
|
+
# an explicit `require` onto every one of the ~15 generic `bin/*` tools
|
|
21
|
+
# that boot an arbitrary checked-in domain (several of which — pizzas,
|
|
22
|
+
# compliance, chess, roster — bind PostgresEra): the FIRST real
|
|
23
|
+
# `Adapters.const_get("PostgresEra")` (`registry/verification.rb`'s own
|
|
24
|
+
# adapter resolution, unchanged) transparently `require`s the plugin
|
|
25
|
+
# entry point, which defines the constant AND calls `register_plugin`
|
|
26
|
+
# as its own side effect (`plugins/era.rb`'s last line) — the exact
|
|
27
|
+
# same effect an explicit `require "hecks/ports/persistence/plugins/
|
|
28
|
+
# era"` has, just deferred to the moment something is actually asked
|
|
29
|
+
# for `PostgresEra` rather than sprinkled across every caller that
|
|
30
|
+
# might. A domain that never binds it never triggers the autoload, so
|
|
31
|
+
# never loads a line of era code — unchanged from ADR 0033's own goal.
|
|
32
|
+
Hecks::Adapters.autoload(:PostgresEra, "hecks/ports/persistence/plugins/era")
|
|
33
|
+
require_relative "driven/lambda"
|
|
34
|
+
require_relative "driven/heki"
|
|
35
|
+
require_relative "driven/prism"
|
|
36
|
+
require_relative "driven/folder"
|
|
37
|
+
require_relative "driven/d1"
|
|
38
|
+
require_relative "driven/mock_stripe_adapter"
|
|
39
|
+
require_relative "driven/secure_random_identity"
|
|
40
|
+
require_relative "driven/system_clock"
|
|
41
|
+
# `SequentialIdentity` — the deterministic identity_generation test
|
|
42
|
+
# double — is NOT required here on purpose. It lives at
|
|
43
|
+
# spec/fixtures/sequential_identity.{adapter,rb}, loaded explicitly by
|
|
44
|
+
# whichever spec wants it: this file's `require_relative` list is what
|
|
45
|
+
# `Folder#load_library`'s `.adapter` glob backs, and `.adapter` DSL
|
|
46
|
+
# declarations register into every registry any real `Hecks.boot` ever
|
|
47
|
+
# builds. Two adapters answering `identity_generation` unconditionally
|
|
48
|
+
# would make the port permanently ambiguous — `Ports::IdentityGeneration
|
|
49
|
+
# .adapter` refuses to choose between more than one — for every
|
|
50
|
+
# consumer, forever, not just specs that want the deterministic one.
|
|
51
|
+
require_relative "driven/governance_authorization"
|
|
52
|
+
require_relative "driven/identity_registry"
|
|
53
|
+
require_relative "driven/google_authentication"
|
|
54
|
+
require_relative "driven/claude_code"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
require_relative "ir"
|
|
2
|
+
|
|
3
|
+
# The authoring surface for `Hecks.behaviors "Name" do ... end` — one
|
|
4
|
+
# `test "description" do ... end` block per case, `tests`/`setup`/`input`/
|
|
5
|
+
# `expect` inside. `instance_eval`-based, the same shape every other
|
|
6
|
+
# hecks DSL builder uses (`WorldBuilder`, `HecksagonBuilder`).
|
|
7
|
+
module Hecks
|
|
8
|
+
module Behaviors
|
|
9
|
+
# Raised at `Hecks.behaviors` build time — a missing `vision` or
|
|
10
|
+
# `loads` line names the fix directly rather than failing later, deep
|
|
11
|
+
# inside a boot, over a suite that was never going to be scoped.
|
|
12
|
+
class Malformed < StandardError; end
|
|
13
|
+
|
|
14
|
+
class TestCaseBuilder
|
|
15
|
+
def initialize(description)
|
|
16
|
+
@description = description
|
|
17
|
+
@tests_command = nil
|
|
18
|
+
@on_aggregate = nil
|
|
19
|
+
@kind = :command
|
|
20
|
+
@setups = []
|
|
21
|
+
@input = {}
|
|
22
|
+
@expect = {}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def tests(command, on: nil, kind: :command)
|
|
26
|
+
@tests_command = command
|
|
27
|
+
@on_aggregate = on
|
|
28
|
+
@kind = kind
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def setup(command, **kwargs)
|
|
32
|
+
@setups << TestSetup.new(command: command, args: kwargs)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def input(**kwargs) = @input.merge!(kwargs)
|
|
36
|
+
def expect(**kwargs) = @expect.merge!(kwargs)
|
|
37
|
+
|
|
38
|
+
def build
|
|
39
|
+
unless @tests_command
|
|
40
|
+
raise Malformed, "test #{@description.inspect} never calls `tests` — " \
|
|
41
|
+
"say which command or query this example exercises"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
validate_expect!
|
|
45
|
+
|
|
46
|
+
TestCase.new(description: @description, tests_command: @tests_command,
|
|
47
|
+
on_aggregate: @on_aggregate, kind: @kind,
|
|
48
|
+
setups: @setups, input: @input, expect: @expect)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# Closes the silent-pass paths a free-form `expect(**kwargs)` merge
|
|
54
|
+
# otherwise leaves open: a test with no `expect` at all asserts
|
|
55
|
+
# nothing and passes whenever dispatch doesn't raise; `count:` on a
|
|
56
|
+
# command and `emits:` on a query are each read by neither runner
|
|
57
|
+
# (Expectations#run_command/#run_query), so they're accepted here
|
|
58
|
+
# and then silently ignored at run time. Checking both at BUILD
|
|
59
|
+
# time, not in the runners, makes them errors on the file that
|
|
60
|
+
# wrote them rather than green checks nobody questions.
|
|
61
|
+
def validate_expect!
|
|
62
|
+
if @expect.empty?
|
|
63
|
+
raise Malformed, "test #{@description.inspect} has no `expect` — say what this " \
|
|
64
|
+
"example proves (ok:, refused:, emits:, count:, or a field name)"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if @kind == :command && @expect.key?(:count)
|
|
68
|
+
raise Malformed, "test #{@description.inspect}: `expect count:` only applies to " \
|
|
69
|
+
"queries — a command has no row count to check"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if @kind == :query && @expect.key?(:emits)
|
|
73
|
+
raise Malformed, "test #{@description.inspect}: `expect emits:` only applies to " \
|
|
74
|
+
"commands — a query never dispatches, so it never emits"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class BehaviorsBuilder
|
|
80
|
+
def initialize(name, source_path:)
|
|
81
|
+
@name = name
|
|
82
|
+
@source_path = source_path
|
|
83
|
+
@source_dir = File.dirname(source_path)
|
|
84
|
+
@vision = nil
|
|
85
|
+
@loads = nil
|
|
86
|
+
@tests = []
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def vision(text) = @vision = text
|
|
90
|
+
|
|
91
|
+
# Relative to THIS `.behaviors` file, never to the filesystem's cwd
|
|
92
|
+
# or a same-stem convention — scope is a fact this file declares,
|
|
93
|
+
# not one a runner infers.
|
|
94
|
+
def loads(*paths)
|
|
95
|
+
@loads = paths.map { |path| File.expand_path(path, @source_dir) }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def test(description, &block)
|
|
99
|
+
builder = TestCaseBuilder.new(description)
|
|
100
|
+
builder.instance_eval(&block) if block
|
|
101
|
+
@tests << builder.build
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def build
|
|
105
|
+
unless @vision
|
|
106
|
+
raise Malformed, "#{@source_path}: no `vision \"...\"` — say in one line " \
|
|
107
|
+
"what this suite is examples of"
|
|
108
|
+
end
|
|
109
|
+
unless @loads
|
|
110
|
+
raise Malformed, "#{@source_path}: no `loads \"...\"` — a behaviors file " \
|
|
111
|
+
"must declare exactly which files to boot"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
BehaviorsSuite.new(name: @name, vision: @vision, loads: @loads,
|
|
115
|
+
tests: @tests, path: @source_path)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def self.build(name, source_path:, &block)
|
|
119
|
+
builder = new(name, source_path: source_path)
|
|
120
|
+
builder.instance_eval(&block) if block
|
|
121
|
+
builder.build
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|