hecks 2.8.0 → 3.0.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 +50 -7
- 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 +32 -100
- 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 +10 -32
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
- data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
- data/lib/hecks/adapters/driven/postgres.rb +149 -136
- data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
- data/lib/hecks/adapters/driven/prism.rb +48 -38
- 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 +76 -62
- 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/bench/cli.rb +62 -0
- data/lib/hecks/bench/environment.rb +71 -0
- data/lib/hecks/bench/postgres_probe.rb +30 -0
- data/lib/hecks/bench/report.rb +76 -0
- data/lib/hecks/bench/ruby_runner.rb +93 -0
- data/lib/hecks/bench/run.rb +55 -0
- data/lib/hecks/bench/rust_runner.rb +101 -0
- data/lib/hecks/bench/stats.rb +46 -0
- data/lib/hecks/bench/suite.rb +98 -0
- data/lib/hecks/bench/workload.rb +89 -0
- data/lib/hecks/bench.rb +17 -0
- 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 +56 -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 +39 -39
- data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
- data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
- data/lib/hecks/bluebook/behaviour/command.rb +14 -66
- data/lib/hecks/bluebook/behaviour/domain_port.rb +11 -20
- data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
- data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
- data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
- data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
- data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
- data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
- data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
- data/lib/hecks/bluebook/capabilities.rb +17 -49
- data/lib/hecks/bluebook/chapter.rb +16 -50
- data/lib/hecks/bluebook/command.rb +23 -81
- data/lib/hecks/bluebook/domain_port.rb +49 -67
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +96 -708
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +63 -169
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +3 -1
- 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 +42 -89
- 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 +148 -197
- 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 +38 -43
- 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 +46 -111
- data/lib/hecks/bluebook/entity.rb +13 -71
- data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
- data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
- data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
- data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
- data/lib/hecks/bluebook/expression/resolver.rb +39 -661
- data/lib/hecks/bluebook/hexagon.rb +18 -33
- 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 +25 -176
- data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -306
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
- data/lib/hecks/bluebook/meta_validator.rb +87 -352
- data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
- data/lib/hecks/bluebook/model_check.rb +122 -596
- data/lib/hecks/bluebook/pattern_subset.rb +49 -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 +27 -51
- data/lib/hecks/bluebook/read_model.rb +57 -81
- data/lib/hecks/bluebook/reference.rb +7 -41
- data/lib/hecks/bluebook/smoke_test.rb +9 -99
- data/lib/hecks/bluebook/synthesizer.rb +13 -62
- data/lib/hecks/bluebook/translation.rb +26 -62
- data/lib/hecks/bluebook/value_object.rb +11 -36
- data/lib/hecks/bluebook.rb +5 -22
- data/lib/hecks/cache_dir.rb +4 -23
- data/lib/hecks/canonical_json.rb +30 -0
- data/lib/hecks/chapters.rb +102 -0
- data/lib/hecks/cli/behaviors.rb +61 -0
- data/lib/hecks/cli/canonicalise.rb +25 -0
- data/lib/hecks/cli/check_engine_agreement.rb +32 -0
- data/lib/hecks/cli/check_era.rb +88 -0
- data/lib/hecks/cli/console.rb +66 -0
- data/lib/hecks/cli/deploy_template_diff.rb +40 -0
- data/lib/hecks/cli/doc_coverage.rb +32 -0
- data/lib/hecks/cli/document.rb +5 -23
- data/lib/hecks/cli/expression_projection.rb +41 -0
- data/lib/hecks/cli/follow.rb +88 -0
- data/lib/hecks/cli/history.rb +44 -0
- data/lib/hecks/cli/ir.rb +4 -6
- data/lib/hecks/cli/mcp.rb +5 -6
- data/lib/hecks/cli/model_check.rb +24 -88
- data/lib/hecks/cli/pattern_cases.rb +47 -0
- data/lib/hecks/cli/present.rb +39 -0
- data/lib/hecks/cli/project_cli.rb +223 -64
- data/lib/hecks/cli/project_diagrams.rb +4 -10
- data/lib/hecks/cli/project_glossary.rb +44 -0
- data/lib/hecks/cli/reference.rb +46 -0
- data/lib/hecks/cli/refresh_projections.rb +44 -0
- data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
- data/lib/hecks/cli/release.rb +70 -0
- data/lib/hecks/cli/release_gem.rb +59 -0
- data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
- data/lib/hecks/cli/rspec_shard_files.rb +66 -0
- data/lib/hecks/cli/run.rb +15 -61
- data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
- data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
- data/lib/hecks/cli/shape.rb +70 -0
- data/lib/hecks/cli/smoke_http.rb +230 -0
- data/lib/hecks/cli/smoke_test.rb +7 -19
- data/lib/hecks/cli/spec_example.rb +27 -0
- data/lib/hecks/cli/statements.rb +32 -0
- data/lib/hecks/cli/stores.rb +4 -6
- data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
- data/lib/hecks/cli.rb +216 -73
- data/lib/hecks/codemod.rb +324 -0
- data/lib/hecks/construct.rb +8 -34
- data/lib/hecks/corpus.rb +423 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +617 -158
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -11
- data/lib/hecks/deploy/oidc.json +82 -2
- data/lib/hecks/doc/reference.rb +351 -0
- data/lib/hecks/doors/cli_door.rb +196 -0
- data/lib/hecks/doors/cli_runner.rb +297 -0
- data/lib/hecks/{facade → doors}/command_request.rb +14 -34
- data/lib/hecks/doors/handle.rb +206 -0
- data/lib/hecks/doors/json_door.rb +132 -0
- data/lib/hecks/doors/launcher_options.rb +129 -0
- data/lib/hecks/{cli → doors}/mcp_door.rb +25 -120
- data/lib/hecks/doors/mcp_door_scope.rb +97 -0
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +162 -0
- data/lib/hecks/doors/ruby_door/chapter.rb +72 -0
- data/lib/hecks/doors/ruby_door.rb +77 -0
- data/lib/hecks/doors.rb +61 -0
- 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 +5 -72
- data/lib/hecks/engine_agreement.rb +156 -0
- data/lib/hecks/facade.rb +23 -51
- data/lib/hecks/forms/app.rb +21 -76
- data/lib/hecks/forms/banking_presentation.rb +50 -0
- 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 +31 -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/fuzzing/bounded_exhaustive_expressions.rb +393 -0
- data/lib/hecks/fuzzing/combination_miner.rb +86 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
- data/lib/hecks/fuzzing/differential.rb +156 -0
- data/lib/hecks/fuzzing/domain_generator.rb +615 -0
- data/lib/hecks/fuzzing/era_boundary.rb +71 -0
- data/lib/hecks/fuzzing/form_census.rb +136 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
- data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
- data/lib/hecks/fuzzing/outside_world.rb +45 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
- data/lib/hecks/fuzzing/properties/guards.rb +190 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
- data/lib/hecks/fuzzing/properties/querying.rb +175 -0
- data/lib/hecks/fuzzing/properties.rb +162 -0
- data/lib/hecks/fuzzing/qa_settings.rb +123 -0
- data/lib/hecks/fuzzing/replay.rb +505 -0
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
- data/lib/hecks/fuzzing/self_consistency.rb +339 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
- data/lib/hecks/fuzzing/shrinker.rb +166 -0
- data/lib/hecks/fuzzing/structural_skips.rb +33 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
- data/lib/hecks/fuzzing/value_generator.rb +216 -0
- data/lib/hecks/fuzzing.rb +23 -0
- data/lib/hecks/grammar/evolve.rb +336 -0
- 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/hecks/adapters/codebase/codemods.rb +45 -0
- data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
- data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
- data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
- data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
- data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
- data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
- data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
- data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
- data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
- data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
- data/lib/hecks/hecks/adapters/git.adapter +7 -0
- data/lib/hecks/hecks/adapters/git.rb +102 -0
- data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
- data/lib/hecks/hecks/adapters/host_http.rb +65 -0
- data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
- data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
- data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
- data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
- data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
- data/lib/hecks/hecks/adapters/local_files.rb +46 -0
- data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
- data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
- data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
- data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
- data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
- data/lib/hecks/hecks/adapters/shell.adapter +7 -0
- data/lib/hecks/hecks/adapters/shell.rb +49 -0
- data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
- data/lib/hecks/hecks/adapters/terminal.rb +74 -0
- data/lib/hecks/hecks/codebase.behaviors +172 -0
- data/lib/hecks/hecks/codebase.bluebook +2453 -0
- data/lib/hecks/hecks/context_map.hecksagon +13 -0
- data/lib/hecks/hecks/custodian.behaviors +179 -0
- data/lib/hecks/hecks/custodian.bluebook +1847 -0
- data/lib/hecks/hecks/deploy.behaviors +74 -0
- data/lib/hecks/hecks/deploy.world +12 -0
- data/lib/hecks/hecks/environments/memory.world +10 -0
- data/lib/hecks/hecks/governance.world +7 -0
- data/lib/hecks/hecks/hecks.behaviors +62 -0
- data/lib/hecks/hecks/hecks.bluebook +124 -0
- data/lib/hecks/hecks/hecks.hecksagon +390 -0
- data/lib/hecks/hecks/hecks.world +42 -0
- data/lib/hecks/ir.rb +14 -83
- data/lib/hecks/kernel_capabilities.rb +194 -0
- 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 +26 -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 +19 -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 +83 -198
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
- data/lib/hecks/language/hecksagon/domain_port.bluebook +14 -19
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +26 -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_stdio_guard.rb +4 -41
- data/lib/hecks/naming.rb +29 -137
- data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
- data/lib/hecks/persistence_legacy_fixture.rb +113 -0
- data/lib/hecks/ports/access_control.rb +7 -23
- data/lib/hecks/ports/agent/answers.rb +5 -23
- data/lib/hecks/ports/agent.rb +47 -172
- data/lib/hecks/ports/authentication.rb +12 -39
- data/lib/hecks/ports/authorization.rb +16 -43
- data/lib/hecks/ports/clock.rb +5 -45
- data/lib/hecks/ports/extraction.rb +7 -12
- data/lib/hecks/ports/identity_assignment.rb +8 -29
- data/lib/hecks/ports/identity_generation.rb +6 -29
- data/lib/hecks/ports/identity_resolution.rb +2 -10
- data/lib/hecks/ports/key_vault.rb +9 -45
- data/lib/hecks/ports/persistence/append_only.rb +61 -64
- data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
- data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
- data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
- data/lib/hecks/ports/persistence/plugin.rb +13 -42
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +44 -144
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +20 -85
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +18 -24
- 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 +19 -76
- 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 +43 -127
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +131 -555
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
- 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 +50 -47
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
- 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 +47 -108
- 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/projection_files.rb +285 -0
- data/lib/hecks/projections/bootstrap_table.rb +12 -44
- 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 +40 -207
- data/lib/hecks/projections/deploy/lambda.rb +191 -708
- 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/preview.sh.erb +1 -1
- 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 +3 -59
- data/lib/hecks/projections/deploy/preview.rb +2 -67
- data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
- data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
- data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/scripts.rb +4 -57
- data/lib/hecks/projections/deploy/shared.rb +48 -147
- 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 +22 -584
- 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 +16 -43
- data/lib/hecks/projections/model.rb +12 -51
- data/lib/hecks/projections/oidc.rb +12 -56
- data/lib/hecks/projections/parser_table.rb +13 -32
- data/lib/hecks/projections/reference.rb +4 -18
- data/lib/hecks/projections/rust_vocabulary.rb +13 -251
- 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 +154 -400
- data/lib/hecks/projector/docs_projector.rb +10 -201
- data/lib/hecks/projector/exporter.rb +42 -134
- data/lib/hecks/projector/ir_projector.rb +2 -9
- data/lib/hecks/projector/narrate_projector.rb +15 -189
- data/lib/hecks/projector/target.rb +13 -76
- data/lib/hecks/projector.rb +30 -121
- data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
- data/lib/hecks/quality_control/adapters/agent.rb +58 -0
- data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
- data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
- data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
- data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
- data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
- data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
- data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
- data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
- data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
- data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
- data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
- data/lib/hecks/quality_control/cli/child.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
- data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
- data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
- data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
- data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
- data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
- data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
- data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
- data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
- data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
- data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
- data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
- data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
- data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
- data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
- data/lib/hecks/query_ir.rb +346 -0
- data/lib/hecks/query_ir_mcp.rb +198 -0
- 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 +5 -8
- data/lib/hecks/release/runner/clean_tree.rb +42 -0
- data/lib/hecks/release/runner/commands.rb +2 -4
- data/lib/hecks/release/runner/console.rb +6 -0
- data/lib/hecks/release/runner/gem_publisher.rb +15 -9
- data/lib/hecks/release/runner/npm_publisher.rb +9 -53
- data/lib/hecks/release/runner/preflight.rb +29 -20
- data/lib/hecks/release/runner/published.rb +8 -26
- data/lib/hecks/release/runner/tagger.rb +31 -23
- data/lib/hecks/release/runner.rb +60 -38
- data/lib/hecks/rendering.rb +4 -26
- data/lib/hecks/router/namespace_installer.rb +15 -39
- data/lib/hecks/router.rb +4 -8
- data/lib/hecks/runtime/adapter_lookup.rb +124 -0
- 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 +27 -160
- data/lib/hecks/runtime/command_interpreter.rb +124 -306
- data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
- data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
- data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
- data/lib/hecks/runtime/command_rules/emission.rb +8 -21
- data/lib/hecks/runtime/command_rules/references.rb +23 -202
- data/lib/hecks/runtime/dependency_planning.rb +50 -104
- data/lib/hecks/runtime/dispatcher.rb +134 -243
- data/lib/hecks/runtime/entity_element.rb +40 -462
- data/lib/hecks/runtime/entity_interpreter.rb +64 -219
- data/lib/hecks/runtime/errors.rb +32 -115
- data/lib/hecks/runtime/event.rb +3 -25
- data/lib/hecks/runtime/identity.rb +18 -154
- data/lib/hecks/runtime/instance.rb +18 -150
- data/lib/hecks/runtime/interpreting.rb +14 -70
- data/lib/hecks/runtime/invocation.rb +28 -164
- data/lib/hecks/runtime/loader.rb +109 -194
- data/lib/hecks/runtime/outbox.rb +39 -262
- data/lib/hecks/runtime/policy_interpreter.rb +43 -202
- data/lib/hecks/runtime/port_operation_interpreter.rb +28 -97
- data/lib/hecks/runtime/query_interpreter.rb +122 -187
- data/lib/hecks/runtime/reaction_invocation.rb +36 -138
- data/lib/hecks/runtime/reaction_outcome.rb +89 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
- data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
- data/lib/hecks/runtime/reference_hop.rb +8 -81
- data/lib/hecks/runtime/refusal_wording.rb +28 -87
- data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
- data/lib/hecks/runtime/registry/verification.rb +213 -227
- data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
- data/lib/hecks/runtime/registry.rb +156 -247
- data/lib/hecks/runtime/remote_dispatcher.rb +68 -154
- 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 +162 -760
- data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
- data/lib/hecks/runtime/value/field_checks.rb +172 -0
- data/lib/hecks/runtime/value.rb +15 -84
- data/lib/hecks/runtime.rb +35 -69
- data/lib/hecks/rust_build/conformance.rb +158 -0
- data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
- data/lib/hecks/rust_build/coverage.rb +218 -0
- data/lib/hecks/rust_build/native_build.rb +124 -0
- data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
- data/lib/hecks/rust_build/project_rust.rb +332 -0
- data/lib/hecks/rust_build/wasm.rb +105 -0
- data/lib/hecks/rust_build/wasm_browser.rb +113 -0
- data/lib/hecks/rust_build.rb +144 -0
- data/lib/hecks/storehouse.rb +155 -582
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +10 -34
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
- data/lib/hecks/three_zero/forms.yml +127 -0
- data/lib/hecks/three_zero.rb +12 -0
- data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
- data/lib/hecks/tools/comment_style.rb +1070 -0
- data/lib/hecks/tools/corpus_report.rb +87 -0
- data/lib/hecks/tools/deploy_recipe.rb +255 -0
- data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
- data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
- data/lib/hecks/tools/era_projection_backfill.rb +94 -0
- data/lib/hecks/tools/era_reattest.rb +146 -0
- data/lib/hecks/tools/evolve_run.rb +189 -0
- data/lib/hecks/tools/fuzz_sweep.rb +373 -0
- data/lib/hecks/tools/heki_compaction.rb +100 -0
- data/lib/hecks/tools/hoist_local_givens.rb +246 -0
- data/lib/hecks/tools/journal_compaction.rb +89 -0
- data/lib/hecks/tools/oidc_manifests.rb +70 -0
- data/lib/hecks/tools/query_ir_run.rb +81 -0
- data/lib/hecks/tools/regeneration_run.rb +166 -0
- data/lib/hecks/tools/rust_comment_style.rb +642 -0
- data/lib/hecks/tools/sequence_script.rb +62 -0
- data/lib/hecks/tools/tail_merge.rb +117 -0
- data/lib/hecks/tools/tenant_provisioning.rb +142 -0
- data/lib/hecks/tools/translation_audit.rb +208 -0
- data/lib/hecks/tools/translation_scaffold.rb +131 -0
- data/lib/hecks/tools.rb +86 -0
- data/lib/hecks/vendoring/git_environment.rb +2 -8
- data/lib/hecks/vendoring/git_source.rb +4 -10
- data/lib/hecks/vendoring.rb +7 -46
- data/lib/hecks/version.rb +3 -14
- data/lib/hecks.rb +52 -90
- 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
- data/qa/settings.yml +66 -0
- data/rust/.cargo/config.toml +6 -0
- data/rust/.gitignore +2 -0
- data/rust/Cargo.lock +54 -0
- data/rust/Cargo.toml +61 -0
- data/rust/build/.cargo/config.toml +3 -0
- data/rust/build/.gitignore +1 -0
- data/rust/build/Cargo.lock +7 -0
- data/rust/build/Cargo.toml +19 -0
- data/rust/build/src/build_artifact.rs +47 -0
- data/rust/build/src/cargo_sync.rs +248 -0
- data/rust/build/src/json.rs +421 -0
- data/rust/build/src/lineage_pass.rs +285 -0
- data/rust/build/src/main.rs +51 -0
- data/rust/build/src/optional_pass.rs +150 -0
- data/rust/build/src/pipeline.rs +264 -0
- data/rust/build/src/reserved_names.rs +76 -0
- data/rust/build/src/resolve.rs +124 -0
- data/rust/build/src/root.rs +25 -0
- data/rust/build/src/sidecars.rs +73 -0
- data/rust/build/src/subprocess.rs +51 -0
- data/rust/build/src/tmp.rs +31 -0
- data/rust/codegen/.cargo/config.toml +3 -0
- data/rust/codegen/.gitignore +1 -0
- data/rust/codegen/Cargo.lock +7 -0
- data/rust/codegen/Cargo.toml +20 -0
- data/rust/codegen/src/attr.rs +32 -0
- data/rust/codegen/src/bridging.rs +481 -0
- data/rust/codegen/src/commands.rs +1226 -0
- data/rust/codegen/src/constraints.rs +109 -0
- data/rust/codegen/src/dependency_planning.rs +209 -0
- data/rust/codegen/src/domain_generator.rs +1288 -0
- data/rust/codegen/src/exemplar.rs +230 -0
- data/rust/codegen/src/expr_emitter.rs +87 -0
- data/rust/codegen/src/fielded.rs +192 -0
- data/rust/codegen/src/hecks_naming.rs +33 -0
- data/rust/codegen/src/json.rs +335 -0
- data/rust/codegen/src/json_codec.rs +697 -0
- data/rust/codegen/src/literal.rs +207 -0
- data/rust/codegen/src/main.rs +290 -0
- data/rust/codegen/src/manifest.rs +227 -0
- data/rust/codegen/src/mutations.rs +784 -0
- data/rust/codegen/src/naming.rs +286 -0
- data/rust/codegen/src/ports.rs +208 -0
- data/rust/codegen/src/prelude.rs +116 -0
- data/rust/codegen/src/queries.rs +786 -0
- data/rust/codegen/src/reactions.rs +532 -0
- data/rust/codegen/src/read_models.rs +697 -0
- data/rust/codegen/src/reference_specs.rs +40 -0
- data/rust/codegen/src/registry.rs +966 -0
- data/rust/codegen/src/reserved_names.rs +76 -0
- data/rust/codegen/src/shared.rs +93 -0
- data/rust/codegen/src/skip_reason.rs +25 -0
- data/rust/codegen/src/types.rs +362 -0
- data/rust/host/.cargo/config.toml +3 -0
- data/rust/host/.gitignore +1 -0
- data/rust/host/Cargo.lock +4297 -0
- data/rust/host/Cargo.toml +61 -0
- data/rust/host/HECKS_RELEASE +1 -0
- data/rust/host/rds-ca-bundle.pem +2736 -0
- data/rust/host/src/api.rs +1596 -0
- data/rust/host/src/approval.rs +652 -0
- data/rust/host/src/auth.rs +1287 -0
- data/rust/host/src/bin/expr_harness.rs +46 -0
- data/rust/host/src/bin/lineage_harness.rs +119 -0
- data/rust/host/src/bin/mint_harness.rs +135 -0
- data/rust/host/src/checkout.rs +265 -0
- data/rust/host/src/dispatch.rs +1443 -0
- data/rust/host/src/expr_json.rs +1117 -0
- data/rust/host/src/field_hints.rs +52 -0
- data/rust/host/src/ir.rs +733 -0
- data/rust/host/src/journal.rs +874 -0
- data/rust/host/src/lambda_client.rs +378 -0
- data/rust/host/src/log.rs +147 -0
- data/rust/host/src/main.rs +474 -0
- data/rust/host/src/mint.rs +1865 -0
- data/rust/host/src/payments/keystore.rs +330 -0
- data/rust/host/src/payments/tests.rs +1400 -0
- data/rust/host/src/payments.rs +767 -0
- data/rust/host/src/presentation.rs +698 -0
- data/rust/host/src/presentation_write.rs +1431 -0
- data/rust/host/src/query_step.rs +149 -0
- data/rust/host/src/rate_limit.rs +904 -0
- data/rust/host/src/reference_transform.rs +241 -0
- data/rust/host/src/reference_validate.rs +338 -0
- data/rust/host/src/resend.rs +296 -0
- data/rust/host/src/secrets.rs +106 -0
- data/rust/host/src/server.rs +663 -0
- data/rust/host/src/storage_shape.rs +175 -0
- data/rust/host/src/test_pg.rs +40 -0
- data/rust/host/src/ui_schema.rs +1351 -0
- data/rust/host/src/wasm_runner.rs +191 -0
- data/rust/host/src/web/newsletter/cooldown.rs +126 -0
- data/rust/host/src/web/newsletter/tests.rs +104 -0
- data/rust/host/src/web/newsletter.rs +310 -0
- data/rust/host/src/web/newsletter_send.rs +357 -0
- data/rust/host/src/web/registrations.rs +1505 -0
- data/rust/host/src/web.rs +2937 -0
- data/rust/lsp/.gitignore +1 -0
- data/rust/lsp/Cargo.lock +7 -0
- data/rust/lsp/Cargo.toml +16 -0
- data/rust/lsp/README.md +133 -0
- data/rust/lsp/src/diagnostics.rs +165 -0
- data/rust/lsp/src/json.rs +385 -0
- data/rust/lsp/src/main.rs +399 -0
- data/rust/lsp/src/outline.rs +177 -0
- data/rust/lsp/src/rpc.rs +67 -0
- data/rust/parser/.cargo/config.toml +5 -0
- data/rust/parser/.gitignore +1 -0
- data/rust/parser/Cargo.lock +7 -0
- data/rust/parser/Cargo.toml +21 -0
- data/rust/parser/src/build/closed_sets.rs +27 -0
- data/rust/parser/src/build/identity.rs +276 -0
- data/rust/parser/src/build/mod.rs +12 -0
- data/rust/parser/src/build/naming.rs +141 -0
- data/rust/parser/src/build/pattern_subset.rs +161 -0
- data/rust/parser/src/build/query_derive.rs +72 -0
- data/rust/parser/src/build/query_inference.rs +269 -0
- data/rust/parser/src/build/query_options.rs +100 -0
- data/rust/parser/src/build/read_model.rs +94 -0
- data/rust/parser/src/build/references.rs +32 -0
- data/rust/parser/src/canonical.rs +114 -0
- data/rust/parser/src/diag.rs +55 -0
- data/rust/parser/src/emit.rs +953 -0
- data/rust/parser/src/expr/ast_json.rs +147 -0
- data/rust/parser/src/expr/evaluator.rs +81 -0
- data/rust/parser/src/expr/mod.rs +84 -0
- data/rust/parser/src/expr/resolver.rs +497 -0
- data/rust/parser/src/ir.rs +387 -0
- data/rust/parser/src/keywords.rs +585 -0
- data/rust/parser/src/lex.rs +662 -0
- data/rust/parser/src/main.rs +252 -0
- data/rust/parser/src/parse/aggregate.rs +612 -0
- data/rust/parser/src/parse/chapter.rs +517 -0
- data/rust/parser/src/parse/command.rs +637 -0
- data/rust/parser/src/parse/domain_port.rs +127 -0
- data/rust/parser/src/parse/entity.rs +527 -0
- data/rust/parser/src/parse/file.rs +67 -0
- data/rust/parser/src/parse/hecksagon.rs +231 -0
- data/rust/parser/src/parse/lifecycle.rs +162 -0
- data/rust/parser/src/parse/mod.rs +1262 -0
- data/rust/parser/src/parse/policy.rs +87 -0
- data/rust/parser/src/parse/process_manager.rs +305 -0
- data/rust/parser/src/parse/query.rs +100 -0
- data/rust/parser/src/parse/read_model.rs +214 -0
- data/rust/parser/src/parse/value_object.rs +250 -0
- data/rust/parser/src/ruby_value.rs +422 -0
- data/rust/project/bridging.rb +257 -0
- data/rust/project/commands.rb +877 -0
- data/rust/project/constraints.rb +86 -0
- data/rust/project/dependency_planning.rb +178 -0
- data/rust/project/domain_generator.rb +980 -0
- data/rust/project/exemplar.rb +148 -0
- data/rust/project/expr_emitter.rb +72 -0
- data/rust/project/fielded.rb +141 -0
- data/rust/project/json_codec.rb +641 -0
- data/rust/project/mutations.rb +721 -0
- data/rust/project/naming.rb +205 -0
- data/rust/project/ports.rb +65 -0
- data/rust/project/queries.rb +493 -0
- data/rust/project/reactions.rb +317 -0
- data/rust/project/read_models.rb +690 -0
- data/rust/project/reference_specs.rb +27 -0
- data/rust/project/registry.rb +396 -0
- data/rust/project/skip_reason.rb +29 -0
- data/rust/project/types.rb +225 -0
- data/rust/project/write_if_changed.rb +83 -0
- data/rust/project.rb +30 -0
- data/rust/project_rust_pipeline.rb +383 -0
- data/rust/src/exemplar/commands.rs +279 -0
- data/rust/src/exemplar/constraints.rs +18 -0
- data/rust/src/exemplar/fielded.rs +280 -0
- data/rust/src/exemplar/json.rs +277 -0
- data/rust/src/exemplar/mod.rs +17 -0
- data/rust/src/exemplar/mutations.rs +80 -0
- data/rust/src/exemplar/queries.rs +33 -0
- data/rust/src/exemplar/reactions.rs +88 -0
- data/rust/src/exemplar/read_models.rs +48 -0
- data/rust/src/exemplar/registry.rs +166 -0
- data/rust/src/exemplar/types.rs +44 -0
- data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
- data/rust/src/kernel/attribute_shapes/list.rs +18 -0
- data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
- data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
- data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
- data/rust/src/kernel/cli.rs +585 -0
- data/rust/src/kernel/dispatch.rs +1090 -0
- data/rust/src/kernel/expr.rs +458 -0
- data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
- data/rust/src/kernel/expression_operators/comparison.rs +48 -0
- data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
- data/rust/src/kernel/expression_operators/logical.rs +13 -0
- data/rust/src/kernel/expression_operators/membership.rs +18 -0
- data/rust/src/kernel/expression_operators/mod.rs +68 -0
- data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
- data/rust/src/kernel/expression_operators/positional.rs +126 -0
- data/rust/src/kernel/expression_operators/presence.rs +101 -0
- data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
- data/rust/src/kernel/expression_operators/sized.rs +35 -0
- data/rust/src/kernel/expression_operators/text.rs +79 -0
- data/rust/src/kernel/expression_operators/to_string.rs +18 -0
- data/rust/src/kernel/json.rs +779 -0
- data/rust/src/kernel/mod.rs +133 -0
- data/rust/src/kernel/named_query.rs +219 -0
- data/rust/src/kernel/orchestrate.rs +1810 -0
- data/rust/src/kernel/pattern.rs +307 -0
- data/rust/src/kernel/query_comparators.rs +206 -0
- data/rust/src/kernel/query_ordering.rs +103 -0
- data/rust/src/kernel/read_model.rs +914 -0
- data/rust/src/kernel/reference_lookup.rs +149 -0
- data/rust/src/kernel/repository.rs +468 -0
- data/rust/src/kernel/routing.rs +612 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
- data/rust/src/kernel/vocab/field_hint.rs +70 -0
- data/rust/src/kernel/vocab/mod.rs +20 -0
- data/rust/src/kernel/vocab/query_comparator.rs +65 -0
- data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
- data/rust/src/lib.rs +8 -0
- data/rust/src/main.rs +16 -0
- data/rust/web/.gitignore +1 -0
- data/rust/web/Cargo.lock +119 -0
- data/rust/web/Cargo.toml +18 -0
- data/rust/web/src/lib.rs +8 -0
- metadata +478 -12
- data/lib/hecks/facade/cli_door.rb +0 -178
- data/lib/hecks/facade/cli_runner.rb +0 -271
- data/lib/hecks/facade/handle.rb +0 -293
- data/lib/hecks/facade/json_door.rb +0 -247
- data/lib/hecks/facade/surface/aggregate_door.rb +0 -222
- data/lib/hecks/facade/surface/chapter.rb +0 -116
- data/lib/hecks/facade/surface.rb +0 -61
- data/lib/hecks/mcp_door_scope.rb +0 -176
|
@@ -3,8 +3,10 @@ require "json"
|
|
|
3
3
|
require_relative "../../../../adapters/driven/sql_query_builder"
|
|
4
4
|
require_relative "../../../../adapters/driven/postgres/outbox"
|
|
5
5
|
require_relative "../../../../adapters/driven/postgres/reconnect"
|
|
6
|
+
require_relative "../../../../adapters/driven/postgres/shared_connection"
|
|
6
7
|
require_relative "postgres_era/lineage"
|
|
7
8
|
require_relative "postgres_era/lineage_manager"
|
|
9
|
+
require_relative "postgres_era/events"
|
|
8
10
|
require_relative "../../../../ports/persistence/append_only"
|
|
9
11
|
require_relative "../../../../query_specification/common/order_by"
|
|
10
12
|
require_relative "../../../../runtime/errors"
|
|
@@ -14,100 +16,45 @@ require_relative "../../../../runtime/registry"
|
|
|
14
16
|
|
|
15
17
|
module Hecks
|
|
16
18
|
module Adapters
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# (translate, fork, merge) where every other adapter can only refuse
|
|
20
|
-
# toward it. Sibling to the plain `Postgres` adapter (postgres.rb),
|
|
21
|
-
# which is the same database with none of this machinery — pick
|
|
22
|
-
# PostgresEra only once a domain actually needs to survive a shape
|
|
23
|
-
# change live. See docs/implemented/postgres-era-adapter-split-plan.md for why
|
|
24
|
-
# the two are split and what each one carries.
|
|
25
|
-
#
|
|
26
|
-
# ## Storage model
|
|
27
|
-
#
|
|
28
|
-
# See postgres_era/lineage.rb for the DDL.
|
|
29
|
-
#
|
|
30
|
-
# - One journal per domain, list-partitioned by era, one ordinal
|
|
31
|
-
# sequence spanning partitions. Appends go there; nothing updates
|
|
32
|
-
# or deletes a journal row (immutability by privilege — UPDATE and
|
|
33
|
-
# DELETE revoked; a deployment's app role connects as a non-owner).
|
|
34
|
-
# - Per aggregate, the head is derived: era 1 reads a plain view
|
|
35
|
-
# (latest save per id); later eras read a view overlaying the
|
|
36
|
-
# materialized, translated ancestor tail with live current-era
|
|
37
|
-
# rows. `project` is therefore a no-op — old entries are never
|
|
38
|
-
# rewritten, and the head is never a table anything writes.
|
|
39
|
-
# - State is one jsonb column. jsonb normalizes key order (and drops
|
|
40
|
-
# duplicate keys), so anything comparing stored state — the corpus
|
|
41
|
-
# history gate above all — must compare canonicalized state, never
|
|
42
|
-
# raw bytes; `bin/canonicalise` deep-sorts keys, which is exactly
|
|
43
|
-
# why the gate survives this normalization.
|
|
44
|
-
# - Query pushdown is the shared SqlQueryBuilder: every declared
|
|
45
|
-
# operator compiles fully into SQL, or the query refuses loudly.
|
|
19
|
+
# Postgres adapter that journals each write per domain and derives every head by era; the
|
|
20
|
+
# only one that can translate, fork or merge on shape drift. Compare jsonb state canonicalized.
|
|
46
21
|
class PostgresEra
|
|
47
22
|
include SqlQueryBuilder
|
|
48
23
|
include Adapters::PostgresOutbox
|
|
49
24
|
include Adapters::PostgresReconnect
|
|
25
|
+
include Events
|
|
50
26
|
|
|
51
27
|
attr_reader :aggregate
|
|
52
28
|
|
|
53
29
|
# Names the optional persistence capabilities this adapter implements natively.
|
|
54
30
|
#
|
|
55
|
-
# `:cross_process_lock`
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
# falls back to. See ADR 0036: that in-process Mutex is invisible
|
|
61
|
-
# to `rust/host` dispatching against the same PostgresEra-bound
|
|
62
|
-
# tables from a separate OS process.
|
|
31
|
+
# `:cross_process_lock` lets dispatch hold `with_write_lock` instead of the in-process
|
|
32
|
+
# mutex, which `rust/host` in another OS process cannot see (ADR 0036). `:atomic_append`
|
|
33
|
+
# tells `RepositoryFactory.build` that `append` (below) already commits the snapshot in
|
|
34
|
+
# the same transaction as the journal row, so replaying the journal through `project` on
|
|
35
|
+
# boot has nothing left to fix up.
|
|
63
36
|
#
|
|
64
|
-
# @return [Array<Symbol>] always `[:atomic_put, :cross_process_lock]`
|
|
65
|
-
def persistence_capabilities = %i[atomic_put cross_process_lock]
|
|
37
|
+
# @return [Array<Symbol>] always `[:atomic_put, :cross_process_lock, :atomic_append]`
|
|
38
|
+
def persistence_capabilities = %i[atomic_put cross_process_lock atomic_append]
|
|
66
39
|
|
|
67
40
|
# Declares that this adapter can act on shape drift rather than only refuse.
|
|
68
41
|
#
|
|
69
|
-
# The capability idiom: only PostgresEra answers true, and only
|
|
70
|
-
# PostgresEra carries an era_check! for the boot gate to delegate to.
|
|
71
|
-
#
|
|
72
42
|
# @return [Boolean] always true
|
|
73
43
|
def self.lineage_capable? = true
|
|
74
44
|
|
|
75
|
-
# Declares that two tenant boots
|
|
76
|
-
#
|
|
77
|
-
# **Tenant-capable** — see Runtime::TenantCheck's own header for the
|
|
78
|
-
# full reasoning. `connect_for`'s own `schema:` setting (the
|
|
79
|
-
# Storehouse shared-instance mechanism, already built, already
|
|
80
|
-
# proven for eras) is what keeps two tenant boots' tables apart:
|
|
81
|
-
# each boot's own `SET search_path` means every unqualified
|
|
82
|
-
# reference this adapter and its lineage classes construct
|
|
83
|
-
# resolves into that boot's own schema, never another tenant's —
|
|
84
|
-
# proven for real, not assumed, by tenant_isolation_spec.rb, the
|
|
85
|
-
# same discipline lineage_capable? already holds itself to.
|
|
45
|
+
# Declares that two tenant boots on separate `schema:` settings keep their tables apart.
|
|
86
46
|
#
|
|
87
47
|
# @return [Boolean] always true
|
|
88
48
|
def self.tenant_capable? = true
|
|
89
49
|
|
|
90
|
-
# Resolves
|
|
91
|
-
#
|
|
92
|
-
# `LineageManager.check!` (see `LineageManager::EraResolver`).
|
|
50
|
+
# Resolves this boot's era, minting the next one when the shape drifted and a translation
|
|
51
|
+
# edge covers it. Delegates to `LineageManager.check!`.
|
|
93
52
|
#
|
|
94
|
-
# @param
|
|
95
|
-
#
|
|
96
|
-
# @
|
|
97
|
-
# @
|
|
98
|
-
# @
|
|
99
|
-
# for this binding; `database` is required, `schema`, `role` and
|
|
100
|
-
# `allow_superuser` are honored
|
|
101
|
-
# @param directory [String, nil] the domain's bluebook directory, where
|
|
102
|
-
# `HECKS_SCAFFOLD=1` writes a translation edge; nil disables scaffolding
|
|
103
|
-
# @return [Integer, nil] the ordinal of the era this boot minted, or nil when it
|
|
104
|
-
# minted nothing (first boot, quiet reboot, or a held-but-superseded shape)
|
|
105
|
-
# @raise [Runtime::WiringError] if the database cannot be reached, the connection
|
|
106
|
-
# is a superuser without `allow_superuser`, a held text fails its integrity
|
|
107
|
-
# check, or the shape drifted and the mint refuses (no edge, a stale or forked
|
|
108
|
-
# edge, an unapproved compute or rekey, uncovered drift, or a failed audit)
|
|
109
|
-
# @raise [Bluebook::DSL::Malformed] if a held era's text parses under neither the
|
|
110
|
-
# current nor the legacy grammar
|
|
53
|
+
# @param settings [Hash{Symbol, String => Object}] persistence settings; needs `database`
|
|
54
|
+
# @param directory [String, nil] where `HECKS_SCAFFOLD=1` writes a translation edge
|
|
55
|
+
# @return [Integer, nil] the ordinal of the era this boot minted, or nil when none
|
|
56
|
+
# @raise [Runtime::WiringError] if the database is unreachable or the mint refuses
|
|
57
|
+
# @raise [Bluebook::DSL::Malformed] if a held era's text parses under no grammar
|
|
111
58
|
def self.era_check!(registry:, bluebook:, current_text:, settings:, directory: nil)
|
|
112
59
|
LineageManager.check!(
|
|
113
60
|
registry: registry, bluebook: bluebook, current_text: current_text,
|
|
@@ -115,23 +62,11 @@ module Hecks
|
|
|
115
62
|
)
|
|
116
63
|
end
|
|
117
64
|
|
|
118
|
-
# Reads one setting
|
|
65
|
+
# Reads one setting under either its Symbol or String spelling.
|
|
119
66
|
#
|
|
120
|
-
#
|
|
121
|
-
# may arrive symbol-keyed (built straight in Ruby) or string-keyed
|
|
122
|
-
# (round-tripped through JSON), and a domain that names one is not
|
|
123
|
-
# obligated to also skip the other. `key?` decides which spelling
|
|
124
|
-
# actually exists, never `||` — `||` cannot tell a genuinely stored
|
|
125
|
-
# `false` apart from an absent key, and would silently prefer the
|
|
126
|
-
# other spelling (or `default`) instead of returning the real, held
|
|
127
|
-
# answer. See Hecks::QuerySpecification::FieldPath#read for the same
|
|
128
|
-
# discipline applied to stored state instead of settings.
|
|
67
|
+
# Uses `key?` rather than `||` so a stored `false` is not mistaken for an absent key.
|
|
129
68
|
#
|
|
130
|
-
# @
|
|
131
|
-
# @param key [Symbol, String] the setting name; tried as given, then as a String
|
|
132
|
-
# @param default [Object, nil] what to return when neither spelling is a key
|
|
133
|
-
# @return [Object, nil] the stored value, including a stored `false` or `nil`, or
|
|
134
|
-
# `default` when the key is absent under both spellings
|
|
69
|
+
# @return [Object, nil] the stored value, or `default` when neither spelling is present
|
|
135
70
|
def self.setting(settings, key, default: nil)
|
|
136
71
|
return settings[key] if settings.key?(key)
|
|
137
72
|
|
|
@@ -141,20 +76,15 @@ module Hecks
|
|
|
141
76
|
default
|
|
142
77
|
end
|
|
143
78
|
|
|
144
|
-
# Opens a connection to the database
|
|
145
|
-
#
|
|
146
|
-
# connection and closes it.
|
|
79
|
+
# Opens a connection to the declared database, selecting the declared `schema` if any.
|
|
80
|
+
# The caller owns the connection and closes it.
|
|
147
81
|
#
|
|
148
82
|
# @param name [String] the domain or aggregate name, used only in refusal messages
|
|
149
|
-
# @param settings [Hash{Symbol, String => Object}]
|
|
150
|
-
#
|
|
151
|
-
# @
|
|
152
|
-
# is declared
|
|
153
|
-
# @raise [Runtime::WiringError] if the settings declare no `database`, or Postgres
|
|
154
|
-
# refuses the connection, the `CREATE SCHEMA` or either `SET`
|
|
83
|
+
# @param settings [Hash{Symbol, String => Object}] `database` is a name or `postgres://` URL
|
|
84
|
+
# @return [PG::Connection] an open connection
|
|
85
|
+
# @raise [Runtime::WiringError] if `database` is missing or Postgres refuses a statement
|
|
155
86
|
def self.connect_for(name, settings)
|
|
156
|
-
#
|
|
157
|
-
# a domain that never wires PostgresEra should never need the gem.
|
|
87
|
+
# Lazy so a domain that never wires PostgresEra does not need the pg gem.
|
|
158
88
|
require "pg"
|
|
159
89
|
|
|
160
90
|
declared = setting(settings, :database)
|
|
@@ -171,39 +101,17 @@ module Hecks
|
|
|
171
101
|
PG.connect(dbname: declared)
|
|
172
102
|
end
|
|
173
103
|
|
|
174
|
-
#
|
|
175
|
-
#
|
|
176
|
-
# storehouse) — every unqualified table/view/function reference
|
|
177
|
-
# this adapter and its lineage classes ever construct resolves
|
|
178
|
-
# through search_path, so this one SET is what makes ALTER
|
|
179
|
-
# TABLE ... SET SCHEMA migrations transparent to the rest of the
|
|
180
|
-
# adapter. A domain with no `schema` setting keeps Postgres's
|
|
181
|
-
# own default search_path (public).
|
|
104
|
+
# A declared `schema` means the instance is shared; search_path makes every unqualified
|
|
105
|
+
# name this adapter and its lineage classes build resolve inside that schema.
|
|
182
106
|
schema = setting(settings, :schema)
|
|
183
107
|
if schema.to_s != ""
|
|
184
|
-
#
|
|
185
|
-
# naming a `schema:` nobody has created yet fails on its first
|
|
186
|
-
# table-creation attempt with Postgres's own "no schema has been
|
|
187
|
-
# selected to create in", found live provisioning tenant_
|
|
188
|
-
# isolation_spec.rb's own multi-schema fixture by hand.
|
|
189
|
-
# `CREATE SCHEMA IF NOT EXISTS` is exactly the
|
|
190
|
-
# same self-healing idempotency this adapter's own table/era
|
|
191
|
-
# provisioning already holds itself to (see the comment right
|
|
192
|
-
# below on `client_min_messages`) — a schema that already
|
|
193
|
-
# exists is the ordinary case for every boot after the first,
|
|
194
|
-
# not news.
|
|
108
|
+
# Idempotent: the schema may not exist yet on the first boot.
|
|
195
109
|
connection.exec("CREATE SCHEMA IF NOT EXISTS #{connection.quote_ident(schema)}")
|
|
196
110
|
connection.exec("SET search_path TO #{connection.quote_ident(schema)}")
|
|
197
111
|
end
|
|
198
112
|
|
|
199
|
-
#
|
|
200
|
-
#
|
|
201
|
-
# already exists is the ordinary case, not news, and Postgres
|
|
202
|
-
# surfaces every one as a notice by default. `bin/set-password`
|
|
203
|
-
# boots a real registry just to mint an Identity, and nobody
|
|
204
|
-
# setting a password needs to see a page of "relation ...
|
|
205
|
-
# already exists, skipping" to do it. Warning and above (real
|
|
206
|
-
# problems) still surface.
|
|
113
|
+
# Provisioning re-runs CREATE ... IF NOT EXISTS on every boot; silence the notices.
|
|
114
|
+
# Warnings and above still surface.
|
|
207
115
|
connection.exec("SET client_min_messages = warning")
|
|
208
116
|
connection
|
|
209
117
|
rescue PG::Error => e
|
|
@@ -211,118 +119,53 @@ module Hecks
|
|
|
211
119
|
"cannot bind PostgresEra at #{declared} for #{name}: #{e.message.strip}"
|
|
212
120
|
end
|
|
213
121
|
|
|
214
|
-
#
|
|
215
|
-
#
|
|
122
|
+
# Joins the process's shared connection for the declared database and schema, so every
|
|
123
|
+
# aggregate of a domain runs on one connection, then provisions the journal, this
|
|
124
|
+
# aggregate's head and field caches, and the event, saga and outbox tables. Every step is
|
|
125
|
+
# idempotent.
|
|
216
126
|
#
|
|
217
|
-
# @param
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
# resolved ordinal, nil to self-resolve to the newest) and `superseded_by` (the
|
|
221
|
-
# ordinal that superseded this boot's era, nil for a current-era boot)
|
|
222
|
-
# @param root [String, nil] the registry root every adapter is offered; unused here
|
|
223
|
-
# @raise [Runtime::WiringError] if the settings declare no `database`, the
|
|
224
|
-
# connection is refused, or a held era text fails its integrity check
|
|
127
|
+
# @param settings [Hash{Symbol, String => Object}] world settings plus what
|
|
128
|
+
# `RepositoryFactory.build` merges in: `domain`, `era` and `superseded_by`
|
|
129
|
+
# @raise [Runtime::WiringError] if `database` is missing or the connection is refused
|
|
225
130
|
def initialize(aggregate:, settings: {}, root: nil)
|
|
226
131
|
@aggregate = aggregate
|
|
227
132
|
@settings = settings
|
|
228
|
-
@db =
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
# (`RepositoryFactory.build`'s own `domain` positional arg —
|
|
232
|
-
# `bluebook.name`, confirmed at `outbox.rb`'s own call site —
|
|
233
|
-
# never this default in real dispatch). This default exists only
|
|
234
|
-
# for a directly-instantiated adapter (specs, consoles) that
|
|
235
|
-
# skips the factory.
|
|
236
|
-
#
|
|
237
|
-
# When the aggregate does have an owning chapter (`hecks_owner`
|
|
238
|
-
# set — true for any aggregate sealed through a real bluebook,
|
|
239
|
-
# even if this adapter itself was built by hand), default to the
|
|
240
|
-
# chapter's own declared PascalCase name. That's the same string
|
|
241
|
-
# `rust/host`'s `pg_advisory_xact_lock` key derives from
|
|
242
|
-
# (`HECKS_DOMAIN`, deploy-time-set to `world.domain`) — the
|
|
243
|
-
# aggregate's own snake_case `storage_name` silently disagreed
|
|
244
|
-
# with it (`hashtext` is byte-sensitive: `"chess"` and `"Chess"`
|
|
245
|
-
# never collide), which is ADR 0036 Blocker 1.
|
|
246
|
-
#
|
|
247
|
-
# When there's no owning chapter at all (a bare
|
|
248
|
-
# `AggregateBuilder` fixture built outside any real bluebook —
|
|
249
|
-
# `hecks_owner` is only ever stamped by chapter construction, see
|
|
250
|
-
# `traits.rb`'s `hecks_owner = self`) there is no chapter name to
|
|
251
|
-
# match Rust against in the first place, so fall back to the
|
|
252
|
-
# aggregate's own name.
|
|
133
|
+
@db = PostgresSharedConnection.for(aggregate.name, settings, connector: self.class)
|
|
134
|
+
# Journal name: the owning bluebook's declared name, matching the key rust/host derives its
|
|
135
|
+
# advisory lock from (ADR 0036). A bare aggregate with no owner falls back to its own name.
|
|
253
136
|
@domain = self.class.setting(
|
|
254
137
|
settings, :domain, default: aggregate.hecks_owner&.name || aggregate.storage_name
|
|
255
138
|
).to_s
|
|
256
139
|
@lineage = Lineage.new(@db, @domain)
|
|
257
140
|
@lineage.ensure_base!
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
# partition); a directly-instantiated adapter defaults to the
|
|
261
|
-
# newest.
|
|
262
|
-
#
|
|
263
|
-
# Not `self.class.setting(...)` here — RepositoryFactory#build
|
|
264
|
-
# always merges `era: registry.resolved_eras[domain]` into
|
|
265
|
-
# settings, so the key is genuinely present (not absent) for
|
|
266
|
-
# any domain the era boot gate hasn't resolved yet (or that
|
|
267
|
-
# doesn't have one at all), just holding `nil`. `setting`'s own
|
|
268
|
-
# presence-over-truthiness discipline (correct for a field like
|
|
269
|
-
# `:role`, where a stored `false` is a real, distinct answer
|
|
270
|
-
# from "unset") does the wrong thing for `:era` specifically:
|
|
271
|
-
# `nil` can never be a meaningful era override — only a real
|
|
272
|
-
# ordinal or "not resolved, self-resolve" ever apply — so
|
|
273
|
-
# coalescing here, not `setting`'s presence check, is what
|
|
274
|
-
# actually honors this comment's own "defaults to the newest"
|
|
275
|
-
# promise.
|
|
141
|
+
# Coalesce instead of `setting`: the factory always passes `era:`, nil until the boot gate
|
|
142
|
+
# resolves it, and nil can never be a real override.
|
|
276
143
|
@era = settings.key?(:era) ? settings[:era] : settings["era"]
|
|
277
144
|
@era ||= @lineage.current_era
|
|
278
|
-
#
|
|
279
|
-
#
|
|
280
|
-
# boot only, and `RepositoryFactory.build` merges it in here as
|
|
281
|
-
# `superseded_by:` — so this is nil for every current-era boot and
|
|
282
|
-
# for a directly-instantiated adapter (specs, consoles), which,
|
|
283
|
-
# like `era:` above, self-resolves rather than being told. When
|
|
284
|
-
# set, `append`/`atomic_put` refuse before issuing an INSERT (see
|
|
285
|
-
# `refuse_superseded_write!`): the RLS fence already refuses the
|
|
286
|
-
# same write for an ordinary role, but a superuser walks through
|
|
287
|
-
# RLS (BUG#24), and this checkout's own knowledge that it is stale
|
|
288
|
-
# is the one guard no role attribute can void. Same coalescing as
|
|
289
|
-
# `era:` — the key is always present from the factory, holding nil
|
|
290
|
-
# for the ordinary case.
|
|
145
|
+
# Non-nil only for a held-but-superseded boot. `append` and `atomic_put` refuse on it
|
|
146
|
+
# because a superuser walks through the RLS fence.
|
|
291
147
|
@superseded_by = settings.key?(:superseded_by) ? settings[:superseded_by] : settings["superseded_by"]
|
|
292
|
-
#
|
|
293
|
-
# suspenders self-healing (compile_head! already ensures this for
|
|
294
|
-
# a freshly-minted era's own name; ensure_first_head! for era 1's)
|
|
295
|
-
# against any boot-ordering surprise, at the cost of one
|
|
296
|
-
# CREATE TABLE IF NOT EXISTS nobody pays for twice.
|
|
148
|
+
# Idempotent self-healing against any boot-ordering surprise.
|
|
297
149
|
@lineage.ensure_head_snapshot!(table, @era)
|
|
298
150
|
@lineage.ensure_first_head!(table) if @era == 1
|
|
299
|
-
#
|
|
300
|
-
#
|
|
301
|
-
# table per `where`-field this aggregate's own declared queries
|
|
302
|
-
# (and its entities' own) actually use, derived automatically
|
|
303
|
-
# (principle 3 — no bluebook keyword), self-healing and
|
|
304
|
-
# idempotent like everything else booted here. `@field_caches`
|
|
305
|
-
# maps field -> cache-table name; `query` below consults it to
|
|
306
|
-
# decide whether a declared query can skip the DISTINCT ON
|
|
307
|
-
# reduction entirely.
|
|
151
|
+
# One row-cache table per where-field the aggregate's queries use; `query` consults it
|
|
152
|
+
# to skip the head reduction.
|
|
308
153
|
@field_caches = ensure_field_caches!
|
|
309
154
|
create_event_table!
|
|
310
155
|
create_saga_table!
|
|
311
156
|
create_outbox_table!
|
|
312
157
|
end
|
|
313
158
|
|
|
314
|
-
# Names this aggregate in storage: the journal's `aggregate`
|
|
315
|
-
#
|
|
159
|
+
# Names this aggregate in storage: the journal's `aggregate` value and the stem of its
|
|
160
|
+
# head view, head snapshot and field-cache names.
|
|
316
161
|
#
|
|
317
162
|
# @return [String] the aggregate's snake_case storage name
|
|
318
163
|
def table = @aggregate.storage_name
|
|
319
164
|
|
|
320
|
-
# Reads one record's current state through the
|
|
321
|
-
# written under an ancestor era comes back translated to the current shape.
|
|
165
|
+
# Reads one record's current state through the head view, translated to the current shape.
|
|
322
166
|
#
|
|
323
167
|
# @param id [String, Object] the record's identity, compared as `id.to_s`
|
|
324
|
-
# @return [Runtime::Instance, nil] the
|
|
325
|
-
# saved row for that id (never written, or deleted)
|
|
168
|
+
# @return [Runtime::Instance, nil] the record, or nil when never written or deleted
|
|
326
169
|
def find(id)
|
|
327
170
|
result = @db.exec_params(%(SELECT id, state FROM #{quoted_head} WHERE id = $1), [id.to_s])
|
|
328
171
|
return nil if result.ntuples.zero?
|
|
@@ -332,21 +175,12 @@ module Hecks
|
|
|
332
175
|
|
|
333
176
|
# Lists every current record, ordered by id unless the caller names a field.
|
|
334
177
|
#
|
|
335
|
-
# order_by is a runtime value,
|
|
336
|
-
#
|
|
337
|
-
# HTTP request, in the console's case. Whitelisted against the
|
|
338
|
-
# aggregate's own real attributes (plus its lifecycle field) before
|
|
339
|
-
# it ever reaches order_expression, unlike a declared query's
|
|
340
|
-
# order_by, which the language itself already only lets name a real
|
|
341
|
-
# attribute at parse time. Without this, an unknown field wouldn't
|
|
342
|
-
# error — query_expression degrades a nil attribute to a harmless
|
|
343
|
-
# no-op path — it would just silently sort by nothing.
|
|
178
|
+
# `order_by` is a runtime value (an HTTP param), so it is checked against the aggregate's
|
|
179
|
+
# attributes; an unknown field would otherwise sort by nothing without erroring.
|
|
344
180
|
#
|
|
345
|
-
# @param order_by [String, Symbol, nil] an attribute
|
|
346
|
-
#
|
|
347
|
-
# @
|
|
348
|
-
# else ascending; nulls sort first ascending and last descending
|
|
349
|
-
# @return [Array<Runtime::Instance>] every saved record, `[]` when there are none
|
|
181
|
+
# @param order_by [String, Symbol, nil] an attribute, the lifecycle field, or a dotted path
|
|
182
|
+
# @param direction [Symbol, String] `desc` sorts descending; nulls sort first ascending
|
|
183
|
+
# @return [Array<Runtime::Instance>] every saved record
|
|
350
184
|
# @raise [Runtime::WiringError] if `order_by` names no attribute of this aggregate
|
|
351
185
|
def all(order_by: nil, direction: :asc)
|
|
352
186
|
return @db.exec(%(SELECT id, state FROM #{quoted_head} ORDER BY id)).map { |row| instance(row) } unless order_by
|
|
@@ -366,51 +200,16 @@ module Hecks
|
|
|
366
200
|
# @return [Integer] how many saved, undeleted records the head holds
|
|
367
201
|
def count = @db.exec(%(SELECT COUNT(*) FROM #{quoted_head}))[0]["count"].to_i
|
|
368
202
|
|
|
369
|
-
# Runs a declared query, looking candidate ids up in the field caches
|
|
370
|
-
#
|
|
203
|
+
# Runs a declared query, looking candidate ids up in the field caches when every `where`
|
|
204
|
+
# clause allows it and only then reading those ids from the head view.
|
|
371
205
|
#
|
|
372
|
-
# The
|
|
373
|
-
#
|
|
374
|
-
# principle 2) always runs correctly here — it filters against
|
|
375
|
-
# `head_view`, which is already the fully-reduced current state —
|
|
376
|
-
# but for a domain that has minted a second era, that reduction
|
|
377
|
-
# itself is the expensive part, and no index on the jsonb path
|
|
378
|
-
# changes that (see field_cache.rb's own header for the SQL-
|
|
379
|
-
# semantics reason why). When every `where` clause this query
|
|
380
|
-
# declares either targets a cached field or is eligible to (an
|
|
381
|
-
# ordinary comparator, not a null-vs-value special case — see
|
|
382
|
-
# `cache_eligible?`), skip the reduction: look candidate ids up in
|
|
383
|
-
# the cache table(s) first (cheap, indexed, no reduction involved),
|
|
384
|
-
# then read only those ids' current state from `head_view` — safe
|
|
385
|
-
# through the reduction because `id` is its own partition key. Any
|
|
386
|
-
# clause that isn't cache-eligible (an uncached field, or a null
|
|
387
|
-
# comparison) is simply re-checked against `head_view` in the
|
|
388
|
-
# second phase, exactly as `super` would have checked it anyway —
|
|
389
|
-
# this can only ever narrow what phase two has to look at, never
|
|
390
|
-
# change what a clause means.
|
|
391
|
-
#
|
|
392
|
-
# Falls back to `super` whenever no clause can be accelerated — a
|
|
393
|
-
# query with no `where` at all (order_by-only — no cache table
|
|
394
|
-
# exists for these, see field_cache.rb), a query whose only clauses
|
|
395
|
-
# target fields with no cache table, or a domain that has never
|
|
396
|
-
# minted a second era at all (`@field_caches` is never empty just
|
|
397
|
-
# because era 1 has no reduction to skip — the cache tables still
|
|
398
|
-
# exist and still accelerate era 1 the same way, but the fallback
|
|
399
|
-
# path is already just as cheap there since head_view is the
|
|
400
|
-
# snapshot table verbatim for era 1; skipping straight to `super`
|
|
401
|
-
# in that case would be a valid future optimization, not attempted
|
|
402
|
-
# here to keep this one code path correct for every era uniformly).
|
|
206
|
+
# The cache only narrows candidates; the head view stays authoritative, so results match
|
|
207
|
+
# `super`. Uncached or null-comparison clauses are re-checked against the head view.
|
|
403
208
|
#
|
|
404
209
|
# @param declared [Bluebook::Query] the declared query, or a delegator wrapping one
|
|
405
|
-
#
|
|
406
|
-
# @
|
|
407
|
-
#
|
|
408
|
-
# @param context [Hash] execution context the query port passes to every adapter;
|
|
409
|
-
# unused here
|
|
410
|
-
# @return [Array<Runtime::Instance>] the matching records in declared order, or by
|
|
411
|
-
# id when the query declares none; `[]` when nothing matches
|
|
412
|
-
# @raise [ArgumentError] if a clause uses an operator this dialect cannot compile,
|
|
413
|
-
# or `contains` on a list of multi-field value objects
|
|
210
|
+
# @param args [Hash{Symbol => Object}] caller-supplied values for named arguments
|
|
211
|
+
# @return [Array<Runtime::Instance>] the matching records, by id when no order is declared
|
|
212
|
+
# @raise [ArgumentError] if a clause uses an operator this dialect cannot compile
|
|
414
213
|
def query(declared, args = {}, context: {})
|
|
415
214
|
return super if @field_caches.empty? || declared.wheres.empty?
|
|
416
215
|
|
|
@@ -424,59 +223,27 @@ module Hecks
|
|
|
424
223
|
head_phase(declared, uncached, ids, args)
|
|
425
224
|
end
|
|
426
225
|
|
|
427
|
-
# Runs a block
|
|
226
|
+
# Runs a block in one transaction holding the domain's cross-process write lock (ADR 0036).
|
|
428
227
|
#
|
|
429
|
-
#
|
|
430
|
-
#
|
|
431
|
-
# `append`'s own transaction below: `lock_writes!` has to be held
|
|
432
|
-
# before hydrate even starts, or two cross-process writers can
|
|
433
|
-
# both hydrate unlocked and race for the write, each blind to the
|
|
434
|
-
# other. `transaction` (via `include Adapters::PostgresOutbox`) is
|
|
435
|
-
# already re-entrant — `append`/`atomic_put`'s own inner
|
|
436
|
-
# `transaction do ... end`, deep inside the block below, joins
|
|
437
|
-
# this same transaction instead of opening/committing its own, so
|
|
438
|
-
# the advisory lock stays held until this whole block returns.
|
|
228
|
+
# The lock must be taken before hydrate, so it wraps the whole dispatch order; the
|
|
229
|
+
# nested `append` joins this transaction and the lock is held until the block returns.
|
|
439
230
|
#
|
|
440
|
-
# @yield the dispatch order
|
|
441
|
-
# transaction back
|
|
442
|
-
# @yieldreturn [Object] whatever the dispatch order produces
|
|
231
|
+
# @yield the dispatch order; an exception rolls the transaction back
|
|
443
232
|
# @return [Object] the block's own value
|
|
444
|
-
# @raise [PG::ConnectionBad] if the connection dropped;
|
|
445
|
-
# then re-raises so the caller decides whether to retry
|
|
446
|
-
# @raise [Runtime::WiringError] if the connection dropped and the reconnect attempt
|
|
447
|
-
# (`connect_for`) is refused as well
|
|
233
|
+
# @raise [PG::ConnectionBad] if the connection dropped; it reconnects, then re-raises
|
|
448
234
|
def with_write_lock(&block) = transaction { lock_writes!; block.call } # rubocop:disable Style/Semicolon
|
|
449
235
|
|
|
450
|
-
# Appends one entry to the journal and
|
|
451
|
-
#
|
|
236
|
+
# Appends one entry to the journal and updates the head snapshot and every field cache,
|
|
237
|
+
# all in one transaction under the domain's write lock.
|
|
452
238
|
#
|
|
453
|
-
# The lock
|
|
454
|
-
#
|
|
455
|
-
#
|
|
456
|
-
# default evaluates. A different key from `mint_era!`/`merge_tail!`'s
|
|
457
|
-
# `hecks_eras:domain` : this serializes plain writes against each
|
|
458
|
-
# other, never against a mint. See postgres/lineage.rb's own comment
|
|
459
|
-
# for why only that half of the race is closed.
|
|
460
|
-
# The journal insert and the snapshot upsert/delete happen in the
|
|
461
|
-
# same transaction — real ACID atomicity, not the append-then-
|
|
462
|
-
# project two-step a file-based adapter needs a crash-recovery
|
|
463
|
-
# replay for (see Heki). If this transaction commits, the snapshot
|
|
464
|
-
# is already exactly as current as the journal; if it doesn't,
|
|
465
|
-
# neither happened. `project` stays uninvolved on purpose — it
|
|
466
|
-
# still runs, cheaply, during AppendOnly#recover!'s full replay on
|
|
467
|
-
# every boot (see `project` below), and a second write there would
|
|
468
|
-
# make that replay pay real DB cost for a snapshot that's already
|
|
469
|
-
# correct. When `with_write_lock` above already drove this whole
|
|
470
|
-
# dispatch, `lock_writes!` here is a harmless re-acquire of the
|
|
471
|
-
# same already-held (per-session-reentrant) advisory lock; a bare
|
|
472
|
-
# `repository.save` outside a full dispatch still takes it fresh.
|
|
239
|
+
# The lock precedes the INSERT because the ordinal comes from `nextval()` inside it. It is
|
|
240
|
+
# a different key from `mint_era!`'s, so it serializes writes, never a write against a mint.
|
|
241
|
+
# `project` stays out of it: AppendOnly#recover! already replays it on every boot.
|
|
473
242
|
#
|
|
474
|
-
# @param entry [Ports::Persistence::Entry] the save or delete to journal
|
|
475
|
-
# is decoded domain state, encoded through `StateCodec` on the way in
|
|
243
|
+
# @param entry [Ports::Persistence::Entry] the save or delete to journal
|
|
476
244
|
# @return [Ports::Persistence::Entry] the same entry, unchanged
|
|
477
245
|
# @raise [Runtime::WiringError] if this boot's era has been superseded by a mint
|
|
478
|
-
# @raise [PG::Error] if Postgres refuses a statement, such as the era fence's
|
|
479
|
-
# row-level security rejecting the INSERT
|
|
246
|
+
# @raise [PG::Error] if Postgres refuses a statement, such as the era fence's RLS
|
|
480
247
|
def append(entry)
|
|
481
248
|
refuse_superseded_write!
|
|
482
249
|
transaction do
|
|
@@ -487,16 +254,10 @@ module Hecks
|
|
|
487
254
|
end
|
|
488
255
|
|
|
489
256
|
# Refuses a write from a checkout whose era a later mint has superseded.
|
|
490
|
-
#
|
|
491
|
-
# Before the transaction, before the lock, before the INSERT — a
|
|
492
|
-
# superseded checkout takes nothing and touches nothing. Reads are
|
|
493
|
-
# deliberately untouched: `EraResolver.check!`'s own contract for an
|
|
494
|
-
# old checkout is "may keep booting and reading, but may not keep
|
|
495
|
-
# writing", and the head views it reads through are its own era's.
|
|
257
|
+
# Runs before the transaction so a stale checkout takes no lock; reads stay allowed.
|
|
496
258
|
#
|
|
497
259
|
# @return [nil] when this boot's era is current
|
|
498
260
|
# @raise [Runtime::WiringError] if the boot gate marked this era superseded
|
|
499
|
-
# (`superseded_by` in the settings)
|
|
500
261
|
def refuse_superseded_write!
|
|
501
262
|
return unless @superseded_by
|
|
502
263
|
|
|
@@ -507,19 +268,12 @@ module Hecks
|
|
|
507
268
|
"reboot to write again."
|
|
508
269
|
end
|
|
509
270
|
|
|
510
|
-
# Saves an entry and reports whether it inserted or replaced,
|
|
511
|
-
#
|
|
512
|
-
#
|
|
513
|
-
# Outcome detection, journal append and every derived projection share
|
|
514
|
-
# the same transaction and domain write lock. The lineage-aware head
|
|
515
|
-
# determines whether this id is already visible; no repository `find`
|
|
516
|
-
# occurs before entering this adapter-native operation.
|
|
271
|
+
# Saves an entry and reports whether it inserted or replaced, decided under the same lock
|
|
272
|
+
# that guards the write.
|
|
517
273
|
#
|
|
518
274
|
# @param entry [Ports::Persistence::Entry] the save to journal
|
|
519
|
-
# @param insert_only [Boolean] when true, an id the head
|
|
520
|
-
#
|
|
521
|
-
# @return [Symbol] `:inserted`, `:replaced`, or `:conflicted` when `insert_only` met
|
|
522
|
-
# an existing record
|
|
275
|
+
# @param insert_only [Boolean] when true, an id already in the head is left untouched
|
|
276
|
+
# @return [Symbol] `:inserted`, `:replaced`, or `:conflicted` when `insert_only` hit a record
|
|
523
277
|
# @raise [Runtime::WiringError] if this boot's era has been superseded by a mint
|
|
524
278
|
# @raise [PG::Error] if Postgres refuses a statement
|
|
525
279
|
def atomic_put(entry, insert_only: false)
|
|
@@ -541,31 +295,22 @@ module Hecks
|
|
|
541
295
|
status
|
|
542
296
|
end
|
|
543
297
|
|
|
544
|
-
# Builds the instance an entry describes, writing nothing
|
|
545
|
-
#
|
|
546
|
-
# The head is derived — projecting is reading, so there is nothing
|
|
547
|
-
# to write here. `append` above already keeps the snapshot the head
|
|
548
|
-
# view reads from current, transactionally. The instance is still
|
|
549
|
-
# built (and validated) so a save returns what every other adapter
|
|
550
|
-
# returns.
|
|
298
|
+
# Builds the instance an entry describes, writing nothing; the head is derived, so
|
|
299
|
+
# projecting is reading.
|
|
551
300
|
#
|
|
552
301
|
# @param entry [Ports::Persistence::Entry] a journaled save or delete
|
|
553
302
|
# @return [Runtime::Instance, nil] the saved record, or nil for a delete entry
|
|
554
|
-
# @raise [Runtime::WiringError] if the entry's state is still in its stored
|
|
555
|
-
# (undecoded) form; see `Ports::Persistence::CodecBoundary`
|
|
303
|
+
# @raise [Runtime::WiringError] if the entry's state is still in its stored form
|
|
556
304
|
def project(entry)
|
|
557
305
|
return if entry.delete?
|
|
558
306
|
|
|
559
307
|
Runtime::Instance.new(aggregate: @aggregate, id: entry.id, state: entry.state)
|
|
560
308
|
end
|
|
561
309
|
|
|
562
|
-
# Reads this aggregate's whole journal, every era, in
|
|
563
|
-
#
|
|
564
|
-
# it was written in.
|
|
310
|
+
# Reads this aggregate's whole journal, every era, in write order. States are decoded but
|
|
311
|
+
# not translated: an ancestor era's row keeps the shape it was written in.
|
|
565
312
|
#
|
|
566
|
-
# @return [Array<Ports::Persistence::Entry>]
|
|
567
|
-
# a delete's `state` is nil, `mirrors` is nil when none were recorded, and a row
|
|
568
|
-
# with no recorded operation reads as `"save"`; `[]` for an empty journal
|
|
313
|
+
# @return [Array<Ports::Persistence::Entry>] entries by ascending ordinal
|
|
569
314
|
def entries
|
|
570
315
|
@db.exec_params(
|
|
571
316
|
"SELECT aggregate_id, operation, state, mirrors FROM #{@lineage.quoted_journal} " \
|
|
@@ -582,30 +327,15 @@ module Hecks
|
|
|
582
327
|
end
|
|
583
328
|
end
|
|
584
329
|
|
|
585
|
-
# Deletes this aggregate's journal rows, refusing when row-level security
|
|
586
|
-
#
|
|
587
|
-
#
|
|
588
|
-
# The journal carries FORCE ROW LEVEL SECURITY with exactly two
|
|
589
|
-
# policies — hecks_current_era's INSERT and hecks_read_all's
|
|
590
|
-
# SELECT (advance_era! above) — and no DELETE policy at all, for
|
|
591
|
-
# anyone. `FORCE` means even the table's own owner is fenced by
|
|
592
|
-
# that (only an actual Postgres superuser or a role granted
|
|
593
|
-
# BYPASSRLS sits above it — see lineage.rb's own header), so a
|
|
594
|
-
# plain `DELETE ... WHERE aggregate = $1` from an ordinary
|
|
595
|
-
# connection silently matches zero rows: no privilege error, no
|
|
596
|
-
# exception, just a no-op that looks like success. Counting
|
|
597
|
-
# before and comparing to what the DELETE itself reports is what
|
|
598
|
-
# tells "nothing to delete" apart from "RLS silently ate the
|
|
599
|
-
# delete" — the same row count, from the same statement, either
|
|
600
|
-
# way, with no separate query racing the DELETE for an answer.
|
|
330
|
+
# Deletes this aggregate's journal rows, refusing when row-level security turns the DELETE
|
|
331
|
+
# into a silent no-op.
|
|
601
332
|
#
|
|
602
|
-
#
|
|
603
|
-
#
|
|
333
|
+
# The journal forces RLS with no DELETE policy, so an ordinary connection matches zero rows
|
|
334
|
+
# without error; comparing the count before with the DELETE's own count exposes that.
|
|
335
|
+
# Head snapshots, field caches, `events` and saga rows are left as they are.
|
|
604
336
|
#
|
|
605
337
|
# @return [Adapters::PostgresEra] this adapter
|
|
606
|
-
# @raise [Runtime::WiringError] if the journal held rows
|
|
607
|
-
# DELETE removed none, which means the connection is neither a superuser nor
|
|
608
|
-
# granted BYPASSRLS
|
|
338
|
+
# @raise [Runtime::WiringError] if the journal held rows and the DELETE removed none
|
|
609
339
|
def reset!
|
|
610
340
|
before = @db.exec_params(
|
|
611
341
|
"SELECT count(*) FROM #{@lineage.quoted_journal} WHERE aggregate = $1", [table]
|
|
@@ -621,86 +351,39 @@ module Hecks
|
|
|
621
351
|
self
|
|
622
352
|
end
|
|
623
353
|
|
|
624
|
-
# Journals an instance's current state as a save entry.
|
|
625
|
-
#
|
|
626
|
-
# `Ports::Persistence::AppendOnly`, which builds the entry itself.
|
|
354
|
+
# Journals an instance's current state as a save entry. A direct-adapter convenience for
|
|
355
|
+
# specs and consoles; a runtime saves through `Ports::Persistence::AppendOnly`.
|
|
627
356
|
#
|
|
628
357
|
# @param instance [Runtime::Instance] the record to persist
|
|
629
358
|
# @return [Runtime::Instance] a fresh instance built from the journaled state
|
|
630
359
|
# @raise [Runtime::WiringError] if this boot's era has been superseded by a mint
|
|
631
|
-
# @raise [PG::Error] if Postgres refuses a statement
|
|
632
360
|
def save(instance)
|
|
633
361
|
entry = Ports::Persistence::Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
|
|
634
362
|
append(entry)
|
|
635
363
|
project(entry)
|
|
636
364
|
end
|
|
637
365
|
|
|
638
|
-
# Journals a delete entry
|
|
639
|
-
#
|
|
366
|
+
# Journals a delete entry and tombstones the id in the head snapshot, without checking
|
|
367
|
+
# that the record exists.
|
|
640
368
|
#
|
|
641
369
|
# @param id [String, Object] the record's identity, journaled as `id.to_s`
|
|
642
|
-
# @return [true] always
|
|
370
|
+
# @return [true] always
|
|
643
371
|
# @raise [Runtime::WiringError] if this boot's era has been superseded by a mint
|
|
644
|
-
# @raise [PG::Error] if Postgres refuses a statement
|
|
645
372
|
def delete(id)
|
|
646
373
|
entry = Ports::Persistence::Entry.new(operation: "delete", id: id.to_s, state: nil)
|
|
647
374
|
append(entry)
|
|
648
375
|
true
|
|
649
376
|
end
|
|
650
377
|
|
|
651
|
-
#
|
|
652
|
-
#
|
|
653
|
-
#
|
|
654
|
-
# @param event [Runtime::Event] the event to record; `payload` is stored as JSON,
|
|
655
|
-
# `occurred_at` as text (an ISO 8601 UTC string, or nil), and `correlation` is
|
|
656
|
-
# not stored
|
|
657
|
-
# @return [PG::Result] the INSERT's result, which carries no rows
|
|
658
|
-
def record_event(event)
|
|
659
|
-
@db.exec_params(
|
|
660
|
-
"INSERT INTO events (name, aggregate, aggregate_id, payload, occurred_at) VALUES ($1, $2, $3, $4, $5)",
|
|
661
|
-
[event.name, event.aggregate, event.id.to_s, JSON.generate(event.payload), event.occurred_at]
|
|
662
|
-
)
|
|
663
|
-
end
|
|
664
|
-
|
|
665
|
-
# Reads every recorded event in the order it was recorded. The `events` table is
|
|
666
|
-
# shared, so this is not limited to this adapter's aggregate.
|
|
667
|
-
#
|
|
668
|
-
# @return [Array<Runtime::Event>] events with symbol-keyed `payload`, `occurred_at`
|
|
669
|
-
# as stored text and `correlation` nil; `[]` when none are recorded
|
|
670
|
-
def events
|
|
671
|
-
@db.exec("SELECT * FROM events ORDER BY id").map do |row|
|
|
672
|
-
Runtime::Event.new(
|
|
673
|
-
name: row["name"],
|
|
674
|
-
aggregate: row["aggregate"],
|
|
675
|
-
id: row["aggregate_id"],
|
|
676
|
-
payload: JSON.parse(row["payload"], symbolize_names: true),
|
|
677
|
-
occurred_at: row["occurred_at"]
|
|
678
|
-
)
|
|
679
|
-
end
|
|
680
|
-
end
|
|
378
|
+
# Saga rows keep `domain` as a column so domains sharing one schema stay isolated. No lock
|
|
379
|
+
# here: `SagaInterpreter`'s own mutex already serializes in-process writers.
|
|
681
380
|
|
|
682
|
-
#
|
|
683
|
-
#
|
|
684
|
-
# correlation), `domain` kept as an explicit column even under
|
|
685
|
-
# schema isolation so two domains sharing one schema (neither
|
|
686
|
-
# declares its own `schema`) still isolate correctly, matching
|
|
687
|
-
# `hecks_eras`' own precedent (postgres/lineage/provisioning.rb).
|
|
688
|
-
# No advisory lock of its own: every call here already runs inside
|
|
689
|
-
# `SagaInterpreter`'s own mutex (§7) serializing in-process writers,
|
|
690
|
-
# and gets the same cross-process safety an aggregate's own writes
|
|
691
|
-
# get from this adapter — no better, no worse.
|
|
692
|
-
|
|
693
|
-
# Checkpoints one saga instance, replacing the row for the same
|
|
694
|
-
# (domain, process manager, correlation) if one exists.
|
|
381
|
+
# Checkpoints one saga instance, replacing the row for the same (domain, process manager,
|
|
382
|
+
# correlation) if one exists.
|
|
695
383
|
#
|
|
696
|
-
# @param process_manager [String, Symbol] the process manager's name
|
|
697
|
-
# @param correlation [String, Object] the instance's correlation value, stored as
|
|
698
|
-
# `correlation.to_s`
|
|
699
|
-
# @param state [String, Symbol] the saga's current state name
|
|
700
384
|
# @param memory [Hash] the saga's memory, stored as JSON
|
|
701
|
-
# @param completed_compensations [Array]
|
|
702
|
-
#
|
|
703
|
-
# @return [PG::Result] the upsert's result, which carries no rows
|
|
385
|
+
# @param completed_compensations [Array] compensable legs already completed, stored as JSON
|
|
386
|
+
# @return [PG::Result] the upsert's result
|
|
704
387
|
def save_saga(process_manager:, correlation:, state:, memory:, completed_compensations: [])
|
|
705
388
|
@db.exec_params(
|
|
706
389
|
"INSERT INTO hecks_saga_instances (domain, process_manager, correlation, state, memory, completed_compensations) " \
|
|
@@ -715,10 +398,7 @@ module Hecks
|
|
|
715
398
|
|
|
716
399
|
# Removes a finished saga instance's checkpoint; a no-op when no such row exists.
|
|
717
400
|
#
|
|
718
|
-
# @
|
|
719
|
-
# @param correlation [String, Object] the instance's correlation value, matched as
|
|
720
|
-
# `correlation.to_s`
|
|
721
|
-
# @return [PG::Result] the DELETE's result, which carries no rows
|
|
401
|
+
# @return [PG::Result] the DELETE's result
|
|
722
402
|
def delete_saga(process_manager:, correlation:)
|
|
723
403
|
@db.exec_params(
|
|
724
404
|
"DELETE FROM hecks_saga_instances WHERE domain = $1 AND process_manager = $2 AND correlation = $3",
|
|
@@ -726,18 +406,10 @@ module Hecks
|
|
|
726
406
|
)
|
|
727
407
|
end
|
|
728
408
|
|
|
729
|
-
# Yields every saga checkpoint stored for this domain, so a booting registry can
|
|
730
|
-
# rehydrate its in-flight sagas.
|
|
409
|
+
# Yields every saga checkpoint stored for this domain, so a booting registry can rehydrate.
|
|
731
410
|
#
|
|
732
|
-
# @yieldparam
|
|
733
|
-
# @
|
|
734
|
-
# @yieldparam state [String] the saga's state name
|
|
735
|
-
# @yieldparam memory [Hash{Symbol => Object}] the saga's memory, keys symbolized at
|
|
736
|
-
# every depth
|
|
737
|
-
# @yieldparam completed_compensations [Array] the completed-compensation ledger,
|
|
738
|
-
# `[]` when the row stores none
|
|
739
|
-
# @return [Enumerator, PG::Result] an enumerator over the same five values when no
|
|
740
|
-
# block is given; otherwise the query result, which callers ignore
|
|
411
|
+
# @yieldparam memory [Hash{Symbol => Object}] the saga's memory, keys symbolized
|
|
412
|
+
# @return [Enumerator, PG::Result] an enumerator when no block is given
|
|
741
413
|
def each_saga
|
|
742
414
|
return enum_for(:each_saga) unless block_given?
|
|
743
415
|
|
|
@@ -777,34 +449,14 @@ module Hecks
|
|
|
777
449
|
"state = EXCLUDED.state WHERE #{quoted_head_snapshot}.ordinal < EXCLUDED.ordinal",
|
|
778
450
|
[entry.id, ordinal, state_json]
|
|
779
451
|
)
|
|
780
|
-
#
|
|
781
|
-
# exactly as current as the snapshot it's derived from, for
|
|
782
|
-
# the identical reason `postgres_era.rb`'s own header comment
|
|
783
|
-
# gives for the journal/snapshot pair: if this transaction
|
|
784
|
-
# commits, every cache row is already correct; if it doesn't,
|
|
785
|
-
# none of them changed.
|
|
452
|
+
# Same transaction and ordinal, so every cache is exactly as current as the snapshot.
|
|
786
453
|
@field_caches.each do |field, cache_table|
|
|
787
454
|
@lineage.upsert_field_cache_row!(cache_table, entry.id, ordinal, state_json, query_expression(field))
|
|
788
455
|
end
|
|
789
456
|
else
|
|
790
|
-
# A tombstone row, not a bare DELETE
|
|
791
|
-
#
|
|
792
|
-
#
|
|
793
|
-
# once an ancestor era is in the picture: for a record carried
|
|
794
|
-
# into this era from an ancestor, removing this era's row leaves
|
|
795
|
-
# nothing on the current-era side of `compile_head!`'s union to
|
|
796
|
-
# outrank the ancestor matview's own (still-present, still
|
|
797
|
-
# `save`) row, so `DISTINCT ON` picks the ancestor's row and
|
|
798
|
-
# the "deleted" record keeps reading back forever. Upserting a
|
|
799
|
-
# tombstone (`operation = 'delete'`, `state` NULL) instead
|
|
800
|
-
# means this era always has its own newest-ordinal row for the
|
|
801
|
-
# id, exactly like a real re-save does ("re-saves are
|
|
802
|
-
# masked correctly" — the audit's own phrasing for why that
|
|
803
|
-
# half never breaks) — it just carries `operation
|
|
804
|
-
# = 'delete'` instead of `'save'`, so `head_view`'s own `WHERE
|
|
805
|
-
# operation = 'save'` still correctly hides it. Ordinal-guarded
|
|
806
|
-
# the same as every other upsert here, so an out-of-order
|
|
807
|
-
# replay can never let a stale delete clobber a newer save.
|
|
457
|
+
# A tombstone row, not a bare DELETE: without one, an ancestor era's saved row would win
|
|
458
|
+
# the head's DISTINCT ON and the deleted record would read back. The ordinal guard keeps
|
|
459
|
+
# a stale replayed delete from clobbering a newer save.
|
|
808
460
|
@db.exec_params(
|
|
809
461
|
"INSERT INTO #{quoted_head_snapshot} (id, ordinal, operation, state) VALUES ($1, $2, 'delete', NULL) " \
|
|
810
462
|
"ON CONFLICT (id) DO UPDATE SET ordinal = EXCLUDED.ordinal, operation = EXCLUDED.operation, " \
|
|
@@ -817,8 +469,6 @@ module Hecks
|
|
|
817
469
|
ordinal
|
|
818
470
|
end
|
|
819
471
|
|
|
820
|
-
# ── SqlQueryBuilder's dialect hooks ─────────────────────────────
|
|
821
|
-
|
|
822
472
|
def select_list = "id, state"
|
|
823
473
|
def from_relation = quoted_head
|
|
824
474
|
def dialect_name = "PostgresEra"
|
|
@@ -854,17 +504,11 @@ module Hecks
|
|
|
854
504
|
@db.exec_params(sql, binds).map { |row| instance(row) }
|
|
855
505
|
end
|
|
856
506
|
|
|
857
|
-
# ── the rest of the dialect ─────────────────────────────────────
|
|
858
|
-
|
|
859
507
|
def instance(row)
|
|
860
508
|
Runtime::Instance.new(aggregate: @aggregate, id: row["id"], state: decode(row["state"]))
|
|
861
509
|
end
|
|
862
510
|
|
|
863
|
-
#
|
|
864
|
-
# translation has already run: the head view hands back the
|
|
865
|
-
# translated jsonb, and decoding it is the last step, the same as
|
|
866
|
-
# every other adapter's read. A never-seeded projected field is
|
|
867
|
-
# simply absent from the blob, and stays absent.
|
|
511
|
+
# Runs after SQL-side era translation, as the last step of every read.
|
|
868
512
|
def decode(state_json)
|
|
869
513
|
Ports::Persistence::StateCodec.decode(@aggregate, JSON.parse(state_json))
|
|
870
514
|
end
|
|
@@ -878,11 +522,8 @@ module Hecks
|
|
|
878
522
|
numeric_field?(field) ? "(#{expression})::numeric" : expression
|
|
879
523
|
end
|
|
880
524
|
|
|
881
|
-
# Postgres defaults to NULLS LAST on ASC;
|
|
882
|
-
#
|
|
883
|
-
# to match) put null rows FIRST ascending and LAST
|
|
884
|
-
# descending. Compile the placement explicitly so a declared query
|
|
885
|
-
# answers identically no matter which adapter serves it.
|
|
525
|
+
# Postgres defaults to NULLS LAST on ASC; place nulls explicitly (first ascending, last
|
|
526
|
+
# descending) so a declared query answers the same on every adapter.
|
|
886
527
|
def order_clause(order_by, policy)
|
|
887
528
|
direction = order_by.direction.to_s.downcase == "desc" ? "DESC" : "ASC"
|
|
888
529
|
nulls = case policy&.mode.to_s
|
|
@@ -893,10 +534,7 @@ module Hecks
|
|
|
893
534
|
"#{order_expression(order_by.field)} #{direction}#{nulls}, id #{direction}"
|
|
894
535
|
end
|
|
895
536
|
|
|
896
|
-
# One shared walk decides numericness at any depth
|
|
897
|
-
# only the first nested segment would let a two-level path
|
|
898
|
-
# (pizza.price_cents.cents) skip the ::numeric cast and order
|
|
899
|
-
# as text: "900" above "1200".
|
|
537
|
+
# One shared walk decides numericness at any depth, so a nested path is not ordered as text.
|
|
900
538
|
def numeric_field?(field)
|
|
901
539
|
name, *path = field.to_s.split(".")
|
|
902
540
|
QuerySpecification::FieldPath.numeric?(@aggregate.attribute(name), path) do |type|
|
|
@@ -904,31 +542,16 @@ module Hecks
|
|
|
904
542
|
end
|
|
905
543
|
end
|
|
906
544
|
|
|
907
|
-
# `ARRAY[...]` of
|
|
908
|
-
#
|
|
909
|
-
# value-object member name, and while today's callers only ever
|
|
910
|
-
# pass schema-declared names, this method has no way to know
|
|
911
|
-
# that, and the '{...}' form has no escaping at all: a segment
|
|
912
|
-
# containing a single quote closes the string early and whatever
|
|
913
|
-
# follows becomes live SQL. Measured, not assumed — a crafted
|
|
914
|
-
# field name of `x}' = '' OR $1::text = $1::text -- ` made a
|
|
915
|
-
# `where(secret: "public")` clause return every row regardless,
|
|
916
|
-
# against the '{...}' form; the `ARRAY[]` form below closes it, verified
|
|
917
|
-
# against the identical payload.
|
|
545
|
+
# `ARRAY[...]` of escaped literals, never the '{a,b}' syntax: that form has no escaping, so
|
|
546
|
+
# a quote in a segment would become live SQL.
|
|
918
547
|
def jsonb_path(segments)
|
|
919
548
|
"state #>> ARRAY[#{segments.map { |segment| text_literal(segment) }.join(', ')}]::text[]"
|
|
920
549
|
end
|
|
921
550
|
|
|
922
551
|
def text_literal(text) = "'#{text.to_s.gsub("'", "''")}'"
|
|
923
552
|
|
|
924
|
-
#
|
|
925
|
-
|
|
926
|
-
# Every declared `where`-field this aggregate's own queries and its
|
|
927
|
-
# entities' own queries use, minus anything a cache table can't
|
|
928
|
-
# represent (see `cacheable_field?`) — never `order_by`-only
|
|
929
|
-
# fields, which never needed a cache in the first place (sorting
|
|
930
|
-
# the reduced output was never blocked by the reduction; only
|
|
931
|
-
# filtering was — see field_cache.rb's own header).
|
|
553
|
+
# Where-fields of the aggregate's and its entities' queries that a cache table can represent;
|
|
554
|
+
# order_by-only fields never needed a cache.
|
|
932
555
|
def ensure_field_caches!
|
|
933
556
|
cached_where_fields.to_h do |field|
|
|
934
557
|
[field, @lineage.ensure_field_cache!(table, @era, field, query_expression(field))]
|
|
@@ -948,14 +571,7 @@ module Hecks
|
|
|
948
571
|
@aggregate.queries + @aggregate.entities.flat_map(&:queries)
|
|
949
572
|
end
|
|
950
573
|
|
|
951
|
-
# List
|
|
952
|
-
# and `Sqlite`/`D1` adapters independently landed on for their own
|
|
953
|
-
# automatic indexing: `contains` means element membership, and a
|
|
954
|
-
# (id, ordinal, one value) cache row has nowhere to put more than
|
|
955
|
-
# one element. Everything else — a plain scalar, the lifecycle
|
|
956
|
-
# field, or a non-list value-object member path — reduces to
|
|
957
|
-
# exactly one comparable value per id, which is the one shape this
|
|
958
|
-
# cache table represents.
|
|
574
|
+
# List fields are excluded: a one-value cache row cannot hold `contains` membership.
|
|
959
575
|
def cacheable_field?(field)
|
|
960
576
|
name = field.to_s.split(".").first
|
|
961
577
|
return true if @aggregate.lifecycle&.field.to_s == name
|
|
@@ -964,28 +580,16 @@ module Hecks
|
|
|
964
580
|
!attribute.nil? && !attribute.list?
|
|
965
581
|
end
|
|
966
582
|
|
|
967
|
-
# A clause
|
|
968
|
-
#
|
|
969
|
-
# `QuerySpecification::Common::NullPolicy` intercepts before
|
|
970
|
-
# `where_clause` ever runs (see `query`'s own header comment) — a
|
|
971
|
-
# clause that fails either check simply flows to `head_phase`
|
|
972
|
-
# unaccelerated, exactly as `super` would have evaluated it.
|
|
583
|
+
# A clause is cache-served when its field has a cache table and it is not a null comparison,
|
|
584
|
+
# which `NullPolicy` intercepts before `where_clause`.
|
|
973
585
|
def cache_eligible?(clause, value)
|
|
974
586
|
@field_caches.key?(clause.field.to_s) &&
|
|
975
587
|
QuerySpecification::Common::NullPolicy.sql_predicate(query_expression(clause.field, value: value), clause.op,
|
|
976
588
|
value).nil?
|
|
977
589
|
end
|
|
978
590
|
|
|
979
|
-
#
|
|
980
|
-
#
|
|
981
|
-
# `INTERSECT`ed into the set that satisfies every cached clause at
|
|
982
|
-
# once. Reuses `where_clause` (SqlQueryBuilder, private, already
|
|
983
|
-
# mixed into this class) unchanged against the cache table's own
|
|
984
|
-
# `value` column instead of a jsonb path expression — the exact
|
|
985
|
-
# same operator compilation (`eq`/`ne`/`gt`/`gte`/`lt`/`lte`/`in`)
|
|
986
|
-
# a live query already gets against the real column, so a cached
|
|
987
|
-
# field supports every comparator `super` would have, not just the
|
|
988
|
-
# `eq` example in the plan doc's own illustration.
|
|
591
|
+
# Phase one: candidate ids from the narrow cache tables, INTERSECTed. Reuses `where_clause`
|
|
592
|
+
# against each cache's `value` column, so every operator `super` supports works here.
|
|
989
593
|
def cache_phase(cached)
|
|
990
594
|
binds = []
|
|
991
595
|
clauses = cached.map do |clause, value|
|
|
@@ -996,20 +600,8 @@ module Hecks
|
|
|
996
600
|
@db.exec_params(clauses.join("\nINTERSECT\n"), binds).map { |row| row["id"] }
|
|
997
601
|
end
|
|
998
602
|
|
|
999
|
-
#
|
|
1000
|
-
#
|
|
1001
|
-
# exactly the way `SqlQueryBuilder#query` (`super`) already applies
|
|
1002
|
-
# every clause today: against the fully-reduced view, which is
|
|
1003
|
-
# already correct regardless of caching (see this file's own
|
|
1004
|
-
# `query` comment — a cache is a speed shortcut, never a
|
|
1005
|
-
# correctness fix; head_view was always safe to filter directly,
|
|
1006
|
-
# just expensive to reduce in the first place). Duplicates a small
|
|
1007
|
-
# slice of `SqlQueryBuilder#query`'s own tail assembly (order_by/
|
|
1008
|
-
# limit/offset) rather than reaching into it — principle 2
|
|
1009
|
-
# (docs/implemented/postgres-era-adapter-split-plan.md): SqlQueryBuilder stays
|
|
1010
|
-
# untouched by the era workaround, so this stays local to
|
|
1011
|
-
# PostgresEra rather than growing a shared hook only one adapter
|
|
1012
|
-
# would ever call.
|
|
603
|
+
# Phase two: the head view restricted to phase one's ids plus the clauses it could not
|
|
604
|
+
# accelerate. Repeats the tail of `SqlQueryBuilder#query` so that module stays untouched.
|
|
1013
605
|
def head_phase(declared, uncached, ids, args)
|
|
1014
606
|
binds = []
|
|
1015
607
|
clauses = ["id IN (#{ids.map { |id| placeholder(binds, id) }.join(', ')})"]
|
|
@@ -1034,19 +626,6 @@ module Hecks
|
|
|
1034
626
|
execute_query(sql, binds)
|
|
1035
627
|
end
|
|
1036
628
|
|
|
1037
|
-
def create_event_table!
|
|
1038
|
-
@db.exec(<<~SQL)
|
|
1039
|
-
CREATE TABLE IF NOT EXISTS events (
|
|
1040
|
-
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
1041
|
-
name text NOT NULL,
|
|
1042
|
-
aggregate text NOT NULL,
|
|
1043
|
-
aggregate_id text NOT NULL,
|
|
1044
|
-
payload jsonb,
|
|
1045
|
-
occurred_at text
|
|
1046
|
-
)
|
|
1047
|
-
SQL
|
|
1048
|
-
end
|
|
1049
|
-
|
|
1050
629
|
def create_saga_table!
|
|
1051
630
|
@db.exec(<<~SQL)
|
|
1052
631
|
CREATE TABLE IF NOT EXISTS hecks_saga_instances (
|
|
@@ -1060,10 +639,7 @@ module Hecks
|
|
|
1060
639
|
PRIMARY KEY (domain, process_manager, correlation)
|
|
1061
640
|
)
|
|
1062
641
|
SQL
|
|
1063
|
-
#
|
|
1064
|
-
# this same domain already created before this column existed —
|
|
1065
|
-
# the same idiom `rust/host/src/journal.rs`'s own
|
|
1066
|
-
# `sagas_backfilled` column addition already uses.
|
|
642
|
+
# Covers a table created before this column existed, where IF NOT EXISTS is a no-op.
|
|
1067
643
|
@db.exec("ALTER TABLE hecks_saga_instances ADD COLUMN IF NOT EXISTS completed_compensations jsonb " \
|
|
1068
644
|
"NOT NULL DEFAULT '[]'::jsonb")
|
|
1069
645
|
end
|