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,120 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "digest"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Runtime
|
|
6
|
+
# The storage-shape projection of a bluebook: exactly the parts of
|
|
7
|
+
# the IR that decide what persisted data looks like — aggregates,
|
|
8
|
+
# their attributes (with full value-object/entity structure and
|
|
9
|
+
# cardinality), references, the lifecycle field, and the identity
|
|
10
|
+
# paths. Everything behavioral — commands, invariants, queries,
|
|
11
|
+
# policies, lifecycle transitions, defaults, descriptions, comments,
|
|
12
|
+
# the declared routing `version:` — is excluded, so editing behavior
|
|
13
|
+
# never bumps an era and editing shape always does.
|
|
14
|
+
#
|
|
15
|
+
# Three attribute facts on the wire are constraints on PERSISTED
|
|
16
|
+
# VALUES, not behavior, and are still excluded — each decided, not
|
|
17
|
+
# overlooked:
|
|
18
|
+
#
|
|
19
|
+
# `optional` — required-ness is enforced at dispatch; stored rows
|
|
20
|
+
# are never re-validated on read, so flipping it
|
|
21
|
+
# strands nothing already written. Excluded.
|
|
22
|
+
# `pattern` — same argument: a fact about what may be WRITTEN
|
|
23
|
+
# next, not about what was stored. Excluded.
|
|
24
|
+
# `admits` — the sharpest of the three: narrowing a closed set
|
|
25
|
+
# CAN strand stored rows outside it, and the wire
|
|
26
|
+
# carries only the set's NAME, so a set whose members
|
|
27
|
+
# changed under a stable name is invisible even to a
|
|
28
|
+
# projection that included the fact (the same lesson
|
|
29
|
+
# recursive value-object drift taught). Excluded, and
|
|
30
|
+
# NAMED as a gap: constraint tightening has no
|
|
31
|
+
# translation-rule vocabulary to acknowledge it yet,
|
|
32
|
+
# so including it would mint era bumps nothing can
|
|
33
|
+
# explain. When the translation language grows a
|
|
34
|
+
# constraint-acknowledgment rule, `admits` (by member
|
|
35
|
+
# list, not by name) is first in line, and that
|
|
36
|
+
# change bumps FORM_VERSION.
|
|
37
|
+
#
|
|
38
|
+
# Projection runs over the canonical dump form (`to_h`, JSON
|
|
39
|
+
# round-tripped), so a verdict depends only on the IR — never on live
|
|
40
|
+
# object graphs. Structural comparison only, never a hash comparison.
|
|
41
|
+
module StorageShape
|
|
42
|
+
module_function
|
|
43
|
+
|
|
44
|
+
def project(bluebook)
|
|
45
|
+
domain = JSON.parse(JSON.generate(bluebook.to_h))
|
|
46
|
+
{
|
|
47
|
+
"name" => bluebook.name,
|
|
48
|
+
"aggregates" => (domain["aggregates"] || [])
|
|
49
|
+
.map { |aggregate| project_aggregate(aggregate) }
|
|
50
|
+
.sort_by { |aggregate| aggregate["name"] }
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def same?(held, current) = project(held) == project(current)
|
|
55
|
+
|
|
56
|
+
# The canonical serialization the Ruby scaffold hashes at MINT time
|
|
57
|
+
# — the one moment identity is computed. Nothing ever recomputes a
|
|
58
|
+
# stored era name to verify it, so this form can evolve freely.
|
|
59
|
+
def canonical(bluebook) = JSON.generate(project(bluebook))
|
|
60
|
+
|
|
61
|
+
def mint_hash(bluebook) = Digest::SHA256.hexdigest(canonical(bluebook))
|
|
62
|
+
|
|
63
|
+
LABEL_LENGTH = 6
|
|
64
|
+
def mint_label(bluebook) = mint_hash(bluebook)[0, LABEL_LENGTH]
|
|
65
|
+
|
|
66
|
+
# The version of the canonical serialization above. Minted-once
|
|
67
|
+
# means a stored name stays valid across form changes — but only
|
|
68
|
+
# if each name records WHICH form minted it, so v1-named and
|
|
69
|
+
# v2-named eras coexist legibly. Stored beside every minted hash
|
|
70
|
+
# (names.tsv fourth field / hecks_eras.canon_form); bump this in
|
|
71
|
+
# the same change that alters project/canonical output.
|
|
72
|
+
FORM_VERSION = 1
|
|
73
|
+
|
|
74
|
+
def project_aggregate(aggregate)
|
|
75
|
+
{
|
|
76
|
+
"name" => aggregate["name"],
|
|
77
|
+
# The declared identity paths, AS A LIST, in declaration order —
|
|
78
|
+
# order is semantic (the paths join in order to form the id).
|
|
79
|
+
# No "id" fallback: an aggregate that declares nothing has [],
|
|
80
|
+
# and that is a real declared state, distinct from an aggregate
|
|
81
|
+
# identified by a field named "id".
|
|
82
|
+
"identity" => Array(aggregate["identified_by"]).map(&:to_s),
|
|
83
|
+
"lifecycle_field" => aggregate.dig("lifecycle", "field"),
|
|
84
|
+
"attributes" => (aggregate["attributes"] || [])
|
|
85
|
+
.map { |attribute| project_attribute(aggregate, attribute, []) }
|
|
86
|
+
.sort_by { |attribute| attribute["name"] }
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def project_attribute(aggregate, attribute, seen)
|
|
91
|
+
{
|
|
92
|
+
"name" => attribute["name"].to_s,
|
|
93
|
+
"list" => attribute["list"] ? true : false,
|
|
94
|
+
"type" => type_signature(aggregate, attribute["type"].to_s, seen)
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# A plain type name for a primitive; the type name plus its
|
|
99
|
+
# members' full signatures for a value object or entity — so two
|
|
100
|
+
# attributes with the same declared type name but different
|
|
101
|
+
# internals are never mistaken for unchanged.
|
|
102
|
+
def type_signature(aggregate, type_name, seen)
|
|
103
|
+
container = nested_type(aggregate, type_name)
|
|
104
|
+
return type_name if container.nil? || seen.include?(type_name)
|
|
105
|
+
|
|
106
|
+
{
|
|
107
|
+
"type" => type_name,
|
|
108
|
+
"members" => (container["attributes"] || [])
|
|
109
|
+
.map { |member| project_attribute(aggregate, member, seen + [type_name]) }
|
|
110
|
+
.sort_by { |member| member["name"] }
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def nested_type(aggregate, type_name)
|
|
115
|
+
(aggregate["value_objects"] || []).find { |vo| vo["name"] == type_name } ||
|
|
116
|
+
(aggregate["entities"] || []).find { |entity| entity["name"] == type_name }
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
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,46 @@
|
|
|
1
|
+
require_relative "../../../../../../runtime/errors"
|
|
2
|
+
require_relative "../../../../../../runtime/instance"
|
|
3
|
+
require_relative "../../../../../../runtime/value/invariant_violation"
|
|
4
|
+
require_relative "../../../../../../bluebook/model_check"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Translation
|
|
8
|
+
module Audit
|
|
9
|
+
# Layer 1 — from the bluebook alone: every translated state must
|
|
10
|
+
# pass the new era's types, value-object invariants, and lifecycle.
|
|
11
|
+
# This is also where a NEW, stricter invariant that old records
|
|
12
|
+
# violate surfaces — there is no "grandfather old records"
|
|
13
|
+
# construct, and the remedy is relaxing the invariant or explicit
|
|
14
|
+
# remediation, never a translation rule.
|
|
15
|
+
module LayerOne
|
|
16
|
+
def layer_one!(violations, aggregate, after)
|
|
17
|
+
after.each do |id, state|
|
|
18
|
+
begin
|
|
19
|
+
symbolized = JSON.parse(JSON.generate(state), symbolize_names: true)
|
|
20
|
+
instance = Runtime::Instance.new(aggregate: aggregate, id: id, state: symbolized)
|
|
21
|
+
lifecycle = aggregate.lifecycle
|
|
22
|
+
next unless lifecycle
|
|
23
|
+
|
|
24
|
+
held = instance[lifecycle.field]
|
|
25
|
+
# `Lifecycle#states` answers default+targets only — a state
|
|
26
|
+
# legitimately declared just as a `from:` (a terminal
|
|
27
|
+
# transition's source, never anyone's target) is real and
|
|
28
|
+
# reachable but invisible to it. `ModelCheck.full_states`
|
|
29
|
+
# is the full declared set (default, every target, AND
|
|
30
|
+
# every from) that `fuzzing/properties.rb`'s own replay
|
|
31
|
+
# check already uses for this identical question — see its
|
|
32
|
+
# comment on this same hole.
|
|
33
|
+
allowed = Bluebook::ModelCheck.full_states(lifecycle)
|
|
34
|
+
unless held.nil? || allowed.include?(held.to_s)
|
|
35
|
+
violations << "#{aggregate.name}##{id}: #{lifecycle.field} is #{held.inspect}, " \
|
|
36
|
+
"a state this era's lifecycle never reaches"
|
|
37
|
+
end
|
|
38
|
+
rescue Runtime::InvariantViolation, Runtime::TypeMismatch => error
|
|
39
|
+
violations << "#{aggregate.name}##{id}: #{error.message}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require_relative "../../../../../../ports/persistence/append_only"
|
|
2
|
+
require_relative "../../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_paths = declared.computes.flat_map { |compute| [compute.from, compute.to] }.map(&:to_s)
|
|
57
|
+
|
|
58
|
+
before.each do |id, state|
|
|
59
|
+
next unless after.key?(id)
|
|
60
|
+
|
|
61
|
+
entry = Ports::Persistence::Entry.new(operation: "save", id: id, state: state.transform_keys(&:to_sym))
|
|
62
|
+
expected = strip_compute_paths(normalize(rules.translate(entry).state), compute_paths)
|
|
63
|
+
actual = strip_compute_paths(normalize(after[id]), compute_paths)
|
|
64
|
+
next if expected == actual
|
|
65
|
+
|
|
66
|
+
diverged = (expected.keys | actual.keys).select { |key| expected[key] != actual[key] }
|
|
67
|
+
violations << "#{aggregate.name}##{id}: the translated state diverges from the reference " \
|
|
68
|
+
"transform at #{diverged.sort.join(', ')}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def normalize(state) = JSON.parse(JSON.generate(state))
|
|
73
|
+
|
|
74
|
+
# Exempts exactly the paths a compute owns, not the whole
|
|
75
|
+
# top-level attribute it happens to live under. A bare path
|
|
76
|
+
# ("price_cents") is itself the compute's entire value — dropping
|
|
77
|
+
# the whole top-level key is correct, there's nothing else there
|
|
78
|
+
# to check. A DOTTED path ("price.cents") only owns that one
|
|
79
|
+
# member of the value object it reaches into; every sibling
|
|
80
|
+
# member (e.g. "price.currency") is untouched by the compute and
|
|
81
|
+
# must stay subject to the equivalence check below. Blanket-
|
|
82
|
+
# dropping the whole top-level key for a dotted compute used to
|
|
83
|
+
# exempt the entire attribute — silent data loss elsewhere in the
|
|
84
|
+
# same value object (a migration that nulls or drops a sibling
|
|
85
|
+
# field) produced zero violations, defeating the one gate whose
|
|
86
|
+
# entire purpose is to catch exactly that.
|
|
87
|
+
def strip_compute_paths(state, paths)
|
|
88
|
+
paths.each do |path|
|
|
89
|
+
segments = path.split(".")
|
|
90
|
+
if segments.length == 1
|
|
91
|
+
state.delete(segments.first)
|
|
92
|
+
else
|
|
93
|
+
parent = segments[0..-2].reduce(state) { |node, segment| node.is_a?(Hash) ? node[segment] : nil }
|
|
94
|
+
parent.delete(segments.last) if parent.is_a?(Hash)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
state
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
# `key?` decides which spelling answers, never `||` — a
|
|
37
|
+
# `false` genuinely held at this path must not fall through
|
|
38
|
+
# to the other spelling (usually absent) and read as `nil`,
|
|
39
|
+
# which would wrongly mark a `false`-valued attribute "unfed".
|
|
40
|
+
node.key?(segment) ? node[segment] : node[segment.to_sym]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
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 "../era_guard"
|
|
3
|
+
require_relative "../../../../../runtime/registry"
|
|
4
|
+
require_relative "../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
|