hecks 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
- data/lib/hecks/adapters/driven/claude_code.rb +127 -0
- data/lib/hecks/adapters/driven/d1.adapter +12 -0
- data/lib/hecks/adapters/driven/d1.rb +403 -0
- data/lib/hecks/adapters/driven/folder.adapter +3 -0
- data/lib/hecks/adapters/driven/folder.rb +199 -0
- data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
- data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
- data/lib/hecks/adapters/driven/governance_authorization.rb +62 -0
- data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
- data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
- data/lib/hecks/adapters/driven/heki.adapter +4 -0
- data/lib/hecks/adapters/driven/heki.rb +148 -0
- data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
- data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
- data/lib/hecks/adapters/driven/lambda.adapter +4 -0
- data/lib/hecks/adapters/driven/lambda.rb +131 -0
- data/lib/hecks/adapters/driven/memory.adapter +3 -0
- data/lib/hecks/adapters/driven/memory.rb +103 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
- data/lib/hecks/adapters/driven/postgres/codec.rb +80 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
- data/lib/hecks/adapters/driven/postgres.adapter +5 -0
- data/lib/hecks/adapters/driven/postgres.rb +391 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
- data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
- data/lib/hecks/adapters/driven/prism.adapter +3 -0
- data/lib/hecks/adapters/driven/prism.rb +80 -0
- data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
- data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
- data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
- data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
- data/lib/hecks/adapters/driven/sqlite.rb +285 -0
- data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +14 -0
- data/lib/hecks/adapters/driven.rb +36 -0
- data/lib/hecks/adapters.rb +6 -0
- data/lib/hecks/behaviors/dsl.rb +96 -0
- data/lib/hecks/behaviors/expectations.rb +280 -0
- data/lib/hecks/behaviors/ir.rb +31 -0
- data/lib/hecks/behaviors/rspec.rb +42 -0
- data/lib/hecks/behaviors/runner.rb +96 -0
- data/lib/hecks/behaviors.rb +25 -0
- data/lib/hecks/bluebook/aggregate.rb +108 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
- data/lib/hecks/bluebook/assembly/build.rb +48 -0
- data/lib/hecks/bluebook/assembly/contract.rb +112 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
- data/lib/hecks/bluebook/assembly/marks.rb +227 -0
- data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
- data/lib/hecks/bluebook/assembly.rb +78 -0
- data/lib/hecks/bluebook/attribute.rb +96 -0
- data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
- data/lib/hecks/bluebook/behaviour/command.rb +116 -0
- data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
- data/lib/hecks/bluebook/behaviour/query.rb +10 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
- data/lib/hecks/bluebook/chapter.rb +68 -0
- data/lib/hecks/bluebook/command.rb +124 -0
- data/lib/hecks/bluebook/domain_port.rb +102 -0
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
- data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
- data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
- data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
- data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
- data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
- data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
- data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
- data/lib/hecks/bluebook/dsl.rb +45 -0
- data/lib/hecks/bluebook/entity.rb +103 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
- data/lib/hecks/bluebook/expression/projection.json +170 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
- data/lib/hecks/bluebook/expression/resolver.rb +630 -0
- data/lib/hecks/bluebook/expression.rb +13 -0
- data/lib/hecks/bluebook/hexagon.rb +60 -0
- data/lib/hecks/bluebook/lifecycle.rb +42 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
- data/lib/hecks/bluebook/meta_validator.rb +452 -0
- data/lib/hecks/bluebook/model_check.rb +318 -0
- data/lib/hecks/bluebook/pattern_subset.rb +120 -0
- data/lib/hecks/bluebook/policy.rb +41 -0
- data/lib/hecks/bluebook/process_manager.rb +86 -0
- data/lib/hecks/bluebook/project_discovery.rb +30 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +82 -0
- data/lib/hecks/bluebook/query.rb +61 -0
- data/lib/hecks/bluebook/read_model.rb +109 -0
- data/lib/hecks/bluebook/reference.rb +74 -0
- data/lib/hecks/bluebook/smoke_test.rb +166 -0
- data/lib/hecks/bluebook/synthesizer.rb +95 -0
- data/lib/hecks/bluebook/translation.rb +92 -0
- data/lib/hecks/bluebook/value_object.rb +58 -0
- data/lib/hecks/bluebook.rb +74 -0
- data/lib/hecks/codemod.rb +342 -0
- data/lib/hecks/construct.rb +71 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
- data/lib/hecks/deploy/oidc.json +18 -0
- data/lib/hecks/doc/reference.rb +389 -0
- data/lib/hecks/embryonaut_bluebook.rb +75 -0
- data/lib/hecks/facade/cli_door.rb +116 -0
- data/lib/hecks/facade/cli_runner.rb +190 -0
- data/lib/hecks/facade/command_request.rb +105 -0
- data/lib/hecks/facade/handle.rb +173 -0
- data/lib/hecks/facade/json_door.rb +154 -0
- data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
- data/lib/hecks/facade/surface/chapter.rb +107 -0
- data/lib/hecks/facade/surface.rb +48 -0
- data/lib/hecks/facade.rb +44 -0
- data/lib/hecks/forms/app.rb +300 -0
- data/lib/hecks/forms/command_form_renderer.rb +113 -0
- data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
- data/lib/hecks/forms/field_renderer.rb +170 -0
- data/lib/hecks/forms/field_shape.rb +232 -0
- data/lib/hecks/forms/html.rb +53 -0
- data/lib/hecks/forms/index_renderer.rb +35 -0
- data/lib/hecks/forms/page.rb +157 -0
- data/lib/hecks/forms/params.rb +132 -0
- data/lib/hecks/forms/query_form_renderer.rb +114 -0
- data/lib/hecks/forms/record_renderer.rb +115 -0
- data/lib/hecks/forms/record_table.rb +63 -0
- data/lib/hecks/forms/reference_options.rb +30 -0
- data/lib/hecks/forms/value_object_shape.rb +46 -0
- data/lib/hecks/forms.rb +54 -0
- data/lib/hecks/fqn.rb +94 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
- data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
- data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
- data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
- data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
- data/lib/hecks/framework/oidc.json +39 -0
- data/lib/hecks/framework.rb +90 -0
- data/lib/hecks/freezer.rb +67 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
- data/lib/hecks/fuzzing/properties.rb +1146 -0
- data/lib/hecks/fuzzing/replay.rb +635 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
- data/lib/hecks/fuzzing/value_generator.rb +177 -0
- data/lib/hecks/fuzzing.rb +12 -0
- data/lib/hecks/grammar/evolve.rb +291 -0
- data/lib/hecks/grammar/expression.bluebook +417 -0
- data/lib/hecks/grammar/expression_operators.json +1152 -0
- data/lib/hecks/grammar/grammar.hecksagon +20 -0
- data/lib/hecks/grammar/oidc.json +69 -0
- data/lib/hecks/grammar/translation.bluebook +203 -0
- data/lib/hecks/grammar.rb +178 -0
- data/lib/hecks/ir.rb +126 -0
- data/lib/hecks/language/adapter.bluebook +116 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
- data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
- data/lib/hecks/language/bluebook/command.bluebook +437 -0
- data/lib/hecks/language/bluebook/entity.bluebook +316 -0
- data/lib/hecks/language/bluebook/policy.bluebook +172 -0
- data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
- data/lib/hecks/language/bluebook/projection.bluebook +267 -0
- data/lib/hecks/language/bluebook/query.bluebook +245 -0
- data/lib/hecks/language/bluebook/shape.bluebook +292 -0
- data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
- data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
- data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
- data/lib/hecks/language/oidc.json +313 -0
- data/lib/hecks/language/port.bluebook +92 -0
- data/lib/hecks/language/translation/translation.bluebook +110 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
- data/lib/hecks/language/world/wiring.bluebook +62 -0
- data/lib/hecks/language/world/world.bluebook +84 -0
- data/lib/hecks/literal.rb +125 -0
- data/lib/hecks/naming.rb +121 -0
- data/lib/hecks/ports/access_control.port +4 -0
- data/lib/hecks/ports/access_control.rb +62 -0
- data/lib/hecks/ports/agent/answers.rb +104 -0
- data/lib/hecks/ports/agent.port +4 -0
- data/lib/hecks/ports/agent.rb +167 -0
- data/lib/hecks/ports/authentication.port +4 -0
- data/lib/hecks/ports/authentication.rb +50 -0
- data/lib/hecks/ports/authorization.port +4 -0
- data/lib/hecks/ports/authorization.rb +59 -0
- data/lib/hecks/ports/clock.port +4 -0
- data/lib/hecks/ports/clock.rb +62 -0
- data/lib/hecks/ports/extraction.port +4 -0
- data/lib/hecks/ports/extraction.rb +37 -0
- data/lib/hecks/ports/identity_assignment.port +4 -0
- data/lib/hecks/ports/identity_assignment.rb +45 -0
- data/lib/hecks/ports/identity_generation.port +4 -0
- data/lib/hecks/ports/identity_generation.rb +49 -0
- data/lib/hecks/ports/identity_resolution.port +4 -0
- data/lib/hecks/ports/identity_resolution.rb +40 -0
- data/lib/hecks/ports/loading.port +4 -0
- data/lib/hecks/ports/loading.rb +13 -0
- data/lib/hecks/ports/persistence/append_only.rb +102 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
- data/lib/hecks/ports/persistence/execution.rb +18 -0
- data/lib/hecks/ports/persistence/lineage.rb +277 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
- data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
- data/lib/hecks/ports/persistence.port +4 -0
- data/lib/hecks/ports/persistence.rb +39 -0
- data/lib/hecks/ports/projection.port +4 -0
- data/lib/hecks/ports/projection.rb +76 -0
- data/lib/hecks/ports/query/in_memory.rb +60 -0
- data/lib/hecks/ports/query/ordering.rb +41 -0
- data/lib/hecks/ports/query.rb +36 -0
- data/lib/hecks/ports.rb +27 -0
- data/lib/hecks/projections/diagrams.rb +413 -0
- data/lib/hecks/projections/ir.rb +18 -0
- data/lib/hecks/projections/model/deviations.rb +98 -0
- data/lib/hecks/projections/model.rb +145 -0
- data/lib/hecks/projections/oidc.rb +110 -0
- data/lib/hecks/projections/parser_table.rb +159 -0
- data/lib/hecks/projections/reference.rb +38 -0
- data/lib/hecks/projections/shape.rb +31 -0
- data/lib/hecks/projections/statements.rb +110 -0
- data/lib/hecks/projections/vocabulary.rb +100 -0
- data/lib/hecks/projections.rb +32 -0
- data/lib/hecks/projector/cli_projector.rb +437 -0
- data/lib/hecks/projector/docs_projector.rb +321 -0
- data/lib/hecks/projector/exporter.rb +141 -0
- data/lib/hecks/projector/ir_projector.rb +18 -0
- data/lib/hecks/projector/narrate_projector.rb +243 -0
- data/lib/hecks/projector/target.rb +97 -0
- data/lib/hecks/projector.rb +186 -0
- data/lib/hecks/query_ir.rb +392 -0
- data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
- data/lib/hecks/query_specification/common/comparators.rb +25 -0
- data/lib/hecks/query_specification/common/comparison.rb +174 -0
- data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
- data/lib/hecks/query_specification/common/dsl.rb +58 -0
- data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
- data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
- data/lib/hecks/query_specification/common/null_policy.rb +80 -0
- data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
- data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
- data/lib/hecks/query_specification/common/options.rb +32 -0
- data/lib/hecks/query_specification/common/order_by.rb +9 -0
- data/lib/hecks/query_specification/common/specification.rb +10 -0
- data/lib/hecks/query_specification/common/where_clause.rb +9 -0
- data/lib/hecks/query_specification/field_path.rb +89 -0
- data/lib/hecks/query_specification/hop_path.rb +130 -0
- data/lib/hecks/query_specification/read_model/specification.rb +18 -0
- data/lib/hecks/query_specification.rb +14 -0
- data/lib/hecks/rendering.rb +48 -0
- data/lib/hecks/router/namespace_installer.rb +157 -0
- data/lib/hecks/router.rb +70 -0
- data/lib/hecks/runtime/caller.rb +50 -0
- data/lib/hecks/runtime/capability_graph.rb +44 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
- data/lib/hecks/runtime/command_interpreter.rb +413 -0
- data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
- data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
- data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
- data/lib/hecks/runtime/command_rules/emission.rb +34 -0
- data/lib/hecks/runtime/command_rules/references.rb +181 -0
- data/lib/hecks/runtime/command_rules.rb +28 -0
- data/lib/hecks/runtime/dependency_planning.rb +231 -0
- data/lib/hecks/runtime/dispatcher.rb +286 -0
- data/lib/hecks/runtime/entity_element.rb +253 -0
- data/lib/hecks/runtime/entity_interpreter.rb +228 -0
- data/lib/hecks/runtime/era_check.rb +136 -0
- data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
- data/lib/hecks/runtime/era_guard.rb +193 -0
- data/lib/hecks/runtime/era_tamper.rb +61 -0
- data/lib/hecks/runtime/errors.rb +100 -0
- data/lib/hecks/runtime/event.rb +51 -0
- data/lib/hecks/runtime/identity.rb +128 -0
- data/lib/hecks/runtime/instance.rb +120 -0
- data/lib/hecks/runtime/interpreting.rb +69 -0
- data/lib/hecks/runtime/loader.rb +143 -0
- data/lib/hecks/runtime/policy_interpreter.rb +366 -0
- data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
- data/lib/hecks/runtime/query_interpreter.rb +254 -0
- data/lib/hecks/runtime/reaction_invocation.rb +245 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
- data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
- data/lib/hecks/runtime/reference_hop.rb +99 -0
- data/lib/hecks/runtime/refusal_wording.rb +124 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
- data/lib/hecks/runtime/registry/verification.rb +158 -0
- data/lib/hecks/runtime/registry.rb +218 -0
- data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
- data/lib/hecks/runtime/routing.rb +96 -0
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
- data/lib/hecks/runtime/saga_interpreter.rb +301 -0
- data/lib/hecks/runtime/storage_shape.rb +120 -0
- data/lib/hecks/runtime/tenant_check.rb +84 -0
- data/lib/hecks/runtime/tenant_scope.rb +55 -0
- data/lib/hecks/runtime/value/admission.rb +112 -0
- data/lib/hecks/runtime/value/coercion.rb +500 -0
- data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
- data/lib/hecks/runtime/value.rb +125 -0
- data/lib/hecks/runtime.rb +95 -0
- data/lib/hecks/translation/audit/approval_digest.rb +31 -0
- data/lib/hecks/translation/audit/layer_one.rb +37 -0
- data/lib/hecks/translation/audit/layer_two.rb +77 -0
- data/lib/hecks/translation/audit/unfed_report.rb +42 -0
- data/lib/hecks/translation/audit.rb +70 -0
- data/lib/hecks/translation/reattest.rb +72 -0
- data/lib/hecks/translation/rule_compiler.rb +120 -0
- data/lib/hecks/translation/scaffold/differ.rb +183 -0
- data/lib/hecks/translation/scaffold/renderer.rb +41 -0
- data/lib/hecks/translation/scaffold/writer.rb +28 -0
- data/lib/hecks/translation/scaffold.rb +30 -0
- data/lib/hecks/translation.rb +11 -0
- data/lib/hecks/version.rb +16 -0
- data/lib/hecks/vocabulary.rb +213 -0
- data/lib/hecks.rb +118 -6
- metadata +401 -222
- data/bin/hecks +0 -7
- data/bin/hecks-package +0 -65
- data/bin/hecks_console +0 -12
- data/bin/hecks_serverless +0 -6
- data/lib/cli/build.rb +0 -14
- data/lib/cli/command_runner.rb +0 -28
- data/lib/cli/console.rb +0 -10
- data/lib/cli/generate.rb +0 -37
- data/lib/cli/hecks-cli.rb +0 -27
- data/lib/cli/test.rb +0 -57
- data/lib/console/commands.rb +0 -8
- data/lib/console/hecks-console.rb +0 -1
- data/lib/packager/README.md +0 -0
- data/lib/packager/app_runner.rb +0 -21
- data/lib/packager/args.rb +0 -26
- data/lib/packager/compatibility/fixnum.rb +0 -6
- data/lib/packager/hecks.rb +0 -39
- data/lib/packager/query_runner.rb +0 -21
- data/lib/packager/resources/Dockerfile +0 -11
- data/lib/packager/resources/app_binary +0 -7
- data/lib/packager/resources/bundle_config +0 -3
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
- data/lib/serverless/Domain +0 -32
- data/lib/serverless/cli.rb +0 -75
- data/lib/serverless/resources/command_name.js +0 -5
- data/lib/serverless/resources/environment.js +0 -7
- data/lib/serverless/resources/handler.js.tt +0 -28
- data/lib/serverless/resources/run_binary.js +0 -22
- data/lib/serverless/resources/serverless.yml +0 -20
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Hecks::Runtime
|
|
2
|
+
#
|
|
3
|
+
# The runtime layer's face. `lib/hecks/runtime/` held thirteen files and
|
|
4
|
+
# no parent, so the layer had no surface of its own : `lib/hecks.rb`
|
|
5
|
+
# required each file by hand, and the runtime's own state — the registry a
|
|
6
|
+
# declaration is being collected into — sat on the top-level Hecks module
|
|
7
|
+
# beside the DSL words.
|
|
8
|
+
#
|
|
9
|
+
# What lives here is what RUNS a domain :
|
|
10
|
+
#
|
|
11
|
+
# Runtime.boot(path) load a directory and return its Dispatcher
|
|
12
|
+
# Runtime.with_registry(r) bind the ambient registry for the duration of a load
|
|
13
|
+
# Runtime.current_registry the registry a declaration is landing in, or nil
|
|
14
|
+
#
|
|
15
|
+
# What does NOT live here is what DECLARES one — `Hecks.bluebook`,
|
|
16
|
+
# `.hecksagon`, `.port`, `.adapter`, `.world` are loading words and stay on the
|
|
17
|
+
# top-level module, which now reads as a facade over this.
|
|
18
|
+
#
|
|
19
|
+
# runtime = Hecks::Runtime.boot("examples/pizzas/bluebook")
|
|
20
|
+
# runtime.dispatch("Pizzas::Pizza.CreatePizza", name: "Margherita")
|
|
21
|
+
#
|
|
22
|
+
# NOTE, and it is the reason this file exists : `current_registry` is still
|
|
23
|
+
# process-global. Each boot builds a fresh Registry, and `Loader.bind_runtime`
|
|
24
|
+
# installs a fresh facade door whose modules close over THAT boot's dispatcher
|
|
25
|
+
# — no class-level runtime binding remains, so two boots in one process no
|
|
26
|
+
# longer share dispatch state, only the top-level NAME (the last-bound door
|
|
27
|
+
# wins the constant, which is what per-boot install means). Owning the state
|
|
28
|
+
# here was the first move ; the dispatcher reachable per-runtime rather than
|
|
29
|
+
# through a constant was the second, and it is done — the door is the only
|
|
30
|
+
# constant left, and it is a projection, not the runtime.
|
|
31
|
+
|
|
32
|
+
require_relative "runtime/event"
|
|
33
|
+
require_relative "runtime/value"
|
|
34
|
+
require_relative "runtime/identity"
|
|
35
|
+
require_relative "runtime/instance"
|
|
36
|
+
require_relative "runtime/registry"
|
|
37
|
+
require_relative "runtime/capability_graph"
|
|
38
|
+
require_relative "runtime/errors"
|
|
39
|
+
require_relative "runtime/refusal_wording"
|
|
40
|
+
require_relative "runtime/caller"
|
|
41
|
+
require_relative "runtime/routing"
|
|
42
|
+
require_relative "runtime/command_rules"
|
|
43
|
+
require_relative "runtime/dependency_planning"
|
|
44
|
+
require_relative "runtime/command_interpreter"
|
|
45
|
+
require_relative "runtime/port_operation_interpreter"
|
|
46
|
+
require_relative "runtime/entity_interpreter"
|
|
47
|
+
require_relative "runtime/query_interpreter"
|
|
48
|
+
require_relative "runtime/read_model_interpreter"
|
|
49
|
+
require_relative "runtime/policy_interpreter"
|
|
50
|
+
require_relative "runtime/saga_interpreter"
|
|
51
|
+
require_relative "runtime/dispatcher"
|
|
52
|
+
require_relative "runtime/rebuild_sweep"
|
|
53
|
+
require_relative "runtime/era_guard"
|
|
54
|
+
require_relative "runtime/storage_shape"
|
|
55
|
+
require_relative "runtime/era_tamper"
|
|
56
|
+
require_relative "runtime/era_check"
|
|
57
|
+
require_relative "runtime/tenant_check"
|
|
58
|
+
require_relative "runtime/loader"
|
|
59
|
+
|
|
60
|
+
module Hecks
|
|
61
|
+
module Runtime
|
|
62
|
+
class << self
|
|
63
|
+
# The registry declarations are currently landing in, or nil outside a
|
|
64
|
+
# boot. Read by the DSL collectors on the top-level module and by the
|
|
65
|
+
# extraction port.
|
|
66
|
+
attr_reader :current_registry
|
|
67
|
+
|
|
68
|
+
# Load a bluebook directory and return the Dispatcher bound to it.
|
|
69
|
+
# `install_facade:`, `environment:` — see Loader.boot.
|
|
70
|
+
def boot(path, shared: nil, install_facade: true, environment: nil)
|
|
71
|
+
Loader.boot(path, shared: shared, install_facade: install_facade, environment: environment)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# `paths` form — see Loader.boot_files.
|
|
75
|
+
def boot_files(paths, shared: nil, install_facade: true, environment: nil)
|
|
76
|
+
Loader.boot_files(paths, shared: shared, install_facade: install_facade, environment: environment)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Bind the ambient registry for the duration of the block, restoring
|
|
80
|
+
# whatever was there before. Nesting is safe ; a raise still restores.
|
|
81
|
+
def with_registry(registry)
|
|
82
|
+
previous = @current_registry
|
|
83
|
+
@current_registry = registry
|
|
84
|
+
yield
|
|
85
|
+
ensure
|
|
86
|
+
@current_registry = previous
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Bind the ambient caller (see Runtime::Caller) for the duration of
|
|
90
|
+
# the block — who a command's declared `role`, if any, is checked
|
|
91
|
+
# against.
|
|
92
|
+
def as_caller(role:, actor_id: nil, &block) = Caller.as(role: role, actor_id: actor_id, &block)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "digest"
|
|
2
|
+
require_relative "../../projector/exporter"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Translation
|
|
6
|
+
module Audit
|
|
7
|
+
# ── the human gate, made real ──────────────────────────────────
|
|
8
|
+
#
|
|
9
|
+
# For the five portable rule kinds the mint verifies the edge
|
|
10
|
+
# mechanically (Layer 2 IS the cross-execution equivalence gate).
|
|
11
|
+
# A compute has no mechanical verification — the Layer-3 sample a
|
|
12
|
+
# human reads is the only one there is — so a compute edge cannot
|
|
13
|
+
# mint until someone has run `bin/translation_audit … --approve`.
|
|
14
|
+
#
|
|
15
|
+
# The approval binds to WHAT WAS ACTUALLY REVIEWED, on both axes:
|
|
16
|
+
# a content digest of the parsed edge (change the edge's meaning
|
|
17
|
+
# and the approval lapses; a comment does not), and the journal's
|
|
18
|
+
# high-water ordinal at review time, recorded IN the target
|
|
19
|
+
# database — a token in the repo could not bind to the data, and
|
|
20
|
+
# samples reviewed against staging or against production-as-of-T
|
|
21
|
+
# say nothing about a database that has moved on. Mint stays
|
|
22
|
+
# non-interactive and its lock stays short; the human decision
|
|
23
|
+
# happens in the audit tool, where the samples are.
|
|
24
|
+
module ApprovalDigest
|
|
25
|
+
def edge_digest(edge)
|
|
26
|
+
Digest::SHA256.hexdigest(JSON.generate(Projector::Exporter.translation_hash(edge)))
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require_relative "../../runtime/errors"
|
|
2
|
+
require_relative "../../runtime/instance"
|
|
3
|
+
require_relative "../../runtime/value/invariant_violation"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Translation
|
|
7
|
+
module Audit
|
|
8
|
+
# Layer 1 — from the bluebook alone: every translated state must
|
|
9
|
+
# pass the new era's types, value-object invariants, and lifecycle.
|
|
10
|
+
# This is also where a NEW, stricter invariant that old records
|
|
11
|
+
# violate surfaces — there is no "grandfather old records"
|
|
12
|
+
# construct, and the remedy is relaxing the invariant or explicit
|
|
13
|
+
# remediation, never a translation rule.
|
|
14
|
+
module LayerOne
|
|
15
|
+
def layer_one!(violations, aggregate, after)
|
|
16
|
+
after.each do |id, state|
|
|
17
|
+
begin
|
|
18
|
+
symbolized = JSON.parse(JSON.generate(state), symbolize_names: true)
|
|
19
|
+
instance = Runtime::Instance.new(aggregate: aggregate, id: id, state: symbolized)
|
|
20
|
+
lifecycle = aggregate.lifecycle
|
|
21
|
+
next unless lifecycle
|
|
22
|
+
|
|
23
|
+
held = instance[lifecycle.field]
|
|
24
|
+
allowed = lifecycle.states | [lifecycle.default]
|
|
25
|
+
unless held.nil? || allowed.include?(held)
|
|
26
|
+
violations << "#{aggregate.name}##{id}: #{lifecycle.field} is #{held.inspect}, " \
|
|
27
|
+
"a state this era's lifecycle never reaches"
|
|
28
|
+
end
|
|
29
|
+
rescue Runtime::InvariantViolation, Runtime::TypeMismatch => error
|
|
30
|
+
violations << "#{aggregate.name}##{id}: #{error.message}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require_relative "../../ports/persistence/append_only"
|
|
2
|
+
require_relative "../../ports/persistence/lineage"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Translation
|
|
6
|
+
module Audit
|
|
7
|
+
# Layer 2 — from the edge alone: per-rule value preservation, no
|
|
8
|
+
# leftover source keys, and id-set conservation across the edge.
|
|
9
|
+
module LayerTwo
|
|
10
|
+
# Per-rule value preservation and leftover source keys are checked
|
|
11
|
+
# against the reference transform IN FULL — never rule by rule in
|
|
12
|
+
# isolation, because rules interact (a rename whose value a later
|
|
13
|
+
# move partially consumes preserves exactly what the transform
|
|
14
|
+
# says it preserves, no more). This makes every mint a run of the
|
|
15
|
+
# cross-execution equivalence gate for the five portable rule
|
|
16
|
+
# kinds: the compiled SQL produced `after`; the port's entry-JSON
|
|
17
|
+
# transform produces `expected`; they must agree byte-for-byte on
|
|
18
|
+
# every path a compute doesn't own. Compute paths are exempt — the
|
|
19
|
+
# SQL is their only implementation, and the Layer-3 sample is
|
|
20
|
+
# their only review. A rekeyed aggregate is exempt from this WHOLE
|
|
21
|
+
# per-record check, for the same reason and one more: there is no
|
|
22
|
+
# old-id → new-id correspondence to look `after` up by once the id
|
|
23
|
+
# itself is what changed.
|
|
24
|
+
def layer_two!(violations, aggregate, declared, before, after)
|
|
25
|
+
rekeyed = declared && !declared.rekeys.empty?
|
|
26
|
+
|
|
27
|
+
# A rekey legitimately changes the id SET (that's the entire
|
|
28
|
+
# point) — set-equality would flag every honest rekey as data
|
|
29
|
+
# loss. What must still hold is RECORD COUNT: a botched rekey
|
|
30
|
+
# colliding two distinct old ids onto one new id, or dropping one
|
|
31
|
+
# (its SQL returning NULL), shows up as the count going down —
|
|
32
|
+
# caught here without needing to track the old→new mapping
|
|
33
|
+
# itself.
|
|
34
|
+
if rekeyed
|
|
35
|
+
unless before.keys.size == after.keys.size
|
|
36
|
+
violations << "#{aggregate.name}: the record count changed across a rekeying edge " \
|
|
37
|
+
"(#{before.keys.size} before, #{after.keys.size} after) — a rekey must not " \
|
|
38
|
+
"collide two distinct ids onto one, or drop one"
|
|
39
|
+
end
|
|
40
|
+
elsif before.keys.sort != after.keys.sort
|
|
41
|
+
gained = after.keys - before.keys
|
|
42
|
+
lost = before.keys - after.keys
|
|
43
|
+
violations << "#{aggregate.name}: the id set changed across the edge " \
|
|
44
|
+
"(lost #{lost.sort.inspect}, gained #{gained.sort.inspect})"
|
|
45
|
+
end
|
|
46
|
+
return unless declared
|
|
47
|
+
# No correspondence between an old id and its rekeyed row exists
|
|
48
|
+
# in Ruby (the rekey's SQL is its only implementation, same as
|
|
49
|
+
# compute's) — exempt from the per-record equivalence gate for
|
|
50
|
+
# the same reason compute paths already are, extended to the
|
|
51
|
+
# whole record since an old-id lookup into `after` can never
|
|
52
|
+
# succeed once the id itself has changed.
|
|
53
|
+
return if rekeyed
|
|
54
|
+
|
|
55
|
+
rules = Ports::Persistence::Lineage.from_declared(declared, aggregate.name)
|
|
56
|
+
compute_tops = declared.computes.flat_map { |compute| [compute.from, compute.to] }
|
|
57
|
+
.map { |path| path.to_s.split(".").first }
|
|
58
|
+
|
|
59
|
+
before.each do |id, state|
|
|
60
|
+
next unless after.key?(id)
|
|
61
|
+
|
|
62
|
+
entry = Ports::Persistence::Entry.new(operation: "save", id: id, state: state.transform_keys(&:to_sym))
|
|
63
|
+
expected = normalize(rules.translate(entry).state).reject { |key, _| compute_tops.include?(key) }
|
|
64
|
+
actual = normalize(after[id]).reject { |key, _| compute_tops.include?(key) }
|
|
65
|
+
next if expected == actual
|
|
66
|
+
|
|
67
|
+
diverged = (expected.keys | actual.keys).select { |key| expected[key] != actual[key] }
|
|
68
|
+
violations << "#{aggregate.name}##{id}: the translated state diverges from the reference " \
|
|
69
|
+
"transform at #{diverged.sort.join(', ')}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def normalize(state) = JSON.parse(JSON.generate(state))
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Translation
|
|
3
|
+
module Audit
|
|
4
|
+
# New-era attributes that nothing feeds: absent from every
|
|
5
|
+
# translated state, produced by no rule, and carrying no default.
|
|
6
|
+
# A report, not a violation — the remedy is a `default:` on the
|
|
7
|
+
# attribute, and the loud refusal for a REQUIRED one comes from
|
|
8
|
+
# Layer 1 the moment an invariant reads it.
|
|
9
|
+
module UnfedReport
|
|
10
|
+
def unfed(aggregate, declared, after)
|
|
11
|
+
fed = if declared
|
|
12
|
+
(declared.renames.values.map(&:to_s) +
|
|
13
|
+
declared.moves.map(&:to) +
|
|
14
|
+
declared.converts.map(&:to) +
|
|
15
|
+
declared.computes.map(&:to)).map { |path| path.to_s.split(".").first }
|
|
16
|
+
else
|
|
17
|
+
[]
|
|
18
|
+
end
|
|
19
|
+
aggregate.attributes.filter_map do |attribute|
|
|
20
|
+
name = attribute.name.to_s
|
|
21
|
+
next if fed.include?(name)
|
|
22
|
+
next unless attribute.default.nil?
|
|
23
|
+
next if after.any? { |_, state| !dig_path(state, name).nil? }
|
|
24
|
+
next if after.empty?
|
|
25
|
+
|
|
26
|
+
name
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def dig_path(state, path)
|
|
31
|
+
return nil if state.nil?
|
|
32
|
+
|
|
33
|
+
path.to_s.split(".").reduce(state) do |node, segment|
|
|
34
|
+
break nil unless node.is_a?(Hash)
|
|
35
|
+
|
|
36
|
+
node[segment] || node[segment.to_sym]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
require_relative "audit/layer_one"
|
|
4
|
+
require_relative "audit/layer_two"
|
|
5
|
+
require_relative "audit/unfed_report"
|
|
6
|
+
require_relative "audit/approval_digest"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module Translation
|
|
10
|
+
# The audit derives its assertions; nobody hand-lists them. One file
|
|
11
|
+
# per layer beside this one: audit/layer_one.rb (the bluebook's own
|
|
12
|
+
# rules), audit/layer_two.rb (the edge against the reference
|
|
13
|
+
# transform), audit/unfed_report.rb (what nothing feeds), and
|
|
14
|
+
# audit/approval_digest.rb (the human gate's binding). Layer 3 — the
|
|
15
|
+
# before/after sample a HUMAN approves, intent not being derivable —
|
|
16
|
+
# is assembled right here in `check`.
|
|
17
|
+
module Audit
|
|
18
|
+
Verdict = Struct.new(:violations, :dropped, :unfed, :samples, keyword_init: true) do
|
|
19
|
+
def ok? = violations.empty?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
SAMPLE_SIZE = 5
|
|
23
|
+
|
|
24
|
+
extend LayerOne
|
|
25
|
+
extend LayerTwo
|
|
26
|
+
extend UnfedReport
|
|
27
|
+
extend ApprovalDigest
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# aggregate — the CURRENT era's IR; declared — this edge's rules
|
|
32
|
+
# for it (may be nil); before/after — {id => state hash}, source
|
|
33
|
+
# era's latest vs translated.
|
|
34
|
+
def check(aggregate:, declared:, before:, after:)
|
|
35
|
+
violations = []
|
|
36
|
+
|
|
37
|
+
layer_one!(violations, aggregate, after)
|
|
38
|
+
layer_two!(violations, aggregate, declared, before, after)
|
|
39
|
+
|
|
40
|
+
Verdict.new(
|
|
41
|
+
violations: violations,
|
|
42
|
+
dropped: declared ? declared.drops.map(&:to_s) : [],
|
|
43
|
+
unfed: unfed(aggregate, declared, after),
|
|
44
|
+
samples: samples_for(declared, before, after)
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def samples_for(declared, before, after)
|
|
49
|
+
return rekeyed_samples(before, after) if declared && !declared.rekeys.empty?
|
|
50
|
+
|
|
51
|
+
before.keys.sort.first(SAMPLE_SIZE).map { |id| { id: id, before: before[id], after: after[id] } }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# A rekey changes the id itself, so `before`'s and `after`'s
|
|
55
|
+
# keyspaces share nothing — pairing by matching id (the ordinary
|
|
56
|
+
# path above) would show every `after` as nil, telling a human
|
|
57
|
+
# nothing. Nothing in-process can compute the old→new
|
|
58
|
+
# correspondence either (the rekey's SQL is its only
|
|
59
|
+
# implementation, same as compute's own). Shown side by side
|
|
60
|
+
# instead, each labelled by its OWN id: real records going in,
|
|
61
|
+
# real records coming out — not claimed to correspond one-to-one,
|
|
62
|
+
# but enough for the human this rule's only verification depends
|
|
63
|
+
# on to actually see real shapes and values, not a wall of null.
|
|
64
|
+
def rekeyed_samples(before, after)
|
|
65
|
+
before.keys.sort.first(SAMPLE_SIZE).map { |id| { id: "#{id} (before)", before: before[id], after: nil } } +
|
|
66
|
+
after.keys.sort.first(SAMPLE_SIZE).map { |id| { id: "#{id} (after)", before: nil, after: after[id] } }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require "tempfile"
|
|
2
|
+
require_relative "../runtime/era_guard"
|
|
3
|
+
require_relative "../runtime/registry"
|
|
4
|
+
require_relative "../runtime/storage_shape"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Translation
|
|
8
|
+
# The question re-attestation must answer before anything else: did
|
|
9
|
+
# the edit change the era's SHAPE, or only its text? Cosmetic edits
|
|
10
|
+
# (comments, whitespace, behavior) re-freeze safely; a shape change
|
|
11
|
+
# would retroactively redefine what era N *meant* for data already
|
|
12
|
+
# written under it, and refuses hard — there is no --accept past
|
|
13
|
+
# this guard.
|
|
14
|
+
#
|
|
15
|
+
# This does not violate minted-once. That prohibition exists so
|
|
16
|
+
# boot-time RECOGNITION never depends on canonicalization stability;
|
|
17
|
+
# this is operator-initiated repair, where a false negative is a
|
|
18
|
+
# loud refusal, never a silent misread.
|
|
19
|
+
module Reattest
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
def shape_guard!(domain:, ordinal:, text:, stored_hash:, stored_projection: nil)
|
|
23
|
+
bluebook = shadow(text)
|
|
24
|
+
unless bluebook
|
|
25
|
+
raise Runtime::WiringError,
|
|
26
|
+
"cannot re-attest era #{ordinal} of #{domain}: the edited text does not load as a " \
|
|
27
|
+
"bluebook — a held era text is bootable source; restore a loadable text"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The stored PROJECTION is the preferred comparison: structural,
|
|
31
|
+
# version-free, and the same mechanism every boot trusts — so
|
|
32
|
+
# this guard never depends on canonicalization stability, and a
|
|
33
|
+
# future canonical-form version cannot make cosmetic edits to
|
|
34
|
+
# old-form eras false-refuse as shape changes. The hash path
|
|
35
|
+
# survives only as a fallback for stores that predate stored
|
|
36
|
+
# projections.
|
|
37
|
+
if stored_projection
|
|
38
|
+
edited = JSON.parse(JSON.generate(Runtime::StorageShape.project(bluebook)))
|
|
39
|
+
return :cosmetic if edited == stored_projection
|
|
40
|
+
|
|
41
|
+
raise Runtime::WiringError,
|
|
42
|
+
"cannot re-attest era #{ordinal} of #{domain}: the edit changed the era's SHAPE, not just " \
|
|
43
|
+
"its text — the text no longer projects to the shape frozen for era #{ordinal}. " \
|
|
44
|
+
"Attesting would retroactively redefine what era #{ordinal} meant for data already written " \
|
|
45
|
+
"under it; restore a text with the original shape"
|
|
46
|
+
end
|
|
47
|
+
return :unnamed unless stored_hash
|
|
48
|
+
|
|
49
|
+
computed = Runtime::StorageShape.mint_hash(bluebook)
|
|
50
|
+
return :cosmetic if computed == stored_hash
|
|
51
|
+
|
|
52
|
+
raise Runtime::WiringError,
|
|
53
|
+
"cannot re-attest era #{ordinal} of #{domain}: the edit changed the era's SHAPE, not just " \
|
|
54
|
+
"its text — its name #{stored_hash[0, Runtime::StorageShape::LABEL_LENGTH]} was minted from " \
|
|
55
|
+
"a different shape (the text now projects to #{computed[0, Runtime::StorageShape::LABEL_LENGTH]}). " \
|
|
56
|
+
"Attesting would retroactively redefine what era #{ordinal} meant for data already written " \
|
|
57
|
+
"under it; restore a text with the original shape"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def shadow(source)
|
|
61
|
+
file = Tempfile.new(["hecks-reattest-", ".bluebook"])
|
|
62
|
+
file.write(source)
|
|
63
|
+
file.flush
|
|
64
|
+
Runtime::EraGuard.shadow_parse(source, file.path)
|
|
65
|
+
rescue StandardError, SyntaxError
|
|
66
|
+
nil
|
|
67
|
+
ensure
|
|
68
|
+
file&.close!
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Translation
|
|
5
|
+
# The closed, PURE half of Postgres's own SQL compiler
|
|
6
|
+
# (adapters/driven/postgres_era/lineage/head_compiler.rb) — the part
|
|
7
|
+
# that turns one TranslationAggregate's declared rules into a
|
|
8
|
+
# jsonb-transforming SQL expression. No database connection, no
|
|
9
|
+
# watermark, no era chain, no catalog lookup: those stay exactly
|
|
10
|
+
# where they were, in head_compiler.rb's own per-mint assembly,
|
|
11
|
+
# which calls into this module instead of defining these methods
|
|
12
|
+
# itself.
|
|
13
|
+
#
|
|
14
|
+
# Extracted here — not left as private methods on
|
|
15
|
+
# Adapters::PostgresEra::Lineage — so a SECOND, adapter-agnostic
|
|
16
|
+
# caller (Exporter.translation_aggregate's build-time SQL export,
|
|
17
|
+
# feeding rust/host's own future boot-time mint) can call the exact
|
|
18
|
+
# SAME code Ruby's own mint path runs, not a hand-ported duplicate
|
|
19
|
+
# that could silently drift the way `Exporter.translation_hash`
|
|
20
|
+
# drifted from `hecks_eras`/`hecks_approvals`' real schema before
|
|
21
|
+
# this file existed (rekeys/backfills were missing for years).
|
|
22
|
+
module RuleCompiler
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# One edge's rules over one jsonb state, compiled as a nested
|
|
26
|
+
# expression tree — hecks_tr_* helpers composed innermost-first
|
|
27
|
+
# in the reference transform's phase order (renames, moves,
|
|
28
|
+
# converts, drops), computes last. `retype` compiles to nothing:
|
|
29
|
+
# stored state never carries a type name.
|
|
30
|
+
def compile_rules(declared)
|
|
31
|
+
expression = "state"
|
|
32
|
+
declared.renames.each do |old_name, new_name|
|
|
33
|
+
expression = "hecks_tr_rename(#{expression}, #{text_literal(old_name)}, #{text_literal(new_name)})"
|
|
34
|
+
end
|
|
35
|
+
declared.moves.each do |move|
|
|
36
|
+
expression = "hecks_tr_move(#{expression}, #{path_literal(move.from)}, #{path_literal(move.to)}, " \
|
|
37
|
+
"#{text_literal("move #{move.from} to: #{move.to}")})"
|
|
38
|
+
end
|
|
39
|
+
declared.converts.each do |convert|
|
|
40
|
+
pairs = JSON.generate(convert.values.map { |key, value| [key, value] })
|
|
41
|
+
expression = "hecks_tr_convert(#{expression}, #{path_literal(convert.from)}, #{path_literal(convert.to)}, " \
|
|
42
|
+
"#{text_literal(pairs)}::jsonb, #{text_literal(convert.from)}, " \
|
|
43
|
+
"#{text_literal("convert #{convert.from} to: #{convert.to}")})"
|
|
44
|
+
end
|
|
45
|
+
declared.drops.each do |name|
|
|
46
|
+
expression = "hecks_tr_drop(#{expression}, #{path_literal(name)})"
|
|
47
|
+
end
|
|
48
|
+
declared.computes.each do |compute|
|
|
49
|
+
expression = compile_compute(expression, compute)
|
|
50
|
+
end
|
|
51
|
+
expression
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Whether THIS edge's declared rules for this aggregate include a
|
|
55
|
+
# rekey — checked directly off the raw IR object, the same way
|
|
56
|
+
# every other rule kind is already read in `compile_rules`
|
|
57
|
+
# (`declared.computes`, `declared.moves`, ...), not through the
|
|
58
|
+
# `Ports::Persistence::Lineage` wrapper the app-level consumers
|
|
59
|
+
# (coverage_check.rb, minter.rb, layer_two.rb) go through — this
|
|
60
|
+
# module builds SQL straight off the IR either way.
|
|
61
|
+
def rekeyed?(declared) = declared && !declared.rekeys.empty?
|
|
62
|
+
|
|
63
|
+
# THE ONLY TWO PLACES `aggregate_id` NEEDS TO CHANGE — guarded so
|
|
64
|
+
# the generated SQL for the overwhelming common case (no rekey
|
|
65
|
+
# declared) stays the bare `aggregate_id` passthrough it always
|
|
66
|
+
# was — this CASE only appears in an edge that actually declares
|
|
67
|
+
# one.
|
|
68
|
+
def id_case(guard, declared)
|
|
69
|
+
"CASE WHEN #{guard} THEN #{compile_id_expression(declared)} ELSE aggregate_id END AS aggregate_id"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# THE REKEY'S OWN SQL — reading `state` directly, not the
|
|
73
|
+
# progressively-built `expression` chain `compile_compute` reads
|
|
74
|
+
# from. A rekey doesn't consume or move any field the way a move
|
|
75
|
+
# or compute does, so there is no same-edge rename/move ordering
|
|
76
|
+
# it needs to see first — it reads the record's stored fields
|
|
77
|
+
# exactly as they already are, the same `__s` convention
|
|
78
|
+
# `compile_compute` exposes.
|
|
79
|
+
def compile_id_expression(declared)
|
|
80
|
+
rekey = declared.rekeys.first
|
|
81
|
+
"(SELECT (#{rekey.sql}) FROM (SELECT (state) AS __s) __outer)"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# A compute is the one rule whose SQL is its only implementation
|
|
85
|
+
# — evaluated exclusively inside the compiled head, never
|
|
86
|
+
# in-process. The old field is exposed under its own name (as
|
|
87
|
+
# text, exactly as the author's expression expects to cast it).
|
|
88
|
+
def compile_compute(expression, compute)
|
|
89
|
+
from = compute.from.to_s
|
|
90
|
+
to = compute.to.to_s
|
|
91
|
+
"(SELECT CASE WHEN __s ? #{text_literal(from)} THEN " \
|
|
92
|
+
"hecks_tr_insert(__s - #{text_literal(from)}, #{path_literal(to)}, to_jsonb((#{compute.sql})), " \
|
|
93
|
+
"#{text_literal("compute #{from} to: #{to}")}) " \
|
|
94
|
+
"ELSE __s END " \
|
|
95
|
+
"FROM (SELECT (#{expression}) AS __s) __outer, " \
|
|
96
|
+
"LATERAL (SELECT (__s ->> #{text_literal(from)}) AS #{quote(from)}) __fields)"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# `PG::Connection.quote_ident` needs the `pg` gem LOADED, not
|
|
100
|
+
# connected — required here, lazily, the same "a domain that
|
|
101
|
+
# never wires PostgresEra should never need the gem" reasoning
|
|
102
|
+
# `PostgresEra.connect_for`'s own `require "pg"` already holds
|
|
103
|
+
# itself to, so a build tool that exports translations for a
|
|
104
|
+
# non-Postgres-bound domain (there are none today, but nothing
|
|
105
|
+
# here should assume there never will be) doesn't gain a hard
|
|
106
|
+
# dependency on `pg` just by loading this file.
|
|
107
|
+
def quote(name)
|
|
108
|
+
require "pg"
|
|
109
|
+
PG::Connection.quote_ident(name.to_s)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def text_literal(text) = "'#{text.to_s.gsub("'", "''")}'"
|
|
113
|
+
|
|
114
|
+
def path_literal(path)
|
|
115
|
+
segments = path.to_s.split(".").map { |segment| text_literal(segment) }
|
|
116
|
+
"ARRAY[#{segments.join(', ')}]::text[]"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|