hecks 2.8.0 → 2.9.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/exe/hecks +4 -6
- data/lib/hecks/adapters/driven/claude_code.rb +4 -104
- data/lib/hecks/adapters/driven/d1.rb +44 -237
- data/lib/hecks/adapters/driven/folder.rb +17 -76
- data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
- data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
- data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
- data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
- data/lib/hecks/adapters/driven/heki.rb +13 -42
- data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
- data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
- data/lib/hecks/adapters/driven/lambda.rb +10 -84
- data/lib/hecks/adapters/driven/local_storage.rb +5 -40
- data/lib/hecks/adapters/driven/memory.rb +17 -98
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
- data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
- data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
- data/lib/hecks/adapters/driven/postgres.rb +145 -134
- data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
- data/lib/hecks/adapters/driven/prism.rb +7 -30
- data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
- data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
- data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
- data/lib/hecks/adapters/driven/sqlite.rb +128 -65
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +5 -48
- data/lib/hecks/adapters/driven.rb +5 -36
- data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
- data/lib/hecks/behaviors/dsl.rb +7 -25
- data/lib/hecks/behaviors/expectations.rb +31 -141
- data/lib/hecks/behaviors/ir.rb +7 -20
- data/lib/hecks/behaviors/rspec.rb +6 -17
- data/lib/hecks/behaviors/runner.rb +10 -34
- data/lib/hecks/behaviors.rb +4 -8
- data/lib/hecks/bluebook/aggregate.rb +9 -75
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
- data/lib/hecks/bluebook/assembly/build.rb +4 -15
- data/lib/hecks/bluebook/assembly/contract.rb +15 -136
- data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
- data/lib/hecks/bluebook/assembly/marks.rb +31 -223
- data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
- data/lib/hecks/bluebook/assembly.rb +6 -45
- data/lib/hecks/bluebook/attribute.rb +12 -61
- data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
- data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
- data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
- data/lib/hecks/bluebook/behaviour/command.rb +14 -66
- data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
- data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
- data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
- data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
- data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
- data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
- data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
- data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
- data/lib/hecks/bluebook/capabilities.rb +17 -49
- data/lib/hecks/bluebook/chapter.rb +10 -48
- data/lib/hecks/bluebook/command.rb +23 -81
- data/lib/hecks/bluebook/domain_port.rb +14 -63
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
- data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
- data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -194
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
- data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
- data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
- data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
- data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
- data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
- data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
- data/lib/hecks/bluebook/dsl/world_builder.rb +19 -111
- data/lib/hecks/bluebook/entity.rb +13 -71
- data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
- data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
- data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
- data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
- data/lib/hecks/bluebook/expression/resolver.rb +37 -660
- data/lib/hecks/bluebook/hexagon.rb +5 -28
- data/lib/hecks/bluebook/lifecycle.rb +5 -16
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
- data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
- data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
- data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
- data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +43 -305
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
- data/lib/hecks/bluebook/meta_validator.rb +79 -349
- data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
- data/lib/hecks/bluebook/model_check.rb +100 -597
- data/lib/hecks/bluebook/pattern_subset.rb +11 -69
- data/lib/hecks/bluebook/process_manager.rb +13 -83
- data/lib/hecks/bluebook/project_loader.rb +3 -39
- data/lib/hecks/bluebook/project_register.rb +12 -60
- data/lib/hecks/bluebook/query.rb +9 -48
- data/lib/hecks/bluebook/read_model.rb +57 -81
- data/lib/hecks/bluebook/reference.rb +7 -41
- data/lib/hecks/bluebook/smoke_test.rb +8 -98
- data/lib/hecks/bluebook/synthesizer.rb +13 -62
- data/lib/hecks/bluebook/translation.rb +26 -62
- data/lib/hecks/bluebook/value_object.rb +11 -36
- data/lib/hecks/bluebook.rb +5 -22
- data/lib/hecks/cache_dir.rb +4 -23
- data/lib/hecks/cli/document.rb +3 -21
- data/lib/hecks/cli/ir.rb +3 -5
- data/lib/hecks/cli/mcp.rb +2 -3
- data/lib/hecks/cli/mcp_door.rb +19 -114
- data/lib/hecks/cli/model_check.rb +18 -85
- data/lib/hecks/cli/project_cli.rb +7 -40
- data/lib/hecks/cli/project_diagrams.rb +0 -7
- data/lib/hecks/cli/run.rb +12 -58
- data/lib/hecks/cli/smoke_test.rb +2 -16
- data/lib/hecks/cli/stores.rb +1 -2
- data/lib/hecks/cli.rb +18 -24
- data/lib/hecks/construct.rb +8 -34
- data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
- data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
- data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
- data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
- data/lib/hecks/embryonaut_bluebook.rb +4 -71
- data/lib/hecks/facade/cli_door.rb +24 -106
- data/lib/hecks/facade/cli_runner.rb +28 -172
- data/lib/hecks/facade/command_request.rb +13 -33
- data/lib/hecks/facade/handle.rb +34 -121
- data/lib/hecks/facade/json_door.rb +31 -146
- data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
- data/lib/hecks/facade/surface/chapter.rb +10 -54
- data/lib/hecks/facade/surface.rb +8 -23
- data/lib/hecks/facade.rb +10 -25
- data/lib/hecks/forms/app.rb +15 -70
- data/lib/hecks/forms/command_form_renderer.rb +10 -29
- data/lib/hecks/forms/field_renderer.rb +17 -156
- data/lib/hecks/forms/field_shape.rb +30 -222
- data/lib/hecks/forms/html.rb +12 -47
- data/lib/hecks/forms/index_renderer.rb +1 -4
- data/lib/hecks/forms/page.rb +3 -9
- data/lib/hecks/forms/params.rb +14 -60
- data/lib/hecks/forms/port_argument.rb +2 -19
- data/lib/hecks/forms/query_form_renderer.rb +4 -79
- data/lib/hecks/forms/record_renderer.rb +8 -75
- data/lib/hecks/forms/record_table.rb +7 -50
- data/lib/hecks/forms/reference_options.rb +3 -30
- data/lib/hecks/forms/value_object_shape.rb +8 -35
- data/lib/hecks/forms.rb +5 -33
- data/lib/hecks/fqn.rb +5 -12
- data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
- data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
- data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
- data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
- data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
- data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
- data/lib/hecks/framework.rb +11 -80
- data/lib/hecks/freezer.rb +5 -39
- data/lib/hecks/grammar/expression.bluebook +15 -40
- data/lib/hecks/grammar/translation.bluebook +3 -6
- data/lib/hecks/grammar.rb +16 -41
- data/lib/hecks/ir.rb +14 -83
- data/lib/hecks/language/adapter.bluebook +2 -7
- data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
- data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
- data/lib/hecks/language/bluebook/command.bluebook +27 -144
- data/lib/hecks/language/bluebook/entity.bluebook +24 -97
- data/lib/hecks/language/bluebook/policy.bluebook +16 -67
- data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
- data/lib/hecks/language/bluebook/projection.bluebook +126 -60
- data/lib/hecks/language/bluebook/query.bluebook +12 -43
- data/lib/hecks/language/bluebook/shape.bluebook +23 -73
- data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
- data/lib/hecks/language/bluebook/vocabulary.bluebook +82 -197
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
- data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
- data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
- data/lib/hecks/language/oidc.json +35 -0
- data/lib/hecks/language/port.bluebook +4 -17
- data/lib/hecks/language/translation/translation.bluebook +3 -9
- data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
- data/lib/hecks/language/world/wiring.bluebook +1 -3
- data/lib/hecks/language/world/world.bluebook +5 -7
- data/lib/hecks/literal.rb +8 -50
- data/lib/hecks/mcp_door_scope.rb +9 -90
- data/lib/hecks/mcp_stdio_guard.rb +4 -41
- data/lib/hecks/naming.rb +29 -137
- data/lib/hecks/ports/access_control.rb +7 -23
- data/lib/hecks/ports/agent/answers.rb +5 -23
- data/lib/hecks/ports/agent.rb +47 -172
- data/lib/hecks/ports/authentication.rb +12 -39
- data/lib/hecks/ports/authorization.rb +16 -43
- data/lib/hecks/ports/clock.rb +5 -45
- data/lib/hecks/ports/extraction.rb +7 -12
- data/lib/hecks/ports/identity_assignment.rb +8 -29
- data/lib/hecks/ports/identity_generation.rb +6 -29
- data/lib/hecks/ports/identity_resolution.rb +2 -10
- data/lib/hecks/ports/key_vault.rb +9 -45
- data/lib/hecks/ports/persistence/append_only.rb +61 -64
- data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
- data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
- data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
- data/lib/hecks/ports/persistence/plugin.rb +13 -42
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
- data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
- data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
- data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
- data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
- data/lib/hecks/ports/persistence/state_codec.rb +23 -103
- data/lib/hecks/ports/persistence.rb +7 -14
- data/lib/hecks/ports/projection.rb +42 -84
- data/lib/hecks/ports/query/in_memory.rb +10 -58
- data/lib/hecks/ports/query/ordering.rb +11 -32
- data/lib/hecks/ports/query.rb +14 -37
- data/lib/hecks/ports.rb +2 -8
- data/lib/hecks/projections/bootstrap_table.rb +11 -43
- data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
- data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
- data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
- data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
- data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
- data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
- data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
- data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
- data/lib/hecks/projections/deploy/fargate.rb +35 -202
- data/lib/hecks/projections/deploy/lambda.rb +150 -678
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
- data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
- data/lib/hecks/projections/deploy/preview/script.rb +2 -14
- data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
- data/lib/hecks/projections/deploy/preview/template.rb +2 -58
- data/lib/hecks/projections/deploy/preview.rb +2 -67
- data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
- data/lib/hecks/projections/deploy/scripts.rb +4 -57
- data/lib/hecks/projections/deploy/shared.rb +47 -146
- data/lib/hecks/projections/deploy/smoke.rb +4 -61
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
- data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
- data/lib/hecks/projections/deploy/template_diff.rb +5 -57
- data/lib/hecks/projections/diagrams.rb +20 -582
- data/lib/hecks/projections/glossary/html.rb +14 -130
- data/lib/hecks/projections/glossary/markdown.rb +8 -69
- data/lib/hecks/projections/glossary/mermaid.rb +9 -66
- data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
- data/lib/hecks/projections/glossary/sentences.rb +14 -159
- data/lib/hecks/projections/glossary.rb +30 -222
- data/lib/hecks/projections/ir.rb +3 -10
- data/lib/hecks/projections/model/deviations.rb +15 -42
- data/lib/hecks/projections/model.rb +12 -51
- data/lib/hecks/projections/oidc.rb +12 -56
- data/lib/hecks/projections/parser_table.rb +9 -28
- data/lib/hecks/projections/reference.rb +2 -15
- data/lib/hecks/projections/rust_vocabulary.rb +12 -250
- data/lib/hecks/projections/shape.rb +3 -29
- data/lib/hecks/projections/statements.rb +7 -86
- data/lib/hecks/projections/vocabulary.rb +6 -22
- data/lib/hecks/projections.rb +2 -16
- data/lib/hecks/projector/cli_projector.rb +60 -375
- data/lib/hecks/projector/docs_projector.rb +10 -201
- data/lib/hecks/projector/exporter.rb +42 -134
- data/lib/hecks/projector/ir_projector.rb +2 -9
- data/lib/hecks/projector/narrate_projector.rb +15 -189
- data/lib/hecks/projector/target.rb +13 -76
- data/lib/hecks/projector.rb +30 -121
- data/lib/hecks/query_specification/common/comparators.rb +5 -26
- data/lib/hecks/query_specification/common/comparison.rb +35 -143
- data/lib/hecks/query_specification/common/dsl.rb +8 -24
- data/lib/hecks/query_specification/common/null_policy.rb +20 -70
- data/lib/hecks/query_specification/common/options.rb +2 -6
- data/lib/hecks/query_specification/common/where_clause.rb +2 -10
- data/lib/hecks/query_specification/field_path.rb +37 -77
- data/lib/hecks/query_specification/hop_path.rb +12 -76
- data/lib/hecks/release/gem_pin.rb +2 -26
- data/lib/hecks/release/runner/ci_publisher.rb +3 -6
- data/lib/hecks/release/runner/commands.rb +2 -4
- data/lib/hecks/release/runner/gem_publisher.rb +2 -3
- data/lib/hecks/release/runner/npm_publisher.rb +6 -20
- data/lib/hecks/release/runner/preflight.rb +4 -19
- data/lib/hecks/release/runner/tagger.rb +2 -9
- data/lib/hecks/release/runner.rb +3 -32
- data/lib/hecks/rendering.rb +4 -26
- data/lib/hecks/router/namespace_installer.rb +12 -36
- data/lib/hecks/router.rb +4 -8
- data/lib/hecks/runtime/aggregate_lock.rb +8 -33
- data/lib/hecks/runtime/boot_gates.rb +3 -18
- data/lib/hecks/runtime/caller.rb +19 -59
- data/lib/hecks/runtime/capability_graph.rb +5 -21
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
- data/lib/hecks/runtime/command_interpreter.rb +124 -306
- data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
- data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
- data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
- data/lib/hecks/runtime/command_rules/emission.rb +8 -21
- data/lib/hecks/runtime/command_rules/references.rb +23 -202
- data/lib/hecks/runtime/dependency_planning.rb +30 -102
- data/lib/hecks/runtime/dispatcher.rb +82 -241
- data/lib/hecks/runtime/entity_element.rb +40 -462
- data/lib/hecks/runtime/entity_interpreter.rb +64 -219
- data/lib/hecks/runtime/errors.rb +32 -115
- data/lib/hecks/runtime/event.rb +3 -25
- data/lib/hecks/runtime/identity.rb +18 -154
- data/lib/hecks/runtime/instance.rb +18 -150
- data/lib/hecks/runtime/interpreting.rb +11 -70
- data/lib/hecks/runtime/invocation.rb +28 -164
- data/lib/hecks/runtime/loader.rb +48 -186
- data/lib/hecks/runtime/outbox.rb +38 -261
- data/lib/hecks/runtime/policy_interpreter.rb +38 -200
- data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
- data/lib/hecks/runtime/query_interpreter.rb +45 -187
- data/lib/hecks/runtime/reaction_invocation.rb +36 -138
- data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
- data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
- data/lib/hecks/runtime/reference_hop.rb +8 -81
- data/lib/hecks/runtime/refusal_wording.rb +8 -89
- data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
- data/lib/hecks/runtime/registry/verification.rb +52 -225
- data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
- data/lib/hecks/runtime/registry.rb +99 -244
- data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
- data/lib/hecks/runtime/routing.rb +4 -15
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
- data/lib/hecks/runtime/saga_interpreter.rb +79 -263
- data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
- data/lib/hecks/runtime/tenant_check.rb +6 -68
- data/lib/hecks/runtime/tenant_scope.rb +3 -28
- data/lib/hecks/runtime/value/admission.rb +16 -67
- data/lib/hecks/runtime/value/coercion.rb +155 -641
- data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
- data/lib/hecks/runtime/value.rb +12 -84
- data/lib/hecks/runtime.rb +17 -65
- data/lib/hecks/storehouse.rb +141 -568
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
- data/lib/hecks/vendoring/git_environment.rb +2 -8
- data/lib/hecks/vendoring/git_source.rb +4 -10
- data/lib/hecks/vendoring.rb +7 -46
- data/lib/hecks/version.rb +3 -14
- data/lib/hecks.rb +26 -81
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
- metadata +3 -2
|
@@ -5,98 +5,24 @@ module Hecks
|
|
|
5
5
|
module Adapters
|
|
6
6
|
class PostgresEra
|
|
7
7
|
class Lineage
|
|
8
|
-
# The read side of lineage: builds and maintains
|
|
9
|
-
#
|
|
10
|
-
# `backfill_head_snapshot!`) and the compiled materialized
|
|
11
|
-
# view/plain view (`compile_head!`) that translate and reduce the
|
|
12
|
-
# append-only journal into the single current-era head a query
|
|
13
|
-
# actually reads.
|
|
8
|
+
# The read side of lineage: builds and maintains the head-snapshot
|
|
9
|
+
# table and the compiled view/matview a query actually reads.
|
|
14
10
|
module HeadCompiler
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
# Creates one aggregate's head-snapshot table for an era if it is missing,
|
|
18
|
-
# brings an older table up to the current column shape, and backfills it
|
|
19
|
-
# from the journal until the backfill is recorded complete.
|
|
20
|
-
#
|
|
21
|
-
# The snapshot table backing one aggregate's current era: one row
|
|
22
|
-
# per live id, upserted transactionally by PostgresEra#append
|
|
23
|
-
# alongside the journal insert it belongs to, never derived by
|
|
24
|
-
# scanning history thereafter. Idempotent and unguarded by
|
|
25
|
-
# `provisioner?` — a plain per-aggregate table, the same
|
|
26
|
-
# privilege class as the per-aggregate views/matviews already
|
|
27
|
-
# created this way, not the shared owner-only journal.
|
|
28
|
-
#
|
|
29
|
-
# Backfilled, not just created, the first time this table comes
|
|
30
|
-
# into existence for a domain that already has journal history —
|
|
31
|
-
# a fresh era (compile_head! at mint) never has any, but era 1
|
|
32
|
-
# against an existing deployment (any domain running before this
|
|
33
|
-
# snapshot table existed at all) does, and an empty table would
|
|
34
|
-
# silently erase every already-written record from every read
|
|
35
|
-
# the instant the head view starts pointing at it.
|
|
36
|
-
#
|
|
37
|
-
# Creation and backfill are deliberately two separate steps,
|
|
38
|
-
# not one nested transaction — principle 1 (docs/implemented/postgres-era-
|
|
39
|
-
# adapter-split-plan.md): no operation may hold a lock across a
|
|
40
|
-
# scan whose duration scales with table size, and
|
|
41
|
-
# `backfill_head_snapshot!` below is a chunked, resumable
|
|
42
|
-
# scan (see `ResumableBackfill`), the opposite of something that
|
|
43
|
-
# belongs inside one transaction. Creation alone (an empty
|
|
44
|
-
# table, nothing to scan) still needs its short-held lock.
|
|
45
|
-
# Backfill runs unconditionally after —
|
|
46
|
-
# cheap and correct whether the table was just created, already
|
|
47
|
-
# fully backfilled by a prior boot (one indexed point lookup via
|
|
48
|
-
# `hecks_backfill_progress`, see ResumableBackfill), or left
|
|
49
|
-
# mid-backfill by a crashed or still-racing concurrent boot
|
|
50
|
-
# (picks up exactly where the last committed chunk left off).
|
|
51
|
-
#
|
|
52
|
-
# @param storage_name [String] the aggregate's snake-cased storage name in `era`
|
|
53
|
-
# @param era [Integer] ordinal of the era the snapshot belongs to
|
|
54
|
-
# @return [void]
|
|
55
|
-
# @raise [PG::Error] if Postgres refuses the DDL, a journal read, or an upsert
|
|
11
|
+
# Backfill runs unconditionally after create: an existing journal
|
|
12
|
+
# must never look empty just because its snapshot table is new.
|
|
56
13
|
def ensure_head_snapshot!(storage_name, era)
|
|
57
14
|
name = head_snapshot(storage_name, era)
|
|
58
15
|
unless table_exists?(name)
|
|
59
|
-
# Locked, not a bare CREATE
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# real Postgres error). Re-checked under the lock: the fast
|
|
63
|
-
# path above skips locking entirely once any boot has
|
|
64
|
-
# already finished this once, which is every boot after the
|
|
65
|
-
# first.
|
|
66
|
-
#
|
|
67
|
-
# Nestable — this runs both standalone (adapter boot, its
|
|
68
|
-
# own transaction) and from `compile_head!` while
|
|
69
|
-
# `mint_era!` is already mid-transaction (its manual
|
|
70
|
-
# `BEGIN`, held open for the era row, every aggregate's
|
|
71
|
-
# matview, and the advisory lock advance_era! relies on).
|
|
72
|
-
# `@db.transaction` is a bare BEGIN/COMMIT with no savepoint
|
|
73
|
-
# nesting (see H2 in docs/audits/2026-08-10-main-bug-
|
|
74
|
-
# audit.md) — called while already inside a transaction, its
|
|
75
|
-
# COMMIT would end that transaction early, releasing mint's
|
|
76
|
-
# advisory lock and letting a later step run uncommitted.
|
|
77
|
-
# `nested_transaction` tells the two cases apart and uses a
|
|
78
|
-
# SAVEPOINT for the second, so the surrounding mint stays
|
|
79
|
-
# one real transaction from BEGIN to its own COMMIT
|
|
80
|
-
# regardless of how deep this is called from.
|
|
16
|
+
# Locked, not a bare CREATE, so two first-time boots can't race;
|
|
17
|
+
# nested_transaction lets this run standalone or inside mint's
|
|
18
|
+
# own already-open transaction without ending it early.
|
|
81
19
|
nested_transaction("hecks_head_snapshot") do
|
|
82
20
|
@db.exec_params("SELECT pg_advisory_xact_lock(hashtext('hecks_head_snapshot:' || $1))", [name])
|
|
83
21
|
next if table_exists?(name)
|
|
84
22
|
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
# from an ancestor era. `compile_head!`'s union below would
|
|
89
|
-
# then have nothing current-era to outrank the ancestor
|
|
90
|
-
# matview's own (still-live-looking) `save` row with, so a
|
|
91
|
-
# deleted ancestor-carried record would keep winning
|
|
92
|
-
# `DISTINCT ON` forever. A delete upserts a tombstone row here
|
|
93
|
-
# instead (`operation = 'delete'`, `state` NULL) — the
|
|
94
|
-
# exact same ordinal-guarded upsert every write already
|
|
95
|
-
# uses, so it participates in the union/`DISTINCT ON`
|
|
96
|
-
# exactly like a save does, and out-ranks the ancestor row
|
|
97
|
-
# by ordinal the same way a real re-save does
|
|
98
|
-
# ("re-saves are masked correctly" — the audit's own
|
|
99
|
-
# phrasing for why that half of the read path holds).
|
|
23
|
+
# A delete upserts a tombstone (operation = 'delete', state
|
|
24
|
+
# NULL) instead of removing the row, so it still outranks a
|
|
25
|
+
# stale ancestor `save` row by ordinal under DISTINCT ON.
|
|
100
26
|
@db.exec(<<~SQL)
|
|
101
27
|
CREATE TABLE #{quote(name)} (
|
|
102
28
|
id text PRIMARY KEY,
|
|
@@ -107,38 +33,16 @@ module Hecks
|
|
|
107
33
|
SQL
|
|
108
34
|
end
|
|
109
35
|
end
|
|
110
|
-
# Self-healing for a table
|
|
111
|
-
#
|
|
112
|
-
# unconditional, runs on every boot, a no-op once the column
|
|
113
|
-
# is there. A table created before H3's fix has no
|
|
114
|
-
# `operation` column and a `state NOT NULL` constraint a
|
|
115
|
-
# tombstone's NULL state would violate; both are corrected
|
|
116
|
-
# here regardless of which branch above just ran.
|
|
36
|
+
# Self-healing for a table created before this column existed;
|
|
37
|
+
# a no-op once it and the relaxed constraint are already there.
|
|
117
38
|
@db.exec("ALTER TABLE #{quote(name)} ADD COLUMN IF NOT EXISTS operation text NOT NULL DEFAULT 'save'")
|
|
118
39
|
@db.exec("ALTER TABLE #{quote(name)} ALTER COLUMN state DROP NOT NULL")
|
|
119
40
|
backfill_head_snapshot!(name, storage_name, era)
|
|
120
41
|
end
|
|
121
42
|
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
# The reduction a live view over the raw journal would run on every
|
|
127
|
-
# single read — DISTINCT ON latest-per-id, saves only — run once,
|
|
128
|
-
# chunked through `ResumableBackfill#chunked_backfill!` instead
|
|
129
|
-
# of one blocking `INSERT ... SELECT` over the whole journal:
|
|
130
|
-
# each chunk reads the next page of distinct ids (a plain SELECT,
|
|
131
|
-
# no lock held — an ordinary reader or writer is never blocked
|
|
132
|
-
# by this running), then upserts it under the same short-held
|
|
133
|
-
# advisory lock + ordinal guard every other upsert in this
|
|
134
|
-
# adapter already uses. See `ResumableBackfill`'s own header for
|
|
135
|
-
# why this is resumable, not merely safe-to-restart.
|
|
136
|
-
#
|
|
137
|
-
# @param name [String] unquoted snapshot table name, from `head_snapshot`
|
|
138
|
-
# @param storage_name [String] the aggregate's snake-cased storage name in `era`
|
|
139
|
-
# @param era [Integer] ordinal of the era whose journal rows are read
|
|
140
|
-
# @return [void]
|
|
141
|
-
# @raise [PG::Error] if Postgres refuses a journal read or an upsert
|
|
43
|
+
# Chunked via ResumableBackfill instead of one blocking
|
|
44
|
+
# INSERT ... SELECT, so an ordinary reader or writer is never
|
|
45
|
+
# blocked while a backfill is in flight.
|
|
142
46
|
def backfill_head_snapshot!(name, storage_name, era)
|
|
143
47
|
chunked_backfill!(
|
|
144
48
|
name,
|
|
@@ -155,9 +59,8 @@ module Hecks
|
|
|
155
59
|
end,
|
|
156
60
|
upsert: lambda do |rows|
|
|
157
61
|
rows.each do |row|
|
|
158
|
-
# Always
|
|
159
|
-
#
|
|
160
|
-
# sees is a delete tombstone.
|
|
62
|
+
# Always 'save' — source_sql above already filtered to
|
|
63
|
+
# operation = 'save' rows only.
|
|
161
64
|
@db.exec_params(
|
|
162
65
|
"INSERT INTO #{quote(name)} (id, ordinal, operation, state) VALUES ($1, $2, 'save', $3) " \
|
|
163
66
|
"ON CONFLICT (id) DO UPDATE SET ordinal = EXCLUDED.ordinal, operation = EXCLUDED.operation, " \
|
|
@@ -169,40 +72,17 @@ module Hecks
|
|
|
169
72
|
)
|
|
170
73
|
end
|
|
171
74
|
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
# @param name [String] unquoted relation name
|
|
176
|
-
# @return [Boolean] true when `to_regclass` resolves the name
|
|
177
|
-
# @raise [PG::Error] if the lookup fails
|
|
75
|
+
# Matches any relation kind via to_regclass, not only a table,
|
|
76
|
+
# despite the name.
|
|
178
77
|
def table_exists?(name)
|
|
179
78
|
@db.exec_params("SELECT to_regclass($1) IS NOT NULL AS present", [name]).getvalue(0, 0) == "t"
|
|
180
79
|
end
|
|
181
80
|
|
|
182
|
-
# Runs the block
|
|
183
|
-
# idle, under a savepoint when a transaction is already open.
|
|
184
|
-
#
|
|
185
|
-
# A transaction wrapper safe to call from inside an already-open
|
|
186
|
-
# transaction, unlike `PG::Connection#transaction` (bare
|
|
187
|
-
# BEGIN/COMMIT, no savepoint nesting — see H2). Standalone
|
|
188
|
-
# (`PQTRANS_IDLE`), this is `@db.transaction` itself: a real
|
|
189
|
-
# transaction, committed or rolled back on the way out. Nested
|
|
190
|
-
# (anything else — `PQTRANS_INTRANS` from a manual `BEGIN` like
|
|
191
|
-
# mint_era!'s, or the gem's own `#transaction`), it's a SAVEPOINT
|
|
192
|
-
# instead: released on success, rolled back to (then the error
|
|
193
|
-
# re-raised) on failure, and either way the surrounding
|
|
194
|
-
# transaction is never touched — no early COMMIT, no early
|
|
195
|
-
# release of whatever advisory lock it holds.
|
|
81
|
+
# Runs the block inside a transaction, nesting via `SAVEPOINT` when one is already open.
|
|
196
82
|
#
|
|
197
|
-
#
|
|
198
|
-
#
|
|
199
|
-
#
|
|
200
|
-
# @yieldparam connection [PG::Connection] the connection, standalone only;
|
|
201
|
-
# the nested case yields nothing
|
|
202
|
-
# @return [Object] the block's value when standalone, the `PG::Result` of
|
|
203
|
-
# `RELEASE SAVEPOINT` when nested; callers here ignore it
|
|
204
|
-
# @raise [StandardError] whatever the block raises, re-raised after the
|
|
205
|
-
# transaction or the savepoint is rolled back
|
|
83
|
+
# `@db.transaction` is a bare `BEGIN`/`COMMIT` with no savepoint nesting,
|
|
84
|
+
# so calling it while already inside a transaction would commit
|
|
85
|
+
# early; this uses a `SAVEPOINT` instead whenever one is already open.
|
|
206
86
|
def nested_transaction(name, &)
|
|
207
87
|
return @db.transaction(&) if @db.transaction_status == PG::PQTRANS_IDLE
|
|
208
88
|
|
|
@@ -216,54 +96,23 @@ module Hecks
|
|
|
216
96
|
end
|
|
217
97
|
end
|
|
218
98
|
|
|
219
|
-
#
|
|
220
|
-
#
|
|
221
|
-
#
|
|
222
|
-
# Era 1: the head is the snapshot table itself, verbatim — no
|
|
223
|
-
# per-read reduction over history left to do, because `append`
|
|
224
|
-
# already keeps the snapshot current as of every write.
|
|
225
|
-
#
|
|
226
|
-
# @param storage_name [String] the aggregate's snake-cased storage name
|
|
227
|
-
# @return [void]
|
|
228
|
-
# @raise [PG::Error] if Postgres refuses the DDL or the snapshot backfill
|
|
99
|
+
# Era 1's head is the snapshot table itself, verbatim — no ancestor
|
|
100
|
+
# tail to reduce.
|
|
229
101
|
def ensure_first_head!(storage_name)
|
|
230
102
|
ensure_head_snapshot!(storage_name, 1)
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
# comment), and a tombstone's `state` is NULL: without this
|
|
235
|
-
# filter a deleted id would still resolve here, just to a nil
|
|
236
|
-
# state, instead of correctly falling out of the head at all.
|
|
103
|
+
# WHERE operation = 'save' excludes delete tombstones, whose
|
|
104
|
+
# state is NULL, so a deleted id falls out of the head instead
|
|
105
|
+
# of resolving to a nil state.
|
|
237
106
|
@db.exec(<<~SQL)
|
|
238
107
|
CREATE OR REPLACE VIEW #{quote(head_view(storage_name))} AS
|
|
239
108
|
SELECT id, state FROM #{quote(head_snapshot(storage_name, 1))} WHERE operation = 'save'
|
|
240
109
|
SQL
|
|
241
110
|
end
|
|
242
111
|
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
# the tail-merge, and the audit all reduce by
|
|
248
|
-
# DISTINCT ON (aggregate_id) ORDER BY ordinal DESC before anyone
|
|
249
|
-
# reads a state, so an entry with a newer sibling can never
|
|
250
|
-
# reach a reader. Translating those siblings computes and stores
|
|
251
|
-
# rows nothing can observe — on an append-only journal a record
|
|
252
|
-
# edited a hundred times cost a hundred translations to serve
|
|
253
|
-
# one.
|
|
254
|
-
#
|
|
255
|
-
# So the tail is reduced before the chain, not after. The
|
|
256
|
-
# translated output is identical (the reducer is idempotent and
|
|
257
|
-
# the survivor is the same row either way); only the work
|
|
258
|
-
# changes, from |journal entries| to |distinct records|.
|
|
259
|
-
#
|
|
260
|
-
# `era` must survive the reduction: each edge's `CASE` reads it to
|
|
261
|
-
# decide whether a row is old enough to need that edge applied.
|
|
262
|
-
#
|
|
263
|
-
# @param tail [String, nil] the `ancestor_tail_sql` SELECT, or an empty string
|
|
264
|
-
# (or nil) when the aggregate has no ancestor era
|
|
265
|
-
# @return [String, nil] a `SELECT DISTINCT ON (aggregate_id)` over `tail`, or
|
|
266
|
-
# `tail` itself, unchanged, when it is empty or nil
|
|
112
|
+
# Reduces the tail before chaining edges over it, not after: every
|
|
113
|
+
# reader already reduces to latest-per-id, so translating a
|
|
114
|
+
# superseded entry would be wasted work. `era` survives the
|
|
115
|
+
# reduction because each edge's case still needs it.
|
|
267
116
|
def latest_per_id(tail)
|
|
268
117
|
return tail if tail.to_s.empty?
|
|
269
118
|
|
|
@@ -271,68 +120,13 @@ module Hecks
|
|
|
271
120
|
"FROM (#{tail}) tail_entries ORDER BY aggregate_id, ordinal DESC"
|
|
272
121
|
end
|
|
273
122
|
|
|
274
|
-
# Builds
|
|
275
|
-
#
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
#
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
# The algebra it rests on, both halves load-bearing:
|
|
282
|
-
#
|
|
283
|
-
# 1. The cuts do not move. ancestor_tail_sql cuts ancestor k at
|
|
284
|
-
# W(k+1) — the watermark recorded when era k+1 was minted.
|
|
285
|
-
# Those are the same literals in the era N-1 build and the
|
|
286
|
-
# era N build, so era N-1's matview already carries exactly
|
|
287
|
-
# the cut era N needs for eras 1..N-2. Only era N-1's own
|
|
288
|
-
# rows are new, and they are cut at W(N). The watermark is
|
|
289
|
-
# still a literal baked into a definition; it is simply
|
|
290
|
-
# baked into the layer beneath. (This is why the tail-merge
|
|
291
|
-
# must pass full: — it moves every watermark at once.)
|
|
292
|
-
#
|
|
293
|
-
# 2. Reducing is associative. reduce(A ∪ B) == reduce(reduce(A) ∪ B),
|
|
294
|
-
# because the survivor is max-ordinal-per-id either way. So
|
|
295
|
-
# reducing per layer is the same answer as reducing the
|
|
296
|
-
# whole tail once.
|
|
297
|
-
#
|
|
298
|
-
# And every row on both sides of the union is already in era
|
|
299
|
-
# N-1's shape — the matview because it was chained through edge
|
|
300
|
-
# N-2, era N-1's own rows because that is the shape they were
|
|
301
|
-
# written under — so the final edge applies uniformly, with no
|
|
302
|
-
# per-era `CASE`. That equality is asserted, not argued: the spec
|
|
303
|
-
# builds a third era both ways and diffs them.
|
|
304
|
-
#
|
|
305
|
-
# `edges.size != era - 1` is a guard in its own right, not a
|
|
306
|
-
# restatement of the two before it: `names_by_era(aggregate,
|
|
307
|
-
# edges)` sizes `names[:storage]` to `edges.size + 1`, and the
|
|
308
|
-
# union in the body indexes it at `names[:storage][era - 2]` — correct
|
|
309
|
-
# only when `edges` is the full chain reaching `era` (mint's own
|
|
310
|
-
# call, and the audit's own "after" reading, both are). A caller
|
|
311
|
-
# handed a shorter chain against the same `era` — exactly what
|
|
312
|
-
# the audit's own "before" reading is, `chain[0..-2]` against the
|
|
313
|
-
# unchanged target era — would index `names[:storage]` out of
|
|
314
|
-
# its real bounds instead of falling back to `chain_sql` the way
|
|
315
|
-
# every other mismatch here already does. Guarded structurally,
|
|
316
|
-
# not by trusting every future caller to know this invariant.
|
|
317
|
-
#
|
|
318
|
-
# The guard clauses are the method: each rules out one way
|
|
319
|
-
# the layered shortcut cannot honestly apply (era too young, too
|
|
320
|
-
# few edges, a mismatched chain length, no prior era, no label,
|
|
321
|
-
# no materialized prior view) before the SQL-building tail runs.
|
|
322
|
-
# Splitting them out would separate each guard from the algebra
|
|
323
|
-
# comment it protects, and thread `held`/`prior`/`prior_view`
|
|
324
|
-
# back out as return values with no simpler shape than they
|
|
325
|
-
# already have.
|
|
326
|
-
#
|
|
327
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate as the target era declares it
|
|
328
|
-
# @param era [Integer] ordinal of the era being built
|
|
329
|
-
# @param edges [Array<Hash{Symbol => Bluebook::Translation}>] one
|
|
330
|
-
# `{ translation: }` per edge, in mint order; only the last is applied
|
|
331
|
-
# @return [String, nil] the layered SELECT, or nil when `era` is under 3, `edges`
|
|
332
|
-
# is not the full chain of `era - 1` edges, or era N-1 has no row, no label, or
|
|
333
|
-
# no existing matview
|
|
334
|
-
# @raise [Runtime::WiringError] if a held era's text fails its digest check
|
|
335
|
-
# @raise [PG::Error] if reading `hecks_eras` or the catalog fails
|
|
123
|
+
# Builds era N by layering its own last edge onto era N-1's existing
|
|
124
|
+
# matview, instead of re-deriving from raw history; returns nil
|
|
125
|
+
# (falling back to chain_sql) whenever that shortcut can't honestly
|
|
126
|
+
# apply. `edges.size != era - 1` guards names_by_era's own
|
|
127
|
+
# assumption that `edges` is the full chain reaching `era` — a
|
|
128
|
+
# shorter chain here would index names[:storage] out of bounds
|
|
129
|
+
# rather than safely falling back.
|
|
336
130
|
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
337
131
|
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
338
132
|
def layered_chain_sql(aggregate, era, edges)
|
|
@@ -371,32 +165,9 @@ module Hecks
|
|
|
371
165
|
SQL
|
|
372
166
|
end
|
|
373
167
|
|
|
374
|
-
#
|
|
375
|
-
#
|
|
376
|
-
#
|
|
377
|
-
#
|
|
378
|
-
# The compiled chain over the ancestor tail — the matview's body,
|
|
379
|
-
# also runnable as a live query (the audit previews a pending
|
|
380
|
-
# era through exactly this SQL before anything is minted).
|
|
381
|
-
#
|
|
382
|
-
# Never flatten the edges into one merged rule set, however
|
|
383
|
-
# tempting the optimization looks. The two-line counterexample:
|
|
384
|
-
# edge 1 renames A→B, edge 2 renames C→A. Flattened, a single
|
|
385
|
-
# phase order either applies C→A before A→B (aliasing C's value
|
|
386
|
-
# into B) or drops the recycled name entirely — there is no
|
|
387
|
-
# correct position for both rules in one pass. Chaining the
|
|
388
|
-
# original edges in mint order reproduces the true execution
|
|
389
|
-
# exactly and needs no such reasoning.
|
|
390
|
-
#
|
|
391
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate as the target era declares it
|
|
392
|
-
# @param era [Integer] ordinal of the era being built; ancestors are eras
|
|
393
|
-
# `1...era`
|
|
394
|
-
# @param edges [Array<Hash{Symbol => Bluebook::Translation}>] one
|
|
395
|
-
# `{ translation: }` per edge, in mint order
|
|
396
|
-
# @return [String] a `WITH tail AS (...), edge_1 AS (...), ...` SELECT yielding
|
|
397
|
-
# `ordinal, aggregate_id, operation, state`
|
|
398
|
-
# @raise [Runtime::WiringError] if a held era's text fails its digest check
|
|
399
|
-
# @raise [PG::Error] if reading `hecks_eras` fails
|
|
168
|
+
# Chains the original edges in mint order rather than flattening them
|
|
169
|
+
# into one rule set: edge 1 renaming A→B then edge 2 renaming C→A
|
|
170
|
+
# has no single phase order that applies both correctly.
|
|
400
171
|
def chain_sql(aggregate, era, edges)
|
|
401
172
|
names = names_by_era(aggregate, edges)
|
|
402
173
|
tail = latest_per_id(ancestor_tail_sql(names, era))
|
|
@@ -422,50 +193,15 @@ module Hecks
|
|
|
422
193
|
SQL
|
|
423
194
|
end
|
|
424
195
|
|
|
425
|
-
#
|
|
426
|
-
#
|
|
427
|
-
#
|
|
428
|
-
# The translated tail as it would stand in era `era`, latest
|
|
429
|
-
# entry per id, saves only — what the audit holds up against the
|
|
430
|
-
# bluebook and the edge. Reads through `head_body_sql`, the same
|
|
431
|
-
# layered-or-full choice `compile_head!` makes at real mint time
|
|
432
|
-
# (that method's own comment has the full reasoning) — so this
|
|
433
|
-
# is provably what the real mint will materialize, not a second
|
|
434
|
-
# implementation that could silently drift from it. Safe for
|
|
435
|
-
# both real callers: the "after" reading (`edges` is the full
|
|
436
|
-
# chain reaching `era`) and the "before" reading (`edges` one
|
|
437
|
-
# shorter, same `era` — `audit.rb`'s own `samples_for`/`check`
|
|
438
|
-
# callers) — `layered_chain_sql`'s own `edges.size != era - 1`
|
|
439
|
-
# guard falls back to `chain_sql` exactly when the shorter
|
|
440
|
-
# chain can't honestly answer the layered question.
|
|
441
|
-
#
|
|
442
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate as the target era declares it
|
|
443
|
-
# @param era [Integer] ordinal of the era being previewed
|
|
444
|
-
# @param edges [Array<Hash{Symbol => Bluebook::Translation}>] one
|
|
445
|
-
# `{ translation: }` per edge, in mint order; the full chain, or one shorter
|
|
446
|
-
# @return [Hash{String => Hash}] parsed state keyed by aggregate id, for every
|
|
447
|
-
# id whose latest entry is a save; `{}` when nothing survives
|
|
448
|
-
# @raise [PG::Error] if the preview query fails, such as a convert rule
|
|
449
|
-
# meeting a value it does not map
|
|
450
|
-
# @raise [Runtime::WiringError] if a held era's text fails its digest check
|
|
196
|
+
# Reads through head_body_sql — the same layered-or-full choice
|
|
197
|
+
# compile_head! makes at mint time — so a preview can't silently
|
|
198
|
+
# drift from what a real mint will materialize.
|
|
451
199
|
def translated_latest(aggregate, era, edges)
|
|
452
200
|
latest_of(head_body_sql(aggregate, era, edges))
|
|
453
201
|
end
|
|
454
202
|
|
|
455
|
-
#
|
|
456
|
-
#
|
|
457
|
-
#
|
|
458
|
-
# The untranslated ancestor tail, latest entry per id — the
|
|
459
|
-
# "before" side of every per-rule preservation check.
|
|
460
|
-
#
|
|
461
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate as the target era declares it
|
|
462
|
-
# @param era [Integer] ordinal of the target era; ancestors are eras `1...era`
|
|
463
|
-
# @param edges [Array<Hash{Symbol => Bluebook::Translation}>] the edge chain,
|
|
464
|
-
# read only for the `was:` names the aggregate carried in each ancestor era
|
|
465
|
-
# @return [Hash{String => Hash}] parsed state keyed by aggregate id, for every
|
|
466
|
-
# id whose latest entry is a save; `{}` when there is no ancestor era
|
|
467
|
-
# @raise [PG::Error] if the query fails
|
|
468
|
-
# @raise [Runtime::WiringError] if a held era's text fails its digest check
|
|
203
|
+
# The untranslated ancestor tail, latest entry per id — the "before"
|
|
204
|
+
# side of a per-rule preservation check.
|
|
469
205
|
def ancestor_latest(aggregate, era, edges)
|
|
470
206
|
names = names_by_era(aggregate, edges)
|
|
471
207
|
tail = ancestor_tail_sql(names, era)
|
|
@@ -474,15 +210,8 @@ module Hecks
|
|
|
474
210
|
latest_of("SELECT ordinal, era, aggregate_id, operation, state FROM (#{tail}) tail_rows")
|
|
475
211
|
end
|
|
476
212
|
|
|
477
|
-
#
|
|
478
|
-
# entry
|
|
479
|
-
# a delete.
|
|
480
|
-
#
|
|
481
|
-
# @param sql [String] a SELECT yielding at least `ordinal`, `aggregate_id`,
|
|
482
|
-
# `operation` and `state`
|
|
483
|
-
# @return [Hash{String => Hash}] `JSON.parse`d state keyed by aggregate id;
|
|
484
|
-
# `{}` when no id survives
|
|
485
|
-
# @raise [PG::Error] if the query fails
|
|
213
|
+
# Reduces to newest entry per aggregate id, dropping any id whose
|
|
214
|
+
# newest entry is a delete rather than returning it with a nil state.
|
|
486
215
|
def latest_of(sql)
|
|
487
216
|
rows = @db.exec(<<~SQL)
|
|
488
217
|
SELECT aggregate_id, state FROM (
|
|
@@ -493,90 +222,20 @@ module Hecks
|
|
|
493
222
|
rows.to_h { |row| [row["aggregate_id"], JSON.parse(row["state"])] }
|
|
494
223
|
end
|
|
495
224
|
|
|
496
|
-
#
|
|
497
|
-
#
|
|
498
|
-
#
|
|
499
|
-
#
|
|
500
|
-
# audit's own preview (`translated_latest`, above: both
|
|
501
|
-
# `bin/translation_audit`'s standalone run and the real
|
|
502
|
-
# mint-time gate in coverage_check.rb#audit!) reads through the
|
|
503
|
-
# identical branch selection a real mint's own `compile_head!`
|
|
504
|
-
# will use, rather than a second, hand-kept-in-sync copy of this
|
|
505
|
-
# same "layered when eligible, else full" choice. A preview that
|
|
506
|
-
# called `chain_sql` unconditionally would let the real mint
|
|
507
|
-
# (`compile_head!`, `full: false` by default), for any era >= 3,
|
|
508
|
-
# materialize via the layered path while the
|
|
509
|
-
# preview that approved it never exercised that branch at all.
|
|
510
|
-
# `full:` mirrors `compile_head!`'s own default; tail_merge's
|
|
511
|
-
# `full: true` rebuild is the one caller that ever needs the
|
|
512
|
-
# non-layered branch unconditionally.
|
|
513
|
-
#
|
|
514
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate as the target era declares it
|
|
515
|
-
# @param era [Integer] ordinal of the era being built
|
|
516
|
-
# @param edges [Array<Hash{Symbol => Bluebook::Translation}>] one
|
|
517
|
-
# `{ translation: }` per edge, in mint order
|
|
518
|
-
# @param full [Boolean] true skips the layered build and always chains from
|
|
519
|
-
# the raw journal
|
|
520
|
-
# @return [String] a SELECT yielding `ordinal, aggregate_id, operation, state`
|
|
521
|
-
# @raise [Runtime::WiringError] if a held era's text fails its digest check
|
|
522
|
-
# @raise [PG::Error] if reading `hecks_eras` or the catalog fails
|
|
225
|
+
# The one place that picks layered vs full, so a preview
|
|
226
|
+
# (translated_latest) can never approve a branch the real mint
|
|
227
|
+
# doesn't take. `full:` mirrors compile_head!'s own default;
|
|
228
|
+
# tail_merge is the one caller that forces it true.
|
|
523
229
|
def head_body_sql(aggregate, era, edges, full: false)
|
|
524
230
|
return chain_sql(aggregate, era, edges) if full
|
|
525
231
|
|
|
526
232
|
layered_chain_sql(aggregate, era, edges) || chain_sql(aggregate, era, edges)
|
|
527
233
|
end
|
|
528
234
|
|
|
529
|
-
#
|
|
530
|
-
#
|
|
531
|
-
#
|
|
532
|
-
#
|
|
533
|
-
# Era N: materialize the translated ancestor tail (edge chain as
|
|
534
|
-
# CTEs, watermarks baked in), then overlay this era's own live
|
|
535
|
-
# rows — read from its snapshot table, not re-derived from raw
|
|
536
|
-
# history — in a plain view.
|
|
537
|
-
#
|
|
538
|
-
# **The frozen-tail invariant**. This materialized view is correct
|
|
539
|
-
# by construction only because both of these hold:
|
|
540
|
-
# 1. journal rows are never updated or deleted (immutability
|
|
541
|
-
# by privilege), and
|
|
542
|
-
# 2. the watermark is baked into this definition as a literal,
|
|
543
|
-
# so post-cut ancestor writes — which do keep arriving
|
|
544
|
-
# while a fork is live — can never enter the head, even on
|
|
545
|
-
# a full `REFRESH`.
|
|
546
|
-
# The ancestor partition is append-only but not frozen. Any
|
|
547
|
-
# future "optimization" that refreshes incrementally, reads the
|
|
548
|
-
# watermark from hecks_eras at query time, or otherwise
|
|
549
|
-
# re-derives the cut will silently leak the old world's post-cut
|
|
550
|
-
# writes into the new head. Rebuilding the definition (mint,
|
|
551
|
-
# merge) is the only way the cut may move.
|
|
552
|
-
#
|
|
553
|
-
# `full:` forces a rebuild from the raw journal. The tail-merge
|
|
554
|
-
# needs it: it moves every watermark to the new tip, so every
|
|
555
|
-
# ancestor matview's cut goes stale in the same statement, and
|
|
556
|
-
# layering on one would carry a cut that does not exist.
|
|
557
|
-
#
|
|
558
|
-
# The live half reads the snapshot table, not `WHERE era = era AND
|
|
559
|
-
# aggregate = name` over the raw journal — that would be a
|
|
560
|
-
# DISTINCT ON re-reducing this era's
|
|
561
|
-
# entire write history on every single read, the exact cost the
|
|
562
|
-
# snapshot table exists to avoid (PostgresEra#append keeps it
|
|
563
|
-
# current, transactionally, as of every write). The head view's union
|
|
564
|
-
# is bounded by live record count for this era instead of its
|
|
565
|
-
# write count; the ancestor side is bounded the same way
|
|
566
|
-
# (the matview only ever holds the reduced tail, never raw
|
|
567
|
-
# history — see latest_per_id's own comment).
|
|
568
|
-
#
|
|
569
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate as era `era` declares it
|
|
570
|
-
# @param era [Integer] ordinal of the era whose head is built, 2 or later
|
|
571
|
-
# @param label [String] the era's minted label, part of the matview's name
|
|
572
|
-
# @param edges [Array<Hash{Symbol => Bluebook::Translation}>] one
|
|
573
|
-
# `{ translation: }` per edge, in mint order, reaching `era`
|
|
574
|
-
# @param full [Boolean] true builds the matview from the raw journal rather
|
|
575
|
-
# than layering on era N-1's matview; the tail-merge passes it
|
|
576
|
-
# @return [void]
|
|
577
|
-
# @raise [PG::Error] if Postgres refuses a statement, including a matview of
|
|
578
|
-
# that name already existing or a convert rule meeting an unmapped value
|
|
579
|
-
# @raise [Runtime::WiringError] if a held era's text fails its digest check
|
|
235
|
+
# The matview bakes each ancestor's watermark in as a literal;
|
|
236
|
+
# refreshing it incrementally or re-deriving the cut at query time
|
|
237
|
+
# would leak post-cut ancestor writes into the head. Only rebuilding
|
|
238
|
+
# the definition (mint, merge) may move the cut.
|
|
580
239
|
def compile_head!(aggregate, era, label, edges, full: false)
|
|
581
240
|
storage_name = aggregate.storage_name
|
|
582
241
|
view = matview(storage_name, era, label)
|
|
@@ -585,38 +244,13 @@ module Hecks
|
|
|
585
244
|
CREATE MATERIALIZED VIEW #{quote(view)} AS
|
|
586
245
|
#{body}
|
|
587
246
|
SQL
|
|
588
|
-
# Additive, changes nothing about what can be pushed through
|
|
589
|
-
# the reduction (that wall is structural, an index doesn't
|
|
590
|
-
# move it — see this file's own module header) — only speeds
|
|
591
|
-
# up the reduction itself, which every read still has to run
|
|
592
|
-
# regardless of a field cache's own two-phase shortcut (a
|
|
593
|
-
# multi-clause query with an uncached clause, an order_by-only
|
|
594
|
-
# query, or simply every read before Track C's cache tables
|
|
595
|
-
# exist for a given field all still hit this). Worth having
|
|
596
|
-
# on any supported server; genuinely skip-scan-usable once
|
|
597
|
-
# running on PG18+.
|
|
598
247
|
@db.exec("CREATE INDEX IF NOT EXISTS #{quote("#{view}_reduce_idx")} ON #{quote(view)} (aggregate_id, ordinal DESC)")
|
|
599
248
|
|
|
600
|
-
# era-qualified, always a fresh table for a newly-minted era —
|
|
601
|
-
# no separate reset step needed; there is structurally nothing
|
|
602
|
-
# in it yet for anyone to have written, until an append lands
|
|
603
|
-
# under this era specifically.
|
|
604
249
|
ensure_head_snapshot!(storage_name, era)
|
|
605
250
|
@db.exec("DROP VIEW IF EXISTS #{quote(head_view(storage_name))}")
|
|
606
|
-
#
|
|
607
|
-
#
|
|
608
|
-
#
|
|
609
|
-
# delete simply removes its row from the snapshot table. For
|
|
610
|
-
# an id carried in from an ancestor era, a removed row leaves the
|
|
611
|
-
# ancestor matview's own `save` row as the only row on either
|
|
612
|
-
# side of this union — which then wins `DISTINCT ON` and
|
|
613
|
-
# `WHERE operation = 'save'` lets it straight through, so a
|
|
614
|
-
# deleted ancestor-carried record keeps resurrecting forever.
|
|
615
|
-
# The snapshot side upserts a real tombstone row on
|
|
616
|
-
# delete (`ensure_head_snapshot!`'s own comment) instead of
|
|
617
|
-
# deleting the row, so reading its actual `operation` here
|
|
618
|
-
# lets that tombstone outrank the stale ancestor `save` row
|
|
619
|
-
# by ordinal, exactly the way a genuine re-save does.
|
|
251
|
+
# Reads the snapshot's own operation column rather than hardcoding
|
|
252
|
+
# 'save', so a tombstone here outranks a stale ancestor save row
|
|
253
|
+
# by ordinal instead of letting it resurrect a deleted record.
|
|
620
254
|
@db.exec(<<~SQL)
|
|
621
255
|
CREATE VIEW #{quote(head_view(storage_name))} AS
|
|
622
256
|
SELECT id, state FROM (
|
|
@@ -630,20 +264,9 @@ module Hecks
|
|
|
630
264
|
SQL
|
|
631
265
|
end
|
|
632
266
|
|
|
633
|
-
#
|
|
634
|
-
# ancestor era's
|
|
635
|
-
#
|
|
636
|
-
# The aggregate's storage name as of each era: `was:` chains
|
|
637
|
-
# walked backward from the current name, one edge at a time.
|
|
638
|
-
# names[:current][i] is the declared (Pascal) name after edge i;
|
|
639
|
-
# names[:storage][i] its snake-cased storage name, so era e
|
|
640
|
-
# (1-based) is `names[:storage][e - 1]`.
|
|
641
|
-
#
|
|
642
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate under its current name
|
|
643
|
-
# @param edges [Array<Hash{Symbol => Bluebook::Translation}>] one
|
|
644
|
-
# `{ translation: }` per edge, in mint order
|
|
645
|
-
# @return [Hash{Symbol => Array<String>}] `:current` and `:storage`, each of
|
|
646
|
-
# `edges.size + 1` names, index 0 being era 1
|
|
267
|
+
# Walks `was:` chains backward from the current name, so each
|
|
268
|
+
# ancestor era's rows can be found under the name of their time.
|
|
269
|
+
# names[:storage][e - 1] is the storage name for era e (1-based).
|
|
647
270
|
def names_by_era(aggregate, edges)
|
|
648
271
|
current = Array.new(edges.size + 1)
|
|
649
272
|
current[edges.size] = aggregate.name
|
|
@@ -655,20 +278,8 @@ module Hecks
|
|
|
655
278
|
{ current: current, storage: storage }
|
|
656
279
|
end
|
|
657
280
|
|
|
658
|
-
#
|
|
659
|
-
#
|
|
660
|
-
#
|
|
661
|
-
# Rows this aggregate contributed to every ancestor era, under
|
|
662
|
-
# the name it had then, cut at the watermark recorded when the
|
|
663
|
-
# next era was minted.
|
|
664
|
-
#
|
|
665
|
-
# @param names [Hash{Symbol => Array<String>}] the `names_by_era` result; only
|
|
666
|
-
# `:storage` is read
|
|
667
|
-
# @param era [Integer] ordinal of the target era; ancestors are eras `1...era`
|
|
668
|
-
# @return [String] the unioned SELECT, or `""` when `era` is 1; an ancestor
|
|
669
|
-
# whose successor has no recorded watermark is read uncut
|
|
670
|
-
# @raise [Runtime::WiringError] if a held era's text fails its digest check
|
|
671
|
-
# @raise [PG::Error] if reading `hecks_eras` fails
|
|
281
|
+
# Cuts each ancestor era's rows at the watermark recorded when its
|
|
282
|
+
# successor was minted, so later writes to the old world don't leak in.
|
|
672
283
|
def ancestor_tail_sql(names, era)
|
|
673
284
|
watermarks = eras.to_h { |held| [held[:ordinal], held[:watermark]] }
|
|
674
285
|
selects = (1...era).map do |ancestor|
|
|
@@ -680,28 +291,16 @@ module Hecks
|
|
|
680
291
|
selects.join(" UNION ALL ")
|
|
681
292
|
end
|
|
682
293
|
|
|
683
|
-
#
|
|
684
|
-
#
|
|
685
|
-
#
|
|
686
|
-
# watermark, no era chain. Separate so Exporter's build-time
|
|
687
|
-
# SQL export (feeding rust/host's own boot-time mint) can call
|
|
688
|
-
# the exact same code this file's own callers do, rather than
|
|
689
|
-
# a hand-ported duplicate. See that module's own header.
|
|
294
|
+
# The rule-compiling helpers live in Translation::RuleCompiler instead,
|
|
295
|
+
# with no database connection, so Exporter's build-time SQL export can
|
|
296
|
+
# call the exact same code these callers do.
|
|
690
297
|
|
|
691
|
-
#
|
|
692
|
-
#
|
|
693
|
-
#
|
|
694
|
-
# @param name [String] unquoted relation name
|
|
695
|
-
# @return [Boolean] true when a visible `pg_class` row of kind `v` or `m` has
|
|
696
|
-
# that name
|
|
697
|
-
# @raise [PG::Error] if the catalog lookup fails
|
|
298
|
+
# Matches a view or materialized view only; a table of the same
|
|
299
|
+
# name does not count.
|
|
698
300
|
def view_exists?(name)
|
|
699
|
-
#
|
|
700
|
-
#
|
|
701
|
-
#
|
|
702
|
-
# the name the same way search_path would, or a sibling
|
|
703
|
-
# domain's same-named view satisfies a check meant for this
|
|
704
|
-
# domain's own.
|
|
301
|
+
# pg_table_is_visible, not information_schema, so this resolves
|
|
302
|
+
# the name the same way search_path would rather than matching a
|
|
303
|
+
# sibling domain's same-named view.
|
|
705
304
|
@db.exec_params(
|
|
706
305
|
"SELECT 1 FROM pg_class WHERE relname = $1 AND relkind IN ('v', 'm') " \
|
|
707
306
|
"AND pg_table_is_visible(oid)",
|