hecks 1.3.0 → 1.5.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 +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +61 -15
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -12,28 +12,32 @@ module Hecks
|
|
|
12
12
|
# the declared routing `version:` — is excluded, so editing behavior
|
|
13
13
|
# never bumps an era and editing shape always does.
|
|
14
14
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
15
|
+
# ## Constraints left out on purpose
|
|
16
|
+
#
|
|
17
|
+
# Three attribute facts on the wire are constraints on persisted
|
|
18
|
+
# values, not behavior, and are still excluded — each decided, not
|
|
17
19
|
# overlooked:
|
|
18
20
|
#
|
|
19
21
|
# `optional` — required-ness is enforced at dispatch; stored rows
|
|
20
22
|
# are never re-validated on read, so flipping it
|
|
21
23
|
# strands nothing already written. Excluded.
|
|
22
|
-
# `pattern` — same argument: a fact about what may be
|
|
24
|
+
# `pattern` — same argument: a fact about what may be written
|
|
23
25
|
# next, not about what was stored. Excluded.
|
|
24
26
|
# `admits` — the sharpest of the three: narrowing a closed set
|
|
25
|
-
#
|
|
26
|
-
# carries only the set's
|
|
27
|
+
# can strand stored rows outside it, and the wire
|
|
28
|
+
# carries only the set's name, so a set whose members
|
|
27
29
|
# changed under a stable name is invisible even to a
|
|
28
30
|
# projection that included the fact (the same lesson
|
|
29
31
|
# recursive value-object drift taught). Excluded, and
|
|
30
|
-
#
|
|
32
|
+
# named as a gap: constraint tightening has no
|
|
31
33
|
# translation-rule vocabulary to acknowledge it yet,
|
|
32
34
|
# so including it would mint era bumps nothing can
|
|
33
35
|
# explain. When the translation language grows a
|
|
34
36
|
# constraint-acknowledgment rule, `admits` (by member
|
|
35
37
|
# list, not by name) is first in line, and that
|
|
36
|
-
# change bumps FORM_VERSION
|
|
38
|
+
# change bumps `FORM_VERSION`.
|
|
39
|
+
#
|
|
40
|
+
# ## Structural comparison over the dump form
|
|
37
41
|
#
|
|
38
42
|
# Projection runs over the canonical dump form (`to_h`, JSON
|
|
39
43
|
# round-tripped), so a verdict depends only on the IR — never on live
|
|
@@ -41,6 +45,11 @@ module Hecks
|
|
|
41
45
|
module StorageShape
|
|
42
46
|
module_function
|
|
43
47
|
|
|
48
|
+
# Reduces a bluebook to the parts of its IR that decide what stored data looks like.
|
|
49
|
+
#
|
|
50
|
+
# @param bluebook [Bluebook::Chapter] the bluebook whose `to_h` dump is projected
|
|
51
|
+
# @return [Hash{String => Object}] `"name"` (the bluebook's name) and `"aggregates"`, an
|
|
52
|
+
# Array of the Hashes `project_aggregate` builds, sorted by aggregate name
|
|
44
53
|
def project(bluebook)
|
|
45
54
|
domain = JSON.parse(JSON.generate(bluebook.to_h))
|
|
46
55
|
{
|
|
@@ -51,30 +60,55 @@ module Hecks
|
|
|
51
60
|
}
|
|
52
61
|
end
|
|
53
62
|
|
|
63
|
+
# Compares two bluebooks by storage shape alone, ignoring every behavioral difference.
|
|
64
|
+
#
|
|
65
|
+
# @param held [Bluebook::Chapter] the bluebook a held era was minted from
|
|
66
|
+
# @param current [Bluebook::Chapter] the bluebook booting now
|
|
67
|
+
# @return [Boolean] true when both project to an equal structure
|
|
54
68
|
def same?(held, current) = project(held) == project(current)
|
|
55
69
|
|
|
56
|
-
# The canonical serialization the Ruby scaffold hashes at
|
|
70
|
+
# The canonical serialization the Ruby scaffold hashes at mint time
|
|
57
71
|
# — the one moment identity is computed. Nothing ever recomputes a
|
|
58
72
|
# stored era name to verify it, so this form can evolve freely.
|
|
73
|
+
#
|
|
74
|
+
# @param bluebook [Bluebook::Chapter] the bluebook to serialize
|
|
75
|
+
# @return [String] compact JSON text of `project(bluebook)`
|
|
59
76
|
def canonical(bluebook) = JSON.generate(project(bluebook))
|
|
60
77
|
|
|
78
|
+
# Computes the era identity of a bluebook's storage shape.
|
|
79
|
+
#
|
|
80
|
+
# @param bluebook [Bluebook::Chapter] the bluebook being minted
|
|
81
|
+
# @return [String] 64 lowercase hex characters, the SHA-256 of `canonical(bluebook)`
|
|
61
82
|
def mint_hash(bluebook) = Digest::SHA256.hexdigest(canonical(bluebook))
|
|
62
83
|
|
|
63
84
|
LABEL_LENGTH = 6
|
|
85
|
+
|
|
86
|
+
# Shortens the era identity to the label translation edges and refusals use.
|
|
87
|
+
#
|
|
88
|
+
# @param bluebook [Bluebook::Chapter] the bluebook being minted
|
|
89
|
+
# @return [String] the first `LABEL_LENGTH` hex characters of `mint_hash(bluebook)`
|
|
64
90
|
def mint_label(bluebook) = mint_hash(bluebook)[0, LABEL_LENGTH]
|
|
65
91
|
|
|
66
92
|
# The version of the canonical serialization above. Minted-once
|
|
67
93
|
# means a stored name stays valid across form changes — but only
|
|
68
|
-
# if each name records
|
|
94
|
+
# if each name records which form minted it, so v1-named and
|
|
69
95
|
# v2-named eras coexist legibly. Stored beside every minted hash
|
|
70
96
|
# (names.tsv fourth field / hecks_eras.canon_form); bump this in
|
|
71
97
|
# the same change that alters project/canonical output.
|
|
72
98
|
FORM_VERSION = 1
|
|
73
99
|
|
|
100
|
+
# Projects one dumped aggregate down to its identity, lifecycle field and attributes.
|
|
101
|
+
#
|
|
102
|
+
# @param aggregate [Hash{String => Object}] one entry of the dumped bluebook's
|
|
103
|
+
# `"aggregates"` list, string-keyed after the JSON round-trip
|
|
104
|
+
# @return [Hash{String => Object}] `"name"`, `"identity"` (Array<String> of declared
|
|
105
|
+
# identity paths, `[]` when none is declared), `"lifecycle_field"` (String, or nil
|
|
106
|
+
# without a lifecycle) and `"attributes"` (Array of `project_attribute` Hashes, sorted
|
|
107
|
+
# by name)
|
|
74
108
|
def project_aggregate(aggregate)
|
|
75
109
|
{
|
|
76
110
|
"name" => aggregate["name"],
|
|
77
|
-
# The declared identity paths,
|
|
111
|
+
# The declared identity paths, as a list, in declaration order —
|
|
78
112
|
# order is semantic (the paths join in order to form the id).
|
|
79
113
|
# No "id" fallback: an aggregate that declares nothing has [],
|
|
80
114
|
# and that is a real declared state, distinct from an aggregate
|
|
@@ -87,6 +121,16 @@ module Hecks
|
|
|
87
121
|
}
|
|
88
122
|
end
|
|
89
123
|
|
|
124
|
+
# Projects one dumped attribute to its name, cardinality and full type signature.
|
|
125
|
+
#
|
|
126
|
+
# @param aggregate [Hash{String => Object}] the dumped aggregate that owns the attribute,
|
|
127
|
+
# searched for the value objects and entities its type may name
|
|
128
|
+
# @param attribute [Hash{String => Object}] the dumped attribute, read for `"name"`,
|
|
129
|
+
# `"list"` and `"type"`
|
|
130
|
+
# @param seen [Array<String>] type names already being expanded, which stops a
|
|
131
|
+
# self-referencing type from recursing forever
|
|
132
|
+
# @return [Hash{String => Object}] `"name"` (String), `"list"` (Boolean) and `"type"`
|
|
133
|
+
# (whatever `type_signature` returns)
|
|
90
134
|
def project_attribute(aggregate, attribute, seen)
|
|
91
135
|
{
|
|
92
136
|
"name" => attribute["name"].to_s,
|
|
@@ -99,6 +143,14 @@ module Hecks
|
|
|
99
143
|
# members' full signatures for a value object or entity — so two
|
|
100
144
|
# attributes with the same declared type name but different
|
|
101
145
|
# internals are never mistaken for unchanged.
|
|
146
|
+
#
|
|
147
|
+
# @param aggregate [Hash{String => Object}] the dumped aggregate whose value objects and
|
|
148
|
+
# entities are searched for `type_name`
|
|
149
|
+
# @param type_name [String] the attribute's declared type name
|
|
150
|
+
# @param seen [Array<String>] type names already being expanded
|
|
151
|
+
# @return [String, Hash{String => Object}] `type_name` itself for a primitive or a type
|
|
152
|
+
# already in `seen`; otherwise `"type"` plus `"members"`, the member attributes'
|
|
153
|
+
# `project_attribute` Hashes sorted by name
|
|
102
154
|
def type_signature(aggregate, type_name, seen)
|
|
103
155
|
container = nested_type(aggregate, type_name)
|
|
104
156
|
return type_name if container.nil? || seen.include?(type_name)
|
|
@@ -111,6 +163,12 @@ module Hecks
|
|
|
111
163
|
}
|
|
112
164
|
end
|
|
113
165
|
|
|
166
|
+
# Looks a type name up among a dumped aggregate's value objects, then its entities.
|
|
167
|
+
#
|
|
168
|
+
# @param aggregate [Hash{String => Object}] the dumped aggregate to search
|
|
169
|
+
# @param type_name [String] the declared type name to find
|
|
170
|
+
# @return [Hash{String => Object}, nil] the dumped value object or entity; nil when the
|
|
171
|
+
# name is a primitive or belongs to nothing this aggregate declares
|
|
114
172
|
def nested_type(aggregate, type_name)
|
|
115
173
|
(aggregate["value_objects"] || []).find { |vo| vo["name"] == type_name } ||
|
|
116
174
|
(aggregate["entities"] || []).find { |entity| entity["name"] == type_name }
|
|
@@ -7,21 +7,27 @@ module Hecks
|
|
|
7
7
|
# ── the human gate, made real ──────────────────────────────────
|
|
8
8
|
#
|
|
9
9
|
# For the five portable rule kinds the mint verifies the edge
|
|
10
|
-
# mechanically (Layer 2
|
|
10
|
+
# mechanically (Layer 2 is the cross-execution equivalence gate).
|
|
11
11
|
# A compute has no mechanical verification — the Layer-3 sample a
|
|
12
12
|
# human reads is the only one there is — so a compute edge cannot
|
|
13
13
|
# mint until someone has run `bin/translation_audit … --approve`.
|
|
14
14
|
#
|
|
15
|
-
# The approval binds to
|
|
15
|
+
# The approval binds to what was actually reviewed, on both axes:
|
|
16
16
|
# a content digest of the parsed edge (change the edge's meaning
|
|
17
17
|
# and the approval lapses; a comment does not), and the journal's
|
|
18
|
-
# high-water ordinal at review time, recorded
|
|
18
|
+
# high-water ordinal at review time, recorded in the target
|
|
19
19
|
# database — a token in the repo could not bind to the data, and
|
|
20
20
|
# samples reviewed against staging or against production-as-of-T
|
|
21
21
|
# say nothing about a database that has moved on. Mint stays
|
|
22
22
|
# non-interactive and its lock stays short; the human decision
|
|
23
23
|
# happens in the audit tool, where the samples are.
|
|
24
24
|
module ApprovalDigest
|
|
25
|
+
# Fingerprints a parsed translation edge, so an approval lapses when the edge's
|
|
26
|
+
# meaning changes but not when only a comment does.
|
|
27
|
+
#
|
|
28
|
+
# @param edge [Bluebook::Translation] the parsed translation edge
|
|
29
|
+
# @return [String] 64 lowercase hex characters: the SHA-256 of the edge's exported
|
|
30
|
+
# JSON (`Projector::Exporter.translation_hash`)
|
|
25
31
|
def edge_digest(edge)
|
|
26
32
|
Digest::SHA256.hexdigest(JSON.generate(Projector::Exporter.translation_hash(edge)))
|
|
27
33
|
end
|
|
@@ -8,11 +8,18 @@ module Hecks
|
|
|
8
8
|
module Audit
|
|
9
9
|
# Layer 1 — from the bluebook alone: every translated state must
|
|
10
10
|
# pass the new era's types, value-object invariants, and lifecycle.
|
|
11
|
-
# This is also where a
|
|
11
|
+
# This is also where a new, stricter invariant that old records
|
|
12
12
|
# violate surfaces — there is no "grandfather old records"
|
|
13
13
|
# construct, and the remedy is relaxing the invariant or explicit
|
|
14
14
|
# remediation, never a translation rule.
|
|
15
15
|
module LayerOne
|
|
16
|
+
# Hydrates every translated state as a current-era instance and records each one
|
|
17
|
+
# the era's types, invariants or lifecycle refuse.
|
|
18
|
+
#
|
|
19
|
+
# @param violations [Array<String>] collector this method appends messages to
|
|
20
|
+
# @param aggregate [Bluebook::Aggregate] the current era's IR for the aggregate
|
|
21
|
+
# @param after [Hash{String => Hash}] translated state per record id, as parsed JSON
|
|
22
|
+
# @return [void]
|
|
16
23
|
def layer_one!(violations, aggregate, after)
|
|
17
24
|
after.each do |id, state|
|
|
18
25
|
symbolized = JSON.parse(JSON.generate(state), symbolize_names: true)
|
|
@@ -25,7 +32,7 @@ module Hecks
|
|
|
25
32
|
# legitimately declared just as a `from:` (a terminal
|
|
26
33
|
# transition's source, never anyone's target) is real and
|
|
27
34
|
# reachable but invisible to it. `ModelCheck.full_states`
|
|
28
|
-
# is the full declared set (default, every target,
|
|
35
|
+
# is the full declared set (default, every target, and
|
|
29
36
|
# every from) that `fuzzing/properties.rb`'s own replay
|
|
30
37
|
# check already uses for this identical question — see its
|
|
31
38
|
# comment on this same hole.
|
|
@@ -7,8 +7,11 @@ module Hecks
|
|
|
7
7
|
# Layer 2 — from the edge alone: per-rule value preservation, no
|
|
8
8
|
# leftover source keys, and id-set conservation across the edge.
|
|
9
9
|
module LayerTwo
|
|
10
|
+
# Checks one aggregate's records across the edge for id conservation and, where a
|
|
11
|
+
# per-record comparison is possible, agreement with the reference transform.
|
|
12
|
+
#
|
|
10
13
|
# Per-rule value preservation and leftover source keys are checked
|
|
11
|
-
# against the reference transform
|
|
14
|
+
# against the reference transform in full — never rule by rule in
|
|
12
15
|
# isolation, because rules interact (a rename whose value a later
|
|
13
16
|
# move partially consumes preserves exactly what the transform
|
|
14
17
|
# says it preserves, no more). This makes every mint a run of the
|
|
@@ -17,10 +20,20 @@ module Hecks
|
|
|
17
20
|
# transform produces `expected`; they must agree byte-for-byte on
|
|
18
21
|
# every path a compute doesn't own. Compute paths are exempt — the
|
|
19
22
|
# SQL is their only implementation, and the Layer-3 sample is
|
|
20
|
-
# their only review. A rekeyed aggregate is exempt from this
|
|
23
|
+
# their only review. A rekeyed aggregate is exempt from this whole
|
|
21
24
|
# per-record check, for the same reason and one more: there is no
|
|
22
25
|
# old-id → new-id correspondence to look `after` up by once the id
|
|
23
26
|
# itself is what changed.
|
|
27
|
+
#
|
|
28
|
+
# @param violations [Array<String>] collector this method appends messages to
|
|
29
|
+
# @param aggregate [Bluebook::Aggregate] the current era's IR for the aggregate
|
|
30
|
+
# @param declared [Bluebook::TranslationAggregate, nil] this edge's rules for the
|
|
31
|
+
# aggregate; nil limits the check to id conservation
|
|
32
|
+
# @param before [Hash{String => Hash}] source state per record id, as parsed JSON
|
|
33
|
+
# @param after [Hash{String => Hash}] translated state per record id, as parsed JSON
|
|
34
|
+
# @return [void]
|
|
35
|
+
# @raise [Runtime::WiringError] if the reference transform cannot translate a `before`
|
|
36
|
+
# state: a convert meets an unmapped value, or a move nests under a non-Hash
|
|
24
37
|
def layer_two!(violations, aggregate, declared, before, after)
|
|
25
38
|
rekeyed = declared && !declared.rekeys.empty?
|
|
26
39
|
|
|
@@ -38,15 +51,25 @@ module Hecks
|
|
|
38
51
|
check_value_preservation!(violations, aggregate, declared, before, after)
|
|
39
52
|
end
|
|
40
53
|
|
|
54
|
+
# Records a violation when the edge loses, gains or collides record ids.
|
|
55
|
+
#
|
|
41
56
|
# Record-count / id-set conservation — one of the two properties
|
|
42
57
|
# this module's own header names, and independent of the other
|
|
43
58
|
# (per-rule value preservation, below): it needs only `before`,
|
|
44
59
|
# `after`, and whether the edge rekeyed, never the declared rules
|
|
45
60
|
# themselves.
|
|
61
|
+
#
|
|
62
|
+
# @param violations [Array<String>] collector this method appends at most one message to
|
|
63
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate, named in the message
|
|
64
|
+
# @param before [Hash{String => Hash}] source state per record id
|
|
65
|
+
# @param after [Hash{String => Hash}] translated state per record id
|
|
66
|
+
# @param rekeyed [Boolean, nil] truthy when the edge rekeys, which compares record
|
|
67
|
+
# counts instead of id sets
|
|
68
|
+
# @return [void]
|
|
46
69
|
def check_id_conservation!(violations, aggregate, before, after, rekeyed:)
|
|
47
|
-
# A rekey legitimately changes the id
|
|
70
|
+
# A rekey legitimately changes the id set (that's the entire
|
|
48
71
|
# point) — set-equality would flag every honest rekey as data
|
|
49
|
-
# loss. What must still hold is
|
|
72
|
+
# loss. What must still hold is record count: a botched rekey
|
|
50
73
|
# colliding two distinct old ids onto one new id, or dropping one
|
|
51
74
|
# (its SQL returning NULL), shows up as the count going down —
|
|
52
75
|
# caught here without needing to track the old→new mapping
|
|
@@ -69,6 +92,12 @@ module Hecks
|
|
|
69
92
|
# header names. Only reached once `layer_two!` has already ruled
|
|
70
93
|
# out "no declared edge" and "rekeyed" (see its own comment on
|
|
71
94
|
# that second guard); this method assumes both are false.
|
|
95
|
+
# @param violations [Array<String>] mutated in place with one message per divergent id
|
|
96
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate being audited
|
|
97
|
+
# @param declared [Bluebook::Translation] the declared translation to check against
|
|
98
|
+
# @param before [Hash{String => Hash}] the old world's own records, keyed by id
|
|
99
|
+
# @param after [Hash{String => Hash}] the new world's own records, keyed by id
|
|
100
|
+
# @return [void]
|
|
72
101
|
def check_value_preservation!(violations, aggregate, declared, before, after)
|
|
73
102
|
rules = Ports::Persistence::Lineage.from_declared(declared, aggregate.name)
|
|
74
103
|
compute_paths = declared.computes.flat_map { |compute| [compute.from, compute.to] }.map(&:to_s)
|
|
@@ -87,21 +116,32 @@ module Hecks
|
|
|
87
116
|
end
|
|
88
117
|
end
|
|
89
118
|
|
|
119
|
+
# Round-trips state through JSON so both sides of the comparison share one spelling.
|
|
120
|
+
#
|
|
121
|
+
# @param state [Hash, nil] a state with Symbol or String keys
|
|
122
|
+
# @return [Hash{String => Object}, nil] a deep copy with String keys at every depth
|
|
90
123
|
def normalize(state) = JSON.parse(JSON.generate(state))
|
|
91
124
|
|
|
125
|
+
# Removes the paths a compute owns from a normalized state, in place.
|
|
126
|
+
#
|
|
92
127
|
# Exempts exactly the paths a compute owns, not the whole
|
|
93
128
|
# top-level attribute it happens to live under. A bare path
|
|
94
129
|
# ("price_cents") is itself the compute's entire value — dropping
|
|
95
130
|
# the whole top-level key is correct, there's nothing else there
|
|
96
|
-
# to check. A
|
|
131
|
+
# to check. A dotted path ("price.cents") only owns that one
|
|
97
132
|
# member of the value object it reaches into; every sibling
|
|
98
133
|
# member (e.g. "price.currency") is untouched by the compute and
|
|
99
134
|
# must stay subject to the equivalence check below. Blanket-
|
|
100
|
-
# dropping the whole top-level key for a dotted compute
|
|
135
|
+
# dropping the whole top-level key for a dotted compute would
|
|
101
136
|
# exempt the entire attribute — silent data loss elsewhere in the
|
|
102
137
|
# same value object (a migration that nulls or drops a sibling
|
|
103
|
-
# field)
|
|
104
|
-
# entire purpose is to catch exactly that.
|
|
138
|
+
# field) would produce zero violations, defeating the one gate
|
|
139
|
+
# whose entire purpose is to catch exactly that.
|
|
140
|
+
#
|
|
141
|
+
# @param state [Hash{String => Object}] a normalized state; mutated
|
|
142
|
+
# @param paths [Array<String>] bare (`"price_cents"`) or dotted (`"price.cents"`)
|
|
143
|
+
# compute paths
|
|
144
|
+
# @return [Hash{String => Object}] `state` itself, with those paths deleted
|
|
105
145
|
def strip_compute_paths(state, paths)
|
|
106
146
|
paths.each do |path|
|
|
107
147
|
segments = path.split(".")
|
|
@@ -4,9 +4,11 @@ module Hecks
|
|
|
4
4
|
# New-era attributes that nothing feeds: absent from every
|
|
5
5
|
# translated state, produced by no rule, and carrying no default.
|
|
6
6
|
# A report, not a violation — the remedy is a `default:` on the
|
|
7
|
-
# attribute, and the loud refusal for a
|
|
7
|
+
# attribute, and the loud refusal for a required one comes from
|
|
8
8
|
# Layer 1 the moment an invariant reads it.
|
|
9
9
|
module UnfedReport
|
|
10
|
+
# Lists the current era's attributes that no rule, default or translated record feeds.
|
|
11
|
+
#
|
|
10
12
|
# `fed` build-up plus one ordered guard chain per attribute
|
|
11
13
|
# (already fed, has a default, actually present in some record, no
|
|
12
14
|
# records to check at all) answering a single question — "is
|
|
@@ -14,6 +16,13 @@ module Hecks
|
|
|
14
16
|
# rules out one way the answer is "not unfed after all" before the
|
|
15
17
|
# next is even worth asking. Splitting the guards out would just
|
|
16
18
|
# turn each into a same-shaped one-line predicate call.
|
|
19
|
+
#
|
|
20
|
+
# @param aggregate [Bluebook::Aggregate] the current era's IR for the aggregate
|
|
21
|
+
# @param declared [Bluebook::TranslationAggregate, nil] this edge's rules; the
|
|
22
|
+
# destinations of its renames, moves, converts and computes count as fed
|
|
23
|
+
# @param after [Hash{String => Hash}] translated state per record id
|
|
24
|
+
# @return [Array<String>] names of unfed attributes, in declaration order; `[]` when
|
|
25
|
+
# every attribute is fed or `after` holds no records
|
|
17
26
|
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
18
27
|
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
19
28
|
def unfed(aggregate, declared, after)
|
|
@@ -36,6 +45,12 @@ module Hecks
|
|
|
36
45
|
end
|
|
37
46
|
end
|
|
38
47
|
|
|
48
|
+
# Reads a dotted path out of a state whose keys may be Strings or Symbols.
|
|
49
|
+
#
|
|
50
|
+
# @param state [Hash, nil] the state to read
|
|
51
|
+
# @param path [String, Symbol] a bare or dotted path, such as `"price.cents"`
|
|
52
|
+
# @return [Object, nil] the value held at the path, `false` included; nil when `state`
|
|
53
|
+
# is nil, a segment is absent, or a segment's parent is not a Hash
|
|
39
54
|
def dig_path(state, path)
|
|
40
55
|
return nil if state.nil?
|
|
41
56
|
|
|
@@ -12,10 +12,14 @@ module Hecks
|
|
|
12
12
|
# rules), audit/layer_two.rb (the edge against the reference
|
|
13
13
|
# transform), audit/unfed_report.rb (what nothing feeds), and
|
|
14
14
|
# audit/approval_digest.rb (the human gate's binding). Layer 3 — the
|
|
15
|
-
# before/after sample a
|
|
15
|
+
# before/after sample a human approves, intent not being derivable —
|
|
16
16
|
# is assembled right here in `check`.
|
|
17
17
|
module Audit
|
|
18
18
|
Verdict = Struct.new(:violations, :dropped, :unfed, :samples, keyword_init: true) do
|
|
19
|
+
# Reports whether the mechanical layers found nothing to refuse.
|
|
20
|
+
#
|
|
21
|
+
# @return [Boolean] true when `violations` is empty; `dropped`, `unfed` and `samples`
|
|
22
|
+
# are reports and never affect the answer
|
|
19
23
|
def ok? = violations.empty?
|
|
20
24
|
end
|
|
21
25
|
|
|
@@ -28,9 +32,20 @@ module Hecks
|
|
|
28
32
|
|
|
29
33
|
module_function
|
|
30
34
|
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
35
|
+
# Audits one aggregate's records across one translation edge and gathers what a
|
|
36
|
+
# human reviewer needs to see.
|
|
37
|
+
#
|
|
38
|
+
# @param aggregate [Bluebook::Aggregate] the current era's IR for the aggregate
|
|
39
|
+
# @param declared [Bluebook::TranslationAggregate, nil] this edge's rules for the
|
|
40
|
+
# aggregate; nil when the edge declares none
|
|
41
|
+
# @param before [Hash{String => Hash}] the source era's latest state per record id, as
|
|
42
|
+
# parsed JSON
|
|
43
|
+
# @param after [Hash{String => Hash}] the translated state per record id, as parsed JSON
|
|
44
|
+
# @return [Translation::Audit::Verdict] `violations` (Layer 1 and 2 messages), `dropped`
|
|
45
|
+
# (declared drop paths as Strings), `unfed` (attribute names nothing feeds) and
|
|
46
|
+
# `samples` (the Layer 3 before/after pairs)
|
|
47
|
+
# @raise [Runtime::WiringError] if Layer 2's reference transform cannot translate a
|
|
48
|
+
# `before` state: a convert meets an unmapped value, or a move nests under a non-Hash
|
|
34
49
|
def check(aggregate:, declared:, before:, after:)
|
|
35
50
|
violations = []
|
|
36
51
|
|
|
@@ -45,22 +60,38 @@ module Hecks
|
|
|
45
60
|
)
|
|
46
61
|
end
|
|
47
62
|
|
|
63
|
+
# Picks the first `SAMPLE_SIZE` records, by sorted id, for a human to compare.
|
|
64
|
+
#
|
|
65
|
+
# @param declared [Bluebook::TranslationAggregate, nil] this edge's rules; a rekey among
|
|
66
|
+
# them switches to `rekeyed_samples`
|
|
67
|
+
# @param before [Hash{String => Hash}] source state per record id
|
|
68
|
+
# @param after [Hash{String => Hash}] translated state per record id
|
|
69
|
+
# @return [Array<Hash{Symbol => Object}>] Hashes with `:id`, `:before` and `:after`;
|
|
70
|
+
# `:after` is nil for an id missing from `after`
|
|
48
71
|
def samples_for(declared, before, after)
|
|
49
72
|
return rekeyed_samples(before, after) if declared && !declared.rekeys.empty?
|
|
50
73
|
|
|
51
74
|
before.keys.sort.first(SAMPLE_SIZE).map { |id| { id: id, before: before[id], after: after[id] } }
|
|
52
75
|
end
|
|
53
76
|
|
|
77
|
+
# Samples both sides of a rekeying edge separately instead of pairing them by id.
|
|
78
|
+
#
|
|
54
79
|
# A rekey changes the id itself, so `before`'s and `after`'s
|
|
55
80
|
# keyspaces share nothing — pairing by matching id (the ordinary
|
|
56
81
|
# path above) would show every `after` as nil, telling a human
|
|
57
82
|
# nothing. Nothing in-process can compute the old→new
|
|
58
83
|
# correspondence either (the rekey's SQL is its only
|
|
59
84
|
# implementation, same as compute's own). Shown side by side
|
|
60
|
-
# instead, each labelled by its
|
|
85
|
+
# instead, each labelled by its own id: real records going in,
|
|
61
86
|
# real records coming out — not claimed to correspond one-to-one,
|
|
62
87
|
# but enough for the human this rule's only verification depends
|
|
63
88
|
# on to actually see real shapes and values, not a wall of null.
|
|
89
|
+
#
|
|
90
|
+
# @param before [Hash{String => Hash}] source state per old record id
|
|
91
|
+
# @param after [Hash{String => Hash}] translated state per new record id
|
|
92
|
+
# @return [Array<Hash{Symbol => Object}>] up to `SAMPLE_SIZE` Hashes whose `:id` ends in
|
|
93
|
+
# `" (before)"` with `:after` nil, then up to `SAMPLE_SIZE` ending in `" (after)"` with
|
|
94
|
+
# `:before` nil
|
|
64
95
|
def rekeyed_samples(before, after)
|
|
65
96
|
before.keys.sort.first(SAMPLE_SIZE).map { |id| { id: "#{id} (before)", before: before[id], after: nil } } +
|
|
66
97
|
after.keys.sort.first(SAMPLE_SIZE).map { |id| { id: "#{id} (after)", before: nil, after: after[id] } }
|
|
@@ -6,19 +6,33 @@ require_relative "../storage_shape"
|
|
|
6
6
|
module Hecks
|
|
7
7
|
module Translation
|
|
8
8
|
# The question re-attestation must answer before anything else: did
|
|
9
|
-
# the edit change the era's
|
|
9
|
+
# the edit change the era's shape, or only its text? Cosmetic edits
|
|
10
10
|
# (comments, whitespace, behavior) re-freeze safely; a shape change
|
|
11
11
|
# would retroactively redefine what era N *meant* for data already
|
|
12
12
|
# written under it, and refuses hard — there is no --accept past
|
|
13
13
|
# this guard.
|
|
14
14
|
#
|
|
15
15
|
# This does not violate minted-once. That prohibition exists so
|
|
16
|
-
# boot-time
|
|
16
|
+
# boot-time recognition never depends on canonicalization stability;
|
|
17
17
|
# this is operator-initiated repair, where a false negative is a
|
|
18
18
|
# loud refusal, never a silent misread.
|
|
19
19
|
module Reattest
|
|
20
20
|
module_function
|
|
21
21
|
|
|
22
|
+
# Decides whether an edited era text still projects to the shape the era was frozen
|
|
23
|
+
# with, refusing the edit when it does not.
|
|
24
|
+
#
|
|
25
|
+
# @param domain [String] the domain's name, used in refusal messages
|
|
26
|
+
# @param ordinal [Integer] the era's ordinal, used in refusal messages
|
|
27
|
+
# @param text [String] the held era's bluebook source as it now stands
|
|
28
|
+
# @param stored_hash [String, nil] the era's minted shape hash (SHA-256 hex); nil for an
|
|
29
|
+
# era that was never named
|
|
30
|
+
# @param stored_projection [Hash{String => Object}, nil] the era's stored
|
|
31
|
+
# `Runtime::StorageShape.project` result as parsed JSON; nil for a store without one
|
|
32
|
+
# @return [Symbol] `:cosmetic` when the shape is unchanged, `:unnamed` when neither a
|
|
33
|
+
# projection nor a hash is stored so no comparison is possible
|
|
34
|
+
# @raise [Runtime::WiringError] if `text` does not load as a bluebook, or it projects to
|
|
35
|
+
# a shape other than the stored one
|
|
22
36
|
def shape_guard!(domain:, ordinal:, text:, stored_hash:, stored_projection: nil)
|
|
23
37
|
bluebook = shadow(text)
|
|
24
38
|
unless bluebook
|
|
@@ -27,7 +41,7 @@ module Hecks
|
|
|
27
41
|
"bluebook — a held era text is bootable source; restore a loadable text"
|
|
28
42
|
end
|
|
29
43
|
|
|
30
|
-
# The stored
|
|
44
|
+
# The stored projection is the preferred comparison: structural,
|
|
31
45
|
# version-free, and the same mechanism every boot trusts — so
|
|
32
46
|
# this guard never depends on canonicalization stability, and a
|
|
33
47
|
# future canonical-form version cannot make cosmetic edits to
|
|
@@ -57,6 +71,12 @@ module Hecks
|
|
|
57
71
|
"under it; restore a text with the original shape"
|
|
58
72
|
end
|
|
59
73
|
|
|
74
|
+
# Parses bluebook source in a scratch registry, through a temporary file that is
|
|
75
|
+
# removed afterwards, without touching the live registry.
|
|
76
|
+
#
|
|
77
|
+
# @param source [String] bluebook source text
|
|
78
|
+
# @return [Bluebook::Chapter, nil] the first bluebook the source declares; nil when it
|
|
79
|
+
# declares none or fails to parse or load for any reason
|
|
60
80
|
def shadow(source)
|
|
61
81
|
file = Tempfile.new(["hecks-reattest-", ".bluebook"])
|
|
62
82
|
file.write(source)
|