defmodule Tarakan.Repo.Migrations.CreateCodePatternRules do use Ecto.Migration @kinds ~w(code_review threat_model privacy_review business_logic verify_findings write_fix diff_review refute_finding reproduce_finding calibrate_severity) @with_synthesis @kinds ++ ~w(synthesize_rule) def up do create table(:code_pattern_rules) do # One rule per code cluster. The cluster is a string key rather than a row, # so this table is where a cluster first becomes a thing you can own. add :code_pattern_key, :string, null: false add :engine, :string, null: false, default: "semgrep" add :language, :string add :rule_yaml, :text, null: false # Validation is done by the worker, which has both the rule engine and the # code. A rule is only servable if it demonstrably matched the instances it # claims to cover: `matched_count` of `checked_count`, with the finding ids # it hit recorded so the claim can be re-tested later. add :checked_count, :integer, null: false, default: 0 add :matched_count, :integer, null: false, default: 0 add :matched_finding_ids, {:array, :binary_id}, null: false, default: [] add :validated_at, :utc_datetime_usec add :author_account_id, references(:accounts, on_delete: :nilify_all) timestamps(type: :utc_datetime_usec) end # Newest validated rule per cluster wins; only one row per author per cluster # so a resubmission updates rather than piles up. create unique_index(:code_pattern_rules, [:code_pattern_key, :author_account_id]) create index(:code_pattern_rules, [:code_pattern_key, :validated_at]) create constraint(:code_pattern_rules, :code_pattern_rules_counts_sane, check: "checked_count >= 0 AND matched_count >= 0 AND matched_count <= checked_count" ) # A rule that matched nothing is not evidence of a pattern, it is a guess. create constraint(:code_pattern_rules, :code_pattern_rules_validated_needs_a_match, check: "validated_at IS NULL OR matched_count > 0" ) alter table(:review_tasks) do add :target_code_pattern_key, :string end create index(:review_tasks, [:target_code_pattern_key]) drop constraint(:review_tasks, :review_tasks_kind_must_be_valid) create constraint(:review_tasks, :review_tasks_kind_must_be_valid, check: kind_check(@with_synthesis) ) end def down do drop constraint(:review_tasks, :review_tasks_kind_must_be_valid) create constraint(:review_tasks, :review_tasks_kind_must_be_valid, check: kind_check(@kinds)) drop index(:review_tasks, [:target_code_pattern_key]) alter table(:review_tasks) do remove :target_code_pattern_key end drop table(:code_pattern_rules) end defp kind_check(kinds) do "kind IN (" <> Enum.map_join(kinds, ", ", &"'#{&1}'") <> ")" end end