hecks 3.2.1 → 3.3.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/lib/hecks/bluebook/assembly/contracts.rb +2 -1
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -39
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/need_word.rb +50 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +6 -1
- data/lib/hecks/bluebook/query.rb +8 -5
- data/lib/hecks/cli/interview_draft.rb +86 -31
- data/lib/hecks/cli/interview_session.rb +77 -26
- data/lib/hecks/cli/interview_shape.rb +179 -0
- data/lib/hecks/doors/cli_runner.rb +14 -1
- data/lib/hecks/doors/launcher_options.rb +13 -3
- data/lib/hecks/doors/mcp_door_scope.rb +2 -1
- data/lib/hecks/doors.rb +1 -1
- data/lib/hecks/embryonaut_bluebook/manifest.rb +117 -0
- data/lib/hecks/embryonaut_bluebook/registry.rb +209 -0
- data/lib/hecks/embryonaut_bluebook/vendor.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +2 -0
- data/lib/hecks/fuzzing/qa_settings.rb +15 -34
- data/lib/hecks/gate/stages.yml +113 -8
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +10 -1
- data/lib/hecks/hecks/adapters/first_admin.rb +108 -0
- data/lib/hecks/hecks/adapters/git.rb +59 -0
- data/lib/hecks/hecks/adapters/in_process_access.rb +25 -0
- data/lib/hecks/hecks/adapters/in_process_boot.rb +2 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +18 -3
- data/lib/hecks/hecks/adapters/site_toolchain.rb +67 -0
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +21 -0
- data/lib/hecks/hecks/custodian.behaviors +6 -0
- data/lib/hecks/hecks/custodian.bluebook +217 -0
- data/lib/hecks/hecks/hecks.hecksagon +24 -0
- data/lib/hecks/hecks/hecks.world +8 -3
- data/lib/hecks/hecks/site.world +5 -2
- data/lib/hecks/language/bluebook/query.bluebook +25 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +213 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +1 -1
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/projections/deploy/box/hosting.rb +170 -0
- data/lib/hecks/projections/deploy/box/hosting_settings.rb +123 -0
- data/lib/hecks/projections/deploy/box/settings.rb +17 -4
- data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +33 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +79 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +77 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +12 -0
- data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +24 -0
- data/lib/hecks/projections/deploy/box/templates/smoke-after-deploy.sh.tmpl +271 -0
- data/lib/hecks/projections/deploy/box.rb +15 -2
- data/lib/hecks/projections/deploy/fargate.rb +3 -3
- data/lib/hecks/projections/deploy/lambda.rb +8 -8
- data/lib/hecks/projections/model/deviations.rb +1 -1
- data/lib/hecks/projections/site/live_edge.rb +162 -0
- data/lib/hecks/quality_control/adapters/git_pr.rb +0 -14
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +32 -16
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_seed_targets.rb +1 -2
- data/lib/hecks/quality_control/quality_control.bluebook +76 -124
- data/lib/hecks/runtime/interpreting.rb +3 -22
- data/lib/hecks/runtime/needs.rb +48 -0
- data/lib/hecks/runtime/query_interpreter.rb +3 -0
- data/lib/hecks/runtime/registry/verification.rb +3 -3
- data/lib/hecks/rust_build/host.rb +147 -0
- data/lib/hecks/rust_build/kernel_input.rb +40 -18
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +0 -18
- data/lib/hecks/rust_build/project_rust.rb +3 -10
- data/lib/hecks/rust_build.rb +1 -0
- data/lib/hecks/site/bluebook/site.bluebook +53 -1
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/sme/bluebook/sme.bluebook +128 -1
- data/lib/hecks/tools/gate.rb +1 -1
- data/lib/hecks/tools/rust_comment_style.rb +1 -3
- data/lib/hecks/tools/tools_doc.rb +204 -0
- data/lib/hecks/tools.rb +1 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +151 -0
- data/qa/settings.yml +1 -2
- data/rust/build/src/lineage_pass.rs +30 -15
- data/rust/build/src/main.rs +4 -2
- data/rust/build/src/pipeline.rs +8 -19
- data/rust/codegen/src/bridging.rs +7 -7
- data/rust/codegen/src/json.rs +214 -23
- data/rust/codegen/src/literal.rs +6 -3
- data/rust/codegen/src/main.rs +20 -9
- data/rust/codegen/src/naming.rs +4 -2
- data/rust/codegen/src/optional_pass.rs +226 -0
- data/rust/{build → codegen}/src/sidecars.rs +9 -5
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +1 -1
- data/rust/host/src/dispatch.rs +5 -0
- data/rust/host/src/needs.rs +99 -2
- data/rust/host/src/resend.rs +4 -4
- data/rust/host/src/ui_schema.rs +1 -1
- data/rust/parser/src/emit.rs +12 -0
- data/rust/parser/src/ir.rs +2 -0
- data/rust/parser/src/keywords.rs +2 -0
- data/rust/parser/src/main.rs +1 -0
- data/rust/parser/src/parse/command.rs +2 -44
- data/rust/parser/src/parse/mod.rs +1 -0
- data/rust/parser/src/parse/needs.rs +60 -0
- data/rust/parser/src/parse/query.rs +3 -0
- data/rust/src/kernel/cli.rs +16 -6
- data/rust/src/kernel/needs.rs +104 -9
- metadata +22 -8
- data/lib/hecks/rust_build/append_optionals.rb +0 -79
- data/lib/hecks/rust_build/rust_literal.rb +0 -33
- data/lib/hecks/three_zero/forms.yml +0 -127
- data/lib/hecks/three_zero.rb +0 -12
- data/rust/build/src/json.rs +0 -421
- data/rust/build/src/optional_pass.rs +0 -150
|
@@ -630,6 +630,219 @@ Hecks.bluebook "Bluebook" do
|
|
|
630
630
|
label: "does this change touch anything rspec_postgres_io_parallel covers?"
|
|
631
631
|
end
|
|
632
632
|
|
|
633
|
+
# The dials of the hecks_qa loop: what each is called, the type of value it takes and what it
|
|
634
|
+
# does. qa/settings.yml holds the values, which are edited and reviewed as a diff; this table
|
|
635
|
+
# holds the rest, so the loader (`Hecks::Fuzzing::QaSettings`) checks the file against it and
|
|
636
|
+
# the QualityControl chapter reads each value as a constant (`QualityControlDials::PR_CAP_PER_DAY`
|
|
637
|
+
# for `pr_cap_per_day`) without a second list of the names or their types. `type` is Integer,
|
|
638
|
+
# Numeric (an Integer or a Float), Boolean, String, Array or Hash. A dial's nested shape, where it
|
|
639
|
+
# has one, is checked by the loader.
|
|
640
|
+
value_object "QaDial" do
|
|
641
|
+
attribute :name, String
|
|
642
|
+
attribute :type, String
|
|
643
|
+
attribute :meaning, String
|
|
644
|
+
|
|
645
|
+
member name: "cadence_seconds", type: "Integer",
|
|
646
|
+
meaning: "Minimum gap in seconds between one tick's report and the next dispatch; 0 is immediately. Read by the `/loop hecks_qa` caller, not by any script."
|
|
647
|
+
member name: "pr_cap_per_day", type: "Integer",
|
|
648
|
+
meaning: "Most PRs (Patch plus Improvement) `hecks quality_control patch.open` opens per calendar day; 0 is uncapped."
|
|
649
|
+
member name: "widening_tiers", type: "Array",
|
|
650
|
+
meaning: "Fuzz depth by `Target.clean_streak`, in three discrete tiers, read by `SweepDepth.for_streak`. The last row (`upto: Float::INFINITY`) is the ceiling."
|
|
651
|
+
member name: "sweep_max_parallel", type: "Integer",
|
|
652
|
+
meaning: "How many `hecks quality_control query sweep.run <target>` children `--all` keeps running at once."
|
|
653
|
+
member name: "liveness_fallback_seconds", type: "Integer",
|
|
654
|
+
meaning: "How long the `/loop hecks_qa` caller waits for a tick's report before assuming it stalled."
|
|
655
|
+
member name: "draft_only", type: "Boolean",
|
|
656
|
+
meaning: "Whether `hecks quality_control patch.open` opens its PR as a draft; false opens it ready for review."
|
|
657
|
+
member name: "auto_merge", type: "Boolean",
|
|
658
|
+
meaning: "Whether `hecks quality_control patch.open` queues the PR for auto-merge once it is open."
|
|
659
|
+
member name: "adversarial_fraction", type: "Numeric",
|
|
660
|
+
meaning: "Fraction (0.0..1.0) of generated command steps mutated adversarially; 0 turns the layer off. Higher finds refusal-ordering splits sooner but reaches deep state less often."
|
|
661
|
+
member name: "guided_generation", type: "Boolean",
|
|
662
|
+
meaning: "Coverage-guided generation across one sweep's seeds (`Hecks::Fuzzing::CoverageCampaign`)."
|
|
663
|
+
member name: "corpus_splice_probability", type: "Numeric",
|
|
664
|
+
meaning: "Fraction (0.0..1.0) of seeds that start from a prefix of an earlier seed's new coverage. Off by default: measured, it trades breadth for depth (banking reached 1.41x the deep-state tuples, chess lost 0.12x of all tuples), while favoring rare verbs alone never lost."
|
|
665
|
+
member name: "favor_rare_verbs", type: "Integer",
|
|
666
|
+
meaning: "How many of the least-hit verbs the next seed weights up; 0 turns the lever off."
|
|
667
|
+
member name: "self_consistency_checks", type: "Boolean",
|
|
668
|
+
meaning: "Whether `hecks quality_control query sweep.run` also runs the self-consistency pass (cold rehydration, replay idempotency, value-object JSON round trip) on the same generated sequence."
|
|
669
|
+
member name: "shrink_budget", type: "Integer",
|
|
670
|
+
meaning: "Candidate replays `hecks quality_control query sweep.run` may spend shrinking one surprised check; 0 turns shrinking off. `concurrency` is never shrunk: a race does not reproduce on demand."
|
|
671
|
+
member name: "yield_weight_seconds", type: "Integer",
|
|
672
|
+
meaning: "Seconds of staleness that one point of yield score is worth in `RotationPriority.pick`. Zero would make yield invisible, so pick a number that lets it matter."
|
|
673
|
+
member name: "yield_decay_percent", type: "Integer",
|
|
674
|
+
meaning: "Percent of the prior yield score kept at each release, so recent findings dominate. Applied by the caller: the mutation language cannot express \"decay, then add\" in `Release`."
|
|
675
|
+
member name: "rotation_stale_floor_seconds", type: "Integer",
|
|
676
|
+
meaning: "Once a waiting target is this stale, `RotationPriority.pick` takes it whatever its yield score, so a quiet target cannot starve."
|
|
677
|
+
member name: "persistence_parity_seed_cap", type: "Integer",
|
|
678
|
+
meaning: "Ceiling on `--seeds` for `--persistence-parity`, which pays for real Postgres per dispatch. `hecks quality_control query sweep.run` clamps down, never up, and prints a note."
|
|
679
|
+
member name: "concurrency_seed_cap", type: "Integer",
|
|
680
|
+
meaning: "Ceiling on `--seeds` for the concurrency mode, which needs two disposable schemas per seed."
|
|
681
|
+
member name: "adapter_parity_pairs", type: "Hash",
|
|
682
|
+
meaning: "The two adapters each adapter-parity mode compares, by mode name; a new pairing is a new entry. `adapter_parity_sqlite` needs no `database:`/`schema:` because sqlite has no disposable database."
|
|
683
|
+
member name: "modes", type: "Hash",
|
|
684
|
+
meaning: "Which comparison modes `hecks quality_control query sweep.run` may run: the enabled ones intersected with what `TargetCapabilities.infer(path)` finds in the target. `--modes a,b,c` overrides for one run. `wasm_front` and `adapter_parity_postgres` are off because nothing runs them; the others are built and off because their I/O cost across the rotation is unmeasured. `hecks quality_control query sweep.run` refuses to start when a mode outside `TargetCapabilities::RUNNABLE_MODES` is enabled."
|
|
685
|
+
member name: "role_draw_probability", type: "Numeric",
|
|
686
|
+
meaning: "How often a generated role-gated command step carries a drawn caller (`role:`/`actor_id:`); 0 turns the draw off."
|
|
687
|
+
member name: "dry_run_fraction", type: "Numeric",
|
|
688
|
+
meaning: "Fraction of generated command steps offered as a dry run instead of a real dispatch; 0 is off. Kept low: a dry run creates no state for later steps to act on."
|
|
689
|
+
member name: "generated_domains_per_tick", type: "Integer",
|
|
690
|
+
meaning: "How many generated domains one `hecks quality_control sweep.tick` checks after the rotation sweep; 0 skips the step."
|
|
691
|
+
member name: "generated_domains_rust", type: "Boolean",
|
|
692
|
+
meaning: "Whether generated domains are also compared against a compiled Rust binary built in a scratch copy."
|
|
693
|
+
member name: "generated_domain_seeds", type: "Integer",
|
|
694
|
+
meaning: "Generated sequences run per generated domain."
|
|
695
|
+
member name: "structural_refusal_boundary", type: "Array",
|
|
696
|
+
meaning: "Construct families a skipped query verb may belong to. A skip inside the list is logged Held, one outside it Surprised. Shrink-only: spec/fuzzing/rust_gap_manifest_spec.rb fails when a family here is declared by no committed manifest."
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
# What each retired `bin/` script became in hecks 3.0.0 (ADR 0080, section 7): one row per
|
|
700
|
+
# command or query that answers it, so a script that became several verbs has several rows.
|
|
701
|
+
# hecks project_tools_doc writes the launcher forms into the marked tables of docs/tools.md,
|
|
702
|
+
# rendering each form from the command's own arguments, so a form can neither keep an argument
|
|
703
|
+
# the command dropped nor miss one it gained; the table specs read the same rows.
|
|
704
|
+
#
|
|
705
|
+
# `section` is the part of docs/tools.md the row is listed under (Custodian, Deploy, Site,
|
|
706
|
+
# Codebase or QualityControl); Custodian and Codebase commands live in the Hecks chapter, the
|
|
707
|
+
# others in the chapter of their own name. `script` is `(new)` for a command no `bin/` script
|
|
708
|
+
# preceded. `aggregate` and `verb` name the command (or, in QualityControl, the query) the way
|
|
709
|
+
# the launcher spells it, `aggregate.verb`.
|
|
710
|
+
#
|
|
711
|
+
# The rest is only what the arguments cannot say: `passes` documents the flags a command hands
|
|
712
|
+
# through to the tool it wraps (its `arguments=` word), `note` is a sentence kept beside the
|
|
713
|
+
# form, and `form` replaces the rendered form when a launcher route spells it its own way.
|
|
714
|
+
value_object "RetiredScript" do
|
|
715
|
+
attribute :script, String
|
|
716
|
+
attribute :section, String
|
|
717
|
+
attribute :aggregate, String
|
|
718
|
+
attribute :verb, String
|
|
719
|
+
attribute :passes, String, optional: true
|
|
720
|
+
attribute :note, String, optional: true
|
|
721
|
+
attribute :form, String, optional: true
|
|
722
|
+
|
|
723
|
+
member script: "ir", section: "Custodian", aggregate: "Introspection", verb: "ir"
|
|
724
|
+
member script: "shape", section: "Custodian", aggregate: "Introspection", verb: "shape"
|
|
725
|
+
member script: "stores", section: "Custodian", aggregate: "Introspection", verb: "stores"
|
|
726
|
+
member script: "history", section: "Custodian", aggregate: "Introspection", verb: "history"
|
|
727
|
+
member script: "statements", section: "Custodian", aggregate: "Introspection", verb: "statements"
|
|
728
|
+
member script: "narrate", section: "Custodian", aggregate: "Introspection", verb: "narrate"
|
|
729
|
+
member script: "docs", section: "Custodian", aggregate: "Introspection", verb: "docs"
|
|
730
|
+
member script: "project_diagrams", section: "Custodian", aggregate: "Introspection", verb: "project_diagrams"
|
|
731
|
+
member script: "project_glossary", section: "Custodian", aggregate: "Introspection", verb: "glossary"
|
|
732
|
+
member script: "model_check", section: "Custodian", aggregate: "ModelCheckRun", verb: "model_check"
|
|
733
|
+
member script: "run", section: "Custodian", aggregate: "Operation", verb: "run", form: "hecks run [domain] <verb [name=value …] | script.json | - | '{\"steps\":[…]}'>"
|
|
734
|
+
member script: "project", section: "Custodian", aggregate: "Operation", verb: "refresh_projections"
|
|
735
|
+
member script: "behaviors", section: "Custodian", aggregate: "Operation", verb: "run_behaviors"
|
|
736
|
+
member script: "console", section: "Custodian", aggregate: "Operation", verb: "open_console"
|
|
737
|
+
member script: "follow", section: "Custodian", aggregate: "Operation", verb: "follow"
|
|
738
|
+
member script: "smoke_test", section: "Custodian", aggregate: "Operation", verb: "smoke_test"
|
|
739
|
+
member script: "smoke_http", section: "Custodian", aggregate: "Operation", verb: "smoke_http"
|
|
740
|
+
member script: "check_era", section: "Custodian", aggregate: "Host", verb: "check_era"
|
|
741
|
+
member script: "merge_tail", section: "Custodian", aggregate: "Era", verb: "merge_tail"
|
|
742
|
+
member script: "reattest_era", section: "Custodian", aggregate: "Era", verb: "reattest"
|
|
743
|
+
member script: "backfill_era_projections", section: "Custodian", aggregate: "Era", verb: "backfill_projections"
|
|
744
|
+
member script: "scaffold_translation", section: "Custodian", aggregate: "Era", verb: "scaffold_translation"
|
|
745
|
+
member script: "translation_audit", section: "Custodian", aggregate: "Era", verb: "audit_translation"
|
|
746
|
+
member script: "translation_audit", section: "Custodian", aggregate: "Era", verb: "approve_translation"
|
|
747
|
+
member script: "compact", section: "Custodian", aggregate: "Era", verb: "compact"
|
|
748
|
+
member script: "heki_compact", section: "Custodian", aggregate: "Era", verb: "compact_heki"
|
|
749
|
+
member script: "vendor_bluebook", section: "Custodian", aggregate: "Package", verb: "vendor", note: "waits: a refusal exits 1 with its reason on stderr"
|
|
750
|
+
member script: "project_cli", section: "Custodian", aggregate: "Door", verb: "project_cli"
|
|
751
|
+
member script: "hecks_mcp_door", section: "Custodian", aggregate: "Door", verb: "serve_mcp"
|
|
752
|
+
member script: "project_rust", section: "Custodian", aggregate: "Build", verb: "project_rust"
|
|
753
|
+
member script: "project_wasm", section: "Custodian", aggregate: "Build", verb: "build_wasm"
|
|
754
|
+
member script: "project_host", section: "Custodian", aggregate: "Build", verb: "build_host"
|
|
755
|
+
member script: "project_wasm_browser", section: "Custodian", aggregate: "Build", verb: "build_browser_wasm"
|
|
756
|
+
member script: "rust_coverage", section: "Custodian", aggregate: "Build", verb: "rust_coverage"
|
|
757
|
+
member script: "rust_coverage", section: "Custodian", aggregate: "Build", verb: "check_coverage_allowlist"
|
|
758
|
+
member script: "rust_conformance", section: "Custodian", aggregate: "Build", verb: "check_conformance"
|
|
759
|
+
member script: "rust_conformance_fuzz", section: "Custodian", aggregate: "Build", verb: "fuzz_conformance"
|
|
760
|
+
member script: "fuzz", section: "Custodian", aggregate: "FuzzRun", verb: "fuzz"
|
|
761
|
+
member script: "generate", section: "Custodian", aggregate: "FuzzRun", verb: "generate_sequence"
|
|
762
|
+
member script: "bench", section: "Custodian", aggregate: "FuzzRun", verb: "bench"
|
|
763
|
+
member script: "(new)", section: "Custodian", aggregate: "Era", verb: "hold_first"
|
|
764
|
+
member script: "project_deploy", section: "Deploy", aggregate: "Recipe", verb: "project"
|
|
765
|
+
member script: "lint_deploy_recipes", section: "Deploy", aggregate: "MakefileCheck", verb: "lint"
|
|
766
|
+
member script: "deploy_template_diff", section: "Deploy", aggregate: "TemplateComparison", verb: "diff"
|
|
767
|
+
member script: "project_oidc", section: "Deploy", aggregate: "OidcManifest", verb: "project_oidc"
|
|
768
|
+
member script: "project_tenant", section: "Deploy", aggregate: "Tenant", verb: "provision"
|
|
769
|
+
member script: "project_tenant", section: "Deploy", aggregate: "Tenant", verb: "reprovision"
|
|
770
|
+
member script: "(new)", section: "Site", aggregate: "SiteProjection", verb: "project_site"
|
|
771
|
+
member script: "project_model", section: "Codebase", aggregate: "LanguageRun", verb: "project_model"
|
|
772
|
+
member script: "project_vocabulary", section: "Codebase", aggregate: "LanguageRun", verb: "project_vocabulary"
|
|
773
|
+
member script: "project_rust_vocabulary", section: "Codebase", aggregate: "LanguageRun", verb: "project_rust_vocabulary"
|
|
774
|
+
member script: "project_refusal_wording", section: "Codebase", aggregate: "LanguageRun", verb: "project_refusal_wording"
|
|
775
|
+
member script: "project_reserved_names", section: "Codebase", aggregate: "LanguageRun", verb: "project_reserved_names"
|
|
776
|
+
member script: "project_parser_table", section: "Codebase", aggregate: "LanguageRun", verb: "project_parser_table"
|
|
777
|
+
member script: "project_bootstrap_table", section: "Codebase", aggregate: "LanguageRun", verb: "project_bootstrap_table"
|
|
778
|
+
member script: "project_field_hints", section: "Codebase", aggregate: "LanguageRun", verb: "project_field_hints"
|
|
779
|
+
member script: "expression_projection", section: "Codebase", aggregate: "LanguageRun", verb: "project_expression_tables"
|
|
780
|
+
member script: "reference", section: "Codebase", aggregate: "LanguageRun", verb: "project_reference"
|
|
781
|
+
member script: "evolve", section: "Codebase", aggregate: "LanguageRun", verb: "word_status"
|
|
782
|
+
member script: "evolve", section: "Codebase", aggregate: "LanguageRun", verb: "propose"
|
|
783
|
+
member script: "evolve", section: "Codebase", aggregate: "LanguageRun", verb: "admit"
|
|
784
|
+
member script: "evolve", section: "Codebase", aggregate: "LanguageRun", verb: "deprecate"
|
|
785
|
+
member script: "evolve", section: "Codebase", aggregate: "LanguageRun", verb: "retire"
|
|
786
|
+
member script: "evolve", section: "Codebase", aggregate: "LanguageRun", verb: "rename"
|
|
787
|
+
member script: "evolve", section: "Codebase", aggregate: "LanguageRun", verb: "propose_argument"
|
|
788
|
+
member script: "evolve", section: "Codebase", aggregate: "LanguageRun", verb: "admit_argument"
|
|
789
|
+
member script: "evolve", section: "Codebase", aggregate: "LanguageRun", verb: "deprecate_argument"
|
|
790
|
+
member script: "evolve", section: "Codebase", aggregate: "LanguageRun", verb: "retire_argument"
|
|
791
|
+
member script: "project_kernel_capabilities", section: "Codebase", aggregate: "KernelRun", verb: "project_kernel_capabilities"
|
|
792
|
+
member script: "rust_kernel_coverage", section: "Codebase", aggregate: "KernelRun", verb: "measure_kernel_coverage"
|
|
793
|
+
member script: "check_engine_agreement", section: "Codebase", aggregate: "ConformanceRun", verb: "check_engine_agreement"
|
|
794
|
+
member script: "doc_coverage", section: "Codebase", aggregate: "ConformanceRun", verb: "measure_doc_coverage"
|
|
795
|
+
member script: "argument_gate_matrix", section: "Codebase", aggregate: "ConformanceRun", verb: "argument_gate_matrix"
|
|
796
|
+
member script: "regen_codegen_domains", section: "Codebase", aggregate: "RegenerationRun", verb: "regenerate_corpus"
|
|
797
|
+
member script: "standardize_comments", section: "Codebase", aggregate: "StyleRun", verb: "report_comments"
|
|
798
|
+
member script: "standardize_comments", section: "Codebase", aggregate: "StyleRun", verb: "check_comments"
|
|
799
|
+
member script: "standardize_comments", section: "Codebase", aggregate: "StyleRun", verb: "fix_comments"
|
|
800
|
+
member script: "standardize_comments", section: "Codebase", aggregate: "StyleRun", verb: "write_comment_baseline"
|
|
801
|
+
member script: "standardize_comments", section: "Codebase", aggregate: "StyleRun", verb: "check_comments_unchanged"
|
|
802
|
+
member script: "standardize_comments_rust", section: "Codebase", aggregate: "StyleRun", verb: "report_rust_comments"
|
|
803
|
+
member script: "standardize_comments_rust", section: "Codebase", aggregate: "StyleRun", verb: "check_rust_comments"
|
|
804
|
+
member script: "standardize_comments_rust", section: "Codebase", aggregate: "StyleRun", verb: "fix_rust_comments"
|
|
805
|
+
member script: "canonicalise", section: "Codebase", aggregate: "StyleRun", verb: "canonicalise"
|
|
806
|
+
member script: "codemod_hoist_local_givens", section: "Codebase", aggregate: "CodemodRun", verb: "hoist_local_givens"
|
|
807
|
+
member script: "codemod_implicit_append_fields", section: "Codebase", aggregate: "CodemodRun", verb: "drop_implicit_append_fields"
|
|
808
|
+
member script: "rspec_shard_files", section: "Codebase", aggregate: "TestSuiteRun", verb: "shard_specs"
|
|
809
|
+
member script: "rspec_io_parallel_files", section: "Codebase", aggregate: "TestSuiteRun", verb: "list_io_parallel_specs"
|
|
810
|
+
member script: "rspec_io_parallel_files", section: "Codebase", aggregate: "TestSuiteRun", verb: "write_io_parallel_spec_list"
|
|
811
|
+
member script: "refresh_rspec_runtime_baseline", section: "Codebase", aggregate: "TestSuiteRun", verb: "refresh_runtime_baseline"
|
|
812
|
+
member script: "spec_example", section: "Codebase", aggregate: "TestSuiteRun", verb: "run_spec_example"
|
|
813
|
+
member script: "stress_concurrency_specs", section: "Codebase", aggregate: "TestSuiteRun", verb: "stress_concurrency"
|
|
814
|
+
member script: "regenerate_persistence_legacy_fixtures", section: "Codebase", aggregate: "TestSuiteRun", verb: "regenerate_legacy_fixtures"
|
|
815
|
+
member script: "seed_semantics_corpus", section: "Codebase", aggregate: "TestSuiteRun", verb: "seed_semantics_corpus"
|
|
816
|
+
member script: "pattern-cases", section: "Codebase", aggregate: "TestSuiteRun", verb: "record_pattern_cases"
|
|
817
|
+
member script: "corpus", section: "Codebase", aggregate: "CorpusRun", verb: "rust_domains"
|
|
818
|
+
member script: "corpus", section: "Codebase", aggregate: "CorpusRun", verb: "regen_order"
|
|
819
|
+
member script: "corpus", section: "Codebase", aggregate: "CorpusRun", verb: "corpus_rust_coverage"
|
|
820
|
+
member script: "query_ir", section: "Codebase", aggregate: "CorpusRun", verb: "ir_constructs"
|
|
821
|
+
member script: "query_ir", section: "Codebase", aggregate: "CorpusRun", verb: "ir_duplicates"
|
|
822
|
+
member script: "query_ir", section: "Codebase", aggregate: "CorpusRun", verb: "ir_impact"
|
|
823
|
+
member script: "hecks_query_ir_mcp", section: "Codebase", aggregate: "CorpusRun", verb: "serve_query_ir_mcp"
|
|
824
|
+
member script: "present", section: "Codebase", aggregate: "CorpusRun", verb: "present"
|
|
825
|
+
member script: "release", section: "Codebase", aggregate: "PublishingRun", verb: "publish", note: "without --confirm, the old --dry-run"
|
|
826
|
+
member script: "release_gem", section: "Codebase", aggregate: "PublishingRun", verb: "publish_gem"
|
|
827
|
+
member script: "(new)", section: "Codebase", aggregate: "GateRun", verb: "gate"
|
|
828
|
+
member script: "qa_tick", section: "QualityControl", aggregate: "Sweep", verb: "tick"
|
|
829
|
+
member script: "qa_sweep", section: "QualityControl", aggregate: "Sweep", verb: "run", passes: "--all --seeds N --steps N --adversarial F --role-draw F --dry-run F --self-consistency BOOL --modes a,b --persistence-parity --no-parity"
|
|
830
|
+
member script: "qa_sweep", section: "QualityControl", aggregate: "Target", verb: "release"
|
|
831
|
+
member script: "qa_pr_check", section: "QualityControl", aggregate: "Clearance", verb: "check_pull_requests"
|
|
832
|
+
member script: "qa_open_pr", section: "QualityControl", aggregate: "Patch", verb: "open"
|
|
833
|
+
member script: "qa_open_pr", section: "QualityControl", aggregate: "Improvement", verb: "open"
|
|
834
|
+
member script: "qa_log_bug", section: "QualityControl", aggregate: "Bug", verb: "log"
|
|
835
|
+
member script: "qa_seed_angles", section: "QualityControl", aggregate: "Angle", verb: "seed"
|
|
836
|
+
member script: "qa_seed_targets", section: "QualityControl", aggregate: "Target", verb: "seed"
|
|
837
|
+
member script: "qa_generated_domains", section: "QualityControl", aggregate: "Target", verb: "check_generated_domains", passes: "--domains 3 --start N --forms a,b --seeds 5 --steps 25 --adversarial 0.3 --rust --shrink-budget 200 --domain-shrink-budget 40 --promote dir --name N --from-dials --blueprint F --source F"
|
|
838
|
+
member script: "qa_mine_combinations", section: "QualityControl", aggregate: "Target", verb: "mine_combinations", passes: "--candidates 3 --rust --seeds 5 --steps 25 --adversarial 0.3 --repair-rounds 1 --agent CMD --from DIR --against PATH --brief"
|
|
839
|
+
member script: "qa_domain_novelty", section: "QualityControl", aggregate: "Target", verb: "judge_novelty", passes: "--against PATH …"
|
|
840
|
+
member script: "qa_discover_external_domains", section: "QualityControl", aggregate: "Target", verb: "discover_external_domains", passes: "--projects-dir ~/Projects --max-depth 3 --known-path PATH …"
|
|
841
|
+
member script: "qa_postgres_migrate", section: "QualityControl", aggregate: "Sweep", verb: "migrate_ledger_from_heki", passes: "[aggregate …] --force", note: "a dry run without --force"
|
|
842
|
+
member script: "qa_postgres_role", section: "QualityControl", aggregate: "Sweep", verb: "create_ledger_role"
|
|
843
|
+
member script: "qa_concurrency_racer", section: "QualityControl", aggregate: "Sweep", verb: "race", note: "not user-facing; ProcessPool starts it"
|
|
844
|
+
end
|
|
845
|
+
|
|
633
846
|
# Rust keywords (strict + reserved) a generated identifier must not
|
|
634
847
|
# collide with. ModelCheck reads this; hecks project_reserved_names
|
|
635
848
|
# projects it into rust/codegen/src/reserved_names.rs, so Ruby and Rust
|
|
@@ -83,7 +83,7 @@ Hecks.bluebook "Hecksagon" do
|
|
|
83
83
|
member word: "hecksagon", context: "File", body: "keywords", inner: "Hecksagon", opens: "Hecksagon", fills: ""
|
|
84
84
|
member word: "subscribe", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "subscriptions"
|
|
85
85
|
member word: "attaches", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "attachments"
|
|
86
|
-
# Deprecated spellings of `attaches`; they warn and are removed in 3.
|
|
86
|
+
# Deprecated spellings of `attaches`; they warn and are removed in 3.4.0.
|
|
87
87
|
member word: "uses_framework", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "attachments", status: "deprecated"
|
|
88
88
|
member word: "uses_embryonaut_bluebook", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "attachments", status: "deprecated"
|
|
89
89
|
member word: "port", context: "Hecksagon", body: "keywords", inner: "DomainPort", opens: "DomainPort", fills: "", calls: "port_impl"
|
|
@@ -236,6 +236,11 @@
|
|
|
236
236
|
"verb": "Bluebook::Query.Filter",
|
|
237
237
|
"role": "Language"
|
|
238
238
|
},
|
|
239
|
+
{
|
|
240
|
+
"scope": "bluebook:query.need",
|
|
241
|
+
"verb": "Bluebook::Query.Need",
|
|
242
|
+
"role": "Language"
|
|
243
|
+
},
|
|
239
244
|
{
|
|
240
245
|
"scope": "bluebook:query.option",
|
|
241
246
|
"verb": "Bluebook::Query.Option",
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
require "shellwords"
|
|
2
|
+
require_relative "../scripts"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Projections
|
|
6
|
+
module Deploy
|
|
7
|
+
module Box
|
|
8
|
+
# The hosting scripts an AWS box deploy ships beside its stacks when the world sets
|
|
9
|
+
# `hosting_scripts true`: the steps an operator runs once `make stacks` has created the box.
|
|
10
|
+
# Container names, repositories, parameters and stacks come from the resolved plan, never
|
|
11
|
+
# from this file.
|
|
12
|
+
module Hosting
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# Adds the hosting scripts to a box file map when the world opts in.
|
|
16
|
+
#
|
|
17
|
+
# @param files [Hash{String => String}] the box target's own files, keyed by path
|
|
18
|
+
# @param plan [Settings::Plan] the resolved settings
|
|
19
|
+
# @param region [String] the validated AWS region
|
|
20
|
+
# @return [Hash{String => String}] `files` itself without the opt-in, else a copy with
|
|
21
|
+
# `hosting.mk`, `deploy-service.sh`, `smoke-after-deploy.sh` and `expected-era`
|
|
22
|
+
def extend_files(files, plan:, region:)
|
|
23
|
+
return files unless plan.hosting
|
|
24
|
+
|
|
25
|
+
files.merge(
|
|
26
|
+
"hosting.mk" => hosting_mk(plan),
|
|
27
|
+
"deploy-service.sh" => deploy_service_sh(plan, region),
|
|
28
|
+
"smoke-after-deploy.sh" => smoke_after_deploy_sh(plan, region),
|
|
29
|
+
"expected-era" => Scripts.expected_era(plan.hosting)
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param plan [Settings::Plan] the resolved settings
|
|
34
|
+
# @return [String] the Makefile fragment the box `Makefile` includes
|
|
35
|
+
def hosting_mk(plan)
|
|
36
|
+
Box.template("hosting.mk.tmpl", "SERVICE" => plan.containers.first.name,
|
|
37
|
+
"URL" => plan.hosting.public_url.to_s)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @param plan [Settings::Plan] the resolved settings
|
|
41
|
+
# @param region [String] the validated AWS region
|
|
42
|
+
# @return [String] `deploy-service.sh`
|
|
43
|
+
def deploy_service_sh(plan, region)
|
|
44
|
+
roll = plan.task_definition ? "deploy-service.taskdef-roll.part" : "deploy-service.services-roll.part"
|
|
45
|
+
Box.template("deploy-service.sh.tmpl",
|
|
46
|
+
"STACK" => plan.infra_name, "MODE_SUMMARY" => mode_summary(plan),
|
|
47
|
+
"SETTINGS" => deploy_constants(plan, region), "SERVICES" => services_block(plan),
|
|
48
|
+
"REPOSITORY_STEP" => repository_step(plan),
|
|
49
|
+
"ROLL_STEPS" => part(roll))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @param plan [Settings::Plan] the resolved settings
|
|
53
|
+
# @param region [String] the validated AWS region
|
|
54
|
+
# @return [String] `smoke-after-deploy.sh`
|
|
55
|
+
def smoke_after_deploy_sh(plan, region)
|
|
56
|
+
hosting = plan.hosting
|
|
57
|
+
image_stack = hosting.stack
|
|
58
|
+
Box.template("smoke-after-deploy.sh.tmpl",
|
|
59
|
+
"STACK" => plan.infra_name, "SETTINGS" => smoke_constants(plan, region),
|
|
60
|
+
"SETTLE_CHECKS" => settle_checks(plan),
|
|
61
|
+
"IMAGE_STACK_WAIT" => image_stack ? ", stack #{image_stack}" : "")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @param plan [Settings::Plan] the resolved settings
|
|
65
|
+
# @return [String] the sentence that says what the roll puts on the box
|
|
66
|
+
def mode_summary(plan)
|
|
67
|
+
if plan.task_definition
|
|
68
|
+
<<~TEXT.gsub(/^/, "# ").chomp
|
|
69
|
+
It then sets the stack's matching image-tag parameter (UsePreviousValue for every other
|
|
70
|
+
one, and it checks that only that one changed), reads the task definition the stack
|
|
71
|
+
registers, refuses to roll it unless it carries the pushed image, and rolls it with
|
|
72
|
+
deploy-box.sh.
|
|
73
|
+
TEXT
|
|
74
|
+
else
|
|
75
|
+
<<~TEXT.gsub(/^/, "# ").chomp
|
|
76
|
+
It then rolls the box with deploy-box.sh, naming the new tag for this container and the
|
|
77
|
+
tag the box runs now for every other one.
|
|
78
|
+
TEXT
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @param plan [Settings::Plan] the resolved settings
|
|
83
|
+
# @return [String] the comment lines that list what "settled" means
|
|
84
|
+
def settle_checks(plan)
|
|
85
|
+
stacks = plan.hosting.stack ? "the box stack and the #{plan.hosting.stack} stack are" : "the box stack is"
|
|
86
|
+
checks = ["#{stacks} CREATE_COMPLETE or UPDATE_COMPLETE, never rolled back or failed;",
|
|
87
|
+
"every container and the proxy of the box's Compose project is up and has stayed up;"]
|
|
88
|
+
if plan.task_definition
|
|
89
|
+
checks << "each container runs the image of the task definition it was rolled from (the latest " \
|
|
90
|
+
"#{plan.task_definition} unless TASKDEF says otherwise)."
|
|
91
|
+
end
|
|
92
|
+
checks.map { |line| "# - #{line}" }.join("\n")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def deploy_constants(plan, region)
|
|
96
|
+
lines = { "REGION" => region }
|
|
97
|
+
if plan.task_definition
|
|
98
|
+
lines["FAMILY"] = plan.task_definition
|
|
99
|
+
lines["IMAGE_STACK"] = plan.hosting.stack
|
|
100
|
+
else
|
|
101
|
+
lines["BOX_STACK"] = plan.box_stack
|
|
102
|
+
lines["BOX_DIR"] = "/opt/#{plan.infra_name}"
|
|
103
|
+
end
|
|
104
|
+
constants(lines)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def smoke_constants(plan, region)
|
|
108
|
+
hosting = plan.hosting
|
|
109
|
+
watched = [plan.box_stack, hosting.stack].compact
|
|
110
|
+
constants(
|
|
111
|
+
"REGION" => region, "BOX_STACK" => plan.box_stack, "BOX_DIR" => "/opt/#{plan.infra_name}",
|
|
112
|
+
"WATCHED_STACKS" => watched.join(" "),
|
|
113
|
+
"EXPECTED_SERVICES" => (plan.containers.map(&:name) + ["caddy"]).join(" ")
|
|
114
|
+
).concat("\n#{smoke_defaults(plan)}")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def smoke_defaults(plan)
|
|
118
|
+
hosting = plan.hosting
|
|
119
|
+
[
|
|
120
|
+
"TASK_DEFINITION=\"${TASKDEF:-#{plan.task_definition}}\"",
|
|
121
|
+
"REPO=\"${REPO:-#{hosting.smoke_repo}}\"",
|
|
122
|
+
"WORKFLOW=\"${WORKFLOW:-#{hosting.smoke_workflow}}\"",
|
|
123
|
+
"SMOKE_REF=\"${SMOKE_REF:-#{hosting.smoke_ref}}\""
|
|
124
|
+
].join("\n")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def constants(values)
|
|
128
|
+
values.map { |name, value| "#{name}=#{Shellwords.escape(value)}" }.join("\n")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# One case arm per container that sets the repository and image-tag parameter to use, and
|
|
132
|
+
# the list of names the script accepts.
|
|
133
|
+
def services_block(plan)
|
|
134
|
+
arms = plan.containers.map do |container|
|
|
135
|
+
setting =
|
|
136
|
+
if plan.task_definition
|
|
137
|
+
"CFN_PARAM_KEY=#{Shellwords.escape(container.tag_parameter)}"
|
|
138
|
+
else
|
|
139
|
+
"ECR_REPOSITORY=#{Shellwords.escape(container.repository)}"
|
|
140
|
+
end
|
|
141
|
+
" #{Shellwords.escape(container.name)}) #{setting} ;;"
|
|
142
|
+
end
|
|
143
|
+
<<~BASH.chomp
|
|
144
|
+
SERVICES='#{plan.containers.map(&:name).join(' ')}'
|
|
145
|
+
|
|
146
|
+
# Sets the container's settings for one service, or exits.
|
|
147
|
+
resolve_service() {
|
|
148
|
+
case "$1" in
|
|
149
|
+
#{arms.join("\n")}
|
|
150
|
+
*) echo "unknown service '$1', must be one of: ${SERVICES}" >&2; exit 1 ;;
|
|
151
|
+
esac
|
|
152
|
+
}
|
|
153
|
+
BASH
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def repository_step(plan)
|
|
157
|
+
plan.task_definition ? "#{part('deploy-service.taskdef.part')}\n\n" : ""
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def part(file)
|
|
161
|
+
file ? File.read(File.join(Box::TEMPLATE_DIR, file)).chomp : ""
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private_class_method :deploy_constants, :smoke_constants, :smoke_defaults, :constants,
|
|
165
|
+
:services_block, :part, :repository_step, :settle_checks
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require_relative "../scripts/settings"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Deploy
|
|
6
|
+
module Box
|
|
7
|
+
# The hosting words of a `deployed_to("AwsBox")` block: `hosting_scripts true` and the
|
|
8
|
+
# settings the generated `deploy-service.sh`, `smoke-after-deploy.sh` and `expected-era`
|
|
9
|
+
# read. Every value is matched against a conservative pattern before a script splices it in.
|
|
10
|
+
module HostingSettings
|
|
11
|
+
PARAMETER = /\A[A-Za-z][A-Za-z0-9]{0,254}\z/
|
|
12
|
+
STACK = /\A[a-zA-Z][a-zA-Z0-9-]{0,127}\z/
|
|
13
|
+
PUBLIC_URL = %r{\Ahttps?://[^\s'"$`\\]+\z}
|
|
14
|
+
# The words that only mean something to the hosting scripts.
|
|
15
|
+
WORDS = %i[hosting_stack smoke_repo smoke_workflow smoke_ref expected_eras public_url].freeze
|
|
16
|
+
|
|
17
|
+
# The checked hosting settings.
|
|
18
|
+
#
|
|
19
|
+
# @!attribute [r] stack [String, nil] the CloudFormation stack whose image-tag parameters
|
|
20
|
+
# feed the task definition; nil without a `task_definition`
|
|
21
|
+
# @!attribute [r] smoke_repo [String, nil] the GitHub repository holding the workflow
|
|
22
|
+
# @!attribute [r] smoke_workflow [String] the smoke workflow's file name
|
|
23
|
+
# @!attribute [r] smoke_ref [String] the ref the smoke workflow is dispatched on
|
|
24
|
+
# @!attribute [r] expected_eras [Array<String>] the eras `expected-era` lists
|
|
25
|
+
# @!attribute [r] public_url [String, nil] the live site, for `make check-era`
|
|
26
|
+
Hosting = Struct.new(:stack, :smoke_repo, :smoke_workflow, :smoke_ref, :expected_eras,
|
|
27
|
+
:public_url, keyword_init: true)
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# @param settings [Hash{Symbol => Object}] the world's `AwsBox` settings
|
|
32
|
+
# @param task_definition [String, nil] the declared task definition family
|
|
33
|
+
# @return [Hosting, nil] the checked settings, or nil when the world has not opted in
|
|
34
|
+
# @raise [ArgumentError] when a hosting word is invalid, missing or has nothing to act on
|
|
35
|
+
def read(settings, task_definition)
|
|
36
|
+
flag = settings.fetch(:hosting_scripts, false)
|
|
37
|
+
raise ArgumentError, "hosting_scripts: #{flag.inspect} must be true or false" unless [true, false].include?(flag)
|
|
38
|
+
|
|
39
|
+
unless flag
|
|
40
|
+
stray = WORDS.select { |word| settings.key?(word) }
|
|
41
|
+
raise ArgumentError, "#{stray.join(', ')} only apply with hosting_scripts true" unless stray.empty?
|
|
42
|
+
|
|
43
|
+
return nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
Hosting.new(stack: read_stack(settings, task_definition), **read_smoke(settings),
|
|
47
|
+
expected_eras: eras(settings), public_url: optional(settings, :public_url, PUBLIC_URL))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# With a task definition, the stack that registers its revisions is where a new image tag
|
|
51
|
+
# goes, so it has to be named. Without one the box is rolled from `services.json` and no
|
|
52
|
+
# stack holds a tag.
|
|
53
|
+
def read_stack(settings, task_definition)
|
|
54
|
+
stack = settings[:hosting_stack]
|
|
55
|
+
if task_definition.nil?
|
|
56
|
+
raise ArgumentError, "hosting_stack names the stack behind a task_definition; this world has none" if stack
|
|
57
|
+
|
|
58
|
+
return nil
|
|
59
|
+
end
|
|
60
|
+
raise ArgumentError, missing_stack(task_definition) if stack.nil?
|
|
61
|
+
|
|
62
|
+
check(:hosting_stack, stack, STACK)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def read_smoke(settings)
|
|
66
|
+
workflow = settings[:smoke_workflow]
|
|
67
|
+
raise ArgumentError, missing_workflow if workflow.nil?
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
smoke_workflow: check(:smoke_workflow, workflow, Scripts::Settings::WORKFLOW),
|
|
71
|
+
smoke_repo: optional(settings, :smoke_repo, Scripts::Settings::GITHUB_REPO),
|
|
72
|
+
smoke_ref: check(:smoke_ref, settings.fetch(:smoke_ref, "main"), Scripts::Settings::REF)
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def eras(settings)
|
|
77
|
+
Array(settings.fetch(:expected_eras, [])).map { |era| check(:expected_eras, era.to_s, Scripts::Settings::ERA) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def optional(settings, key, pattern)
|
|
81
|
+
value = settings[key]
|
|
82
|
+
value.nil? ? nil : check(key, value, pattern)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def check(key, value, pattern)
|
|
86
|
+
return value if value.is_a?(String) && value.match?(pattern)
|
|
87
|
+
|
|
88
|
+
raise ArgumentError, "#{key}: #{value.inspect} does not match #{pattern.inspect}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def missing_workflow
|
|
92
|
+
<<~MSG
|
|
93
|
+
deployed_to("AwsBox") enables hosting_scripts but names no smoke_workflow. Name the GitHub
|
|
94
|
+
workflow file the post-deploy smoke dispatches, e.g.:
|
|
95
|
+
|
|
96
|
+
deployed_to("AwsBox") do
|
|
97
|
+
...
|
|
98
|
+
hosting_scripts true
|
|
99
|
+
smoke_workflow "smoke-prod.yml"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
A deploy that ends without a smoke of the live site cannot tell a roll that took from one
|
|
103
|
+
that only looked live.
|
|
104
|
+
MSG
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def missing_stack(family)
|
|
108
|
+
<<~MSG
|
|
109
|
+
deployed_to("AwsBox") enables hosting_scripts with task_definition #{family.inspect} but names no
|
|
110
|
+
hosting_stack. Name the CloudFormation stack whose TaskDefinition resource reads an image-tag
|
|
111
|
+
parameter per container (`<Name>ImageTag` unless a container sets `tag_parameter`), e.g.:
|
|
112
|
+
|
|
113
|
+
hosting_stack "my-platform"
|
|
114
|
+
|
|
115
|
+
deploy-service.sh sets that parameter to the tag it pushed, so the stack, not a hand-registered
|
|
116
|
+
revision, is the source of truth for what runs.
|
|
117
|
+
MSG
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require_relative "hosting_settings"
|
|
2
|
+
|
|
1
3
|
module Hecks
|
|
2
4
|
module Projections
|
|
3
5
|
module Deploy
|
|
@@ -47,7 +49,9 @@ module Hecks
|
|
|
47
49
|
# @!attribute [r] env [Hash{String => String}] plain environment variables
|
|
48
50
|
# @!attribute [r] secrets [Hash{String => String}] environment variable => secret name,
|
|
49
51
|
# resolved on the box at deploy time and never written into a template
|
|
50
|
-
|
|
52
|
+
# @!attribute [r] tag_parameter [String] the stack parameter that holds this container's
|
|
53
|
+
# image tag, which a hosting `deploy-service.sh` sets
|
|
54
|
+
Container = Struct.new(:name, :repository, :port, :env, :secrets, :tag_parameter, keyword_init: true)
|
|
51
55
|
|
|
52
56
|
# A set of URL paths one container serves.
|
|
53
57
|
#
|
|
@@ -82,7 +86,7 @@ module Hecks
|
|
|
82
86
|
:storage_gb, :backup_days, :snapshots_keep, :database_name, :engine_version,
|
|
83
87
|
:containers, :routes, :default_container, :origin_header, :origin_secret,
|
|
84
88
|
:secret_prefixes, :writable_secrets, :origin_env, :tunnel, :tunnel_service, :proxy_image, :task_definition,
|
|
85
|
-
:migration, :s3_buckets, keyword_init: true
|
|
89
|
+
:migration, :s3_buckets, :hosting, keyword_init: true
|
|
86
90
|
) do
|
|
87
91
|
# @return [String] the CloudFormation stack that holds the database
|
|
88
92
|
def rds_stack = "#{stack_prefix}-#{infra_name}-rds"
|
|
@@ -126,7 +130,8 @@ module Hecks
|
|
|
126
130
|
tunnel: tunnel, tunnel_service: tunnel_service,
|
|
127
131
|
proxy_image: check(:proxy_image, s.fetch(:proxy_image, PROXY_IMAGE), IMAGE),
|
|
128
132
|
task_definition: task_definition, migration: read_migration(s[:migration], database_name),
|
|
129
|
-
s3_buckets: read_s3_access(s.fetch(:s3_access, []))
|
|
133
|
+
s3_buckets: read_s3_access(s.fetch(:s3_access, [])),
|
|
134
|
+
hosting: HostingSettings.read(s, task_definition)
|
|
130
135
|
)
|
|
131
136
|
end
|
|
132
137
|
|
|
@@ -224,10 +229,18 @@ module Hecks
|
|
|
224
229
|
name: name, repository: check(:repository, spec.fetch(:repository, "#{infra_name}-#{name}"), REPOSITORY),
|
|
225
230
|
port: integer(:port, spec.fetch(:port) { raise ArgumentError, "containers: #{name} has no port" }, 1, 65_535),
|
|
226
231
|
env: string_map(:env, spec.fetch(:env, {}), ENV_KEY, ENV_VALUE),
|
|
227
|
-
secrets: string_map(:secrets, spec.fetch(:secrets, {}), ENV_KEY, SECRET_NAME)
|
|
232
|
+
secrets: string_map(:secrets, spec.fetch(:secrets, {}), ENV_KEY, SECRET_NAME),
|
|
233
|
+
tag_parameter: check(:tag_parameter, spec.fetch(:tag_parameter, default_tag_parameter(name)),
|
|
234
|
+
HostingSettings::PARAMETER)
|
|
228
235
|
)
|
|
229
236
|
end
|
|
230
237
|
|
|
238
|
+
# @param name [String] a container name such as `web-app`
|
|
239
|
+
# @return [String] the image-tag parameter a stack names it by, such as `WebAppImageTag`
|
|
240
|
+
def default_tag_parameter(name)
|
|
241
|
+
"#{name.split('-').map(&:capitalize).join}ImageTag"
|
|
242
|
+
end
|
|
243
|
+
|
|
231
244
|
def read_routes(list, containers)
|
|
232
245
|
raise ArgumentError, "routes: expected a list" unless list.is_a?(Array)
|
|
233
246
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# deploy-box.sh renders every container the roll does not name at "latest". Name the tag each other
|
|
2
|
+
# container runs now, read from the box's own Compose file, so rolling one service leaves the rest alone.
|
|
3
|
+
BOX_ID="$(aws cloudformation describe-stacks --stack-name "$BOX_STACK" --region "$REGION" \
|
|
4
|
+
--query "Stacks[0].Outputs[?OutputKey=='InstanceId'].OutputValue" --output text)"
|
|
5
|
+
[ -n "$BOX_ID" ] && [ "$BOX_ID" != None ] || { echo "no instance in stack ${BOX_STACK}" >&2; exit 1; }
|
|
6
|
+
|
|
7
|
+
echo "==> reading the tags the box runs now"
|
|
8
|
+
READ_JSON="$(jq -n --arg c "cat ${BOX_DIR}/compose.json 2>/dev/null || echo '{}'" '{commands: [$c]}')"
|
|
9
|
+
COMMAND_ID="$(aws ssm send-command --instance-ids "$BOX_ID" --document-name AWS-RunShellScript --region "$REGION" \
|
|
10
|
+
--parameters "$READ_JSON" --query 'Command.CommandId' --output text)"
|
|
11
|
+
for _ in $(seq 1 30); do
|
|
12
|
+
sleep 2
|
|
13
|
+
state="$(aws ssm get-command-invocation --command-id "$COMMAND_ID" --instance-id "$BOX_ID" --region "$REGION" \
|
|
14
|
+
--query Status --output text 2>/dev/null || echo Pending)"
|
|
15
|
+
[ "$state" != InProgress ] && [ "$state" != Pending ] && break
|
|
16
|
+
done
|
|
17
|
+
[ "$state" = Success ] || { echo "could not read the box's Compose file (${state}); nothing was rolled" >&2; exit 1; }
|
|
18
|
+
aws ssm get-command-invocation --command-id "$COMMAND_ID" --instance-id "$BOX_ID" --region "$REGION" \
|
|
19
|
+
--query StandardOutputContent --output text > "${SCRATCH_DIR}/compose.json"
|
|
20
|
+
|
|
21
|
+
TAGS=()
|
|
22
|
+
for name in $SERVICES; do
|
|
23
|
+
if [ "$name" = "$SERVICE_NAME" ]; then
|
|
24
|
+
TAGS+=("${name}=${TAG}")
|
|
25
|
+
continue
|
|
26
|
+
fi
|
|
27
|
+
image="$(jq -r --arg n "$name" '.services[$n].image // empty' "${SCRATCH_DIR}/compose.json")"
|
|
28
|
+
# A container the box does not run yet starts at its "latest" image.
|
|
29
|
+
if [ -n "$image" ]; then TAGS+=("${name}=${image##*:}"); fi
|
|
30
|
+
done
|
|
31
|
+
echo "==> rolling ${TAGS[*]}"
|
|
32
|
+
|
|
33
|
+
bash ./deploy-box.sh "${TAGS[@]}"
|