hecks 2.7.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 +11 -0
- 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 +18 -99
- 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 +15 -37
- 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 +32 -142
- 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 +17 -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 +38 -26
- 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 +17 -65
- 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 -189
- 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 -106
- 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 +54 -268
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
- 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 +10 -91
- data/lib/hecks/bluebook/model_check.rb +102 -600
- 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 +84 -0
- data/lib/hecks/cli/document.rb +86 -0
- data/lib/hecks/cli/ir.rb +45 -0
- data/lib/hecks/cli/mcp.rb +31 -0
- data/lib/hecks/cli/mcp_door.rb +366 -0
- data/lib/hecks/cli/model_check.rb +203 -0
- data/lib/hecks/cli/project_cli.rb +90 -0
- data/lib/hecks/cli/project_diagrams.rb +36 -0
- data/lib/hecks/cli/run.rb +173 -0
- data/lib/hecks/cli/smoke_test.rb +45 -0
- data/lib/hecks/cli/stores.rb +53 -0
- data/lib/hecks/cli.rb +160 -0
- 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 +98 -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 +95 -0
- 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 +62 -65
- 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 +7 -26
- 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 +22 -122
- 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 +151 -679
- 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 +9 -16
- 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 +46 -138
- 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 +93 -0
- data/lib/hecks/release/runner/commands.rb +66 -0
- data/lib/hecks/release/runner/console.rb +52 -0
- data/lib/hecks/release/runner/gem_publisher.rb +43 -0
- data/lib/hecks/release/runner/git.rb +48 -0
- data/lib/hecks/release/runner/npm_publisher.rb +75 -0
- data/lib/hecks/release/runner/preflight.rb +104 -0
- data/lib/hecks/release/runner/published.rb +55 -0
- data/lib/hecks/release/runner/tagger.rb +80 -0
- data/lib/hecks/release/runner.rb +215 -0
- 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 +125 -307
- 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 +65 -220
- 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 +139 -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 +103 -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 +146 -567
- 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/vocabulary.rb +5 -0
- data/lib/hecks.rb +32 -82
- 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 +30 -59
- data/lib/hecks/bench/cli.rb +0 -74
- data/lib/hecks/bench/environment.rb +0 -100
- data/lib/hecks/bench/postgres_probe.rb +0 -41
- data/lib/hecks/bench/report.rb +0 -116
- data/lib/hecks/bench/ruby_runner.rb +0 -151
- data/lib/hecks/bench/run.rb +0 -55
- data/lib/hecks/bench/rust_runner.rb +0 -165
- data/lib/hecks/bench/stats.rb +0 -75
- data/lib/hecks/bench/suite.rb +0 -172
- data/lib/hecks/bench/workload.rb +0 -137
- data/lib/hecks/bench.rb +0 -28
- data/lib/hecks/codemod.rb +0 -443
- data/lib/hecks/corpus.rb +0 -479
- data/lib/hecks/doc/reference.rb +0 -585
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
- data/lib/hecks/fuzzing/combination_miner.rb +0 -178
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
- data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
- data/lib/hecks/fuzzing/differential.rb +0 -192
- data/lib/hecks/fuzzing/domain_generator.rb +0 -871
- data/lib/hecks/fuzzing/era_boundary.rb +0 -154
- data/lib/hecks/fuzzing/form_census.rb +0 -285
- data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
- data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
- data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
- data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
- data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
- data/lib/hecks/fuzzing/properties/guards.rb +0 -398
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
- data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
- data/lib/hecks/fuzzing/properties/querying.rb +0 -260
- data/lib/hecks/fuzzing/properties.rb +0 -267
- data/lib/hecks/fuzzing/qa_settings.rb +0 -164
- data/lib/hecks/fuzzing/replay.rb +0 -930
- data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
- data/lib/hecks/fuzzing/self_consistency.rb +0 -825
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
- data/lib/hecks/fuzzing/shrinker.rb +0 -309
- data/lib/hecks/fuzzing/structural_skips.rb +0 -53
- data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
- data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
- data/lib/hecks/fuzzing/value_generator.rb +0 -340
- data/lib/hecks/fuzzing.rb +0 -23
- data/lib/hecks/grammar/evolve.rb +0 -524
- data/lib/hecks/query_ir.rb +0 -482
|
@@ -2,67 +2,12 @@ module Hecks
|
|
|
2
2
|
module Adapters
|
|
3
3
|
class PostgresEra
|
|
4
4
|
class Lineage
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# every field-cache table's own initial backfill. Governing
|
|
8
|
-
# principle 1
|
|
9
|
-
# (docs/implemented/postgres-era-adapter-split-plan.md): no
|
|
10
|
-
# operation this plan touches may hold a lock across a scan whose
|
|
11
|
-
# duration scales with table size — a single `INSERT ... SELECT`
|
|
12
|
-
# over the whole journal (the one-shot form of
|
|
13
|
-
# `backfill_head_snapshot!`) is exactly that, and so is a naive
|
|
14
|
-
# "populate every cache row in one statement" field-cache backfill.
|
|
15
|
-
#
|
|
16
|
-
# ## The shape
|
|
17
|
-
#
|
|
18
|
-
# Read one bounded chunk (real rows, real ordinals) with
|
|
19
|
-
# a plain SELECT — no lock held across it, so an ordinary reader or
|
|
20
|
-
# writer is never blocked by a backfill in progress — then upsert
|
|
21
|
-
# that chunk under the same transactionally-scoped advisory lock +
|
|
22
|
-
# ordinal-guard idiom `append`'s own snapshot upsert already uses
|
|
23
|
-
# (`WHERE ordinal < EXCLUDED.ordinal`), then persist a cursor
|
|
24
|
-
# before moving to the next chunk. Repeat until a chunk reads back
|
|
25
|
-
# short of a full page — that page was the last one.
|
|
26
|
-
#
|
|
27
|
-
# ## Resumable, not merely restartable
|
|
28
|
-
#
|
|
29
|
-
# A crash (or a second
|
|
30
|
-
# concurrent boot) mid-backfill leaves the cursor exactly where the
|
|
31
|
-
# last committed chunk left it — `hecks_backfill_progress` is
|
|
32
|
-
# updated in the same transaction as the chunk's own upsert, so
|
|
33
|
-
# cursor and data can never observably disagree (see
|
|
34
|
-
# `run_chunk!`). The next attempt reads that cursor and continues;
|
|
35
|
-
# it does not rescan what a prior attempt already committed.
|
|
36
|
-
# Restartable would also be correct here (every upsert is
|
|
37
|
-
# idempotent and ordinal-guarded — rerunning an already-done chunk
|
|
38
|
-
# from id 1 changes nothing) but wastes real work on a large
|
|
39
|
-
# table; resumability is what keeps a crash near the end of a
|
|
40
|
-
# large backfill cheap to recover from instead of starting over.
|
|
41
|
-
#
|
|
42
|
-
# ## The lock key
|
|
43
|
-
#
|
|
44
|
-
# The lock key prefix is `hecks_field_cache:` — deliberately
|
|
45
|
-
# disjoint from the three families already in use elsewhere in
|
|
46
|
-
# this adapter (`hecks_ordinal:`, `hecks_eras:`,
|
|
47
|
-
# `hecks_head_snapshot:` — see lineage.rb/head_compiler.rb/
|
|
48
|
-
# mint_transaction.rb/tail_merge.rb) so a backfill chunk never
|
|
49
|
-
# contends with a plain write, a mint, or a snapshot-table's own
|
|
50
|
-
# first-creation lock. It is held for exactly one chunk's own
|
|
51
|
-
# transaction, never across the whole backfill — two concurrent
|
|
52
|
-
# backfillers of the same target simply take turns one chunk at a
|
|
53
|
-
# time rather than racing to duplicate work; neither blocks an
|
|
54
|
-
# unrelated reader or writer for even an instant.
|
|
5
|
+
# Chunked, lock-free, resumable backfill loop shared by the head-snapshot backfill and each
|
|
6
|
+
# field-cache table's initial backfill.
|
|
55
7
|
module ResumableBackfill
|
|
56
8
|
CHUNK_SIZE = 5_000
|
|
57
9
|
|
|
58
|
-
# Creates `hecks_backfill_progress`, the
|
|
59
|
-
#
|
|
60
|
-
# Idempotent, unguarded — same idiom as every other DDL helper
|
|
61
|
-
# in this file tree (`ensure_head_snapshot!` et al.): cheap,
|
|
62
|
-
# runs on every boot, only ever does real work once.
|
|
63
|
-
#
|
|
64
|
-
# @return [void]
|
|
65
|
-
# @raise [PG::Error] if Postgres refuses the DDL
|
|
10
|
+
# Creates `hecks_backfill_progress`, the per-target cursor table, if absent.
|
|
66
11
|
def ensure_backfill_progress_table!
|
|
67
12
|
@db.exec(<<~SQL)
|
|
68
13
|
CREATE TABLE IF NOT EXISTS hecks_backfill_progress (
|
|
@@ -76,40 +21,11 @@ module Hecks
|
|
|
76
21
|
|
|
77
22
|
# Fills `target` one committed chunk at a time, resuming from its stored cursor.
|
|
78
23
|
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
# not one — a head-snapshot row and a field-cache row carry
|
|
83
|
-
# different columns (`state` jsonb vs. a single extracted
|
|
84
|
-
# `value`), so there is no one generic "upsert this row" shape
|
|
85
|
-
# to share; only the loop, the lock, and the cursor are generic.
|
|
86
|
-
#
|
|
87
|
-
# source_sql.call(cursor) — given the last-processed id (nil
|
|
88
|
-
# before the first chunk), returns a SQL SELECT whose result
|
|
89
|
-
# has an `id` column (text, ordered ascending) plus whatever
|
|
90
|
-
# other columns `upsert` below needs. Must read `id >
|
|
91
|
-
# cursor` (or unconditional when cursor is nil), `ORDER BY
|
|
92
|
-
# id`, `LIMIT CHUNK_SIZE` — the caller owns the actual
|
|
93
|
-
# column list/source tables; this method only owns the loop,
|
|
94
|
-
# the lock, and the cursor.
|
|
95
|
-
#
|
|
96
|
-
# upsert.call(rows) — given the PG::Result of one chunk's
|
|
97
|
-
# read, performs the actual guarded upsert into `target` and
|
|
98
|
-
# returns nothing meaningful; runs inside the same
|
|
99
|
-
# transaction/advisory-lock scope as the cursor update below,
|
|
100
|
-
# so a crash between "wrote the chunk" and "advanced the
|
|
101
|
-
# cursor" is impossible — they commit together or not at
|
|
102
|
-
# all.
|
|
24
|
+
# `source_sql` takes the last id (nil before the first chunk) and returns a SELECT
|
|
25
|
+
# with an ascending text `id` column, `id > cursor`, `LIMIT CHUNK_SIZE`. `upsert`
|
|
26
|
+
# takes the chunk's PG::Result and runs in the cursor update's transaction.
|
|
103
27
|
#
|
|
104
|
-
# @
|
|
105
|
-
# and the advisory-lock key
|
|
106
|
-
# @param source_sql [#call] callable given the cursor (`String` id of the last row
|
|
107
|
-
# processed, nil before the first chunk) that returns the chunk's SELECT as a `String`
|
|
108
|
-
# @param upsert [#call] callable given one chunk's `PG::Result` that writes it into
|
|
109
|
-
# `target`; its return value is ignored
|
|
110
|
-
# @return [void]
|
|
111
|
-
# @raise [PG::Error] if Postgres refuses the progress-table DDL, a source read, or a
|
|
112
|
-
# cursor update; whatever `upsert` raises propagates too, rolling back that chunk
|
|
28
|
+
# @raise [PG::Error] on a failed read or cursor update; `upsert` errors roll back
|
|
113
29
|
def chunked_backfill!(target, source_sql:, upsert:)
|
|
114
30
|
ensure_backfill_progress_table!
|
|
115
31
|
loop do
|
|
@@ -120,21 +36,16 @@ module Hecks
|
|
|
120
36
|
|
|
121
37
|
private
|
|
122
38
|
|
|
123
|
-
# One chunk, one transaction
|
|
124
|
-
#
|
|
125
|
-
# concurrent booter may have already finished this exact chunk
|
|
126
|
-
# (or the whole backfill) while this process was waiting for the
|
|
127
|
-
# lock; without the re-read, it would redundantly reprocess a
|
|
128
|
-
# chunk another process just committed. Harmless either way
|
|
129
|
-
# (idempotent, ordinal-guarded) but the re-read is what keeps
|
|
130
|
-
# two concurrent boots from both doing the full scan instead of
|
|
131
|
-
# splitting it.
|
|
39
|
+
# One chunk, one transaction. Progress is re-read under the lock: a concurrent booter may
|
|
40
|
+
# have committed this chunk while we waited.
|
|
132
41
|
def run_chunk!(target, source_sql:, upsert:)
|
|
133
42
|
progress = backfill_progress(target)
|
|
134
43
|
return true if progress[:completed]
|
|
135
44
|
|
|
136
45
|
completed = false
|
|
137
46
|
nested_transaction("hecks_backfill_chunk") do
|
|
47
|
+
# Prefix is disjoint from the ordinal/era/head-snapshot lock keys, so a chunk
|
|
48
|
+
# never blocks a write or a mint.
|
|
138
49
|
@db.exec_params("SELECT pg_advisory_xact_lock(hashtext('hecks_field_cache:' || $1))", [target])
|
|
139
50
|
progress = backfill_progress(target)
|
|
140
51
|
if progress[:completed]
|
|
@@ -151,20 +62,14 @@ module Hecks
|
|
|
151
62
|
|
|
152
63
|
upsert.call(rows)
|
|
153
64
|
completed = rows.ntuples < CHUNK_SIZE
|
|
154
|
-
# `PG::Result#[]`
|
|
155
|
-
# a negative one (unlike a plain Ruby Array) — same trap as
|
|
156
|
-
# `backfill_progress` above, the explicit last-index form.
|
|
65
|
+
# `PG::Result#[]` rejects negative indexes, so index the last row explicitly.
|
|
157
66
|
last_cursor = rows[rows.ntuples - 1]["id"]
|
|
158
67
|
upsert_backfill_progress!(target, cursor: last_cursor, completed: completed)
|
|
159
68
|
end
|
|
160
69
|
completed
|
|
161
70
|
end
|
|
162
71
|
|
|
163
|
-
# `PG::Result#[]` raises IndexError
|
|
164
|
-
# unlike a plain Ruby Array, it does not return nil — so the
|
|
165
|
-
# entirely ordinary case of "no progress row exists yet" (every
|
|
166
|
-
# target's very first check) cannot be read via a bare `[0]`.
|
|
167
|
-
# `ntuples.zero?` first, always.
|
|
72
|
+
# `PG::Result#[]` raises IndexError out of range, so check `ntuples.zero?` before `[0]`.
|
|
168
73
|
def backfill_progress(target)
|
|
169
74
|
result = @db.exec_params(
|
|
170
75
|
"SELECT cursor, completed FROM hecks_backfill_progress WHERE target = $1", [target]
|
|
@@ -4,21 +4,12 @@ module Hecks
|
|
|
4
4
|
module Adapters
|
|
5
5
|
class PostgresEra
|
|
6
6
|
class Lineage
|
|
7
|
-
#
|
|
8
|
-
# `
|
|
9
|
-
# current era under named winners, and `diverged_count` observes how
|
|
10
|
-
# far the two worlds have drifted at any time before that.
|
|
7
|
+
# `merge_tail!` closes a fork: an old era's post-cut writes re-enter under named winners;
|
|
8
|
+
# `diverged_count` measures the drift before that.
|
|
11
9
|
module TailMerge
|
|
12
|
-
# ── fork observability ─────────────────────────────────────────
|
|
13
|
-
|
|
14
10
|
# Counts the journal rows an old era wrote after the next era's cut.
|
|
15
11
|
#
|
|
16
|
-
#
|
|
17
|
-
# — the divergence between the worlds, observable at any time.
|
|
18
|
-
#
|
|
19
|
-
# @param old_era [Integer] ordinal of the superseded era to measure
|
|
20
|
-
# @return [Integer] number of `old_era` rows past the watermark of era `old_era + 1`;
|
|
21
|
-
# 0 when the domain holds no such newer era
|
|
12
|
+
# @return [Integer] 0 when the domain holds no newer era
|
|
22
13
|
# @raise [Runtime::WiringError] if a held era's text fails its integrity check
|
|
23
14
|
def diverged_count(old_era)
|
|
24
15
|
cut = eras.find { |era| era[:ordinal] == old_era + 1 }&.dig(:watermark)
|
|
@@ -29,41 +20,18 @@ module Hecks
|
|
|
29
20
|
)[0]["count"].to_i
|
|
30
21
|
end
|
|
31
22
|
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# Folds every old era's post-cut writes into the current era's heads, under named winners.
|
|
23
|
+
# Folds every old era's post-cut writes into the current era's heads, under winners.
|
|
35
24
|
#
|
|
36
|
-
#
|
|
37
|
-
# retiring), never a shape change. One transaction: advance the
|
|
38
|
-
# watermarks, rebuild the head so the tail interleaves by its
|
|
39
|
-
# recorded global ordinal, append the declared winners, audit —
|
|
40
|
-
# and roll the whole thing back on any refusal. Records touched
|
|
41
|
-
# by both worlds since the cut refuse until each has an explicit
|
|
42
|
-
# winner; resolution itself is append-only (the winner's state
|
|
43
|
-
# re-enters as the newest row and wins structurally — originals
|
|
44
|
-
# stay immutable).
|
|
25
|
+
# One transaction; any refusal rolls back. Records both worlds touched need a winner.
|
|
45
26
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
46
|
-
# One
|
|
47
|
-
#
|
|
48
|
-
# invariant (`new_states` captured before the head rebuild lets
|
|
49
|
-
# the tail interleave). Splitting the steps into separate
|
|
50
|
-
# methods would force each to independently know how to roll
|
|
51
|
-
# back the same shared transaction, and would separate the
|
|
52
|
-
# snapshot from the mutation it must precede.
|
|
27
|
+
# One transaction with a manual `ROLLBACK` at each refusal, and `new_states` must be
|
|
28
|
+
# captured before the head rebuild; splitting the steps would scatter both.
|
|
53
29
|
#
|
|
54
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the current bluebook's aggregates
|
|
55
|
-
#
|
|
56
|
-
# @param
|
|
57
|
-
#
|
|
58
|
-
# @
|
|
59
|
-
# `"new"`; the winner's whole state is appended as the newest row
|
|
60
|
-
# @param audit [#call, nil] callable run just before `COMMIT` that returns an
|
|
61
|
-
# `Array<String>` of violations, empty to pass; nil skips the audit
|
|
62
|
-
# @return [true] always, once the merge is committed; every refusal raises instead
|
|
63
|
-
# @raise [Runtime::WiringError] if the domain stands at era 1, if an id touched by both
|
|
64
|
-
# worlds since the cut has no entry in `winners`, if `audit` reports a violation, if
|
|
65
|
-
# another mint or merge holds the domain lock for over 10s, if Postgres refuses any
|
|
66
|
-
# statement, or if a held era's text fails its integrity check
|
|
30
|
+
# @param aggregates [Array<Bluebook::Aggregate>] the current bluebook's aggregates
|
|
31
|
+
# @param edges [Array<Hash>] the mint-order edge chain, from `LineageManager.edge_chain`
|
|
32
|
+
# @param winners [Hash{String => String}] aggregate id to `"old"` or `"new"`
|
|
33
|
+
# @param audit [#call, nil] returns violations (Array<String>); run before `COMMIT`
|
|
34
|
+
# @raise [Runtime::WiringError] at era 1, an unresolved conflict, audit or lock failure
|
|
67
35
|
def merge_tail!(aggregates:, edges:, winners: {}, audit: nil)
|
|
68
36
|
@db.exec("BEGIN")
|
|
69
37
|
@db.exec("SET LOCAL lock_timeout = '10s'")
|
|
@@ -125,23 +93,14 @@ module Hecks
|
|
|
125
93
|
end
|
|
126
94
|
next unless state
|
|
127
95
|
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
# PostgresEra#append entirely (it writes through Lineage
|
|
131
|
-
# directly). Skipping the snapshot half here would mean a
|
|
132
|
-
# merge winner lands in the journal but head_view — which
|
|
133
|
-
# reads this era's live rows from the snapshot table, not
|
|
134
|
-
# by re-scanning the journal — never shows it.
|
|
96
|
+
# Journal first, snapshot second, as a live write does: this INSERT bypasses
|
|
97
|
+
# PostgresEra#append, and head_view reads live rows from the snapshot table.
|
|
135
98
|
ordinal = @db.exec_params(
|
|
136
99
|
"INSERT INTO #{quoted_journal} (era, aggregate, aggregate_id, operation, state) " \
|
|
137
100
|
"VALUES ($1, $2, $3, 'save', $4) RETURNING ordinal",
|
|
138
101
|
[era, aggregate.storage_name, id, state]
|
|
139
102
|
)[0]["ordinal"]
|
|
140
|
-
# Always
|
|
141
|
-
# the matview's own `operation = 'save'` rows or the new
|
|
142
|
-
# world's live head (`new_states`, captured from
|
|
143
|
-
# `head_view`, which is save-only by construction), so
|
|
144
|
-
# nothing reaching this INSERT is ever a delete.
|
|
103
|
+
# Always 'save': winners come from save-only sources, never a delete.
|
|
145
104
|
@db.exec_params(
|
|
146
105
|
"INSERT INTO #{quote(head_snapshot(aggregate.storage_name, era))} (id, ordinal, operation, state) " \
|
|
147
106
|
"VALUES ($1, $2, 'save', $3) " \
|
|
@@ -182,26 +141,10 @@ module Hecks
|
|
|
182
141
|
end
|
|
183
142
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
184
143
|
|
|
185
|
-
# Ids
|
|
186
|
-
# post-cut tail INTERSECTed with the new world's own writes.
|
|
144
|
+
# Ids written by both worlds since the cut, as `[storage_name, aggregate_id]` pairs.
|
|
187
145
|
#
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
# ids are the same entity, rekeyed." If a domain's history
|
|
191
|
-
# includes a rekey (see TranslationRekey) and is later
|
|
192
|
-
# merged here, a record's pre-rekey and post-rekey rows will
|
|
193
|
-
# never intersect — they just silently survive as two separate,
|
|
194
|
-
# unrelated-looking heads (a duplicate, not corruption: nothing
|
|
195
|
-
# here deletes or clobbers either side). Resolve any such
|
|
196
|
-
# duplicate manually after a merge; teaching this intersect
|
|
197
|
-
# about a rekey mapping is real, separate work, deliberately
|
|
198
|
-
# out of scope for rekey's first pass.
|
|
199
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate to check for conflicting ids
|
|
200
|
-
# @param edges [Hash] the fork's own era edges, as `names_by_era` reads them
|
|
201
|
-
# @param era [Integer] the new world's own era
|
|
202
|
-
# @param cut [Integer] the ordinal the fork happened at
|
|
203
|
-
# @return [Array<Array(String, String)>] `[storage_name, aggregate_id]` pairs touched
|
|
204
|
-
# by both worlds since the cut
|
|
146
|
+
# Compares raw ids: a rekeyed record's pre- and post-rekey rows never intersect and
|
|
147
|
+
# survive as two separate heads (a duplicate, not corruption); resolve by hand.
|
|
205
148
|
def conflict_ids(aggregate, edges, era, cut)
|
|
206
149
|
names = names_by_era(aggregate, edges)
|
|
207
150
|
olds = (1...era).map { |ancestor| text_literal(names[:storage][ancestor - 1]) }.join(", ")
|
|
@@ -2,32 +2,14 @@ module Hecks
|
|
|
2
2
|
module Adapters
|
|
3
3
|
class PostgresEra
|
|
4
4
|
class Lineage
|
|
5
|
-
# Installs the shared
|
|
6
|
-
#
|
|
7
|
-
# that a compiled translation rule set's SQL calls into — the SQL
|
|
8
|
-
# compilation target kept equal to the Ruby reference transform by
|
|
9
|
-
# the cross-execution equivalence spec, never a second source of
|
|
10
|
-
# truth.
|
|
5
|
+
# Installs the shared `hecks_tr_*` jsonb functions that compiled translation SQL calls;
|
|
6
|
+
# kept equal to the Ruby reference transform by the equivalence spec.
|
|
11
7
|
module TransformInstaller
|
|
12
|
-
# Installs or replaces the six `hecks_tr_*` functions under a database-wide
|
|
8
|
+
# Installs or replaces the six `hecks_tr_*` functions under a database-wide lock.
|
|
13
9
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# target, not a second source of truth.
|
|
18
|
-
#
|
|
19
|
-
# Locked, unlike every other statement `ensure_base!` runs — those
|
|
20
|
-
# are all `CREATE ... IF NOT EXISTS`/`ADD COLUMN IF NOT EXISTS`,
|
|
21
|
-
# which Postgres itself resolves safely under concurrent boots.
|
|
22
|
-
# `CREATE OR REPLACE FUNCTION` is not: it always rewrites the
|
|
23
|
-
# `pg_proc` row, so two sessions racing to (re)install the same
|
|
24
|
-
# function — these six are shared/global, not per-domain, so any
|
|
25
|
-
# two domains' concurrent first-boots can collide here — hit a
|
|
26
|
-
# real `PG::InternalError: tuple concurrently updated`, not a
|
|
27
|
-
# graceful no-op. `nested_transaction` is the same
|
|
28
|
-
# already-open-transaction-safe wrapper `ensure_field_cache!`
|
|
29
|
-
# uses for its own advisory lock; a fixed, domain-independent key
|
|
30
|
-
# is correct since these functions have no domain of their own.
|
|
10
|
+
# Locked because `CREATE OR REPLACE FUNCTION` rewrites the `pg_proc` row, so concurrent
|
|
11
|
+
# boots of any two domains (the functions are global) can hit
|
|
12
|
+
# `tuple concurrently updated`.
|
|
31
13
|
#
|
|
32
14
|
# @return [void]
|
|
33
15
|
# @raise [PG::Error] if Postgres refuses the lock or a function definition
|
|
@@ -38,21 +20,7 @@ module Hecks
|
|
|
38
20
|
end
|
|
39
21
|
end
|
|
40
22
|
|
|
41
|
-
# Runs the six
|
|
42
|
-
# the locked entry point.
|
|
43
|
-
#
|
|
44
|
-
# Six independent `CREATE OR REPLACE FUNCTION` statements — each
|
|
45
|
-
# self-contained SQL, no shared Ruby state, and (per this
|
|
46
|
-
# module's own header comment) safe in any install order since
|
|
47
|
-
# plpgsql bodies aren't resolved against each other until
|
|
48
|
-
# called, not at create time. Split one-per-function below
|
|
49
|
-
# purely so each has its own name and (where relevant) its own
|
|
50
|
-
# comment to sit next to, not because the six have any
|
|
51
|
-
# sequencing dependency on one another.
|
|
52
|
-
#
|
|
53
|
-
# @return [void]
|
|
54
|
-
# @raise [PG::Error] if Postgres refuses a definition, including
|
|
55
|
-
# `tuple concurrently updated` when two unlocked sessions race
|
|
23
|
+
# Runs the six definitions without a lock; `install_transforms!` is the locked entry.
|
|
56
24
|
def install_transform_functions!
|
|
57
25
|
install_hecks_tr_extract!
|
|
58
26
|
install_hecks_tr_insert!
|
|
@@ -96,16 +64,8 @@ module Hecks
|
|
|
96
64
|
SQL
|
|
97
65
|
end
|
|
98
66
|
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
# — is refused rather than silently overwritten with an empty
|
|
102
|
-
# object the moment a dotted destination needs to nest under it.
|
|
103
|
-
# Overwriting is a drop that never declared itself, the one
|
|
104
|
-
# thing this language exists to make explicit (see
|
|
105
|
-
# hecks_tr_convert's own refusal below, the same shape). The
|
|
106
|
-
# Ruby reference transform
|
|
107
|
-
# (ports/persistence/plugins/era/lineage.rb's `insert`) raises
|
|
108
|
-
# the identical wording.
|
|
67
|
+
# Refuses to nest under a non-object value (such as a reference id): overwriting it would
|
|
68
|
+
# be an undeclared drop. The Ruby reference transform's `insert` raises the same wording.
|
|
109
69
|
def install_hecks_tr_insert!
|
|
110
70
|
@db.exec(<<~SQL)
|
|
111
71
|
CREATE OR REPLACE FUNCTION hecks_tr_insert(state jsonb, path text[], value jsonb, rule_label text) RETURNS jsonb
|
|
@@ -14,86 +14,8 @@ require_relative "../../../../../naming"
|
|
|
14
14
|
module Hecks
|
|
15
15
|
module Adapters
|
|
16
16
|
class PostgresEra
|
|
17
|
-
# The lineage topology inside one Postgres database:
|
|
18
|
-
#
|
|
19
|
-
# sequence spanning partitions (total order across eras is
|
|
20
|
-
# structural); a `hecks_eras` table holding each era's frozen
|
|
21
|
-
# source text, its once-minted hash/label, and the watermark cut
|
|
22
|
-
# into its ancestor; and, per aggregate, a head derived from the
|
|
23
|
-
# journal — never a table anything rewrites.
|
|
24
|
-
#
|
|
25
|
-
# ## How a head is derived
|
|
26
|
-
#
|
|
27
|
-
# For era 1 the head is a plain view (latest save per id). From
|
|
28
|
-
# era 2 on, the ancestor tail is a MATERIALIZED view whose
|
|
29
|
-
# definition is the compiled, chained edge sequence — one CTE per
|
|
30
|
-
# original edge, in mint order, never a flattened merged rule set
|
|
31
|
-
# — with the watermark baked into the definition, so post-cut
|
|
32
|
-
# old-era writes cannot leak into the new head even on `REFRESH`.
|
|
33
|
-
# Live current-era writes overlay it through the head view.
|
|
34
|
-
#
|
|
35
|
-
# ## The current-era write fence
|
|
36
|
-
#
|
|
37
|
-
# Writing to a superseded schema drops the instant the new one
|
|
38
|
-
# materializes: one shared row policy admits INSERTs to whichever
|
|
39
|
-
# era was just established (era 1 at first hold, era N at mint),
|
|
40
|
-
# and advancing it is part of the same transaction that builds the
|
|
41
|
-
# new era's matview. There is no persisted per-role fork — any
|
|
42
|
-
# granted role, app or the table's own owner
|
|
43
|
-
# (`FORCE ROW LEVEL SECURITY` applies this to the owner too, not only
|
|
44
|
-
# ordinary roles), writes the current era or nothing, from the moment that
|
|
45
|
-
# transaction commits. A stale-era write during the narrow window
|
|
46
|
-
# before that commit — RLS is checked once, when the statement
|
|
47
|
-
# executes, never re-checked at commit, so a transaction that
|
|
48
|
-
# inserted while the old era was still current can still land
|
|
49
|
-
# after a concurrent mint has already moved the fence on — is
|
|
50
|
-
# exactly what `diverged_count`/`merge_tail!` exist to reconcile; it
|
|
51
|
-
# is the residual of an unavoidable race (ordinals are
|
|
52
|
-
# sequence-assigned, not transactional — see below), not a
|
|
53
|
-
# supported way to keep operating two schemas side by side. Only
|
|
54
|
-
# an actual Postgres superuser (or a role granted BYPASSRLS)
|
|
55
|
-
# sits above `FORCE` and keeps writing at will, forever.
|
|
56
|
-
#
|
|
57
|
-
# ## Ordinal order and commit order
|
|
58
|
-
#
|
|
59
|
-
# Lineage order is ordinal-assignment order: the ordinal comes from a
|
|
60
|
-
# sequence, and a sequence's `nextval()` is never rolled back with its
|
|
61
|
-
# transaction — accepted and documented rather than papered over.
|
|
62
|
-
#
|
|
63
|
-
# **One part of that is closed**. Left unserialized, two concurrent
|
|
64
|
-
# plain writes can call `nextval()` in one order and COMMIT in the
|
|
65
|
-
# other — nothing about a single autocommit INSERT statement stops a
|
|
66
|
-
# slower one from finishing after a faster one that started later —
|
|
67
|
-
# which makes "ordinal order" and "commit order" formally two different
|
|
68
|
-
# total orders even with no mint anywhere near either write. So
|
|
69
|
-
# `PostgresEra#append` holds
|
|
70
|
-
# `pg_advisory_xact_lock(hashtext('hecks_ordinal:' || domain))` for the
|
|
71
|
-
# length of its own transaction, a different key from `mint_era!` and
|
|
72
|
-
# `merge_tail!`'s `hecks_eras:domain` — plain writes serialize against
|
|
73
|
-
# each other only, never against a mint, and ordinal order equals
|
|
74
|
-
# commit order for them.
|
|
75
|
-
#
|
|
76
|
-
# **The other part is deliberately open**. A stale-era write during the
|
|
77
|
-
# narrow window before a mint's fence-move commits is the same race by
|
|
78
|
-
# a different name — and closing it would mean a plain write
|
|
79
|
-
# serializing against a mint, which is exactly the guarantee
|
|
80
|
-
# `spec/adapters/driven/postgres_era/lineage_spec.rb`'s "an old
|
|
81
|
-
# checkout keeps writing its own era through a mint" pins the absence
|
|
82
|
-
# of. That race stays the residual `diverged_count`/`merge_tail!`
|
|
83
|
-
# exist to reconcile, not something a plain write should ever block
|
|
84
|
-
# for.
|
|
85
|
-
#
|
|
86
|
-
# ## File layout
|
|
87
|
-
#
|
|
88
|
-
# One concern per file under lineage/: provisioning (DDL and the
|
|
89
|
-
# RLS posture), era_store (the hecks_eras rows and their integrity),
|
|
90
|
-
# mint_transaction (the one transaction that makes an era real),
|
|
91
|
-
# tail_merge (the one deliberate merge command), resumable_backfill
|
|
92
|
-
# (the one chunked/lock-free/resumable scan loop, shared by
|
|
93
|
-
# head_compiler's own backfill and field_cache's), head_compiler
|
|
94
|
-
# (the chained-edge SQL a head derives through), field_cache (the
|
|
95
|
-
# per-where-field read cache that lets a query skip the reduction
|
|
96
|
-
# entirely), transform_installer (the hecks_tr_* jsonb helpers).
|
|
17
|
+
# The lineage topology inside one Postgres database: a journal per domain, partitioned by
|
|
18
|
+
# era, with each aggregate's head derived from it rather than stored.
|
|
97
19
|
class Lineage
|
|
98
20
|
include Provisioning
|
|
99
21
|
include EraStore
|
|
@@ -106,16 +28,7 @@ module Hecks
|
|
|
106
28
|
|
|
107
29
|
JOURNAL_COLUMNS = "ordinal, era, aggregate, aggregate_id, operation, state, mirrors".freeze
|
|
108
30
|
|
|
109
|
-
#
|
|
110
|
-
# silently truncated, never refused — so two different overlong
|
|
111
|
-
# names that happen to share their first 63 bytes would collide
|
|
112
|
-
# again, at a longer length, the exact same failure mode this
|
|
113
|
-
# whole file exists to close for `storage_name` alone. Domain-
|
|
114
|
-
# qualifying every name below (`qualified_name`, private) makes
|
|
115
|
-
# that readily reachable: a long domain name stacked onto a long
|
|
116
|
-
# aggregate name. A constant, not private — Ruby constants are
|
|
117
|
-
# never actually scoped by `private`
|
|
118
|
-
# (Lint/UselessConstantScoping), so this stays above it.
|
|
31
|
+
# Longer identifiers are silently truncated by Postgres, so `qualified_name` hashes them.
|
|
119
32
|
POSTGRES_IDENTIFIER_LIMIT = 63
|
|
120
33
|
|
|
121
34
|
attr_reader :db, :domain, :formerly_known_as
|
|
@@ -154,17 +67,7 @@ module Hecks
|
|
|
154
67
|
|
|
155
68
|
# Names the view an aggregate's current state is read through.
|
|
156
69
|
#
|
|
157
|
-
# Domain-qualified
|
|
158
|
-
# `qualified_name`'s own comment and docs/decisions/0059. Unqualified,
|
|
159
|
-
# two different domains bound to PostgresEra against the same
|
|
160
|
-
# database, each declaring an aggregate whose own name snake_cases
|
|
161
|
-
# to the same storage_name (found live: two unrelated "Note"
|
|
162
|
-
# aggregates), derive the exact same
|
|
163
|
-
# `note_head`/`note_head_snapshot_1` physical relations — every
|
|
164
|
-
# boot of the second domain silently clobbers the first's
|
|
165
|
-
# already-compiled head view, `ensure_first_head!`'s own
|
|
166
|
-
# "belt-and-suspenders self-healing" being exactly the mechanism
|
|
167
|
-
# that does it (postgres_era.rb's own comment there).
|
|
70
|
+
# Domain-qualified so two domains sharing an aggregate name do not clobber each other.
|
|
168
71
|
#
|
|
169
72
|
# @param storage_name [String] the aggregate's snake-cased storage name
|
|
170
73
|
# @return [String] unquoted view name, at most `POSTGRES_IDENTIFIER_LIMIT` bytes
|
|
@@ -172,15 +75,7 @@ module Hecks
|
|
|
172
75
|
|
|
173
76
|
# Names the snapshot table that backs one aggregate's head within one era.
|
|
174
77
|
#
|
|
175
|
-
#
|
|
176
|
-
# per live id, keyed by id, carrying the ordinal it was last written
|
|
177
|
-
# at. Scoped by era, not just storage_name — an aggregate that
|
|
178
|
-
# isn't renamed across a mint keeps the same storage_name in both
|
|
179
|
-
# eras, so storage_name alone would have era N+1 sharing one
|
|
180
|
-
# physical table with era N: a freshly-minted era would inherit
|
|
181
|
-
# every pre-mint (and, worse, pre-rekey/pre-translation) row
|
|
182
|
-
# instead of starting empty. era-qualified naming is what
|
|
183
|
-
# `partition`/`matview` already do for exactly this reason.
|
|
78
|
+
# Era-scoped so a new era starts empty, not with the previous era's rows.
|
|
184
79
|
#
|
|
185
80
|
# @param storage_name [String] the aggregate's snake-cased storage name
|
|
186
81
|
# @param era [Integer] ordinal of the era the snapshot belongs to
|
|
@@ -197,19 +92,7 @@ module Hecks
|
|
|
197
92
|
|
|
198
93
|
private
|
|
199
94
|
|
|
200
|
-
#
|
|
201
|
-
# in the ordinary case (every one of these names gets read
|
|
202
|
-
# directly at a psql prompt during a live incident — see
|
|
203
|
-
# docs/decisions/0059's own verification section), degrading to a
|
|
204
|
-
# hashed, truncated form only once the readable form would
|
|
205
|
-
# actually risk exceeding `POSTGRES_IDENTIFIER_LIMIT`. The same
|
|
206
|
-
# trade `Runtime::StorageShape.mint_label` (a bare hash-prefix, no
|
|
207
|
-
# attempt at readability at all — a mint label is never meant to
|
|
208
|
-
# be legible on its own) and `FieldCache#field_cache` (fully
|
|
209
|
-
# hashed, for the same reason) already make elsewhere in this
|
|
210
|
-
# adapter — this one keeps more of the readable form than either,
|
|
211
|
-
# since unlike a mint label or a field-cache table, these names
|
|
212
|
-
# are the ones an operator reads and types by hand.
|
|
95
|
+
# Snake-cased domain folded onto `suffix`; hashed and truncated only past the length limit.
|
|
213
96
|
def qualified_name(suffix)
|
|
214
97
|
full = "#{Naming.snake(@domain)}_#{suffix}"
|
|
215
98
|
return full if full.bytesize <= POSTGRES_IDENTIFIER_LIMIT
|