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
|
@@ -5,47 +5,24 @@ module Hecks
|
|
|
5
5
|
module Adapters
|
|
6
6
|
class PostgresEra
|
|
7
7
|
class Lineage
|
|
8
|
-
# `mint_era
|
|
9
|
-
#
|
|
10
|
-
# an app role, and — last, right before commit — advances the
|
|
11
|
-
# current-era RLS fence (`advance_era!`) that drops write access to
|
|
12
|
-
# the old schema the instant it commits.
|
|
8
|
+
# `mint_era!`: writes a new era in one transaction and advances the current-era
|
|
9
|
+
# RLS fence (`advance_era!`) just before commit.
|
|
13
10
|
module MintTransaction
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# Makes a new era real, or reports that a concurrent minter already did.
|
|
17
|
-
#
|
|
18
|
-
# One transaction: the new era row (held text + minted name +
|
|
19
|
-
# cut watermark), the new partition, and every aggregate's
|
|
20
|
-
# recompiled matview + head view. The advisory lock is the
|
|
21
|
-
# writer fence — two concurrent minters serialize, and the
|
|
22
|
-
# second finds the era already held. Populating the matview
|
|
23
|
-
# inside the transaction means a convert meeting an unmapped
|
|
24
|
-
# value refuses the whole mint, loudly, before anything boots.
|
|
11
|
+
# Makes a new era real in one locked transaction, or reports a concurrent minter won.
|
|
25
12
|
#
|
|
26
13
|
# @param ordinal [Integer] ordinal of the era to mint, one past the newest held era
|
|
27
|
-
# @param hash [String] the
|
|
28
|
-
# @param label [String] the
|
|
14
|
+
# @param hash [String] the era's shape hash, SHA-256 hex
|
|
15
|
+
# @param label [String] the era's short label, a prefix of `hash`
|
|
29
16
|
# @param held_text [String] the bluebook source text to freeze as this era
|
|
30
|
-
# @param aggregates [Array<Bluebook::Aggregate>]
|
|
31
|
-
#
|
|
32
|
-
# @param
|
|
33
|
-
#
|
|
34
|
-
# @
|
|
35
|
-
#
|
|
36
|
-
# @param projection [Hash{String => Object}, nil] the bluebook's storage-shape
|
|
37
|
-
# projection, as `Runtime::StorageShape.project` returns it; nil stores none
|
|
38
|
-
# @return [Boolean] true once the era is committed; false, with everything rolled back,
|
|
39
|
-
# when the domain already holds `ordinal`
|
|
40
|
-
# @raise [Runtime::WiringError] if another mint or merge holds the domain lock for over
|
|
41
|
-
# 10s, if Postgres refuses any statement (a convert meeting an unmapped value
|
|
42
|
-
# included), or if a held era's text fails its integrity check
|
|
17
|
+
# @param aggregates [Array<Bluebook::Aggregate>] aggregates whose heads are recompiled
|
|
18
|
+
# @param edges [Array<Hash>] the full edge chain in mint order, one per step
|
|
19
|
+
# @param role [String, nil] app role to grant privileges to; nil grants nothing
|
|
20
|
+
# @param projection [Hash{String => Object}, nil] storage-shape projection, or nil
|
|
21
|
+
# @return [Boolean] true once committed; false, rolled back, if `ordinal` is held
|
|
22
|
+
# @raise [Runtime::WiringError] on a lock wait over 10s or a Postgres error
|
|
43
23
|
def mint_era!(ordinal:, hash:, label:, held_text:, aggregates:, edges:, role: nil, projection: nil)
|
|
44
24
|
@db.exec("BEGIN")
|
|
45
|
-
#
|
|
46
|
-
# rather than hanging: mint is non-interactive (the human
|
|
47
|
-
# decision already happened in the audit tool), so the lock is
|
|
48
|
-
# only ever held for the transaction itself.
|
|
25
|
+
# Mint is non-interactive, so a concurrent minter fails fast with a name, not a hang.
|
|
49
26
|
@db.exec("SET LOCAL lock_timeout = '10s'")
|
|
50
27
|
@db.exec("SELECT pg_advisory_xact_lock(hashtext('hecks_eras:' || #{text_literal(@domain)}))")
|
|
51
28
|
if eras.any? { |era| era[:ordinal] == ordinal }
|
|
@@ -63,35 +40,16 @@ module Hecks
|
|
|
63
40
|
)
|
|
64
41
|
archive_text!(ordinal, held_text)
|
|
65
42
|
ensure_partition!(ordinal)
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
# grant_role! grants on, and compile_head! is what creates it.
|
|
69
|
-
# Granting first would GRANT on a relation that does not
|
|
70
|
-
# exist yet for any aggregate renamed in this very edge.
|
|
43
|
+
# grant_role! must follow compile_head!, which creates the per-era snapshot
|
|
44
|
+
# tables that it grants on.
|
|
71
45
|
aggregates.each { |aggregate| compile_head!(aggregate, ordinal, label, edges) }
|
|
72
46
|
grant_role!(role, aggregates: aggregates, era: ordinal) if role
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
# DROP POLICY/CREATE POLICY (AccessExclusiveLock, same family
|
|
77
|
-
# as ALTER TABLE, and unavoidably so: Postgres has no lighter
|
|
78
|
-
# form for changing a policy, unlike the partition attach
|
|
79
|
-
# below) blocks every concurrent writer for as long as it sits
|
|
80
|
-
# before the expensive step. Ordered here, that block is the
|
|
81
|
-
# width of a few catalog statements plus the commit itself,
|
|
82
|
-
# not the width of compile_head!'s matview build. Measured, not
|
|
83
|
-
# assumed: placed above compile_head!, this reintroduces
|
|
84
|
-
# exactly the mint-stops-the-world cost ensure_partition!'s
|
|
85
|
-
# build-then-ATTACH exists to avoid — a cost only a genuine
|
|
86
|
-
# concurrent-write test shows.
|
|
47
|
+
# Must stay last, right before `COMMIT`: DROP/CREATE POLICY takes an
|
|
48
|
+
# AccessExclusiveLock held to commit, so placing it before compile_head!
|
|
49
|
+
# would block every writer for the whole matview build.
|
|
87
50
|
#
|
|
88
|
-
# Unconditional
|
|
89
|
-
#
|
|
90
|
-
# to be configured on this boot, because the cutoff is a fact
|
|
91
|
-
# about the era, not about who happened to mint it: an old
|
|
92
|
-
# checkout's role, granted by some earlier boot this one knows
|
|
93
|
-
# nothing about, must lose write access the instant this
|
|
94
|
-
# transaction commits.
|
|
51
|
+
# Unconditional, even with no role configured: the cutoff is a fact about the
|
|
52
|
+
# era, and a role granted by an earlier boot must lose old-era writes on commit.
|
|
95
53
|
advance_era!(ordinal)
|
|
96
54
|
@db.exec("COMMIT")
|
|
97
55
|
true
|
|
@@ -115,41 +73,16 @@ module Hecks
|
|
|
115
73
|
|
|
116
74
|
# Grants an app role what it needs to append to the journal and read and write heads.
|
|
117
75
|
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
# admits it, and owns nothing. Idempotent, and unconcerned with
|
|
121
|
-
# which era is current: that is advance_era!'s job, not this
|
|
122
|
-
# one's, so a role can be onboarded at any time without
|
|
123
|
-
# disturbing who may write what right now.
|
|
124
|
-
#
|
|
125
|
-
# Owner-only work, so a non-owner app boot skips it: the
|
|
126
|
-
# provisioner has already granted that role, and re-affirming is
|
|
127
|
-
# idempotent anyway.
|
|
128
|
-
#
|
|
129
|
-
# `aggregates:`/`era:` cover the read-cache side of the same
|
|
130
|
-
# story: unlike the journal (immutable, owner-provisioned once),
|
|
131
|
-
# each aggregate's head_snapshot table is a table an app role
|
|
132
|
-
# must itself `INSERT`/`UPDATE`/`DELETE` into — PostgresEra#append writes
|
|
133
|
-
# it directly, not through a view — so it needs real DML grants,
|
|
134
|
-
# not just the SELECT a derived read surface would need. `era:`
|
|
135
|
-
# is the ordinal this role is about to write under (the one
|
|
136
|
-
# hold_first!/mint_era! just made current, or the superseded one
|
|
137
|
-
# a stale checkout still speaks) — head_snapshot is era-scoped,
|
|
138
|
-
# so granting on the wrong era's table would grant on a table
|
|
139
|
-
# this role will never touch. Every caller passes its own
|
|
140
|
-
# bluebook's aggregates and resolved era — including a
|
|
141
|
-
# held-but-superseded checkout, since a role connecting to it
|
|
142
|
-
# for the first time (a new instance of an old checkout) has no
|
|
143
|
-
# privileges yet either.
|
|
76
|
+
# Idempotent and owner-only, so a non-owner boot skips it. Head snapshot tables are
|
|
77
|
+
# era-scoped, so `era:` names the ordinal the role is about to write under.
|
|
144
78
|
#
|
|
145
79
|
# @param role [String] name of the Postgres role to grant to
|
|
146
|
-
# @param aggregates [Array<Bluebook::Aggregate>] aggregates whose head
|
|
147
|
-
#
|
|
148
|
-
# @param era [Integer, nil]
|
|
149
|
-
#
|
|
80
|
+
# @param aggregates [Array<Bluebook::Aggregate>] aggregates whose head tables and views
|
|
81
|
+
# are granted on; ignored when `era` is nil
|
|
82
|
+
# @param era [Integer, nil] era whose snapshot tables to grant on; nil grants only the
|
|
83
|
+
# journal and sequence privileges
|
|
150
84
|
# @return [void]
|
|
151
|
-
# @raise [PG::Error] if Postgres refuses a grant
|
|
152
|
-
# snapshot table that does not exist
|
|
85
|
+
# @raise [PG::Error] if Postgres refuses a grant
|
|
153
86
|
def grant_role!(role, aggregates: [], era: nil)
|
|
154
87
|
return unless provisioner?
|
|
155
88
|
|
|
@@ -170,41 +103,15 @@ module Hecks
|
|
|
170
103
|
|
|
171
104
|
# Replaces the journal's row policies: one era accepts INSERTs, every row stays readable.
|
|
172
105
|
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
# the old schema the instant the new one materializes: the
|
|
176
|
-
# moment this commits, no role — old or new, whether or not it
|
|
177
|
-
# did anything to earn this mint — may insert anything but the
|
|
178
|
-
# era named here. There is no persisted fork: a checkout that
|
|
179
|
-
# keeps a role fenced to a stale era does not exist in this
|
|
180
|
-
# design, because there is no such thing as a role fenced to an
|
|
181
|
-
# era at all — only the one era everyone currently shares.
|
|
182
|
-
#
|
|
183
|
-
# A row policy, not a partition grant. Postgres checks INSERT
|
|
184
|
-
# privilege on the partitioned parent for a routed insert and
|
|
185
|
-
# never consults the partition's own grants, so a per-partition
|
|
186
|
-
# GRANT/REVOKE is inert in both directions: grant only on the
|
|
187
|
-
# partition and nobody can write at all; grant on the parent and
|
|
188
|
-
# they may write into every era, ancestors included. Measured,
|
|
189
|
-
# not reasoned about — see the spec, which writes through the
|
|
190
|
-
# fence rather than asserting the catalog.
|
|
191
|
-
#
|
|
192
|
-
# The table owner bypasses RLS by default, and that is load
|
|
193
|
-
# bearing: mint and merge run as the owner and must be able to
|
|
194
|
-
# write any era (the merge re-enters a winner's state into the
|
|
195
|
-
# current era, and compile_head! reads every ancestor).
|
|
106
|
+
# One policy shared by every role; call only with the new current ordinal, since a
|
|
107
|
+
# superseded one would roll the fence back. The table owner bypasses RLS.
|
|
196
108
|
#
|
|
197
|
-
#
|
|
198
|
-
#
|
|
199
|
-
# ordinal — from a boot that merely recognizes an old checkout —
|
|
200
|
-
# would roll the fence backward and silently reopen the old
|
|
201
|
-
# schema for everyone. That path grants a role's privileges
|
|
202
|
-
# (grant_role!) and stops there on purpose.
|
|
109
|
+
# Row policy rather than partition grants: Postgres checks INSERT privilege on the
|
|
110
|
+
# partitioned parent and ignores the partition's own grants.
|
|
203
111
|
#
|
|
204
112
|
# @param ordinal [Integer] ordinal of the era that becomes the only writable one
|
|
205
113
|
# @return [void]
|
|
206
|
-
# @raise [PG::Error] if Postgres refuses the policy change, as
|
|
207
|
-
# does not own the journal
|
|
114
|
+
# @raise [PG::Error] if Postgres refuses the policy change, such as for a non-owner
|
|
208
115
|
def advance_era!(ordinal)
|
|
209
116
|
@db.exec("DROP POLICY IF EXISTS hecks_current_era ON #{quoted_journal}")
|
|
210
117
|
@db.exec(
|
|
@@ -2,36 +2,13 @@ module Hecks
|
|
|
2
2
|
module Adapters
|
|
3
3
|
class PostgresEra
|
|
4
4
|
class Lineage
|
|
5
|
-
# Owner-only schema bootstrap:
|
|
6
|
-
#
|
|
7
|
-
# partitioned journal), sets up its RLS fence, bridges a renamed
|
|
8
|
-
# domain's history (`rename_domain!`), and attaches each era's
|
|
9
|
-
# journal partition (`ensure_partition!`) — the build-then-ATTACH
|
|
10
|
-
# dance that keeps a mint from stopping every writer.
|
|
5
|
+
# Owner-only schema bootstrap: lineage tables, the RLS fence, the domain rename
|
|
6
|
+
# bridge, and per-era journal partitions.
|
|
11
7
|
module Provisioning
|
|
12
|
-
# Builds or tops up this domain's lineage schema when the connected role
|
|
13
|
-
#
|
|
14
|
-
# attempts the `formerly_known_as` rename bridge, when one is declared, and
|
|
15
|
-
# leaves the schema as the owner built it.
|
|
8
|
+
# Builds or tops up this domain's lineage schema when the connected role owns the
|
|
9
|
+
# journal (or none exists yet); any other role only attempts the rename bridge.
|
|
16
10
|
#
|
|
17
|
-
#
|
|
18
|
-
# deliberately not the owner — it may append and read, and it
|
|
19
|
-
# owns nothing. By the time such a role connects, the base is
|
|
20
|
-
# already built, so its boot verifies rather than builds.
|
|
21
|
-
#
|
|
22
|
-
# Without this guard the per-era fence below is unreachable: the
|
|
23
|
-
# ALTER TABLE and REVOKE here are owner-only, so the very role
|
|
24
|
-
# grant_era! exists to constrain could never finish booting
|
|
25
|
-
# ("must be owner of table hecks_eras"). A fence nothing can
|
|
26
|
-
# reach is not a fence.
|
|
27
|
-
# One bootstrap sequence of DDL, order-dependent throughout —
|
|
28
|
-
# every guarded ALTER (RLS, REVOKE) is guarded and placed
|
|
29
|
-
# exactly where it is for measured, documented reasons (see the
|
|
30
|
-
# inline comments on each: catalog-lock avoidance, RLS timing,
|
|
31
|
-
# `FORCE` semantics). Splitting this into smaller methods would
|
|
32
|
-
# only hide that single ordered sequence behind several call
|
|
33
|
-
# sites, with nothing gained — each statement already reads as
|
|
34
|
-
# one step, and the length here is DDL, not branching logic.
|
|
11
|
+
# ALTER TABLE and REVOKE are owner-only, so a non-owner app role must skip them to boot.
|
|
35
12
|
#
|
|
36
13
|
# @return [void]
|
|
37
14
|
# @raise [Runtime::WiringError] if the `formerly_known_as` rename cannot take
|
|
@@ -55,12 +32,10 @@ module Hecks
|
|
|
55
32
|
SQL
|
|
56
33
|
@db.exec("ALTER TABLE hecks_eras ADD COLUMN IF NOT EXISTS held_digest text")
|
|
57
34
|
@db.exec("ALTER TABLE hecks_eras ADD COLUMN IF NOT EXISTS held_projection jsonb")
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
# column carry NULL, read as an implicit 1
|
|
35
|
+
# Canonical-form version that minted the name (Runtime::StorageShape::FORM_VERSION);
|
|
36
|
+
# NULL reads as 1.
|
|
61
37
|
@db.exec("ALTER TABLE hecks_eras ADD COLUMN IF NOT EXISTS canon_form int")
|
|
62
|
-
#
|
|
63
|
-
# reach it — the recovery the hard reattest refusal points at
|
|
38
|
+
# Every frozen text version, archived where an edit cannot reach it.
|
|
64
39
|
@db.exec(<<~SQL)
|
|
65
40
|
CREATE TABLE IF NOT EXISTS hecks_era_texts (
|
|
66
41
|
domain text NOT NULL,
|
|
@@ -82,10 +57,8 @@ module Hecks
|
|
|
82
57
|
)
|
|
83
58
|
SQL
|
|
84
59
|
@db.exec("CREATE SEQUENCE IF NOT EXISTS #{quote(sequence)}")
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
# sequence default is the same spanning ordinal on any
|
|
88
|
-
# supported server.
|
|
60
|
+
# An owned sequence default, not generated always as identity, which
|
|
61
|
+
# partitioned tables only support from Postgres 17.
|
|
89
62
|
@db.exec(<<~SQL)
|
|
90
63
|
CREATE TABLE IF NOT EXISTS #{quoted_journal} (
|
|
91
64
|
ordinal bigint NOT NULL DEFAULT nextval('#{sequence}'),
|
|
@@ -98,83 +71,27 @@ module Hecks
|
|
|
98
71
|
) PARTITION BY LIST (era)
|
|
99
72
|
SQL
|
|
100
73
|
ensure_partition!(1)
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
# era, at mint time.
|
|
106
|
-
#
|
|
107
|
-
# Guarded, same reasoning as the RLS ALTER TABLE calls just
|
|
108
|
-
# below: REVOKE still writes pg_class.relacl (and takes the
|
|
109
|
-
# matching lock) even when the resulting privileges are
|
|
110
|
-
# unchanged, so an unconditional reissue on every ordinary
|
|
111
|
-
# reboot races two concurrent boots into
|
|
112
|
-
# `PG::InternalError: tuple concurrently updated` — read
|
|
113
|
-
# first, touch the catalog only on the boot that actually
|
|
114
|
-
# needs to.
|
|
74
|
+
# Journal rows are immutable by privilege: nothing updates or deletes them.
|
|
75
|
+
# The REVOKE is guarded because it rewrites pg_class.relacl and takes a lock
|
|
76
|
+
# even when nothing changes, so concurrent reboots would race into
|
|
77
|
+
# `tuple concurrently updated`.
|
|
115
78
|
#
|
|
116
|
-
# `relacl IS NULL`, not
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
# `PUBLIC`), so `has_table_privilege` answers false both before
|
|
121
|
-
# the REVOKE has ever run and after it has — indistinguishable
|
|
122
|
-
# by that check alone. Guarding on it means the very first
|
|
123
|
-
# boot's own REVOKE never actually runs, `relacl` stays NULL
|
|
124
|
-
# forever, and "journal rows accept no `UPDATE`/`DELETE` from
|
|
125
|
-
# `PUBLIC`" is true only by accident of Postgres's default, not
|
|
126
|
-
# by the explicit privilege revocation this method exists to
|
|
127
|
-
# record.
|
|
128
|
-
# `relacl IS NULL` means "default ACL, nothing explicit yet" —
|
|
129
|
-
# exactly the one-time signal needed, and (like the RLS flags
|
|
130
|
-
# below) `pg_table_is_visible(oid)`, not a bare relname match,
|
|
131
|
-
# for the same shared-instance/storehouse reason.
|
|
79
|
+
# The guard is `relacl IS NULL`, not has_table_privilege('public', ..., 'UPDATE'):
|
|
80
|
+
# a new table already answers "no UPDATE", so that check would skip the first
|
|
81
|
+
# REVOKE and leave relacl NULL forever. pg_table_is_visible(oid), not a bare
|
|
82
|
+
# relname match, so a sibling schema's same-named table is never mistaken for ours.
|
|
132
83
|
relacl_null = @db.exec_params(
|
|
133
84
|
"SELECT relacl IS NULL FROM pg_class WHERE relname = $1 AND pg_table_is_visible(oid)", [journal]
|
|
134
85
|
).getvalue(0, 0)
|
|
135
86
|
@db.exec("REVOKE UPDATE, DELETE ON #{quoted_journal} FROM PUBLIC") if relacl_null == "t"
|
|
136
|
-
# RLS goes on at provisioning, never mid-life
|
|
137
|
-
#
|
|
138
|
-
# whatever the shape of the schema happened to be at that
|
|
139
|
-
# moment.
|
|
140
|
-
#
|
|
141
|
-
# `FORCE`, not merely `ENABLE`: without it, the table owner is
|
|
142
|
-
# exempt from every policy here, by Postgres default — which
|
|
143
|
-
# would leave the schema writable forever to whoever holds
|
|
144
|
-
# the owner's credentials, the one connection this whole
|
|
145
|
-
# design cannot fence. Checked, not assumed: mint_era! never
|
|
146
|
-
# inserts into the journal at all (only hecks_eras/
|
|
147
|
-
# hecks_era_texts, neither RLS-protected), and merge_tail!'s
|
|
148
|
-
# one journal INSERT targets the current era, which the fence
|
|
149
|
-
# already admits for anyone with base privileges — so `FORCE`
|
|
150
|
-
# costs the owner nothing operations here actually need.
|
|
87
|
+
# RLS goes on at provisioning, never mid-life: enabling it later would deny
|
|
88
|
+
# every role that has no policy yet.
|
|
151
89
|
#
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
# narrows what `ENABLE` already narrows for the owner
|
|
155
|
-
# specifically, and superuser bypass sits above both. Running
|
|
156
|
-
# migrations as a real superuser (self-hosted Postgres, most
|
|
157
|
-
# commonly) leaves this gap open regardless; a managed
|
|
158
|
-
# provider's admin account is typically not a superuser, and
|
|
159
|
-
# is exactly what `FORCE` closes.
|
|
90
|
+
# FORCE, not just enable, or the table owner is exempt from every policy. A
|
|
91
|
+
# superuser or BYPASSRLS role stays exempt regardless (see check_fence_applies!).
|
|
160
92
|
#
|
|
161
|
-
# Guarded
|
|
162
|
-
#
|
|
163
|
-
# takes AccessExclusiveLock even when the setting is already
|
|
164
|
-
# correct (Postgres does not skip the lock just because the
|
|
165
|
-
# statement would be a no-op). ensure_base! runs on every
|
|
166
|
-
# boot by the owning role, not only the first — so an
|
|
167
|
-
# unconditional reissue here would mean every ordinary
|
|
168
|
-
# reboot of the deployment's own identity re-freezes every
|
|
169
|
-
# concurrent writer, on any era, for as long as that
|
|
170
|
-
# ALTER TABLE has to wait its turn. Read the current state first;
|
|
171
|
-
# touch the catalog only on the boot that actually needs to.
|
|
172
|
-
# pg_table_is_visible, not a bare relname match — a shared
|
|
173
|
-
# instance (storehouse) can hold a same-named journal table
|
|
174
|
-
# per schema; catalog lookups here must resolve the same way
|
|
175
|
-
# search_path resolves an unqualified SQL reference, or a
|
|
176
|
-
# sibling domain's table satisfies a query meant for this
|
|
177
|
-
# domain's own.
|
|
93
|
+
# Guarded: enable/FORCE takes an AccessExclusiveLock even as a no-op, and this
|
|
94
|
+
# runs on every boot, so an unconditional reissue would freeze concurrent writers.
|
|
178
95
|
current = @db.exec_params(
|
|
179
96
|
"SELECT relrowsecurity, relforcerowsecurity FROM pg_class " \
|
|
180
97
|
"WHERE relname = $1 AND pg_table_is_visible(oid)", [journal]
|
|
@@ -184,48 +101,16 @@ module Hecks
|
|
|
184
101
|
install_transforms!
|
|
185
102
|
end
|
|
186
103
|
|
|
187
|
-
# Moves a
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
# **A domain's own identity changed** — bridge its history under the
|
|
191
|
-
# new name, before `provisioner?`/the CREATE TABLE IF NOT EXISTS
|
|
192
|
-
# block in `ensure_base!` ever run. That ordering is load-bearing, not
|
|
193
|
-
# tidiness: `provisioner?` and every statement in ensure_base!
|
|
194
|
-
# test for the journal under `journal` — the new name — and a
|
|
195
|
-
# freshly-renamed domain looks, to those checks, exactly like a
|
|
196
|
-
# domain that has never been provisioned at all. Called any later,
|
|
197
|
-
# ensure_base! would happily CREATE TABLE IF NOT EXISTS
|
|
198
|
-
# a brand-new, empty journal under the new name before
|
|
199
|
-
# this method ever got a chance to run — and the
|
|
200
|
-
# `ALTER TABLE ... RENAME` here would then fail, renaming onto a
|
|
201
|
-
# name that already exists.
|
|
202
|
-
#
|
|
203
|
-
# Three-way precheck, cheapest first:
|
|
204
|
-
# 1. no hecks_eras table at all yet — a genuinely fresh
|
|
205
|
-
# database; nothing to bridge, fall through to the
|
|
206
|
-
# ordinary provisioning path.
|
|
207
|
-
# 2. hecks_eras already has rows under the new name — this
|
|
208
|
-
# rename already ran (a prior boot, possibly this one on a
|
|
209
|
-
# retry); idempotent no-op.
|
|
210
|
-
# 3. hecks_eras has rows under the old name — run the
|
|
211
|
-
# migration.
|
|
212
|
-
# Anything else (no rows under either name) falls through
|
|
213
|
-
# harmlessly — `formerly_known_as` pointing at a name with no
|
|
214
|
-
# held history is not an error, just inert.
|
|
104
|
+
# Moves a domain's journal, sequence, partitions and lineage rows from
|
|
105
|
+
# `formerly_known_as` to the current name, in one transaction.
|
|
215
106
|
#
|
|
216
|
-
#
|
|
217
|
-
#
|
|
218
|
-
# (old before new, eras before ordinal)
|
|
219
|
-
# avoid a deadlock against a concurrent rename/mint/write.
|
|
220
|
-
# Splitting this would separate the precheck from the locking
|
|
221
|
-
# from the renames from the commit/rollback handling — each a
|
|
222
|
-
# piece of one transaction that must stay in this exact order
|
|
223
|
-
# or the deadlock-avoidance guarantee stops holding.
|
|
107
|
+
# Runs before `provisioner?` and the CREATE TABLE calls in `ensure_base!`; a no-op when
|
|
108
|
+
# `hecks_eras` is missing, the new name has rows, or the former name has none.
|
|
109
|
+
# Locks go in a fixed order (old before new, eras before ordinal) so nothing deadlocks.
|
|
224
110
|
#
|
|
225
111
|
# @return [void]
|
|
226
|
-
# @raise [Runtime::WiringError] if the
|
|
227
|
-
#
|
|
228
|
-
# prechecks included (`PG::Error`, message carried over); both roll back first
|
|
112
|
+
# @raise [Runtime::WiringError] if the locks are not granted within 10s or Postgres
|
|
113
|
+
# refuses a statement (prechecks included); both roll back first
|
|
229
114
|
# rubocop:disable-next Metrics/AbcSize
|
|
230
115
|
# rubocop:disable-next Metrics/MethodLength
|
|
231
116
|
def rename_domain!
|
|
@@ -245,10 +130,6 @@ module Hecks
|
|
|
245
130
|
|
|
246
131
|
@db.exec("BEGIN")
|
|
247
132
|
@db.exec("SET LOCAL lock_timeout = '10s'")
|
|
248
|
-
# Fixed order — old before new, the `eras:` family before the
|
|
249
|
-
# `ordinal:` family — so two rename attempts (or a rename
|
|
250
|
-
# racing a mint/plain-write on either name) can only ever
|
|
251
|
-
# queue behind one another, never deadlock.
|
|
252
133
|
[
|
|
253
134
|
"hecks_eras:#{@formerly_known_as}", "hecks_eras:#{@domain}",
|
|
254
135
|
"hecks_ordinal:#{@formerly_known_as}", "hecks_ordinal:#{@domain}"
|
|
@@ -257,18 +138,10 @@ module Hecks
|
|
|
257
138
|
end
|
|
258
139
|
|
|
259
140
|
@db.exec("ALTER TABLE #{quote(old_journal)} RENAME TO #{quote(journal)}")
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
# plain `CREATE SEQUENCE` the journal's `DEFAULT` merely points
|
|
263
|
-
# at (see ensure_base!), so it does need its own rename, and
|
|
264
|
-
# the column default survives untouched: Postgres stores
|
|
265
|
-
# nextval('...') as a regclass reference internally, not
|
|
266
|
-
# literal text.
|
|
141
|
+
# The sequence is a plain create sequence, not owned by the column, so it
|
|
142
|
+
# does not move with the table and needs its own rename.
|
|
267
143
|
@db.exec("ALTER SEQUENCE #{quote(old_sequence)} RENAME TO #{quote(sequence)}")
|
|
268
|
-
#
|
|
269
|
-
# child partition names — each one needs its own explicit
|
|
270
|
-
# rename, sourced from the ordinals held under the old name,
|
|
271
|
-
# captured above before the `UPDATE` below flips the column.
|
|
144
|
+
# Renaming the parent does not rename its partitions.
|
|
272
145
|
ordinals.each do |ordinal|
|
|
273
146
|
old_partition = "#{old_journal}_era_#{ordinal}"
|
|
274
147
|
@db.exec("ALTER TABLE #{quote(old_partition)} RENAME TO #{quote(partition(ordinal))}")
|
|
@@ -277,10 +150,7 @@ module Hecks
|
|
|
277
150
|
%w[hecks_eras hecks_era_texts hecks_approvals].each do |table|
|
|
278
151
|
@db.exec_params("UPDATE #{table} SET domain = $1 WHERE domain = $2", [@domain, @formerly_known_as])
|
|
279
152
|
end
|
|
280
|
-
#
|
|
281
|
-
# ensure_base! at all — only lazily, on first reattest! — so a
|
|
282
|
-
# domain that never needed one must not be forced through an
|
|
283
|
-
# `UPDATE` against a table that doesn't exist.
|
|
153
|
+
# hecks_attestations is created lazily on first reattest!, so it may not exist.
|
|
284
154
|
if @db.exec_params(
|
|
285
155
|
"SELECT to_regclass($1)", ["hecks_attestations"]
|
|
286
156
|
)[0]["to_regclass"]
|
|
@@ -307,35 +177,11 @@ module Hecks
|
|
|
307
177
|
raise Runtime::WiringError, "cannot rename #{@formerly_known_as} to #{@domain}: #{e.message.strip}"
|
|
308
178
|
end
|
|
309
179
|
|
|
310
|
-
# Refuses to boot on a connection whose role Postgres exempts from row-level
|
|
311
|
-
#
|
|
180
|
+
# Refuses to boot on a connection whose role Postgres exempts from row-level security
|
|
181
|
+
# (a superuser or BYPASSRLS role) unless the caller opts in.
|
|
312
182
|
#
|
|
313
|
-
#
|
|
314
|
-
# `
|
|
315
|
-
# row-level security (`FORCE ROW LEVEL SECURITY` above, plus
|
|
316
|
-
# advance_era!'s one INSERT policy), and Postgres exempts a
|
|
317
|
-
# superuser — or any role granted BYPASSRLS — from every policy
|
|
318
|
-
# on every table, unconditionally: `FORCE` only narrows the
|
|
319
|
-
# owner's exemption and has no lever against either of those.
|
|
320
|
-
# Found live, not reasoned about (BUG#24): the QA ledger's own
|
|
321
|
-
# `.world` named a bare database, so every session connected as
|
|
322
|
-
# the machine's default Postgres user — a superuser — and an
|
|
323
|
-
# old checkout wrote its own superseded era straight through
|
|
324
|
-
# two mints, 37 rows no newer head could read, and nothing
|
|
325
|
-
# warned. Asked of the catalog once, at boot, before anything
|
|
326
|
-
# is provisioned: the answer is a fact about the role, not about
|
|
327
|
-
# any table, so there is nothing to wait for and nothing to
|
|
328
|
-
# half-build first.
|
|
329
|
-
#
|
|
330
|
-
# Refuses by default — quiet divergence is the enemy — naming
|
|
331
|
-
# the role and both ways out. `allow_superuser` boots anyway,
|
|
332
|
-
# but says so on stderr on every boot, because the only guard
|
|
333
|
-
# left standing then is PostgresEra#append's own in-process
|
|
334
|
-
# superseded-era check (the belt to this suspender), and an
|
|
335
|
-
# operator reading the log deserves to know which one they are
|
|
336
|
-
# relying on. Presence-over-truthiness for the setting itself
|
|
337
|
-
# is `PostgresEra.setting`'s job (the caller's); here a truthy
|
|
338
|
-
# value opts in and anything else does not.
|
|
183
|
+
# Those roles bypass the write-fence, so a stale checkout could keep writing a
|
|
184
|
+
# superseded era unnoticed. With `allow_superuser` it boots and warns on stderr.
|
|
339
185
|
#
|
|
340
186
|
# @param allow_superuser [Boolean, Object, nil] any truthy value boots anyway
|
|
341
187
|
# with a warning on stderr; `false` or `nil` refuses
|
|
@@ -370,10 +216,6 @@ module Hecks
|
|
|
370
216
|
# Tells whether this connection's role is the one that builds the schema:
|
|
371
217
|
# it owns the domain's journal, or no journal is visible yet.
|
|
372
218
|
#
|
|
373
|
-
# Nothing provisioned yet — build it. Provisioned and owned —
|
|
374
|
-
# keep it current. Provisioned by someone else — this is an app
|
|
375
|
-
# role, and the owner has already done this work.
|
|
376
|
-
#
|
|
377
219
|
# @return [Boolean] true when no journal table is visible on the search path
|
|
378
220
|
# or `current_user` owns it; false when another role owns it
|
|
379
221
|
# @raise [PG::Error] if the catalog lookup fails
|
|
@@ -389,25 +231,10 @@ module Hecks
|
|
|
389
231
|
# Creates and attaches one era's journal partition, unless it is already
|
|
390
232
|
# attached; finishes the attach for a table a crashed boot left detached.
|
|
391
233
|
#
|
|
392
|
-
#
|
|
393
|
-
#
|
|
394
|
-
#
|
|
395
|
-
#
|
|
396
|
-
# CREATE TABLE ... PARTITION OF → AccessExclusiveLock (parent)
|
|
397
|
-
# CREATE, then ALTER ... ATTACH → ShareUpdateExclusiveLock
|
|
398
|
-
#
|
|
399
|
-
# AccessExclusive conflicts with every insert in the hierarchy —
|
|
400
|
-
# routed through the parent or addressed to an existing leaf —
|
|
401
|
-
# so attaching the new era that way inside the mint transaction
|
|
402
|
-
# stops every writer for the whole mint, tail materialization
|
|
403
|
-
# included. ShareUpdateExclusive conflicts with neither, so the
|
|
404
|
-
# old checkout keeps writing its own era straight through the
|
|
405
|
-
# build and only pauses for the head swap at the end.
|
|
406
|
-
#
|
|
407
|
-
# That is what makes the fork real during a mint rather than
|
|
408
|
-
# merely before and after one. Measured, and pinned by the spec
|
|
409
|
-
# — which writes through a live mint rather than reading a lock
|
|
410
|
-
# mode out of the catalog.
|
|
234
|
+
# Build then ATTACH, never CREATE ... PARTITION OF: the latter takes an
|
|
235
|
+
# AccessExclusiveLock on the parent and stops every writer for the whole mint,
|
|
236
|
+
# while ATTACH takes only ShareUpdateExclusiveLock, so a running checkout keeps
|
|
237
|
+
# writing through the build.
|
|
411
238
|
#
|
|
412
239
|
# @param era [Integer] ordinal of the era whose partition is ensured
|
|
413
240
|
# @return [void]
|
|
@@ -429,9 +256,8 @@ module Hecks
|
|
|
429
256
|
# Tells whether an era's partition is part of the journal, by asking
|
|
430
257
|
# `pg_inherits` rather than checking that the table exists.
|
|
431
258
|
#
|
|
432
|
-
#
|
|
433
|
-
# the
|
|
434
|
-
# and the next boot must finish the job rather than skip it.
|
|
259
|
+
# A table left by a crash between CREATE and ATTACH exists but is not attached,
|
|
260
|
+
# and the next boot must finish the job.
|
|
435
261
|
#
|
|
436
262
|
# @param era [Integer] ordinal of the era whose partition is looked up
|
|
437
263
|
# @return [Boolean] true when the partition is attached to this domain's
|