hecks 2.9.0 → 3.0.1
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 +51 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
- data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +1 -1
- data/lib/hecks/adapters/driven/memory.rb +16 -3
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
- data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
- data/lib/hecks/adapters/driven/postgres.rb +13 -4
- data/lib/hecks/adapters/driven/prism.rb +41 -8
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
- data/lib/hecks/behaviors/expectations.rb +1 -1
- data/lib/hecks/behaviors.rb +1 -1
- 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/assembly/contracts.rb +2 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
- data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
- data/lib/hecks/bluebook/chapter.rb +6 -2
- data/lib/hecks/bluebook/domain_port.rb +36 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
- data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
- data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
- data/lib/hecks/bluebook/expression/resolver.rb +3 -2
- data/lib/hecks/bluebook/hexagon.rb +13 -5
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
- data/lib/hecks/bluebook/meta_validator.rb +9 -4
- data/lib/hecks/bluebook/model_check.rb +23 -0
- data/lib/hecks/bluebook/pattern_subset.rb +38 -0
- data/lib/hecks/bluebook/query.rb +20 -5
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- 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 +2 -2
- 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 +1 -1
- data/lib/hecks/cli/mcp.rb +3 -3
- data/lib/hecks/cli/model_check.rb +15 -8
- data/lib/hecks/cli/pattern_cases.rb +47 -0
- data/lib/hecks/cli/present.rb +39 -0
- data/lib/hecks/cli/project_cli.rb +224 -32
- data/lib/hecks/cli/project_diagrams.rb +4 -3
- 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 +3 -3
- 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 +5 -3
- data/lib/hecks/cli/spec_example.rb +27 -0
- data/lib/hecks/cli/statements.rb +32 -0
- data/lib/hecks/cli/stores.rb +4 -5
- data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
- data/lib/hecks/cli.rb +201 -52
- data/lib/hecks/codemod.rb +324 -0
- data/lib/hecks/corpus.rb +423 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
- 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 +1 -1
- data/lib/hecks/{facade → doors}/handle.rb +2 -2
- data/lib/hecks/{facade → doors}/json_door.rb +4 -4
- data/lib/hecks/doors/launcher_options.rb +129 -0
- data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
- data/lib/hecks/doors/mcp_door_scope.rb +97 -0
- data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
- data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
- data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
- data/lib/hecks/doors.rb +61 -0
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +1 -1
- data/lib/hecks/engine_agreement.rb +156 -0
- data/lib/hecks/facade.rb +24 -37
- data/lib/hecks/forms/app.rb +6 -6
- data/lib/hecks/forms/banking_presentation.rb +50 -0
- data/lib/hecks/forms/field_shape.rb +2 -1
- data/lib/hecks/forms/port_argument.rb +1 -1
- 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/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/kernel_capabilities.rb +194 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
- data/lib/hecks/language/bluebook/query.bluebook +7 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
- data/lib/hecks/mcp_stdio_guard.rb +2 -2
- data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
- data/lib/hecks/persistence_legacy_fixture.rb +113 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +15 -5
- 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 +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
- data/lib/hecks/ports/persistence/state_codec.rb +24 -5
- data/lib/hecks/projection_files.rb +285 -0
- data/lib/hecks/projections/bootstrap_table.rb +1 -1
- data/lib/hecks/projections/deploy/fargate.rb +5 -5
- data/lib/hecks/projections/deploy/lambda.rb +43 -32
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/preview/template.rb +1 -1
- 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/smoke-after-deploy.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/shared.rb +1 -1
- data/lib/hecks/projections/diagrams.rb +3 -3
- data/lib/hecks/projections/model/deviations.rb +1 -1
- data/lib/hecks/projections/parser_table.rb +4 -4
- data/lib/hecks/projections/reference.rb +2 -3
- data/lib/hecks/projections/rust_vocabulary.rb +1 -1
- data/lib/hecks/projector/cli_projector.rb +106 -37
- data/lib/hecks/projector.rb +1 -1
- 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/release/runner/ci_publisher.rb +2 -2
- data/lib/hecks/release/runner/clean_tree.rb +42 -0
- data/lib/hecks/release/runner/console.rb +6 -0
- data/lib/hecks/release/runner/gem_publisher.rb +14 -7
- data/lib/hecks/release/runner/npm_publisher.rb +6 -36
- data/lib/hecks/release/runner/preflight.rb +26 -2
- data/lib/hecks/release/runner/published.rb +8 -26
- data/lib/hecks/release/runner/tagger.rb +30 -15
- data/lib/hecks/release/runner.rb +57 -6
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/runtime/adapter_lookup.rb +124 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
- data/lib/hecks/runtime/dependency_planning.rb +20 -2
- data/lib/hecks/runtime/dispatcher.rb +53 -3
- data/lib/hecks/runtime/interpreting.rb +3 -0
- data/lib/hecks/runtime/loader.rb +65 -12
- data/lib/hecks/runtime/outbox.rb +1 -1
- data/lib/hecks/runtime/policy_interpreter.rb +5 -2
- data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
- data/lib/hecks/runtime/query_interpreter.rb +77 -0
- data/lib/hecks/runtime/reaction_outcome.rb +89 -0
- data/lib/hecks/runtime/refusal_wording.rb +23 -1
- data/lib/hecks/runtime/registry/verification.rb +163 -4
- data/lib/hecks/runtime/registry.rb +57 -3
- data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
- data/lib/hecks/runtime/value/coercion.rb +23 -135
- data/lib/hecks/runtime/value/field_checks.rb +172 -0
- data/lib/hecks/runtime/value.rb +4 -1
- data/lib/hecks/runtime.rb +20 -6
- 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 +16 -16
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
- 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/version.rb +1 -1
- data/lib/hecks.rb +32 -15
- 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 +477 -12
- data/lib/hecks/facade/cli_door.rb +0 -96
- data/lib/hecks/facade/cli_runner.rb +0 -127
- data/lib/hecks/mcp_door_scope.rb +0 -95
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "time"
|
|
5
|
+
require_relative "console_capture"
|
|
6
|
+
require "hecks/cli/run"
|
|
7
|
+
require "hecks/cli/model_check"
|
|
8
|
+
require "hecks/cli/smoke_test"
|
|
9
|
+
require "hecks/cli/behaviors"
|
|
10
|
+
require "hecks/cli/refresh_projections"
|
|
11
|
+
|
|
12
|
+
module Hecks
|
|
13
|
+
module Adapters
|
|
14
|
+
# What the `DomainRuntime` port's adapter does when a journaled Custodian command asks it to:
|
|
15
|
+
# the asks of `ModelCheckRun` and `Operation`, plus the `Follow` query.
|
|
16
|
+
#
|
|
17
|
+
# An ask is handed the whole record it was made on (its `held_state`), so each method reads the
|
|
18
|
+
# record's fields, runs the existing `Hecks::CLI::*` entry point captured, and answers the text
|
|
19
|
+
# it printed. An answer records a success; a raise records a refusal, so a check that finds
|
|
20
|
+
# something, or a run that fails, is refused with the report as its reason.
|
|
21
|
+
module InProcessOperations
|
|
22
|
+
# The chapters `hecks model_check` sweep when no domain is named need a
|
|
23
|
+
# checkout; an installed gem has none.
|
|
24
|
+
CHECKOUT_MARKER = "hecks.gemspec"
|
|
25
|
+
|
|
26
|
+
# Longest a `Follow` query may wait for new entries, so one call cannot hold a launcher open.
|
|
27
|
+
MAX_FOLLOW_WAIT = 60
|
|
28
|
+
|
|
29
|
+
# Runs the static analysis over the named domains, or over the whole corpus in a checkout.
|
|
30
|
+
#
|
|
31
|
+
# @param held [Hash] the `ModelCheckRun` record: `domains` (comma separated paths), `strict`
|
|
32
|
+
# and `profile`
|
|
33
|
+
# @return [Hash{Symbol => Hash}] `report:` the analysis as printed
|
|
34
|
+
# @raise [ConsoleCapture::Failure] when a finding was left, or the analysis could not run
|
|
35
|
+
def check(**held)
|
|
36
|
+
argv = plain(held[:domains]).to_s.split(",").map(&:strip).reject(&:empty?)
|
|
37
|
+
missing = argv.reject { |domain| File.exist?(domain) }
|
|
38
|
+
raise Runtime::NotFound, "no such domain #{missing.first.inspect}" if missing.any?
|
|
39
|
+
|
|
40
|
+
argv.unshift("--strict") if plain(held[:strict])
|
|
41
|
+
argv.unshift("--profile", plain(held[:profile]).to_s) if present?(held[:profile])
|
|
42
|
+
|
|
43
|
+
text = ConsoleCapture.answer do
|
|
44
|
+
CLI::ModelCheck.call(argv, program: "hecks model_check", root: checkout_root)
|
|
45
|
+
end
|
|
46
|
+
{ report: { value: text } }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Dispatches one verb, or executes a step list, against a domain.
|
|
50
|
+
#
|
|
51
|
+
# @param held [Hash] the `Operation` record: `subject` (the domain), then `script`, or
|
|
52
|
+
# `verb` with `arguments` (its `name=value` words)
|
|
53
|
+
# @return [Hash{Symbol => Hash}] `output:` what the run reported
|
|
54
|
+
# @raise [ConsoleCapture::Failure] when the verb was refused or an expectation was unmet
|
|
55
|
+
def execute(**held)
|
|
56
|
+
argv = [plain(held[:subject])].compact
|
|
57
|
+
script = plain(held[:script])
|
|
58
|
+
argv += script ? [script] : [plain(held[:verb]), *words(held[:arguments])].compact
|
|
59
|
+
|
|
60
|
+
output(ConsoleCapture.answer { CLI::Run.call(argv, program: "hecks run") })
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Forces every read-model projection the domain declares to catch up.
|
|
64
|
+
#
|
|
65
|
+
# @param held [Hash] the `Operation` record: `subject` (the domain)
|
|
66
|
+
# @return [Hash{Symbol => Hash}] `output:` how many projections were refreshed
|
|
67
|
+
# @raise [Runtime::NotFound] if the domain cannot be found
|
|
68
|
+
def refresh(**held)
|
|
69
|
+
count = CLI::RefreshProjections.call(boot(held[:subject]))
|
|
70
|
+
output("refreshed #{count} projection(s)")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Runs a `.behaviors` file, or every one under a directory.
|
|
74
|
+
#
|
|
75
|
+
# @param held [Hash] the `Operation` record: `subject` (the file or directory)
|
|
76
|
+
# @return [Hash{Symbol => Hash}] `output:` the per-test report
|
|
77
|
+
# @raise [ConsoleCapture::Failure] when any test failed, errored or did not parse
|
|
78
|
+
def verify(**held)
|
|
79
|
+
argv = [plain(held[:subject])].compact
|
|
80
|
+
output(ConsoleCapture.answer { CLI::Behaviors.call(argv, program: "hecks run_behaviors") })
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Boots a wired domain and dispatches one synthesized call per command and report.
|
|
84
|
+
#
|
|
85
|
+
# @param held [Hash] the `Operation` record: `subject` (the domain; the enclosing one when
|
|
86
|
+
# absent and this is not a checkout)
|
|
87
|
+
# @return [Hash{Symbol => Hash}] `output:` the per-domain report
|
|
88
|
+
# @raise [ConsoleCapture::Failure] when any dispatch failed
|
|
89
|
+
def smoke(**held)
|
|
90
|
+
domain = plain(held[:subject]) || (checkout_root ? nil : Folder.new.domain_root)
|
|
91
|
+
argv = [domain].compact
|
|
92
|
+
|
|
93
|
+
output(ConsoleCapture.answer { CLI::SmokeTest.call(argv, root: checkout_root || Dir.pwd) })
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# A domain's event log past a cursor, answered once: what the stream yielded before it
|
|
97
|
+
# ended. Pass the answered `cursor` back as `since` to keep tailing. `wait` bounds the stream
|
|
98
|
+
# and it ends at the first entries, so a call returns as soon as there is something to say.
|
|
99
|
+
#
|
|
100
|
+
# @param domain [Hash, String] a domain directory
|
|
101
|
+
# @param aggregate [Hash, String, nil] only entries of this aggregate, by bare name
|
|
102
|
+
# @param since [Hash, Integer, nil] how many entries were already seen; all when absent
|
|
103
|
+
# @param from_now [Hash, Boolean, nil] skip what exists; answer only a cursor at its end
|
|
104
|
+
# @param wait [Hash, Integer, nil] seconds to wait for a new entry, at most 60
|
|
105
|
+
# @param interval [Hash, Float, nil] seconds between checks of the log
|
|
106
|
+
# @return [Hash] the `Tail` row: `cursor:`, `events:` (payloads as JSON text) and `taken_at:`
|
|
107
|
+
# @raise [Runtime::NotFound] if the domain cannot be found or keeps no event log
|
|
108
|
+
def follow(domain:, aggregate: nil, since: nil, from_now: nil, wait: nil, interval: nil)
|
|
109
|
+
registry = boot(domain).registry
|
|
110
|
+
entries = []
|
|
111
|
+
cursor = stream(domain: domain, aggregate: aggregate, since: since, from_now: from_now,
|
|
112
|
+
timeout: [plain(wait).to_f, MAX_FOLLOW_WAIT].min, interval: interval,
|
|
113
|
+
registry: registry) do |entry|
|
|
114
|
+
entries << entry.merge("payload" => JSON.generate(entry["payload"]))
|
|
115
|
+
:batch
|
|
116
|
+
end
|
|
117
|
+
{ cursor: cursor, events: entries, taken_at: Time.at(Ports::Clock.now(registry)).utc.iso8601 }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Tails a domain's event log, handing each entry to the block as the log shows it, until
|
|
121
|
+
# the block returns `:stop` (or `:batch`: after this check's entries), `limit` entries were
|
|
122
|
+
# handed over, `timeout` seconds pass, or the reader goes away; unbounded, until interrupted.
|
|
123
|
+
#
|
|
124
|
+
# @param domain [Hash, String] a domain directory
|
|
125
|
+
# @param aggregate [Hash, String, nil] only entries of this aggregate, by bare name
|
|
126
|
+
# @param since [Hash, Integer, nil] entries already seen; `from_now` skips what exists
|
|
127
|
+
# @param limit [Hash, Integer, nil] entries to hand over at most, or `timeout` seconds
|
|
128
|
+
# @param interval [Hash, Float, nil] seconds between checks (0.5 when absent)
|
|
129
|
+
# @param registry [Runtime::Registry, nil] the domain's booted registry; booted here if absent
|
|
130
|
+
# @return [Integer] the count of log entries seen, to pass back as `since`
|
|
131
|
+
# @raise [Runtime::NotFound] if the domain cannot be found or keeps no event log
|
|
132
|
+
def stream(domain:, aggregate: nil, since: nil, from_now: nil, limit: nil, timeout: nil, interval: nil,
|
|
133
|
+
registry: nil, &block)
|
|
134
|
+
repository = event_repository(domain, registry)
|
|
135
|
+
tail = Tail.new(plain(since)&.to_i || (plain(from_now) ? repository.events.size : 0), 0, false)
|
|
136
|
+
cap = plain(limit)&.to_i
|
|
137
|
+
deadline = plain(timeout) && (monotonic + plain(timeout).to_f)
|
|
138
|
+
|
|
139
|
+
loop do
|
|
140
|
+
verdict = drain(repository, tail, plain(aggregate), cap, &block)
|
|
141
|
+
return tail.seen if verdict == :stop || tail.batch_done || (deadline && monotonic >= deadline)
|
|
142
|
+
|
|
143
|
+
sleep((plain(interval) || 0.5).to_f)
|
|
144
|
+
end
|
|
145
|
+
rescue Errno::EPIPE, Interrupt
|
|
146
|
+
tail.seen
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
# Where a stream stands: log entries seen, entries handed over, whether a batch ended it.
|
|
152
|
+
Tail = Struct.new(:seen, :handed, :batch_done)
|
|
153
|
+
|
|
154
|
+
# Hands over what the log holds past the tail, one entry at a time; answers `:stop` when the
|
|
155
|
+
# block or the limit ends the stream, so the tail's `seen` stays just past the last entry
|
|
156
|
+
# handed over.
|
|
157
|
+
def drain(repository, tail, filter, cap)
|
|
158
|
+
events = repository.events
|
|
159
|
+
while tail.seen < events.size
|
|
160
|
+
event = events[tail.seen]
|
|
161
|
+
tail.seen += 1
|
|
162
|
+
next unless followed?(event, filter)
|
|
163
|
+
|
|
164
|
+
verdict = yield JSON.parse(JSON.generate(event.to_h))
|
|
165
|
+
tail.handed += 1
|
|
166
|
+
tail.batch_done ||= verdict == :batch
|
|
167
|
+
return :stop if verdict == :stop || (cap && tail.handed >= cap)
|
|
168
|
+
end
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
173
|
+
|
|
174
|
+
def output(text) = { output: { value: text } }
|
|
175
|
+
|
|
176
|
+
def present?(argument) = !plain(argument).to_s.empty?
|
|
177
|
+
|
|
178
|
+
# A list of value objects or bare strings, as plain strings.
|
|
179
|
+
def words(list) = Array(list).map { |word| plain(word) }
|
|
180
|
+
|
|
181
|
+
# The repository root when this is a hecks checkout: `hecks.gemspec` sits beside `lib/`.
|
|
182
|
+
def checkout_root
|
|
183
|
+
root = File.expand_path("../../../..", __dir__)
|
|
184
|
+
root if File.exist?(File.join(root, CHECKOUT_MARKER))
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def event_repository(domain, registry = nil)
|
|
188
|
+
registry ||= boot(domain).registry
|
|
189
|
+
repositories = registry.bluebooks.flat_map do |name, bluebook|
|
|
190
|
+
bluebook.aggregates.map { |aggregate| registry.repository(name, aggregate) }
|
|
191
|
+
end
|
|
192
|
+
repositories.find { |repository| repository.respond_to?(:events) } or
|
|
193
|
+
raise Runtime::NotFound, "no adapter in #{plain(domain)} persists an event log"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Says whether `event` passes the aggregate filter: a bare name, case-insensitive, or exact.
|
|
197
|
+
def followed?(event, filter)
|
|
198
|
+
filter.nil? || event.aggregate.to_s.split("::").last.casecmp?(filter) || event.aggregate == filter
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "time"
|
|
5
|
+
require_relative "held_domain"
|
|
6
|
+
require_relative "edge_audit"
|
|
7
|
+
require_relative "compaction"
|
|
8
|
+
require_relative "../git"
|
|
9
|
+
|
|
10
|
+
module Hecks
|
|
11
|
+
module Adapters
|
|
12
|
+
class JournalStore
|
|
13
|
+
# The changes the journal store makes once `Era.Permit` has let one through.
|
|
14
|
+
#
|
|
15
|
+
# The rules that need no database are `given`s of the Era commands, including that a
|
|
16
|
+
# re-attested text differs from its digest and keeps the era's shape. What stays here is what
|
|
17
|
+
# only the database can enforce: the connection's write fence, the per-domain advisory lock
|
|
18
|
+
# and its timeout, the append-only re-attestation, the audit that rolls a merge back, the
|
|
19
|
+
# monotonic `compacted_through` floor and the row-level-security delete check.
|
|
20
|
+
module Changes
|
|
21
|
+
# Makes the change the record asks for.
|
|
22
|
+
#
|
|
23
|
+
# A change is made only for a record `Era.Permit` admitted, whichever way `apply` is asked.
|
|
24
|
+
#
|
|
25
|
+
# @param held [Hash] the `Era` record
|
|
26
|
+
# @return [Hash{Symbol => Hash}] `report:` what was done, as the script said it
|
|
27
|
+
# @raise [Runtime::WiringError] if the record was not admitted, or the database or an
|
|
28
|
+
# era's own guard refuses the change
|
|
29
|
+
# @raise [Runtime::NotFound] if the domain cannot be loaded
|
|
30
|
+
def apply(**held)
|
|
31
|
+
unless plain(held[:status]) == "admitted"
|
|
32
|
+
raise Runtime::WiringError,
|
|
33
|
+
"the change was not admitted (status #{plain(held[:status]).inspect}): " \
|
|
34
|
+
"only an admitted change is made"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
{ report: { value: send(:"apply_#{plain(held[:operation])}", held) } }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def apply_hold_first(held)
|
|
43
|
+
domain = HeldDomain.open(plain(held[:domain]), mode: :bare)
|
|
44
|
+
domain.writing do |lineage|
|
|
45
|
+
raise Runtime::WiringError, "#{domain.bluebook.name} already holds an era" unless lineage.eras.empty?
|
|
46
|
+
|
|
47
|
+
lineage.hold_first!(domain.source_text, projection: domain.shape)
|
|
48
|
+
end
|
|
49
|
+
"#{domain.bluebook.name} holds era 1 now."
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def apply_merge_tail(held)
|
|
53
|
+
domain = HeldDomain.open(plain(held[:domain]))
|
|
54
|
+
named = winners(held)
|
|
55
|
+
diverged = domain.writing { |lineage| diverged_writes(domain, lineage) }
|
|
56
|
+
PostgresEra::LineageManager.merge!(registry: domain.registry, bluebook: domain.bluebook,
|
|
57
|
+
settings: domain.settings, winners: named)
|
|
58
|
+
["#{domain.bluebook.name}: #{diverged} post-cut write#{'s' unless diverged == 1} in ancestor " \
|
|
59
|
+
"eras before the merge",
|
|
60
|
+
"merged — the head now interleaves both worlds by their recorded ordinals",
|
|
61
|
+
*named.map { |id, side| " winner #{id}=#{side} appended as the newest row" }].join("\n")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def diverged_writes(domain, lineage)
|
|
65
|
+
eras = domain.held_eras(lineage)
|
|
66
|
+
eras.size > 1 ? (1...eras.last[:ordinal]).sum { |era| lineage.diverged_count(era) } : 0
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def apply_reattest(held)
|
|
70
|
+
domain = HeldDomain.open(plain(held[:domain]), mode: :bare)
|
|
71
|
+
ordinal = plain(held[:era])
|
|
72
|
+
domain.writing { |lineage| reattest(domain, lineage, ordinal) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def reattest(domain, lineage, ordinal)
|
|
76
|
+
lineage.raw_era(ordinal) or raise Runtime::NotFound, "#{domain.bluebook.name} holds no era #{ordinal}"
|
|
77
|
+
fresh = lineage.reattest!(ordinal)
|
|
78
|
+
"ATTESTED: era #{ordinal} re-frozen as #{fresh[0, 12]}… — the old and new digests are recorded."
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def apply_backfill_projections(held)
|
|
82
|
+
domain = HeldDomain.open(plain(held[:domain]), mode: :bare)
|
|
83
|
+
domain.writing { |lineage| backfill(domain, lineage) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def backfill(domain, lineage)
|
|
87
|
+
missing = -> { missing_projections(lineage) }
|
|
88
|
+
before = missing.call
|
|
89
|
+
return "#{domain.bluebook.name}: every held era already carries a projection — nothing to do." if before.zero?
|
|
90
|
+
|
|
91
|
+
begin
|
|
92
|
+
# Raises on the first row whose own digest check fails: a tampered row, not a merely
|
|
93
|
+
# legacy one, which backfilling must not paper over.
|
|
94
|
+
lineage.eras
|
|
95
|
+
rescue Runtime::WiringError => e
|
|
96
|
+
raise Runtime::WiringError, "#{domain.bluebook.name}: stopped at a row that isn't merely legacy — " \
|
|
97
|
+
"it fails its own integrity check: #{e.message} — resolve that first " \
|
|
98
|
+
"(`hecks reattest`), then run this again for the remaining rows."
|
|
99
|
+
end
|
|
100
|
+
done = before - missing.call
|
|
101
|
+
"#{domain.bluebook.name}: backfilled #{done} era#{'s' unless done == 1} — every row now carries a projection."
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def missing_projections(lineage)
|
|
105
|
+
lineage.db.exec_params(
|
|
106
|
+
"SELECT count(*)::int AS n FROM hecks_eras WHERE domain = $1 AND held_projection IS NULL",
|
|
107
|
+
[lineage.domain]
|
|
108
|
+
)[0]["n"].to_i
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def apply_compact(held) = apply_compaction(held, :entries)
|
|
112
|
+
|
|
113
|
+
def apply_compact_heki(held) = apply_compaction(held, :heki)
|
|
114
|
+
|
|
115
|
+
def apply_compaction(held, kind)
|
|
116
|
+
Compaction.new(plain(held[:domain]), aggregates: names(held[:aggregates]), kind: kind).apply!.join("\n")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Writes `translations/<edge>.approval`; the host reads it at its next boot.
|
|
120
|
+
def apply_approve_translation(held)
|
|
121
|
+
domain = HeldDomain.open(plain(held[:domain]))
|
|
122
|
+
finding = domain.reading { |lineage| audited_edge(domain, lineage) }
|
|
123
|
+
document = Translation::ApprovalFile.build(
|
|
124
|
+
edge: finding.edge, approved_by: Git.new.identity(chdir: domain.directory),
|
|
125
|
+
approved_at: Time.now.utc.iso8601, rehearsal: rehearsal_block(held)
|
|
126
|
+
)
|
|
127
|
+
path = Translation::ApprovalFile.write!(domain.directory, finding.edge, document)
|
|
128
|
+
"approval of edge #{document['edge']} written to #{path} (digest #{document['edge_digest'][0, 12]}…, " \
|
|
129
|
+
"by #{document['approved_by']}). Commit it with the edge; the host applies it at its next boot."
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def audited_edge(domain, lineage)
|
|
133
|
+
eras = domain.held_eras(lineage)
|
|
134
|
+
finding = eras.empty? ? nil : EdgeAudit.call(domain, lineage, eras)
|
|
135
|
+
raise Runtime::WiringError, "#{domain.bluebook.name} has no edge to approve" unless finding
|
|
136
|
+
raise Runtime::WiringError, "#{finding.report}\nAUDIT REFUSED." unless finding.ok
|
|
137
|
+
|
|
138
|
+
finding
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hecks"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Adapters
|
|
7
|
+
class JournalStore
|
|
8
|
+
# The journals of one booted domain that can be compacted, and what compacting them does.
|
|
9
|
+
#
|
|
10
|
+
# `:entries` are Postgres and Sqlite journals, compacted through the aggregate's own
|
|
11
|
+
# checkpoint (`compact_entries!`); `:heki` journals are compacted to empty (`compact!`),
|
|
12
|
+
# the snapshot keeping the current state. The database-level guard, that the
|
|
13
|
+
# `compacted_through` floor only moves forward, stays in the adapter that owns the journal.
|
|
14
|
+
class Compaction
|
|
15
|
+
Candidate = Struct.new(:domain_name, :aggregate, :adapter, keyword_init: true)
|
|
16
|
+
|
|
17
|
+
KINDS = { entries: %i[compact_entries! checkpoint], heki: %i[compact!] }.freeze
|
|
18
|
+
|
|
19
|
+
# @param path [String] the domain directory
|
|
20
|
+
# @param aggregates [Array<String>] aggregate names or storage names; every one when empty
|
|
21
|
+
# @param kind [Symbol] `:entries` or `:heki`
|
|
22
|
+
# @raise [Runtime::NotFound] if the directory does not exist
|
|
23
|
+
def initialize(path, aggregates:, kind:)
|
|
24
|
+
raise Runtime::NotFound, "no such domain #{path.inspect}" unless Dir.exist?(path.to_s)
|
|
25
|
+
|
|
26
|
+
@registry = Hecks.boot(File.expand_path(path), install_doors: false).registry
|
|
27
|
+
@wanted = aggregates
|
|
28
|
+
@kind = kind
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Array<Candidate>] the aggregates whose journals this kind can compact
|
|
32
|
+
def candidates
|
|
33
|
+
@candidates ||= @registry.bluebooks.flat_map do |domain_name, bluebook|
|
|
34
|
+
bluebook.aggregates.filter_map { |aggregate| candidate(domain_name, aggregate) }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The candidates a `projected_by` binding reads: their projections replay the full journal.
|
|
39
|
+
#
|
|
40
|
+
# @return [Array<Candidate>] the bound candidates
|
|
41
|
+
def bound
|
|
42
|
+
candidates.select do |candidate|
|
|
43
|
+
Ports::Projection.binds_for(@registry, candidate.domain_name, candidate.aggregate).any?
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# What compacting would do, without doing it.
|
|
48
|
+
#
|
|
49
|
+
# @return [Array<String>] one line per candidate
|
|
50
|
+
def preview
|
|
51
|
+
candidates.map { |candidate| bound.include?(candidate) ? refusal_line(candidate) : dry_line(candidate) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Compacts every candidate.
|
|
55
|
+
#
|
|
56
|
+
# @return [Array<String>] one line per candidate
|
|
57
|
+
# @raise [Runtime::WiringError] if a projection reads a journal that would be emptied, or
|
|
58
|
+
# the journal's own floor refuses the compaction
|
|
59
|
+
def apply!
|
|
60
|
+
raise Runtime::WiringError, preview.grep(/^REFUSED/).join("\n") if bound.any?
|
|
61
|
+
|
|
62
|
+
candidates.map { |candidate| kind_apply(candidate) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def candidate(domain_name, aggregate)
|
|
68
|
+
return unless @wanted.empty? || @wanted.include?(aggregate.hecks_name) || @wanted.include?(aggregate.storage_name)
|
|
69
|
+
|
|
70
|
+
repository = @registry.repository(domain_name, aggregate)
|
|
71
|
+
return unless repository.is_a?(Ports::Persistence::AppendOnly)
|
|
72
|
+
|
|
73
|
+
adapter = repository.adapter
|
|
74
|
+
return unless KINDS.fetch(@kind).all? { |operation| adapter.respond_to?(operation) }
|
|
75
|
+
|
|
76
|
+
Candidate.new(domain_name: domain_name, aggregate: aggregate, adapter: adapter)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def kind_apply(candidate)
|
|
80
|
+
@kind == :heki ? compact_heki(candidate) : compact_entries(candidate)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def dry_line(candidate)
|
|
84
|
+
@kind == :heki ? heki_dry(candidate) : entries_dry(candidate)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def refusal_line(candidate)
|
|
88
|
+
"REFUSED #{candidate.aggregate.storage_name}: a projected_by binding reads its full journal — " \
|
|
89
|
+
"compacting would break its catch-up. Not compacted."
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def entries_dry(candidate)
|
|
93
|
+
name = candidate.aggregate.storage_name
|
|
94
|
+
adapter = candidate.adapter
|
|
95
|
+
through = adapter.checkpoint
|
|
96
|
+
if through <= adapter.compacted_through
|
|
97
|
+
return "SKIP #{name}: nothing new to compact (already compacted through #{through})"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
"DRY RUN #{name}: would delete journal entries through sequence #{through} (compacted_through is " \
|
|
101
|
+
"currently #{adapter.compacted_through}); current state (#{adapter.count} records) is unaffected."
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def compact_entries(candidate)
|
|
105
|
+
name = candidate.aggregate.storage_name
|
|
106
|
+
adapter = candidate.adapter
|
|
107
|
+
through = adapter.checkpoint
|
|
108
|
+
if through <= adapter.compacted_through
|
|
109
|
+
return "SKIP #{name}: nothing new to compact (already compacted through #{through})"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
removed = adapter.compact_entries!(through: through)
|
|
113
|
+
"COMPACTED #{name}: deleted #{removed} journal entries through sequence #{through}; " \
|
|
114
|
+
"#{adapter.count} records preserved"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def heki_dry(candidate)
|
|
118
|
+
name = candidate.aggregate.storage_name
|
|
119
|
+
adapter = candidate.adapter
|
|
120
|
+
return "SKIP #{name}: journal already empty" if adapter.entries.empty?
|
|
121
|
+
|
|
122
|
+
"DRY RUN #{name}: would discard #{adapter.entries.length} journal entries (#{journal_bytes(adapter)} " \
|
|
123
|
+
"bytes) at #{adapter.path}.journal; current state (#{adapter.count} records) is unaffected."
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def compact_heki(candidate)
|
|
127
|
+
name = candidate.aggregate.storage_name
|
|
128
|
+
adapter = candidate.adapter
|
|
129
|
+
entries = adapter.entries.length
|
|
130
|
+
return "SKIP #{name}: journal already empty" if entries.zero?
|
|
131
|
+
|
|
132
|
+
bytes = journal_bytes(adapter)
|
|
133
|
+
adapter.compact!
|
|
134
|
+
"COMPACTED #{name}: discarded #{entries} journal entries (#{bytes} bytes freed); " \
|
|
135
|
+
"#{adapter.count} records preserved at #{adapter.path}"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def journal_bytes(adapter)
|
|
139
|
+
path = "#{adapter.path}.journal"
|
|
140
|
+
File.exist?(path) ? File.size(path) : 0
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "held_domain"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Adapters
|
|
8
|
+
class JournalStore
|
|
9
|
+
# The translation audit of the edge that leads a domain's latest held era to its current
|
|
10
|
+
# shape, without holding or naming anything: an era that has no name yet is named in memory.
|
|
11
|
+
#
|
|
12
|
+
# It answers what the audit's three layers found, as the report `hecks audit_translation`
|
|
13
|
+
# prints, and the edge itself, so approving it audits the very edge that was read.
|
|
14
|
+
class EdgeAudit
|
|
15
|
+
# What one audit found.
|
|
16
|
+
#
|
|
17
|
+
# @!attribute [r] edge
|
|
18
|
+
# @return [Bluebook::Translation] the audited edge
|
|
19
|
+
# @!attribute [r] leaving
|
|
20
|
+
# @return [Integer] how many edges leave the latest held era
|
|
21
|
+
# @!attribute [r] ok
|
|
22
|
+
# @return [Boolean] whether no layer refused
|
|
23
|
+
# @!attribute [r] report
|
|
24
|
+
# @return [String] the report, one block per aggregate
|
|
25
|
+
Finding = Struct.new(:edge, :leaving, :ok, :report, keyword_init: true)
|
|
26
|
+
|
|
27
|
+
# Audits the edge that leaves the latest held era.
|
|
28
|
+
#
|
|
29
|
+
# @param held [HeldDomain] the loaded domain, translation edges included
|
|
30
|
+
# @param lineage [Lineage] the domain's lineage
|
|
31
|
+
# @param eras [Array<Hash>] the eras the lineage holds, at least one
|
|
32
|
+
# @return [Finding, nil] the finding; nil when the domain stands at era 1, with no edge
|
|
33
|
+
# @raise [Runtime::NotFound] if no edge leads the latest era to the current shape, or a
|
|
34
|
+
# preview query is refused by Postgres
|
|
35
|
+
def self.call(held, lineage, eras)
|
|
36
|
+
new(held, lineage, eras).call
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param held [HeldDomain] the loaded domain
|
|
40
|
+
# @param lineage [Lineage] the domain's lineage
|
|
41
|
+
# @param eras [Array<Hash>] the held eras
|
|
42
|
+
def initialize(held, lineage, eras)
|
|
43
|
+
@held = held
|
|
44
|
+
@lineage = lineage
|
|
45
|
+
@eras = eras
|
|
46
|
+
@manager = PostgresEra::LineageManager
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Finding, nil] see `EdgeAudit.call`
|
|
50
|
+
def call
|
|
51
|
+
latest = @held.named(@eras.last)
|
|
52
|
+
if @held.shape == Runtime::StorageShape.project(@manager.shadow(latest[:held_text]))
|
|
53
|
+
return nil if latest[:ordinal] == 1
|
|
54
|
+
|
|
55
|
+
settled(latest)
|
|
56
|
+
else
|
|
57
|
+
pending(latest)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# The shape already minted: the edge that arrives at the latest era.
|
|
64
|
+
def settled(latest)
|
|
65
|
+
chain = @manager.edge_chain(@held.registry, @held.bluebook, @eras[0..-2], latest[:label])
|
|
66
|
+
finding(chain, latest[:ordinal], 1)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The shape not yet minted: the edge that leaves the latest era for the current shape.
|
|
70
|
+
def pending(latest)
|
|
71
|
+
label = Runtime::StorageShape.mint_hash(@held.bluebook)[0, Runtime::StorageShape::LABEL_LENGTH]
|
|
72
|
+
domain = @held.bluebook.name
|
|
73
|
+
leaving = @held.registry.translations.select { |t| t.domain == domain && t.from == latest[:label] }
|
|
74
|
+
edge = leaving.find { |t| t.to == label } or
|
|
75
|
+
raise Runtime::NotFound, "no translation edge leads #{latest[:label]} to #{label} — " \
|
|
76
|
+
"run `hecks scaffold_translation` first"
|
|
77
|
+
ancestors = @eras[0..-2] + [latest]
|
|
78
|
+
chain = @manager.edge_chain(@held.registry, @held.bluebook, ancestors, label)
|
|
79
|
+
finding(chain, latest[:ordinal] + 1, leaving.size, edge)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def finding(chain, era, leaving, edge = chain.last[:translation])
|
|
83
|
+
lines = []
|
|
84
|
+
ok = true
|
|
85
|
+
@held.bluebook.aggregates.each do |aggregate|
|
|
86
|
+
verdict, records = verdict_for(aggregate, edge, era, chain)
|
|
87
|
+
lines.concat(describe(aggregate, edge, verdict, records))
|
|
88
|
+
ok &&= verdict.ok?
|
|
89
|
+
end
|
|
90
|
+
Finding.new(edge: edge, leaving: leaving, ok: ok, report: lines.join("\n"))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def verdict_for(aggregate, edge, era, chain)
|
|
94
|
+
after = translated(aggregate, era, chain)
|
|
95
|
+
before = if chain.size > 1
|
|
96
|
+
translated(aggregate, era, chain[0..-2])
|
|
97
|
+
else
|
|
98
|
+
@lineage.ancestor_latest(aggregate, era, chain)
|
|
99
|
+
end
|
|
100
|
+
verdict = Translation::Audit.check(aggregate: aggregate, declared: edge.for_aggregate(aggregate.name),
|
|
101
|
+
before: before, after: after)
|
|
102
|
+
[verdict, after.size]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def translated(aggregate, era, chain)
|
|
106
|
+
@lineage.translated_latest(aggregate, era, chain)
|
|
107
|
+
rescue PG::Error => e
|
|
108
|
+
raise Runtime::NotFound, "REFUSED: #{e.message.strip}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def describe(aggregate, edge, verdict, records)
|
|
112
|
+
lines = ["── #{@held.bluebook.name}::#{aggregate.name} (edge #{edge.from} → #{edge.to}, " \
|
|
113
|
+
"#{records} record#{'s' unless records == 1})"]
|
|
114
|
+
verdict.violations.each { |violation| lines << " REFUSED: #{violation}" }
|
|
115
|
+
lines << " dropped (declared data loss): #{verdict.dropped.join(', ')}" unless verdict.dropped.empty?
|
|
116
|
+
unless verdict.unfed.empty?
|
|
117
|
+
lines << " unfed (no rule, no default — add default: if required): #{verdict.unfed.join(', ')}"
|
|
118
|
+
end
|
|
119
|
+
verdict.samples.each { |sample| lines.concat(sample_lines(sample)) }
|
|
120
|
+
lines
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def sample_lines(sample)
|
|
124
|
+
lines = [" ##{sample[:id]}"]
|
|
125
|
+
lines << " before: #{JSON.generate(sample[:before])}" if sample[:before]
|
|
126
|
+
lines << " after: #{JSON.generate(sample[:after])}" if sample[:after]
|
|
127
|
+
lines
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|