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,474 @@
|
|
|
1
|
+
//! Lambda custom-runtime entry point (or a long-lived server under
|
|
2
|
+
//! HECKS_SERVE_MODE=1): boots Postgres/wasm state, then dispatches events.
|
|
3
|
+
|
|
4
|
+
mod api;
|
|
5
|
+
mod approval;
|
|
6
|
+
mod auth;
|
|
7
|
+
mod checkout;
|
|
8
|
+
mod dispatch;
|
|
9
|
+
mod expr_json;
|
|
10
|
+
mod field_hints;
|
|
11
|
+
mod ir;
|
|
12
|
+
mod journal;
|
|
13
|
+
mod lambda_client;
|
|
14
|
+
mod log;
|
|
15
|
+
mod mint;
|
|
16
|
+
mod payments;
|
|
17
|
+
mod presentation;
|
|
18
|
+
mod presentation_write;
|
|
19
|
+
mod query_step;
|
|
20
|
+
mod rate_limit;
|
|
21
|
+
mod reference_transform;
|
|
22
|
+
mod reference_validate;
|
|
23
|
+
mod resend;
|
|
24
|
+
mod secrets;
|
|
25
|
+
mod server;
|
|
26
|
+
mod storage_shape;
|
|
27
|
+
#[cfg(test)]
|
|
28
|
+
mod test_pg;
|
|
29
|
+
mod ui_schema;
|
|
30
|
+
mod wasm_runner;
|
|
31
|
+
mod web;
|
|
32
|
+
|
|
33
|
+
use lambda_runtime::{service_fn, Error, LambdaEvent};
|
|
34
|
+
use std::path::PathBuf;
|
|
35
|
+
use std::sync::Arc;
|
|
36
|
+
use tokio::sync::Mutex;
|
|
37
|
+
|
|
38
|
+
#[tokio::main]
|
|
39
|
+
async fn main() -> Result<(), Error> {
|
|
40
|
+
// HECKS_SERVE_MODE=1 switches the boot into the long-lived server
|
|
41
|
+
// loop below instead of the Lambda custom-runtime loop; every step
|
|
42
|
+
// from here through the era-minting sequence is shared, unchanged,
|
|
43
|
+
// by both modes.
|
|
44
|
+
let boot_started = std::time::Instant::now();
|
|
45
|
+
let serve_mode = std::env::var("HECKS_SERVE_MODE").as_deref() == Ok("1");
|
|
46
|
+
|
|
47
|
+
// HECKS_SESSION_COOKIE names the account cookie; refuse the boot
|
|
48
|
+
// here if it can't be written into a Set-Cookie header, rather
|
|
49
|
+
// than falling back silently at request time.
|
|
50
|
+
auth::resolve_account_cookie(std::env::var("HECKS_SESSION_COOKIE").ok().as_deref())?;
|
|
51
|
+
|
|
52
|
+
// DB_SECRET_ARN: the password is fetched from Secrets Manager here,
|
|
53
|
+
// at cold start, rather than trusted from a plaintext
|
|
54
|
+
// Environment.Variables value, which any principal with
|
|
55
|
+
// lambda:GetFunctionConfiguration could read regardless of the
|
|
56
|
+
// secret-at-rest protection its name suggests. Falls back to
|
|
57
|
+
// DATABASE_URL directly when DB_SECRET_ARN is absent (manual
|
|
58
|
+
// debugging over an SSM tunnel).
|
|
59
|
+
//
|
|
60
|
+
// The fetcher built here is reused below for GOOGLE_OAUTH_SECRET_ID
|
|
61
|
+
// and SESSION_SECRET_ARN too.
|
|
62
|
+
let secret_fetcher = secrets::AwsSecretFetcher::from_env().await;
|
|
63
|
+
let database_url = match std::env::var("DB_SECRET_ARN") {
|
|
64
|
+
Ok(secret_arn) => {
|
|
65
|
+
let db_host = std::env::var("DB_HOST").map_err(|_| "DB_HOST is required when DB_SECRET_ARN is set")?;
|
|
66
|
+
let db_name = std::env::var("DB_NAME").map_err(|_| "DB_NAME is required when DB_SECRET_ARN is set")?;
|
|
67
|
+
let secret_json = secret_fetcher
|
|
68
|
+
.fetch_secret_string(&secret_arn)
|
|
69
|
+
.await
|
|
70
|
+
.map_err(|e| format!("fetching DB_SECRET_ARN from Secrets Manager: {e:#}"))?;
|
|
71
|
+
let password = secrets::extract_field(&secret_json, "password")?;
|
|
72
|
+
// Literal, unescaped, no percent-encoding -- parse_database_url
|
|
73
|
+
// below never percent-decodes the password segment either way.
|
|
74
|
+
format!("postgres://postgres:{password}@{db_host}:5432/{db_name}")
|
|
75
|
+
}
|
|
76
|
+
Err(_) => std::env::var("DATABASE_URL")
|
|
77
|
+
.map_err(|_| "either DB_SECRET_ARN (+ DB_HOST/DB_NAME) or DATABASE_URL is required")?,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// GOOGLE_OAUTH_SECRET_ID/SESSION_SECRET_ARN: the same
|
|
81
|
+
// Secrets-Manager-at-cold-start approach as DB_SECRET_ARN above,
|
|
82
|
+
// applied to auth.rs's GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET and
|
|
83
|
+
// web.rs's SESSION_SECRET.
|
|
84
|
+
//
|
|
85
|
+
// Safety: this runs before any request is dispatched, so nothing
|
|
86
|
+
// else reads or writes the environment concurrently with these
|
|
87
|
+
// `set_var` calls.
|
|
88
|
+
if let Ok(oauth_secret_id) = std::env::var("GOOGLE_OAUTH_SECRET_ID") {
|
|
89
|
+
let secret_json = secret_fetcher
|
|
90
|
+
.fetch_secret_string(&oauth_secret_id)
|
|
91
|
+
.await
|
|
92
|
+
.map_err(|e| format!("fetching GOOGLE_OAUTH_SECRET_ID from Secrets Manager: {e:#}"))?;
|
|
93
|
+
let client_id = secrets::extract_field(&secret_json, "client_id")?;
|
|
94
|
+
let client_secret = secrets::extract_field(&secret_json, "client_secret")?;
|
|
95
|
+
unsafe {
|
|
96
|
+
std::env::set_var("GOOGLE_CLIENT_ID", client_id);
|
|
97
|
+
std::env::set_var("GOOGLE_CLIENT_SECRET", client_secret);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if let Ok(session_secret_arn) = std::env::var("SESSION_SECRET_ARN") {
|
|
101
|
+
let secret_json = secret_fetcher
|
|
102
|
+
.fetch_secret_string(&session_secret_arn)
|
|
103
|
+
.await
|
|
104
|
+
.map_err(|e| format!("fetching SESSION_SECRET_ARN from Secrets Manager: {e:#}"))?;
|
|
105
|
+
let session_secret = secrets::extract_field(&secret_json, "session_secret")?;
|
|
106
|
+
unsafe {
|
|
107
|
+
std::env::set_var("SESSION_SECRET", session_secret);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// RESEND_SECRET_ID: fetched the same way, but a failed fetch only
|
|
111
|
+
// logs -- mail is optional, and a missing secret must not stop the
|
|
112
|
+
// host from serving everything else (resend.rs answers 503 without
|
|
113
|
+
// a key).
|
|
114
|
+
if let Ok(resend_secret_id) = std::env::var("RESEND_SECRET_ID") {
|
|
115
|
+
let fetched = secret_fetcher.fetch_secret_string(&resend_secret_id).await.map_err(|e| format!("{e:#}"));
|
|
116
|
+
match fetched.and_then(|json| secrets::extract_field(&json, "api_key")) {
|
|
117
|
+
Ok(api_key) => unsafe { std::env::set_var("RESEND_API_KEY", api_key) },
|
|
118
|
+
Err(e) => eprintln!("RESEND_SECRET_ID could not be read, so email sending stays off: {e}"),
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
let wasm_path = PathBuf::from(
|
|
122
|
+
std::env::var("HECKS_WASM_PATH").unwrap_or_else(|_| "banking.wasm".to_string()),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Must run before the Postgres connection opens -- `SET search_path`
|
|
126
|
+
// below has to run before `journal::ensure_schema` or anything else
|
|
127
|
+
// touches the connection, or unqualified names resolve against
|
|
128
|
+
// Postgres's default search_path instead of this domain's.
|
|
129
|
+
// HECKS_SCHEMA is optional.
|
|
130
|
+
let domain = std::env::var("HECKS_DOMAIN").map_err(|_| "HECKS_DOMAIN is required")?;
|
|
131
|
+
let schema = std::env::var("HECKS_SCHEMA").ok().filter(|s| !s.is_empty());
|
|
132
|
+
// Checkout on AWS keeps the business's payment keys in a named Secrets
|
|
133
|
+
// Manager secret; refuse before touching the database when none is named.
|
|
134
|
+
payments::check_boot(web::checkout_enabled(std::env::var("HECKS_CHECKOUT_DOMAIN").ok().as_deref(), &domain))?;
|
|
135
|
+
|
|
136
|
+
// RDS refuses a plain NoTls connection by default and needs AWS's
|
|
137
|
+
// own RDS CA specifically, not a generic public bundle (see
|
|
138
|
+
// Cargo.toml).
|
|
139
|
+
let mut roots = rustls::RootCertStore::empty();
|
|
140
|
+
for cert in rustls_pemfile::certs(&mut include_bytes!("../rds-ca-bundle.pem").as_slice()) {
|
|
141
|
+
roots.add(cert.map_err(|e| format!("parsing rds-ca-bundle.pem: {e}"))?)?;
|
|
142
|
+
}
|
|
143
|
+
let tls_config = rustls::ClientConfig::builder()
|
|
144
|
+
.with_root_certificates(roots)
|
|
145
|
+
.with_no_client_auth();
|
|
146
|
+
let tls = tokio_postgres_rustls::MakeRustlsConnect::new(tls_config);
|
|
147
|
+
// Not tokio_postgres::connect(url, tls) -- that treats DATABASE_URL
|
|
148
|
+
// as a strict URI, and RDS-generated passwords can contain
|
|
149
|
+
// URI-reserved `?`/`#` that CloudFormation never percent-encodes.
|
|
150
|
+
let mut config = parse_database_url(&database_url)?;
|
|
151
|
+
// RDS requires TLS (rds-ca-bundle.pem); local Postgres over loopback
|
|
152
|
+
// can't be verified against the RDS CA, so localhost uses NoTls.
|
|
153
|
+
let local_postgres = database_url_is_local(&config);
|
|
154
|
+
if !local_postgres {
|
|
155
|
+
config.ssl_mode(tokio_postgres::config::SslMode::Require);
|
|
156
|
+
}
|
|
157
|
+
// NoTlsStream and RustlsStream can't unify in one branch, so the
|
|
158
|
+
// two paths are spawned separately.
|
|
159
|
+
let connect_phase = log::phase_with("db_connect", serde_json::json!({ "tls": !local_postgres }));
|
|
160
|
+
let client = if local_postgres {
|
|
161
|
+
let (client, connection) = config
|
|
162
|
+
.connect(tokio_postgres::NoTls)
|
|
163
|
+
.await
|
|
164
|
+
.map_err(|e| format!("connecting to Postgres via DATABASE_URL: {e:?}"))?;
|
|
165
|
+
tokio::spawn(async move {
|
|
166
|
+
if let Err(e) = connection.await {
|
|
167
|
+
eprintln!("postgres connection error: {e:#}");
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
client
|
|
171
|
+
} else {
|
|
172
|
+
let (client, connection) = config
|
|
173
|
+
.connect(tls)
|
|
174
|
+
.await
|
|
175
|
+
.map_err(|e| format!("connecting to Postgres via DATABASE_URL: {e:?}"))?;
|
|
176
|
+
tokio::spawn(async move {
|
|
177
|
+
if let Err(e) = connection.await {
|
|
178
|
+
eprintln!("postgres connection error: {e:#}");
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
client
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
connect_phase.end();
|
|
185
|
+
|
|
186
|
+
// Every unqualified table/view reference this binary issues
|
|
187
|
+
// resolves through search_path, so this is what makes a shared
|
|
188
|
+
// instance's per-domain schemas transparent to the rest of the
|
|
189
|
+
// binary.
|
|
190
|
+
//
|
|
191
|
+
// CREATE SCHEMA before SET search_path: setting search_path to a
|
|
192
|
+
// schema that doesn't exist yet succeeds in Postgres, so a missing
|
|
193
|
+
// schema only surfaces later as a confusing "no schema has been
|
|
194
|
+
// selected to create in" error from `ensure_schema`. Idempotent: an
|
|
195
|
+
// existing schema is the ordinary case on every boot after the first.
|
|
196
|
+
if let Some(schema) = &schema {
|
|
197
|
+
client
|
|
198
|
+
.batch_execute(&format!("CREATE SCHEMA IF NOT EXISTS {}", journal::quote_ident(schema)))
|
|
199
|
+
.await
|
|
200
|
+
.map_err(|e| format!("creating schema {schema:?}: {e:#}"))?;
|
|
201
|
+
client
|
|
202
|
+
.batch_execute(&format!("SET search_path TO {}", journal::quote_ident(schema)))
|
|
203
|
+
.await
|
|
204
|
+
.map_err(|e| format!("setting search_path to {schema:?}: {e:#}"))?;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let phase = log::phase("ensure_schema");
|
|
208
|
+
journal::ensure_schema(&client)
|
|
209
|
+
.await
|
|
210
|
+
.map_err(|e| format!("provisioning hecks_lambda_journal: {e:#}"))?;
|
|
211
|
+
phase.end();
|
|
212
|
+
|
|
213
|
+
let ir = ir::ir().ok_or("HECKS_IR_PATH is not set or unreadable — this binary needs its own domain's ir.json sidecar")?;
|
|
214
|
+
ir::refuse_unsupported_persistence_adapters(ir)?;
|
|
215
|
+
let my_hash = storage_shape::mint_hash(ir);
|
|
216
|
+
let my_label = storage_shape::mint_label(ir);
|
|
217
|
+
|
|
218
|
+
let mut aggregates: Vec<mint::Aggregate> = ir::lineage_capable_aggregates(ir)
|
|
219
|
+
.into_iter()
|
|
220
|
+
.map(|(qualified, storage_name)| {
|
|
221
|
+
let name = qualified.rsplit("::").next().unwrap_or(&qualified).to_string();
|
|
222
|
+
mint::Aggregate { name, storage_name }
|
|
223
|
+
})
|
|
224
|
+
.collect();
|
|
225
|
+
// A chapter that `provides "membership"` names who may sign in; that
|
|
226
|
+
// aggregate can be vendored, so it may be missing from this domain's
|
|
227
|
+
// lineage-capable list. Mint its head anyway for Google-auth to read.
|
|
228
|
+
if let Some(membership) = ir::membership_provider(ir) {
|
|
229
|
+
let name = membership.aggregate.rsplit("::").next().unwrap_or(&membership.aggregate).to_string();
|
|
230
|
+
let storage_name = journal::snake(&name);
|
|
231
|
+
if !aggregates.iter().any(|a| a.storage_name == storage_name) {
|
|
232
|
+
aggregates.push(mint::Aggregate { name, storage_name });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// The era-partitioned lineage subsystem is provisioned only when
|
|
237
|
+
// something needs it: a lineage-capable aggregate, or Google auth
|
|
238
|
+
// configured (ADR 0034 requires it for Member sessions either way).
|
|
239
|
+
let needs_lineage = !aggregates.is_empty() || std::env::var("GOOGLE_CLIENT_ID").is_ok();
|
|
240
|
+
|
|
241
|
+
let era: Option<i32> = if needs_lineage {
|
|
242
|
+
// Idempotent (gated by `provisioner?`); must run before the
|
|
243
|
+
// first `journal::held_eras` call, since `hecks_eras` doesn't
|
|
244
|
+
// exist yet on a truly fresh Postgres instance.
|
|
245
|
+
let phase = log::phase("ensure_base");
|
|
246
|
+
mint::ensure_base(&client, &domain).await.map_err(|e| format!("provisioning hecks_eras for {domain}: {e:#}"))?;
|
|
247
|
+
phase.end();
|
|
248
|
+
|
|
249
|
+
// Compares this binary's own shape hash (`storage_shape`, over
|
|
250
|
+
// `ir::ir()`) against held eras to decide the boot action: boot
|
|
251
|
+
// at a matching era, mint era 1 if the domain is new, or mint
|
|
252
|
+
// the next era if it has drifted -- refusing toward
|
|
253
|
+
// `hecks scaffold_translation` (no edge covers it) or
|
|
254
|
+
// `hecks audit_translation --approve` (needs review) otherwise.
|
|
255
|
+
let era_phase = log::phase("era_resolve");
|
|
256
|
+
let held = journal::held_eras(&client, &domain).await.map_err(|e| format!("checking hecks_eras for {domain}: {e:#}"))?;
|
|
257
|
+
|
|
258
|
+
// The decision itself is a pure, unit-tested function
|
|
259
|
+
// (`mint::decide_boot_action`); everything below just carries
|
|
260
|
+
// out whichever of its four outcomes came back.
|
|
261
|
+
let decision = mint::decide_boot_action(&held, &my_label);
|
|
262
|
+
era_phase.end_with(serde_json::json!({ "held_eras": held.len(), "decision": decision.name() }));
|
|
263
|
+
let ordinal: i32 = match decision {
|
|
264
|
+
// Adopting an existing era still provisions what this binary
|
|
265
|
+
// is about to write into, the same as minting does --
|
|
266
|
+
// skipping it would let a matched-era host write into
|
|
267
|
+
// head-snapshot tables that were never created.
|
|
268
|
+
mint::BootDecision::UseExisting { ordinal } => {
|
|
269
|
+
let phase = log::phase_with("adopt_head_snapshots", serde_json::json!({ "era": ordinal }));
|
|
270
|
+
mint::adopt_head_snapshots(&client, &domain, &aggregates, ordinal)
|
|
271
|
+
.await
|
|
272
|
+
.map_err(|e| format!("adopting era {ordinal} of {domain}: {e:#}"))?;
|
|
273
|
+
phase.end();
|
|
274
|
+
ordinal
|
|
275
|
+
}
|
|
276
|
+
mint::BootDecision::HoldFirst => {
|
|
277
|
+
let source_text =
|
|
278
|
+
ir.get("source_text").and_then(serde_json::Value::as_str).ok_or("ir.json is missing source_text — regenerate with hecks project_rust")?;
|
|
279
|
+
let phase = log::phase_with("hold_first", serde_json::json!({ "era": 1 }));
|
|
280
|
+
mint::hold_first(&client, &domain, source_text, ir, &aggregates, None).await.map_err(|e| format!("minting era 1 of {domain}: {e:#}"))?;
|
|
281
|
+
phase.end();
|
|
282
|
+
1
|
|
283
|
+
}
|
|
284
|
+
mint::BootDecision::LatestUnnamed { ordinal } => {
|
|
285
|
+
return Err(format!(
|
|
286
|
+
"cannot boot: {domain}'s latest held era (ordinal {ordinal}) has no name yet -- this crate can't \
|
|
287
|
+
name it itself (no bluebook parser, ADR 0012's own boundary) -- boot Ruby once against this \
|
|
288
|
+
database to name it (Runtime::EraCheck's own lazy-naming path), then redeploy",
|
|
289
|
+
)
|
|
290
|
+
.into());
|
|
291
|
+
}
|
|
292
|
+
mint::BootDecision::Mint { ordinal, from_ordinal, from_label } => {
|
|
293
|
+
let mut labels: Vec<String> = held.iter().map(|held_era| held_era.label.clone().unwrap_or_default()).collect();
|
|
294
|
+
labels.push(my_label.clone());
|
|
295
|
+
let edges = mint::parse_edges(ir, &domain);
|
|
296
|
+
let chain = mint::edge_chain(&edges, &labels).map_err(|e| {
|
|
297
|
+
format!(
|
|
298
|
+
"cannot boot: {domain} has drifted from era {from_ordinal} ({from_label}) to a shape \
|
|
299
|
+
({my_label}) no translation edge covers -- {e} -- run hecks scaffold_translation, review it \
|
|
300
|
+
with hecks audit_translation, then redeploy",
|
|
301
|
+
)
|
|
302
|
+
})?;
|
|
303
|
+
|
|
304
|
+
// Checks every edge in the chain with a compute/rekey
|
|
305
|
+
// rule, not just the newest -- a chain can carry more
|
|
306
|
+
// than one, matched against the raw ir.json edge
|
|
307
|
+
// (digest-relevant shape) by the same from/to pair.
|
|
308
|
+
let raw_edges = ir.get("translations").and_then(serde_json::Value::as_array).cloned().unwrap_or_default();
|
|
309
|
+
let committed_approvals = approval::committed(ir);
|
|
310
|
+
let approval_phase = log::phase_with("approval_check", serde_json::json!({ "edges": chain.len(), "committed": committed_approvals.len() }));
|
|
311
|
+
for edge in &chain {
|
|
312
|
+
let Some(raw_edge) = raw_edges.iter().find(|candidate| {
|
|
313
|
+
candidate.get("domain").and_then(serde_json::Value::as_str) == Some(domain.as_str())
|
|
314
|
+
&& candidate.get("from").and_then(serde_json::Value::as_str) == Some(edge.from.as_str())
|
|
315
|
+
&& candidate.get("to").and_then(serde_json::Value::as_str) == Some(edge.to.as_str())
|
|
316
|
+
}) else {
|
|
317
|
+
return Err(format!("cannot boot: {domain}'s edge {} -> {} vanished between parsing and approval-checking it", edge.from, edge.to).into());
|
|
318
|
+
};
|
|
319
|
+
approval::check(&client, &domain, raw_edge, ordinal, &committed_approvals).await.map_err(|e| format!("{e:#}"))?;
|
|
320
|
+
}
|
|
321
|
+
approval_phase.end();
|
|
322
|
+
|
|
323
|
+
// Runs before anything is minted, over the live compiled
|
|
324
|
+
// chain (plain SELECTs, never a persisted matview), so a
|
|
325
|
+
// refusal leaves no half-born era.
|
|
326
|
+
let watermarks: std::collections::HashMap<i32, Option<i64>> = held.iter().map(|held_era| (held_era.ordinal, held_era.watermark)).collect();
|
|
327
|
+
let audit_phase = log::phase_with("audit_before_mint", serde_json::json!({ "era": ordinal, "edges": chain.len() }));
|
|
328
|
+
mint::audit_before_mint(&client, &domain, ir, &aggregates, ordinal, &chain, &raw_edges, &watermarks)
|
|
329
|
+
.await
|
|
330
|
+
.map_err(|e| format!("{e:#}"))?;
|
|
331
|
+
audit_phase.end();
|
|
332
|
+
|
|
333
|
+
let held_text = ir.get("source_text").and_then(serde_json::Value::as_str).ok_or("ir.json is missing source_text — regenerate with hecks project_rust")?;
|
|
334
|
+
let mint_phase = log::phase_with("mint_era", serde_json::json!({ "era": ordinal, "edges": chain.len() }));
|
|
335
|
+
mint::mint_era(&client, &domain, ordinal, &my_hash, &my_label, held_text, &aggregates, &chain, None, &mint::lifecycle_defaults(ir))
|
|
336
|
+
.await
|
|
337
|
+
.map_err(|e| format!("minting era {ordinal} of {domain}: {e:#}"))?;
|
|
338
|
+
mint_phase.end();
|
|
339
|
+
ordinal
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
Some(ordinal)
|
|
343
|
+
} else {
|
|
344
|
+
None
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// The mirrored set is the IR's own capable list, by qualified name
|
|
348
|
+
// -- never "everything this kernel can mutate". A domain that binds
|
|
349
|
+
// everything to plain `Postgres` mirrors nothing.
|
|
350
|
+
let mirrored = ir::mirrored_aggregates(ir);
|
|
351
|
+
let lineage_config = Arc::new(journal::LineageConfig { domain, era, mirrored: Some(mirrored) });
|
|
352
|
+
|
|
353
|
+
// Mutex, not a bare Arc<Client> -- dispatch::handle needs
|
|
354
|
+
// Client::transaction (&mut Client) to hold the advisory lock
|
|
355
|
+
// across the rehydrate-then-append sequence (see dispatch.rs).
|
|
356
|
+
let client = Arc::new(Mutex::new(client));
|
|
357
|
+
let wasm_path = Arc::new(wasm_path);
|
|
358
|
+
// One invoker for this process's lifetime -- credentials/region
|
|
359
|
+
// resolve once at boot, not per invocation. See lambda_client.rs:
|
|
360
|
+
// the compiled `.wasm` module can't dispatch this itself (no
|
|
361
|
+
// network inside that sandbox).
|
|
362
|
+
let invoker = Arc::new(lambda_client::AwsLambdaInvoker::from_env().await);
|
|
363
|
+
|
|
364
|
+
// `role` is optional, mirroring `args`: an absent key means no
|
|
365
|
+
// caller asserted a role, and `dispatch::handle`'s `check_role`
|
|
366
|
+
// stays on its unchecked path. Read by `server::dispatch_body` below.
|
|
367
|
+
if serve_mode {
|
|
368
|
+
log::info(
|
|
369
|
+
"boot",
|
|
370
|
+
serde_json::json!({ "mode": "serve", "domain": &lineage_config.domain, "era": &my_label, "boot_ms": boot_started.elapsed().as_millis() as u64 }),
|
|
371
|
+
);
|
|
372
|
+
let version = server::version_body(&my_label, &my_hash, std::env::var("HECKS_BUILD").ok().as_deref());
|
|
373
|
+
let limits = Arc::new(rate_limit::RateLimits::from_env());
|
|
374
|
+
let state = server::ServerState { client, wasm_path, lineage_config, invoker, limits };
|
|
375
|
+
return server::serve(state, version).await;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
lambda_runtime::run(service_fn(move |event: LambdaEvent<serde_json::Value>| {
|
|
379
|
+
let client = Arc::clone(&client);
|
|
380
|
+
let wasm_path = Arc::clone(&wasm_path);
|
|
381
|
+
let lineage_config = Arc::clone(&lineage_config);
|
|
382
|
+
let invoker = Arc::clone(&invoker);
|
|
383
|
+
async move {
|
|
384
|
+
let (body, _context) = event.into_parts();
|
|
385
|
+
server::dispatch_body(body, &client, &wasm_path, &lineage_config, invoker.as_ref()).await
|
|
386
|
+
}
|
|
387
|
+
}))
|
|
388
|
+
.await
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Parses `postgres://user:password@host:port/db` without treating it
|
|
392
|
+
// as a URI -- an RDS/Aurora auto-generated password can contain
|
|
393
|
+
// `?`/`#`, which a strict URI parser would treat as delimiters.
|
|
394
|
+
fn parse_database_url(url: &str) -> Result<tokio_postgres::Config, String> {
|
|
395
|
+
let rest = url
|
|
396
|
+
.strip_prefix("postgres://")
|
|
397
|
+
.or_else(|| url.strip_prefix("postgresql://"))
|
|
398
|
+
.ok_or_else(|| format!("DATABASE_URL doesn't start with postgres:// or postgresql://"))?;
|
|
399
|
+
|
|
400
|
+
let (authority, db_and_query) = rest
|
|
401
|
+
.split_once('/')
|
|
402
|
+
.ok_or_else(|| "DATABASE_URL has no '/' separating host from database name".to_string())?;
|
|
403
|
+
let dbname = db_and_query.split(['?', '#']).next().unwrap_or(db_and_query);
|
|
404
|
+
|
|
405
|
+
let (credentials, host_and_port) = match authority.rsplit_once('@') {
|
|
406
|
+
Some((credentials, host_and_port)) => (Some(credentials), host_and_port),
|
|
407
|
+
None => (None, authority),
|
|
408
|
+
};
|
|
409
|
+
let (host, port) = match host_and_port.rsplit_once(':') {
|
|
410
|
+
Some((host, port)) => {
|
|
411
|
+
let port: u16 = port
|
|
412
|
+
.parse()
|
|
413
|
+
.map_err(|e| format!("DATABASE_URL's port {port:?} isn't a valid number: {e}"))?;
|
|
414
|
+
(host, port)
|
|
415
|
+
}
|
|
416
|
+
None => (host_and_port, 5432),
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
let mut config = tokio_postgres::Config::new();
|
|
420
|
+
config.host(host).port(port).dbname(dbname);
|
|
421
|
+
if let Some(credentials) = credentials {
|
|
422
|
+
match credentials.split_once(':') {
|
|
423
|
+
Some((user, password)) => {
|
|
424
|
+
config.user(user).password(password);
|
|
425
|
+
}
|
|
426
|
+
None => {
|
|
427
|
+
config.user(credentials);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
Ok(config)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
fn database_url_is_local(config: &tokio_postgres::Config) -> bool {
|
|
435
|
+
use tokio_postgres::config::Host;
|
|
436
|
+
config.get_hosts().iter().any(|host| match host {
|
|
437
|
+
Host::Tcp(name) => name == "localhost" || name == "127.0.0.1" || name == "::1",
|
|
438
|
+
_ => false,
|
|
439
|
+
})
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
#[cfg(test)]
|
|
443
|
+
mod tests {
|
|
444
|
+
use super::*;
|
|
445
|
+
|
|
446
|
+
#[test]
|
|
447
|
+
fn parses_a_password_with_uri_reserved_characters() {
|
|
448
|
+
let config = parse_database_url(
|
|
449
|
+
"postgres://postgres:T3st$Pa?#ss*>Word@db.example.com:5432/pizzas",
|
|
450
|
+
)
|
|
451
|
+
.expect("should parse despite ?/# in the password");
|
|
452
|
+
assert_eq!(config.get_hosts().len(), 1);
|
|
453
|
+
assert_eq!(config.get_ports(), &[5432]);
|
|
454
|
+
assert_eq!(config.get_user(), Some("postgres"));
|
|
455
|
+
assert_eq!(config.get_dbname(), Some("pizzas"));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
#[test]
|
|
459
|
+
fn parses_a_plain_password_too() {
|
|
460
|
+
let config = parse_database_url("postgres://postgres:plainpassword@localhost:5432/pizzas")
|
|
461
|
+
.expect("should parse a password with no special characters");
|
|
462
|
+
assert_eq!(config.get_user(), Some("postgres"));
|
|
463
|
+
assert_eq!(config.get_dbname(), Some("pizzas"));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
#[test]
|
|
467
|
+
fn parses_a_local_url_with_no_user_or_port() {
|
|
468
|
+
let config = parse_database_url("postgres://localhost/app_development")
|
|
469
|
+
.expect("should parse a peer-auth local URL");
|
|
470
|
+
assert_eq!(config.get_dbname(), Some("app_development"));
|
|
471
|
+
assert_eq!(config.get_ports(), &[5432]);
|
|
472
|
+
assert!(database_url_is_local(&config));
|
|
473
|
+
}
|
|
474
|
+
}
|