hecks 1.2.0 → 1.4.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 +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +60 -2
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -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/postgres_era.adapter +5 -0
- data/lib/hecks/adapters/driven/prism.rb +4 -4
- 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.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +51 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -3
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -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 +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- 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 +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- 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 +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- 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 +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +122 -86
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +301 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- 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 +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -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 -20
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +361 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +124 -0
- data/lib/hecks/fuzzing/form_census.rb +199 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +226 -38
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
- data/lib/hecks/fuzzing/properties/guards.rb +129 -26
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
- data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +84 -39
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +224 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +676 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +39 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
- data/lib/hecks/fuzzing/value_generator.rb +66 -14
- data/lib/hecks/fuzzing.rb +11 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- 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 +365 -15
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +89 -21
- data/lib/hecks/ports/access_control.rb +58 -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 +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- 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 +160 -58
- 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 +28 -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 +44 -6
- 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 +57 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +338 -83
- 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 +42 -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 +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -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 +29 -6
- 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 +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +250 -0
- data/lib/hecks/projections/glossary/markdown.rb +105 -0
- data/lib/hecks/projections/glossary/mermaid.rb +110 -0
- data/lib/hecks/projections/glossary/page.css +271 -0
- data/lib/hecks/projections/glossary/page.js +72 -0
- data/lib/hecks/projections/glossary/sections.rb +17 -0
- data/lib/hecks/projections/glossary/sentences.rb +205 -0
- data/lib/hecks/projections/glossary.rb +214 -286
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +15 -22
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +132 -24
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +57 -13
- 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 +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +64 -67
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- 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 +94 -19
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +282 -46
- data/lib/hecks/runtime/entity_interpreter.rb +160 -69
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +111 -56
- data/lib/hecks/runtime/reaction_invocation.rb +76 -9
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -112
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +56 -27
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -88
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +93 -56
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +379 -226
- data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
- data/lib/hecks/runtime/value.rb +28 -23
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +207 -5
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +43 -2
|
@@ -11,12 +11,20 @@ module Hecks
|
|
|
11
11
|
# version, recording a Layer-3 approval, and minting an era's name
|
|
12
12
|
# once (never recomputed after).
|
|
13
13
|
module EraStore
|
|
14
|
+
# Lists every era the domain holds, oldest first, refusing any whose text was edited.
|
|
15
|
+
#
|
|
14
16
|
# Every held text is verified against its raw-byte digest on the
|
|
15
17
|
# way out — an edited storage fact refuses rather than silently
|
|
16
|
-
# reporting "no drift". This is
|
|
18
|
+
# reporting "no drift". This is not the era name (which hashes
|
|
17
19
|
# the canonical projection, minted once, never recomputed): it
|
|
18
|
-
# is a plain integrity check over bytes, and a row
|
|
19
|
-
#
|
|
20
|
+
# is a plain integrity check over bytes, and a row that carries
|
|
21
|
+
# no digest yet is backfilled, not refused.
|
|
22
|
+
#
|
|
23
|
+
# @return [Array<Hash{Symbol => Object}>] one Hash per era in ordinal order, `[]` when
|
|
24
|
+
# the domain holds none; keys are `:ordinal` (Integer), `:hash` and `:label` (String,
|
|
25
|
+
# nil until `mint_name!` names the era), `:held_text` (String) and `:watermark`
|
|
26
|
+
# (Integer journal ordinal the era was cut at, nil for era 1)
|
|
27
|
+
# @raise [Runtime::WiringError] if a held text no longer matches its stored digest
|
|
20
28
|
def eras
|
|
21
29
|
@db.exec_params(
|
|
22
30
|
"SELECT ordinal, hash, label, held_text, watermark, held_digest, held_projection::text " \
|
|
@@ -29,8 +37,15 @@ module Hecks
|
|
|
29
37
|
end
|
|
30
38
|
end
|
|
31
39
|
|
|
40
|
+
# Reads one era's stored row without running the integrity check that `eras` applies.
|
|
41
|
+
#
|
|
32
42
|
# The unverified row — what bin/reattest_era shows an operator
|
|
33
43
|
# after the integrity check fires.
|
|
44
|
+
#
|
|
45
|
+
# @param ordinal [Integer] ordinal of the era to read
|
|
46
|
+
# @return [Hash{Symbol => String, nil}, nil] `:held_text`, `:held_digest` (SHA-256 hex,
|
|
47
|
+
# nil if never stored), `:hash` (nil until minted) and `:held_projection` (JSON text,
|
|
48
|
+
# nil if never stored); nil when the domain holds no such era
|
|
34
49
|
def raw_era(ordinal)
|
|
35
50
|
rows = @db.exec_params(
|
|
36
51
|
"SELECT held_text, held_digest, hash, held_projection::text FROM hecks_eras WHERE domain = $1 AND ordinal = $2",
|
|
@@ -42,10 +57,17 @@ module Hecks
|
|
|
42
57
|
held_projection: rows[0]["held_projection"] }
|
|
43
58
|
end
|
|
44
59
|
|
|
45
|
-
#
|
|
60
|
+
# Accepts an era's held text as it now stands, replacing its stored digest.
|
|
61
|
+
#
|
|
62
|
+
# The recovery path: tamper-evidence (against accident and
|
|
46
63
|
# drift, not adversaries) gets resolved by a human acknowledging
|
|
47
64
|
# the text as it now stands — recorded in an append-only
|
|
48
|
-
# attestation table, never by a bare psql UPDATE
|
|
65
|
+
# attestation table, never by a bare psql `UPDATE`.
|
|
66
|
+
#
|
|
67
|
+
# @param ordinal [Integer] ordinal of the era to re-attest
|
|
68
|
+
# @return [String] the new SHA-256 hex digest of the held text
|
|
69
|
+
# @raise [Runtime::WiringError] if the domain holds no era with that ordinal
|
|
70
|
+
# @raise [PG::Error] if Postgres refuses the attestation DDL or a write
|
|
49
71
|
def reattest!(ordinal)
|
|
50
72
|
era = raw_era(ordinal)
|
|
51
73
|
raise Runtime::WiringError, "#{@domain} holds no era #{ordinal} to re-attest" unless era
|
|
@@ -72,6 +94,19 @@ module Hecks
|
|
|
72
94
|
fresh
|
|
73
95
|
end
|
|
74
96
|
|
|
97
|
+
# Checks one held text against its stored digest, storing the digest if the row has none.
|
|
98
|
+
#
|
|
99
|
+
# A text that passes (or that had no digest to fail against) also gets
|
|
100
|
+
# `backfill_frozen_facts!`; a text that fails gets nothing written.
|
|
101
|
+
#
|
|
102
|
+
# @param ordinal [Integer] ordinal of the era the text belongs to
|
|
103
|
+
# @param text [String] the era's held bluebook source text
|
|
104
|
+
# @param stored_digest [String, nil] SHA-256 hex digest stored with the row; nil when
|
|
105
|
+
# the row carries none yet
|
|
106
|
+
# @param stored_projection_json [String, nil] the row's stored projection as JSON text;
|
|
107
|
+
# nil when the row carries none yet
|
|
108
|
+
# @return [void]
|
|
109
|
+
# @raise [Runtime::WiringError] if `stored_digest` is present and does not match `text`
|
|
75
110
|
def verify_integrity!(ordinal, text, stored_digest, stored_projection_json)
|
|
76
111
|
digest = Digest::SHA256.hexdigest(text)
|
|
77
112
|
if stored_digest.nil?
|
|
@@ -90,9 +125,17 @@ module Hecks
|
|
|
90
125
|
raise Runtime::WiringError, Runtime::EraTamper.refusal(domain: @domain, ordinal: ordinal)
|
|
91
126
|
end
|
|
92
127
|
|
|
93
|
-
#
|
|
128
|
+
# Fills in the archive copy and the stored projection an era row is missing.
|
|
129
|
+
#
|
|
130
|
+
# A verified-authentic text backfills what a row may lack: its
|
|
94
131
|
# projection and its archive copy. Never run on a text that
|
|
95
132
|
# failed verification — that would bless the edit.
|
|
133
|
+
#
|
|
134
|
+
# @param ordinal [Integer] ordinal of the era the text belongs to
|
|
135
|
+
# @param text [String] the era's verified held source text
|
|
136
|
+
# @param stored_projection_json [String, nil] the row's stored projection as JSON text;
|
|
137
|
+
# nil makes this derive one from `text` and store it, unless `text` does not load
|
|
138
|
+
# @return [void]
|
|
96
139
|
def backfill_frozen_facts!(ordinal, text, stored_projection_json)
|
|
97
140
|
archive_text!(ordinal, text)
|
|
98
141
|
return if stored_projection_json
|
|
@@ -107,11 +150,27 @@ module Hecks
|
|
|
107
150
|
)
|
|
108
151
|
end
|
|
109
152
|
|
|
153
|
+
# Reports the ordinal of the newest era the domain holds.
|
|
154
|
+
#
|
|
155
|
+
# @return [Integer] the highest held ordinal, or 1 when no era is held yet
|
|
156
|
+
# @raise [Runtime::WiringError] if a held text no longer matches its stored digest
|
|
110
157
|
def current_era
|
|
111
158
|
held = eras
|
|
112
159
|
held.empty? ? 1 : held.last[:ordinal]
|
|
113
160
|
end
|
|
114
161
|
|
|
162
|
+
# Holds a domain's first source text as era 1 and makes era 1 the writable era.
|
|
163
|
+
#
|
|
164
|
+
# Call only while the domain holds no era — every caller checks `eras.empty?` first.
|
|
165
|
+
# An existing era 1 row is kept (`ON CONFLICT DO NOTHING`), but `advance_era!(1)` runs
|
|
166
|
+
# regardless, so calling this on a domain past era 1 would roll the write fence back.
|
|
167
|
+
#
|
|
168
|
+
# @param text [String] the bluebook source text to freeze as era 1
|
|
169
|
+
# @param projection [Hash{String => Object}, nil] the storage-shape projection of the
|
|
170
|
+
# bluebook, as `Runtime::StorageShape.project` returns it; nil stores none
|
|
171
|
+
# @return [void]
|
|
172
|
+
# @raise [PG::Error] if Postgres refuses the insert or the policy change, as it does
|
|
173
|
+
# for a role that does not own the journal
|
|
115
174
|
def hold_first!(text, projection: nil)
|
|
116
175
|
@db.exec_params(
|
|
117
176
|
"INSERT INTO hecks_eras (domain, ordinal, held_text, held_digest, held_projection) " \
|
|
@@ -124,6 +183,15 @@ module Hecks
|
|
|
124
183
|
advance_era!(1)
|
|
125
184
|
end
|
|
126
185
|
|
|
186
|
+
# Copies one version of an era's text into `hecks_era_texts`, keyed by its digest.
|
|
187
|
+
#
|
|
188
|
+
# A version already archived is left alone (`ON CONFLICT DO NOTHING`), so every
|
|
189
|
+
# distinct text an era ever held survives a re-attestation.
|
|
190
|
+
#
|
|
191
|
+
# @param ordinal [Integer] ordinal of the era the text belongs to
|
|
192
|
+
# @param text [String] the source text to archive
|
|
193
|
+
# @return [void]
|
|
194
|
+
# @raise [PG::Error] if Postgres refuses the insert
|
|
127
195
|
def archive_text!(ordinal, text)
|
|
128
196
|
@db.exec_params(
|
|
129
197
|
"INSERT INTO hecks_era_texts (domain, ordinal, digest, held_text) VALUES ($1, $2, $3, $4) " \
|
|
@@ -132,10 +200,19 @@ module Hecks
|
|
|
132
200
|
)
|
|
133
201
|
end
|
|
134
202
|
|
|
135
|
-
#
|
|
203
|
+
# Records that a human approved a translation edge's audit samples.
|
|
204
|
+
#
|
|
205
|
+
# A Layer-3 approval, recorded in the database it was reviewed
|
|
136
206
|
# against — bound to the edge's parsed content and the journal's
|
|
137
207
|
# high-water ordinal at review time. The latest row for a shape
|
|
138
208
|
# pair wins (re-approval supersedes).
|
|
209
|
+
#
|
|
210
|
+
# @param from [String] label of the era shape the edge leaves
|
|
211
|
+
# @param to [String] label of the era shape the edge leads to
|
|
212
|
+
# @param edge_digest [String] SHA-256 hex digest of the edge's parsed content, as
|
|
213
|
+
# `Translation::Audit.edge_digest` computes it
|
|
214
|
+
# @return [void]
|
|
215
|
+
# @raise [PG::Error] if Postgres refuses the insert
|
|
139
216
|
def record_approval!(from:, to:, edge_digest:)
|
|
140
217
|
@db.exec_params(
|
|
141
218
|
"INSERT INTO hecks_approvals (domain, from_label, to_label, edge_digest, reviewed_ordinal) " \
|
|
@@ -144,6 +221,13 @@ module Hecks
|
|
|
144
221
|
)
|
|
145
222
|
end
|
|
146
223
|
|
|
224
|
+
# Finds the most recent approval recorded for the edge between two era shapes.
|
|
225
|
+
#
|
|
226
|
+
# @param from [String] label of the era shape the edge leaves
|
|
227
|
+
# @param to [String] label of the era shape the edge leads to
|
|
228
|
+
# @return [Hash{Symbol => Object}, nil] `:edge_digest` (String, SHA-256 hex of the edge
|
|
229
|
+
# that was reviewed) and `:reviewed_ordinal` (Integer, the journal's highest ordinal at
|
|
230
|
+
# review time); nil when no approval is recorded for the pair
|
|
147
231
|
def approval_for(from:, to:)
|
|
148
232
|
rows = @db.exec_params(
|
|
149
233
|
"SELECT edge_digest, reviewed_ordinal FROM hecks_approvals " \
|
|
@@ -155,8 +239,16 @@ module Hecks
|
|
|
155
239
|
{ edge_digest: rows[0]["edge_digest"], reviewed_ordinal: rows[0]["reviewed_ordinal"].to_i }
|
|
156
240
|
end
|
|
157
241
|
|
|
158
|
-
#
|
|
242
|
+
# Stores an era's hash and label, only if the era has no hash yet.
|
|
243
|
+
#
|
|
244
|
+
# Era identity is minted once and stored; nothing ever recomputes
|
|
159
245
|
# a stored name to verify it.
|
|
246
|
+
#
|
|
247
|
+
# @param ordinal [Integer] ordinal of the era to name
|
|
248
|
+
# @param hash [String] the era's shape hash, SHA-256 hex
|
|
249
|
+
# @param label [String] the short label edges and refusals use, a prefix of `hash`
|
|
250
|
+
# @return [void]
|
|
251
|
+
# @raise [PG::Error] if Postgres refuses the update
|
|
160
252
|
def mint_name!(ordinal, hash, label)
|
|
161
253
|
@db.exec_params(
|
|
162
254
|
"UPDATE hecks_eras SET hash = $3, label = $4, canon_form = $5 " \
|
|
@@ -165,6 +257,9 @@ module Hecks
|
|
|
165
257
|
)
|
|
166
258
|
end
|
|
167
259
|
|
|
260
|
+
# Reads the journal's high-water mark across every era's partition.
|
|
261
|
+
#
|
|
262
|
+
# @return [Integer] the highest ordinal in the journal, or 0 when the journal is empty
|
|
168
263
|
def last_ordinal
|
|
169
264
|
@db.exec("SELECT COALESCE(max(ordinal), 0) AS o FROM #{quoted_journal}")[0]["o"].to_i
|
|
170
265
|
end
|
|
@@ -4,11 +4,16 @@ module Hecks
|
|
|
4
4
|
module Adapters
|
|
5
5
|
class PostgresEra
|
|
6
6
|
class Lineage
|
|
7
|
-
#
|
|
7
|
+
# The per-`where`-field read cache that lets a declared query find
|
|
8
|
+
# its ids without reducing the whole aggregate first.
|
|
9
|
+
#
|
|
10
|
+
# ## Why a cache and not an index
|
|
11
|
+
#
|
|
12
|
+
# **The read-side of the era workaround**. Once a domain mints a
|
|
8
13
|
# second era, `head_view` is a `DISTINCT ON`/`UNION ALL`
|
|
9
14
|
# reduction — and a `where` clause on anything but `id` (its own
|
|
10
15
|
# partition key) cannot be pushed through that reduction, so every
|
|
11
|
-
# declared query pays the cost of reducing the
|
|
16
|
+
# declared query pays the cost of reducing the whole aggregate
|
|
12
17
|
# before it can filter anything (see docs/implemented/postgres-era-adapter-
|
|
13
18
|
# split-plan.md's own trigger section — this is a real
|
|
14
19
|
# SQL-semantics wall, not a missing index; adding one to
|
|
@@ -16,29 +21,35 @@ module Hecks
|
|
|
16
21
|
# prove a predicate on a non-partition column commutes with
|
|
17
22
|
# `DISTINCT ON` without risking a stale row winning).
|
|
18
23
|
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
24
|
+
# ## The table
|
|
25
|
+
#
|
|
26
|
+
# **The workaround**: one narrow table per declared `where`-field
|
|
27
|
+
# (never `order_by`-only fields — sorting the reduced output is
|
|
28
|
+
# not blocked by the reduction; only filtering is), holding
|
|
29
|
+
# exactly (id, ordinal, value) — the current extracted value for
|
|
23
30
|
# every live id, maintained transactionally by `append` alongside
|
|
24
31
|
# the head snapshot it already upserts (same ordinal-guard idiom:
|
|
25
32
|
# `WHERE ordinal < EXCLUDED.ordinal`). A query whose sole `where`
|
|
26
33
|
# is on a cached field never runs the reduction at all: `SELECT id
|
|
27
34
|
# FROM <field>_cache WHERE value <op> $1`, then `SELECT id, state
|
|
28
|
-
# FROM head_view WHERE id = ANY($ids)` — safe
|
|
35
|
+
# FROM head_view WHERE id = ANY($ids)` — safe through the
|
|
29
36
|
# reduction because `id` is its own partition key, unlike the
|
|
30
37
|
# original field.
|
|
31
38
|
#
|
|
32
|
-
#
|
|
39
|
+
# ## What it does not cover
|
|
40
|
+
#
|
|
41
|
+
# Scoped to non-list fields only, same reasoning Track A (plain
|
|
33
42
|
# Postgres) and Track B (Sqlite/D1) both landed on independently
|
|
34
43
|
# for their own automatic indexing: this codebase's `contains`
|
|
35
44
|
# compiles to element-membership (`jsonb_array_elements`/
|
|
36
45
|
# `json_each` + `EXISTS`), which a (id, ordinal, value) cache table
|
|
37
|
-
# keyed on
|
|
46
|
+
# keyed on one scalar value per id has no way to represent — a
|
|
38
47
|
# list field would need one row per (id, element), a different
|
|
39
48
|
# shape this plan does not build. `PostgresEra#eligible_for_cache?`
|
|
40
49
|
# is where that boundary lives.
|
|
41
50
|
module FieldCache
|
|
51
|
+
# Names the cache table for one field of one aggregate in one era.
|
|
52
|
+
#
|
|
42
53
|
# Deterministic and collision-free regardless of field-path
|
|
43
54
|
# length or storage-name length — Postgres identifiers cap at 63
|
|
44
55
|
# bytes, and a value-object member path (`compliance.review.
|
|
@@ -46,22 +57,52 @@ module Hecks
|
|
|
46
57
|
# that if built any less defensively. Not meant to be
|
|
47
58
|
# human-readable; `hecks_backfill_progress`/catalog lookups are
|
|
48
59
|
# always driven by this same computed name, never typed by hand.
|
|
60
|
+
#
|
|
61
|
+
# `@domain` is part of the hash input — this table is exposed to
|
|
62
|
+
# the exact same collision this whole file's siblings
|
|
63
|
+
# (`head_view`/`head_snapshot`/`matview`, `lineage.rb`) are
|
|
64
|
+
# domain-qualified against in docs/decisions/0059: without it,
|
|
65
|
+
# two domains bound to PostgresEra against the same database,
|
|
66
|
+
# each with an aggregate sharing a storage_name and a cached
|
|
67
|
+
# where-field of the same name, derive the identical
|
|
68
|
+
# `hecks_fc_<hash>` table and silently share cached rows across
|
|
69
|
+
# domains. Already fully hashed, so folding the domain in costs
|
|
70
|
+
# nothing readability could lose.
|
|
71
|
+
#
|
|
72
|
+
# @param storage_name [String] the aggregate's snake-cased storage name
|
|
73
|
+
# @param era [Integer] ordinal of the era the cache belongs to
|
|
74
|
+
# @param field [String] the cached `where` field, a dotted path for a
|
|
75
|
+
# value-object member such as `"compliance.review.deadline_at"`
|
|
76
|
+
# @return [String] unquoted table name, `hecks_fc_` plus 20 hex characters
|
|
49
77
|
def field_cache(storage_name, era, field)
|
|
50
|
-
"hecks_fc_#{Digest::SHA256.hexdigest("#{storage_name}\0#{era}\0#{field}")[0, 20]}"
|
|
78
|
+
"hecks_fc_#{Digest::SHA256.hexdigest("#{@domain}\0#{storage_name}\0#{era}\0#{field}")[0, 20]}"
|
|
51
79
|
end
|
|
52
80
|
|
|
81
|
+
# Creates one field's cache table if it is missing, then backfills it
|
|
82
|
+
# from the current head until the backfill is recorded complete.
|
|
83
|
+
#
|
|
53
84
|
# Self-healing, same idiom as `ensure_head_snapshot!`: cheap,
|
|
54
|
-
# unconditional, safe to call on every boot.
|
|
85
|
+
# unconditional, safe to call on every boot. Creation is a
|
|
55
86
|
# short-held lock (table doesn't exist yet — nothing to block);
|
|
56
|
-
#
|
|
87
|
+
# backfill runs outside it, chunk by chunk, each chunk its own
|
|
57
88
|
# short lock — never one transaction spanning the scan, per
|
|
58
89
|
# principle 1. `value_expression` is the exact SQL
|
|
59
90
|
# `PostgresEra#query_expression(field)` already compiles for
|
|
60
91
|
# this field, applied against a `state` column this module's own
|
|
61
92
|
# SQL always makes available in scope (see `field_cache_source_sql`)
|
|
62
|
-
# —
|
|
93
|
+
# — one source of truth for "what does this field mean", not a
|
|
63
94
|
# second hand-rolled copy that could silently drift from what a
|
|
64
95
|
# live query actually filters on.
|
|
96
|
+
#
|
|
97
|
+
# @param storage_name [String] the aggregate's snake-cased storage name
|
|
98
|
+
# @param era [Integer] ordinal of the era the cache belongs to
|
|
99
|
+
# @param field [String] the cached `where` field, dotted for a value-object member
|
|
100
|
+
# @param value_expression [String] SQL expression that extracts the field's
|
|
101
|
+
# value as text from a jsonb column named `state`
|
|
102
|
+
# @return [String] unquoted name of the cache table, as `field_cache` derives it
|
|
103
|
+
# @raise [PG::Error] if Postgres refuses the DDL, a source read, or an upsert
|
|
104
|
+
# @raise [Runtime::WiringError] if `era` is past 1, a chunk is still to be
|
|
105
|
+
# backfilled, and a held era's text fails its digest check (see `eras`)
|
|
65
106
|
def ensure_field_cache!(storage_name, era, field, value_expression)
|
|
66
107
|
name = field_cache(storage_name, era, field)
|
|
67
108
|
unless table_exists?(name)
|
|
@@ -83,6 +124,19 @@ module Hecks
|
|
|
83
124
|
name
|
|
84
125
|
end
|
|
85
126
|
|
|
127
|
+
# Fills an existing cache table from the reduced head, one committed chunk
|
|
128
|
+
# of `ResumableBackfill::CHUNK_SIZE` ids at a time, resuming from the cursor
|
|
129
|
+
# held in `hecks_backfill_progress` and doing nothing once it is complete.
|
|
130
|
+
#
|
|
131
|
+
# @param name [String] unquoted cache table name, from `field_cache`
|
|
132
|
+
# @param storage_name [String] the aggregate's snake-cased storage name
|
|
133
|
+
# @param era [Integer] ordinal of the era whose head is read
|
|
134
|
+
# @param value_expression [String] SQL expression that extracts the field's
|
|
135
|
+
# value as text from a jsonb column named `state`
|
|
136
|
+
# @return [void]
|
|
137
|
+
# @raise [PG::Error] if Postgres refuses a source read or an upsert
|
|
138
|
+
# @raise [Runtime::WiringError] if `era` is past 1, a chunk is still to be
|
|
139
|
+
# backfilled, and a held era's text fails its digest check (see `eras`)
|
|
86
140
|
def backfill_field_cache!(name, storage_name, era, value_expression)
|
|
87
141
|
chunked_backfill!(
|
|
88
142
|
name,
|
|
@@ -99,16 +153,29 @@ module Hecks
|
|
|
99
153
|
)
|
|
100
154
|
end
|
|
101
155
|
|
|
102
|
-
#
|
|
156
|
+
# Upserts one id's cached value from the state being written, leaving the
|
|
157
|
+
# row alone when it already carries an equal or newer ordinal.
|
|
158
|
+
#
|
|
159
|
+
# **The live-write side** — called from `append`, inside the same
|
|
103
160
|
# transaction as the journal insert and the head-snapshot
|
|
104
161
|
# upsert, for every field this aggregate has a cache table for.
|
|
105
|
-
# `state_json` is the entry's
|
|
162
|
+
# `state_json` is the entry's own new state (already the thing
|
|
106
163
|
# `append` is about to write) — bound as a literal jsonb value
|
|
107
164
|
# and aliased `state`, so `value_expression` (built to read a
|
|
108
165
|
# column literally named `state`) evaluates identically here and
|
|
109
166
|
# in every backfill/query-time use, with no separate Ruby-side
|
|
110
167
|
# re-implementation of "how to pick a value-object's member" to
|
|
111
168
|
# keep in sync.
|
|
169
|
+
#
|
|
170
|
+
# @param name [String] unquoted cache table name, from `field_cache`
|
|
171
|
+
# @param id [String] the aggregate id the entry belongs to
|
|
172
|
+
# @param ordinal [String, Integer] the journal ordinal the entry was written
|
|
173
|
+
# at; `append` passes the decimal text Postgres returned from its INSERT
|
|
174
|
+
# @param state_json [String] the entry's encoded state, as JSON text
|
|
175
|
+
# @param value_expression [String] SQL expression that extracts the field's
|
|
176
|
+
# value as text from a jsonb column named `state`
|
|
177
|
+
# @return [void]
|
|
178
|
+
# @raise [PG::Error] if Postgres refuses the upsert
|
|
112
179
|
def upsert_field_cache_row!(name, id, ordinal, state_json, value_expression)
|
|
113
180
|
@db.exec_params(<<~SQL, [id, ordinal, state_json])
|
|
114
181
|
INSERT INTO #{quote(name)} (id, ordinal, value)
|
|
@@ -119,6 +186,14 @@ module Hecks
|
|
|
119
186
|
SQL
|
|
120
187
|
end
|
|
121
188
|
|
|
189
|
+
# Removes a deleted id's row from one cache table, so a cached-field query
|
|
190
|
+
# stops offering that id. Called from `append` for a delete entry, inside the
|
|
191
|
+
# same transaction as the journal insert and the head-snapshot tombstone.
|
|
192
|
+
#
|
|
193
|
+
# @param name [String] unquoted cache table name, from `field_cache`
|
|
194
|
+
# @param id [String] the aggregate id whose row is removed
|
|
195
|
+
# @return [void]
|
|
196
|
+
# @raise [PG::Error] if Postgres refuses the delete
|
|
122
197
|
def delete_field_cache_row!(name, id)
|
|
123
198
|
@db.exec_params("DELETE FROM #{quote(name)} WHERE id = $1", [id])
|
|
124
199
|
end
|
|
@@ -126,19 +201,19 @@ module Hecks
|
|
|
126
201
|
private
|
|
127
202
|
|
|
128
203
|
# The reduced (id, ordinal, state) source a field cache backfills
|
|
129
|
-
# from —
|
|
130
|
-
# own source): a field cache reflects the
|
|
204
|
+
# from — not the raw journal (unlike `backfill_head_snapshot!`'s
|
|
205
|
+
# own source): a field cache reflects the current head value,
|
|
131
206
|
# which for era > 1 can come from either this era's own live
|
|
132
|
-
# writes
|
|
207
|
+
# writes or an untouched-this-era id whose value still comes
|
|
133
208
|
# from the translated ancestor tail. Era 1 has no ancestor tail
|
|
134
|
-
# (the snapshot table
|
|
135
|
-
#
|
|
209
|
+
# (the snapshot table is the head, verbatim); era N reduces the
|
|
210
|
+
# same two sources `compile_head!`'s own final view definition
|
|
136
211
|
# unions (head_compiler.rb) — this is deliberately the identical
|
|
137
212
|
# shape, ordinal kept in the SELECT list instead of dropped,
|
|
138
|
-
# because a backfilled cache row needs a
|
|
213
|
+
# because a backfilled cache row needs a real ordinal to guard
|
|
139
214
|
# against a concurrent live write the same way every other
|
|
140
215
|
# upsert in this adapter already does. Ordinals are safe to
|
|
141
|
-
# compare across that union because they come from
|
|
216
|
+
# compare across that union because they come from one spanning
|
|
142
217
|
# sequence per domain (see lineage.rb's own header comment) —
|
|
143
218
|
# an ancestor-era ordinal is always numerically smaller than any
|
|
144
219
|
# current-era one, so the ordinary `ordinal <` guard already
|
|
@@ -157,7 +232,7 @@ module Hecks
|
|
|
157
232
|
"SELECT ordinal, aggregate_id AS id, state FROM #{quote(view)} WHERE operation = 'save'"
|
|
158
233
|
else
|
|
159
234
|
# No compiled ancestor matview yet (e.g. this era was just
|
|
160
|
-
# minted and compile_head! hasn't run for
|
|
235
|
+
# minted and compile_head! hasn't run for this aggregate) —
|
|
161
236
|
# the live snapshot side alone is the whole truth so far.
|
|
162
237
|
"SELECT NULL::bigint AS ordinal, NULL::text AS id, NULL::jsonb AS state WHERE FALSE"
|
|
163
238
|
end
|