hecks 2.9.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 +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 +4 -2
- 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 +7 -4
- 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 +6 -3
- 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,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hecks"
|
|
4
|
+
require_relative "../tools"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Tools
|
|
8
|
+
# Tail-merge: interleaves the old world's post-cut writes into the live head by their recorded
|
|
9
|
+
# global ordinals, in one transaction.
|
|
10
|
+
#
|
|
11
|
+
# A record touched by both worlds since the cut refuses until it has an explicit winner:
|
|
12
|
+
#
|
|
13
|
+
# hecks merge_tail <domain> [--winner <id>=old] [--winner <id>=new] ...
|
|
14
|
+
module TailMerge
|
|
15
|
+
USAGE = "usage: hecks merge_tail <domain> [--winner <id>=old|new] ..."
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# Merges the tail and prints how many writes it interleaved.
|
|
20
|
+
#
|
|
21
|
+
# @param argv [Array<String>] the domain directory and any `--winner <id>=old|new`
|
|
22
|
+
# @param root [String] unused: the domain is read from the path given
|
|
23
|
+
# @return [Integer] 0
|
|
24
|
+
# @raise [SystemExit] when the arguments are malformed, the domain cannot be loaded, or the
|
|
25
|
+
# merge is refused
|
|
26
|
+
def main(argv, **)
|
|
27
|
+
domain_path, winners = parse(argv.dup)
|
|
28
|
+
registry, bluebook = load_domain(domain_path)
|
|
29
|
+
settings = lineage_settings(registry, bluebook)
|
|
30
|
+
report_divergence(bluebook, settings)
|
|
31
|
+
|
|
32
|
+
begin
|
|
33
|
+
Hecks::Adapters::PostgresEra::LineageManager.merge!(
|
|
34
|
+
registry: registry, bluebook: bluebook, settings: settings, winners: winners
|
|
35
|
+
)
|
|
36
|
+
rescue Hecks::Runtime::WiringError => e
|
|
37
|
+
abort "REFUSED: #{e.message}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
puts "merged — the head now interleaves both worlds by their recorded ordinals"
|
|
41
|
+
winners.each { |id, side| puts " winner #{id}=#{side} appended as the newest row" }
|
|
42
|
+
0
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Array] the domain directory and the winners by record id
|
|
46
|
+
def parse(argv)
|
|
47
|
+
domain_path = nil
|
|
48
|
+
winners = {}
|
|
49
|
+
until argv.empty?
|
|
50
|
+
argument = argv.shift
|
|
51
|
+
if argument == "--winner"
|
|
52
|
+
declaration = argv.shift or abort "--winner needs <id>=old|new"
|
|
53
|
+
id, side = declaration.split("=", 2)
|
|
54
|
+
abort "--winner #{declaration}: the side must be `old` or `new`" unless %w[old new].include?(side)
|
|
55
|
+
winners[id] = side
|
|
56
|
+
else
|
|
57
|
+
domain_path = argument
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
abort USAGE unless domain_path
|
|
61
|
+
[domain_path, winners]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @param domain_path [String] the domain directory
|
|
65
|
+
# @return [Array] the registry and its bluebook
|
|
66
|
+
def load_domain(domain_path)
|
|
67
|
+
loading = Hecks::Ports::Loading.bootstrap
|
|
68
|
+
directory = loading.bluebook_directory(domain_path)
|
|
69
|
+
registry = Hecks::Runtime::Registry.new(root: File.dirname(directory))
|
|
70
|
+
begin
|
|
71
|
+
Hecks.with_registry(registry) do
|
|
72
|
+
loading.load_library
|
|
73
|
+
loading.load_project(loading.shared_root(nil, directory))
|
|
74
|
+
loading.load_domain(directory)
|
|
75
|
+
end
|
|
76
|
+
rescue Hecks::Bluebook::DSL::Malformed => e
|
|
77
|
+
abort "REFUSED at load: #{e.message}"
|
|
78
|
+
end
|
|
79
|
+
bluebook = registry.bluebooks.values.first or abort "no bluebook in #{directory}"
|
|
80
|
+
[registry, bluebook]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @return [Hash] the persistence binding's settings
|
|
84
|
+
# @raise [SystemExit] when the bound adapter is not lineage-capable
|
|
85
|
+
def lineage_settings(registry, bluebook)
|
|
86
|
+
first = bluebook.aggregates.first or abort "#{bluebook.name} declares no aggregates"
|
|
87
|
+
adapter_name = Hecks::Ports::Persistence::BindingPolicy.resolve(registry, bluebook.name, first).adapter
|
|
88
|
+
capable =
|
|
89
|
+
begin
|
|
90
|
+
adapter_class = registry.adapter_class(adapter_name)
|
|
91
|
+
adapter_class.respond_to?(:lineage_capable?) && adapter_class.lineage_capable?
|
|
92
|
+
rescue StandardError
|
|
93
|
+
false
|
|
94
|
+
end
|
|
95
|
+
unless capable
|
|
96
|
+
abort "a tail-merge needs a lineage journal; #{bluebook.name} is bound to #{adapter_name}, " \
|
|
97
|
+
"not PostgresEra"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
registry.binding_settings(bluebook.name, Hecks::Ports::Persistence::VERB, adapter_name)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @return [void]
|
|
104
|
+
def report_divergence(bluebook, settings)
|
|
105
|
+
db = Hecks::Adapters::PostgresEra.connect_for(bluebook.name, settings)
|
|
106
|
+
lineage = Hecks::Adapters::PostgresEra::Lineage.new(db, bluebook.name)
|
|
107
|
+
lineage.ensure_base!
|
|
108
|
+
eras = lineage.eras
|
|
109
|
+
diverged = eras.size > 1 ? (1...eras.last[:ordinal]).sum { |era| lineage.diverged_count(era) } : 0
|
|
110
|
+
db.close
|
|
111
|
+
|
|
112
|
+
puts "#{bluebook.name}: #{diverged} post-cut write#{'s' unless diverged == 1} " \
|
|
113
|
+
"in ancestor eras before the merge"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hecks"
|
|
4
|
+
# --adapter=PostgresEra needs the era persistence plugin (ADR 0033).
|
|
5
|
+
require "hecks/ports/persistence/plugins/era"
|
|
6
|
+
require "optparse"
|
|
7
|
+
require_relative "../tools"
|
|
8
|
+
|
|
9
|
+
module Hecks
|
|
10
|
+
module Tools
|
|
11
|
+
# Provisions a tenant: validates the declared shape, writes the `.world` overlay through the
|
|
12
|
+
# `TenantProvisioning` port, boots once, and checks `tenant_capable?` and the Tenancy record.
|
|
13
|
+
#
|
|
14
|
+
# Deploy and Tenancy boot into one shared registry so the `translates` reaction in
|
|
15
|
+
# tenancy.hecksagon can see Deploy's `TenantProvisioned` event. The overlay replaces (shallow
|
|
16
|
+
# merge) the base `persisted_by` settings, so `--database` is required. A policy's refusal is
|
|
17
|
+
# swallowed by `PolicyInterpreter#deliver`, so success is confirmed by query.
|
|
18
|
+
#
|
|
19
|
+
# hecks deploy provision <domain-directory> <slug> --domain=name --realm=name \
|
|
20
|
+
# --schema=name --database=name [--adapter=PostgresEra]
|
|
21
|
+
module TenantProvisioning
|
|
22
|
+
USAGE = "usage: hecks deploy provision <domain-directory> <slug> --domain=NAME --realm=NAME " \
|
|
23
|
+
"--schema=NAME --database=NAME [--adapter=PostgresEra]"
|
|
24
|
+
|
|
25
|
+
# The chapters that must boot together for a tenant to be declared and registered.
|
|
26
|
+
CHAPTER_FILES = %w[deploy/bluebook/deploy.bluebook deploy/bluebook/deploy.hecksagon
|
|
27
|
+
tenancy/bluebook/tenancy.bluebook tenancy/bluebook/tenancy.hecksagon].freeze
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# Provisions the tenant and prints what it wrote and confirmed.
|
|
32
|
+
#
|
|
33
|
+
# @param argv [Array<String>] the domain directory, the tenant's slug, then the flags
|
|
34
|
+
# @param root [String] unused: the chapters are read from the library
|
|
35
|
+
# @return [Integer] 0
|
|
36
|
+
# @raise [SystemExit] with the reason on stderr when the shape is invalid, provisioning is
|
|
37
|
+
# refused, or the tenant never registers
|
|
38
|
+
def main(argv, **)
|
|
39
|
+
argv = argv.dup
|
|
40
|
+
directory = argv.shift
|
|
41
|
+
slug = argv.shift
|
|
42
|
+
(directory && slug) or abort USAGE
|
|
43
|
+
|
|
44
|
+
options = parse(argv)
|
|
45
|
+
domain_directory = File.expand_path(directory)
|
|
46
|
+
File.directory?(domain_directory) or abort "no such domain directory: #{domain_directory}"
|
|
47
|
+
|
|
48
|
+
dispatcher = provision(slug, options, domain_directory)
|
|
49
|
+
confirm(dispatcher, slug, options, domain_directory)
|
|
50
|
+
0
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @param argv [Array<String>] the flags after the slug
|
|
54
|
+
# @return [Hash{Symbol => String}] `domain`, `realm`, `schema`, `database` and `adapter`
|
|
55
|
+
# @raise [SystemExit] when a required flag is missing
|
|
56
|
+
def parse(argv)
|
|
57
|
+
options = { adapter: "PostgresEra" }
|
|
58
|
+
OptionParser.new do |parser|
|
|
59
|
+
parser.on("--domain=NAME") { |v| options[:domain] = v }
|
|
60
|
+
parser.on("--realm=NAME") { |v| options[:realm] = v }
|
|
61
|
+
parser.on("--schema=NAME") { |v| options[:schema] = v }
|
|
62
|
+
parser.on("--database=NAME") { |v| options[:database] = v }
|
|
63
|
+
parser.on("--adapter=NAME") { |v| options[:adapter] = v }
|
|
64
|
+
end.parse!(argv)
|
|
65
|
+
|
|
66
|
+
%i[domain realm schema database].each do |flag|
|
|
67
|
+
options[flag] or abort "hecks deploy provision needs --#{flag}"
|
|
68
|
+
end
|
|
69
|
+
options
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Boots Deploy and Tenancy together, validates the declared shape with a dry run of
|
|
73
|
+
# `Tenant.Provision` (nothing is recorded), then dispatches it: the record is created once,
|
|
74
|
+
# its policy writes the overlay through the `TenantProvisioning` port, and `TenantProvisioned`
|
|
75
|
+
# reaches Tenancy. The port answers `TenantProvisioned` or `ProvisioningRefused` and never
|
|
76
|
+
# raises, so the record's status says which.
|
|
77
|
+
#
|
|
78
|
+
# @param slug [String] the tenant's slug
|
|
79
|
+
# @param options [Hash] the parsed flags
|
|
80
|
+
# @param domain_directory [String] the domain's directory
|
|
81
|
+
# @return [Hecks::Runtime::Dispatcher] the dispatcher both chapters share
|
|
82
|
+
# @raise [SystemExit] when the declared shape is refused, or provisioning is refused
|
|
83
|
+
def provision(slug, options, domain_directory)
|
|
84
|
+
lib_hecks = File.expand_path("..", __dir__)
|
|
85
|
+
dispatcher = Hecks.boot_files(CHAPTER_FILES.map { |file| File.join(lib_hecks, file) }, install_doors: false)
|
|
86
|
+
facts = {
|
|
87
|
+
slug: { value: slug },
|
|
88
|
+
domain: { value: options[:domain] },
|
|
89
|
+
realm: { value: options[:realm] },
|
|
90
|
+
schema: { value: options[:schema] },
|
|
91
|
+
database: { value: options[:database] },
|
|
92
|
+
adapter: { value: options[:adapter] },
|
|
93
|
+
directory: { value: domain_directory }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
begin
|
|
97
|
+
dispatcher.dry_run?("Deploy::Tenant.Provision", **facts)
|
|
98
|
+
dispatcher.dispatch("Deploy::Tenant.Provision", to: slug, with: facts)
|
|
99
|
+
rescue *Hecks::Runtime::DOMAIN_REFUSALS => e
|
|
100
|
+
abort "tenant #{slug.inspect} is invalid: #{e.message}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
tenant = dispatcher.registry.bluebook("Deploy").aggregate("Tenant")
|
|
104
|
+
record = dispatcher.registry.repository("Deploy", tenant).find(slug)
|
|
105
|
+
refused = record&.state&.dig(:refusal)
|
|
106
|
+
refused = refused[:value] if refused.is_a?(Hash)
|
|
107
|
+
record&.state&.dig(:status) == "refused" and
|
|
108
|
+
abort "provisioning #{slug.inspect} was refused: #{refused}"
|
|
109
|
+
|
|
110
|
+
puts "wrote #{File.join(domain_directory, 'environments', "#{slug}.world")}"
|
|
111
|
+
dispatcher
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Boots the domain under the tenant's overlay (which also provisions the schema, since
|
|
115
|
+
# `PostgresEra#connect_for` runs an idempotent `CREATE SCHEMA`), checks every bluebook of
|
|
116
|
+
# the registry is tenant capable, and confirms the tenant by query.
|
|
117
|
+
#
|
|
118
|
+
# @param dispatcher [Hecks::Runtime::Dispatcher] the shared dispatcher
|
|
119
|
+
# @param slug [String] the tenant's slug
|
|
120
|
+
# @param options [Hash] the parsed flags
|
|
121
|
+
# @param domain_directory [String] the domain's directory
|
|
122
|
+
# @return [void]
|
|
123
|
+
# @raise [SystemExit] when the tenant was never registered in Tenancy
|
|
124
|
+
def confirm(dispatcher, slug, options, domain_directory)
|
|
125
|
+
target = Hecks.boot(domain_directory, environment: slug, install_doors: false)
|
|
126
|
+
puts "booted #{options[:domain]} for tenant #{slug.inspect} under realm #{options[:realm].inspect}"
|
|
127
|
+
|
|
128
|
+
target.registry.bluebooks.each_key do |name|
|
|
129
|
+
Hecks::Runtime::TenantCheck.refuse_unless_tenant_capable!(target.registry, name)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# A refused reaction never raises, so the record itself is the proof.
|
|
133
|
+
tenant = dispatcher.registry.repository("Tenancy", dispatcher.registry.bluebook("Tenancy").aggregate("Tenant"))
|
|
134
|
+
.find(slug)
|
|
135
|
+
tenant or abort "provisioning succeeded but #{slug.inspect} was never registered in Tenancy — " \
|
|
136
|
+
"check registry.reaction_log for a silently refused Register"
|
|
137
|
+
|
|
138
|
+
puts "#{options[:domain]} is tenant_capable? for #{slug.inspect} — registered under realm #{options[:realm].inspect}"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hecks"
|
|
4
|
+
# The audit reads the era persistence plugin's lineage journal and its audit machinery (ADR 0033).
|
|
5
|
+
require "hecks/ports/persistence/plugins/era"
|
|
6
|
+
require "json"
|
|
7
|
+
require_relative "../tools"
|
|
8
|
+
|
|
9
|
+
module Hecks
|
|
10
|
+
module Tools
|
|
11
|
+
# Runs the translation audit's three layers standalone against a domain's latest or pending
|
|
12
|
+
# edge. Read-only except `--approve`, which records the human approval in the database.
|
|
13
|
+
#
|
|
14
|
+
# hecks audit_translation <domain> [--approve]
|
|
15
|
+
module TranslationAudit
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# Audits the edge and prints each aggregate's verdict and samples.
|
|
19
|
+
#
|
|
20
|
+
# @param argv [Array<String>] the domain directory, and `--approve` to record approval
|
|
21
|
+
# @param root [String] unused: the domain is read from the path given
|
|
22
|
+
# @return [Integer] 0 when the audit passes or there is no edge, 1 when it is refused
|
|
23
|
+
# @raise [SystemExit] when the domain cannot be loaded or is not lineage-capable
|
|
24
|
+
def main(argv, **)
|
|
25
|
+
argv = argv.dup
|
|
26
|
+
approve = argv.delete("--approve")
|
|
27
|
+
domain_path = argv.shift or abort "usage: hecks audit_translation <domain> [--approve]"
|
|
28
|
+
|
|
29
|
+
registry, bluebook, directory = load_domain(domain_path)
|
|
30
|
+
db, lineage = open_lineage(registry, bluebook)
|
|
31
|
+
audit(registry, bluebook, directory, db, lineage, approve)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param domain_path [String] the domain directory
|
|
35
|
+
# @return [Array] the registry, its bluebook and the bluebook directory
|
|
36
|
+
def load_domain(domain_path)
|
|
37
|
+
loading = Hecks::Ports::Loading.bootstrap
|
|
38
|
+
directory = loading.bluebook_directory(domain_path)
|
|
39
|
+
registry = Hecks::Runtime::Registry.new(root: File.dirname(directory))
|
|
40
|
+
begin
|
|
41
|
+
Hecks.with_registry(registry) do
|
|
42
|
+
loading.load_library
|
|
43
|
+
loading.load_project(loading.shared_root(nil, directory))
|
|
44
|
+
# Passes an environment overlay to `load_domain`, as `scaffold_translation` does.
|
|
45
|
+
loading.load_domain(directory, environment: ENV.fetch("HECKS_PROJECT_ENVIRONMENT", nil))
|
|
46
|
+
end
|
|
47
|
+
rescue Hecks::Bluebook::DSL::Malformed => e
|
|
48
|
+
abort "REFUSED at load: #{e.message}"
|
|
49
|
+
end
|
|
50
|
+
bluebook = registry.bluebooks.values.first or abort "no bluebook in #{directory}"
|
|
51
|
+
[registry, bluebook, directory]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @param registry [Hecks::Runtime::Registry] the loaded registry
|
|
55
|
+
# @param bluebook [Object] the domain's bluebook
|
|
56
|
+
# @return [Array] the open connection and its ensured lineage
|
|
57
|
+
# @raise [SystemExit] when the bound adapter is not lineage-capable
|
|
58
|
+
def open_lineage(registry, bluebook)
|
|
59
|
+
first = bluebook.aggregates.first or abort "#{bluebook.name} declares no aggregates"
|
|
60
|
+
adapter_name = Hecks::Ports::Persistence::BindingPolicy.resolve(registry, bluebook.name, first).adapter
|
|
61
|
+
capable =
|
|
62
|
+
begin
|
|
63
|
+
adapter_class = registry.adapter_class(adapter_name)
|
|
64
|
+
adapter_class.respond_to?(:lineage_capable?) && adapter_class.lineage_capable?
|
|
65
|
+
rescue StandardError
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
unless capable
|
|
69
|
+
abort "the audit reads a lineage journal; #{bluebook.name} is bound to #{adapter_name}, " \
|
|
70
|
+
"not PostgresEra"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
settings = registry.world(bluebook.name)&.for_binding(Hecks::Ports::Persistence::VERB, adapter_name) || {}
|
|
74
|
+
# The same `HECKS_SCHEMA` support as `scaffold_translation`.
|
|
75
|
+
settings = settings.merge(schema: ENV["HECKS_SCHEMA"]) if ENV["HECKS_SCHEMA"]
|
|
76
|
+
db = Hecks::Adapters::PostgresEra.connect_for(bluebook.name, settings)
|
|
77
|
+
lineage = Hecks::Adapters::PostgresEra::Lineage.new(db, bluebook.name)
|
|
78
|
+
lineage.ensure_base!
|
|
79
|
+
[db, lineage]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @return [Integer] the exit status
|
|
83
|
+
def audit(registry, bluebook, directory, db, lineage, approve)
|
|
84
|
+
if lineage.eras.empty?
|
|
85
|
+
lineage.hold_first!(Hecks::Runtime::EraCheck.source_text_for(bluebook, directory),
|
|
86
|
+
projection: Hecks::Runtime::StorageShape.project(bluebook))
|
|
87
|
+
puts "#{bluebook.name} held era 1 just now — no edge to audit."
|
|
88
|
+
db.close
|
|
89
|
+
return 0
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
era, chain = edge_chain(registry, bluebook, lineage)
|
|
93
|
+
return 0 unless era
|
|
94
|
+
|
|
95
|
+
edge = chain.last[:translation]
|
|
96
|
+
failed = report(bluebook, lineage, edge, era, chain)
|
|
97
|
+
conclude(db, lineage, edge, failed, approve)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# The era the edge leads to and the chain of translations ending in it, or nil when the
|
|
101
|
+
# journal stands at era 1 with nothing to audit.
|
|
102
|
+
#
|
|
103
|
+
# @return [Array, nil] the era ordinal and the chain
|
|
104
|
+
def edge_chain(registry, bluebook, lineage)
|
|
105
|
+
manager = Hecks::Adapters::PostgresEra::LineageManager
|
|
106
|
+
eras = lineage.eras
|
|
107
|
+
latest = eras.last
|
|
108
|
+
current_shape = Hecks::Runtime::StorageShape.project(bluebook)
|
|
109
|
+
held_shape = Hecks::Runtime::StorageShape.project(manager.shadow(latest[:held_text]))
|
|
110
|
+
|
|
111
|
+
if held_shape == current_shape
|
|
112
|
+
era = latest[:ordinal]
|
|
113
|
+
if era == 1
|
|
114
|
+
puts "#{bluebook.name} stands at era 1 — no edge to audit."
|
|
115
|
+
return nil
|
|
116
|
+
end
|
|
117
|
+
[era, manager.edge_chain(registry, bluebook, eras, latest[:label])]
|
|
118
|
+
else
|
|
119
|
+
# a pending edge: audit it before any mint
|
|
120
|
+
manager.ensure_named!(lineage, latest)
|
|
121
|
+
latest = lineage.eras.last
|
|
122
|
+
to_label = Hecks::Runtime::StorageShape.mint_hash(bluebook)[0, Hecks::Runtime::StorageShape::LABEL_LENGTH]
|
|
123
|
+
pending = registry.translations.find do |t|
|
|
124
|
+
t.domain == bluebook.name && t.from == latest[:label] && t.to == to_label
|
|
125
|
+
end
|
|
126
|
+
pending or abort "no translation edge leads #{latest[:label]} to #{to_label} — " \
|
|
127
|
+
"run hecks scaffold_translation first"
|
|
128
|
+
chain = begin
|
|
129
|
+
manager.edge_chain(registry, bluebook, eras, latest[:label])
|
|
130
|
+
rescue StandardError
|
|
131
|
+
[]
|
|
132
|
+
end
|
|
133
|
+
[latest[:ordinal] + 1, (chain || []) + [{ translation: pending }]]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Prints each aggregate's verdict.
|
|
138
|
+
#
|
|
139
|
+
# @return [Boolean] whether any aggregate's audit was refused
|
|
140
|
+
def report(bluebook, lineage, edge, era, chain)
|
|
141
|
+
failed = false
|
|
142
|
+
bluebook.aggregates.each do |aggregate|
|
|
143
|
+
declared = edge.for_aggregate(aggregate.name)
|
|
144
|
+
after = begin
|
|
145
|
+
lineage.translated_latest(aggregate, era, chain)
|
|
146
|
+
rescue PG::Error => e
|
|
147
|
+
abort "REFUSED: #{e.message.strip}"
|
|
148
|
+
end
|
|
149
|
+
before = if chain.size > 1
|
|
150
|
+
lineage.translated_latest(aggregate, era, chain[0..-2])
|
|
151
|
+
else
|
|
152
|
+
lineage.ancestor_latest(aggregate, era, chain)
|
|
153
|
+
end
|
|
154
|
+
verdict = Hecks::Translation::Audit.check(aggregate: aggregate, declared: declared,
|
|
155
|
+
before: before, after: after)
|
|
156
|
+
print_verdict(bluebook, aggregate, edge, after, verdict)
|
|
157
|
+
failed ||= !verdict.ok?
|
|
158
|
+
end
|
|
159
|
+
failed
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @return [void]
|
|
163
|
+
def print_verdict(bluebook, aggregate, edge, after, verdict)
|
|
164
|
+
puts "── #{bluebook.name}::#{aggregate.name} (edge #{edge.from} → #{edge.to}, " \
|
|
165
|
+
"#{after.size} record#{'s' unless after.size == 1})"
|
|
166
|
+
verdict.violations.each { |violation| puts " REFUSED: #{violation}" }
|
|
167
|
+
puts " dropped (declared data loss): #{verdict.dropped.join(', ')}" unless verdict.dropped.empty?
|
|
168
|
+
puts " unfed (no rule, no default — add default: if required): #{verdict.unfed.join(', ')}" unless verdict.unfed.empty?
|
|
169
|
+
verdict.samples.each do |sample|
|
|
170
|
+
puts " ##{sample[:id]}"
|
|
171
|
+
puts " before: #{JSON.generate(sample[:before])}" if sample[:before]
|
|
172
|
+
puts " after: #{JSON.generate(sample[:after])}" if sample[:after]
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# @return [Integer] the exit status
|
|
177
|
+
def conclude(db, lineage, edge, failed, approve)
|
|
178
|
+
if failed
|
|
179
|
+
db.close
|
|
180
|
+
puts "AUDIT REFUSED."
|
|
181
|
+
return 1
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
needs_approval = edge.aggregates.any? { |declared| !declared.computes.empty? || !declared.rekeys.empty? }
|
|
185
|
+
if needs_approval && approve
|
|
186
|
+
# Binds to what was reviewed — this edge and the journal as it stands now — so it must
|
|
187
|
+
# live in the database, not the repo.
|
|
188
|
+
lineage.record_approval!(
|
|
189
|
+
from: edge.from, to: edge.to,
|
|
190
|
+
edge_digest: Hecks::Translation::Audit.edge_digest(edge)
|
|
191
|
+
)
|
|
192
|
+
db.close
|
|
193
|
+
puts "AUDIT PASSED — approval recorded in the database, bound to this edge and this journal."
|
|
194
|
+
puts "A journal that advances before the mint invalidates it; re-run with --approve if that happens."
|
|
195
|
+
elsif needs_approval
|
|
196
|
+
db.close
|
|
197
|
+
puts "AUDIT PASSED — this edge carries a compute or rekey rule, and the samples above are " \
|
|
198
|
+
"its ONLY verification."
|
|
199
|
+
puts "If they show what you intended, run again with --approve; the mint refuses until you do."
|
|
200
|
+
else
|
|
201
|
+
db.close
|
|
202
|
+
puts "AUDIT PASSED — review the samples above; intent is yours to approve."
|
|
203
|
+
end
|
|
204
|
+
0
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hecks"
|
|
4
|
+
# The translation and scaffold machinery lives in the era persistence plugin (ADR 0033).
|
|
5
|
+
require "hecks/ports/persistence/plugins/era"
|
|
6
|
+
require_relative "../tools"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module Tools
|
|
10
|
+
# Diffs the held era against the current bluebook into an edge file: confident rules inline,
|
|
11
|
+
# ambiguities as `unresolved` lines to resolve by hand.
|
|
12
|
+
#
|
|
13
|
+
# hecks scaffold_translation <domain>
|
|
14
|
+
module TranslationScaffold
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# Writes the edge file and prints what is left to decide.
|
|
18
|
+
#
|
|
19
|
+
# @param argv [Array<String>] the domain directory
|
|
20
|
+
# @param root [String] unused: the domain is read from the path given
|
|
21
|
+
# @return [Integer] 0
|
|
22
|
+
# @raise [SystemExit] when the domain cannot be loaded or its adapter holds no eras
|
|
23
|
+
def main(argv, **)
|
|
24
|
+
argv = argv.dup
|
|
25
|
+
domain_path = argv.shift or abort "usage: hecks scaffold_translation <domain>"
|
|
26
|
+
|
|
27
|
+
registry, bluebook, directory = load_domain(domain_path)
|
|
28
|
+
first = bluebook.aggregates.first or abort "#{bluebook.name} declares no aggregates"
|
|
29
|
+
adapter_name = Hecks::Ports::Persistence::BindingPolicy.resolve(registry, bluebook.name, first).adapter
|
|
30
|
+
capable =
|
|
31
|
+
begin
|
|
32
|
+
adapter_class = registry.adapter_class(adapter_name)
|
|
33
|
+
adapter_class.respond_to?(:lineage_capable?) && adapter_class.lineage_capable?
|
|
34
|
+
rescue StandardError
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
# Eras belong to the adapters that can carry data across them. An edge scaffolded for an
|
|
38
|
+
# adapter that cannot apply it would be a document nothing will ever run.
|
|
39
|
+
unless capable
|
|
40
|
+
abort "#{bluebook.name} is bound to #{adapter_name}, which holds no eras — " \
|
|
41
|
+
"a translation edge is only applied by a lineage-capable adapter. " \
|
|
42
|
+
"Bind PostgresEra, or change the shape and its stored data by hand."
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
scaffold(registry, bluebook, directory, adapter_name)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param domain_path [String] the domain directory
|
|
49
|
+
# @return [Array] the registry, its bluebook and the bluebook directory
|
|
50
|
+
def load_domain(domain_path)
|
|
51
|
+
loading = Hecks::Ports::Loading.bootstrap
|
|
52
|
+
directory = loading.bluebook_directory(domain_path)
|
|
53
|
+
registry = Hecks::Runtime::Registry.new(root: File.dirname(directory))
|
|
54
|
+
Hecks.with_registry(registry) do
|
|
55
|
+
loading.load_library
|
|
56
|
+
loading.load_project(loading.shared_root(nil, directory))
|
|
57
|
+
# deliberately not translations/*.bluebook: an unresolved edge file refuses to load, and
|
|
58
|
+
# regenerating it is this tool's whole job
|
|
59
|
+
loading.load_each(directory, %w[*.port *.adapter])
|
|
60
|
+
loading.load_bluebooks(directory)
|
|
61
|
+
loading.load_each(directory, %w[*.hecksagon *.world])
|
|
62
|
+
|
|
63
|
+
# A domain that splits `persisted_by` per environment declares none in its base
|
|
64
|
+
# `.hecksagon`, so resolution fails without this overlay — opt-in via
|
|
65
|
+
# HECKS_PROJECT_ENVIRONMENT, a no-op otherwise.
|
|
66
|
+
if (target_environment = ENV.fetch("HECKS_PROJECT_ENVIRONMENT", nil))
|
|
67
|
+
loading.load_each(directory, [File.join("environments", "#{target_environment}.hecksagon")])
|
|
68
|
+
loading.load_each(directory, [File.join("environments", "#{target_environment}.world")])
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
bluebook = registry.bluebooks.values.first or abort "no bluebook in #{directory}"
|
|
72
|
+
[registry, bluebook, directory]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [Integer] 0
|
|
76
|
+
def scaffold(registry, bluebook, directory, adapter_name)
|
|
77
|
+
current_shape = Hecks::Runtime::StorageShape.project(bluebook)
|
|
78
|
+
manager = Hecks::Adapters::PostgresEra::LineageManager
|
|
79
|
+
|
|
80
|
+
# `World#for_binding` keys settings by the world-declared adapter name (e.g.
|
|
81
|
+
# "PostgresEra"); a bypassed name like "Postgres" finds nothing.
|
|
82
|
+
settings = registry.world(bluebook.name)&.for_binding(Hecks::Ports::Persistence::VERB, adapter_name) || {}
|
|
83
|
+
# A Shared-mode domain's `.world` file declares no schema of its own — production is scoped
|
|
84
|
+
# by the Lambda's own HECKS_SCHEMA. Run by hand outside Lambda, this tool needs that same
|
|
85
|
+
# schema supplied the same way.
|
|
86
|
+
settings = settings.merge(schema: ENV["HECKS_SCHEMA"]) if ENV["HECKS_SCHEMA"]
|
|
87
|
+
db = Hecks::Adapters::PostgresEra.connect_for(bluebook.name, settings)
|
|
88
|
+
lineage = Hecks::Adapters::PostgresEra::Lineage.new(db, bluebook.name)
|
|
89
|
+
lineage.ensure_base!
|
|
90
|
+
if lineage.eras.empty?
|
|
91
|
+
lineage.hold_first!(Hecks::Runtime::EraCheck.source_text_for(bluebook, directory),
|
|
92
|
+
projection: current_shape)
|
|
93
|
+
puts "#{bluebook.name} held era 1 just now — nothing to scaffold."
|
|
94
|
+
return 0
|
|
95
|
+
end
|
|
96
|
+
latest = lineage.eras.last
|
|
97
|
+
held_bluebook = manager.shadow(latest[:held_text])
|
|
98
|
+
if Hecks::Runtime::StorageShape.project(held_bluebook) == current_shape
|
|
99
|
+
puts "#{bluebook.name} matches era #{latest[:ordinal]} — nothing to scaffold."
|
|
100
|
+
return 0
|
|
101
|
+
end
|
|
102
|
+
path = manager.scaffold!(registry, bluebook, lineage, latest, directory)
|
|
103
|
+
diffed = Hecks::Translation::Scaffold.diff(held_bluebook, bluebook)
|
|
104
|
+
db.close
|
|
105
|
+
|
|
106
|
+
report(path, diffed)
|
|
107
|
+
0
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @return [void]
|
|
111
|
+
def report(path, diffed)
|
|
112
|
+
text = File.read(path)
|
|
113
|
+
unresolved = text.scan(/^\s*unresolved /).size
|
|
114
|
+
unclaimed = diffed ? diffed[:unclaimed] : []
|
|
115
|
+
puts "wrote #{path}"
|
|
116
|
+
unclaimed.each do |name|
|
|
117
|
+
puts "UNCLAIMED: #{name} existed and now doesn't, and its successor is ambiguous — " \
|
|
118
|
+
"add `aggregate \"NewName\", was: #{name.inspect}` (with its rules) or " \
|
|
119
|
+
"`retired #{name.inspect}` by hand."
|
|
120
|
+
end
|
|
121
|
+
if unresolved.zero? && unclaimed.empty?
|
|
122
|
+
puts "0 unresolved — this shape change costs one extra boot and no typing. " \
|
|
123
|
+
"Check it with hecks audit_translation, then boot."
|
|
124
|
+
elsif unresolved.positive?
|
|
125
|
+
puts "#{unresolved} unresolved — decide what each became (rename/move/convert/drop, or " \
|
|
126
|
+
"compute on PostgresEra), then boot."
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|