hecks 3.4.2 → 3.6.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/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +1 -1
- data/lib/hecks/adapters/driven/claude_code.rb +11 -7
- data/lib/hecks/adapters/driven/d1/connection.rb +90 -0
- data/lib/hecks/adapters/driven/d1.rb +48 -314
- data/lib/hecks/adapters/driven/folder.rb +20 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +18 -4
- data/lib/hecks/adapters/driven/governance_authorization.rb +2 -2
- data/lib/hecks/adapters/driven/heki/journal.rb +11 -10
- data/lib/hecks/adapters/driven/heki/saga_store.rb +4 -6
- data/lib/hecks/adapters/driven/heki/sagas.rb +55 -0
- data/lib/hecks/adapters/driven/heki.rb +21 -66
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +37 -12
- data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +1 -8
- data/lib/hecks/adapters/driven/memory/outbox_rows.rb +76 -0
- data/lib/hecks/adapters/driven/memory/shared_elements/values.rb +117 -0
- data/lib/hecks/adapters/driven/memory/shared_elements.rb +10 -97
- data/lib/hecks/adapters/driven/memory.rb +3 -65
- data/lib/hecks/adapters/driven/postgres/codec.rb +29 -26
- data/lib/hecks/adapters/driven/postgres/ddl.rb +50 -0
- data/lib/hecks/adapters/driven/postgres/dialect.rb +93 -0
- data/lib/hecks/adapters/driven/postgres/events.rb +57 -0
- data/lib/hecks/adapters/driven/postgres/journal.rb +127 -0
- data/lib/hecks/adapters/driven/postgres/outbox.rb +39 -31
- data/lib/hecks/adapters/driven/postgres/repository.rb +190 -0
- data/lib/hecks/adapters/driven/postgres/sagas.rb +72 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +13 -41
- data/lib/hecks/adapters/driven/postgres.rb +61 -528
- data/lib/hecks/adapters/driven/prism.rb +1 -1
- data/lib/hecks/adapters/driven/sql_query_builder.rb +45 -53
- data/lib/hecks/adapters/driven/sql_value_members.rb +57 -0
- data/lib/hecks/adapters/driven/sqlite/codec.rb +32 -29
- data/lib/hecks/adapters/driven/sqlite/ddl.rb +68 -0
- data/lib/hecks/adapters/driven/sqlite/dialect.rb +55 -0
- data/lib/hecks/adapters/driven/sqlite/events.rb +61 -0
- data/lib/hecks/adapters/driven/sqlite/journal.rb +125 -0
- data/lib/hecks/adapters/driven/sqlite/outbox.rb +98 -0
- data/lib/hecks/adapters/driven/sqlite/projection.rb +82 -63
- data/lib/hecks/adapters/driven/sqlite/repository.rb +146 -0
- data/lib/hecks/adapters/driven/sqlite/sagas.rb +69 -0
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +8 -55
- data/lib/hecks/adapters/driven/sqlite.rb +36 -513
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +13 -7
- data/lib/hecks/adapters/driving/github_webhook.rb +11 -8
- data/lib/hecks/behaviors/dsl.rb +17 -12
- data/lib/hecks/behaviors/expectations/boot.rb +81 -0
- data/lib/hecks/behaviors/expectations/checks.rb +178 -0
- data/lib/hecks/behaviors/expectations/dispatching.rb +93 -0
- data/lib/hecks/behaviors/expectations/qualify.rb +81 -0
- data/lib/hecks/behaviors/expectations.rb +39 -355
- data/lib/hecks/behaviors/rspec.rb +12 -12
- data/lib/hecks/behaviors/runner.rb +20 -11
- data/lib/hecks/bench/cli.rb +16 -13
- data/lib/hecks/bench/environment.rb +1 -1
- data/lib/hecks/bench/report.rb +11 -7
- data/lib/hecks/bench/ruby_runner.rb +13 -8
- data/lib/hecks/bench/rust_runner.rb +19 -11
- data/lib/hecks/bench/suite.rb +48 -20
- data/lib/hecks/bench/workload.rb +42 -31
- data/lib/hecks/bluebook/aggregate.rb +15 -21
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +18 -21
- data/lib/hecks/bluebook/assembly/build.rb +12 -7
- data/lib/hecks/bluebook/assembly/contracts/read_models.rb +70 -0
- data/lib/hecks/bluebook/assembly/contracts/rules.rb +84 -0
- data/lib/hecks/bluebook/assembly/contracts/sagas.rb +109 -0
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +91 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +9 -319
- data/lib/hecks/bluebook/assembly/marks.rb +9 -8
- data/lib/hecks/bluebook/attribute.rb +13 -10
- data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -3
- data/lib/hecks/bluebook/behaviour/read_model.rb +26 -13
- data/lib/hecks/bluebook/chapter.rb +22 -14
- data/lib/hecks/bluebook/command.rb +14 -18
- data/lib/hecks/bluebook/domain_port.rb +11 -8
- data/lib/hecks/bluebook/dsl/aggregate_builder/correction_sealing.rb +78 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/draining.rb +74 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/query_field_sealing.rb +105 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/query_sealing.rb +121 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/relationships.rb +128 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/rules.rb +84 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +61 -249
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +51 -285
- data/lib/hecks/bluebook/dsl/attribute_collector/identity_resolution.rb +113 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +58 -117
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +24 -19
- data/lib/hecks/bluebook/dsl/bluebook_builder/pending_givens.rb +109 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/correlation_keys.rb +105 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/event_shapes.rb +126 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/hop_tails.rb +79 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +76 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +65 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/query_hops.rb +123 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/references.rb +112 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +126 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +34 -595
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +18 -107
- data/lib/hecks/bluebook/dsl/command_builder/implicit_attributes.rb +132 -0
- data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +190 -0
- data/lib/hecks/bluebook/dsl/command_builder/owner.rb +22 -0
- data/lib/hecks/bluebook/dsl/command_builder/rules.rb +57 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +60 -360
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +19 -13
- data/lib/hecks/bluebook/dsl/entity_builder/rules.rb +96 -0
- data/lib/hecks/bluebook/dsl/entity_builder/scope.rb +25 -0
- data/lib/hecks/bluebook/dsl/entity_builder/sealing.rb +94 -0
- data/lib/hecks/bluebook/dsl/entity_builder.rb +35 -169
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +53 -24
- data/lib/hecks/bluebook/dsl/hecksagon_builder/chapter_scope.rb +133 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +3 -97
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +73 -73
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +7 -3
- data/lib/hecks/bluebook/dsl/need_word.rb +1 -1
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +34 -29
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +57 -41
- data/lib/hecks/bluebook/dsl/query_builder.rb +12 -15
- data/lib/hecks/bluebook/dsl/read_model_builder/sealing.rb +116 -0
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +25 -102
- data/lib/hecks/bluebook/dsl/rule_reference.rb +26 -12
- data/lib/hecks/bluebook/dsl/translation_builder.rb +24 -14
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +29 -19
- data/lib/hecks/bluebook/dsl/word_gate.rb +34 -27
- data/lib/hecks/bluebook/entity.rb +13 -17
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +93 -0
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb-e +88 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +49 -76
- data/lib/hecks/bluebook/expression/ast_reader.rb +95 -28
- data/lib/hecks/bluebook/expression/canonical_form.rb +6 -26
- data/lib/hecks/bluebook/expression/evaluator/parser.rb +62 -0
- data/lib/hecks/bluebook/expression/evaluator/scanning.rb +122 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +11 -111
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +28 -44
- data/lib/hecks/bluebook/expression/resolver/lookups.rb +77 -0
- data/lib/hecks/bluebook/expression/resolver/operations.rb +78 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +110 -0
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +139 -0
- data/lib/hecks/bluebook/expression/resolver/values.rb +115 -0
- data/lib/hecks/bluebook/expression/resolver.rb +16 -414
- data/lib/hecks/bluebook/{hexagon.rb → hecksagon.rb} +22 -14
- data/lib/hecks/bluebook/keyword_fields.rb +42 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +7 -6
- data/lib/hecks/bluebook/meta_validator/judge/cells.rb +99 -0
- data/lib/hecks/bluebook/meta_validator/judge/dispatching.rb +111 -0
- data/lib/hecks/bluebook/meta_validator/judge/identity.rb +108 -0
- data/lib/hecks/bluebook/meta_validator/judge/walking.rb +141 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +31 -376
- data/lib/hecks/bluebook/meta_validator/modes.rb +137 -0
- data/lib/hecks/bluebook/meta_validator/plan/category.rb +44 -0
- data/lib/hecks/bluebook/meta_validator/plan/verbs.rb +73 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +38 -122
- data/lib/hecks/bluebook/meta_validator/readings/fields.rb +100 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +10 -95
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +89 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction/reading.rb +96 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +25 -151
- data/lib/hecks/bluebook/meta_validator/shapes/helpers.rb +83 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +8 -75
- data/lib/hecks/bluebook/meta_validator/syntax_boot/disk_cache.rb +77 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +31 -95
- data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +74 -0
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +15 -65
- data/lib/hecks/bluebook/meta_validator/verdict_cache/tagging.rb +63 -0
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +22 -43
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +8 -4
- data/lib/hecks/bluebook/meta_validator.rb +59 -175
- data/lib/hecks/bluebook/model_check/lifecycle_checks.rb +122 -0
- data/lib/hecks/bluebook/model_check/policy_checks.rb +129 -0
- data/lib/hecks/bluebook/model_check/saga_checks.rb +134 -0
- data/lib/hecks/bluebook/model_check/verbs.rb +69 -0
- data/lib/hecks/bluebook/model_check.rb +73 -413
- data/lib/hecks/bluebook/pattern_subset/refusals.rb +54 -0
- data/lib/hecks/bluebook/pattern_subset/scan.rb +58 -0
- data/lib/hecks/bluebook/pattern_subset.rb +93 -134
- data/lib/hecks/bluebook/process_manager.rb +10 -9
- data/lib/hecks/bluebook/project_discovery.rb +10 -7
- data/lib/hecks/bluebook/project_register.rb +42 -41
- data/lib/hecks/bluebook/query.rb +19 -12
- data/lib/hecks/bluebook/read_model.rb +41 -39
- data/lib/hecks/bluebook/smoke_test.rb +51 -36
- data/lib/hecks/bluebook/synthesizer.rb +13 -5
- data/lib/hecks/bluebook/translation.rb +12 -12
- data/lib/hecks/bluebook.rb +1 -1
- data/lib/hecks/chapters.rb +32 -16
- data/lib/hecks/cli/behaviors.rb +22 -15
- data/lib/hecks/cli/check_era.rb +2 -2
- data/lib/hecks/cli/command_table.rb +72 -0
- data/lib/hecks/cli/console.rb +10 -7
- data/lib/hecks/cli/deploy_template_diff.rb +22 -9
- data/lib/hecks/cli/document.rb +14 -10
- data/lib/hecks/cli/domain_stub.rb +1 -1
- data/lib/hecks/cli/domain_writer.rb +1 -1
- data/lib/hecks/cli/expression_projection.rb +8 -6
- data/lib/hecks/cli/follow.rb +10 -4
- data/lib/hecks/cli/interview_aggregate.rb +132 -0
- data/lib/hecks/cli/interview_draft.rb +16 -200
- data/lib/hecks/cli/interview_record.rb +130 -0
- data/lib/hecks/cli/interview_run.rb +19 -8
- data/lib/hecks/cli/interview_session/agent_state.rb +70 -0
- data/lib/hecks/cli/interview_session/catalog.rb +76 -0
- data/lib/hecks/cli/interview_session/manual_findings.rb +48 -0
- data/lib/hecks/cli/interview_session/proposals.rb +90 -0
- data/lib/hecks/cli/interview_session.rb +8 -205
- data/lib/hecks/cli/interview_shape.rb +13 -6
- data/lib/hecks/cli/ir.rb +8 -11
- data/lib/hecks/cli/launcher_words.rb +168 -0
- data/lib/hecks/cli/model_check/examination.rb +93 -0
- data/lib/hecks/cli/model_check/sources.rb +57 -0
- data/lib/hecks/cli/model_check.rb +28 -134
- data/lib/hecks/cli/project_cli/launcher_source.rb +177 -0
- data/lib/hecks/cli/project_cli/templates.rb +106 -0
- data/lib/hecks/cli/project_cli.rb +57 -236
- data/lib/hecks/cli/project_diagrams.rb +26 -4
- data/lib/hecks/cli/project_glossary.rb +19 -9
- data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +29 -13
- data/lib/hecks/cli/release.rb +29 -22
- data/lib/hecks/cli/release_gem.rb +12 -6
- data/lib/hecks/cli/rspec_io_parallel_files.rb +87 -30
- data/lib/hecks/cli/rspec_shard_files.rb +24 -9
- data/lib/hecks/cli/run.rb +25 -44
- data/lib/hecks/cli/run_expectations.rb +63 -0
- data/lib/hecks/cli/seed_semantics_corpus.rb +37 -17
- data/lib/hecks/cli/smoke_http/checks.rb +66 -0
- data/lib/hecks/cli/smoke_http/settings.rb +63 -0
- data/lib/hecks/cli/smoke_http/transport.rb +45 -0
- data/lib/hecks/cli/smoke_http.rb +8 -142
- data/lib/hecks/cli/smoke_test.rb +18 -4
- data/lib/hecks/cli/stores.rb +7 -4
- data/lib/hecks/cli/stress_concurrency_specs/reporting.rb +44 -0
- data/lib/hecks/cli/stress_concurrency_specs/usage.rb +28 -0
- data/lib/hecks/cli/stress_concurrency_specs.rb +82 -80
- data/lib/hecks/cli.rb +32 -208
- data/lib/hecks/codemod/runner.rb +92 -0
- data/lib/hecks/codemod/sweep.rb +94 -0
- data/lib/hecks/codemod.rb +22 -165
- data/lib/hecks/corpus/routes.rb +34 -0
- data/lib/hecks/corpus/rust.rb +193 -0
- data/lib/hecks/corpus/sources.rb +46 -0
- data/lib/hecks/corpus.rb +28 -265
- data/lib/hecks/deploy/bluebook/deploy.bluebook +1510 -3
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +57 -0
- data/lib/hecks/deploy/oidc.json +225 -0
- data/lib/hecks/doc/reference/coverage.rb +70 -0
- data/lib/hecks/doc/reference/harvester.rb +55 -0
- data/lib/hecks/doc/reference/readme.rb +89 -0
- data/lib/hecks/doc/reference/rendering.rb +149 -0
- data/lib/hecks/doc/reference.rb +14 -273
- data/lib/hecks/doors/cli_door/nesting.rb +34 -0
- data/lib/hecks/doors/cli_door/shorthand.rb +85 -0
- data/lib/hecks/doors/cli_door.rb +23 -95
- data/lib/hecks/doors/cli_runner/answers.rb +138 -0
- data/lib/hecks/doors/cli_runner/resolution.rb +146 -0
- data/lib/hecks/doors/cli_runner/settling.rb +115 -0
- data/lib/hecks/doors/cli_runner/streaming.rb +90 -0
- data/lib/hecks/doors/cli_runner/suggestions.rb +43 -0
- data/lib/hecks/doors/cli_runner.rb +12 -336
- data/lib/hecks/doors/command_request.rb +42 -31
- data/lib/hecks/doors/handle.rb +32 -30
- data/lib/hecks/doors/launcher_options.rb +62 -7
- data/lib/hecks/doors/mcp_door/properties.rb +47 -0
- data/lib/hecks/doors/mcp_door/tool_calls.rb +113 -0
- data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +51 -0
- data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +69 -0
- data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +97 -0
- data/lib/hecks/doors/mcp_door/tool_definitions.rb +15 -0
- data/lib/hecks/doors/mcp_door.rb +55 -301
- data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +92 -0
- data/lib/hecks/doors/mcp_door_scope/env_problems.rb +55 -0
- data/lib/hecks/doors/mcp_door_scope/presenter.rb +78 -0
- data/lib/hecks/doors/mcp_door_scope.rb +16 -172
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +1 -1
- data/lib/hecks/doors/ruby_door/chapter.rb +33 -16
- data/lib/hecks/doors/ruby_door.rb +14 -11
- data/lib/hecks/doors/usage_cache.rb +14 -8
- data/lib/hecks/doors.rb +10 -4
- data/lib/hecks/embryonaut_bluebook/manifest.rb +11 -3
- data/lib/hecks/embryonaut_bluebook/registry/changelog.rb +26 -0
- data/lib/hecks/embryonaut_bluebook/registry/versions.rb +27 -0
- data/lib/hecks/embryonaut_bluebook/registry.rb +27 -37
- data/lib/hecks/embryonaut_bluebook/vendor/acceptance.rb +37 -0
- data/lib/hecks/embryonaut_bluebook/vendor.rb +22 -36
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +9 -6
- data/lib/hecks/embryonaut_bluebook.rb +16 -11
- data/lib/hecks/engine_agreement.rb +24 -17
- data/lib/hecks/forms/app/commands.rb +98 -0
- data/lib/hecks/forms/app/queries.rb +62 -0
- data/lib/hecks/forms/app/records.rb +67 -0
- data/lib/hecks/forms/app/responses.rb +46 -0
- data/lib/hecks/forms/app.rb +33 -221
- data/lib/hecks/forms/banking_presentation.rb +5 -0
- data/lib/hecks/forms/command_form_renderer.rb +55 -14
- data/lib/hecks/forms/field_controls.rb +95 -0
- data/lib/hecks/forms/field_renderer.rb +34 -88
- data/lib/hecks/forms/field_shape.rb +9 -34
- data/lib/hecks/forms/html.rb +1 -1
- data/lib/hecks/forms/index_renderer.rb +14 -9
- data/lib/hecks/forms/page.rb +2 -106
- data/lib/hecks/forms/page_assets.rb +88 -0
- data/lib/hecks/forms/page_script.rb +28 -0
- data/lib/hecks/forms/params.rb +17 -8
- data/lib/hecks/forms/query_form_renderer.rb +11 -7
- data/lib/hecks/forms/record_renderer.rb +28 -19
- data/lib/hecks/forms/reference_options.rb +1 -1
- data/lib/hecks/forms/text_shape.rb +40 -0
- data/lib/hecks/fqn.rb +64 -32
- data/lib/hecks/framework.rb +10 -3
- data/lib/hecks/freezer.rb +10 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions/fragments.rb +61 -0
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions/productions.rb +217 -0
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +5 -194
- data/lib/hecks/fuzzing/combination_miner.rb +23 -12
- data/lib/hecks/fuzzing/concurrent_dispatch/racing.rb +64 -0
- data/lib/hecks/fuzzing/concurrent_dispatch/world_copy.rb +66 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +31 -107
- data/lib/hecks/fuzzing/coverage_campaign.rb +7 -7
- data/lib/hecks/fuzzing/differential/wire_comparison.rb +74 -0
- data/lib/hecks/fuzzing/differential.rb +59 -84
- data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +70 -0
- data/lib/hecks/fuzzing/domain_generator/builder/attribute_shapes.rb +105 -0
- data/lib/hecks/fuzzing/domain_generator/builder/shapes.rb +97 -0
- data/lib/hecks/fuzzing/domain_generator/builder/wiring.rb +128 -0
- data/lib/hecks/fuzzing/domain_generator/builder.rb +105 -0
- data/lib/hecks/fuzzing/domain_generator/pruning.rb +90 -0
- data/lib/hecks/fuzzing/domain_generator/removals.rb +62 -0
- data/lib/hecks/fuzzing/domain_generator/rendering.rb +94 -0
- data/lib/hecks/fuzzing/domain_generator/tokens.rb +38 -0
- data/lib/hecks/fuzzing/domain_generator.rb +7 -555
- data/lib/hecks/fuzzing/era_boundary.rb +34 -24
- data/lib/hecks/fuzzing/form_census.rb +18 -8
- data/lib/hecks/fuzzing/generated_domain_check.rb +23 -11
- data/lib/hecks/fuzzing/isolated_boot/copying.rb +62 -0
- data/lib/hecks/fuzzing/isolated_boot/postgres_scratch.rb +133 -0
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +102 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +25 -252
- data/lib/hecks/fuzzing/persistence_parity.rb +5 -5
- data/lib/hecks/fuzzing/properties/aggregation_recompute.rb +121 -0
- data/lib/hecks/fuzzing/properties/catalog.rb +138 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +23 -20
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +64 -185
- data/lib/hecks/fuzzing/properties/group_by_recompute.rb +69 -0
- data/lib/hecks/fuzzing/properties/guards.rb +76 -108
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +66 -227
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +46 -35
- data/lib/hecks/fuzzing/properties/mutation_recompute.rb +162 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +41 -24
- data/lib/hecks/fuzzing/properties/piece_invariants.rb +61 -0
- data/lib/hecks/fuzzing/properties/policy_wiring.rb +140 -0
- data/lib/hecks/fuzzing/properties/query_rows.rb +67 -0
- data/lib/hecks/fuzzing/properties/querying.rb +69 -90
- data/lib/hecks/fuzzing/properties/references.rb +100 -0
- data/lib/hecks/fuzzing/properties/tenant_scope.rb +84 -0
- data/lib/hecks/fuzzing/properties.rb +6 -119
- data/lib/hecks/fuzzing/qa_settings.rb +23 -17
- data/lib/hecks/fuzzing/replay/fan_out.rb +78 -0
- data/lib/hecks/fuzzing/replay/filters.rb +72 -0
- data/lib/hecks/fuzzing/replay/guard_check.rb +88 -0
- data/lib/hecks/fuzzing/replay/mutation_trace.rb +117 -0
- data/lib/hecks/fuzzing/replay/observation.rb +60 -0
- data/lib/hecks/fuzzing/replay/read_only_steps.rb +84 -0
- data/lib/hecks/fuzzing/replay/session.rb +151 -0
- data/lib/hecks/fuzzing/replay.rb +35 -442
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +3 -4
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +133 -0
- data/lib/hecks/fuzzing/self_consistency/sagas.rb +93 -0
- data/lib/hecks/fuzzing/self_consistency/value_objects.rb +87 -0
- data/lib/hecks/fuzzing/self_consistency.rb +8 -213
- data/lib/hecks/fuzzing/sequence_generator/adversary/argument_mutations.rb +116 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary/caller_draw.rb +68 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary/identity_mutations.rb +62 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary/precedence_mutations.rb +126 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +23 -283
- data/lib/hecks/fuzzing/sequence_generator/argument_drawing.rb +78 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +69 -53
- data/lib/hecks/fuzzing/sequence_generator/identity_shaping.rb +85 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +20 -16
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +12 -6
- data/lib/hecks/fuzzing/sequence_generator/prefixing.rb +45 -0
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +19 -8
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +43 -144
- data/lib/hecks/fuzzing/sequence_generator.rb +65 -63
- data/lib/hecks/fuzzing/shrinker.rb +33 -29
- data/lib/hecks/fuzzing/target_capabilities.rb +10 -4
- data/lib/hecks/fuzzing/value_generator.rb +27 -17
- data/lib/hecks/gate/stages.yml +10 -0
- data/lib/hecks/grammar/evolve/arguments.rb +90 -0
- data/lib/hecks/grammar/evolve/keywords.rb +81 -0
- data/lib/hecks/grammar/evolve/seeds.rb +172 -0
- data/lib/hecks/grammar/evolve.rb +22 -263
- data/lib/hecks/grammar/operators.rb +111 -0
- data/lib/hecks/grammar.rb +53 -126
- data/lib/hecks/hecks/adapters/bluebook_report.rb +91 -0
- data/lib/hecks/hecks/adapters/codebase/codemods.rb +1 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +23 -9
- data/lib/hecks/hecks/adapters/codebase/language/edits.rb +75 -0
- data/lib/hecks/hecks/adapters/codebase/language.rb +30 -72
- data/lib/hecks/hecks/adapters/codebase/promotion/candidates.rb +54 -0
- data/lib/hecks/hecks/adapters/codebase/promotion/gate.rb +50 -0
- data/lib/hecks/hecks/adapters/codebase/promotion/move.rb +70 -0
- data/lib/hecks/hecks/adapters/codebase/promotion/standing.rb +88 -0
- data/lib/hecks/hecks/adapters/codebase/promotion/watch.rb +83 -0
- data/lib/hecks/hecks/adapters/codebase/promotion.rb +115 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +31 -12
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +43 -6
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +16 -5
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +9 -1
- data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +1 -1
- data/lib/hecks/hecks/adapters/codebase/style.rb +1 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +11 -5
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +27 -8
- data/lib/hecks/hecks/adapters/console_capture.rb +21 -17
- data/lib/hecks/hecks/adapters/cost_explorer.rb +11 -6
- data/lib/hecks/hecks/adapters/deploy_plans.rb +49 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain/answers.rb +141 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain/scripts.rb +95 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +74 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +122 -59
- data/lib/hecks/hecks/adapters/finding_github.rb +2 -2
- data/lib/hecks/hecks/adapters/first_admin.rb +22 -8
- data/lib/hecks/hecks/adapters/git.rb +18 -5
- data/lib/hecks/hecks/adapters/git_refs.rb +144 -0
- data/lib/hecks/hecks/adapters/github_rulesets.rb +126 -0
- data/lib/hecks/hecks/adapters/host_http.rb +16 -6
- data/lib/hecks/hecks/adapters/in_process_boot.rb +9 -5
- data/lib/hecks/hecks/adapters/in_process_operations/tailing.rb +61 -0
- data/lib/hecks/hecks/adapters/in_process_operations.rb +39 -37
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +21 -15
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +29 -12
- data/lib/hecks/hecks/adapters/journal_store/readings/attestation.rb +52 -0
- data/lib/hecks/hecks/adapters/journal_store/readings/scaffolding.rb +52 -0
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +5 -62
- data/lib/hecks/hecks/adapters/local_files.rb +27 -10
- data/lib/hecks/hecks/adapters/pg_admin/ownership.rb +115 -0
- data/lib/hecks/hecks/adapters/pg_admin.rb +14 -87
- data/lib/hecks/hecks/adapters/process_pool.rb +54 -22
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +12 -4
- data/lib/hecks/hecks/adapters/site_toolchain.rb +19 -10
- data/lib/hecks/hecks/adapters/terminal.rb +13 -9
- data/lib/hecks/hecks/codebase.bluebook +322 -5
- data/lib/hecks/hecks/custodian.bluebook +12 -2
- data/lib/hecks/hecks/hecks.hecksagon +73 -0
- data/lib/hecks/hecks/hecks.world +15 -3
- data/lib/hecks/kernel_capabilities/templates.rb +92 -0
- data/lib/hecks/kernel_capabilities.rb +18 -85
- data/lib/hecks/language/bluebook/vocabulary.bluebook +43 -1
- data/lib/hecks/literal/item_splitter.rb +50 -0
- data/lib/hecks/literal.rb +29 -38
- data/lib/hecks/mcp_stdio_guard.rb +8 -8
- data/lib/hecks/naming.rb +9 -3
- data/lib/hecks/persistence_legacy_fixture/postgres_stores.rb +81 -0
- data/lib/hecks/persistence_legacy_fixture/regenerate.rb +26 -66
- data/lib/hecks/persistence_legacy_fixture.rb +13 -10
- data/lib/hecks/ports/access_control.rb +13 -10
- data/lib/hecks/ports/agent/answers.rb +2 -2
- data/lib/hecks/ports/agent.rb +13 -10
- data/lib/hecks/ports/authentication.rb +13 -10
- data/lib/hecks/ports/authorization.rb +13 -10
- data/lib/hecks/ports/clock.rb +13 -10
- data/lib/hecks/ports/extraction.rb +13 -12
- data/lib/hecks/ports/identity_assignment.rb +13 -10
- data/lib/hecks/ports/identity_generation.rb +13 -10
- data/lib/hecks/ports/identity_resolution.rb +13 -10
- data/lib/hecks/ports/key_vault.rb +13 -10
- data/lib/hecks/ports/persistence/append_only.rb +5 -5
- data/lib/hecks/ports/persistence/binding_policy.rb +33 -9
- data/lib/hecks/ports/persistence/codec_boundary.rb +22 -11
- data/lib/hecks/ports/persistence/constants.rb +10 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +18 -16
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +27 -18
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +52 -23
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +14 -8
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +17 -6
- data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +58 -0
- data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +130 -0
- data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +25 -0
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +20 -180
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +41 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/class_methods.rb +105 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/dialect.rb +137 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +25 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/head_writes.rb +62 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/journal.rb +128 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +74 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/chain_sql.rb +169 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +91 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_integrity.rb +99 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +0 -73
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +55 -39
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +51 -222
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/merge_writes.rb +120 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +85 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +97 -181
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +19 -19
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/sql_templates.rb +88 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +77 -118
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +3 -117
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_sql.rb +116 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +3 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/context.rb +38 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +43 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +62 -38
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +21 -16
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +66 -86
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +75 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +27 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/query_phases.rb +63 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/sagas.rb +78 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +28 -482
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +40 -12
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +22 -15
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +39 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -16
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +48 -25
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +60 -99
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/path_diff.rb +57 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +21 -11
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +13 -4
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +4 -2
- data/lib/hecks/ports/persistence/postgres_dump.rb +13 -7
- data/lib/hecks/ports/persistence/repository_factory.rb +35 -11
- data/lib/hecks/ports/persistence/state_codec.rb +10 -0
- data/lib/hecks/ports/persistence.rb +1 -10
- data/lib/hecks/ports/projection.rb +34 -21
- data/lib/hecks/ports/query/in_memory.rb +16 -8
- data/lib/hecks/projection_files/expression_tables.rb +64 -0
- data/lib/hecks/projection_files/texts.rb +67 -0
- data/lib/hecks/projection_files.rb +19 -103
- data/lib/hecks/projections/bootstrap_table.rb +43 -29
- data/lib/hecks/projections/deploy/bastion_lines.rb +32 -0
- data/lib/hecks/projections/deploy/box/access.rb +73 -0
- data/lib/hecks/projections/deploy/box/caddy.rb +87 -0
- data/lib/hecks/projections/deploy/box/compose.rb +70 -0
- data/lib/hecks/projections/deploy/box/hosting.rb +16 -108
- data/lib/hecks/projections/deploy/box/hosting_parts.rb +97 -0
- data/lib/hecks/projections/deploy/box/hosting_settings.rb +1 -1
- data/lib/hecks/projections/deploy/box/roll_recipe.rb +62 -0
- data/lib/hecks/projections/deploy/box/roll_scripts.rb +135 -0
- data/lib/hecks/projections/deploy/box/settings/checks.rb +38 -0
- data/lib/hecks/projections/deploy/box/settings/containers.rb +123 -0
- data/lib/hecks/projections/deploy/box/settings/origin.rb +56 -0
- data/lib/hecks/projections/deploy/box/settings/patterns.rb +43 -0
- data/lib/hecks/projections/deploy/box/settings/types.rb +72 -0
- data/lib/hecks/projections/deploy/box/settings.rb +58 -263
- data/lib/hecks/projections/deploy/box/stack_yaml.rb +107 -0
- data/lib/hecks/projections/deploy/box/templates/MIGRATION.md.tmpl +39 -0
- data/lib/hecks/projections/deploy/box/templates/Makefile.tmpl +24 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +51 -4
- data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +4 -3
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +16 -4
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +8 -7
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +1 -1
- data/lib/hecks/projections/deploy/box/templates/deploy-usage-services.tmpl +4 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-usage-taskdef.tmpl +5 -0
- data/lib/hecks/projections/deploy/box/templates/ecr-repository.yaml.tmpl +9 -0
- data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +6 -3
- data/lib/hecks/projections/deploy/box/templates/mode-summary-services.tmpl +2 -0
- data/lib/hecks/projections/deploy/box/templates/mode-summary-taskdef.tmpl +4 -0
- data/lib/hecks/projections/deploy/box/templates/restore-to-rds.sh.tmpl +8 -3
- data/lib/hecks/projections/deploy/box/templates/services-block.tmpl +9 -0
- data/lib/hecks/projections/deploy/box/templates/tunnel-egress.yaml.tmpl +11 -0
- data/lib/hecks/projections/deploy/box/templates/verify-copy.sh.tmpl +2 -2
- data/lib/hecks/projections/deploy/box/templates/writable-secrets.yaml.tmpl +7 -0
- data/lib/hecks/projections/deploy/box.rb +37 -503
- data/lib/hecks/projections/deploy/fargate/assembly.rb +13 -142
- data/lib/hecks/projections/deploy/fargate/cdn/behaviors.rb +76 -0
- data/lib/hecks/projections/deploy/fargate/cdn/lines.rb +112 -0
- data/lib/hecks/projections/deploy/fargate/cdn/options.rb +85 -0
- data/lib/hecks/projections/deploy/fargate/cdn.rb +23 -215
- data/lib/hecks/projections/deploy/fargate/check.rb +16 -6
- data/lib/hecks/projections/deploy/fargate/containers/definition_lines.rb +61 -0
- data/lib/hecks/projections/deploy/fargate/containers/reading.rb +86 -0
- data/lib/hecks/projections/deploy/fargate/containers/rendering.rb +132 -0
- data/lib/hecks/projections/deploy/fargate/containers/routing.rb +93 -0
- data/lib/hecks/projections/deploy/fargate/containers.rb +10 -286
- data/lib/hecks/projections/deploy/fargate/extras/reading.rb +90 -0
- data/lib/hecks/projections/deploy/fargate/extras/rendering.rb +81 -0
- data/lib/hecks/projections/deploy/fargate/extras.rb +9 -158
- data/lib/hecks/projections/deploy/fargate/monitoring/alarms.rb +115 -0
- data/lib/hecks/projections/deploy/fargate/monitoring/reading.rb +84 -0
- data/lib/hecks/projections/deploy/fargate/monitoring/topic.rb +59 -0
- data/lib/hecks/projections/deploy/fargate/monitoring/warmer.rb +90 -0
- data/lib/hecks/projections/deploy/fargate/monitoring/warmer_roles.rb +76 -0
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +14 -348
- data/lib/hecks/projections/deploy/fargate/sections.rb +115 -0
- data/lib/hecks/projections/deploy/fargate/settings/ids.rb +55 -0
- data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +77 -0
- data/lib/hecks/projections/deploy/fargate/settings.rb +17 -104
- data/lib/hecks/projections/deploy/fargate/stack/blocks.rb +91 -0
- data/lib/hecks/projections/deploy/fargate/stack/readers.rb +87 -0
- data/lib/hecks/projections/deploy/fargate/stack/recipes.rb +106 -0
- data/lib/hecks/projections/deploy/fargate/stack.rb +146 -0
- data/lib/hecks/projections/deploy/fargate/task_sections.rb +56 -0
- data/lib/hecks/projections/deploy/fargate/yaml.rb +1 -1
- data/lib/hecks/projections/deploy/fargate.rb +51 -756
- data/lib/hecks/projections/deploy/lambda/pii.rb +49 -0
- data/lib/hecks/projections/deploy/lambda/stack/blocks.rb +118 -0
- data/lib/hecks/projections/deploy/lambda/stack/checks.rb +64 -0
- data/lib/hecks/projections/deploy/lambda/stack/deploy_chain.rb +119 -0
- data/lib/hecks/projections/deploy/lambda/stack/readers.rb +62 -0
- data/lib/hecks/projections/deploy/lambda/stack/reading.rb +170 -0
- data/lib/hecks/projections/deploy/lambda/stack/reading_web.rb +53 -0
- data/lib/hecks/projections/deploy/lambda/stack/recipes.rb +112 -0
- data/lib/hecks/projections/deploy/lambda/stack/web.rb +57 -0
- data/lib/hecks/projections/deploy/lambda/stack.rb +54 -0
- data/lib/hecks/projections/deploy/lambda.rb +77 -1885
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +8 -74
- data/lib/hecks/projections/deploy/preview/containers.rb +18 -7
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +15 -87
- data/lib/hecks/projections/deploy/preview/settings.rb +7 -4
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +42 -106
- data/lib/hecks/projections/deploy/preview/template.rb +11 -106
- data/lib/hecks/projections/deploy/preview/yaml_text.rb +1 -1
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +1 -1
- data/lib/hecks/projections/deploy/scripts/settings.rb +14 -6
- data/lib/hecks/projections/deploy/scripts.rb +11 -9
- data/lib/hecks/projections/deploy/shared/rows.rb +48 -0
- data/lib/hecks/projections/deploy/shared.rb +89 -449
- data/lib/hecks/projections/deploy/smoke.rb +15 -10
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +21 -147
- data/lib/hecks/projections/deploy/template_diff/normalizer.rb +60 -0
- data/lib/hecks/projections/deploy/template_diff/value_diff.rb +136 -0
- data/lib/hecks/projections/deploy/template_diff.rb +8 -3
- data/lib/hecks/projections/deploy/templates/fargate/Dockerfile.tmpl +28 -0
- data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +63 -0
- data/lib/hecks/projections/deploy/templates/fargate/always_params.tmpl +4 -0
- data/lib/hecks/projections/deploy/templates/fargate/deploy_plain.tmpl +1 -0
- data/lib/hecks/projections/deploy/templates/fargate/deploy_shared.tmpl +11 -0
- data/lib/hecks/projections/deploy/templates/fargate/mint_era_own.tmpl +44 -0
- data/lib/hecks/projections/deploy/templates/fargate/mint_era_shared.tmpl +8 -0
- data/lib/hecks/projections/deploy/templates/fargate/missing_owner.tmpl +9 -0
- data/lib/hecks/projections/deploy/templates/fargate/oauth_params.tmpl +7 -0
- data/lib/hecks/projections/deploy/templates/fargate/oauth_task_env.tmpl +4 -0
- data/lib/hecks/projections/deploy/templates/fargate/oauth_task_policy.tmpl +7 -0
- data/lib/hecks/projections/deploy/templates/fargate/owning_params.tmpl +33 -0
- data/lib/hecks/projections/deploy/templates/fargate/template.yaml.tmpl +281 -0
- data/lib/hecks/projections/deploy/templates/lambda/Makefile.tmpl +218 -0
- data/lib/hecks/projections/deploy/templates/lambda/aurora_depends.tmpl +18 -0
- data/lib/hecks/projections/deploy/templates/lambda/build_dispatch_none.tmpl +10 -0
- data/lib/hecks/projections/deploy/templates/lambda/build_pg.tmpl +15 -0
- data/lib/hecks/projections/deploy/templates/lambda/build_plain.tmpl +2 -0
- data/lib/hecks/projections/deploy/templates/lambda/build_web.tmpl +11 -0
- data/lib/hecks/projections/deploy/templates/lambda/env_blind_guard.tmpl +4 -0
- data/lib/hecks/projections/deploy/templates/lambda/mint_era_own.tmpl +122 -0
- data/lib/hecks/projections/deploy/templates/lambda/mint_era_shared.tmpl +47 -0
- data/lib/hecks/projections/deploy/templates/lambda/missing_owner.tmpl +9 -0
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +14 -0
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_plain.tmpl +3 -0
- data/lib/hecks/projections/deploy/templates/lambda/oauth_note.tmpl +8 -0
- data/lib/hecks/projections/deploy/templates/lambda/params_oauth.tmpl +13 -0
- data/lib/hecks/projections/deploy/templates/lambda/params_shared.tmpl +33 -0
- data/lib/hecks/projections/deploy/templates/lambda/pg_native.tmpl +71 -0
- data/lib/hecks/projections/deploy/templates/lambda/pii_oac.tmpl +8 -0
- data/lib/hecks/projections/deploy/templates/lambda/pii_permission.tmpl +8 -0
- data/lib/hecks/projections/deploy/templates/lambda/pii_resources.tmpl +116 -0
- data/lib/hecks/projections/deploy/templates/lambda/predeploy_bridge.tmpl +30 -0
- data/lib/hecks/projections/deploy/templates/lambda/predeploy_bridge_oauth.tmpl +12 -0
- data/lib/hecks/projections/deploy/templates/lambda/predeploy_bridge_plain.tmpl +7 -0
- data/lib/hecks/projections/deploy/templates/lambda/rename_schema_own.tmpl +93 -0
- data/lib/hecks/projections/deploy/templates/lambda/rename_schema_shared.tmpl +7 -0
- data/lib/hecks/projections/deploy/templates/lambda/rust_oauth_env.tmpl +17 -0
- data/lib/hecks/projections/deploy/templates/lambda/rust_oauth_policy.tmpl +16 -0
- data/lib/hecks/projections/deploy/templates/lambda/rust_session_secret.tmpl +13 -0
- data/lib/hecks/projections/deploy/templates/lambda/samconfig.toml.tmpl +12 -0
- data/lib/hecks/projections/deploy/templates/lambda/shared_note.tmpl +6 -0
- data/lib/hecks/projections/deploy/templates/lambda/sync_oauth.tmpl +23 -0
- data/lib/hecks/projections/deploy/templates/lambda/template.yaml.tmpl +187 -0
- data/lib/hecks/projections/deploy/templates/lambda/translation_own.tmpl +46 -0
- data/lib/hecks/projections/deploy/templates/lambda/translation_shared.tmpl +2 -0
- data/lib/hecks/projections/deploy/templates/lambda/web_function.tmpl +207 -0
- data/lib/hecks/projections/deploy/templates/lambda/web_google_oauth_env.tmpl +12 -0
- data/lib/hecks/projections/deploy/templates/lambda/web_no_oauth_env.tmpl +7 -0
- data/lib/hecks/projections/deploy/templates/lambda/web_oauth_policy.tmpl +4 -0
- data/lib/hecks/projections/deploy/templates/lambda/webhook_secret_resource.tmpl +20 -0
- data/lib/hecks/projections/deploy/templates/preview/alb.tmpl +8 -0
- data/lib/hecks/projections/deploy/templates/preview/alb_security_group.tmpl +11 -0
- data/lib/hecks/projections/deploy/templates/preview/assume_role.tmpl +6 -0
- data/lib/hecks/projections/deploy/templates/preview/base_parameters.tmpl +24 -0
- data/lib/hecks/projections/deploy/templates/preview/cluster.tmpl +4 -0
- data/lib/hecks/projections/deploy/templates/preview/container_secret.tmpl +9 -0
- data/lib/hecks/projections/deploy/templates/preview/db_init_script.sh.tmpl +23 -0
- data/lib/hecks/projections/deploy/templates/preview/db_init_secrets.tmpl +5 -0
- data/lib/hecks/projections/deploy/templates/preview/db_init_task.tmpl +39 -0
- data/lib/hecks/projections/deploy/templates/preview/desired_count.tmpl +5 -0
- data/lib/hecks/projections/deploy/templates/preview/distribution.tmpl +24 -0
- data/lib/hecks/projections/deploy/templates/preview/execution_role.tmpl +16 -0
- data/lib/hecks/projections/deploy/templates/preview/header.tmpl +16 -0
- data/lib/hecks/projections/deploy/templates/preview/ingress.tmpl +8 -0
- data/lib/hecks/projections/deploy/templates/preview/listener.tmpl +9 -0
- data/lib/hecks/projections/deploy/templates/preview/listener_rule.tmpl +11 -0
- data/lib/hecks/projections/deploy/templates/preview/log_group.tmpl +5 -0
- data/lib/hecks/projections/deploy/templates/preview/log_options.tmpl +5 -0
- data/lib/hecks/projections/deploy/templates/preview/outputs.tmpl +14 -0
- data/lib/hecks/projections/deploy/templates/preview/service.tmpl +17 -0
- data/lib/hecks/projections/deploy/templates/preview/session_secret.tmpl +11 -0
- data/lib/hecks/projections/deploy/templates/preview/target_group.tmpl +9 -0
- data/lib/hecks/projections/deploy/templates/preview/task_definition.tmpl +15 -0
- data/lib/hecks/projections/deploy/templates/preview/task_role.tmpl +22 -0
- data/lib/hecks/projections/deploy/templates/shared/aurora_db.tmpl +52 -0
- data/lib/hecks/projections/deploy/templates/shared/bastion.tmpl +74 -0
- data/lib/hecks/projections/deploy/templates/shared/bastion_own_public.tmpl +41 -0
- data/lib/hecks/projections/deploy/templates/shared/bastion_reuse_public.tmpl +10 -0
- data/lib/hecks/projections/deploy/templates/shared/nat_gateway.tmpl +102 -0
- data/lib/hecks/projections/deploy/templates/shared/oauth_egress.tmpl +17 -0
- data/lib/hecks/projections/deploy/templates/shared/plain_db.tmpl +16 -0
- data/lib/hecks/projections/deploy/templates/shared/vpc_and_database.tmpl +67 -0
- data/lib/hecks/projections/deploy/text_template.rb +35 -0
- data/lib/hecks/projections/diagrams/dispatch.rb +114 -0
- data/lib/hecks/projections/diagrams/statecharts.rb +59 -0
- data/lib/hecks/projections/diagrams/structure.rb +114 -0
- data/lib/hecks/projections/diagrams.rb +33 -256
- data/lib/hecks/projections/glossary/entries.rb +140 -0
- data/lib/hecks/projections/glossary/html/page.rb +96 -0
- data/lib/hecks/projections/glossary/html/parser.rb +66 -0
- data/lib/hecks/projections/glossary/html/sections.rb +78 -0
- data/lib/hecks/projections/glossary/html.rb +7 -179
- data/lib/hecks/projections/glossary/index.rb +45 -0
- data/lib/hecks/projections/glossary/markdown.rb +22 -11
- data/lib/hecks/projections/glossary/mermaid.rb +44 -31
- data/lib/hecks/projections/glossary/sensitivity.rb +7 -4
- data/lib/hecks/projections/glossary/sentences/value_objects.rb +77 -0
- data/lib/hecks/projections/glossary/sentences.rb +42 -81
- data/lib/hecks/projections/glossary/slugs.rb +33 -0
- data/lib/hecks/projections/glossary.rb +22 -190
- data/lib/hecks/projections/model.rb +20 -14
- data/lib/hecks/projections/rust_vocabulary/arguments/support.rb +33 -0
- data/lib/hecks/projections/rust_vocabulary/arguments.rb +171 -0
- data/lib/hecks/projections/rust_vocabulary/enum_body.rb +114 -0
- data/lib/hecks/projections/rust_vocabulary/tests.rb +144 -0
- data/lib/hecks/projections/rust_vocabulary.rb +24 -293
- data/lib/hecks/projections/site/admin.rb +146 -0
- data/lib/hecks/projections/site/admin_cms/templates/membership.rb +115 -0
- data/lib/hecks/projections/site/admin_cms/templates/session_strategy.rb +90 -0
- data/lib/hecks/projections/site/admin_cms/templates/sso.rb +101 -0
- data/lib/hecks/projections/site/admin_cms/templates/sso_tail.rb +54 -0
- data/lib/hecks/projections/site/admin_cms/templates/users.rb +83 -0
- data/lib/hecks/projections/site/admin_cms/templates.rb +33 -0
- data/lib/hecks/projections/site/admin_cms.rb +78 -0
- data/lib/hecks/projections/site/edge/behaviours.rb +10 -6
- data/lib/hecks/projections/site/edge/checks.rb +30 -80
- data/lib/hecks/projections/site/edge/findings.rb +28 -0
- data/lib/hecks/projections/site/edge/route_checks.rb +110 -0
- data/lib/hecks/projections/site/edge/row_reader.rb +52 -0
- data/lib/hecks/projections/site/edge/rules.rb +28 -26
- data/lib/hecks/projections/site/edge.rb +20 -31
- data/lib/hecks/projections/site/live_edge.rb +9 -7
- data/lib/hecks/projections/site/payload/fields/definitions.rb +90 -0
- data/lib/hecks/projections/site/payload/fields/readers.rb +69 -0
- data/lib/hecks/projections/site/payload/fields.rb +137 -0
- data/lib/hecks/projections/site/payload/lifecycle/text.rb +84 -0
- data/lib/hecks/projections/site/payload/lifecycle/text_tail.rb +106 -0
- data/lib/hecks/projections/site/payload/lifecycle.rb +33 -0
- data/lib/hecks/projections/site/payload/model.rb +128 -0
- data/lib/hecks/projections/site/payload/specs/wire.rb +69 -0
- data/lib/hecks/projections/site/payload/specs.rb +135 -0
- data/lib/hecks/projections/site/payload_driver.rb +95 -0
- data/lib/hecks/projections/site/root_rows.rb +67 -0
- data/lib/hecks/projections/site/routes_ts/helpers.rb +79 -0
- data/lib/hecks/projections/site/routes_ts/listings.rb +75 -0
- data/lib/hecks/projections/site/routes_ts/navigation.rb +79 -0
- data/lib/hecks/projections/site/routes_ts.rb +27 -175
- data/lib/hecks/projections/site/site_admin/access.rb +104 -0
- data/lib/hecks/projections/site/site_admin/session.rb +127 -0
- data/lib/hecks/projections/site/site_admin.rb +85 -0
- data/lib/hecks/projections/site/site_cdn.rb +12 -7
- data/lib/hecks/projections/site/site_host/dockerfile.rb +75 -0
- data/lib/hecks/projections/site/site_host/templates.rb +109 -0
- data/lib/hecks/projections/site/site_host.rb +58 -0
- data/lib/hecks/projections/site/site_root/workflow.rb +65 -0
- data/lib/hecks/projections/site/site_root.rb +128 -0
- data/lib/hecks/projections/site/table/checks.rb +28 -57
- data/lib/hecks/projections/site/table/link_builder.rb +14 -5
- data/lib/hecks/projections/site/table/navigation_checks.rb +75 -0
- data/lib/hecks/projections/site/table/row_builder.rb +31 -53
- data/lib/hecks/projections/site/table/row_defaults.rb +65 -0
- data/lib/hecks/projections/site/table.rb +9 -5
- data/lib/hecks/projections/vocabulary.rb +34 -31
- data/lib/hecks/projector/cli_audience.rb +42 -0
- data/lib/hecks/projector/cli_projector/claims.rb +149 -0
- data/lib/hecks/projector/cli_projector/command_help.rb +65 -0
- data/lib/hecks/projector/cli_projector/command_specs.rb +133 -0
- data/lib/hecks/projector/cli_projector/listing.rb +100 -0
- data/lib/hecks/projector/cli_projector/option_specs.rb +116 -0
- data/lib/hecks/projector/cli_projector/query_specs.rb +67 -0
- data/lib/hecks/projector/cli_projector/usage.rb +101 -0
- data/lib/hecks/projector/cli_projector.rb +15 -532
- data/lib/hecks/projector/docs_projector/attributes.rb +78 -0
- data/lib/hecks/projector/docs_projector/behaviour.rb +127 -0
- data/lib/hecks/projector/docs_projector/closing.rb +40 -0
- data/lib/hecks/projector/docs_projector.rb +58 -192
- data/lib/hecks/projector/exporter/bindings.rb +174 -0
- data/lib/hecks/projector/exporter/translations.rb +127 -0
- data/lib/hecks/projector/exporter.rb +8 -279
- data/lib/hecks/projector/narrate_projector/commands.rb +108 -0
- data/lib/hecks/projector/narrate_projector/reactions.rb +33 -0
- data/lib/hecks/projector/narrate_projector.rb +60 -160
- data/lib/hecks/projector.rb +22 -13
- data/lib/hecks/quality_control/adapters/agent_profile.rb +25 -16
- data/lib/hecks/quality_control/adapters/github_checks.rb +42 -6
- data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +1 -1
- data/lib/hecks/quality_control/adapters/github_issues.rb +22 -10
- data/lib/hecks/quality_control/cli/qa_discover_external_domains/parsing.rb +54 -0
- data/lib/hecks/quality_control/cli/qa_discover_external_domains/reporting.rb +44 -0
- data/lib/hecks/quality_control/cli/qa_discover_external_domains/scanning.rb +119 -0
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +28 -155
- data/lib/hecks/quality_control/cli/qa_domain_novelty/verdict.rb +76 -0
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +33 -66
- data/lib/hecks/quality_control/cli/qa_generated_domains/arguments.rb +62 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains/blueprints.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains/evaluation.rb +107 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains/promotion.rb +107 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains/reporting.rb +97 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains/shrinking.rb +70 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +43 -327
- data/lib/hecks/quality_control/cli/qa_log_bug/arguments.rb +55 -0
- data/lib/hecks/quality_control/cli/qa_log_bug/proof.rb +65 -0
- data/lib/hecks/quality_control/cli/qa_log_bug.rb +18 -81
- data/lib/hecks/quality_control/cli/qa_mine_combinations/arguments.rb +54 -0
- data/lib/hecks/quality_control/cli/qa_mine_combinations/booting.rb +96 -0
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +45 -109
- data/lib/hecks/quality_control/cli/qa_open_pr/arguments.rb +55 -0
- data/lib/hecks/quality_control/cli/qa_open_pr/recording.rb +79 -0
- data/lib/hecks/quality_control/cli/qa_open_pr/rules.rb +122 -0
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +41 -200
- data/lib/hecks/quality_control/cli/qa_postgres_migrate/copying.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_postgres_migrate/reporting.rb +33 -0
- data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +37 -63
- data/lib/hecks/quality_control/cli/qa_postgres_role/ownership.rb +57 -0
- data/lib/hecks/quality_control/cli/qa_postgres_role.rb +66 -71
- data/lib/hecks/quality_control/cli/qa_pr_check/checking.rb +118 -0
- data/lib/hecks/quality_control/cli/qa_pr_check/reporting.rb +54 -0
- data/lib/hecks/quality_control/cli/qa_pr_check.rb +28 -145
- data/lib/hecks/quality_control/cli/qa_seed_angles/seed.rb +80 -0
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +13 -86
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode/children.rb +80 -0
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode/report.rb +125 -0
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +63 -193
- data/lib/hecks/quality_control/cli/qa_sweep/checks/boundary.rb +92 -0
- data/lib/hecks/quality_control/cli/qa_sweep/checks/engines.rb +92 -0
- data/lib/hecks/quality_control/cli/qa_sweep/checks/seeds.rb +86 -0
- data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +69 -0
- data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +10 -248
- data/lib/hecks/quality_control/cli/qa_sweep/conclusion.rb +60 -0
- data/lib/hecks/quality_control/cli/qa_sweep/depth.rb +61 -0
- data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +26 -13
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report/shrunk.rb +42 -0
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +40 -41
- data/lib/hecks/quality_control/cli/qa_sweep/parsing/validation.rb +70 -0
- data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +35 -65
- data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +41 -22
- data/lib/hecks/quality_control/cli/qa_sweep/scratch.rb +67 -0
- data/lib/hecks/quality_control/cli/qa_sweep/seat.rb +94 -0
- data/lib/hecks/quality_control/cli/qa_sweep/seed_loop.rb +149 -0
- data/lib/hecks/quality_control/cli/qa_sweep/target_setup.rb +115 -0
- data/lib/hecks/quality_control/cli/qa_sweep.rb +22 -445
- data/lib/hecks/quality_control/cli/qa_tick/output.rb +107 -0
- data/lib/hecks/quality_control/cli/qa_tick/preflight.rb +95 -0
- data/lib/hecks/quality_control/cli/qa_tick.rb +42 -175
- data/lib/hecks/query_ir/formatting.rb +86 -0
- data/lib/hecks/query_ir/rules.rb +132 -0
- data/lib/hecks/query_ir/touchpoints.rb +104 -0
- data/lib/hecks/query_ir.rb +33 -281
- data/lib/hecks/query_ir_mcp/tools.rb +68 -0
- data/lib/hecks/query_ir_mcp.rb +27 -71
- data/lib/hecks/query_specification/common/comparison.rb +25 -5
- data/lib/hecks/query_specification/common/dsl.rb +1 -1
- data/lib/hecks/query_specification/common/null_policy.rb +9 -6
- data/lib/hecks/query_specification/common/options.rb +21 -14
- data/lib/hecks/query_specification/field_path.rb +5 -2
- data/lib/hecks/query_specification/hop_path.rb +15 -18
- data/lib/hecks/release/lane.rb +22 -0
- data/lib/hecks/release/runner/clean_tree.rb +2 -2
- data/lib/hecks/release/runner/commands.rb +1 -1
- data/lib/hecks/release/runner/options.rb +82 -0
- data/lib/hecks/release/runner/preflight.rb +12 -7
- data/lib/hecks/release/runner/publishing.rb +86 -0
- data/lib/hecks/release/runner/tagger.rb +8 -3
- data/lib/hecks/release/runner.rb +40 -156
- data/lib/hecks/rendering.rb +13 -9
- data/lib/hecks/router/door.rb +37 -0
- data/lib/hecks/router/namespace_installer.rb +45 -83
- data/lib/hecks/router/options_proxy.rb +44 -0
- data/lib/hecks/runtime/adapter_lookup.rb +45 -24
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -8
- data/lib/hecks/runtime/command_interpreter/delegation.rb +116 -0
- data/lib/hecks/runtime/command_interpreter/enforcement.rb +38 -0
- data/lib/hecks/runtime/command_interpreter/hydration.rb +139 -0
- data/lib/hecks/runtime/command_interpreter/list_mutations.rb +98 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +67 -114
- data/lib/hecks/runtime/command_interpreter/persisting.rb +96 -0
- data/lib/hecks/runtime/command_interpreter.rb +57 -352
- data/lib/hecks/runtime/command_rules/admissibility.rb +64 -147
- data/lib/hecks/runtime/command_rules/arithmetic.rb +13 -72
- data/lib/hecks/runtime/command_rules/authorization.rb +9 -9
- data/lib/hecks/runtime/command_rules/emission.rb +13 -14
- data/lib/hecks/runtime/command_rules/guard_state.rb +49 -0
- data/lib/hecks/runtime/command_rules/invariants.rb +84 -0
- data/lib/hecks/runtime/command_rules/operands.rb +88 -0
- data/lib/hecks/runtime/command_rules/references.rb +59 -57
- data/lib/hecks/runtime/command_rules/tenant_boundary.rb +69 -0
- data/lib/hecks/runtime/dependency_planning/analyzer.rb +90 -0
- data/lib/hecks/runtime/dependency_planning/expression_reads.rb +73 -0
- data/lib/hecks/runtime/dependency_planning/mutation_analysis.rb +89 -0
- data/lib/hecks/runtime/dependency_planning/rule_analysis.rb +82 -0
- data/lib/hecks/runtime/dependency_planning.rb +3 -250
- data/lib/hecks/runtime/dispatcher/queries.rb +62 -0
- data/lib/hecks/runtime/dispatcher/result.rb +55 -0
- data/lib/hecks/runtime/dispatcher/routing.rb +107 -0
- data/lib/hecks/runtime/dispatcher.rb +64 -209
- data/lib/hecks/runtime/entity_element/location.rb +124 -0
- data/lib/hecks/runtime/entity_element/mutations.rb +150 -0
- data/lib/hecks/runtime/entity_element.rb +5 -177
- data/lib/hecks/runtime/entity_interpreter/enforcement.rb +51 -0
- data/lib/hecks/runtime/entity_interpreter/locating.rb +62 -0
- data/lib/hecks/runtime/entity_interpreter/resolution.rb +42 -0
- data/lib/hecks/runtime/entity_interpreter/saving.rb +49 -0
- data/lib/hecks/runtime/entity_interpreter.rb +45 -164
- data/lib/hecks/runtime/identity.rb +15 -9
- data/lib/hecks/runtime/instance.rb +11 -7
- data/lib/hecks/runtime/interpreting.rb +16 -8
- data/lib/hecks/runtime/invocation.rb +3 -155
- data/lib/hecks/runtime/invocation_builder.rb +132 -0
- data/lib/hecks/runtime/invocation_facts.rb +87 -0
- data/lib/hecks/runtime/loader/boot_steps.rb +96 -0
- data/lib/hecks/runtime/loader/described.rb +27 -0
- data/lib/hecks/runtime/loader.rb +27 -114
- data/lib/hecks/runtime/outbox/consumer_runner.rb +40 -0
- data/lib/hecks/runtime/outbox/fanout.rb +82 -0
- data/lib/hecks/runtime/outbox/relay.rb +149 -0
- data/lib/hecks/runtime/outbox.rb +3 -214
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +67 -0
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +96 -0
- data/lib/hecks/runtime/policy_interpreter.rb +22 -137
- data/lib/hecks/runtime/port_operation_interpreter.rb +18 -17
- data/lib/hecks/runtime/query_interpreter/arguments.rb +51 -0
- data/lib/hecks/runtime/query_interpreter/entity_rows.rb +82 -0
- data/lib/hecks/runtime/query_interpreter/outside_answer.rb +89 -0
- data/lib/hecks/runtime/query_interpreter.rb +43 -191
- data/lib/hecks/runtime/reaction_invocation/identities.rb +92 -0
- data/lib/hecks/runtime/reaction_invocation/target_resolution.rb +88 -0
- data/lib/hecks/runtime/reaction_invocation.rb +66 -183
- data/lib/hecks/runtime/reaction_outcome.rb +7 -3
- data/lib/hecks/runtime/read_model_interpreter/head_ordering.rb +49 -0
- data/lib/hecks/runtime/read_model_interpreter/reductions.rb +123 -0
- data/lib/hecks/runtime/read_model_interpreter/shaping.rb +100 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +71 -214
- data/lib/hecks/runtime/reference_hop.rb +11 -11
- data/lib/hecks/runtime/refusal_wording.rb +11 -8
- data/lib/hecks/runtime/registry/chapter_verification.rb +84 -0
- data/lib/hecks/runtime/registry/declarations.rb +17 -0
- data/lib/hecks/runtime/registry/durability_warnings.rb +71 -0
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +124 -0
- data/lib/hecks/runtime/registry/merging.rb +49 -0
- data/lib/hecks/runtime/registry/providers.rb +139 -0
- data/lib/hecks/runtime/registry/query_verification.rb +144 -0
- data/lib/hecks/runtime/registry/read_repository.rb +61 -0
- data/lib/hecks/runtime/registry/runtime_state.rb +27 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +12 -8
- data/lib/hecks/runtime/registry/verification.rb +45 -389
- data/lib/hecks/runtime/registry/world_defaults.rb +3 -3
- data/lib/hecks/runtime/registry.rb +58 -296
- data/lib/hecks/runtime/remote_dispatcher.rb +47 -31
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +90 -0
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +19 -10
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +147 -0
- data/lib/hecks/runtime/saga_interpreter/lifecycle.rb +63 -0
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +112 -0
- data/lib/hecks/runtime/saga_interpreter.rb +41 -320
- data/lib/hecks/runtime/tenant_check.rb +14 -9
- data/lib/hecks/runtime/value/admission.rb +28 -19
- data/lib/hecks/runtime/value/coercion.rb +55 -404
- data/lib/hecks/runtime/value/entity_list_coercion.rb +27 -29
- data/lib/hecks/runtime/value/field_checks.rb +57 -100
- data/lib/hecks/runtime/value/flags.rb +65 -0
- data/lib/hecks/runtime/value/identifiers.rb +64 -0
- data/lib/hecks/runtime/value/references.rb +195 -0
- data/lib/hecks/runtime/value/shape_checks.rb +61 -0
- data/lib/hecks/runtime/value/validation.rb +125 -0
- data/lib/hecks/runtime/value.rb +18 -6
- data/lib/hecks/rust_build/conformance/surface.rb +75 -0
- data/lib/hecks/rust_build/conformance.rb +27 -59
- data/lib/hecks/rust_build/conformance_fuzz.rb +26 -15
- data/lib/hecks/rust_build/coverage.rb +32 -104
- data/lib/hecks/rust_build/coverage_parts/expected.rb +68 -0
- data/lib/hecks/rust_build/coverage_parts/printer.rb +75 -0
- data/lib/hecks/rust_build/host.rb +23 -13
- data/lib/hecks/rust_build/kernel_input.rb +19 -12
- data/lib/hecks/rust_build/native_build.rb +19 -15
- data/lib/hecks/rust_build/project_rust/domain_loader.rb +75 -0
- data/lib/hecks/rust_build/project_rust/root_mod/notes.rb +66 -0
- data/lib/hecks/rust_build/project_rust/root_mod.rb +22 -55
- data/lib/hecks/rust_build/project_rust/target_ir.rb +74 -0
- data/lib/hecks/rust_build/project_rust.rb +21 -89
- data/lib/hecks/rust_build/wasm.rb +11 -4
- data/lib/hecks/rust_build/wasm_browser.rb +7 -7
- data/lib/hecks/rust_build/write_if_changed.rb +3 -6
- data/lib/hecks/rust_build.rb +7 -3
- data/lib/hecks/site/bluebook/site.bluebook +7 -1
- data/lib/hecks/storehouse/audit.rb +106 -0
- data/lib/hecks/storehouse/callers.rb +73 -0
- data/lib/hecks/storehouse/dispatching.rb +149 -0
- data/lib/hecks/storehouse/introspection.rb +145 -0
- data/lib/hecks/storehouse/querying.rb +78 -0
- data/lib/hecks/storehouse.rb +43 -451
- data/lib/hecks/tickets/bluebook/tickets.bluebook +29 -0
- data/lib/hecks/tools/argument_gate_matrix/dispatching.rb +69 -0
- data/lib/hecks/tools/argument_gate_matrix/rows.rb +121 -0
- data/lib/hecks/tools/argument_gate_matrix/violations.rb +108 -0
- data/lib/hecks/tools/argument_gate_matrix.rb +43 -212
- data/lib/hecks/tools/ci_gate_decision.rb +21 -14
- data/lib/hecks/tools/ci_gates.rb +40 -26
- data/lib/hecks/tools/comment_style/baseline.rb +41 -0
- data/lib/hecks/tools/comment_style/caps_words.rb +104 -0
- data/lib/hecks/tools/comment_style/cli.rb +121 -0
- data/lib/hecks/tools/comment_style/dictionary.rb +60 -0
- data/lib/hecks/tools/comment_style/fixing.rb +119 -0
- data/lib/hecks/tools/comment_style/long_blocks.rb +51 -0
- data/lib/hecks/tools/comment_style/masking.rb +94 -0
- data/lib/hecks/tools/comment_style/patterns.rb +61 -0
- data/lib/hecks/tools/comment_style/run.rb +161 -0
- data/lib/hecks/tools/comment_style/signature.rb +59 -0
- data/lib/hecks/tools/comment_style/source_file.rb +105 -0
- data/lib/hecks/tools/comment_style/structure_checks.rb +113 -0
- data/lib/hecks/tools/comment_style/text_checks.rb +67 -0
- data/lib/hecks/tools/comment_style/vocabulary.rb +55 -0
- data/lib/hecks/tools/comment_style/walker.rb +136 -0
- data/lib/hecks/tools/comment_style.rb +17 -1000
- data/lib/hecks/tools/corpus_report.rb +26 -16
- data/lib/hecks/tools/deploy_recipe/targets.rb +61 -0
- data/lib/hecks/tools/deploy_recipe/worlds.rb +114 -0
- data/lib/hecks/tools/deploy_recipe.rb +60 -174
- data/lib/hecks/tools/deploy_recipe_lint/checks.rb +78 -0
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +88 -0
- data/lib/hecks/tools/deploy_recipe_lint/makefile.rb +84 -0
- data/lib/hecks/tools/deploy_recipe_lint.rb +29 -192
- data/lib/hecks/tools/drop_implicit_append_fields.rb +20 -12
- data/lib/hecks/tools/era_projection_backfill.rb +50 -33
- data/lib/hecks/tools/era_reattest/reporting.rb +55 -0
- data/lib/hecks/tools/era_reattest.rb +32 -43
- data/lib/hecks/tools/evolve_run/arguments.rb +49 -0
- data/lib/hecks/tools/evolve_run/words.rb +60 -0
- data/lib/hecks/tools/evolve_run.rb +43 -90
- data/lib/hecks/tools/fuzz_sweep/findings.rb +106 -0
- data/lib/hecks/tools/fuzz_sweep/options.rb +69 -0
- data/lib/hecks/tools/fuzz_sweep/pool.rb +74 -0
- data/lib/hecks/tools/fuzz_sweep/reporting.rb +88 -0
- data/lib/hecks/tools/fuzz_sweep.rb +75 -273
- data/lib/hecks/tools/gate.rb +31 -16
- data/lib/hecks/tools/heki_compaction.rb +63 -42
- data/lib/hecks/tools/hoist_local_givens/rewriting.rb +119 -0
- data/lib/hecks/tools/hoist_local_givens.rb +89 -157
- data/lib/hecks/tools/journal_compaction.rb +40 -31
- data/lib/hecks/tools/lanes/files.rb +82 -0
- data/lib/hecks/tools/lanes/live.rb +41 -0
- data/lib/hecks/tools/lanes/rows.rb +45 -0
- data/lib/hecks/tools/lanes/ruleset.rb +44 -0
- data/lib/hecks/tools/lanes/watch_workflow.rb +82 -0
- data/lib/hecks/tools/lanes/workflow.rb +79 -0
- data/lib/hecks/tools/lanes.rb +71 -0
- data/lib/hecks/tools/oidc_manifests.rb +10 -9
- data/lib/hecks/tools/query_ir_run.rb +7 -3
- data/lib/hecks/tools/regeneration_run.rb +40 -26
- data/lib/hecks/tools/rust_comment_style/caps_words.rb +98 -0
- data/lib/hecks/tools/rust_comment_style/fixing.rb +102 -0
- data/lib/hecks/tools/rust_comment_style/masking.rb +85 -0
- data/lib/hecks/tools/rust_comment_style/run.rb +94 -0
- data/lib/hecks/tools/rust_comment_style/source_file.rb +71 -0
- data/lib/hecks/tools/rust_comment_style/structure.rb +79 -0
- data/lib/hecks/tools/rust_comment_style/text_checks.rb +63 -0
- data/lib/hecks/tools/rust_comment_style/tokenizer.rb +102 -0
- data/lib/hecks/tools/rust_comment_style.rb +38 -535
- data/lib/hecks/tools/sequence_script.rb +20 -14
- data/lib/hecks/tools/site_routes/cli.rb +70 -0
- data/lib/hecks/tools/site_routes/root_files.rb +59 -0
- data/lib/hecks/tools/site_routes/templates.rb +77 -0
- data/lib/hecks/tools/site_routes.rb +102 -108
- data/lib/hecks/tools/tail_merge.rb +45 -37
- data/lib/hecks/tools/tenant_provisioning.rb +45 -31
- data/lib/hecks/tools/tools_doc.rb +42 -19
- data/lib/hecks/tools/translation_audit/domain_loading.rb +71 -0
- data/lib/hecks/tools/translation_audit/edges.rb +71 -0
- data/lib/hecks/tools/translation_audit/verdicts.rb +107 -0
- data/lib/hecks/tools/translation_audit.rb +30 -161
- data/lib/hecks/tools/translation_scaffold/reporting.rb +40 -0
- data/lib/hecks/tools/translation_scaffold.rb +77 -60
- data/lib/hecks/tools.rb +1 -0
- data/lib/hecks/vendoring.rb +37 -11
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +16 -1
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +7 -9
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +8 -6
- data/rust/.cargo/mutants.toml +8 -0
- data/rust/host/Cargo.toml +5 -0
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/auth.rs +32 -0
- data/rust/host/src/web.rs +2 -3
- data/rust/src/kernel/dispatch.rs +4 -0
- data/rust/src/kernel/dispatch_guard_tests.rs +506 -0
- data/rust/src/kernel/orchestrate.rs +4 -0
- data/rust/src/kernel/orchestrate_guard_tests.rs +708 -0
- metadata +607 -3
- /data/lib/hecks/bluebook/behaviour/{hexagon.rb → hecksagon.rb} +0 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
require_relative "../../projector"
|
|
2
2
|
require_relative "shared"
|
|
3
|
+
require_relative "text_template"
|
|
4
|
+
require_relative "lambda/stack"
|
|
5
|
+
require_relative "lambda/pii"
|
|
3
6
|
|
|
4
7
|
module Hecks
|
|
5
8
|
module Projections
|
|
@@ -22,1901 +25,90 @@ module Hecks
|
|
|
22
25
|
# @return [Hash{String => String}] generated file contents by filename
|
|
23
26
|
# @raise [ArgumentError] if `deployed_to("AwsLambda")` is invalid
|
|
24
27
|
def call(bluebook:, options: {})
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# Structural-only load (no `run_boot_gates!`/live persistence adapter)
|
|
38
|
-
# populates `registry.pending_privacy_markings` for pii detection below
|
|
39
|
-
# without requiring a real Postgres connection to generate a template.
|
|
40
|
-
pii_registry = Hecks::Runtime::Registry.new(root: File.expand_path(domain))
|
|
41
|
-
Hecks.with_registry(pii_registry) do
|
|
42
|
-
bootstrap = Hecks::Ports::Loading.bootstrap
|
|
43
|
-
bluebook_dir = File.join(domain, "bluebook")
|
|
44
|
-
bootstrap.load_library
|
|
45
|
-
bootstrap.load_project(bootstrap.shared_root(nil, bluebook_dir))
|
|
46
|
-
bootstrap.load_bluebooks(bluebook_dir)
|
|
47
|
-
Dir.glob(File.join(bluebook_dir, "*.hecksagon")).each { |file| Kernel.load(file) }
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Only `category: "pii"` triggers CloudFront/WAF provisioning; other
|
|
51
|
-
# marking categories (e.g. "phi") stay a Governance/redaction concern.
|
|
52
|
-
pii_detected = pii_registry.pending_privacy_markings.any? { |marking| marking[:category].to_s == "pii" }
|
|
53
|
-
|
|
54
|
-
# Applied before infra_name/hecks_schema below, so both read the
|
|
55
|
-
# override the same way they read any other `deployed_to` setting.
|
|
56
|
-
if tenant_options[:tenant]
|
|
57
|
-
base_stack_name = deploy_settings[:stack_name] || domain_name
|
|
58
|
-
deploy_settings = deploy_settings.merge(
|
|
59
|
-
stack_name: "#{base_stack_name}-#{tenant_options[:tenant]}",
|
|
60
|
-
schema: tenant_options[:schema] || tenant_options[:tenant]
|
|
61
|
-
)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Every AWS-facing name (stack, logical ids, S3 prefixes, secrets)
|
|
65
|
-
# reads `infra_name`, not `domain_name`, so a `stack_name` override
|
|
66
|
-
# survives a later `formerly_known_as` rename of the declared identity
|
|
67
|
-
# without renaming (and disconnecting from) the live AWS stack.
|
|
68
|
-
infra_name = deploy_settings[:stack_name] || domain_name
|
|
69
|
-
|
|
70
|
-
# RDS's own DBName/DatabaseName parameter refuses non-alphanumeric
|
|
71
|
-
# characters; every downstream consumer (Environment vars, Makefile
|
|
72
|
-
# shell commands) has to read the same sanitized spelling.
|
|
73
|
-
db_name = infra_name.gsub(/[^a-zA-Z0-9]/, "")
|
|
74
|
-
|
|
75
|
-
# Dispatches into LambdaTarget.Declare's given/invariant machinery for
|
|
76
|
-
# named, corpus-testable refusals (memory/timeout checked as real
|
|
77
|
-
# Integers against Lambda's 128-10240 MB / 900s ceilings).
|
|
78
|
-
deploy_dispatcher = Hecks.boot(File.expand_path("../../deploy", __dir__))
|
|
79
|
-
begin
|
|
80
|
-
target = deploy_dispatcher.dispatch(
|
|
81
|
-
"Deploy::LambdaTarget.Declare",
|
|
82
|
-
with: {
|
|
83
|
-
domain: { value: declared_domain_name },
|
|
84
|
-
region: { value: deploy_settings[:region] },
|
|
85
|
-
memory: { value: deploy_settings.fetch(:memory, 512) },
|
|
86
|
-
timeout: { value: deploy_settings.fetch(:timeout, 10) },
|
|
87
|
-
database: { value: deploy_settings.fetch(:database, "Postgres") },
|
|
88
|
-
web: { value: deploy_settings.fetch(:web, "None") },
|
|
89
|
-
**%i[dispatch handler_module secret_env].select { |key| deploy_settings.key?(key) }
|
|
90
|
-
.to_h { |key| [key, { value: deploy_settings[key].to_s }] }
|
|
91
|
-
}
|
|
92
|
-
).instance
|
|
93
|
-
rescue *Hecks::Runtime::DOMAIN_REFUSALS => e
|
|
94
|
-
raise ArgumentError, "#{world_file}'s deployed_to(\"AwsLambda\") is invalid: #{e.message}"
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# `dispatch "None"` skips generating a rust/host dispatch Lambda for a
|
|
98
|
-
# domain with no `.bluebook` command surface to dispatch through (e.g.
|
|
99
|
-
# QualityControl's GitHub webhook, dispatching straight into Ruby);
|
|
100
|
-
# such a domain's WebFunction becomes its only Lambda.
|
|
101
|
-
dispatch_none = deploy_settings[:dispatch].to_s == "None"
|
|
102
|
-
|
|
103
|
-
# Names the env var WebFunction's `lambda_handler.rb` finds the fetched
|
|
104
|
-
# secret's plaintext under, once it resolves `#{secret_env}_ARN`.
|
|
105
|
-
webhook_secret_env = deploy_settings[:secret_env]
|
|
106
|
-
|
|
107
|
-
# The module name `lambda_handler.rb` actually defines; named here
|
|
108
|
-
# since `dispatch "None"` domains may each pick their own.
|
|
109
|
-
webhook_handler_module = deploy_settings.fetch(:handler_module, "WebLambdaHandler")
|
|
110
|
-
|
|
111
|
-
# Scans every loaded chapter, not just this domain's own — a
|
|
112
|
-
# `attaches`-attached chapter can itself declare a cross-domain
|
|
113
|
-
# policy, including one nested inside `aggregate "X" do ... end`.
|
|
114
|
-
cross_domain_lambda_targets = cross_domain_registry.bluebooks.flat_map { |chapter_name, bluebook|
|
|
115
|
-
bluebook.policies.select(&:target_domain).map(&:target_domain)
|
|
116
|
-
}.uniq.sort
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
region = target.state[:region].value
|
|
120
|
-
memory = target.state[:memory].value
|
|
121
|
-
timeout = target.state[:timeout].value
|
|
122
|
-
database = target.state[:database].value
|
|
123
|
-
aurora = database == "Aurora"
|
|
124
|
-
shared = database == "Shared"
|
|
125
|
-
rust_web = target.state[:web].value == "Rust"
|
|
126
|
-
|
|
127
|
-
# AWS::WAFv2::WebACL with Scope: CLOUDFRONT is refused by
|
|
128
|
-
# CloudFormation outside us-east-1; refused here before writing a file.
|
|
129
|
-
if pii_detected && region != "us-east-1"
|
|
130
|
-
raise ArgumentError, "#{world_file} marks a field \"pii\" but deployed_to(\"AwsLambda\") sets region " \
|
|
131
|
-
"#{region.inspect} — a CloudFront-scoped WAFv2 WebACL can only be created in " \
|
|
132
|
-
"us-east-1. Set region \"us-east-1\", or remove the pii marking if this domain " \
|
|
133
|
-
"genuinely holds none."
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Read off `deploy_settings` directly, not `target` — these two are
|
|
137
|
-
# optional and pii-only, unlike every always-validated `target.state`.
|
|
138
|
-
geo_restriction_type = deploy_settings[:geo_restriction] || "none"
|
|
139
|
-
geo_restriction_countries = deploy_settings[:geo_restriction_countries] || []
|
|
140
|
-
|
|
141
|
-
# Shared mode provisions no RDS/VPC of its own; it borrows another
|
|
142
|
-
# already-deployed domain's instance, isolated by Postgres schema.
|
|
143
|
-
if shared
|
|
144
|
-
owner_domain_name = deploy_settings[:owner] or raise ArgumentError, <<~MSG
|
|
145
|
-
#{world_file}'s deployed_to("AwsLambda") declares database "Shared" but no owner. Add one, e.g.:
|
|
146
|
-
|
|
147
|
-
deployed_to("AwsLambda") do
|
|
148
|
-
...
|
|
149
|
-
database "Shared"
|
|
150
|
-
owner "Core"
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
naming the already-deployed domain whose Postgres instance this one borrows.
|
|
154
|
-
MSG
|
|
155
|
-
# `owner_stack` is the escape hatch for an owner whose live stack
|
|
156
|
-
# name predates the "hecks-<lowercase name>" convention below.
|
|
157
|
-
owner_stack_name = deploy_settings[:owner_stack] || "hecks-#{owner_domain_name.downcase}"
|
|
158
|
-
owner_db_name = owner_domain_name.downcase
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
# rust/host's optional counterpart to Ruby's `settings[:schema]`.
|
|
162
|
-
# Automatic (this domain's lowercase name) in Shared mode; otherwise
|
|
163
|
-
# explicit via `deployed_to("AwsLambda") { schema "..." }`.
|
|
164
|
-
hecks_schema = shared ? infra_name : deploy_settings[:schema]
|
|
165
|
-
|
|
166
|
-
logical_id = "#{infra_name.split(/[_-]/).map(&:capitalize).join}Function"
|
|
167
|
-
# The "hecks-" half of this domain's own stack name; overridable for a
|
|
168
|
-
# live stack that predates the convention. Everything downstream
|
|
169
|
-
# (FunctionNames, OAuth secret, bastion stack, samconfig) reads
|
|
170
|
-
# `stack_name`; `infra_name` (logical ids, DB name) is untouched.
|
|
171
|
-
stack_prefix = deploy_settings[:stack_prefix] || "hecks"
|
|
172
|
-
stack_name = "#{stack_prefix}-#{infra_name}"
|
|
173
|
-
|
|
174
|
-
db_id = "#{logical_id.sub(/Function\z/, '')}Db"
|
|
175
|
-
# Aurora splits the database into a DBCluster plus DBInstance(s), so
|
|
176
|
-
# every `.Endpoint.Address`/`.MasterUserSecret` reference below has to
|
|
177
|
-
# point at the cluster, not `db_id`, when Aurora is chosen.
|
|
178
|
-
db_ref_id = aurora ? "#{db_id}Cluster" : db_id
|
|
179
|
-
|
|
180
|
-
# Aurora uses a self-managed secret (ManageMasterUserPassword rotation
|
|
181
|
-
# would break a Lambda's static Environment); plain RDS uses its own
|
|
182
|
-
# MasterUserSecret attribute. `secret_sub` is for `!Sub "${...}"`
|
|
183
|
-
# interpolation; `secret_intrinsic` is the same fact as a bare value.
|
|
184
|
-
secret_sub = aurora ? "#{db_id}Secret" : "#{db_ref_id}.MasterUserSecret.SecretArn"
|
|
185
|
-
secret_intrinsic = aurora ? "!Ref #{db_id}Secret" : "!GetAtt #{db_ref_id}.MasterUserSecret.SecretArn"
|
|
186
|
-
# Shared mode has no `secret_sub` of its own; `OwningDatabaseSecretArn`
|
|
187
|
-
# (this template's Parameter) is already a bare ARN string.
|
|
188
|
-
db_secret_ref = shared ? "OwningDatabaseSecretArn" : secret_sub
|
|
189
|
-
|
|
190
|
-
# Opt-in by file presence: a domain wanting its own web app on Lambda
|
|
191
|
-
# drops a `lambda_handler.rb` (the Rack-to-Function-URL-event adapter
|
|
192
|
-
# Sinatra runs through) at its own root. No separate .world verb.
|
|
193
|
-
web_handler_present = File.exist?(File.join(domain, "lambda_handler.rb"))
|
|
194
|
-
|
|
195
|
-
# A domain declares one web story, not both.
|
|
196
|
-
if rust_web && web_handler_present
|
|
197
|
-
raise ArgumentError, "#{domain} declares both web \"Rust\" (#{world_file}) and a lambda_handler.rb (#{File.join(domain, 'lambda_handler.rb')}) — pick one."
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
# Read from the domain's own Gemfile.lock, not hardcoded — the
|
|
201
|
-
# `patch-pg-native` build recipe below must fetch the same pg version
|
|
202
|
-
# Bundler resolved, or the from-source extension can drift from it.
|
|
203
|
-
pg_version = nil
|
|
204
|
-
if web_handler_present
|
|
205
|
-
# `dispatch "None"` reads this project's own root Gemfile.lock, not
|
|
206
|
-
# #{domain}'s: #{domain} has no Gemfile of its own to bundle (it
|
|
207
|
-
# needs `lib/hecks` from one level up), so CodeUri is `root` there.
|
|
208
|
-
pg_version_path = File.join(dispatch_none ? root : domain, "Gemfile.lock")
|
|
209
|
-
pg_version = File.read(pg_version_path)[/^\s+pg \(([\d.]+)\)/, 1]
|
|
210
|
-
end
|
|
211
|
-
web_logical_id = "WebFunction"
|
|
212
|
-
|
|
213
|
-
# Same `dispatch "None"` split as `pg_version_path` above: #{domain}
|
|
214
|
-
# alone has no Gemfile/lib/hecks to zip, so CodeUri is `root` instead.
|
|
215
|
-
web_code_uri = dispatch_none ? root : domain
|
|
216
|
-
web_handler_relpath = dispatch_none ? "#{domain}/lambda_handler" : "lambda_handler"
|
|
217
|
-
|
|
218
|
-
# `.env.local` is #{domain}'s gitignored local-dev secrets file, read
|
|
219
|
-
# only to detect the key is present; `make sync-google-oauth` syncs
|
|
220
|
-
# the real value into Secrets Manager at deploy time.
|
|
221
|
-
google_oauth_present = (web_handler_present || rust_web) &&
|
|
222
|
-
File.exist?(File.join(domain, ".env.local")) &&
|
|
223
|
-
File.read(File.join(domain, ".env.local")).match?(/^GOOGLE_CLIENT_ID=\S/)
|
|
28
|
+
stack = Stack.new(options)
|
|
29
|
+
files = { "template.yaml" => template_yaml(stack) }
|
|
30
|
+
# A separate, sibling stack for the few minutes `make mint-era` needs it, then deleted:
|
|
31
|
+
# SSM Session Manager only, never SSH, no inbound rule at all. Skipped when `shared`:
|
|
32
|
+
# era-minting reuses the owner's own standing infrastructure instead.
|
|
33
|
+
files["bastion.yaml"] = bastion_yaml(stack) unless stack.shared
|
|
34
|
+
files["Makefile"] = TextTemplate.render_from("lambda/Makefile.tmpl", stack)
|
|
35
|
+
# Everything `sam deploy --guided` would ask interactively is already known from the
|
|
36
|
+
# domain's own `deployed_to("AwsLambda")` block.
|
|
37
|
+
files["samconfig.toml"] = TextTemplate.render_from("lambda/samconfig.toml.tmpl", stack)
|
|
38
|
+
files
|
|
39
|
+
end
|
|
224
40
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
41
|
+
# Builds the CloudFront/WAFv2/logging resources a pii-marked domain is fronted by.
|
|
42
|
+
#
|
|
43
|
+
# @param fronted_logical_id [String] the function the distribution fronts
|
|
44
|
+
# @param use_oac [Boolean] whether the distribution signs its requests to the function
|
|
45
|
+
# @param geo_restriction_type [String] `none`, `whitelist` or `blacklist`
|
|
46
|
+
# @param geo_restriction_countries [Array<String>] the country codes the restriction lists
|
|
47
|
+
# @return [String] the resources, indented two spaces under `Resources:`
|
|
48
|
+
def pii_cloudfront_yaml(fronted_logical_id:, use_oac:, geo_restriction_type:, geo_restriction_countries:)
|
|
49
|
+
Pii.cloudfront_yaml(fronted_logical_id: fronted_logical_id, use_oac: use_oac,
|
|
50
|
+
geo_restriction_type: geo_restriction_type,
|
|
51
|
+
geo_restriction_countries: geo_restriction_countries)
|
|
52
|
+
end
|
|
230
53
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
54
|
+
# Renders `template.yaml`, then the edits that are made after it renders, not interpolated
|
|
55
|
+
# inside it: a `#{...}` marker at column 0 would drag the template's own dedent computation
|
|
56
|
+
# to zero and leave every other line un-stripped.
|
|
57
|
+
#
|
|
58
|
+
# @param stack [Stack] the stack the world declares
|
|
59
|
+
# @return [String] the SAM template
|
|
60
|
+
def template_yaml(stack)
|
|
61
|
+
text = TextTemplate.render_from("lambda/template.yaml.tmpl", stack)
|
|
62
|
+
text = text.sub(/^([ \t]*)# TMPL:cross_domain_lambda_policies\n/) do
|
|
63
|
+
Shared.cross_domain_invoke_policy_yaml(stack.cross_domain_targets, Regexp.last_match(1))
|
|
237
64
|
end
|
|
65
|
+
text = with_pii_distribution(text, stack) if stack.pii_detected
|
|
66
|
+
stack.dispatch_none ? without_dispatch_function(text, stack) : text
|
|
67
|
+
end
|
|
238
68
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
shared: shared, db_id: db_id, db_ref_id: db_ref_id, secret_intrinsic: secret_intrinsic,
|
|
247
|
-
compute_security_group_ref: "!Ref #{logical_id}SecurityGroup", google_oauth_present: google_oauth_present
|
|
69
|
+
# Operates on the already-rendered string: the pii resources are built correctly indented,
|
|
70
|
+
# so nothing here interacts with a template's own dedent computation.
|
|
71
|
+
def with_pii_distribution(text, stack)
|
|
72
|
+
resources = pii_cloudfront_yaml(
|
|
73
|
+
fronted_logical_id: stack.web_handler_present ? stack.web_logical_id : stack.logical_id,
|
|
74
|
+
use_oac: !stack.web_handler_present && !stack.rust_web,
|
|
75
|
+
geo_restriction_type: stack.geo_restriction_type, geo_restriction_countries: stack.geo_restriction_countries
|
|
248
76
|
)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
# A plain local, not two heredocs opened side by side inside the
|
|
253
|
-
# template heredoc's own interpolation — a heredoc body starts on the
|
|
254
|
-
# line after it opens, so an if/else here keeps each one unambiguous.
|
|
255
|
-
web_google_oauth_env_yaml =
|
|
256
|
-
if google_oauth_present
|
|
257
|
-
<<~GOOGLE.each_line.with_index.map { |l, i| i.zero? ? l : " " + l }.join.rstrip
|
|
258
|
-
# BY NAME, not `!Ref`/`!GetAtt` -- #{web_logical_id}GoogleOauth
|
|
259
|
-
# is deliberately NOT declared as a CloudFormation resource
|
|
260
|
-
# here (would need either GenerateSecretString, which can't
|
|
261
|
-
# invent a real Google client_id/secret, or a value baked
|
|
262
|
-
# into this git-TRACKED template as plaintext). `make
|
|
263
|
-
# sync-google-oauth` owns this secret's whole lifecycle
|
|
264
|
-
# instead, straight from #{domain}'s own gitignored
|
|
265
|
-
# .env.local, outside CloudFormation entirely. GetSecretValue
|
|
266
|
-
# accepts a bare name directly, no ARN needed -- same
|
|
267
|
-
# generator-only caveat as DB_SECRET_ARN's own comment above.
|
|
268
|
-
GOOGLE_OAUTH_SECRET_ID: #{stack_name}-web-google-oauth
|
|
269
|
-
GOOGLE_REDIRECT_URI: !Sub "${WebRedirectBaseUrl}/auth/google/callback"
|
|
270
|
-
GOOGLE
|
|
271
|
-
else
|
|
272
|
-
<<~NOOAUTH.each_line.with_index.map { |l, i| i.zero? ? l : " " + l }.join.rstrip
|
|
273
|
-
# GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET/GOOGLE_REDIRECT_URI
|
|
274
|
-
# deliberately NOT set here — #{domain}'s own .env.local has
|
|
275
|
-
# no real GOOGLE_CLIENT_ID yet, matching Fly's own current
|
|
276
|
-
# reality (its secrets list holds only
|
|
277
|
-
# DATABASE_URL and SESSION_SECRET) that nobody has configured
|
|
278
|
-
# OAuth for this app at all. Drop real credentials into
|
|
279
|
-
# .env.local and regenerate to wire this up.
|
|
280
|
-
NOOAUTH
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
template_yaml = <<~YAML
|
|
284
|
-
# GENERATED by hecks deploy project #{domain} — re-run it to refresh
|
|
285
|
-
# this file rather than hand-editing. Source: #{world_file}'s own
|
|
286
|
-
# deployed_to("AwsLambda") block.
|
|
287
|
-
#
|
|
288
|
-
# SELF-CONTAINED, ON PURPOSE — this stack owns its own private VPC,
|
|
289
|
-
# subnets, security groups, and RDS Postgres instance, not just the
|
|
290
|
-
# Lambda. No externally-supplied VPC/database parameters, no
|
|
291
|
-
# DatabaseUrl secret anyone has to type: RDS's own
|
|
292
|
-
# ManageMasterUserPassword generates and stores the password in
|
|
293
|
-
# Secrets Manager, and DATABASE_URL is composed from it via a
|
|
294
|
-
# CloudFormation dynamic reference — resolved at deploy time, never
|
|
295
|
-
# visible in this template, a parameter, or any tool's output.
|
|
296
|
-
#
|
|
297
|
-
# NO NAT GATEWAY for #{logical_id} — deliberately. Its own subnets
|
|
298
|
-
# have no route to the internet at all, only to the RDS instance
|
|
299
|
-
# inside the same VPC: dispatch a command, read/write the journal.
|
|
300
|
-
# CloudWatch Logs delivery doesn't go through the function's own VPC
|
|
301
|
-
# networking, so it needs no route either.
|
|
302
|
-
#{google_oauth_present ? " #\n # #{web_logical_id} is the ONE exception -- real Google OAuth token\n # exchange (hecks's own GoogleAuthentication adapter posting to\n # https://oauth2.googleapis.com/token) is a genuine third-party HTTPS\n # call with no AWS PrivateLink/VPC Endpoint option (unlike\n # lambda:InvokeFunction, above), and #{web_logical_id} is ALSO\n # VPC-attached (Member's own permanent Postgres binding needs the\n # private RDS instance directly). A real, live 15-second Lambda\n # timeout caught this -- the token-exchange POST had nowhere to\n # route to and just hung. #{db_id}NatGateway below is the real,\n # ongoing-cost fix (one NAT Gateway, one public subnet) -- opt-in,\n # the SAME google_oauth_present signal that wires\n # GOOGLE_CLIENT_ID/SECRET, since nothing else in this stack needs\n # outbound internet at all." : " # Real outbound internet access from here (a future external API\n # call, say) would need a NAT Gateway added -- a real, ongoing cost\n # this template doesn't take on until something actually needs it."}
|
|
303
|
-
AWSTemplateFormatVersion: '2010-09-09'
|
|
304
|
-
Transform: AWS::Serverless-2016-10-31
|
|
305
|
-
Description: >
|
|
306
|
-
#{infra_name} — dispatched through hecks's rust/host, a
|
|
307
|
-
wasmtime-sandboxed Rust binary with a rehydrate-and-replay Postgres
|
|
308
|
-
journal (docs/decisions/0018), backed by its own private RDS
|
|
309
|
-
Postgres instance. PackageType Zip, no container.
|
|
310
|
-
#{
|
|
311
|
-
# google_oauth_present and shared are independent and collected
|
|
312
|
-
# into one array under a single `Parameters:` header, not an
|
|
313
|
-
# if/elsif/else — both can be true at once (a Shared-mode
|
|
314
|
-
# rust_web domain with real Google OAuth).
|
|
315
|
-
param_blocks = []
|
|
316
|
-
if google_oauth_present
|
|
317
|
-
param_blocks << <<~OAUTHPARAMS.rstrip
|
|
318
|
-
# Lambda Function URLs get a random, un-derivable hostname at
|
|
319
|
-
# creation -- #{web_logical_id}'s own GOOGLE_REDIRECT_URI can't
|
|
320
|
-
# reference `!GetAtt #{web_logical_id}Url.FunctionUrl` from inside
|
|
321
|
-
# #{web_logical_id}'s OWN Environment (that's a real circular
|
|
322
|
-
# dependency CloudFormation rejects: the value doesn't exist until
|
|
323
|
-
# the function+URL do). Resolved OUTSIDE the stack instead --
|
|
324
|
-
# `make deploy`'s own recipe looks up the CURRENTLY deployed
|
|
325
|
-
# Function URL (empty on a true first deploy, before the URL
|
|
326
|
-
# exists at all) and passes it as `--parameter-overrides`; every
|
|
327
|
-
# deploy after the first self-heals this correctly.
|
|
328
|
-
WebRedirectBaseUrl:
|
|
329
|
-
Type: String
|
|
330
|
-
Default: ""
|
|
331
|
-
OAUTHPARAMS
|
|
332
|
-
end
|
|
333
|
-
if shared
|
|
334
|
-
param_blocks << <<~SHAREDPARAMS.rstrip
|
|
335
|
-
# THE STOREHOUSE — #{owner_domain_name}'s own live stack Outputs,
|
|
336
|
-
# looked up at deploy time (Makefile's own `deploy:`/`mint-era`
|
|
337
|
-
# targets, below) via the SAME `aws cloudformation describe-stacks`
|
|
338
|
-
# pattern `mint-era` already used for a bastion, one stack over
|
|
339
|
-
# instead of one sibling stack over. Never a CloudFormation
|
|
340
|
-
# Export/ImportValue -- this codebase uses none anywhere (hecks
|
|
341
|
-
# deploy project's own header on why: a live shell lookup keeps
|
|
342
|
-
# this stack independently deployable/deletable, never coupled to
|
|
343
|
-
# #{owner_domain_name}'s own stack through CloudFormation itself).
|
|
344
|
-
OwningVpcId:
|
|
345
|
-
Type: AWS::EC2::VPC::Id
|
|
346
|
-
OwningSubnetAId:
|
|
347
|
-
Type: AWS::EC2::Subnet::Id
|
|
348
|
-
OwningSubnetBId:
|
|
349
|
-
Type: AWS::EC2::Subnet::Id
|
|
350
|
-
# #{owner_domain_name}'s own Lambda security group, NOT its
|
|
351
|
-
# DB-facing one -- reused whole, not copied, the same shape
|
|
352
|
-
# this codebase already trusts elsewhere (a WebFunction sharing
|
|
353
|
-
# its own dispatch Lambda's security group rather than minting
|
|
354
|
-
# a second one), one stack over instead of one resource over.
|
|
355
|
-
# #{owner_domain_name}'s own DB security group already permits
|
|
356
|
-
# ingress FROM this group (every member of it, regardless of
|
|
357
|
-
# which stack minted the member -- security group references
|
|
358
|
-
# match on GROUP MEMBERSHIP, not resource identity), so joining
|
|
359
|
-
# it is what actually grants this Lambda access, not a new
|
|
360
|
-
# ingress rule #{owner_domain_name}'s own stack would otherwise
|
|
361
|
-
# need to add.
|
|
362
|
-
OwningSecurityGroupId:
|
|
363
|
-
Type: AWS::EC2::SecurityGroup::Id
|
|
364
|
-
OwningDatabaseEndpoint:
|
|
365
|
-
Type: String
|
|
366
|
-
OwningDatabaseSecretArn:
|
|
367
|
-
Type: String
|
|
368
|
-
SHAREDPARAMS
|
|
369
|
-
end
|
|
370
|
-
if param_blocks.empty?
|
|
371
|
-
""
|
|
372
|
-
else
|
|
373
|
-
# Indented 2: the heredocs above dedent to column 0, but these
|
|
374
|
-
# are Parameters: children, which YAML requires indented under it.
|
|
375
|
-
"Parameters:\n" + param_blocks.join("\n").each_line.map { |l| " #{l}" }.join
|
|
376
|
-
end
|
|
377
|
-
}
|
|
378
|
-
Resources:
|
|
379
|
-
#{shared ? "" : Shared.vpc_and_database_yaml(
|
|
380
|
-
db_id: db_id, db_name: db_name, infra_name: infra_name, aurora: aurora,
|
|
381
|
-
google_oauth_present: google_oauth_present, compute_logical_id: logical_id,
|
|
382
|
-
compute_description: "#{logical_id} - no inbound (Lambda receives no traffic via its VPC ENI), egress rule attached separately below"
|
|
383
|
-
).each_line.with_index.map { |l, i| (i.zero? ? "" : " ") + l }.join.rstrip}
|
|
384
|
-
|
|
385
|
-
#{rust_web && google_oauth_present ? <<~RUSTSECRET.each_line.with_index.map { |l, i| (i.zero? ? "" : " ") + l }.join.rstrip : ""}
|
|
386
|
-
# Auto-generated, never typed or seen -- rust/host's own
|
|
387
|
-
# auth.rs signs both the session cookie and the OAuth `state`
|
|
388
|
-
# token with this. Same ManageMasterUserPassword-style pattern
|
|
389
|
-
# #{db_id} already uses for its own password.
|
|
390
|
-
#{logical_id}SessionSecret:
|
|
391
|
-
Type: AWS::SecretsManager::Secret
|
|
392
|
-
Properties:
|
|
393
|
-
GenerateSecretString:
|
|
394
|
-
SecretStringTemplate: '{}'
|
|
395
|
-
GenerateStringKey: session_secret
|
|
396
|
-
PasswordLength: 64
|
|
397
|
-
ExcludePunctuation: true
|
|
398
|
-
|
|
399
|
-
RUSTSECRET
|
|
400
|
-
#{logical_id}:
|
|
401
|
-
Type: AWS::Serverless::Function#{aurora ? "\n # `{{resolve:secretsmanager:...}}` dynamic references do NOT\n # create an implicit CloudFormation dependency on the referenced\n # resource (documented AWS behavior) -- #{logical_id}'s own\n # Environment resolves one against #{db_id}Secret below. A plain\n # `DependsOn: #{db_id}Secret` alone (CREATE_COMPLETE before this\n # resource's own update starts) still hit a real, live, repeatable\n # \"Secrets Manager can't find the specified secret\n # (ResourceNotFoundException)\" -- CREATE_COMPLETE from\n # CloudFormation's own perspective doesn't guarantee the secret is\n # yet READABLE via a dynamic reference from a DIFFERENT resource's\n # own concurrent update (AWS-side eventual consistency, not a\n # CloudFormation ordering bug `DependsOn` alone can close).\n # Chaining through #{db_id}Cluster too -- which ALSO reads this\n # secret, and whose own RDS-side update takes real, substantial\n # wall-clock time to apply -- gives that propagation window time\n # to close before #{logical_id}'s own resolution is attempted.\n DependsOn: [#{db_id}Secret, #{db_id}Cluster]" : ""}
|
|
402
|
-
Metadata:
|
|
403
|
-
BuildMethod: makefile
|
|
404
|
-
Properties:
|
|
405
|
-
# PINNED, not SAM's own auto-suffixed default — a Ruby-side
|
|
406
|
-
# LambdaClient needs to know this function's name AHEAD OF a
|
|
407
|
-
# first deploy (there's no chicken-egg lookup step), the same
|
|
408
|
-
# "configured for me, in the projection" standard every other
|
|
409
|
-
# piece of generated deploy config already holds to. Matches
|
|
410
|
-
# `stack_name` above exactly — one name, two places it's read
|
|
411
|
-
# from, never out of sync since both derive from `infra_name`.
|
|
412
|
-
FunctionName: #{stack_name}
|
|
413
|
-
# EXPLICIT, not omitted -- `sam build WebFunction` and
|
|
414
|
-
# `sam build --use-container WebFunction` run as TWO SEPARATE
|
|
415
|
-
# single-resource invocations (deploy:'s own comment on why:
|
|
416
|
-
# cargo-lambda cross-compiles on this host directly, pg's
|
|
417
|
-
# native extension needs the container -- `--use-container` is
|
|
418
|
-
# a global sam build flag, can't be scoped per-resource). Each
|
|
419
|
-
# invocation regenerates .aws-sam/build/template.yaml from
|
|
420
|
-
# THIS source template for every resource, not just the one it
|
|
421
|
-
# builds -- a resource with no CodeUri here reverts to
|
|
422
|
-
# CodeUri-less after the SECOND invocation, and `sam deploy`
|
|
423
|
-
# then defaults a missing CodeUri to the template's own
|
|
424
|
-
# containing directory (deploy/#{infra_name}/ itself),
|
|
425
|
-
# zipping the whole project tree -- including WebFunction's
|
|
426
|
-
# own source -- as this function's code. A real, live deploy
|
|
427
|
-
# shipped exactly that broken artifact (confirmed via
|
|
428
|
-
# `aws lambda get-function` + downloading the actual deployed
|
|
429
|
-
# zip). Pinning CodeUri here survives that regeneration: it
|
|
430
|
-
# still resolves to `.aws-sam/build/#{logical_id}/`, where
|
|
431
|
-
# build-#{logical_id}'s own `cp ... $(ARTIFACTS_DIR)` already
|
|
432
|
-
# placed the real bootstrap+wasm, regardless of which of the
|
|
433
|
-
# two sam build calls ran most recently.
|
|
434
|
-
# "." NOT "#{logical_id}" -- SAM's Makefile build workflow
|
|
435
|
-
# requires the Makefile to live INSIDE CodeUri itself
|
|
436
|
-
# (confirmed live: "Makefile not found at
|
|
437
|
-
# .../WebFunction/Makefile" when CodeUri named a
|
|
438
|
-
# directory of its own). This project's one Makefile lives at
|
|
439
|
-
# the deploy directory's own root, alongside this template --
|
|
440
|
-
# "." is where it actually is.
|
|
441
|
-
CodeUri: .
|
|
442
|
-
PackageType: Zip
|
|
443
|
-
Runtime: provided.al2023
|
|
444
|
-
Architectures: [arm64]
|
|
445
|
-
Handler: bootstrap
|
|
446
|
-
MemorySize: #{memory}
|
|
447
|
-
Timeout: #{timeout}
|
|
448
|
-
Environment:
|
|
449
|
-
Variables:
|
|
450
|
-
# NOT a `{{resolve:secretsmanager:...}}`-composed DATABASE_URL
|
|
451
|
-
# anymore (the WebFunction handler below still uses that
|
|
452
|
-
# mechanism, unchanged) -- that dynamic reference genuinely
|
|
453
|
-
# IS resolved fresh at deploy time and never rendered into
|
|
454
|
-
# this template, this tool's output, or the CloudFormation
|
|
455
|
-
# console. But once it resolves INTO a Lambda's own
|
|
456
|
-
# Environment.Variables entry, the resolved PLAINTEXT is
|
|
457
|
-
# stored in the function's OWN configuration:
|
|
458
|
-
# `lambda:GetFunctionConfiguration` returns it decrypted,
|
|
459
|
-
# and the Lambda console's Configuration tab displays it, to
|
|
460
|
-
# ANY principal holding read-only account access (AWS's own
|
|
461
|
-
# managed ReadOnlyAccess policy included) -- caught
|
|
462
|
-
# reviewing this template, not live; AWS's own guidance is
|
|
463
|
-
# to fetch a secret from Secrets Manager at RUNTIME instead,
|
|
464
|
-
# precisely to avoid this. DB_HOST/DB_NAME/DB_SECRET_ARN
|
|
465
|
-
# below are not secrets themselves -- an endpoint address, a
|
|
466
|
-
# database name, and a Secrets Manager ARN authenticate
|
|
467
|
-
# nothing on their own -- so sitting here as plain
|
|
468
|
-
# Environment.Variables values costs nothing; main.rs itself
|
|
469
|
-
# fetches the actual password from Secrets Manager at cold
|
|
470
|
-
# start, over the AWS SDK, and never hands it back to
|
|
471
|
-
# CloudFormation or Lambda's own configuration at all.
|
|
472
|
-
# #{logical_id}'s own least-privilege Policies grant (below,
|
|
473
|
-
# sibling of VpcConfig) is the one runtime IAM permission
|
|
474
|
-
# that fetch needs, scoped to this one secret ARN.
|
|
475
|
-
#{if shared
|
|
476
|
-
# Owner's live Endpoint/Secret, looked up into
|
|
477
|
-
# OwningDatabaseEndpoint/OwningDatabaseSecretArn.
|
|
478
|
-
"DB_HOST: !Ref OwningDatabaseEndpoint\n" \
|
|
479
|
-
" DB_NAME: #{owner_db_name}\n" \
|
|
480
|
-
" DB_SECRET_ARN: !Sub \"${OwningDatabaseSecretArn}\""
|
|
481
|
-
else
|
|
482
|
-
"DB_HOST: !GetAtt #{db_ref_id}.Endpoint.Address\n" \
|
|
483
|
-
" DB_NAME: #{db_name}\n" \
|
|
484
|
-
" DB_SECRET_ARN: !Sub \"${#{secret_sub}}\""
|
|
485
|
-
end}
|
|
486
|
-
# `domain_name`, NOT `infra_name` — this names the actual
|
|
487
|
-
# .wasm FILE `hecks build_wasm` writes (rust/dist/#{domain_name}.wasm,
|
|
488
|
-
# its own target_mod_name convention, a Rust-build-pipeline
|
|
489
|
-
# concern unrelated to AWS resource identity) and what the
|
|
490
|
-
# Makefile below actually bundles into the Lambda package
|
|
491
|
-
# under. Confirmed the hard way: pinning this to infra_name
|
|
492
|
-
# alongside the real AWS-identity fields left it looking for
|
|
493
|
-
# a file `hecks build_wasm` never produces.
|
|
494
|
-
HECKS_WASM_PATH: !Sub "/var/task/#{domain_name}.wasm"
|
|
495
|
-
# STATIC, not a deploy-time parameter — HECKS_DOMAIN is this
|
|
496
|
-
# domain's own declared name, known at generation time the
|
|
497
|
-
# same way DBName/HECKS_WASM_PATH already are. HECKS_ERA is
|
|
498
|
-
# "1" because that's what a FRESH database always gets:
|
|
499
|
-
# PostgresEra::LineageManager::EraResolver#check! mints era 1
|
|
500
|
-
# unconditionally the first time any Ruby process boots
|
|
501
|
-
# against an empty `hecks_eras` (era_resolver.rb's own
|
|
502
|
-
# `held.empty?` branch) — deterministic, not guessed. `make
|
|
503
|
-
# mint-era` (this directory's own Makefile) is what actually
|
|
504
|
-
# triggers that first boot; a LATER real schema evolution
|
|
505
|
-
# (era 2+) is a separate, later re-generation, not this one.
|
|
506
|
-
HECKS_DOMAIN: #{declared_domain_name}
|
|
507
|
-
# UNCONDITIONAL, not gated on `rust_web` — `rust/host/src/main.rs`'s
|
|
508
|
-
# own boot sequence reads `ir::ir().ok_or(...)?` for every domain
|
|
509
|
-
# regardless of web mode (era-lineage bookkeeping needs the IR, not
|
|
510
|
-
# just the optional in-process web UI), so a Shared-mode domain with
|
|
511
|
-
# no web layer still needs this sidecar path to boot at all. Previously
|
|
512
|
-
# gated on `rust_web`, which left every such domain's Lambda crashing
|
|
513
|
-
# on cold start — checkout.rs's own header flagged this exact
|
|
514
|
-
# contradiction, confirmed live against Banking's committed template.
|
|
515
|
-
HECKS_IR_PATH: !Sub "/var/task/#{domain_name}.ir.json"
|
|
516
|
-
HECKS_ERA: "1"#{hecks_schema ? %(\n HECKS_SCHEMA: #{hecks_schema}) : ""}#{rust_web && google_oauth_present ? <<~RUSTOAUTH.each_line.with_index.map { |l, i| i.zero? ? "\n " + l : " " + l }.join.rstrip : ""}
|
|
517
|
-
# NOT `{{resolve:secretsmanager:...}}` composing
|
|
518
|
-
# GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET/SESSION_SECRET
|
|
519
|
-
# directly anymore -- the identical GetFunctionConfiguration
|
|
520
|
-
# exposure DATABASE_URL's own comment above documents
|
|
521
|
-
# applied here too. main.rs's own cold start fetches both
|
|
522
|
-
# secrets itself and `set_var`s the real GOOGLE_CLIENT_ID/
|
|
523
|
-
# GOOGLE_CLIENT_SECRET/SESSION_SECRET keys before anything
|
|
524
|
-
# else runs -- auth.rs/web.rs still read those exact keys,
|
|
525
|
-
# unchanged. GOOGLE_OAUTH_SECRET_ID is a bare NAME (`make
|
|
526
|
-
# sync-google-oauth` owns #{stack_name}-web-google-oauth's
|
|
527
|
-
# whole lifecycle outside CloudFormation, straight from
|
|
528
|
-
# #{domain}'s own gitignored .env.local, never baked into
|
|
529
|
-
# this generated, git-tracked template as plaintext) --
|
|
530
|
-
# GetSecretValue accepts a name directly, no ARN needed.
|
|
531
|
-
GOOGLE_OAUTH_SECRET_ID: #{stack_name}-web-google-oauth
|
|
532
|
-
GOOGLE_REDIRECT_URI: !Sub "${WebRedirectBaseUrl}/auth/google/callback"
|
|
533
|
-
SESSION_SECRET_ARN: !Sub "${#{logical_id}SessionSecret}"
|
|
534
|
-
RUSTOAUTH
|
|
535
|
-
# LEAST-PRIVILEGE -- #{logical_id}'s own execution role is
|
|
536
|
-
# otherwise SAM's bare default (logging only); this is the one
|
|
537
|
-
# runtime AWS permission the DB_SECRET_ARN handling above needs,
|
|
538
|
-
# scoped to the single secret ARN this stack itself depends on,
|
|
539
|
-
# never `Resource: "*"`.
|
|
540
|
-
Policies:
|
|
541
|
-
- Statement:
|
|
542
|
-
- Effect: Allow
|
|
543
|
-
Action: secretsmanager:GetSecretValue
|
|
544
|
-
Resource: !Sub "${#{db_secret_ref}}"
|
|
545
|
-
#{rust_web && google_oauth_present ? <<~OAUTHPOLICY.each_line.with_index.map { |l, i| i.zero? ? l : " " + l }.join.rstrip : ""}
|
|
546
|
-
- Statement:
|
|
547
|
-
- Effect: Allow
|
|
548
|
-
Action: secretsmanager:GetSecretValue
|
|
549
|
-
# NAME-BASED, not `!Ref`/`!GetAtt` -- this secret is
|
|
550
|
-
# never a stack resource (RUSTOAUTH's own Environment
|
|
551
|
-
# comment, above, has the full story), so there is no
|
|
552
|
-
# exact ARN to point at. The trailing `-*` is AWS's
|
|
553
|
-
# own documented pattern for granting a secret known
|
|
554
|
-
# only by name: every real ARN Secrets Manager mints
|
|
555
|
-
# is this name plus a random 6-character suffix,
|
|
556
|
-
# which can't be predicted at template-render time.
|
|
557
|
-
Resource: !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:#{stack_name}-web-google-oauth-*"
|
|
558
|
-
- Statement:
|
|
559
|
-
- Effect: Allow
|
|
560
|
-
Action: secretsmanager:GetSecretValue
|
|
561
|
-
Resource: !Sub "${#{logical_id}SessionSecret}"
|
|
562
|
-
OAUTHPOLICY
|
|
563
|
-
# TMPL:cross_domain_lambda_policies
|
|
564
|
-
VpcConfig:
|
|
565
|
-
#{if shared
|
|
566
|
-
"SubnetIds: [!Ref OwningSubnetAId, !Ref OwningSubnetBId]\n SecurityGroupIds: [!Ref OwningSecurityGroupId]"
|
|
567
|
-
else
|
|
568
|
-
"SubnetIds: [!Ref #{db_id}SubnetA, !Ref #{db_id}SubnetB]\n SecurityGroupIds: [!Ref #{logical_id}SecurityGroup]"
|
|
569
|
-
end}
|
|
570
|
-
# AWS_IAM by default — this dispatches real domain commands
|
|
571
|
-
# (cap-table/governance-shaped data);
|
|
572
|
-
# an unauthenticated public URL is the wrong default for that,
|
|
573
|
-
# even though it's the simpler one to demo with. NONE only when
|
|
574
|
-
# `web "Rust"` (rust/host/src/web.rs) makes this the public web
|
|
575
|
-
# UI itself — safe even though the SAME Function URL also
|
|
576
|
-
# carries the internal {"verb"}/{"read"} dispatch shapes,
|
|
577
|
-
# because a Function-URL HTTP event can never be crafted into
|
|
578
|
-
# that raw top-level shape (Function URLs always wrap the real
|
|
579
|
-
# HTTP body inside requestContext.http/rawPath/event.body,
|
|
580
|
-
# unconditionally); that raw shape is reachable only through
|
|
581
|
-
# the AWS SDK's own IAM-authenticated Invoke API, which never
|
|
582
|
-
# goes through a Function URL at all.
|
|
583
|
-
FunctionUrlConfig:
|
|
584
|
-
AuthType: #{rust_web ? "NONE" : "AWS_IAM"}
|
|
585
|
-
#{web_handler_present ? <<~WEB.each_line.map { |l| " " + l }.join : ""}
|
|
586
|
-
# THE WEB APP'S OWN LAMBDA — opt-in, see `web_handler_present` above.
|
|
587
|
-
# VPC-attached for the SAME reason #{logical_id} is (Member's own
|
|
588
|
-
# persistence needs the private RDS instance directly, permanently —
|
|
589
|
-
# the hecksagon's own comment on why), sharing
|
|
590
|
-
# #{logical_id}SecurityGroup rather than minting a second one, since
|
|
591
|
-
# both need the identical egress-to-DB rule. That VPC attachment
|
|
592
|
-
# ALSO cuts off the public internet by default (this stack's own "NO
|
|
593
|
-
# NAT GATEWAY" design) — but this function has to reach
|
|
594
|
-
# #{logical_id} itself via `lambda:InvokeFunction`, a public AWS API
|
|
595
|
-
# call. #{web_logical_id}LambdaEndpoint below is the targeted fix: a
|
|
596
|
-
# VPC Interface Endpoint for the Lambda service ONLY (not a NAT
|
|
597
|
-
# Gateway) — a few dollars a month, not a general internet route.
|
|
598
|
-
#{db_id}LambdaEndpointSecurityGroup:
|
|
599
|
-
Type: AWS::EC2::SecurityGroup
|
|
600
|
-
Properties:
|
|
601
|
-
VpcId: !Ref #{db_id}Vpc
|
|
602
|
-
# EC2's own GroupDescription character set excludes apostrophes
|
|
603
|
-
# too, not just em-dashes -- "this stack's own" was a real,
|
|
604
|
-
# live rejection ("Invalid security group description").
|
|
605
|
-
GroupDescription: #{db_id}LambdaEndpoint - HTTPS from this stacks own Lambdas only
|
|
606
|
-
|
|
607
|
-
#{db_id}LambdaEndpointIngress:
|
|
608
|
-
Type: AWS::EC2::SecurityGroupIngress
|
|
609
|
-
Properties:
|
|
610
|
-
GroupId: !Ref #{db_id}LambdaEndpointSecurityGroup
|
|
611
|
-
IpProtocol: tcp
|
|
612
|
-
FromPort: 443
|
|
613
|
-
ToPort: 443
|
|
614
|
-
SourceSecurityGroupId: !Ref #{logical_id}SecurityGroup
|
|
615
|
-
|
|
616
|
-
#{db_id}LambdaEndpoint:
|
|
617
|
-
Type: AWS::EC2::VPCEndpoint
|
|
618
|
-
Properties:
|
|
619
|
-
VpcId: !Ref #{db_id}Vpc
|
|
620
|
-
ServiceName: !Sub "com.amazonaws.${AWS::Region}.lambda"
|
|
621
|
-
VpcEndpointType: Interface
|
|
622
|
-
PrivateDnsEnabled: true
|
|
623
|
-
SubnetIds: [!Ref #{db_id}SubnetA, !Ref #{db_id}SubnetB]
|
|
624
|
-
SecurityGroupIds: [!Ref #{db_id}LambdaEndpointSecurityGroup]
|
|
625
|
-
|
|
626
|
-
# Auto-generated, never typed or seen — the SAME
|
|
627
|
-
# ManageMasterUserPassword pattern #{db_id} already uses for its own
|
|
628
|
-
# password, applied to Sinatra's own session-signing secret instead.
|
|
629
|
-
#{web_logical_id}SessionSecret:
|
|
630
|
-
Type: AWS::SecretsManager::Secret
|
|
631
|
-
Properties:
|
|
632
|
-
GenerateSecretString:
|
|
633
|
-
SecretStringTemplate: '{}'
|
|
634
|
-
GenerateStringKey: session_secret
|
|
635
|
-
PasswordLength: 64
|
|
636
|
-
ExcludePunctuation: true
|
|
637
|
-
|
|
638
|
-
#{webhook_secret_env ? <<~WEBHOOKSECRET.rstrip : ""}
|
|
639
|
-
# Auto-generated, never typed or seen — the SAME
|
|
640
|
-
# ManageMasterUserPassword/SessionSecret pattern above, applied to
|
|
641
|
-
# `secret_env`'s own webhook secret instead (deploy_settings' own
|
|
642
|
-
# `secret_env`, read far above). #{web_logical_id}'s own
|
|
643
|
-
# `lambda_handler.rb` fetches this by ARN at cold start (the
|
|
644
|
-
# `#{webhook_secret_env}_ARN` Environment variable below) and exposes
|
|
645
|
-
# its plaintext under `ENV["#{webhook_secret_env}"]` itself — the
|
|
646
|
-
# SAME two-step indirection DB_SECRET_ARN/SESSION_SECRET_ARN already
|
|
647
|
-
# use, and for the identical reason (this Environment block's own
|
|
648
|
-
# header comment on why a resolved plaintext must never land in
|
|
649
|
-
# Lambda's own configuration).
|
|
650
|
-
#{web_logical_id}WebhookSecret:
|
|
651
|
-
Type: AWS::SecretsManager::Secret
|
|
652
|
-
Properties:
|
|
653
|
-
GenerateSecretString:
|
|
654
|
-
SecretStringTemplate: '{}'
|
|
655
|
-
GenerateStringKey: value
|
|
656
|
-
PasswordLength: 64
|
|
657
|
-
ExcludePunctuation: true
|
|
658
|
-
|
|
659
|
-
WEBHOOKSECRET
|
|
660
|
-
#{web_logical_id}:
|
|
661
|
-
Type: AWS::Serverless::Function
|
|
662
|
-
# NO Metadata.BuildMethod — "bundler" isn't a real SAM value (a
|
|
663
|
-
# real, live UnsupportedBuilderException caught this); `Runtime:
|
|
664
|
-
# ruby3.2` alone is enough for SAM to select its own built-in Ruby
|
|
665
|
-
# bundler workflow automatically. #{logical_id}'s own `BuildMethod:
|
|
666
|
-
# makefile` above is different: rust/host isn't a runtime SAM
|
|
667
|
-
# knows how to build at all, so it needs an explicit custom
|
|
668
|
-
# workflow — this function doesn't. STAYS `ruby3.2` deliberately,
|
|
669
|
-
# even though `sam validate --lint` now flags it deprecated
|
|
670
|
-
# (2026-03-31, creation disabled 2027-02-01, still comfortably
|
|
671
|
-
# ahead of today) — `patch-pg-native`'s own build-ruby3.2 container
|
|
672
|
-
# image and `lib/3.2/pg_ext.so` path glob (this Makefile, below)
|
|
673
|
-
# are BOTH hardcoded to this exact runtime; bumping Runtime alone
|
|
674
|
-
# without reworking that whole from-source pg build for a newer
|
|
675
|
-
# image is a real, separate undertaking (untested container image
|
|
676
|
-
# availability, a different vendor/bundle native-extension path)
|
|
677
|
-
# out of scope here. Matches the live, currently-
|
|
678
|
-
# deployed choice exactly, so both consumers of this one shared
|
|
679
|
-
# WebFunction code path stay on the same, PROVEN runtime.
|
|
680
|
-
Properties:
|
|
681
|
-
FunctionName: #{stack_name}-web
|
|
682
|
-
Runtime: ruby3.2
|
|
683
|
-
Architectures: [arm64]
|
|
684
|
-
# THREE parts (file.Class.method), not two — aws-lambda-ric's own
|
|
685
|
-
# 2-part form calls a plain TOP-LEVEL function via __send__, not
|
|
686
|
-
# a module method; #{web_handler_relpath}.rb's own comment on
|
|
687
|
-
# why its module is named the way it is explains the rest — a
|
|
688
|
-
# real, live "LambdaHandler is not a module" caught both halves
|
|
689
|
-
# of this the hard way. `web_handler_relpath` (not a bare
|
|
690
|
-
# "lambda_handler") whenever `dispatch "None"` moved CodeUri off
|
|
691
|
-
# #{domain} itself — see that variable's own comment, above.
|
|
692
|
-
Handler: #{web_handler_relpath}.#{webhook_handler_module}.lambda_handler
|
|
693
|
-
CodeUri: #{web_code_uri}
|
|
694
|
-
MemorySize: 512
|
|
695
|
-
Timeout: 15
|
|
696
|
-
Environment:
|
|
697
|
-
Variables:
|
|
698
|
-
DOMAIN_ROOT: /var/task
|
|
699
|
-
# NOT `declared_domain_name` — `Adapters::Lambda`/
|
|
700
|
-
# `RemoteDispatcher` both read this to compute WHICH
|
|
701
|
-
# deployed Lambda function to invoke (`hecks-\#{...}`,
|
|
702
|
-
# matching this stack's own name), a question `infra_name`
|
|
703
|
-
# answers and a domain's own business identity does not.
|
|
704
|
-
# Real, live, and confirmed the hard way: this was still
|
|
705
|
-
# wired to `declared_domain_name` when a client app
|
|
706
|
-
# first deployed, and WebFunction immediately threw
|
|
707
|
-
# `ResourceNotFoundException: Function not found:
|
|
708
|
-
# hecks-<domain>` on every read AND on the
|
|
709
|
-
# Google OAuth callback itself — a real signed-in user hit
|
|
710
|
-
# this within minutes of the deploy that introduced it.
|
|
711
|
-
DOMAIN_NAME: #{infra_name}
|
|
712
|
-
HECKS_LAMBDA_ROUTING: "true"
|
|
713
|
-
HECKS_LAMBDA_REGION: #{region}#{hecks_schema ? %(\n HECKS_SCHEMA: #{hecks_schema}) : ""}
|
|
714
|
-
# SAME setting #{domain}'s own fly.toml/Dockerfile already
|
|
715
|
-
# sets for the Fly deployment this one replaces — Sinatra's
|
|
716
|
-
# own `host_authorization` default is only PERMISSIVE in
|
|
717
|
-
# production mode (an empty `permitted_hosts` list means
|
|
718
|
-
# "allow any host" — checked directly in rack-protection's
|
|
719
|
-
# own source); its DEVELOPMENT default is the restrictive
|
|
720
|
-
# one (only localhost/.test), which is what Sinatra falls
|
|
721
|
-
# back to when NEITHER RACK_ENV nor APP_ENV is set. A real,
|
|
722
|
-
# live "Host not permitted" 403 against this function's own
|
|
723
|
-
# unpredictable *.lambda-url.*.on.aws hostname caught the
|
|
724
|
-
# gap — this function's Environment never set either var.
|
|
725
|
-
RACK_ENV: production
|
|
726
|
-
# The precompiled `pg` gem's aarch64-linux native extension
|
|
727
|
-
# needs GLIBC 2.29+; Lambda's ruby3.2 MANAGED runtime is
|
|
728
|
-
# Amazon Linux 2 (glibc 2.26) -- a real, live "Init<NameError>:
|
|
729
|
-
# uninitialized constant PG::Error" caught this (rescuing
|
|
730
|
-
# PG::Error itself failed to resolve, because pg's own
|
|
731
|
-
# require died before reaching the file that defines it).
|
|
732
|
-
# `make deploy`'s own `patch-pg-native` step (this directory's
|
|
733
|
-
# Makefile) replaces the broken precompiled extension with one
|
|
734
|
-
# compiled from source, in-container, against a real
|
|
735
|
-
# SSL-enabled libpq -- and drops that libpq's .so beside it at
|
|
736
|
-
# /var/task/lib, found by the dynamic loader with NO explicit
|
|
737
|
-
# LD_LIBRARY_PATH override needed: the base ruby3.2 image's
|
|
738
|
-
# own baked-in default (`docker inspect
|
|
739
|
-
# public.ecr.aws/lambda/ruby:3.2-arm64`) is already
|
|
740
|
-
# "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib"
|
|
741
|
-
# -- setting LD_LIBRARY_PATH here as a Lambda env var
|
|
742
|
-
# REPLACES that whole list rather than extending it (a real,
|
|
743
|
-
# live gotcha: Lambda env vars always fully override a
|
|
744
|
-
# same-named baked-in image env, never merge with it), which
|
|
745
|
-
# is exactly what broke Ruby's OWN interpreter startup
|
|
746
|
-
# ("libcrypt.so.1: cannot open shared object file") the one
|
|
747
|
-
# time this was set explicitly, before libpq.so.5.16 itself
|
|
748
|
-
# ever got a chance to matter.
|
|
749
|
-
# Member's own permanent Postgres connection — the SAME RDS
|
|
750
|
-
# instance #{logical_id} itself writes rust/host's flat
|
|
751
|
-
# journal into, different database engine role (Ruby's own
|
|
752
|
-
# era/lineage schema, not rust/host's), never the real
|
|
753
|
-
# Fly-hosted Postgres this whole effort is retiring away from.
|
|
754
|
-
#
|
|
755
|
-
# GENERATOR-ONLY FIX, NOT YET SAFE TO DEPLOY — DB_HOST/DB_NAME/
|
|
756
|
-
# DB_SECRET_ARN/SESSION_SECRET_ARN/GOOGLE_OAUTH_SECRET_ID below
|
|
757
|
-
# replace the SAME `{{resolve:secretsmanager:...}}`-into-
|
|
758
|
-
# Environment.Variables shape rust/host's own main.rs (this
|
|
759
|
-
# template's own #{logical_id}) used to have -- readable in
|
|
760
|
-
# plaintext by any read-only account principal via
|
|
761
|
-
# lambda:GetFunctionConfiguration, the exact exposure
|
|
762
|
-
# #{logical_id}'s own comment above the Policies section
|
|
763
|
-
# documents. But THIS function's consumer (`lambda_handler.rb`)
|
|
764
|
-
# is not a file this generator writes or this repo carries --
|
|
765
|
-
# it lives in the deploying app's own domain directory (real,
|
|
766
|
-
# live example: a Sinatra app), outside this
|
|
767
|
-
# codebase entirely. Redeploying a domain with an existing
|
|
768
|
-
# `lambda_handler.rb` BEFORE that handler is updated to fetch
|
|
769
|
-
# these itself (`aws-sdk-secretsmanager`, not yet in this
|
|
770
|
-
# project's own Gemfile) breaks it: it still expects
|
|
771
|
-
# `ENV["DATABASE_URL"]`/`ENV["SESSION_SECRET"]`/
|
|
772
|
-
# `ENV["GOOGLE_CLIENT_ID"]`/`ENV["GOOGLE_CLIENT_SECRET"]`
|
|
773
|
-
# already resolved, and none of those keys exist below anymore.
|
|
774
|
-
DB_HOST: !GetAtt #{db_ref_id}.Endpoint.Address
|
|
775
|
-
DB_NAME: #{db_name}
|
|
776
|
-
DB_SECRET_ARN: !Sub "${#{secret_sub}}"
|
|
777
|
-
SESSION_SECRET_ARN: !Sub "${#{web_logical_id}SessionSecret}"#{webhook_secret_env ? %(\n #{webhook_secret_env}_ARN: !Sub "${#{web_logical_id}WebhookSecret}") : ""}
|
|
778
|
-
#{web_google_oauth_env_yaml}
|
|
779
|
-
VpcConfig:
|
|
780
|
-
SubnetIds: [!Ref #{db_id}SubnetA, !Ref #{db_id}SubnetB]
|
|
781
|
-
SecurityGroupIds: [!Ref #{logical_id}SecurityGroup]
|
|
782
|
-
# LEAST-PRIVILEGE, scoped to #{logical_id}'s own ARN specifically
|
|
783
|
-
# (SAM's own LambdaInvokePolicy template) — WebFunctionRole is
|
|
784
|
-
# otherwise SAM's bare default execution role (logging only), and
|
|
785
|
-
# had NO lambda:InvokeFunction permission at all until this: a
|
|
786
|
-
# real, live AccessDeniedException caught it, the same request
|
|
787
|
-
# that also caught the wrong-function-name bug just above
|
|
788
|
-
# (WebFunctionRole is not authorized... on resource
|
|
789
|
-
# hecks-task — this stack has never had ANY policy granting
|
|
790
|
-
# that action, so the fix isn't complete without adding one).
|
|
791
|
-
# THE SAME secretsmanager:GetSecretValue GRANTS #{logical_id}'s
|
|
792
|
-
# own Policies section (above) needed, once this function's own
|
|
793
|
-
# `lambda_handler.rb` fetches DB_SECRET_ARN/SESSION_SECRET_ARN/
|
|
794
|
-
# GOOGLE_OAUTH_SECRET_ID itself instead of relying on a resolved
|
|
795
|
-
# Environment.Variables value -- see this Environment block's own
|
|
796
|
-
# comment for why that consumer-side change isn't in this repo.
|
|
797
|
-
Policies:
|
|
798
|
-
- LambdaInvokePolicy:
|
|
799
|
-
FunctionName: !Ref #{logical_id}
|
|
800
|
-
- Statement:
|
|
801
|
-
- Effect: Allow
|
|
802
|
-
Action: secretsmanager:GetSecretValue
|
|
803
|
-
Resource: !Sub "${#{secret_sub}}"
|
|
804
|
-
- Statement:
|
|
805
|
-
- Effect: Allow
|
|
806
|
-
Action: secretsmanager:GetSecretValue
|
|
807
|
-
Resource: !Sub "${#{web_logical_id}SessionSecret}"#{webhook_secret_env ? %(\n - Statement:\n - Effect: Allow\n Action: secretsmanager:GetSecretValue\n Resource: !Sub "${#{web_logical_id}WebhookSecret}") : ""}
|
|
808
|
-
#{google_oauth_present ? <<~WEBOAUTHPOLICY.each_line.with_index.map { |l, i| i.zero? ? l : " " + l }.join.rstrip : ""}
|
|
809
|
-
- Statement:
|
|
810
|
-
- Effect: Allow
|
|
811
|
-
Action: secretsmanager:GetSecretValue
|
|
812
|
-
Resource: !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:#{stack_name}-web-google-oauth-*"
|
|
813
|
-
WEBOAUTHPOLICY
|
|
814
|
-
# NONE, not AWS_IAM — real users need to reach sign-in over
|
|
815
|
-
# plain HTTPS, unlike #{logical_id}'s own AWS_IAM-protected URL
|
|
816
|
-
# (this function is the ONLY thing meant to call that one).
|
|
817
|
-
FunctionUrlConfig:
|
|
818
|
-
AuthType: NONE
|
|
819
|
-
WEB
|
|
820
|
-
|
|
821
|
-
Outputs:
|
|
822
|
-
FunctionUrl:
|
|
823
|
-
Value: !GetAtt #{logical_id}Url.FunctionUrl#{web_handler_present ? "\n WebFunctionUrl:\n Value: !GetAtt #{web_logical_id}Url.FunctionUrl" : ""}
|
|
824
|
-
# stack_outputs (hecks deploy project, above the heredocs) is the ONE
|
|
825
|
-
# place these four names live — DatabaseEndpoint/DatabaseSecretArn
|
|
826
|
-
# feed `make mint-era`'s own boot step directly; VpcId/
|
|
827
|
-
# DbSecurityGroupId ALSO feed bastion.yaml's own Parameters (a
|
|
828
|
-
# separate, sibling stack, created only for the few minutes a mint
|
|
829
|
-
# takes and destroyed right after, attaching into the SAME private
|
|
830
|
-
# network without this main template ever knowing a bastion
|
|
831
|
-
# exists) and the Makefile's own --parameter-overrides. Rename a
|
|
832
|
-
# key in that table and every consumer follows; reference one that
|
|
833
|
-
# doesn't exist there and hecks deploy project refuses before
|
|
834
|
-
# writing a single file, instead of a bastion.yaml whose Parameter
|
|
835
|
-
# nothing could ever fill. (`PrivateSubnetId` used to sit here too
|
|
836
|
-
# — dropped, nothing ever consumed it; bastion.yaml mints its own
|
|
837
|
-
# subnet instead.)
|
|
838
|
-
#{stack_outputs.map { |o| "#{o[:key]}:\n Value: #{o[:ref]}" }.join("\n ")}
|
|
839
|
-
YAML
|
|
840
|
-
|
|
841
|
-
# Spliced in after the heredoc renders, not interpolated inside it —
|
|
842
|
-
# a `#{...}` marker at column 0 would drag the heredoc's own dedent
|
|
843
|
-
# computation to zero and leave every other line un-stripped.
|
|
844
|
-
template_yaml = template_yaml.sub(/^([ \t]*)# TMPL:cross_domain_lambda_policies\n/) { Shared.cross_domain_invoke_policy_yaml(cross_domain_lambda_targets, $1) }
|
|
845
|
-
|
|
846
|
-
# Operates on the already-rendered string — `pii_cloudfront_yaml`
|
|
847
|
-
# builds its own correctly-indented text, so nothing here interacts
|
|
848
|
-
# with a heredoc's own dedent computation.
|
|
849
|
-
if pii_detected
|
|
850
|
-
fronted_logical_id = web_handler_present ? web_logical_id : logical_id
|
|
851
|
-
use_oac = !web_handler_present && !rust_web
|
|
852
|
-
|
|
853
|
-
pii_resources = pii_cloudfront_yaml(
|
|
854
|
-
fronted_logical_id: fronted_logical_id, use_oac: use_oac,
|
|
855
|
-
geo_restriction_type: geo_restriction_type, geo_restriction_countries: geo_restriction_countries
|
|
856
|
-
)
|
|
857
|
-
template_yaml = template_yaml.sub(/^Outputs:\n/) { "#{pii_resources}Outputs:\n PiiDistributionDomainName:\n Value: !GetAtt PiiDistribution.DomainName\n" }
|
|
858
|
-
end
|
|
859
|
-
|
|
860
|
-
# Surgical removal, post-render, not a nested heredoc — a conditional
|
|
861
|
-
# nested heredoc broke embedded multi-line `#{shared ? ... : ...}`
|
|
862
|
-
# strings inside the resource block, which assume exactly one
|
|
863
|
-
# reindentation layer (confirmed regenerating examples/banking).
|
|
864
|
-
if dispatch_none
|
|
865
|
-
# Non-greedy through the resource's own FunctionUrlConfig/AuthType
|
|
866
|
-
# closing pair, so WebFunction's own later "AuthType:" can't match.
|
|
867
|
-
template_yaml = template_yaml.sub(
|
|
868
|
-
/^ #{Regexp.escape(logical_id)}:\n.*?\n AuthType: (?:NONE|AWS_IAM)\n/m, ""
|
|
869
|
-
)
|
|
870
|
-
# Removes the now-dangling `!Ref` to the resource just deleted above
|
|
871
|
-
# — SAM refuses an unresolved Ref at package time, not silently.
|
|
872
|
-
template_yaml = template_yaml.sub(
|
|
873
|
-
/^ - LambdaInvokePolicy:\n FunctionName: !Ref #{Regexp.escape(logical_id)}\n/, ""
|
|
874
|
-
)
|
|
875
|
-
# WebFunction's own URL becomes the stack's only "FunctionUrl".
|
|
876
|
-
template_yaml = template_yaml.sub(
|
|
877
|
-
/^ FunctionUrl:\n Value: !GetAtt #{Regexp.escape(logical_id)}Url\.FunctionUrl\n WebFunctionUrl:\n Value: !GetAtt #{Regexp.escape(web_logical_id)}Url\.FunctionUrl\n/,
|
|
878
|
-
" FunctionUrl:\n Value: !GetAtt #{web_logical_id}Url.FunctionUrl\n"
|
|
879
|
-
)
|
|
880
|
-
end
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
# A separate, sibling stack for the few minutes `make mint-era`
|
|
885
|
-
# needs it, then deleted — SSM Session Manager only, never SSH, no
|
|
886
|
-
# inbound rule at all. Skipped when `shared`: era-minting reuses the
|
|
887
|
-
# owner's own standing infrastructure instead.
|
|
888
|
-
bastion_yaml = shared ? nil : Shared.bastion_yaml(
|
|
889
|
-
domain: domain, infra_name: infra_name, stack_name: stack_name, db_id: db_id,
|
|
890
|
-
google_oauth_present: google_oauth_present, bastion_parameters: bastion_parameters
|
|
891
|
-
)
|
|
892
|
-
|
|
893
|
-
# A top-level variable, not inlined in the Makefile heredoc below —
|
|
894
|
-
# Make recipe lines need a literal leading tab, and a nested
|
|
895
|
-
# interpolation's re-indent pass would corrupt that detection.
|
|
896
|
-
mint_era_recipe =
|
|
897
|
-
if shared
|
|
898
|
-
<<~SHAREDMINT.rstrip
|
|
899
|
-
# NOT AUTOMATED YET for a Shared-mode domain — it has no RDS/VPC
|
|
900
|
-
# of its own to stand a temporary bastion next to (bastion.yaml
|
|
901
|
-
# itself isn't even generated here — hecks deploy project's own
|
|
902
|
-
# comment on why). Minting era 1 for #{infra_name}'s own schema
|
|
903
|
-
# needs a tunnel to #{owner_domain_name}'s ALREADY-EXISTING RDS
|
|
904
|
-
# instance instead -- reuse #{owner_domain_name}'s own deploy
|
|
905
|
-
# directory's `make mint-era` machinery (or an already-open
|
|
906
|
-
# tunnel to it) to run this domain's own boot against
|
|
907
|
-
# `postgres://...@<tunnel-host>:<port>/#{owner_db_name}` with
|
|
908
|
-
# `schema: #{infra_name.inspect}` in LineageManager.check!'s own
|
|
909
|
-
# settings -- the exact same call this target runs automatically
|
|
910
|
-
# for a domain with its own dedicated instance.
|
|
911
|
-
#
|
|
912
|
-
# IF THIS DOMAIN VENDORS/ATTACHES ANOTHER BLUEBOOK (attaches,
|
|
913
|
-
# attaches ... from: :vendor), `check!` alone leaves THAT chapter's own
|
|
914
|
-
# aggregates with no snapshot table at all -- it only provisions the
|
|
915
|
-
# ONE bluebook it's called with (era_resolver.rb's own `bluebook.
|
|
916
|
-
# aggregates.each`), never the whole registry. Found live minting
|
|
917
|
-
# a client site's own era: `Payments::Payment.Initiate` refused with
|
|
918
|
-
# "relation \\"payment_head_snapshot_1\\" does not exist" the first
|
|
919
|
-
# time this domain's own checkout route ran for real. The automated
|
|
920
|
-
# (non-Shared) recipe below now does this correctly for every OTHER
|
|
921
|
-
# loaded bluebook, using ONE `Lineage` keyed by THIS domain's own
|
|
922
|
-
# name (never each chapter's own -- a vendored chapter's aggregates
|
|
923
|
-
# share THIS domain's single `hecks_journal_#{infra_name}` and
|
|
924
|
-
# snapshot tables, they do not get a separate journal of their own).
|
|
925
|
-
# Reproduce the same shape manually here: after `LineageManager.
|
|
926
|
-
# check!` returns, `db = PostgresEra.connect_for(bluebook.name,
|
|
927
|
-
# settings)`, `lineage = Lineage.new(db, bluebook.name)`, then for
|
|
928
|
-
# every OTHER loaded bluebook's own aggregates, `lineage.
|
|
929
|
-
# ensure_first_head!(aggregate.storage_name)`.
|
|
930
|
-
#
|
|
931
|
-
# EXIT 0, NOT 1 -- `deploy:`'s own last line (below) always chains
|
|
932
|
-
# `$(MAKE) mint-era` unconditionally, even for a Shared-mode domain,
|
|
933
|
-
# so a nonzero exit here used to mean a fully successful `sam
|
|
934
|
-
# deploy` still left `make deploy` exiting nonzero -- confirmed
|
|
935
|
-
# live: CI and any scripted caller read every Shared-mode deploy as
|
|
936
|
-
# failed, which trains operators to ignore a red `make deploy` on
|
|
937
|
-
# exactly the domains where a real failure most needs to stand out.
|
|
938
|
-
# This step never fails at its own job (it has no automated job to
|
|
939
|
-
# fail at -- it only reports that a manual step remains), so it
|
|
940
|
-
# reports success and leaves the manual-step reminder in the echo
|
|
941
|
-
# text above, not in the exit code. A human running `make mint-era`
|
|
942
|
-
# directly still SEES the same message; they just don't get a
|
|
943
|
-
# misleading "command failed" on top of it either.
|
|
944
|
-
\t@echo "mint-era isn't automated yet for a Shared-mode domain (database \\"Shared\\") -- see this target's own comment in the generated Makefile for the manual path through #{owner_domain_name}'s own tunnel. This is NOT a failure -- exiting 0 so a genuinely successful \\"make deploy\\" still reports success; era-minting for this domain remains a separate manual step."; \\
|
|
945
|
-
\texit 0
|
|
946
|
-
SHAREDMINT
|
|
947
|
-
else
|
|
948
|
-
<<~OWNMINT.rstrip
|
|
949
|
-
\t@echo "Looking up $(STACK)'s VPC/security group..."
|
|
950
|
-
# stack_outputs/bastion_parameters (hecks deploy project) are the ONE
|
|
951
|
-
# place these OutputKey strings and parameter names live — this eval
|
|
952
|
-
# chain and the --parameter-overrides line just below are both
|
|
953
|
-
# generated from the SAME table template.yaml's Outputs and
|
|
954
|
-
# bastion.yaml's Parameters already read from.
|
|
955
|
-
\t#{stack_outputs.map { |o| %($(eval #{o[:var]} := $(shell aws cloudformation describe-stacks --stack-name $(STACK) --query "Stacks[0].Outputs[?OutputKey=='#{o[:key]}'].OutputValue" --output text))) }.join("\n\t")}
|
|
956
|
-
\t@echo "Deploying the temporary bastion stack $(BASTION_STACK)..."
|
|
957
|
-
\taws cloudformation deploy --template-file bastion.yaml --stack-name $(BASTION_STACK) \\
|
|
958
|
-
\t\t--parameter-overrides #{bastion_parameters.map { |p| "#{p[:name]}=$(#{stack_outputs.find { |o| o[:key] == p[:from_output] }[:var]})" }.join(" ")} \\
|
|
959
|
-
\t\t--capabilities CAPABILITY_IAM
|
|
960
|
-
# ONE continuous shell invocation from here on (every line ends in
|
|
961
|
-
# \\, joining it to the next) — INSTANCE_ID is a SHELL variable, not
|
|
962
|
-
# a Make one: a Make-level $$(eval $$(shell ...)) expands at parse
|
|
963
|
-
# time, BEFORE the `aws cloudformation deploy` line above ever runs,
|
|
964
|
-
# which would make INSTANCE_ID permanently empty. It can only be
|
|
965
|
-
# computed here, after the bastion stack genuinely exists.
|
|
966
|
-
#
|
|
967
|
-
# A COMMENT LINE MUST NEVER SIT BETWEEN TWO \\-CONTINUED RECIPE
|
|
968
|
-
# LINES — confirmed live, the hard way: a `#`-prefixed line inserted
|
|
969
|
-
# between `DB_PASS=...; \\` and the line that used it silently
|
|
970
|
-
# corrupted Make's own assembly of this whole block into one shell
|
|
971
|
-
# script, and `DB_PASS` came out empty on the far side despite
|
|
972
|
-
# printing correctly one line earlier. Every explanatory comment for
|
|
973
|
-
# this whole chain belongs HERE, before it starts, same as this one —
|
|
974
|
-
# never spliced into the middle of it.
|
|
975
|
-
#
|
|
976
|
-
# BASTION TEARDOWN IS PART OF THIS SAME CHAIN, ALL THE WAY THROUGH —
|
|
977
|
-
# a prior version of this recipe ended at `exit $$BOOT_STATUS` with
|
|
978
|
-
# no trailing `\\`, which made Make treat the delete-stack lines
|
|
979
|
-
# after it as a SEPARATE recipe line, reached only on a zero exit.
|
|
980
|
-
# A failed boot (bad tunnel, bad `ruby -e` invocation, anything) left
|
|
981
|
-
# the bastion stack standing forever — including a live 5432 ingress
|
|
982
|
-
# rule punched into the PRODUCTION RDS security group. Teardown now
|
|
983
|
-
# runs unconditionally, with BOOT_STATUS captured up front and
|
|
984
|
-
# returned last, so `make mint-era`'s own exit code still reflects
|
|
985
|
-
# whether era 1 actually got minted.
|
|
986
|
-
#
|
|
987
|
-
# `LineageManager.check!` directly below, NOT `Hecks.boot` — also
|
|
988
|
-
# confirmed live (the very first real invoke: "relation
|
|
989
|
-
# \\"hecks_eras\\" does not exist", despite this step itself
|
|
990
|
-
# reporting success): `Hecks.boot` always dispatches persistence
|
|
991
|
-
# through whatever the domain's OWN .world file declares
|
|
992
|
-
# (`persisted_by("Heki")` for a domain that hasn't opted into
|
|
993
|
-
# Postgres for local dev), by EXPLICIT design — loader.rb's own
|
|
994
|
-
# comment: "persisted_by(\\"PostgresEra\\") is never inferred from
|
|
995
|
-
# anything else." `EraCheck.check!` (what `Hecks.boot` runs) is
|
|
996
|
-
# itself adapter-capability-gated on that same declaration and simply
|
|
997
|
-
# no-ops for a non-lineage-capable adapter — it never touches
|
|
998
|
-
# hecks_eras at all. The fix reuses the SAME loading pipeline
|
|
999
|
-
# `Loader.boot` itself does (`Ports::Loading.bootstrap`/
|
|
1000
|
-
# `load_library`/`load_project`/`load_domain`) to build a real,
|
|
1001
|
-
# fully-loaded registry (bluebook + any attaches attachments),
|
|
1002
|
-
# then calls straight into PostgresEra's own `LineageManager.check!`
|
|
1003
|
-
# with THIS deploy's actual `DATABASE_URL` — bypassing the world
|
|
1004
|
-
# file's own adapter choice entirely, the same targeted pattern
|
|
1005
|
-
# spec/adapters/driven/postgres_era/lineage_spec.rb's own `check!` helper
|
|
1006
|
-
# already uses.
|
|
1007
|
-
#
|
|
1008
|
-
# THE WHOLE TUNNEL IS RESTARTED PER ATTEMPT, not just the boot
|
|
1009
|
-
# check retried through one long-lived tunnel — confirmed live,
|
|
1010
|
-
# the hard way: an SSM port-forwarding session can start and then
|
|
1011
|
-
# exit again within the first few seconds (visible in its own log
|
|
1012
|
-
# as a session ID that both starts AND exits before the retries
|
|
1013
|
-
# even finish), not merely "slow to become ready". Retrying a
|
|
1014
|
-
# Postgres connection five times against a tunnel process that has
|
|
1015
|
-
# already died is retrying against a corpse — it fails identically
|
|
1016
|
-
# every time, for a different reason than the one the retry was
|
|
1017
|
-
# built for. Each attempt below kills whatever tunnel the LAST
|
|
1018
|
-
# attempt opened (a no-op the first time) and opens a fresh one,
|
|
1019
|
-
# so a dead session gets a real replacement, not just more patience.
|
|
1020
|
-
#
|
|
1021
|
-
# DB_PASS_URLENC, DERIVED RIGHT AFTER DB_PASS BELOW, IS WHAT ACTUALLY
|
|
1022
|
-
# GOES INTO THE `postgres://` URL FURTHER DOWN — not DB_PASS itself.
|
|
1023
|
-
# RDS/Secrets-Manager-generated passwords are not guaranteed free of
|
|
1024
|
-
# `%`, and libpq's own connection-URI parser treats a bare `%` as an
|
|
1025
|
-
# invalid token (percent-encoding is the one escape libpq's URI form
|
|
1026
|
-
# actually documents and decodes; a raw `%` is not) -- roughly 29% of
|
|
1027
|
-
# 32-char generated passwords contain at least one `%`, so this was
|
|
1028
|
-
# silently unreachable until it wasn't. `ERB::Util.url_encode`
|
|
1029
|
-
# percent-encodes everything outside RFC 3986's unreserved set,
|
|
1030
|
-
# exactly what a URI password component needs -- a strict superset
|
|
1031
|
-
# of the ExcludeCharacters the Aurora path below already excludes
|
|
1032
|
-
# (a no-op there), and the actual fix for the plain-RDS
|
|
1033
|
-
# ManageMasterUserPassword path, whose generated character set this
|
|
1034
|
-
# template has no control over at all. This comment sits HERE,
|
|
1035
|
-
# before the chain starts, on purpose -- see the note further below
|
|
1036
|
-
# on why a comment line can never be spliced between two
|
|
1037
|
-
# `\`-continued recipe lines.
|
|
1038
|
-
\t@INSTANCE_ID=$$(aws cloudformation describe-stacks --stack-name $(BASTION_STACK) --query "Stacks[0].Outputs[?OutputKey=='InstanceId'].OutputValue" --output text); \\
|
|
1039
|
-
\techo "Waiting for $$INSTANCE_ID to register with SSM..."; \\
|
|
1040
|
-
\tfor i in $$(seq 1 30); do \\
|
|
1041
|
-
\t\tSTATUS=$$(aws ssm describe-instance-information --filters "Key=InstanceIds,Values=$$INSTANCE_ID" --query "InstanceInformationList[0].PingStatus" --output text 2>/dev/null); \\
|
|
1042
|
-
\t\tif [ "$$STATUS" = "Online" ]; then break; fi; \\
|
|
1043
|
-
\t\tsleep 5; \\
|
|
1044
|
-
\tdone; \\
|
|
1045
|
-
\tDB_PASS=$$(aws secretsmanager get-secret-value --secret-id $(DB_SECRET_ARN) --query SecretString --output text | ruby -rjson -e 'print JSON.parse(STDIN.read)["password"]'); \\
|
|
1046
|
-
\tDB_PASS_URLENC=$$(ruby -rerb -e 'print ERB::Util.url_encode(ARGV[0])' "$$DB_PASS"); \\
|
|
1047
|
-
\tBOOT_STATUS=1; \\
|
|
1048
|
-
\tfor attempt in 1 2 3 4 5; do \\
|
|
1049
|
-
\t\tkill $$TUNNEL_PID 2>/dev/null; \\
|
|
1050
|
-
\t\techo "Opening an SSM tunnel to $(DB_HOST):5432 and minting era 1 (attempt $$attempt/5)..."; \\
|
|
1051
|
-
\t\taws ssm start-session --target $$INSTANCE_ID \\
|
|
1052
|
-
\t\t\t--document-name AWS-StartPortForwardingSessionToRemoteHost \\
|
|
1053
|
-
\t\t\t--parameters "{\\"host\\":[\\"$(DB_HOST)\\"],\\"portNumber\\":[\\"5432\\"],\\"localPortNumber\\":[\\"15432\\"]}" \\
|
|
1054
|
-
\t\t\t>/tmp/$(BASTION_STACK)-tunnel-$$attempt.log 2>&1 & \\
|
|
1055
|
-
\t\tTUNNEL_PID=$$!; \\
|
|
1056
|
-
\t\tfor i in $$(seq 1 15); do \\
|
|
1057
|
-
\t\t\tnc -z localhost 15432 2>/dev/null && break; \\
|
|
1058
|
-
\t\t\tsleep 1; \\
|
|
1059
|
-
\t\tdone; \\
|
|
1060
|
-
\t\tcd $(ROOT) && DATABASE_URL="postgres://postgres:$$DB_PASS_URLENC@localhost:15432/#{db_name}" ruby -Ilib -e 'require "hecks"; require "hecks/ports/persistence/plugins/era"; loading = Hecks::Ports::Loading.bootstrap; directory = loading.bluebook_directory(ARGV[0]); root = loading.shared_root(nil, directory); registry = Hecks::Runtime::Registry.new(root: File.dirname(directory)); Hecks.with_registry(registry) { loading.load_library; loading.load_project(root); loading.load_domain(directory) }; bluebook = registry.bluebooks[#{declared_domain_name.inspect}] or abort "no #{declared_domain_name} bluebook loaded"; current_text = Hecks::Runtime::EraCheck.source_text_for(bluebook, directory); Hecks::Adapters::PostgresEra::LineageManager.check!(registry: registry, bluebook: bluebook, current_text: current_text, settings: { database: ENV["DATABASE_URL"]#{hecks_schema ? ", schema: #{hecks_schema.inspect}" : ""} }); db = Hecks::Adapters::PostgresEra.connect_for(bluebook.name, { database: ENV["DATABASE_URL"]#{hecks_schema ? ", schema: #{hecks_schema.inspect}" : ""} }); lineage = Hecks::Adapters::PostgresEra::Lineage.new(db, bluebook.name); (registry.bluebooks.values - [bluebook]).each { |other| other.aggregates.each { |aggregate| lineage.ensure_first_head!(aggregate.storage_name) } }; db.close; puts "booted OK — era resolution ran"' $(DOMAIN) && { BOOT_STATUS=0; break; }; \\
|
|
1061
|
-
\t\tBOOT_STATUS=$$?; \\
|
|
1062
|
-
\t\techo "boot check attempt $$attempt/5 failed (exit $$BOOT_STATUS) -- restarting the tunnel and retrying in 3s..."; \\
|
|
1063
|
-
\t\tsleep 3; \\
|
|
1064
|
-
\tdone; \\
|
|
1065
|
-
\tkill $$TUNNEL_PID 2>/dev/null; \\
|
|
1066
|
-
\techo "Tearing down the temporary bastion stack..."; \\
|
|
1067
|
-
\taws cloudformation delete-stack --stack-name $(BASTION_STACK); \\
|
|
1068
|
-
\taws cloudformation wait stack-delete-complete --stack-name $(BASTION_STACK); \\
|
|
1069
|
-
\techo "$(BASTION_STACK) deleted. Era 1 should now be held if BOOT_STATUS was 0 — verify with hecks console or a Postgres query against hecks_eras."; \\
|
|
1070
|
-
\texit $$BOOT_STATUS
|
|
1071
|
-
OWNMINT
|
|
1072
|
-
end
|
|
1073
|
-
|
|
1074
|
-
# Shares mint_era_recipe's bastion/tunnel/retry/teardown chain but
|
|
1075
|
-
# runs `hecks query era.scaffold_translation` or
|
|
1076
|
-
# `hecks query era.audit_translation` over it.
|
|
1077
|
-
#
|
|
1078
|
-
# Neither script actually reads DATABASE_URL/HECKS_SCHEMA (both call
|
|
1079
|
-
# `registry.binding_settings`, a lookup against the literal `database
|
|
1080
|
-
# "..."` in #{domain}'s `.world`) — without `db_env_blind:`, this
|
|
1081
|
-
# recipe would open a real tunnel to production and silently
|
|
1082
|
-
# scaffold/audit the local dev database instead, reporting success.
|
|
1083
|
-
# `db_env_blind` refuses unless `ALLOW_LOCAL_DB=1` is set.
|
|
1084
|
-
translation_recipe = lambda do |verb, script, extra_args = "", cwd: "$(ROOT)", run_prefix: "ruby -Ilib", db_env_blind: false,
|
|
1085
|
-
domain_arg: "$(DOMAIN) "|
|
|
1086
|
-
if shared
|
|
1087
|
-
<<~SHAREDTRANSLATION.rstrip
|
|
1088
|
-
\t@echo "#{verb} isn't automated yet for a Shared-mode domain (database \\"Shared\\") -- see mint-era's own comment in the generated Makefile for the manual path through #{owner_domain_name}'s own tunnel."; \\
|
|
1089
|
-
\texit 1
|
|
1090
|
-
SHAREDTRANSLATION
|
|
1091
|
-
else
|
|
1092
|
-
<<~OWNTRANSLATION.rstrip
|
|
1093
|
-
#{db_env_blind ? <<~ENVBLINDGUARD.rstrip
|
|
1094
|
-
\t@if [ -z "$$ALLOW_LOCAL_DB" ]; then \\
|
|
1095
|
-
\t\techo "REFUSING: #{script} resolves its OWN database connection from #{domain}'s .world file, NOT from DATABASE_URL/HECKS_SCHEMA -- opening a real tunnel to #{stack_name}'s production RDS instance below would silently scaffold/audit the LOCAL dev database instead and report success (see this recipe's own comment, above, for the confirmed root cause). Set ALLOW_LOCAL_DB=1 to run #{verb} against #{domain}'s local .world-declared database on purpose (e.g. to exercise the scaffold/audit logic itself); otherwise run this for real against production via the manual tunnel path mint-era's own comment describes."; \\
|
|
1096
|
-
\t\texit 1; \\
|
|
1097
|
-
\tfi
|
|
1098
|
-
ENVBLINDGUARD
|
|
1099
|
-
: ""}
|
|
1100
|
-
\t@echo "Looking up $(STACK)'s VPC/security group..."
|
|
1101
|
-
\t#{stack_outputs.map { |o| %($(eval #{o[:var]} := $(shell aws cloudformation describe-stacks --stack-name $(STACK) --query "Stacks[0].Outputs[?OutputKey=='#{o[:key]}'].OutputValue" --output text))) }.join("\n\t")}
|
|
1102
|
-
\t@echo "Deploying the temporary bastion stack $(BASTION_STACK)..."
|
|
1103
|
-
\taws cloudformation deploy --template-file bastion.yaml --stack-name $(BASTION_STACK) \\
|
|
1104
|
-
\t\t--parameter-overrides #{bastion_parameters.map { |p| "#{p[:name]}=$(#{stack_outputs.find { |o| o[:key] == p[:from_output] }[:var]})" }.join(" ")} \\
|
|
1105
|
-
\t\t--capabilities CAPABILITY_IAM
|
|
1106
|
-
# DB_PASS_URLENC -- same percent-encoding fix as mint_era_recipe's
|
|
1107
|
-
# own comment above the equivalent line in that recipe (libpq's URI
|
|
1108
|
-
# parser rejects a bare `%`, which a generated RDS password isn't
|
|
1109
|
-
# guaranteed to be free of); this recipe's DATABASE_URL below uses
|
|
1110
|
-
# the encoded form for the same reason, even though the script it
|
|
1111
|
-
# feeds doesn't currently read it (see db_env_blind above).
|
|
1112
|
-
\t@INSTANCE_ID=$$(aws cloudformation describe-stacks --stack-name $(BASTION_STACK) --query "Stacks[0].Outputs[?OutputKey=='InstanceId'].OutputValue" --output text); \\
|
|
1113
|
-
\techo "Waiting for $$INSTANCE_ID to register with SSM..."; \\
|
|
1114
|
-
\tfor i in $$(seq 1 30); do \\
|
|
1115
|
-
\t\tSTATUS=$$(aws ssm describe-instance-information --filters "Key=InstanceIds,Values=$$INSTANCE_ID" --query "InstanceInformationList[0].PingStatus" --output text 2>/dev/null); \\
|
|
1116
|
-
\t\tif [ "$$STATUS" = "Online" ]; then break; fi; \\
|
|
1117
|
-
\t\tsleep 5; \\
|
|
1118
|
-
\tdone; \\
|
|
1119
|
-
\tDB_PASS=$$(aws secretsmanager get-secret-value --secret-id $(DB_SECRET_ARN) --query SecretString --output text | ruby -rjson -e 'print JSON.parse(STDIN.read)["password"]'); \\
|
|
1120
|
-
\tDB_PASS_URLENC=$$(ruby -rerb -e 'print ERB::Util.url_encode(ARGV[0])' "$$DB_PASS"); \\
|
|
1121
|
-
\tRUN_STATUS=1; \\
|
|
1122
|
-
\tfor attempt in 1 2 3 4 5; do \\
|
|
1123
|
-
\t\tkill $$TUNNEL_PID 2>/dev/null; \\
|
|
1124
|
-
\t\techo "Opening an SSM tunnel to $(DB_HOST):5432 and running #{verb} (attempt $$attempt/5)..."; \\
|
|
1125
|
-
\t\taws ssm start-session --target $$INSTANCE_ID \\
|
|
1126
|
-
\t\t\t--document-name AWS-StartPortForwardingSessionToRemoteHost \\
|
|
1127
|
-
\t\t\t--parameters "{\\"host\\":[\\"$(DB_HOST)\\"],\\"portNumber\\":[\\"5432\\"],\\"localPortNumber\\":[\\"15432\\"]}" \\
|
|
1128
|
-
\t\t\t>/tmp/$(BASTION_STACK)-tunnel-$$attempt.log 2>&1 & \\
|
|
1129
|
-
\t\tTUNNEL_PID=$$!; \\
|
|
1130
|
-
\t\tfor i in $$(seq 1 15); do \\
|
|
1131
|
-
\t\t\tnc -z localhost 15432 2>/dev/null && break; \\
|
|
1132
|
-
\t\t\tsleep 1; \\
|
|
1133
|
-
\t\tdone; \\
|
|
1134
|
-
\t\tcd #{cwd} && DATABASE_URL="postgres://postgres:$$DB_PASS_URLENC@localhost:15432/#{db_name}" HECKS_SCHEMA="#{hecks_schema}" #{run_prefix} #{script} #{cwd == "$(ROOT)" ? domain_arg : ""}#{extra_args}&& { RUN_STATUS=0; break; }; \\
|
|
1135
|
-
\t\tRUN_STATUS=$$?; \\
|
|
1136
|
-
\t\techo "#{verb} attempt $$attempt/5 failed (exit $$RUN_STATUS) -- restarting the tunnel and retrying in 3s..."; \\
|
|
1137
|
-
\t\tsleep 3; \\
|
|
1138
|
-
\tdone; \\
|
|
1139
|
-
\tkill $$TUNNEL_PID 2>/dev/null; \\
|
|
1140
|
-
\techo "Tearing down the temporary bastion stack..."; \\
|
|
1141
|
-
\taws cloudformation delete-stack --stack-name $(BASTION_STACK); \\
|
|
1142
|
-
\taws cloudformation wait stack-delete-complete --stack-name $(BASTION_STACK); \\
|
|
1143
|
-
\techo "$(BASTION_STACK) deleted."; \\
|
|
1144
|
-
\texit $$RUN_STATUS
|
|
1145
|
-
OWNTRANSLATION
|
|
1146
|
-
end
|
|
77
|
+
text.sub(/^Outputs:\n/) do
|
|
78
|
+
"#{resources}Outputs:\n PiiDistributionDomainName:\n Value: !GetAtt PiiDistribution.DomainName\n"
|
|
1147
79
|
end
|
|
1148
|
-
|
|
1149
|
-
# `hecks ask` answers the scaffold as text, and refuses (exit 1) where the script aborted.
|
|
1150
|
-
scaffold_translation_recipe = translation_recipe.call(
|
|
1151
|
-
"scaffold-translation", "exe/hecks query era.scaffold_translation", db_env_blind: true,
|
|
1152
|
-
run_prefix: "HECKS_ENVIRONMENT=memory ruby", domain_arg: "domain=$(DOMAIN) "
|
|
1153
|
-
)
|
|
1154
|
-
# A refused audit is a refused query: `hecks ask` exits 1, so the target fails too.
|
|
1155
|
-
translation_audit_recipe = translation_recipe.call(
|
|
1156
|
-
"translation-audit", "exe/hecks query era.audit_translation", db_env_blind: true,
|
|
1157
|
-
run_prefix: "HECKS_ENVIRONMENT=memory ruby", domain_arg: "domain=$(DOMAIN) "
|
|
1158
|
-
)
|
|
1159
|
-
|
|
1160
|
-
# Same chain, running an app-owned one-time migration script instead
|
|
1161
|
-
# of a hecks tool. `bin/migrate_console_settings` belongs to the domain's own
|
|
1162
|
-
# application, not to hecks, so it stays a `bin/` path when hecks drops its own.
|
|
1163
|
-
# Harmless when the domain has none: `bundle exec ruby` on a missing file just fails
|
|
1164
|
-
# loudly.
|
|
1165
|
-
migrate_console_settings_recipe = translation_recipe.call(
|
|
1166
|
-
"migrate-console-settings", "bin/migrate_console_settings", "",
|
|
1167
|
-
cwd: "$(DOMAIN)", run_prefix: "bundle exec ruby"
|
|
1168
|
-
)
|
|
1169
|
-
|
|
1170
|
-
# `OLD`/`NEW` are Make command-line variables (`make rename-schema
|
|
1171
|
-
# OLD=old NEW=new`), reusable across whichever domain owns this
|
|
1172
|
-
# stack's RDS instance. Idempotent by inspection (checks which schema
|
|
1173
|
-
# exists first), not by catching a Postgres error.
|
|
1174
|
-
rename_schema_recipe =
|
|
1175
|
-
if shared
|
|
1176
|
-
<<~SHAREDRENAME.rstrip
|
|
1177
|
-
# NOT AUTOMATED for a Shared-mode domain, same reason mint-era
|
|
1178
|
-
# isn't: there is no bastion.yaml here to stand up next to (this
|
|
1179
|
-
# domain has no RDS/VPC of its own). This domain's own schema
|
|
1180
|
-
# lives inside #{owner_domain_name}'s database — run this same
|
|
1181
|
-
# target from #{owner_domain_name}'s own deploy directory instead.
|
|
1182
|
-
\t@echo "rename-schema isn't automated for a Shared-mode domain (database \\"Shared\\") -- run it from #{owner_domain_name}'s own deploy directory instead, which owns the RDS instance this domain's schema actually lives on."; \\
|
|
1183
|
-
\texit 1
|
|
1184
|
-
SHAREDRENAME
|
|
1185
|
-
else
|
|
1186
|
-
<<~OWNRENAME.rstrip
|
|
1187
|
-
# THE WHOLE TUNNEL IS RESTARTED PER ATTEMPT, not just probed once
|
|
1188
|
-
# through a single long-lived session — confirmed live, the hard
|
|
1189
|
-
# way: an SSM port-forwarding session can start and then exit
|
|
1190
|
-
# again within seconds, not merely "slow to become ready". Probing
|
|
1191
|
-
# the same dead tunnel five times fails identically every time,
|
|
1192
|
-
# for a different reason than the retry was built for. Without
|
|
1193
|
-
# this restart, a still-dead tunnel also doesn't just fail loudly
|
|
1194
|
-
# — `EXISTS_OLD`/`EXISTS_NEW` would both come back empty, which the
|
|
1195
|
-
# branch below reads as "neither schema exists" and reports as if
|
|
1196
|
-
# that were the real answer, not a connectivity failure wearing
|
|
1197
|
-
# its name — which is exactly why `TUNNEL_READY` gates that branch
|
|
1198
|
-
# explicitly rather than trusting empty results at face value.
|
|
1199
|
-
#
|
|
1200
|
-
# OLD/NEW ARE ALLOWLISTED AS BARE IDENTIFIERS, not SQL-escaped, before
|
|
1201
|
-
# either one reaches a psql command line: both get interpolated
|
|
1202
|
-
# straight into the `nspname = '...'` lookups above and the
|
|
1203
|
-
# `ALTER SCHEMA "..." RENAME TO "..."` below, and a schema name can't
|
|
1204
|
-
# be bound as a parameter the way a value can — quoting/escaping an
|
|
1205
|
-
# identifier is exactly the kind of thing that's easy to get subtly
|
|
1206
|
-
# wrong, so instead of trying, this refuses anything that isn't
|
|
1207
|
-
# `^[A-Za-z_][A-Za-z0-9_]*$` outright. `make` runs this recipe as an
|
|
1208
|
-
# operator command, not user-facing web input, but it still executes
|
|
1209
|
-
# against production RDS, so a typo'd or copy-pasted `OLD`/`NEW`
|
|
1210
|
-
# containing quotes or `;` must fail loudly here, before either
|
|
1211
|
-
# tunnel or query, rather than run as-is.
|
|
1212
|
-
#
|
|
1213
|
-
# `$$OLD`/`$$NEW` HERE, NOT `$(OLD)`/`$(NEW)`: this check itself has
|
|
1214
|
-
# to read the value without Make ever splicing its raw text into the
|
|
1215
|
-
# recipe line, or a value containing `;`/`"` would break out of THIS
|
|
1216
|
-
# line's shell command and run before the pattern match ever sees it
|
|
1217
|
-
# — confirmed live, the same way the tunnel-restart behavior above
|
|
1218
|
-
# was. `$$OLD` instead reads the real shell environment variable
|
|
1219
|
-
# `make` already exports for command-line-assigned vars, so the
|
|
1220
|
-
# value is one opaque string to the shell no matter what characters
|
|
1221
|
-
# it contains. Once a value's passed this gate it's provably just
|
|
1222
|
-
# `[A-Za-z0-9_]`, so the existing `$(OLD)`/`$(NEW)` splices further
|
|
1223
|
-
# below (already written before this fix, reused as-is) are safe.
|
|
1224
|
-
\t@[ -n "$$OLD" ] && [ -n "$$NEW" ] || { echo "usage: make rename-schema OLD=<old-schema> NEW=<new-schema>"; exit 1; }
|
|
1225
|
-
\t@echo "$$OLD" | grep -Eq '^[A-Za-z_][A-Za-z0-9_]*$$' || { echo "invalid OLD schema name -- schema names must match ^[A-Za-z_][A-Za-z0-9_]*$$, refusing to touch SQL"; exit 1; }
|
|
1226
|
-
\t@echo "$$NEW" | grep -Eq '^[A-Za-z_][A-Za-z0-9_]*$$' || { echo "invalid NEW schema name -- schema names must match ^[A-Za-z_][A-Za-z0-9_]*$$, refusing to touch SQL"; exit 1; }
|
|
1227
|
-
\t@echo "Looking up $(STACK)'s VPC/security group..."
|
|
1228
|
-
\t#{stack_outputs.map { |o| %($(eval #{o[:var]} := $(shell aws cloudformation describe-stacks --stack-name $(STACK) --query "Stacks[0].Outputs[?OutputKey=='#{o[:key]}'].OutputValue" --output text))) }.join("\n\t")}
|
|
1229
|
-
\t@echo "Deploying the temporary bastion stack $(BASTION_STACK)..."
|
|
1230
|
-
\taws cloudformation deploy --template-file bastion.yaml --stack-name $(BASTION_STACK) \\
|
|
1231
|
-
\t\t--parameter-overrides #{bastion_parameters.map { |p| "#{p[:name]}=$(#{stack_outputs.find { |o| o[:key] == p[:from_output] }[:var]})" }.join(" ")} \\
|
|
1232
|
-
\t\t--capabilities CAPABILITY_IAM
|
|
1233
|
-
\t@INSTANCE_ID=$$(aws cloudformation describe-stacks --stack-name $(BASTION_STACK) --query "Stacks[0].Outputs[?OutputKey=='InstanceId'].OutputValue" --output text); \\
|
|
1234
|
-
\techo "Waiting for $$INSTANCE_ID to register with SSM..."; \\
|
|
1235
|
-
\tfor i in $$(seq 1 30); do \\
|
|
1236
|
-
\t\tSTATUS=$$(aws ssm describe-instance-information --filters "Key=InstanceIds,Values=$$INSTANCE_ID" --query "InstanceInformationList[0].PingStatus" --output text 2>/dev/null); \\
|
|
1237
|
-
\t\tif [ "$$STATUS" = "Online" ]; then break; fi; \\
|
|
1238
|
-
\t\tsleep 5; \\
|
|
1239
|
-
\tdone; \\
|
|
1240
|
-
\tDB_PASS=$$(aws secretsmanager get-secret-value --secret-id $(DB_SECRET_ARN) --query SecretString --output text | ruby -rjson -e 'print JSON.parse(STDIN.read)["password"]'); \\
|
|
1241
|
-
\tTUNNEL_READY=1; \\
|
|
1242
|
-
\tfor attempt in 1 2 3 4 5; do \\
|
|
1243
|
-
\t\tkill $$TUNNEL_PID 2>/dev/null; \\
|
|
1244
|
-
\t\techo "Opening an SSM tunnel to $(DB_HOST):5432 (attempt $$attempt/5)..."; \\
|
|
1245
|
-
\t\taws ssm start-session --target $$INSTANCE_ID \\
|
|
1246
|
-
\t\t\t--document-name AWS-StartPortForwardingSessionToRemoteHost \\
|
|
1247
|
-
\t\t\t--parameters "{\\"host\\":[\\"$(DB_HOST)\\"],\\"portNumber\\":[\\"5432\\"],\\"localPortNumber\\":[\\"15432\\"]}" \\
|
|
1248
|
-
\t\t\t>/tmp/$(BASTION_STACK)-tunnel-$$attempt.log 2>&1 & \\
|
|
1249
|
-
\t\tTUNNEL_PID=$$!; \\
|
|
1250
|
-
\t\tfor i in $$(seq 1 15); do \\
|
|
1251
|
-
\t\t\tnc -z localhost 15432 2>/dev/null && break; \\
|
|
1252
|
-
\t\t\tsleep 1; \\
|
|
1253
|
-
\t\tdone; \\
|
|
1254
|
-
\t\tPGPASSWORD=$$DB_PASS psql -h localhost -p 15432 -U postgres -d #{db_name} -tAc "SELECT 1" >/dev/null 2>&1 && { TUNNEL_READY=0; break; }; \\
|
|
1255
|
-
\t\techo "tunnel not carrying real traffic yet (attempt $$attempt/5) -- restarting the tunnel and retrying in 3s..."; \\
|
|
1256
|
-
\t\tsleep 3; \\
|
|
1257
|
-
\tdone; \\
|
|
1258
|
-
\tEXISTS_OLD=$$(PGPASSWORD=$$DB_PASS psql -h localhost -p 15432 -U postgres -d #{db_name} -tAc "SELECT 1 FROM pg_namespace WHERE nspname = '$(OLD)'"); \\
|
|
1259
|
-
\tEXISTS_NEW=$$(PGPASSWORD=$$DB_PASS psql -h localhost -p 15432 -U postgres -d #{db_name} -tAc "SELECT 1 FROM pg_namespace WHERE nspname = '$(NEW)'"); \\
|
|
1260
|
-
\tif [ "$$TUNNEL_READY" != "0" ]; then \\
|
|
1261
|
-
\t\techo "tunnel never carried a real connection -- aborting without touching either schema"; \\
|
|
1262
|
-
\t\tBOOT_STATUS=1; \\
|
|
1263
|
-
\telif [ "$$EXISTS_NEW" = "1" ]; then \\
|
|
1264
|
-
\t\techo "schema $(NEW) already exists -- already renamed, no-op"; \\
|
|
1265
|
-
\t\tBOOT_STATUS=0; \\
|
|
1266
|
-
\telif [ "$$EXISTS_OLD" = "1" ]; then \\
|
|
1267
|
-
\t\tPGPASSWORD=$$DB_PASS psql -h localhost -p 15432 -U postgres -d #{db_name} -v ON_ERROR_STOP=1 -c "ALTER SCHEMA \\"$(OLD)\\" RENAME TO \\"$(NEW)\\""; \\
|
|
1268
|
-
\t\tBOOT_STATUS=$$?; \\
|
|
1269
|
-
\t\techo "renamed schema $(OLD) to $(NEW) (exit $$BOOT_STATUS)"; \\
|
|
1270
|
-
\telse \\
|
|
1271
|
-
\t\techo "neither schema $(OLD) nor $(NEW) exists on #{db_name} -- nothing to rename"; \\
|
|
1272
|
-
\t\tBOOT_STATUS=1; \\
|
|
1273
|
-
\tfi; \\
|
|
1274
|
-
\tkill $$TUNNEL_PID 2>/dev/null; \\
|
|
1275
|
-
\techo "Tearing down the temporary bastion stack..."; \\
|
|
1276
|
-
\taws cloudformation delete-stack --stack-name $(BASTION_STACK); \\
|
|
1277
|
-
\taws cloudformation wait stack-delete-complete --stack-name $(BASTION_STACK); \\
|
|
1278
|
-
\techo "$(BASTION_STACK) deleted."; \\
|
|
1279
|
-
\texit $$BOOT_STATUS
|
|
1280
|
-
OWNRENAME
|
|
1281
|
-
end
|
|
1282
|
-
|
|
1283
|
-
# `google_oauth_present` and `shared` are independent facts, not
|
|
1284
|
-
# alternatives — an elsif would silently drop Owning* overrides
|
|
1285
|
-
# whenever OAuth is also present. `@` (Make's "don't echo" prefix)
|
|
1286
|
-
# is only meaningful on the chain's true first line, so lines are
|
|
1287
|
-
# built unprefixed and `@` is added once, right before joining.
|
|
1288
|
-
owner_lookup_lines = shared ? [
|
|
1289
|
-
%(OWNER_VPC_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query "Stacks[0].Outputs[?OutputKey=='VpcId'].OutputValue" --output text); \\),
|
|
1290
|
-
%(OWNER_SUBNET_A_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query "Stacks[0].Outputs[?OutputKey=='PrivateSubnetAId'].OutputValue" --output text); \\),
|
|
1291
|
-
%(OWNER_SUBNET_B_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query "Stacks[0].Outputs[?OutputKey=='PrivateSubnetBId'].OutputValue" --output text); \\),
|
|
1292
|
-
%(OWNER_SG_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query "Stacks[0].Outputs[?OutputKey=='FunctionSecurityGroupId'].OutputValue" --output text); \\),
|
|
1293
|
-
%(OWNER_DB_HOST=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query "Stacks[0].Outputs[?OutputKey=='DatabaseEndpoint'].OutputValue" --output text); \\),
|
|
1294
|
-
%(OWNER_DB_SECRET_ARN=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query "Stacks[0].Outputs[?OutputKey=='DatabaseSecretArn'].OutputValue" --output text); \\),
|
|
1295
|
-
] : []
|
|
1296
|
-
owning_overrides = "OwningVpcId=$$OWNER_VPC_ID OwningSubnetAId=$$OWNER_SUBNET_A_ID OwningSubnetBId=$$OWNER_SUBNET_B_ID OwningSecurityGroupId=$$OWNER_SG_ID OwningDatabaseEndpoint=$$OWNER_DB_HOST OwningDatabaseSecretArn=$$OWNER_DB_SECRET_ARN"
|
|
1297
|
-
|
|
1298
|
-
sam_deploy_lines =
|
|
1299
|
-
if google_oauth_present && shared
|
|
1300
|
-
[
|
|
1301
|
-
%(WEB_URL=$$(aws lambda get-function-url-config --function-name #{rust_web ? stack_name : "#{stack_name}-web"} --query FunctionUrl --output text 2>/dev/null | sed 's:/$$::'); \\),
|
|
1302
|
-
"if [ -n \"$$WEB_URL\" ]; then \\",
|
|
1303
|
-
%(\tsam deploy --parameter-overrides WebRedirectBaseUrl="$$WEB_URL" #{owning_overrides}; \\),
|
|
1304
|
-
"else \\",
|
|
1305
|
-
"\tsam deploy --parameter-overrides #{owning_overrides}; \\",
|
|
1306
|
-
"fi",
|
|
1307
|
-
]
|
|
1308
|
-
elsif google_oauth_present
|
|
1309
|
-
[
|
|
1310
|
-
%(WEB_URL=$$(aws lambda get-function-url-config --function-name #{rust_web ? stack_name : "#{stack_name}-web"} --query FunctionUrl --output text 2>/dev/null | sed 's:/$$::'); \\),
|
|
1311
|
-
"if [ -n \"$$WEB_URL\" ]; then \\",
|
|
1312
|
-
%(\tsam deploy --parameter-overrides WebRedirectBaseUrl="$$WEB_URL"; \\),
|
|
1313
|
-
"else \\",
|
|
1314
|
-
"\tsam deploy; \\",
|
|
1315
|
-
"fi",
|
|
1316
|
-
]
|
|
1317
|
-
elsif shared
|
|
1318
|
-
["sam deploy --parameter-overrides #{owning_overrides}"]
|
|
1319
|
-
else
|
|
1320
|
-
["sam deploy"]
|
|
1321
|
-
end
|
|
1322
|
-
|
|
1323
|
-
deploy_shell_chain = owner_lookup_lines + sam_deploy_lines
|
|
1324
|
-
deploy_shell_chain = ["@#{deploy_shell_chain.first}"] + deploy_shell_chain.drop(1) if deploy_shell_chain.first&.match?(/=\$\$\(/)
|
|
1325
|
-
|
|
1326
|
-
# Google OAuth newly added to an existing stack would deadlock this
|
|
1327
|
-
# bridge: PublicSubnetId/BastionSubnetId don't exist on the live stack
|
|
1328
|
-
# until the upcoming `sam deploy` creates them, so the pre-check's own
|
|
1329
|
-
# `mint-era` call would hand bastion.yaml an empty Subnet::Id and
|
|
1330
|
-
# CloudFormation would refuse it before `sam deploy` ever runs. Skip
|
|
1331
|
-
# the bridge in exactly that case; the unconditional `mint-era` call
|
|
1332
|
-
# at the end of `deploy:` still covers it afterward.
|
|
1333
|
-
predeploy_bridge_shell =
|
|
1334
|
-
if google_oauth_present
|
|
1335
|
-
<<~OAUTHBRIDGE.rstrip
|
|
1336
|
-
@echo "Checking whether $(STACK) already exists, and if so whether it already has Google OAuth's PublicSubnetId/BastionSubnetId outputs, before deciding whether to bridge era history now or let this deploy create them first..."
|
|
1337
|
-
@if aws cloudformation describe-stacks --stack-name $(STACK) >/dev/null 2>&1; then \\
|
|
1338
|
-
\tOAUTH_OUTPUTS_READY=$$(aws cloudformation describe-stacks --stack-name $(STACK) --query "Stacks[0].Outputs[?OutputKey=='PublicSubnetId'].OutputValue" --output text 2>/dev/null); \\
|
|
1339
|
-
\tif [ -z "$$OAUTH_OUTPUTS_READY" ]; then \\
|
|
1340
|
-
\t\techo "Existing stack found, but Google OAuth is newly being added -- PublicSubnetId/BastionSubnetId don't exist on it yet (sam deploy, below, is what creates them); skipping the pre-deploy bridge this one time so THIS deploy can actually run. mint-era still runs once, after sam deploy, same as a genuine first deploy."; \\
|
|
1341
|
-
\telse \\
|
|
1342
|
-
\t\techo "Existing stack found -- bridging era history before this deploy flips $(STACK) over, not after."; \\
|
|
1343
|
-
\t\t$(MAKE) mint-era || exit 1; \\
|
|
1344
|
-
\tfi; \\
|
|
1345
|
-
else \\
|
|
1346
|
-
\techo "No existing stack -- first deploy, nothing to bridge yet; mint-era runs once, below, after the stack (and its RDS instance) exist."; \\
|
|
1347
|
-
fi
|
|
1348
|
-
OAUTHBRIDGE
|
|
1349
|
-
else
|
|
1350
|
-
<<~PLAINBRIDGE.rstrip
|
|
1351
|
-
@echo "Checking whether $(STACK) already exists, to decide whether this deploy needs to bridge era history before it flips the stack over..."
|
|
1352
|
-
@if aws cloudformation describe-stacks --stack-name $(STACK) >/dev/null 2>&1; then \\
|
|
1353
|
-
\techo "Existing stack found -- bridging era history before this deploy flips $(STACK) over, not after."; \\
|
|
1354
|
-
\t$(MAKE) mint-era || exit 1; \\
|
|
1355
|
-
else \\
|
|
1356
|
-
\techo "No existing stack -- first deploy, nothing to bridge yet; mint-era runs once, below, after the stack (and its RDS instance) exist."; \\
|
|
1357
|
-
fi
|
|
1358
|
-
PLAINBRIDGE
|
|
1359
|
-
end
|
|
1360
|
-
|
|
1361
|
-
deploy_recipe_lines = (
|
|
1362
|
-
(google_oauth_present ? ["$(MAKE) sync-google-oauth"] : []) +
|
|
1363
|
-
(shared ? [%(@echo "Looking up #{owner_stack_name}'s shared VpcId/PrivateSubnetAId/PrivateSubnetBId/FunctionSecurityGroupId/DatabaseEndpoint/DatabaseSecretArn outputs to pass as $(STACK)'s Owning* parameters...")] : []) +
|
|
1364
|
-
deploy_shell_chain
|
|
1365
|
-
).map { |l| "\t#{l}" }.join("\n")
|
|
1366
|
-
|
|
1367
|
-
makefile_content = <<~MAKE
|
|
1368
|
-
# GENERATED by hecks deploy project #{domain} — re-run it to refresh
|
|
1369
|
-
# this file rather than hand-editing.
|
|
1370
|
-
#
|
|
1371
|
-
# `sam build` invokes the build-<LogicalId> target below (SAM's own
|
|
1372
|
-
# BuildMethod: makefile convention — see template.yaml's Metadata).
|
|
1373
|
-
# `cargo lambda` (https://www.cargo-lambda.info) cross-compiles
|
|
1374
|
-
# rust/host for arm64 Lambda without Docker or a hand-configured
|
|
1375
|
-
# cross-linker — install once with `pip3 install cargo-lambda` or
|
|
1376
|
-
# `brew install cargo-lambda`.
|
|
1377
|
-
|
|
1378
|
-
HOST_DIR := #{File.join(root, "rust", "host")}
|
|
1379
|
-
# `domain_name`, matching `hecks build_wasm`'s own target_mod_name
|
|
1380
|
-
# (a Rust-build-pipeline naming convention, not AWS resource
|
|
1381
|
-
# identity — see HECKS_WASM_PATH's own comment above).
|
|
1382
|
-
WASM := #{File.join(root, "rust", "dist", "#{domain_name}.wasm")}
|
|
1383
|
-
|
|
1384
|
-
build-#{logical_id}:
|
|
1385
|
-
\t@command -v cargo-lambda >/dev/null 2>&1 || { \\
|
|
1386
|
-
\t\techo "cargo-lambda isn't installed. Install it once with: pip3 install cargo-lambda"; \\
|
|
1387
|
-
\t\texit 1; \\
|
|
1388
|
-
\t}
|
|
1389
|
-
# ALWAYS rebuilt, never `test -f $(WASM) || ...` — that guard only
|
|
1390
|
-
# checked EXISTENCE, not staleness, so a wasm built once at the
|
|
1391
|
-
# start of a work session silently kept getting deployed through
|
|
1392
|
-
# every later kernel change (a real, live bug: this cost a stale
|
|
1393
|
-
# deploy against a wasm that predated the kernel's own "mutations"
|
|
1394
|
-
# field). `hecks build_wasm` recompiles fast enough that "always
|
|
1395
|
-
# rebuild" is the safe default, matching rust/host's own bootstrap
|
|
1396
|
-
# build just below, which was never guarded this way to begin with.
|
|
1397
|
-
\tcd #{root} && HECKS_ENVIRONMENT=memory ruby exe/hecks build.build_wasm domain=#{domain} --wait
|
|
1398
|
-
\t@rustup target list --installed 2>/dev/null | grep -qx aarch64-unknown-linux-gnu || rustup target add aarch64-unknown-linux-gnu
|
|
1399
|
-
# `rustup run stable`, not a bare `cargo lambda` — same reasoning
|
|
1400
|
-
# `hecks build_wasm`'s own Makefile-equivalent line holds itself to: a
|
|
1401
|
-
# `cargo`/`rustc` earlier on PATH than rustup's own shims (Homebrew
|
|
1402
|
-
# installs one; common on this kind of machine) is a DIFFERENT
|
|
1403
|
-
# toolchain that never saw `rustup target add`, and fails with
|
|
1404
|
-
# "can't find crate for \`core\`" for the cross target even though
|
|
1405
|
-
# `rustup target list --installed` reports it present.
|
|
1406
|
-
# macOS -> Linux arm64 cross-compiling by default routes through
|
|
1407
|
-
# zig (cargo-lambda's own bundled cargo-zigbuild) — every zig
|
|
1408
|
-
# release checked so far (0.15.2, 0.16.0) rejects
|
|
1409
|
-
# `-Wl,--fix-cortex-a53-843419`, a flag rustc's own
|
|
1410
|
-
# aarch64-unknown-linux-gnu target emits unconditionally: "error:
|
|
1411
|
-
# unsupported linker arg: --fix-cortex-a53-843419", found live on
|
|
1412
|
-
# THIS domain's own very first real deploy attempt (upstream:
|
|
1413
|
-
# https://github.com/ziglang/zig/issues, no fix released yet as of
|
|
1414
|
-
# this comment). Not fixable by a flag here — it's zig's own `zig
|
|
1415
|
-
# cc` argument-translation layer refusing before rustc's link step
|
|
1416
|
-
# ever runs. If this build fails with that error, cargo-lambda has
|
|
1417
|
-
# a real, documented non-zig, non-Docker fallback: `-c cargo` (or
|
|
1418
|
-
# `CARGO_LAMBDA_COMPILER=cargo`) plus a real GNU cross-toolchain —
|
|
1419
|
-
# `brew tap messense/macos-cross-toolchains && brew install
|
|
1420
|
-
# aarch64-unknown-linux-gnu` (this flag has worked in real
|
|
1421
|
-
# binutils/LLD since 2018; only zig's own translation of it is the
|
|
1422
|
-
# gap), then set `CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=
|
|
1423
|
-
# aarch64-linux-gnu-gcc` with that toolchain's own bin/ on PATH.
|
|
1424
|
-
# Confirmed working end to end (a real `sam deploy` against a real
|
|
1425
|
-
# Lambda) the same day this comment was written.
|
|
1426
|
-
\tcd $(HOST_DIR) && rustup run stable cargo lambda build --release --arm64
|
|
1427
|
-
\tcp $(HOST_DIR)/target/lambda/bootstrap/bootstrap $(ARTIFACTS_DIR)/bootstrap
|
|
1428
|
-
# UNCONDITIONAL, not gated on rust_web -- same reason HECKS_IR_PATH
|
|
1429
|
-
# itself is unconditional above: main.rs's own boot sequence reads
|
|
1430
|
-
# the IR for every domain regardless of web mode, so the sidecar
|
|
1431
|
-
# file this env var points to has to actually be in the package
|
|
1432
|
-
# too, or the env var alone just changes the crash from "not set"
|
|
1433
|
-
# to "not found".
|
|
1434
|
-
\tcp $(WASM) $(ARTIFACTS_DIR)/#{domain_name}.wasm
|
|
1435
|
-
\tcp #{File.join(root, "rust", "dist", "#{domain_name}.ir.json")} $(ARTIFACTS_DIR)/#{domain_name}.ir.json
|
|
1436
|
-
|
|
1437
|
-
# `sam build <resource>` WIPES .aws-sam/build/ entirely before
|
|
1438
|
-
# building just the one resource named -- confirmed live: building
|
|
1439
|
-
# #{web_logical_id} second left NO #{logical_id}/ directory behind at
|
|
1440
|
-
# all, and reverted its OWN built CodeUri back to the raw, unbuilt
|
|
1441
|
-
# source value ("../.." -- CodeUri: . resolved two directories up
|
|
1442
|
-
# from .aws-sam/build/template.yaml). `sam deploy` then zips THAT
|
|
1443
|
-
# directory verbatim as #{logical_id}'s own code -- a real, live
|
|
1444
|
-
# deploy shipped #{web_logical_id}'s own source tree (Gemfile,
|
|
1445
|
-
# lambda_handler.rb, even .env.local) as #{logical_id}'s Lambda
|
|
1446
|
-
# package this way, caught by downloading the actually-deployed
|
|
1447
|
-
# code and finding #{web_logical_id}'s file tree inside it.
|
|
1448
|
-
# `deploy:`'s own two separate `sam build <resource>` calls can
|
|
1449
|
-
# never both survive in .aws-sam/build/ at once — this target
|
|
1450
|
-
# re-populates #{logical_id}'s build directory from the SAME
|
|
1451
|
-
# already-built bootstrap+wasm build-#{logical_id} just produced
|
|
1452
|
-
# (untouched by the second sam build call — only .aws-sam/build/
|
|
1453
|
-
# itself gets wiped, never $(HOST_DIR)/target or $(WASM)) and
|
|
1454
|
-
# repoints the built template's CodeUri back at it.
|
|
1455
|
-
.PHONY: restore-#{logical_id}-build
|
|
1456
|
-
restore-#{logical_id}-build:
|
|
1457
|
-
\t@mkdir -p .aws-sam/build/#{logical_id}
|
|
1458
|
-
\tcp $(HOST_DIR)/target/lambda/bootstrap/bootstrap .aws-sam/build/#{logical_id}/bootstrap
|
|
1459
|
-
\tcp $(WASM) .aws-sam/build/#{logical_id}/#{domain_name}.wasm
|
|
1460
|
-
\tcp #{File.join(root, "rust", "dist", "#{domain_name}.ir.json")} .aws-sam/build/#{logical_id}/#{domain_name}.ir.json
|
|
1461
|
-
\truby -e 'lines = File.readlines(".aws-sam/build/template.yaml"); start = lines.index { |l| l.strip == "#{logical_id}:" } or raise "restore-#{logical_id}-build: #{logical_id} resource not found in built template"; idx = (start+1...lines.length).find { |i| lines[i] =~ /CodeUri:/ } or raise "restore-#{logical_id}-build: no CodeUri line found under #{logical_id}"; lines[idx] = lines[idx].sub(/CodeUri:.*/, "CodeUri: #{logical_id}"); File.write(".aws-sam/build/template.yaml", lines.join)'
|
|
1462
|
-
|
|
1463
|
-
# `make verify-parity-#{logical_id}` — closes the exact gap the
|
|
1464
|
-
# equivalence-gap plan's own Phase 8 named: parity was checked ONLY
|
|
1465
|
-
# against CI's fixed test corpus, completely decoupled from what a
|
|
1466
|
-
# real `sam deploy` actually ships — `hecks check_conformance` (the
|
|
1467
|
-
# differential harness, docs/decisions/0010-ruby-is-the-reference-
|
|
1468
|
-
# implementation.md) had never once been run against a specific
|
|
1469
|
-
# compiled deploy artifact. Runs it here, for real, against
|
|
1470
|
-
# $(WASM) -- the EXACT file `build-#{logical_id}` (above) just built
|
|
1471
|
-
# and `deploy:` (below) is about to ship -- not a corpus-wide `cargo
|
|
1472
|
-
# build`'s own separate binary. `hecks check_conformance --wait` exits non-zero
|
|
1473
|
-
# on any mismatch, which this target lets propagate uncaught: a real
|
|
1474
|
-
# divergence between THIS artifact and Ruby's own reading of the
|
|
1475
|
-
# identical source blocks `make deploy` before `sam deploy` ever
|
|
1476
|
-
# runs, the same way a failing build already would.
|
|
1477
|
-
#
|
|
1478
|
-
# `spec/corpus/#{domain_name}.json` is this domain's OWN pinned
|
|
1479
|
-
# fuzzer-replay script (the same shape `hecks fuzz`/`hecks run` already
|
|
1480
|
-
# use) -- if a domain doesn't have one yet, this warns LOUDLY and
|
|
1481
|
-
# continues rather than either silently skipping (this project's own
|
|
1482
|
-
# standing rule against a silent gap reading as full coverage) or
|
|
1483
|
-
# blocking every deploy of a domain nobody has written one for yet.
|
|
1484
|
-
#
|
|
1485
|
-
# SECOND HALF, BELOW — `hecks fuzz_conformance`, ADR 0037's own fuzz
|
|
1486
|
-
# bridge pointed at this exact $(WASM). A pinned script only proves
|
|
1487
|
-
# "matches Ruby on the cases we thought to write down" (ADR 0039's own
|
|
1488
|
-
# honest framing); the fuzz bridge is what actually FOUND Findings 1-6.
|
|
1489
|
-
# Deliberately WARN-ONLY (`-@`, not `@`) for now: ADR 0037 already
|
|
1490
|
-
# catalogues real, confirmed, not-yet-fixed divergences (Findings 3 and
|
|
1491
|
-
# 5) that fire on ordinary generated sequences for more than one real
|
|
1492
|
-
# domain today, so making this a hard blocker before those are closed
|
|
1493
|
-
# would turn every affected domain's `make deploy` red for a reason
|
|
1494
|
-
# this target can't yet point at a fix for. Flip `-@` to `@` once ADR
|
|
1495
|
-
# 0037's open findings are closed and this stops firing in practice.
|
|
1496
|
-
.PHONY: verify-parity-#{logical_id}
|
|
1497
|
-
verify-parity-#{logical_id}:
|
|
1498
|
-
\t@if [ -f #{root}/spec/corpus/#{domain_name}.json ]; then \\
|
|
1499
|
-
\t\tcd #{root} && HECKS_ENVIRONMENT=memory ruby exe/hecks build.check_conformance domain=#{domain} script=spec/corpus/#{domain_name}.json artifact=$(WASM) --wait; \\
|
|
1500
|
-
\telse \\
|
|
1501
|
-
\t\techo "verify-parity-#{logical_id}: no spec/corpus/#{domain_name}.json -- SKIPPING the pre-deploy Ruby/Rust parity check, nothing to compare $(WASM) against. Write one (hecks fuzz/hecks run's own script shape) before this domain's next deploy."; \\
|
|
1502
|
-
\tfi
|
|
1503
|
-
\t@echo "verify-parity-#{logical_id}: fuzzing $(WASM) against generated sequences (ADR 0037's bridge, WARN-ONLY -- see this target's own comment)..."
|
|
1504
|
-
\t-@cd #{root} && HECKS_ENVIRONMENT=memory ruby exe/hecks build.fuzz_conformance domain=#{domain} artifact=$(WASM) --wait
|
|
1505
|
-
|
|
1506
|
-
# `make mint-era` — takes #{stack_name}'s RDS instance from freshly
|
|
1507
|
-
# created to "era 1 minted, ready for HECKS_DOMAIN/HECKS_ERA to
|
|
1508
|
-
# resolve" (template.yaml's own env vars already assume era 1 —
|
|
1509
|
-
# this is what makes that true). Run ONCE per fresh database, before
|
|
1510
|
-
# the first real `sam deploy` of the Lambda itself; safe to re-run
|
|
1511
|
-
# (a second boot against an already-held era 1 is a no-op — see
|
|
1512
|
-
# era_resolver.rb's own quiet-reboot branch). Stands up bastion.yaml
|
|
1513
|
-
# as its own sibling stack, uses it for one Ruby boot over an SSM
|
|
1514
|
-
# tunnel, then deletes it — nothing from this target is left running
|
|
1515
|
-
# afterward.
|
|
1516
|
-
ROOT := #{root}
|
|
1517
|
-
DOMAIN := #{domain}
|
|
1518
|
-
STACK := #{stack_name}
|
|
1519
|
-
BASTION_STACK := #{stack_name}-bastion
|
|
1520
|
-
|
|
1521
|
-
mint-era:
|
|
1522
|
-
#{mint_era_recipe}
|
|
1523
|
-
|
|
1524
|
-
# `make scaffold-translation` — run this when a deploy's own
|
|
1525
|
-
# pre-flight boot check (or `make mint-era` directly) refuses with
|
|
1526
|
-
# "the shape changed (era N) and no translation edge covers it".
|
|
1527
|
-
# Diffs the held era against the current bluebook over the same
|
|
1528
|
-
# bastion tunnel mint-era uses and WRITES a translations/*.bluebook
|
|
1529
|
-
# edge file into this domain's own repo — confident rules inline,
|
|
1530
|
-
# genuine ambiguities as `unresolved` lines a human has to resolve by
|
|
1531
|
-
# hand (a rename, a drop, a compute — never guessed). Review the
|
|
1532
|
-
# written file, then `make translation-audit`.
|
|
1533
|
-
scaffold-translation:
|
|
1534
|
-
#{scaffold_translation_recipe}
|
|
1535
|
-
|
|
1536
|
-
# `make translation-audit` — verifies a written (or still-pending)
|
|
1537
|
-
# translation edge over the same tunnel: every translated state
|
|
1538
|
-
# against the new era's own types/invariants/lifecycle, the compiled
|
|
1539
|
-
# SQL against the port's own reference transform, and a before/after
|
|
1540
|
-
# sample of real records printed for human review. Re-run
|
|
1541
|
-
# `make deploy` (or `make mint-era`) once this passes — that's what
|
|
1542
|
-
# actually applies the edge; this only checks it.
|
|
1543
|
-
translation-audit:
|
|
1544
|
-
#{translation_audit_recipe}
|
|
1545
|
-
|
|
1546
|
-
# `make migrate-console-settings` — runs this domain's own
|
|
1547
|
-
# bin/migrate_console_settings (if it has one) over the same bastion
|
|
1548
|
-
# tunnel every other Postgres-touching target here uses, against the
|
|
1549
|
-
# real deployed database instead of local dev. A one-time data
|
|
1550
|
-
# migration, not a repeatable ops procedure like mint-era/scaffold-
|
|
1551
|
-
# translation — generated the same way regardless, per this
|
|
1552
|
-
# project's own standing rule against hand-run ops steps.
|
|
1553
|
-
migrate-console-settings:
|
|
1554
|
-
#{migrate_console_settings_recipe}
|
|
1555
|
-
|
|
1556
|
-
# `make rename-schema OLD=<old> NEW=<new>` — a native `ALTER SCHEMA
|
|
1557
|
-
# ... RENAME TO ...` over the same bastion tunnel mint-era uses,
|
|
1558
|
-
# for when this domain's own declared identity changes
|
|
1559
|
-
# (`formerly_known_as`) and its storage schema should follow. Purely
|
|
1560
|
-
# a Postgres namespace rename: every table/row/sequence inside it is
|
|
1561
|
-
# untouched, so this is safe to run against a live, in-use database
|
|
1562
|
-
# — the only requirement is that nothing else names the schema by
|
|
1563
|
-
# its OLD name at the moment this runs (this domain's own deployed
|
|
1564
|
-
# Lambda's HECKS_SCHEMA env var, most directly — run `make deploy`
|
|
1565
|
-
# right before or right after this, not with a long gap either way).
|
|
1566
|
-
rename-schema:
|
|
1567
|
-
#{rename_schema_recipe}
|
|
1568
|
-
|
|
1569
|
-
#{pg_version ? <<~PGNATIVE : ""}
|
|
1570
|
-
# #{web_logical_id}'s own `pg` gem needs a native extension SAM's
|
|
1571
|
-
# default Ruby builder can't produce correctly: the precompiled
|
|
1572
|
-
# aarch64-linux binary needs GLIBC 2.29+, but Lambda's ruby3.2
|
|
1573
|
-
# MANAGED runtime is Amazon Linux 2 (glibc 2.26) — a real, live
|
|
1574
|
-
# "Init<NameError>: uninitialized constant PG::Error" caught this
|
|
1575
|
-
# (rescuing PG::Error itself failed to resolve, because pg's own
|
|
1576
|
-
# `require` died before ever reaching the file that defines it).
|
|
1577
|
-
# Built once, from source, in the SAME container `sam build
|
|
1578
|
-
# --use-container` itself uses (its glibc, not the host's, is what
|
|
1579
|
-
# has to match the deployed runtime) — then cached under
|
|
1580
|
-
# PG_NATIVE_DIR so every later `make deploy` skips straight to the
|
|
1581
|
-
# fast path. Delete that directory to force a clean rebuild.
|
|
1582
|
-
#
|
|
1583
|
-
# OpenSSL is built `no-shared` and linked STATICALLY into libpq, so
|
|
1584
|
-
# the libpq.so this produces carries no runtime dependency on
|
|
1585
|
-
# whatever OpenSSL (if any) happens to already be on the Lambda
|
|
1586
|
-
# execution image — sidesteps the AL2 package conflict between
|
|
1587
|
-
# `openssl-libs` and the build image's own pre-installed
|
|
1588
|
-
# `openssl-snapsafe-libs` entirely, rather than fighting it.
|
|
1589
|
-
PG_NATIVE_DIR := #{root}/tmp/pg-native-arm64-ruby3.2
|
|
1590
|
-
|
|
1591
|
-
$(PG_NATIVE_DIR)/pg_ext.so:
|
|
1592
|
-
\t@mkdir -p $(PG_NATIVE_DIR)
|
|
1593
|
-
\t@echo "Building #{web_logical_id}'s pg native extension from source (cached under $(PG_NATIVE_DIR) after this — a few minutes, one time only)..."
|
|
1594
|
-
\tdocker run --rm --platform linux/arm64 -v $(PG_NATIVE_DIR):/out \\
|
|
1595
|
-
\t\t--entrypoint /bin/bash public.ecr.aws/sam/build-ruby3.2:latest-arm64 -c ' \\
|
|
1596
|
-
\t\t\tset -e; \\
|
|
1597
|
-
\t\t\tyum install -y perl-IPC-Cmd >/dev/null; \\
|
|
1598
|
-
\t\t\tcd /tmp; \\
|
|
1599
|
-
\t\t\tcurl -sL https://www.openssl.org/source/openssl-3.0.15.tar.gz | tar xz; \\
|
|
1600
|
-
\t\t\tcd openssl-3.0.15; \\
|
|
1601
|
-
\t\t\t./Configure linux-aarch64 no-shared no-tests --prefix=/tmp/openssl-install >/dev/null; \\
|
|
1602
|
-
\t\t\tmake -j$$(nproc) >/dev/null; \\
|
|
1603
|
-
\t\t\tmake install_sw >/dev/null; \\
|
|
1604
|
-
\t\t\tcd /tmp; \\
|
|
1605
|
-
\t\t\tcurl -sL https://ftp.postgresql.org/pub/source/v16.4/postgresql-16.4.tar.gz | tar xz; \\
|
|
1606
|
-
\t\t\tcd postgresql-16.4; \\
|
|
1607
|
-
\t\t\tCPPFLAGS="-I/tmp/openssl-install/include" LDFLAGS="-L/tmp/openssl-install/lib" \\
|
|
1608
|
-
\t\t\t\t./configure --without-readline --without-zlib --without-icu --with-ssl=openssl --prefix=/tmp/pg-install >/dev/null; \\
|
|
1609
|
-
\t\t\tmake -C src/include install >/dev/null; \\
|
|
1610
|
-
\t\t\tmake -C src/interfaces/libpq -j$$(nproc) install >/dev/null; \\
|
|
1611
|
-
\t\t\tcd /tmp; \\
|
|
1612
|
-
\t\t\tgem fetch pg -v #{pg_version} --platform ruby >/dev/null; \\
|
|
1613
|
-
\t\t\tgem unpack pg-#{pg_version}.gem --target=/tmp/gemsrc >/dev/null; \\
|
|
1614
|
-
\t\t\tcd /tmp/gemsrc/pg-#{pg_version}/ext; \\
|
|
1615
|
-
\t\t\truby -I.. -I../lib extconf.rb --with-pg-include=/tmp/pg-install/include --with-pg-lib=/tmp/pg-install/lib >/dev/null; \\
|
|
1616
|
-
\t\t\tmake >/dev/null; \\
|
|
1617
|
-
\t\t\tcp pg_ext.so /out/pg_ext.so; \\
|
|
1618
|
-
\t\t\tcp /tmp/pg-install/lib/libpq.so.5.16 /out/libpq.so.5.16; \\
|
|
1619
|
-
\t\t'
|
|
1620
|
-
|
|
1621
|
-
# `find`, not a hardcoded gem-version path — survives #{domain}'s
|
|
1622
|
-
# own pg version bumping in its Gemfile.lock without this Makefile
|
|
1623
|
-
# needing regeneration to match. libpq.so.5.16 lands beside the
|
|
1624
|
-
# rest of #{web_logical_id}'s code at /var/task/lib — the ruby3.2
|
|
1625
|
-
# base image's OWN baked-in LD_LIBRARY_PATH default already searches
|
|
1626
|
-
# that exact path, so no rpath patching AND no explicit
|
|
1627
|
-
# LD_LIBRARY_PATH override belongs in template.yaml (setting one
|
|
1628
|
-
# there REPLACES the image's full default list instead of extending
|
|
1629
|
-
# it — a real, live "libcrypt.so.1: cannot open shared object file"
|
|
1630
|
-
# caught this — #{web_logical_id}'s own Environment no longer sets
|
|
1631
|
-
# it at all, on purpose).
|
|
1632
|
-
.PHONY: patch-pg-native
|
|
1633
|
-
patch-pg-native: $(PG_NATIVE_DIR)/pg_ext.so
|
|
1634
|
-
\t@PG_EXT=$$(find .aws-sam/build/#{web_logical_id}/vendor/bundle -path "*/pg-*/lib/3.2/pg_ext.so" | head -1); \\
|
|
1635
|
-
\t\ttest -n "$$PG_EXT" || { echo "patch-pg-native: no pg_ext.so found under .aws-sam/build/#{web_logical_id} — run sam build --use-container #{web_logical_id} first"; exit 1; }; \\
|
|
1636
|
-
\t\tcp $(PG_NATIVE_DIR)/pg_ext.so "$$PG_EXT"
|
|
1637
|
-
\t@mkdir -p .aws-sam/build/#{web_logical_id}/lib
|
|
1638
|
-
\tcp $(PG_NATIVE_DIR)/libpq.so.5.16 .aws-sam/build/#{web_logical_id}/lib/libpq.so.5.16
|
|
1639
|
-
\tln -sf libpq.so.5.16 .aws-sam/build/#{web_logical_id}/lib/libpq.so.5
|
|
1640
|
-
|
|
1641
|
-
PGNATIVE
|
|
1642
|
-
#{google_oauth_present ? <<~SYNCOAUTH : ""}
|
|
1643
|
-
# Owns #{stack_name}-web-google-oauth's WHOLE lifecycle (create on
|
|
1644
|
-
# first run, update every run after — idempotent, cheap, no reason
|
|
1645
|
-
# to cache the way patch-pg-native's own multi-minute build does) —
|
|
1646
|
-
# deliberately OUTSIDE CloudFormation, straight from #{domain}'s own
|
|
1647
|
-
# gitignored .env.local, so the real client_id/secret never lands in
|
|
1648
|
-
# this generated, git-TRACKED template.yaml as plaintext. Read by
|
|
1649
|
-
# name (`{{resolve:secretsmanager:#{stack_name}-web-google-oauth:...}}`,
|
|
1650
|
-
# template.yaml's own Environment — #{rust_web ? logical_id : web_logical_id}'s
|
|
1651
|
-
# own), not by `!Ref` — see that comment for why this secret isn't a
|
|
1652
|
-
# stack resource at all. NOT nested inside patch-pg-native's own
|
|
1653
|
-
# pg_version gate above -- a real, live "No rule to make target
|
|
1654
|
-
# `sync-google-oauth'" caught that this target used to only exist
|
|
1655
|
-
# when a Ruby WebFunction (with its own pg gem) was also present,
|
|
1656
|
-
# even though Google OAuth itself has nothing to do with pg at all.
|
|
1657
|
-
.PHONY: sync-google-oauth
|
|
1658
|
-
sync-google-oauth:
|
|
1659
|
-
\t@CLIENT_ID=$$(grep '^GOOGLE_CLIENT_ID=' #{domain}/.env.local | cut -d= -f2-); \\
|
|
1660
|
-
\t\tCLIENT_SECRET=$$(grep '^GOOGLE_CLIENT_SECRET=' #{domain}/.env.local | cut -d= -f2-); \\
|
|
1661
|
-
\t\ttest -n "$$CLIENT_ID" -a -n "$$CLIENT_SECRET" || { echo "sync-google-oauth: #{domain}/.env.local is missing GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET"; exit 1; }; \\
|
|
1662
|
-
\t\tSECRET_JSON=$$(ruby -rjson -e 'puts JSON.generate({client_id: ARGV[0], client_secret: ARGV[1]})' "$$CLIENT_ID" "$$CLIENT_SECRET"); \\
|
|
1663
|
-
\t\taws secretsmanager put-secret-value --secret-id #{stack_name}-web-google-oauth --secret-string "$$SECRET_JSON" >/dev/null 2>&1 || \\
|
|
1664
|
-
\t\taws secretsmanager create-secret --name #{stack_name}-web-google-oauth --secret-string "$$SECRET_JSON" >/dev/null
|
|
1665
|
-
|
|
1666
|
-
SYNCOAUTH
|
|
1667
|
-
# `make deploy` — THE one command. Chains `sam build`, `sam deploy`
|
|
1668
|
-
# (creates/updates the VPC+RDS+Lambda stack), then `mint-era` (safe
|
|
1669
|
-
# to run every time — a second boot against an already-held era 1
|
|
1670
|
-
# is a no-op, era_resolver.rb's own quiet-reboot branch) so a fresh
|
|
1671
|
-
# database always ends this command with era 1 actually held, not a
|
|
1672
|
-
# separate step someone has to remember. No hand-run AWS CLI
|
|
1673
|
-
# sequence anywhere in this path.
|
|
1674
|
-
#{google_oauth_present ? <<~OAUTHNOTE : ""}
|
|
1675
|
-
# deploy's own last step below looks up #{web_logical_id}'s
|
|
1676
|
-
# CURRENTLY deployed Function URL live (not tracked anywhere
|
|
1677
|
-
# generated) and passes it as WebRedirectBaseUrl -- see that
|
|
1678
|
-
# Parameter's own comment, above, for why GOOGLE_REDIRECT_URI can't
|
|
1679
|
-
# just be a CloudFormation intrinsic. Empty on a genuine first
|
|
1680
|
-
# deploy (before the Url exists at all); every deploy after that
|
|
1681
|
-
# finds the real, STABLE hostname (confirmed unchanged across every
|
|
1682
|
-
# redeploy this session) and self-heals GOOGLE_REDIRECT_URI to match.
|
|
1683
|
-
OAUTHNOTE
|
|
1684
|
-
#{shared ? <<~SHAREDNOTE : ""}
|
|
1685
|
-
# deploy's own last step below looks up #{owner_domain_name}'s live
|
|
1686
|
-
# stack Outputs (never tracked anywhere generated -- same reasoning
|
|
1687
|
-
# as WebRedirectBaseUrl's own live lookup above, and the SAME
|
|
1688
|
-
# `aws cloudformation describe-stacks` pattern mint-era's own eval
|
|
1689
|
-
# chain, below, already proves works against a sibling stack) and
|
|
1690
|
-
# passes them as this template's own Owning* Parameters.
|
|
1691
|
-
SHAREDNOTE
|
|
1692
|
-
.PHONY: deploy mint-era
|
|
1693
|
-
deploy:
|
|
1694
|
-
#{if dispatch_none
|
|
1695
|
-
"\t# ONE build only -- dispatch \"None\" means there is no\n\t# #{logical_id} of any kind (no cargo-lambda, no rust artifact, no\n\t# verify-parity: nothing rust/host-shaped for this domain at all),\n\t# so unlike every other WebFunction-carrying domain there is no\n\t# second build to protect from `sam build <resource>`'s own\n\t# WIPES-.aws-sam/build/-first behavior (build-#{logical_id}'s own\n\t# sibling comment on why that matters for domains that DO have one)\n\t# and nothing to restore afterward.\n\tsam build --use-container #{web_logical_id}\n\t$(MAKE) patch-pg-native"
|
|
1696
|
-
elsif pg_version
|
|
1697
|
-
"\t# TWO SEPARATE builds, not one `--use-container` run -- that flag is\n\t# global to `sam build`, but #{logical_id} deliberately builds on\n\t# THIS machine's own toolchain (cargo-lambda already cross-compiles\n\t# to arm64 without a container -- build-#{logical_id}'s own comment\n\t# on why), which a generic provided.al2023 container doesn't have\n\t# at all (a real, live \"Make Failed\" caught this). Only #{web_logical_id}\n\t# needs the container, to cross-compile pg's native extension for\n\t# Amazon Linux -- `sam build <resource>` WIPES .aws-sam/build/ before\n\t# building just the one named, so #{logical_id}'s own build gets\n\t# restored below (restore-#{logical_id}-build's own comment on why).\n\tsam build #{logical_id}\n\tsam build --use-container #{web_logical_id}\n\t$(MAKE) restore-#{logical_id}-build\n\t$(MAKE) patch-pg-native\n\t$(MAKE) verify-parity-#{logical_id}"
|
|
1698
|
-
elsif web_handler_present
|
|
1699
|
-
"\t# TWO SEPARATE builds -- #{web_logical_id} has no native extension to\n\t# cross-compile (no `pg` in #{domain}/Gemfile.lock), so a plain `sam\n\t# build` targets it fine; #{logical_id} still gets its own `sam build\n\t# <resource>` first, then restored below -- `sam build <resource>`\n\t# WIPES .aws-sam/build/ before building just the one named, so the\n\t# second call here would otherwise erase #{logical_id}'s own build\n\t# (restore-#{logical_id}-build's own comment on why).\n\tsam build #{logical_id}\n\tsam build #{web_logical_id}\n\t$(MAKE) restore-#{logical_id}-build\n\t$(MAKE) verify-parity-#{logical_id}"
|
|
1700
|
-
else
|
|
1701
|
-
"\tsam build\n\t$(MAKE) verify-parity-#{logical_id}"
|
|
1702
|
-
end}
|
|
1703
|
-
#{shared ? "" : <<~PREDEPLOYBRIDGE.each_line.map { |l| "\t" + l }.join.rstrip
|
|
1704
|
-
# THE TRANSACTION-SAFETY GAP, closed for the case that actually
|
|
1705
|
-
# matters: `sam deploy` below is what flips this Lambda's own
|
|
1706
|
-
# HECKS_DOMAIN/HECKS_SCHEMA env vars live — the INSTANT it
|
|
1707
|
-
# completes, real traffic can hit a domain/schema combination
|
|
1708
|
-
# whose era history hasn't been bridged yet if THIS domain was
|
|
1709
|
-
# just renamed (formerly_known_as) or its schema just moved.
|
|
1710
|
-
# `mint-era` already bridges that (era_resolver.rb's own
|
|
1711
|
-
# rename_domain! path, idempotent either way) — it only ran
|
|
1712
|
-
# AFTER `sam deploy` before this existed, which is exactly the
|
|
1713
|
-
# order that left a real live window open the one time this was
|
|
1714
|
-
# actually run in anger (an SSM tunnel flaked mid-bridge, after
|
|
1715
|
-
# the Lambda had already flipped over).
|
|
1716
|
-
#
|
|
1717
|
-
# STILL RUNS AFTER TOO (unchanged, below) — this pre-check
|
|
1718
|
-
# cannot replace that: a domain's VERY FIRST deploy has no
|
|
1719
|
-
# stack, hence no RDS, hence nothing to tunnel to yet, so
|
|
1720
|
-
# mint-era's real first run has to stay where it always was,
|
|
1721
|
-
# once `sam deploy` has just created that RDS instance. This
|
|
1722
|
-
# only ADDS a second, EARLIER run for a domain that already has
|
|
1723
|
-
# a live stack (a rename or an ordinary redeploy) — cheap and
|
|
1724
|
-
# safe either way, since mint-era's own quiet-reboot path is a
|
|
1725
|
-
# no-op the moment nothing has actually changed.
|
|
1726
|
-
#
|
|
1727
|
-
# See predeploy_bridge_shell's own comment (hecks deploy project,
|
|
1728
|
-
# above, right before deploy_recipe_lines) for why this branches
|
|
1729
|
-
# on google_oauth_present at all -- the short version: adding
|
|
1730
|
-
# Google OAuth to an EXISTING stack would otherwise deadlock this
|
|
1731
|
-
# exact pre-check against outputs the upcoming sam deploy (not
|
|
1732
|
-
# yet run) hasn't created.
|
|
1733
|
-
#{predeploy_bridge_shell}
|
|
1734
|
-
PREDEPLOYBRIDGE
|
|
1735
|
-
}
|
|
1736
|
-
#{deploy_recipe_lines}
|
|
1737
|
-
\t$(MAKE) mint-era
|
|
1738
|
-
MAKE
|
|
1739
|
-
|
|
1740
|
-
# Everything `sam deploy --guided` would ask interactively is already
|
|
1741
|
-
# known from the domain's own `deployed_to("AwsLambda")` block.
|
|
1742
|
-
samconfig_toml = <<~TOML
|
|
1743
|
-
# GENERATED by hecks deploy project #{domain} — re-run it to refresh
|
|
1744
|
-
# this file rather than hand-editing.
|
|
1745
|
-
version = 0.1
|
|
1746
|
-
|
|
1747
|
-
[default.deploy.parameters]
|
|
1748
|
-
stack_name = "#{stack_name}"
|
|
1749
|
-
region = "#{region}"
|
|
1750
|
-
resolve_s3 = true
|
|
1751
|
-
s3_prefix = "#{stack_name}"
|
|
1752
|
-
capabilities = "CAPABILITY_IAM"
|
|
1753
|
-
confirm_changeset = false
|
|
1754
|
-
disable_rollback = false
|
|
1755
|
-
TOML
|
|
1756
|
-
|
|
1757
|
-
files = { "template.yaml" => template_yaml }
|
|
1758
|
-
files["bastion.yaml"] = bastion_yaml if bastion_yaml
|
|
1759
|
-
files["Makefile"] = makefile_content
|
|
1760
|
-
files["samconfig.toml"] = samconfig_toml
|
|
1761
|
-
files
|
|
1762
80
|
end
|
|
81
|
+
private_class_method :with_pii_distribution
|
|
82
|
+
|
|
83
|
+
# Surgical removal, post-render, not a nested conditional heredoc: a conditional nested
|
|
84
|
+
# heredoc broke embedded multi-line `#{shared ? ... : ...}` strings inside the resource
|
|
85
|
+
# block, which assume exactly one reindentation layer.
|
|
86
|
+
def without_dispatch_function(text, stack)
|
|
87
|
+
function = Regexp.escape(stack.logical_id)
|
|
88
|
+
# Non-greedy through the resource's own FunctionUrlConfig/AuthType closing pair, so
|
|
89
|
+
# WebFunction's own later "AuthType:" can't match.
|
|
90
|
+
text = text.sub(/^ #{function}:\n.*?\n AuthType: (?:NONE|AWS_IAM)\n/m, "")
|
|
91
|
+
# Removes the now-dangling `!Ref` to the resource just deleted above: SAM refuses an
|
|
92
|
+
# unresolved Ref at package time, not silently.
|
|
93
|
+
text = text.sub(/^ - LambdaInvokePolicy:\n FunctionName: !Ref #{function}\n/, "")
|
|
94
|
+
# WebFunction's own URL becomes the stack's only "FunctionUrl".
|
|
95
|
+
text.sub(web_url_output(stack), " FunctionUrl:\n Value: !GetAtt #{stack.web_logical_id}Url.FunctionUrl\n")
|
|
96
|
+
end
|
|
97
|
+
private_class_method :without_dispatch_function
|
|
1763
98
|
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
def pii_cloudfront_yaml(fronted_logical_id:, use_oac:, geo_restriction_type:, geo_restriction_countries:)
|
|
1770
|
-
reindent = ->(text) { text.each_line.map { |line| line.strip.empty? ? line : " #{line}" }.join }
|
|
1771
|
-
|
|
1772
|
-
# Not pre-reindented (unlike the return value as a whole, below) — each
|
|
1773
|
-
# is spliced back into the still-being-dedented `RESOURCES` heredoc via
|
|
1774
|
-
# `#{...}`, which the outer `reindent.call` already shifts by 2
|
|
1775
|
-
# spaces once; reindenting here too would double it.
|
|
1776
|
-
origin_access_control = use_oac ? <<~OAC : ""
|
|
1777
|
-
PiiOriginAccessControl:
|
|
1778
|
-
Type: AWS::CloudFront::OriginAccessControl
|
|
1779
|
-
Properties:
|
|
1780
|
-
OriginAccessControlConfig:
|
|
1781
|
-
Name: !Sub "${AWS::StackName}-pii-oac"
|
|
1782
|
-
OriginAccessControlOriginType: lambda
|
|
1783
|
-
SigningBehavior: always
|
|
1784
|
-
SigningProtocol: sigv4
|
|
1785
|
-
OAC
|
|
1786
|
-
|
|
1787
|
-
invoke_permission = <<~PERMISSION
|
|
1788
|
-
PiiLambdaInvokePermission:
|
|
1789
|
-
Type: AWS::Lambda::Permission
|
|
1790
|
-
Properties:
|
|
1791
|
-
Action: lambda:InvokeFunctionUrl
|
|
1792
|
-
FunctionName: !Ref #{fronted_logical_id}
|
|
1793
|
-
Principal: cloudfront.amazonaws.com
|
|
1794
|
-
SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${PiiDistribution}"
|
|
1795
|
-
FunctionUrlAuthType: #{use_oac ? "AWS_IAM" : "NONE"}
|
|
1796
|
-
PERMISSION
|
|
1797
|
-
|
|
1798
|
-
countries_yaml = geo_restriction_countries.map { |code| " - #{code}" }.join("\n")
|
|
1799
|
-
|
|
1800
|
-
reindent.call(<<~RESOURCES)
|
|
1801
|
-
PiiAccessLogsBucket:
|
|
1802
|
-
Type: AWS::S3::Bucket
|
|
1803
|
-
Properties:
|
|
1804
|
-
# `BucketOwnerPreferred`, not the newer `BucketOwnerEnforced`
|
|
1805
|
-
# default — CloudFront's own classic access-log delivery
|
|
1806
|
-
# (`Logging:`, on PiiDistribution below) still authorizes itself
|
|
1807
|
-
# via a canned ACL (`AccessControlTranslation` between accounts is
|
|
1808
|
-
# a distinct, newer mechanism this bucket has no other account to
|
|
1809
|
-
# need), which an ACLs-disabled bucket refuses outright.
|
|
1810
|
-
OwnershipControls:
|
|
1811
|
-
Rules:
|
|
1812
|
-
- ObjectOwnership: BucketOwnerPreferred
|
|
1813
|
-
AccessControl: LogDeliveryWrite
|
|
1814
|
-
LifecycleConfiguration:
|
|
1815
|
-
Rules:
|
|
1816
|
-
- Id: ExpirePiiAccessLogs
|
|
1817
|
-
Status: Enabled
|
|
1818
|
-
ExpirationInDays: 365
|
|
1819
|
-
|
|
1820
|
-
PiiResponseHeadersPolicy:
|
|
1821
|
-
Type: AWS::CloudFront::ResponseHeadersPolicy
|
|
1822
|
-
Properties:
|
|
1823
|
-
ResponseHeadersPolicyConfig:
|
|
1824
|
-
Name: !Sub "${AWS::StackName}-pii-headers"
|
|
1825
|
-
SecurityHeadersConfig:
|
|
1826
|
-
StrictTransportSecurity:
|
|
1827
|
-
AccessControlMaxAgeSec: 63072000
|
|
1828
|
-
IncludeSubdomains: true
|
|
1829
|
-
Override: true
|
|
1830
|
-
ContentTypeOptions:
|
|
1831
|
-
Override: true
|
|
1832
|
-
FrameOptions:
|
|
1833
|
-
FrameOption: DENY
|
|
1834
|
-
Override: true
|
|
1835
|
-
ReferrerPolicy:
|
|
1836
|
-
ReferrerPolicy: same-origin
|
|
1837
|
-
Override: true
|
|
1838
|
-
XSSProtection:
|
|
1839
|
-
ModeBlock: true
|
|
1840
|
-
Protection: true
|
|
1841
|
-
Override: true
|
|
1842
|
-
|
|
1843
|
-
PiiWebAcl:
|
|
1844
|
-
Type: AWS::WAFv2::WebACL
|
|
1845
|
-
Properties:
|
|
1846
|
-
Name: !Sub "${AWS::StackName}-pii-waf"
|
|
1847
|
-
Scope: CLOUDFRONT
|
|
1848
|
-
DefaultAction:
|
|
1849
|
-
Allow: {}
|
|
1850
|
-
VisibilityConfig:
|
|
1851
|
-
SampledRequestsEnabled: true
|
|
1852
|
-
CloudWatchMetricsEnabled: true
|
|
1853
|
-
MetricName: !Sub "${AWS::StackName}PiiWebAcl"
|
|
1854
|
-
Rules:
|
|
1855
|
-
- Name: AWSManagedRulesCommonRuleSet
|
|
1856
|
-
Priority: 0
|
|
1857
|
-
OverrideAction:
|
|
1858
|
-
None: {}
|
|
1859
|
-
Statement:
|
|
1860
|
-
ManagedRuleGroupStatement:
|
|
1861
|
-
VendorName: AWS
|
|
1862
|
-
Name: AWSManagedRulesCommonRuleSet
|
|
1863
|
-
VisibilityConfig:
|
|
1864
|
-
SampledRequestsEnabled: true
|
|
1865
|
-
CloudWatchMetricsEnabled: true
|
|
1866
|
-
MetricName: !Sub "${AWS::StackName}PiiCommonRuleSet"
|
|
1867
|
-
- Name: AWSManagedRulesKnownBadInputsRuleSet
|
|
1868
|
-
Priority: 1
|
|
1869
|
-
OverrideAction:
|
|
1870
|
-
None: {}
|
|
1871
|
-
Statement:
|
|
1872
|
-
ManagedRuleGroupStatement:
|
|
1873
|
-
VendorName: AWS
|
|
1874
|
-
Name: AWSManagedRulesKnownBadInputsRuleSet
|
|
1875
|
-
VisibilityConfig:
|
|
1876
|
-
SampledRequestsEnabled: true
|
|
1877
|
-
CloudWatchMetricsEnabled: true
|
|
1878
|
-
MetricName: !Sub "${AWS::StackName}PiiKnownBadInputs"
|
|
1879
|
-
|
|
1880
|
-
#{origin_access_control}
|
|
1881
|
-
PiiDistribution:
|
|
1882
|
-
Type: AWS::CloudFront::Distribution
|
|
1883
|
-
Properties:
|
|
1884
|
-
DistributionConfig:
|
|
1885
|
-
Enabled: true
|
|
1886
|
-
HttpVersion: http2
|
|
1887
|
-
WebACLId: !GetAtt PiiWebAcl.Arn
|
|
1888
|
-
Restrictions:
|
|
1889
|
-
GeoRestriction:
|
|
1890
|
-
RestrictionType: #{geo_restriction_type}
|
|
1891
|
-
Locations:#{geo_restriction_type == "none" ? " []" : "\n" + countries_yaml}
|
|
1892
|
-
Logging:
|
|
1893
|
-
Bucket: !GetAtt PiiAccessLogsBucket.RegionalDomainName
|
|
1894
|
-
IncludeCookies: false
|
|
1895
|
-
Prefix: cloudfront/
|
|
1896
|
-
Origins:
|
|
1897
|
-
- Id: PiiOrigin
|
|
1898
|
-
# `!Select [2, !Split ["/", ...]]` — the standard AWS-documented
|
|
1899
|
-
# way to pull the bare hostname out of a Function URL's own
|
|
1900
|
-
# `https://<id>.lambda-url.<region>.on.aws/` shape for use as a
|
|
1901
|
-
# CustomOriginConfig DomainName, which admits no scheme or path.
|
|
1902
|
-
DomainName: !Select [2, !Split ["/", !GetAtt #{fronted_logical_id}Url.FunctionUrl]]
|
|
1903
|
-
CustomOriginConfig:
|
|
1904
|
-
OriginProtocolPolicy: https-only
|
|
1905
|
-
OriginSSLProtocols: [TLSv1.2]
|
|
1906
|
-
#{use_oac ? " OriginAccessControlId: !Ref PiiOriginAccessControl" : ""}
|
|
1907
|
-
DefaultCacheBehavior:
|
|
1908
|
-
TargetOriginId: PiiOrigin
|
|
1909
|
-
ViewerProtocolPolicy: redirect-to-https
|
|
1910
|
-
AllowedMethods: [GET, HEAD, OPTIONS, PUT, PATCH, POST, DELETE]
|
|
1911
|
-
CachedMethods: [GET, HEAD]
|
|
1912
|
-
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
|
|
1913
|
-
OriginRequestPolicyId: 216adef6-5c7f-47e4-b989-5492eafa07d3
|
|
1914
|
-
ResponseHeadersPolicyId: !Ref PiiResponseHeadersPolicy
|
|
1915
|
-
|
|
1916
|
-
#{invoke_permission}
|
|
1917
|
-
RESOURCES
|
|
99
|
+
def web_url_output(stack)
|
|
100
|
+
function = Regexp.escape(stack.logical_id)
|
|
101
|
+
web = Regexp.escape(stack.web_logical_id)
|
|
102
|
+
Regexp.new(["^ FunctionUrl:\\n Value: !GetAtt #{function}Url\\.FunctionUrl\\n",
|
|
103
|
+
" WebFunctionUrl:\\n Value: !GetAtt #{web}Url\\.FunctionUrl\\n"].join)
|
|
1918
104
|
end
|
|
105
|
+
private_class_method :web_url_output
|
|
1919
106
|
|
|
107
|
+
def bastion_yaml(stack)
|
|
108
|
+
Shared.bastion_yaml(stack.network, domain: stack.domain, stack_name: stack.stack_name,
|
|
109
|
+
bastion_parameters: stack.bastion_parameters)
|
|
110
|
+
end
|
|
111
|
+
private_class_method :bastion_yaml
|
|
1920
112
|
end
|
|
1921
113
|
end
|
|
1922
114
|
end
|