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,183 @@
|
|
|
1
|
+
require_relative "../../storage_shape"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Translation
|
|
5
|
+
module Scaffold
|
|
6
|
+
# Diff two eras' storage-shape projections into the edge's
|
|
7
|
+
# declarations — confident rules by unique signature pairing,
|
|
8
|
+
# everything ambiguous left unresolved for a human.
|
|
9
|
+
module Differ
|
|
10
|
+
# Diff two bluebook IRs into the edge's declarations.
|
|
11
|
+
def diff(held_bluebook, current_bluebook)
|
|
12
|
+
held_shapes = projections(held_bluebook)
|
|
13
|
+
current_shapes = projections(current_bluebook)
|
|
14
|
+
|
|
15
|
+
matched = match_aggregates(held_shapes, current_shapes)
|
|
16
|
+
aggregates = current_bluebook.aggregates.filter_map do |aggregate|
|
|
17
|
+
held_name = matched[:pairs][aggregate.name]
|
|
18
|
+
next unless held_name
|
|
19
|
+
|
|
20
|
+
rules = attribute_rules(held_shapes[held_name], current_shapes[aggregate.name])
|
|
21
|
+
was = held_name == aggregate.name ? nil : held_name
|
|
22
|
+
next if rules.empty? && was.nil?
|
|
23
|
+
|
|
24
|
+
ScaffoldedAggregate.new(name: aggregate.name, was: was, rules: rules)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
{ aggregates: aggregates, retired: matched[:retired], unclaimed: matched[:unclaimed] }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def projections(bluebook)
|
|
31
|
+
Runtime::StorageShape.project(bluebook)["aggregates"].to_h { |shape| [shape["name"], shape] }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# A vanished aggregate whose full shape reappears under exactly one
|
|
35
|
+
# new name was renamed. `retired` is only confident when NOTHING
|
|
36
|
+
# remains it could plausibly have become — a vanished aggregate
|
|
37
|
+
# beside an unmatched new one might be a rename-plus-reshape, and
|
|
38
|
+
# writing `retired` there would be a guess that strands data.
|
|
39
|
+
# Anything ambiguous stays unclaimed, and the coverage gate names
|
|
40
|
+
# it until a human decides.
|
|
41
|
+
def match_aggregates(held_shapes, current_shapes)
|
|
42
|
+
pairs = {}
|
|
43
|
+
current_shapes.each_key { |name| pairs[name] = held_shapes.key?(name) ? name : nil }
|
|
44
|
+
|
|
45
|
+
vanished = held_shapes.keys - current_shapes.keys
|
|
46
|
+
appeared = current_shapes.keys.select { |name| pairs[name].nil? }
|
|
47
|
+
unclaimed = []
|
|
48
|
+
vanished.each do |old_name|
|
|
49
|
+
old_shape = held_shapes[old_name].merge("name" => nil)
|
|
50
|
+
candidates = appeared.select { |name| current_shapes[name].merge("name" => nil) == old_shape }
|
|
51
|
+
if candidates.size == 1
|
|
52
|
+
pairs[candidates.first] = old_name
|
|
53
|
+
appeared -= candidates
|
|
54
|
+
else
|
|
55
|
+
unclaimed << old_name
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
retired = appeared.empty? ? unclaimed : []
|
|
60
|
+
{ pairs: pairs, retired: retired, unclaimed: retired.empty? ? unclaimed : [] }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Every changed path in one aggregate, resolved into rules by
|
|
64
|
+
# signature matching over the FULL path set — top-level names and
|
|
65
|
+
# dotted members alike. Unique signature pair: a rename (both
|
|
66
|
+
# top-level) or a move (any dotted end). Same path, same members,
|
|
67
|
+
# new type name: a retype. Anything else: unresolved, carrying its
|
|
68
|
+
# type-compatible candidates (an empty list is the arrow toward
|
|
69
|
+
# compute, or drop).
|
|
70
|
+
def attribute_rules(held_shape, current_shape)
|
|
71
|
+
rules = []
|
|
72
|
+
rules << identity_hint(held_shape, current_shape)
|
|
73
|
+
rules.compact!
|
|
74
|
+
held_attrs = (held_shape["attributes"] || []).to_h { |attribute| [attribute["name"], attribute] }
|
|
75
|
+
current_attrs = (current_shape["attributes"] || []).to_h { |attribute| [attribute["name"], attribute] }
|
|
76
|
+
|
|
77
|
+
# retype pass: same attribute name, same member structure, the
|
|
78
|
+
# TYPE's own name changed
|
|
79
|
+
(held_attrs.keys & current_attrs.keys).each do |name|
|
|
80
|
+
held = held_attrs[name]
|
|
81
|
+
current = current_attrs[name]
|
|
82
|
+
next if held == current
|
|
83
|
+
next unless container?(held) && container?(current)
|
|
84
|
+
next unless held["type"]["members"] == current["type"]["members"] && held["list"] == current["list"]
|
|
85
|
+
|
|
86
|
+
rules << { kind: :retype, from: held["type"]["type"], to: current["type"]["type"] }
|
|
87
|
+
end
|
|
88
|
+
retyped = rules.filter_map { |rule| rule[:from] if rule[:kind] == :retype }
|
|
89
|
+
|
|
90
|
+
vanished = vanished_paths(held_attrs, current_attrs, retyped)
|
|
91
|
+
appeared = appeared_paths(held_attrs, current_attrs, retyped)
|
|
92
|
+
|
|
93
|
+
vanished.each do |path, signature|
|
|
94
|
+
matches = appeared.select { |_, candidate| candidate == signature }.keys
|
|
95
|
+
taken = rules.filter_map { |rule| rule[:to] if %i[rename move].include?(rule[:kind]) }
|
|
96
|
+
matches -= taken
|
|
97
|
+
if matches.size == 1 && vanished.count { |_, other| other == signature } == 1
|
|
98
|
+
target = matches.first
|
|
99
|
+
kind = path.include?(".") || target.include?(".") ? :move : :rename
|
|
100
|
+
rules << { kind: kind, from: path, to: target }
|
|
101
|
+
else
|
|
102
|
+
candidates = matches.empty? ? compatible_candidates(appeared, signature) : matches
|
|
103
|
+
rules << { kind: :unresolved, from: path, candidates: candidates }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
rules
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Held paths that need explaining: whole attributes that vanished,
|
|
110
|
+
# and members that vanished (or changed type) inside a kept
|
|
111
|
+
# attribute — mirroring exactly what EraGuard will demand coverage
|
|
112
|
+
# for.
|
|
113
|
+
def vanished_paths(held_attrs, current_attrs, retyped)
|
|
114
|
+
paths = {}
|
|
115
|
+
held_attrs.each do |name, held|
|
|
116
|
+
next if retyped.include?(container?(held) ? held["type"]["type"] : nil)
|
|
117
|
+
|
|
118
|
+
current = current_attrs[name]
|
|
119
|
+
if current.nil?
|
|
120
|
+
paths[name] = held["type"]
|
|
121
|
+
next
|
|
122
|
+
end
|
|
123
|
+
next if held == current
|
|
124
|
+
|
|
125
|
+
held_members = members_of(held)
|
|
126
|
+
current_members = members_of(current)
|
|
127
|
+
held_members.each do |member, signature|
|
|
128
|
+
paths["#{name}.#{member}"] = signature if current_members[member] != signature
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
paths
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def appeared_paths(held_attrs, current_attrs, retyped)
|
|
135
|
+
paths = {}
|
|
136
|
+
current_attrs.each do |name, current|
|
|
137
|
+
next if retyped.include?(container?(current) ? current["type"]["type"] : nil)
|
|
138
|
+
|
|
139
|
+
held = held_attrs[name]
|
|
140
|
+
if held.nil?
|
|
141
|
+
paths[name] = current["type"]
|
|
142
|
+
next
|
|
143
|
+
end
|
|
144
|
+
next if held == current
|
|
145
|
+
|
|
146
|
+
held_members = members_of(held)
|
|
147
|
+
members_of(current).each do |member, signature|
|
|
148
|
+
paths["#{name}.#{member}"] = signature if held_members[member] != signature
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
paths
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# THE ONE THING `attribute_rules` COULD NOT SEE BEFORE — the shape
|
|
155
|
+
# projection already carries `"identity"` (`StorageShape
|
|
156
|
+
# .project_aggregate`), it was simply never read here. An
|
|
157
|
+
# unresolved placeholder, not a guess: `coverage_check.rb`'s own
|
|
158
|
+
# `check_identity_unchanged!` is the real gate this only hints
|
|
159
|
+
# toward, the same "tool proactively guides you" pattern the
|
|
160
|
+
# generic unresolved message already gives unfed fields.
|
|
161
|
+
def identity_hint(held_shape, current_shape)
|
|
162
|
+
return if held_shape["identity"] == current_shape["identity"]
|
|
163
|
+
|
|
164
|
+
{ kind: :unresolved, from: :identity, candidates: [] }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def compatible_candidates(appeared, signature)
|
|
168
|
+
appeared.select { |_, candidate| scalar_of(candidate) == scalar_of(signature) }.keys
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def scalar_of(signature) = signature.is_a?(Hash) ? signature["members"] : signature
|
|
172
|
+
|
|
173
|
+
def members_of(attribute)
|
|
174
|
+
return {} unless container?(attribute)
|
|
175
|
+
|
|
176
|
+
attribute["type"]["members"].to_h { |member| [member["name"], member["type"]] }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def container?(attribute) = attribute["type"].is_a?(Hash)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Translation
|
|
3
|
+
module Scaffold
|
|
4
|
+
# Render an edge into .bluebook text — confident rules inline,
|
|
5
|
+
# ambiguities as parse-refusing `unresolved` constructs, never
|
|
6
|
+
# comments, so an unresolved file can only boot into a refusal.
|
|
7
|
+
module Renderer
|
|
8
|
+
def render(edge)
|
|
9
|
+
lines = ["Hecks.data_translation #{edge.domain.inspect}, from: #{edge.from.inspect}, to: #{edge.to.inspect} do"]
|
|
10
|
+
edge.aggregates.each do |aggregate|
|
|
11
|
+
header = " aggregate #{aggregate.name.inspect}"
|
|
12
|
+
header += ", was: #{aggregate.was.inspect}" if aggregate.was
|
|
13
|
+
if aggregate.rules.empty?
|
|
14
|
+
lines << "#{header} do"
|
|
15
|
+
else
|
|
16
|
+
lines << "#{header} do"
|
|
17
|
+
aggregate.rules.each { |rule| lines << " #{render_rule(rule)}" }
|
|
18
|
+
end
|
|
19
|
+
lines << " end"
|
|
20
|
+
end
|
|
21
|
+
edge.retired.each { |name| lines << " retired #{name.inspect}" }
|
|
22
|
+
lines << "end"
|
|
23
|
+
"#{lines.join("\n")}\n"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def render_rule(rule)
|
|
27
|
+
case rule[:kind]
|
|
28
|
+
when :rename then "rename :#{rule[:from]}, to: :#{rule[:to]}"
|
|
29
|
+
when :move then "move #{rule[:from].inspect}, to: #{rule[:to].inspect}"
|
|
30
|
+
when :retype then "retype #{rule[:from].inspect}, to: #{rule[:to].inspect}"
|
|
31
|
+
when :unresolved
|
|
32
|
+
candidates = rule[:candidates].map { |candidate| render_path(candidate) }.join(", ")
|
|
33
|
+
"unresolved #{render_path(rule[:from])}, candidates: [#{candidates}]"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def render_path(path) = path.to_s.include?(".") ? path.to_s.inspect : ":#{path}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Translation
|
|
5
|
+
module Scaffold
|
|
6
|
+
# Put the rendered edge on disk, regenerating in place when a file
|
|
7
|
+
# for the same shape pair already exists.
|
|
8
|
+
module Writer
|
|
9
|
+
# The edge file, regenerated in place when one for the same shape
|
|
10
|
+
# pair already exists (matched textually — an unresolved file
|
|
11
|
+
# cannot be LOADED to ask, that being the whole point of
|
|
12
|
+
# unresolved).
|
|
13
|
+
def write!(directory, edge)
|
|
14
|
+
translations_dir = File.join(directory, "translations")
|
|
15
|
+
FileUtils.mkdir_p(translations_dir)
|
|
16
|
+
|
|
17
|
+
existing = Dir[File.join(translations_dir, "*.bluebook")].find do |path|
|
|
18
|
+
text = File.read(path)
|
|
19
|
+
text.include?("from: #{edge.from.inspect}") && text.include?("to: #{edge.to.inspect}")
|
|
20
|
+
end
|
|
21
|
+
path = existing || File.join(translations_dir, "#{edge.ordinal}-#{edge.label}.bluebook")
|
|
22
|
+
File.write(path, render(edge))
|
|
23
|
+
path
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require_relative "scaffold/differ"
|
|
2
|
+
require_relative "scaffold/renderer"
|
|
3
|
+
require_relative "scaffold/writer"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Translation
|
|
7
|
+
# The scaffold writes translations; humans resolve ambiguity. It
|
|
8
|
+
# diffs the held era's storage-shape projection against the current
|
|
9
|
+
# one (scaffold/differ.rb) and WRITES the edge file
|
|
10
|
+
# (scaffold/renderer.rb, scaffold/writer.rb): confident rules inline
|
|
11
|
+
# (unique signature pairs → rename/move, aggregate renames → `was:`,
|
|
12
|
+
# type renames with identical members → retype, aggregates gone
|
|
13
|
+
# without a successor → retired), ambiguities as parse-refusing
|
|
14
|
+
# `unresolved` constructs — never comments, so an unresolved file can
|
|
15
|
+
# only boot into a refusal, never a guess. It NEVER proposes a
|
|
16
|
+
# `compute` (a computation takes domain judgment no mechanical diff
|
|
17
|
+
# can infer) — an empty-candidate `unresolved` is what points the
|
|
18
|
+
# author there. It never proposes a `drop` either: data loss is a
|
|
19
|
+
# decision, not a default. Re-running regenerates the file, matched
|
|
20
|
+
# by shape pair.
|
|
21
|
+
module Scaffold
|
|
22
|
+
Edge = Struct.new(:domain, :from, :to, :ordinal, :label, :aggregates, :retired, keyword_init: true)
|
|
23
|
+
ScaffoldedAggregate = Struct.new(:name, :was, :rules, keyword_init: true)
|
|
24
|
+
|
|
25
|
+
extend Differ
|
|
26
|
+
extend Renderer
|
|
27
|
+
extend Writer
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
# The era-translation toolchain: scaffold drafts a translation from two
|
|
3
|
+
# eras' shapes, audit judges a written one, reattest re-seals it.
|
|
4
|
+
module Translation
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
require_relative "translation/rule_compiler"
|
|
9
|
+
require_relative "translation/scaffold"
|
|
10
|
+
require_relative "translation/audit"
|
|
11
|
+
require_relative "translation/reattest"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require_relative "../plugin"
|
|
2
|
+
require_relative "era/era_check"
|
|
3
|
+
require_relative "era/era_guard"
|
|
4
|
+
require_relative "era/era_tamper"
|
|
5
|
+
require_relative "era/storage_shape"
|
|
6
|
+
require_relative "era/lineage"
|
|
7
|
+
require_relative "era/postgres_era"
|
|
8
|
+
require_relative "era/translation"
|
|
9
|
+
|
|
10
|
+
module Hecks
|
|
11
|
+
module Ports
|
|
12
|
+
module Persistence
|
|
13
|
+
module Plugins
|
|
14
|
+
# ADR 0033 — requiring THIS FILE is installing the plugin. Nothing in
|
|
15
|
+
# Hecks core requires it; an app that binds `PostgresEra`, or wants
|
|
16
|
+
# schema-translation support at all, requires it explicitly — the
|
|
17
|
+
# same shape every adapter-specific spec fixture already uses to
|
|
18
|
+
# load one particular `.adapter` file rather than all of them.
|
|
19
|
+
module Era
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# `Runtime::Loader.run_boot_gates!` asks every loaded persistence
|
|
23
|
+
# plugin to contribute here, generically — it never mentions
|
|
24
|
+
# `EraCheck` or "era" by name. Two gates, both `:pre_verify`:
|
|
25
|
+
#
|
|
26
|
+
# `:era_compute_rules` — unconditional, whenever this plugin is
|
|
27
|
+
# loaded at all. A `compute`/`rekey` rule requires Postgres
|
|
28
|
+
# whatever adapter is actually bound (ADR 0031's own reasoning,
|
|
29
|
+
# unchanged) — this is the rich, adapter-aware version of that
|
|
30
|
+
# check; `Runtime::Loader`'s own structural backstop (plain
|
|
31
|
+
# `Bluebook::Translation` data, no plugin-specific class) only
|
|
32
|
+
# ever fires when NO persistence plugin is loaded at all.
|
|
33
|
+
#
|
|
34
|
+
# `:era_check` — conditional, exactly ADR 0031's own gate,
|
|
35
|
+
# unchanged: registered only when this registry has an aggregate
|
|
36
|
+
# actually bound to a lineage-capable adapter.
|
|
37
|
+
def contribute_boot_gates(registry, gates)
|
|
38
|
+
gates.register(:era_compute_rules, ->(reg, _dir) { Runtime::EraCheck.check_compute_rules_for_registry!(reg) }, phase: :pre_verify)
|
|
39
|
+
gates.register(:era_check, Runtime::EraCheck.method(:check_lineage!), phase: :pre_verify) if
|
|
40
|
+
Runtime::EraCheck.lineage_capable_registry?(registry)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Hecks::Ports::Persistence.register_plugin(:era, Hecks::Ports::Persistence::Plugins::Era)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require_relative "../../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
module Persistence
|
|
6
|
+
# THE OTHER SHAPE AN ADAPTER CAN BE. `AppendOnly` names one shape
|
|
7
|
+
# already — something that stores bytes, locally (Postgres, SQLite,
|
|
8
|
+
# Heki) or over the network (D1), doesn't matter, the point is it
|
|
9
|
+
# does real local interpretation and has real entries to replay.
|
|
10
|
+
# This names the second, different shape: not "storage reached
|
|
11
|
+
# remotely," but the real INTERPRETER itself living behind a call
|
|
12
|
+
# boundary — `append`/`project` don't do partial local work and
|
|
13
|
+
# then fail, they raise unconditionally, because there is no local
|
|
14
|
+
# write-ahead log to have; `entries` is always `[]` for the same
|
|
15
|
+
# reason (`AppendOnly#recover!` replaying zero entries here is a
|
|
16
|
+
# correct no-op, not a lie).
|
|
17
|
+
#
|
|
18
|
+
# `Adapters::Lambda` is the first adapter to `include` this, not
|
|
19
|
+
# the only one meant to — any future adapter for some other
|
|
20
|
+
# remote-runtime target (a different compute platform, a gRPC
|
|
21
|
+
# stub, whatever) gets this shape for free by including one module
|
|
22
|
+
# instead of re-deriving the same raise-on-write/always-empty-
|
|
23
|
+
# entries boilerplate from scratch. And because it's a real,
|
|
24
|
+
# checkable module rather than an adapter's own ad hoc
|
|
25
|
+
# implementation, a caller can ask "is this adapter a remote-
|
|
26
|
+
# runtime delegate?" as a genuine capability check
|
|
27
|
+
# (`registry.adapter_class(name) <= RemoteRuntime`,
|
|
28
|
+
# `Runtime::RemoteDispatcher`'s own use) instead of comparing
|
|
29
|
+
# adapter names by string.
|
|
30
|
+
module RemoteRuntime
|
|
31
|
+
def append(*)
|
|
32
|
+
raise Runtime::WiringError,
|
|
33
|
+
"#{self.class.name} is a remote-runtime delegate — dispatch through " \
|
|
34
|
+
"Runtime::RemoteDispatcher instead of writing through the repository directly"
|
|
35
|
+
end
|
|
36
|
+
alias_method :project, :append
|
|
37
|
+
|
|
38
|
+
def entries = []
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require_relative "append_only"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
module Persistence
|
|
6
|
+
# Turns a declared adapter binding plus its world configuration into a
|
|
7
|
+
# concrete repository. Selection policy stays out of adapter creation.
|
|
8
|
+
module RepositoryFactory
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def build(registry, domain, aggregate, bind, recover: true, settings_verb: VERB)
|
|
12
|
+
registry.check_verb(bind)
|
|
13
|
+
settings = (registry.world(domain)&.for_binding(settings_verb, bind.adapter) || {})
|
|
14
|
+
.reject { |key, _| key.to_sym == :role }
|
|
15
|
+
registry.check_settings(bind, settings)
|
|
16
|
+
# The domain and the resolved era ride along after the
|
|
17
|
+
# declared-settings check: a lineage adapter journals per
|
|
18
|
+
# DOMAIN and writes into its own ERA's partition — neither of
|
|
19
|
+
# which a world's settings carry.
|
|
20
|
+
adapter = registry.adapter_class(bind.adapter)
|
|
21
|
+
.new(aggregate: aggregate,
|
|
22
|
+
settings: settings.merge(domain: domain.to_s, era: registry.resolved_eras[domain.to_s]),
|
|
23
|
+
root: registry.root)
|
|
24
|
+
repository = AppendOnly.new(adapter)
|
|
25
|
+
recover ? repository.recover! : repository
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Ports
|
|
3
|
+
module Persistence
|
|
4
|
+
NAME = "persistence"
|
|
5
|
+
VERB = "persisted_by"
|
|
6
|
+
DEFAULT_ADAPTER = "Memory"
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require_relative "persistence/binding_policy"
|
|
12
|
+
require_relative "persistence/plugin"
|
|
13
|
+
require_relative "persistence/repository_factory"
|
|
14
|
+
require_relative "persistence/append_only"
|
|
15
|
+
require_relative "persistence/execution"
|
|
16
|
+
require_relative "persistence/remote_runtime"
|
|
17
|
+
require_relative "persistence/null_saga_store"
|
|
18
|
+
|
|
19
|
+
module Hecks
|
|
20
|
+
module Ports
|
|
21
|
+
module Persistence
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# The public persistence port owns only authoritative aggregate heads.
|
|
25
|
+
def repository(registry, domain, aggregate)
|
|
26
|
+
authoritative = BindingPolicy.resolve(registry, domain, aggregate)
|
|
27
|
+
RepositoryFactory.build(registry, domain, aggregate, authoritative)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def binds_for(registry, domain, aggregate)
|
|
31
|
+
[BindingPolicy.resolve(registry, domain, aggregate), []]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def bind_for(registry, domain, aggregate)
|
|
35
|
+
binds_for(registry, domain, aggregate).first
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require_relative "persistence"
|
|
2
|
+
require_relative "persistence/repository_factory"
|
|
3
|
+
require_relative "../runtime/registry"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Ports
|
|
7
|
+
# Projection is a read-side port. Its stores are rebuildable consumers of
|
|
8
|
+
# authoritative entries and never participate in command decisions.
|
|
9
|
+
module Projection
|
|
10
|
+
NAME = "projection"
|
|
11
|
+
VERB = "projected_by"
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def binds_for(registry, domain, aggregate)
|
|
16
|
+
registry.hecksagon(domain)&.binds_for(aggregate.hecks_name, VERB) || []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def worker(registry, domain, aggregate, policy: :refresh)
|
|
20
|
+
bind = binds_for(registry, domain, aggregate).first
|
|
21
|
+
return unless bind
|
|
22
|
+
|
|
23
|
+
authoritative = Persistence.repository(registry, domain, aggregate)
|
|
24
|
+
target = Persistence::RepositoryFactory.build(registry, domain, aggregate, bind,
|
|
25
|
+
recover: true, settings_verb: VERB)
|
|
26
|
+
Worker.new(authoritative, target, policy: policy)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class Worker
|
|
30
|
+
attr_reader :projection
|
|
31
|
+
|
|
32
|
+
# The only two policies anything in this codebase ever passes
|
|
33
|
+
# (`bin/project`, every spec) — there is no third, legitimate
|
|
34
|
+
# "lenient append" policy on record anywhere. Before this, any
|
|
35
|
+
# value OTHER than the exact symbol `:strict` silently fell
|
|
36
|
+
# through the `consistent?` check below and appended onto
|
|
37
|
+
# divergent history without a word — not just a real typo like
|
|
38
|
+
# `:strikt`, but a caller-supplied String `"strict"` too (this
|
|
39
|
+
# duck-typed fine via `policy.to_sym`, but that was luck, not a
|
|
40
|
+
# contract: nothing here declared what a valid policy even was).
|
|
41
|
+
# Refusing loudly at construction, once, for anything outside
|
|
42
|
+
# this list turns a silent no-op into an immediate, named error
|
|
43
|
+
# — the "refuse rather than silently skip" reading of L1, since
|
|
44
|
+
# `:strict` really is meant to be the only enforcing contract.
|
|
45
|
+
VALID_POLICIES = %i[refresh strict].freeze
|
|
46
|
+
|
|
47
|
+
def initialize(authoritative, projection, policy: :refresh)
|
|
48
|
+
@authoritative = authoritative
|
|
49
|
+
@projection = projection
|
|
50
|
+
@policy = policy.to_sym
|
|
51
|
+
unless VALID_POLICIES.include?(@policy)
|
|
52
|
+
raise ArgumentError,
|
|
53
|
+
"unknown projection catch_up! policy #{@policy.inspect} — expected one of " \
|
|
54
|
+
"#{VALID_POLICIES.map(&:inspect).join(' or ')}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Invoke from a separate process or scheduler. The command-side write
|
|
59
|
+
# path never calls this method.
|
|
60
|
+
def catch_up!
|
|
61
|
+
entries = Queue.new(@authoritative).entries
|
|
62
|
+
present = @projection.entries
|
|
63
|
+
if @policy == :refresh
|
|
64
|
+
@projection.reset!
|
|
65
|
+
present = []
|
|
66
|
+
end
|
|
67
|
+
unless consistent?(entries, present)
|
|
68
|
+
raise Runtime::WiringError, "projection history does not match its authoritative history" if @policy == :strict
|
|
69
|
+
end
|
|
70
|
+
entries.drop(present.length).each { |entry| @projection.append(entry); @projection.project(entry) }
|
|
71
|
+
@projection
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def checkpoint = @projection.entries.length
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def same_entry?(left, right) = left.operation == right.operation && left.id == right.id && left.state == right.state
|
|
79
|
+
|
|
80
|
+
def consistent?(entries, present)
|
|
81
|
+
present.length <= entries.length && entries.first(present.length).zip(present).all? { |left, right|
|
|
82
|
+
same_entry?(left, right)
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The authoritative append-only journal is the durable projection queue.
|
|
88
|
+
# It is committed before a worker sees it; projection entries are the
|
|
89
|
+
# worker's durable checkpoint, so delivery is at-least-once and safe to replay.
|
|
90
|
+
class Queue
|
|
91
|
+
def initialize(authoritative) = @authoritative = authoritative
|
|
92
|
+
def entries = @authoritative.entries
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require_relative "ordering"
|
|
2
|
+
require_relative "../../runtime/value"
|
|
3
|
+
require_relative "../../query_specification/field_path"
|
|
4
|
+
require_relative "../../query_specification/common/comparison"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Ports
|
|
8
|
+
module Query
|
|
9
|
+
module InMemory
|
|
10
|
+
FieldPath = QuerySpecification::FieldPath
|
|
11
|
+
Comparison = QuerySpecification::Common::Comparison
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def execute(records, declared, args = {}, registry: nil)
|
|
16
|
+
matched = records.select do |record|
|
|
17
|
+
declared.wheres.all? { |clause|
|
|
18
|
+
holds?(clause, comparable(FieldPath.dig(record, clause.field)), args, registry: registry)
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
field = declared.order_by&.field
|
|
22
|
+
matched = Ordering.apply(matched, declared.order_by, declared.null_semantics,
|
|
23
|
+
identity: ->(record) { record.id.to_s }) { |record| comparable(FieldPath.dig(record, field)) }
|
|
24
|
+
# OFFSET FIRST, THEN LIMIT — the order SQL means by `LIMIT n
|
|
25
|
+
# OFFSET m`, which is what `SqlQueryBuilder` emits and therefore
|
|
26
|
+
# what every SQL-backed aggregate already answers. Written the
|
|
27
|
+
# other way round here, and the two engines disagreed on the
|
|
28
|
+
# same declaration: `limit 2, offset 1` over three rows is rows
|
|
29
|
+
# two and three in Postgres and Sqlite, and was row two alone in
|
|
30
|
+
# memory. Silently — a short page reads exactly like a page that
|
|
31
|
+
# ran out of rows.
|
|
32
|
+
#
|
|
33
|
+
# It gets worse the further you page, which is the case nobody
|
|
34
|
+
# writing the first page ever sees: at `limit 10, offset 10`,
|
|
35
|
+
# taking ten and then dropping ten leaves NOTHING, so page two
|
|
36
|
+
# of a memory-backed query came back empty however many rows
|
|
37
|
+
# were really there.
|
|
38
|
+
matched = matched.drop(resolve(declared.offset.value, args).to_i) if declared.offset
|
|
39
|
+
matched = matched.first(resolve(declared.limit.value, args).to_i) if declared.limit
|
|
40
|
+
matched
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The comparator table itself lives in
|
|
44
|
+
# QuerySpecification::Common::Comparison — this module and
|
|
45
|
+
# Runtime::QueryInterpreter used to carry a copy each, and the two
|
|
46
|
+
# drifted (see that file's own comment for what it cost). What
|
|
47
|
+
# stays here is how a value is REACHED for this path: a registry
|
|
48
|
+
# arrives as an argument rather than as instance state, and the
|
|
49
|
+
# field is dug through FieldPath before it arrives.
|
|
50
|
+
def holds?(clause, held, args, registry: nil)
|
|
51
|
+
Comparison.holds?(clause.op, held, comparable(resolve(clause.value, args)), registry: registry)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def resolve(value, args) = value.is_a?(Symbol) ? args[value] : value
|
|
55
|
+
|
|
56
|
+
def comparable(value) = Comparison.comparable(value)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require_relative "../../query_specification/common/null_policy"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
module Query
|
|
6
|
+
# What order an ask ANSWERS IN — the meaning of the ask, not a property
|
|
7
|
+
# of the store that happens to hold it. Declared here once so an adapter
|
|
8
|
+
# may satisfy it natively but never redefine it : SQLite pushes both
|
|
9
|
+
# tiers into SQL (NullPolicy.sql_order renders `field DIR, id DIR`),
|
|
10
|
+
# while Heki and Memory have no query engine and delegate straight back
|
|
11
|
+
# to InMemory.
|
|
12
|
+
#
|
|
13
|
+
# Two tiers, in this order : the DECLARED order_by when there is one,
|
|
14
|
+
# then IDENTITY, always. The identity tier is what makes an ask total.
|
|
15
|
+
# Without it, an ask with no order_by — or a declared order with tied
|
|
16
|
+
# keys — hands back whatever order the store happened to hold, and
|
|
17
|
+
# store order was quietly standing in for a rule while every
|
|
18
|
+
# hand-written query in the corpus stayed green : not one of them
|
|
19
|
+
# had a tie for store order to decide.
|
|
20
|
+
#
|
|
21
|
+
# An adapter that pushes ordering down MUST push limit down with it.
|
|
22
|
+
# Re-ordering a page the store already cut would be a top-N of the
|
|
23
|
+
# wrong N — the one way this can be got quietly, expensively wrong.
|
|
24
|
+
module Ordering
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
def apply(rows, order_by, null_semantics = nil, identity:, &value_of)
|
|
28
|
+
# STABLE, because sort_by is not : two rows whose identity ties would
|
|
29
|
+
# otherwise swap arbitrarily, and a tier meant to REMOVE store-dependence
|
|
30
|
+
# would be adding a coin flip of its own.
|
|
31
|
+
rows = rows.each_with_index.sort_by { |row, index| [identity.call(row), index] }.map(&:first)
|
|
32
|
+
return rows unless order_by
|
|
33
|
+
|
|
34
|
+
QuerySpecification::Common::NullPolicy.order(
|
|
35
|
+
rows, direction: order_by.direction, policy: null_semantics, &value_of
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|