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,169 @@
|
|
|
1
|
+
require_relative "../../../../ports/persistence"
|
|
2
|
+
require_relative "../../../../ports/persistence/binding_policy"
|
|
3
|
+
require_relative "lineage"
|
|
4
|
+
require_relative "../../../../naming"
|
|
5
|
+
require_relative "../../../../framework"
|
|
6
|
+
require_relative "../../../../runtime/registry"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module Runtime
|
|
10
|
+
# The boot-time era gate, run for the adapters that HAVE eras — the
|
|
11
|
+
# lineage-capable ones. An era is a fact about stored data that some
|
|
12
|
+
# adapter can carry across a shape change; an adapter that cannot
|
|
13
|
+
# translate has no era to hold, and holding one for it would record
|
|
14
|
+
# history about data nothing can act on.
|
|
15
|
+
#
|
|
16
|
+
# The whole domain is handed to the capable adapter's own era check
|
|
17
|
+
# (Postgres: hold, recognize, mint, or refuse toward the scaffold),
|
|
18
|
+
# which keeps its era facts as rows BESIDE its data. That
|
|
19
|
+
# co-location is load-bearing — a watermark only means something
|
|
20
|
+
# against the journal it was cut from, and an approval recorded
|
|
21
|
+
# anywhere but the reviewed database binds to nothing.
|
|
22
|
+
#
|
|
23
|
+
# The capability is asked of the adapter, never of its name: a
|
|
24
|
+
# second adapter that grows an era story declares lineage_capable?
|
|
25
|
+
# and era_check! and needs no change here.
|
|
26
|
+
#
|
|
27
|
+
# Detection and identity are separate jobs: this check never hashes
|
|
28
|
+
# anything — era names come from the store, already minted (by the
|
|
29
|
+
# scaffold) or absent. Refusal wordings are contract, pinned by the
|
|
30
|
+
# corpus.
|
|
31
|
+
module EraCheck
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
# EACH BLUEBOOK'S OWN SOURCE, not one file read once and reused
|
|
35
|
+
# for every bluebook in the registry — true as long as a domain
|
|
36
|
+
# directory only ever held exactly one, and silently wrong the
|
|
37
|
+
# moment `uses_framework` made a second, differently-sourced
|
|
38
|
+
# bluebook (Governance, Identity — `lib/hecks/framework/bluebook/`,
|
|
39
|
+
# not the domain's own directory) share a boot with the first.
|
|
40
|
+
# Caught the hard way: three domains booted together, one real
|
|
41
|
+
# source text (the domain's own), and every OTHER domain's era-1
|
|
42
|
+
# held THAT text under its own name — a shadow-parse of it later
|
|
43
|
+
# reconstructs a completely different shape, and every boot after
|
|
44
|
+
# the first refuses toward a scaffold that was never the real
|
|
45
|
+
# drift.
|
|
46
|
+
def check!(registry, directory)
|
|
47
|
+
check_compute_rules_for_registry!(registry)
|
|
48
|
+
check_lineage!(registry, directory)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# The domain-agnostic half, split out for ADR 0031's boot-gate
|
|
52
|
+
# registry: a compute rule requires Postgres whatever adapter is
|
|
53
|
+
# actually bound, so this must run for EVERY registry, the same way
|
|
54
|
+
# `registry.verify!` does — it is not conditional on any adapter
|
|
55
|
+
# being lineage-capable, and must never be skipped by
|
|
56
|
+
# `check_lineage!`'s own capability gate below.
|
|
57
|
+
def check_compute_rules_for_registry!(registry)
|
|
58
|
+
registry.bluebooks.each_value { |bluebook| check_compute_rules!(registry, bluebook) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The capability-gated half — ADR 0031's registered `:era_check`
|
|
62
|
+
# gate. Registration is conditional on `lineage_capable_registry?`;
|
|
63
|
+
# `check_bluebook!` below still carries its own per-bluebook
|
|
64
|
+
# `lineage_capable?` return-early, unchanged, for a registry with a
|
|
65
|
+
# mix of lineage-capable and plain-adapter bluebooks.
|
|
66
|
+
def check_lineage!(registry, directory)
|
|
67
|
+
registry.bluebooks.each_value do |bluebook|
|
|
68
|
+
check_bluebook!(registry, bluebook, source_text_for(bluebook, directory), directory: directory)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The `:era_check` gate's own registration predicate: true iff at
|
|
73
|
+
# least one bluebook's own anchor (first) aggregate resolves to a
|
|
74
|
+
# lineage-capable adapter — mirrors `check_bluebook!`'s existing
|
|
75
|
+
# per-bluebook anchor check, just asked once, up front, of the
|
|
76
|
+
# whole registry, so a registry with nothing lineage-capable bound
|
|
77
|
+
# anywhere never registers the gate at all.
|
|
78
|
+
def lineage_capable_registry?(registry)
|
|
79
|
+
registry.bluebooks.each_value.any? do |bluebook|
|
|
80
|
+
first = bluebook.aggregates.first
|
|
81
|
+
next false unless first
|
|
82
|
+
|
|
83
|
+
lineage_capable?(registry, adapter_for(registry, bluebook.name, first))
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The domain's own directory first, matched by name — a real app's
|
|
88
|
+
# directory may hold more than one file once `uses_framework`
|
|
89
|
+
# exists, so ".first" alone can no longer be trusted, the exact
|
|
90
|
+
# way it silently wasn't the day this was found: three domains
|
|
91
|
+
# booted together, one real source text read once (the domain's
|
|
92
|
+
# own, ".first"'d), and every OTHER domain's era-1 held THAT text
|
|
93
|
+
# under its own name — a later shadow-parse of it reconstructs a
|
|
94
|
+
# completely different shape, and every boot after the first
|
|
95
|
+
# refuses toward a scaffold that was never the real drift.
|
|
96
|
+
#
|
|
97
|
+
# A single-file directory whose one file names something ELSE
|
|
98
|
+
# falls back to it anyway (a fixture may legitimately name its
|
|
99
|
+
# file differently from the `Hecks.bluebook` it declares) — UNLESS
|
|
100
|
+
# this bluebook is a known framework member, in which case that
|
|
101
|
+
# one file is certainly some OTHER domain's, not this one's, and
|
|
102
|
+
# the framework registry is asked instead — the only other place a
|
|
103
|
+
# bluebook in this registry could have come from, per
|
|
104
|
+
# `uses_framework`.
|
|
105
|
+
def source_text_for(bluebook, directory)
|
|
106
|
+
domain_files = Dir[File.join(directory, "*.bluebook")]
|
|
107
|
+
own = domain_files.select do |path|
|
|
108
|
+
File.foreach(path, encoding: "UTF-8").any? { |line| line.match?(/\A\s*Hecks\.bluebook\s+#{Regexp.escape(bluebook.name.inspect)}/) }
|
|
109
|
+
end
|
|
110
|
+
own = domain_files if own.empty? && domain_files.size == 1 && !Framework.members.key?(bluebook.name)
|
|
111
|
+
own = [Framework.members[bluebook.name]].compact if own.empty?
|
|
112
|
+
return if own.empty?
|
|
113
|
+
|
|
114
|
+
own.map { |path| File.read(path, encoding: "UTF-8") }.join("\n")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def check_bluebook!(registry, bluebook, current_text, directory: nil)
|
|
118
|
+
first = bluebook.aggregates.first
|
|
119
|
+
return unless first
|
|
120
|
+
|
|
121
|
+
adapter_name = adapter_for(registry, bluebook.name, first)
|
|
122
|
+
return unless lineage_capable?(registry, adapter_name)
|
|
123
|
+
|
|
124
|
+
unless current_text
|
|
125
|
+
raise WiringError,
|
|
126
|
+
"cannot boot #{bluebook.name}: bound to a lineage-capable adapter, but no source file for " \
|
|
127
|
+
"it could be found (checked #{directory.inspect} and the framework registry)"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
settings = registry.world(bluebook.name)&.for_binding(Ports::Persistence::VERB, adapter_name) || {}
|
|
131
|
+
registry.adapter_class(adapter_name).era_check!(
|
|
132
|
+
registry: registry, bluebook: bluebook, current_text: current_text, settings: settings,
|
|
133
|
+
directory: directory
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# The per-rule capability gate — NOT an era fact, and so it
|
|
138
|
+
# survives on every adapter: a compute rule's SQL is its only
|
|
139
|
+
# implementation, so an aggregate carrying one cannot boot
|
|
140
|
+
# anywhere but Postgres, whatever any shape comparison would say.
|
|
141
|
+
def check_compute_rules!(registry, bluebook)
|
|
142
|
+
bluebook.aggregates.each do |aggregate|
|
|
143
|
+
lineage = Ports::Persistence::Lineage.for(registry, bluebook.name, aggregate)
|
|
144
|
+
next unless lineage&.computes?
|
|
145
|
+
|
|
146
|
+
adapter = adapter_for(registry, bluebook.name, aggregate)
|
|
147
|
+
next if lineage_capable?(registry, adapter)
|
|
148
|
+
|
|
149
|
+
raise WiringError, "compute rules require the Postgres adapter; #{aggregate.name} is bound to #{adapter}"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def adapter_for(registry, domain, aggregate)
|
|
154
|
+
Ports::Persistence::BindingPolicy.resolve(registry, domain, aggregate).adapter
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# The capability idiom: an adapter CLASS that answers
|
|
158
|
+
# lineage_capable? with true carries eras and may act on drift
|
|
159
|
+
# (translate, fork, merge). Postgres alone does today; the seam is
|
|
160
|
+
# what lets a second one arrive without touching this file.
|
|
161
|
+
def lineage_capable?(registry, adapter_name)
|
|
162
|
+
adapter_class = registry.adapters[adapter_name] && registry.adapter_class(adapter_name)
|
|
163
|
+
adapter_class.respond_to?(:lineage_capable?) && adapter_class.lineage_capable?
|
|
164
|
+
rescue StandardError
|
|
165
|
+
false
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Runtime
|
|
3
|
+
module EraGuard
|
|
4
|
+
# The pure half: computing an aggregate's structural signature and
|
|
5
|
+
# diffing two eras' shapes. No file I/O — everything here answers
|
|
6
|
+
# from the IR it is handed, which is why the Postgres mint path can
|
|
7
|
+
# call it too.
|
|
8
|
+
module ShapeDiff
|
|
9
|
+
def shape(aggregate)
|
|
10
|
+
aggregate.attributes.map { |attribute|
|
|
11
|
+
[attribute.name, attribute_signature(aggregate, attribute.type)]
|
|
12
|
+
}.sort_by(&:first)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# A plain type name for a primitive; `[type_name, member_signatures]`
|
|
16
|
+
# for a value object or entity, walked recursively — so two
|
|
17
|
+
# attributes with the same declared type name but a different
|
|
18
|
+
# internal shape are never mistaken for unchanged.
|
|
19
|
+
def attribute_signature(aggregate, type_name, seen = [])
|
|
20
|
+
container = nested_type(aggregate, type_name)
|
|
21
|
+
return type_name if container.nil? || seen.include?(type_name)
|
|
22
|
+
|
|
23
|
+
members = container.attributes.map { |member|
|
|
24
|
+
[member.name, attribute_signature(aggregate, member.type, seen + [type_name])]
|
|
25
|
+
}.sort_by(&:first)
|
|
26
|
+
[type_name, members]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def nested_type(aggregate, type_name)
|
|
30
|
+
aggregate.value_object(type_name) || aggregate.entities.find { |entity| entity.name == type_name }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Attributes present now that the held shape never had — the
|
|
34
|
+
# addition side of drift, which `uncovered_attributes` above never
|
|
35
|
+
# looks at, since it only walks the HELD shape (vanish/retype).
|
|
36
|
+
# Most additions are free (ADR 0025, "Added attributes and
|
|
37
|
+
# absence"): a default:, a list_of (frozen []), or a value object
|
|
38
|
+
# whose fields all default fill an existing record automatically
|
|
39
|
+
# via `Instance.hydrate_with_defaults`. Only the fourth case — a
|
|
40
|
+
# non-optional attribute with no way to fill itself — can leave an
|
|
41
|
+
# existing record with the field genuinely absent, and that is
|
|
42
|
+
# what this reports: unfilled by a declared translation's own
|
|
43
|
+
# `backfill`, OR by a move/convert that lands an OLD field inside
|
|
44
|
+
# this brand-new attribute (`Lineage#fills?` — the destination-
|
|
45
|
+
# side question, never `explains?`'s source-side one, since a
|
|
46
|
+
# rename from Crate to Bin can introduce a top-level attribute
|
|
47
|
+
# name that never existed to have "vanished").
|
|
48
|
+
def unsafe_additions(aggregate, held_aggregate, lineage)
|
|
49
|
+
held_names = held_aggregate.attributes.map(&:name)
|
|
50
|
+
|
|
51
|
+
aggregate.attributes
|
|
52
|
+
.reject { |attribute| held_names.include?(attribute.name) }
|
|
53
|
+
.select { |attribute| possibly_absent?(aggregate, attribute) }
|
|
54
|
+
.reject { |attribute| lineage&.fills?(attribute.name.to_s) }
|
|
55
|
+
.map(&:name)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# THE FOUR-ROW TABLE, as a predicate over the fourth row only — the
|
|
59
|
+
# other three (default:, list_of, a fully-defaulted value object)
|
|
60
|
+
# all fill an existing record for free and never reach here.
|
|
61
|
+
def possibly_absent?(aggregate, attribute)
|
|
62
|
+
return false if attribute.optional?
|
|
63
|
+
return false unless attribute.default.nil?
|
|
64
|
+
return false if attribute.list?
|
|
65
|
+
|
|
66
|
+
value_object = aggregate.value_object(attribute.type)
|
|
67
|
+
return false if value_object && value_object.attributes.all? { |field| !field.default.nil? }
|
|
68
|
+
|
|
69
|
+
true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Paths the translation needs to explain: attributes that vanished
|
|
73
|
+
# by name, attributes that kept their name but changed type (a
|
|
74
|
+
# `convert` is what lets that be declared at all), and — recursing
|
|
75
|
+
# into a same-named, same-typed value object — its OWN members
|
|
76
|
+
# vanishing or changing type one level down, reported as a dotted
|
|
77
|
+
# path ("price.currency"). A pure addition, at any depth, never
|
|
78
|
+
# needs covering; only vanish-or-retype does, matching the
|
|
79
|
+
# top-level rule at every depth.
|
|
80
|
+
def uncovered_attributes(aggregate, held_aggregate, lineage)
|
|
81
|
+
paths = held_aggregate.attributes.flat_map do |held_attribute|
|
|
82
|
+
current_attribute = aggregate.attribute(held_attribute.name)
|
|
83
|
+
next [held_attribute.name.to_s] unless current_attribute
|
|
84
|
+
|
|
85
|
+
diff_type(held_attribute.name.to_s, held_attribute.type, current_attribute.type, held_aggregate, aggregate, lineage)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
return paths unless lineage
|
|
89
|
+
|
|
90
|
+
paths.reject { |path| lineage.explains?(path) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# `held_type`/`current_type` are type NAMES, resolved against each
|
|
94
|
+
# side's OWN value_object AND entity declarations — neither is ever
|
|
95
|
+
# nested in the DSL, only in the type graph, so both are always
|
|
96
|
+
# looked up flat off their respective aggregate. A `list_of` entity
|
|
97
|
+
# is only reached here to DETECT a member vanish-or-retype; there is
|
|
98
|
+
# no per-element translation machinery yet (`move`/`convert`/`drop`
|
|
99
|
+
# only reach into a single nested hash, not each element of an
|
|
100
|
+
# array) — the only way to satisfy a refusal on an entity path
|
|
101
|
+
# today is a top-level `drop` of the whole list attribute, which
|
|
102
|
+
# `explains?` already recognizes as covering everything nested
|
|
103
|
+
# under it. Blunt, but loud beats silent.
|
|
104
|
+
def diff_type(path, held_type, current_type, held_aggregate, aggregate, lineage, seen = [])
|
|
105
|
+
if held_type != current_type
|
|
106
|
+
# A declared retype says the two TYPE names mean the same shape
|
|
107
|
+
# — accept the pair, but still recurse into the members so a
|
|
108
|
+
# member drift hiding beneath the rename is caught by name.
|
|
109
|
+
return [path] unless lineage&.retype?(held_type, current_type)
|
|
110
|
+
end
|
|
111
|
+
return [] if seen.include?(current_type)
|
|
112
|
+
|
|
113
|
+
held_container = nested_type(held_aggregate, held_type)
|
|
114
|
+
current_container = nested_type(aggregate, current_type)
|
|
115
|
+
return [] unless held_container && current_container
|
|
116
|
+
|
|
117
|
+
held_container.attributes.flat_map do |held_member|
|
|
118
|
+
current_member = current_container.attribute(held_member.name)
|
|
119
|
+
next ["#{path}.#{held_member.name}"] unless current_member
|
|
120
|
+
|
|
121
|
+
diff_type("#{path}.#{held_member.name}", held_member.type, current_member.type, held_aggregate, aggregate, lineage,
|
|
122
|
+
seen + [current_type])
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
require_relative "era_guard/shape_diff"
|
|
2
|
+
require_relative "../../../../bluebook/dsl/malformed"
|
|
3
|
+
require_relative "../../../../bluebook/meta_validator"
|
|
4
|
+
require_relative "../../../../ports/loading"
|
|
5
|
+
require_relative "../../../../runtime/registry"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module Runtime
|
|
9
|
+
# The shape-drift coverage primitives — ADR 0032. Not a driver: nothing
|
|
10
|
+
# here walks a registry or reads/writes a held snapshot on its own.
|
|
11
|
+
# `PostgresEra::LineageManager::CoverageCheck` calls `uncovered_
|
|
12
|
+
# attributes`/`unsafe_additions`/`refuse_uncovered!`/`refuse_unsafe_
|
|
13
|
+
# addition!`/`check_vanished_aggregates!` directly, per translation
|
|
14
|
+
# edge, over its own DB-held shapes; `Translation::Reattest` and
|
|
15
|
+
# `PostgresEra::LineageManager` call `shadow_parse` directly, to read
|
|
16
|
+
# historical bluebook text under old grammar defaults. Both are real,
|
|
17
|
+
# independent, currently-shipped consumers.
|
|
18
|
+
#
|
|
19
|
+
# A prior version of this module ALSO drove its own top-level check —
|
|
20
|
+
# `check!`/`check_bluebook!`, walking a registry and reading/writing a
|
|
21
|
+
# held snapshot under `data/eras/*.bluebook` — duplicating, on its own,
|
|
22
|
+
# the same per-aggregate walk `CoverageCheck` already performs against
|
|
23
|
+
# `PostgresEra`'s own DB-held shapes. Nothing in production ever called
|
|
24
|
+
# it (only a direct unit spec did); deleted rather than kept unwired,
|
|
25
|
+
# per ADR 0032. Wanted again, it's rebuilt informed by `CoverageCheck`'s
|
|
26
|
+
# real orchestration, not resurrected from here.
|
|
27
|
+
module EraGuard
|
|
28
|
+
extend ShapeDiff
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
# An aggregate that existed in the held text and answers to no
|
|
33
|
+
# current name — renamed silently, with nothing declaring `was:` to
|
|
34
|
+
# explain where its data went — is exactly the disease this guards
|
|
35
|
+
# against, and a plain per-aggregate diff would never see it: the
|
|
36
|
+
# current aggregate simply has no held counterpart to compare to.
|
|
37
|
+
def check_vanished_aggregates!(registry, bluebook, held_bluebook)
|
|
38
|
+
held_bluebook.aggregates.each do |held_aggregate|
|
|
39
|
+
claimed = bluebook.aggregates.any? do |aggregate|
|
|
40
|
+
aggregate.name == held_aggregate.name ||
|
|
41
|
+
registry.translations.any? do |translation|
|
|
42
|
+
translation.domain == bluebook.name &&
|
|
43
|
+
translation.for_aggregate(aggregate.name)&.was == held_aggregate.name
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
claimed ||= registry.translations.any? do |translation|
|
|
47
|
+
translation.domain == bluebook.name && translation.retired.include?(held_aggregate.name)
|
|
48
|
+
end
|
|
49
|
+
next if claimed
|
|
50
|
+
|
|
51
|
+
raise WiringError,
|
|
52
|
+
"cannot boot #{bluebook.name}: #{held_aggregate.name} existed and now doesn't, " \
|
|
53
|
+
"and nothing declares was: #{held_aggregate.name.inspect} to explain where its data went."
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The Layer-1 coverage refusal — one wording, shared with whoever
|
|
58
|
+
# calls it (today, `PostgresEra::LineageManager::CoverageCheck`'s
|
|
59
|
+
# own mint-time coverage check).
|
|
60
|
+
def refuse_uncovered!(bluebook, aggregate, uncovered)
|
|
61
|
+
raise WiringError,
|
|
62
|
+
"cannot boot #{bluebook.name}::#{aggregate.name}: its shape changed and " \
|
|
63
|
+
"#{uncovered.map { |path| render_path(path) }.join(', ')} #{uncovered.size == 1 ? 'is' : 'are'} not " \
|
|
64
|
+
"explained by any rename, move, convert, retype, or drop. Update bluebook/translations/*.bluebook, e.g. " \
|
|
65
|
+
"#{suggestion(uncovered.first)}."
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The addition-side sibling of refuse_uncovered! above — same
|
|
69
|
+
# wording shape, different cause: nothing vanished or changed type,
|
|
70
|
+
# something new arrived that an existing record has no way to hold.
|
|
71
|
+
def refuse_unsafe_addition!(bluebook, aggregate, unsafe)
|
|
72
|
+
raise WiringError,
|
|
73
|
+
"cannot boot #{bluebook.name}::#{aggregate.name}: #{unsafe.map { |name| ":#{name}" }.join(', ')} " \
|
|
74
|
+
"#{unsafe.size == 1 ? 'is new and required' : 'are new and required'}, with no default: to fill " \
|
|
75
|
+
"an existing record and no translation explaining what one should read there. Give it a " \
|
|
76
|
+
"default:, make it optional: true or list_of, or declare bluebook/translations/*.bluebook, e.g. " \
|
|
77
|
+
"`backfill :#{unsafe.first}, default: ...`."
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def render_path(path) = path.include?(".") ? path.inspect : ":#{path}"
|
|
81
|
+
|
|
82
|
+
def suggestion(path)
|
|
83
|
+
if path.include?(".")
|
|
84
|
+
"`move #{path.inspect}, to: #{path.inspect}` or `drop #{path.inspect}`"
|
|
85
|
+
else
|
|
86
|
+
"`rename :#{path}, to: :new_name` or `drop :#{path}`"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Parses held source into its own IR, in a scratch registry so a past
|
|
91
|
+
# era's text never touches the one actually booting.
|
|
92
|
+
#
|
|
93
|
+
# NORMAL PARSE FIRST, shadow only as a FALLBACK — not shadow-parsing
|
|
94
|
+
# unconditionally, which is what this used to do. A handful of DSL
|
|
95
|
+
# defaults fork on `MetaValidator.shadow_parsing?` for a reason
|
|
96
|
+
# that has NOTHING to do with syntax the live grammar can no longer
|
|
97
|
+
# read at all (`identified_by { }`, `belongs_to`, `has_one`,
|
|
98
|
+
# `has_many` — genuinely removed spellings, exactly what shadow-
|
|
99
|
+
# parsing exists to keep readable): `reference_to`'s own default
|
|
100
|
+
# mint name (`default_reference_name`, attribute_collector.rb)
|
|
101
|
+
# changed from `_id`-suffixed to bare under ADR 0025, and THAT fork
|
|
102
|
+
# applies even to text using nothing but current, live syntax.
|
|
103
|
+
# Held text minted under the CURRENT grammar — every real era in
|
|
104
|
+
# this corpus today, since nothing has ever minted a second one —
|
|
105
|
+
# parses fine normally; only the reference-naming DEFAULT differed
|
|
106
|
+
# once shadow mode engaged unconditionally, so it silently
|
|
107
|
+
# reconstructed a DIFFERENT shape (and hash) than a fresh parse of
|
|
108
|
+
# the identical text — the same text hashing two different ways
|
|
109
|
+
# depending on which code path read it, breaking `ensure_named!`'s
|
|
110
|
+
# own from/to edge lookup with a spurious "no translation edge
|
|
111
|
+
# covers it" refusal that has nothing to do with any real
|
|
112
|
+
# translation gap.
|
|
113
|
+
#
|
|
114
|
+
# A normal parse can only ever SUCCEED on text the live grammar
|
|
115
|
+
# fully understands — there is no way for it to silently produce a
|
|
116
|
+
# wrong-but-plausible answer for genuinely legacy text, since every
|
|
117
|
+
# removed spelling refuses loudly (`Malformed`) rather than
|
|
118
|
+
# degrading. So: try normal first — if the ordinary grammar reads
|
|
119
|
+
# this text without complaint, that IS the canonical, unambiguous
|
|
120
|
+
# interpretation, the same one `label_of`/`mint_hash` on the same
|
|
121
|
+
# source text always computes, whoever's asking. Only on a
|
|
122
|
+
# `Malformed` refusal — the one signal that actually means "this
|
|
123
|
+
# spelling doesn't exist anymore" — fall back to the legacy
|
|
124
|
+
# grammar, exactly as before this change. Any OTHER exception (a
|
|
125
|
+
# genuine syntax error, an unrelated validation refusal) propagates
|
|
126
|
+
# unchanged; swallowing it here to retry under shadow mode would
|
|
127
|
+
# risk masking a real defect in the held text behind a confusing
|
|
128
|
+
# second failure instead of the original, more specific one.
|
|
129
|
+
#
|
|
130
|
+
# `MetaValidator.while_shadow_parsing` (ADR 0025, docs/dsl-work-
|
|
131
|
+
# slices.md's S0a) is what makes the fallback a LEGACY grammar
|
|
132
|
+
# rather than just a second copy of today's: it stops
|
|
133
|
+
# `BluebookBuilder.build` from judging this text against the
|
|
134
|
+
# grammar as it stands NOW, which is the one thing that would make
|
|
135
|
+
# a removed spelling refuse HISTORY the day it is removed from
|
|
136
|
+
# live source. The scratch registry is throwaway either way —
|
|
137
|
+
# nothing here is dispatched against or exposed to the real one —
|
|
138
|
+
# so skipping the judge/assemble round-trip changes nothing this
|
|
139
|
+
# method reads: `shape`, `uncovered_attributes`, and friends only
|
|
140
|
+
# ever ask the built IR for its own structure.
|
|
141
|
+
def shadow_parse(source, path)
|
|
142
|
+
parse_bluebook(source, path, shadow: false)
|
|
143
|
+
rescue Hecks::Bluebook::DSL::Malformed
|
|
144
|
+
parse_bluebook(source, path, shadow: true)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def parse_bluebook(source, path, shadow:)
|
|
148
|
+
scratch = Registry.new
|
|
149
|
+
loading = Ports::Loading.bootstrap
|
|
150
|
+
run = lambda do
|
|
151
|
+
Hecks.with_registry(scratch) do
|
|
152
|
+
loading.load_library
|
|
153
|
+
Kernel.eval(source, TOPLEVEL_BINDING, path, 1)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
shadow ? Hecks::Bluebook::MetaValidator.while_shadow_parsing(&run) : run.call
|
|
157
|
+
scratch.bluebooks.values.first
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "tempfile"
|
|
3
|
+
require_relative "era_guard"
|
|
4
|
+
require_relative "storage_shape"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Runtime
|
|
8
|
+
# The refusal wording for a held era whose digest no longer matches its
|
|
9
|
+
# frozen text — the digest mismatch alone is what DETECTS tampering (a
|
|
10
|
+
# plain SHA256 comparison over raw bytes, unrelated to any of this);
|
|
11
|
+
# this only supplies the wording once that's already fired.
|
|
12
|
+
#
|
|
13
|
+
# LIVES HERE, NOT UNDER `Ports::Persistence` — `project` below directly
|
|
14
|
+
# calls `Runtime::EraGuard.shadow_parse`/`Runtime::StorageShape.project`
|
|
15
|
+
# (DSL-execution machinery), the "an adapter/port reaches into the
|
|
16
|
+
# runtime instead of being handed already-computed data" shape that
|
|
17
|
+
# has caused trouble here before. A capability this dependent on the
|
|
18
|
+
# runtime is a runtime-owned one that the Postgres adapter CALLS, not
|
|
19
|
+
# a ports-level module that happens to reach sideways into it.
|
|
20
|
+
#
|
|
21
|
+
# Used to also distinguish a cosmetic edit from a real shape change
|
|
22
|
+
# here, by re-parsing the edited text — a pure quality-of-message
|
|
23
|
+
# nicety, not a safety property, since dropped: every tamper refusal
|
|
24
|
+
# reaches the same generic wording now, the one this already fell to
|
|
25
|
+
# whenever it couldn't classify an edit anyway. An operator judges
|
|
26
|
+
# "did this matter" themselves,
|
|
27
|
+
# reading the still-archived original — an anomalous recovery moment
|
|
28
|
+
# already, not a normal boot path.
|
|
29
|
+
module EraTamper
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
def refusal(domain:, ordinal:)
|
|
33
|
+
"cannot boot #{domain}: the held text of era #{ordinal} was edited after it was frozen — " \
|
|
34
|
+
"held era texts are storage facts; restore the original text, or reset the data"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The storage-shape projection of a bluebook text, JSON-normalized
|
|
38
|
+
# for structural comparison against a stored projection; nil when
|
|
39
|
+
# the text does not load. ALWAYS parsed through a fresh tempfile,
|
|
40
|
+
# never the held file's own path: the predicate extractor caches
|
|
41
|
+
# source by path, and a held path whose content has changed (the
|
|
42
|
+
# very situation this module exists for) would hand it stale
|
|
43
|
+
# lines.
|
|
44
|
+
def project(text, _source_path = nil)
|
|
45
|
+
file = Tempfile.new(["hecks-tamper-", ".bluebook"])
|
|
46
|
+
begin
|
|
47
|
+
file.write(text)
|
|
48
|
+
file.flush
|
|
49
|
+
bluebook = EraGuard.shadow_parse(text, file.path)
|
|
50
|
+
ensure
|
|
51
|
+
file.close!
|
|
52
|
+
end
|
|
53
|
+
return nil unless bluebook
|
|
54
|
+
|
|
55
|
+
JSON.parse(JSON.generate(StorageShape.project(bluebook)))
|
|
56
|
+
rescue StandardError, SyntaxError
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|