adjust schema

This commit is contained in:
aecsocket
2026-08-02 17:05:41 +01:00
parent 5ead1499cd
commit 4c8abfb78d
31 changed files with 577 additions and 275 deletions
@@ -461,10 +461,16 @@ const TEST_INPUTS: Labrinth.TechReview.Internal.RuleInput[] = [
sha512: 'fedcba9876543210',
},
},
scope: {
project_id: 'example-project',
version_id: 'example-version',
file_id: 'example-file',
project: {
id: 'example-project',
types: ['mod'],
},
version: {
id: 'example-version',
loaders: ['fabric'],
},
file: {
id: 'example-file',
},
},
{
@@ -489,10 +495,16 @@ const TEST_INPUTS: Labrinth.TechReview.Internal.RuleInput[] = [
sha512: '0123456789abcdef',
},
},
scope: {
project_id: 'telemetry-project',
version_id: 'telemetry-version',
file_id: 'telemetry-file',
project: {
id: 'telemetry-project',
types: ['mod'],
},
version: {
id: 'telemetry-version',
loaders: ['neoforge'],
},
file: {
id: 'telemetry-file',
},
},
]
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM payouts_values_notifications WHERE notified = FALSE AND user_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
null
]
},
"hash": "0f3d943e4fc48a94363b77c8a7d36eb1dd626e77331d8278c406df952691be4c"
}
@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO users (id, username, email, role)\n VALUES\n (1000, 'userland', 'userland@modrinth.com', 'developer'),\n (1001, 'useful', 'useful@modrinth.com', 'developer'),\n (1002, 'Useless', 'useless@modrinth.com', 'developer')\n ",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "0ffebf149d50149d0c128478e6267a2296efc16763cdbd5d8c295a1a7ca08021"
}
@@ -0,0 +1,32 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT id AS \"id!: DelphiRuleId\", name, rule\n FROM delphi_rules\n WHERE NOT delete_on_next_revision\n ORDER BY priority DESC, id\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!: DelphiRuleId",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "rule",
"type_info": "Varchar"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false
]
},
"hash": "182be4110f45b6be6a9174200a07a887cebbaeab0a8c1da8b008e0c5e94fbc4e"
}
@@ -0,0 +1,26 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n id,\n status AS \"status: PayoutStatus\"\n FROM payouts\n ORDER BY id\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "status: PayoutStatus",
"type_info": "Varchar"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false
]
},
"hash": "1adbd24d815107e13bc1440c7a8f4eeff66ab4165a9f4980032e114db4dc1286"
}
@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM payouts_values_notifications WHERE notified = FALSE",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "20cff8fdf7971e91c9d473b9a4663ce02ca16781e32232ae0fa7a0af1973d3a4"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE mods SET status = 'processing' WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "329c009635ad2a4c214da2f4651b8c14fe329deac983bc2f43ba74e066f9cf8d"
}
@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO users (id, username, email, role)\n VALUES (2100, 'prefix_under_score', 'prefix_under_score@modrinth.com', 'developer')\n ",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "38ace0abad724d1111b79a72c7c8518f1272690ce7766dbdc41ec1e98f7f1fec"
}
@@ -1,26 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT id AS \"id!: DelphiRuleId\", rule\n FROM delphi_rules\n WHERE NOT delete_on_next_revision\n ORDER BY priority DESC, id\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!: DelphiRuleId",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "rule",
"type_info": "Varchar"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false
]
},
"hash": "3f1aaeed6e394294fa32da7e7f83765fb64bd9499a796470d544466886e1a808"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO users (id, username, email, role)\n VALUES ($1, $2, $3, 'developer')\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "5cb65218a2a4a7343130d838f8a78df08d25844f1019d539712b0cb686e4ae81"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO payouts_values_notifications (date_available, user_id, notified)\n VALUES ($1, $2, FALSE)\n ON CONFLICT (date_available, user_id) DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Timestamptz",
"Int8"
]
},
"nullable": []
},
"hash": "6678cd4c51771cfaae2be8021ba66908ea41a06ba858dc5b523aef6aae27b850"
}
@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "CREATE SCHEMA public;",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "66868d0b35229abffa3f668eaef19dc0cdef502935dd5939a2f3ab89b0766ad2"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO payouts_values (user_id, mod_id, amount, created, date_available)\n VALUES ($1, NULL, $2, NOW(), $3)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Numeric",
"Timestamptz"
]
},
"nullable": []
},
"hash": "69a1cb4b7f1115a990d1fc4805d58541fc78e910111c09ba3d50a12d9ca4a9f8"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n detail.id,\n detail.key,\n issue.issue_type,\n detail.severity AS \"severity: DelphiSeverity\",\n detail.jar,\n detail.file_path,\n detail.data AS \"data: Json<HashMap<String, serde_json::Value>>\",\n report.delphi_version,\n file.size AS \"size?\",\n file.id AS \"file_id?\",\n version.id AS \"version_id?\",\n version.mod_id AS \"project_id?\",\n COALESCE(file_hashes.hashes, '{}'::jsonb)\n AS \"hashes!: Json<BTreeMap<String, String>>\"\n FROM delphi_report_issue_details detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report ON report.id = issue.report_id\n LEFT JOIN files file ON file.id = report.file_id\n LEFT JOIN versions version ON version.id = file.version_id\n LEFT JOIN LATERAL (\n SELECT\n jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes\n FROM hashes\n WHERE hashes.file_id = file.id\n ) file_hashes ON TRUE\n WHERE detail.id = ANY($1::bigint[])\n ORDER BY detail.id\n ",
"query": "\n SELECT\n detail.id,\n detail.key,\n issue.issue_type,\n detail.severity AS \"severity: DelphiSeverity\",\n detail.jar,\n detail.file_path,\n detail.data AS \"data: Json<HashMap<String, serde_json::Value>>\",\n report.delphi_version,\n file.size AS \"size?\",\n file.id AS \"file_id?\",\n version.id AS \"version_id?\",\n version.mod_id AS \"project_id?\",\n COALESCE(version_metadata.project_types, ARRAY[]::text[])\n AS \"project_types!: Vec<String>\",\n COALESCE(version_metadata.loaders, ARRAY[]::text[])\n AS \"loaders!: Vec<String>\",\n COALESCE(file_hashes.hashes, '{}'::jsonb)\n AS \"hashes!: Json<BTreeMap<String, String>>\"\n FROM delphi_report_issue_details detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report ON report.id = issue.report_id\n LEFT JOIN files file ON file.id = report.file_id\n LEFT JOIN versions version ON version.id = file.version_id\n LEFT JOIN LATERAL (\n SELECT\n ARRAY_AGG(\n DISTINCT project_type.name::text\n ORDER BY project_type.name::text\n ) FILTER (WHERE project_type.name IS NOT NULL)\n AS project_types,\n ARRAY_AGG(\n DISTINCT loader.loader\n ORDER BY loader.loader\n ) AS loaders\n FROM loaders_versions loader_version\n INNER JOIN loaders loader\n ON loader.id = loader_version.loader_id\n LEFT JOIN loaders_project_types loader_project_type\n ON loader_project_type.joining_loader_id = loader_version.loader_id\n LEFT JOIN project_types project_type\n ON project_type.id = loader_project_type.joining_project_type_id\n WHERE loader_version.version_id = version.id\n ) version_metadata ON TRUE\n LEFT JOIN LATERAL (\n SELECT\n jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes\n FROM hashes\n WHERE hashes.file_id = file.id\n ) file_hashes ON TRUE\n WHERE detail.id = ANY($1::bigint[])\n ORDER BY detail.id\n ",
"describe": {
"columns": [
{
@@ -79,6 +79,16 @@
},
{
"ordinal": 12,
"name": "project_types!: Vec<String>",
"type_info": "TextArray"
},
{
"ordinal": 13,
"name": "loaders!: Vec<String>",
"type_info": "VarcharArray"
},
{
"ordinal": 14,
"name": "hashes!: Json<BTreeMap<String, String>>",
"type_info": "Jsonb"
}
@@ -101,8 +111,10 @@
true,
true,
true,
null,
null,
null
]
},
"hash": "868d19328921b9a3bd56fa9354e5e75d09c76a3eb3ed9c89b05b9aab13505bf9"
"hash": "7e7e2e9f67158b94eb62834cbfd65fd5165d02ebcc0f8454554cbd870e4ddb42"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n detail.id,\n detail.key,\n issue.issue_type,\n detail.severity AS \"severity: DelphiSeverity\",\n detail.jar,\n detail.file_path,\n detail.data AS \"data: Json<HashMap<String, serde_json::Value>>\",\n report.delphi_version,\n file.size AS \"size?\",\n file.id AS \"file_id?\",\n version.id AS \"version_id?\",\n version.mod_id AS \"project_id?\",\n COALESCE(file_hashes.hashes, '{}'::jsonb)\n AS \"hashes!: Json<BTreeMap<String, String>>\"\n FROM delphi_report_issue_details detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report ON report.id = issue.report_id\n LEFT JOIN files file ON file.id = report.file_id\n LEFT JOIN versions version ON version.id = file.version_id\n LEFT JOIN (\n SELECT\n file_id,\n jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes\n FROM hashes\n GROUP BY file_id\n ) file_hashes ON file_hashes.file_id = file.id\n ORDER BY detail.id\n ",
"query": "\n SELECT\n detail.id,\n detail.key,\n issue.issue_type,\n detail.severity AS \"severity: DelphiSeverity\",\n detail.jar,\n detail.file_path,\n detail.data AS \"data: Json<HashMap<String, serde_json::Value>>\",\n report.delphi_version,\n file.size AS \"size?\",\n file.id AS \"file_id?\",\n version.id AS \"version_id?\",\n version.mod_id AS \"project_id?\",\n COALESCE(version_metadata.project_types, ARRAY[]::text[])\n AS \"project_types!: Vec<String>\",\n COALESCE(version_metadata.loaders, ARRAY[]::text[])\n AS \"loaders!: Vec<String>\",\n COALESCE(file_hashes.hashes, '{}'::jsonb)\n AS \"hashes!: Json<BTreeMap<String, String>>\"\n FROM delphi_report_issue_details detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report ON report.id = issue.report_id\n LEFT JOIN files file ON file.id = report.file_id\n LEFT JOIN versions version ON version.id = file.version_id\n LEFT JOIN (\n SELECT\n loader_version.version_id,\n ARRAY_AGG(\n DISTINCT project_type.name::text\n ORDER BY project_type.name::text\n ) FILTER (WHERE project_type.name IS NOT NULL)\n AS project_types,\n ARRAY_AGG(\n DISTINCT loader.loader\n ORDER BY loader.loader\n ) AS loaders\n FROM loaders_versions loader_version\n INNER JOIN loaders loader\n ON loader.id = loader_version.loader_id\n LEFT JOIN loaders_project_types loader_project_type\n ON loader_project_type.joining_loader_id = loader_version.loader_id\n LEFT JOIN project_types project_type\n ON project_type.id = loader_project_type.joining_project_type_id\n GROUP BY loader_version.version_id\n ) version_metadata\n ON version_metadata.version_id = version.id\n LEFT JOIN (\n SELECT\n file_id,\n jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes\n FROM hashes\n GROUP BY file_id\n ) file_hashes ON file_hashes.file_id = file.id\n ORDER BY detail.id\n ",
"describe": {
"columns": [
{
@@ -79,6 +79,16 @@
},
{
"ordinal": 12,
"name": "project_types!: Vec<String>",
"type_info": "TextArray"
},
{
"ordinal": 13,
"name": "loaders!: Vec<String>",
"type_info": "VarcharArray"
},
{
"ordinal": 14,
"name": "hashes!: Json<BTreeMap<String, String>>",
"type_info": "Jsonb"
}
@@ -99,8 +109,10 @@
false,
false,
false,
null,
null,
null
]
},
"hash": "2fb03e1de120cf921146b6dbd35691117412407c4225f966c013fdfef21a82fb"
"hash": "8537362d9c11e437ada9ce97bb604666378d1d24981ab8113badded16fb63413"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE moderation_locks SET locked_at = NOW() - ($1::bigint * INTERVAL '1 minute') - INTERVAL '1 second' WHERE project_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Int8"
]
},
"nullable": []
},
"hash": "8ca7f46618bd72cfd5fe1f246d41a31da1885cc22bb61be6853950ba52be9aa7"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n detail.key,\n issue.issue_type,\n detail.severity AS \"severity: DelphiSeverity\",\n detail.jar,\n detail.file_path,\n detail.data AS \"data: Json<HashMap<String, serde_json::Value>>\",\n report.delphi_version,\n file.size AS \"size?\",\n file.id AS \"file_id?\",\n version.id AS \"version_id?\",\n version.mod_id AS \"project_id?\",\n COALESCE(file_hashes.hashes, '{}'::jsonb)\n AS \"hashes!: Json<BTreeMap<String, String>>\"\n FROM delphi_report_issue_details detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report ON report.id = issue.report_id\n LEFT JOIN files file ON file.id = report.file_id\n LEFT JOIN versions version ON version.id = file.version_id\n LEFT JOIN LATERAL (\n SELECT\n jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes\n FROM hashes\n WHERE hashes.file_id = file.id\n ) file_hashes ON TRUE\n WHERE detail.id = $1\n ",
"query": "\n SELECT\n detail.key,\n issue.issue_type,\n detail.severity AS \"severity: DelphiSeverity\",\n detail.jar,\n detail.file_path,\n detail.data AS \"data: Json<HashMap<String, serde_json::Value>>\",\n report.delphi_version,\n file.size AS \"size?\",\n file.id AS \"file_id?\",\n version.id AS \"version_id?\",\n version.mod_id AS \"project_id?\",\n COALESCE(version_metadata.project_types, ARRAY[]::text[])\n AS \"project_types!: Vec<String>\",\n COALESCE(version_metadata.loaders, ARRAY[]::text[])\n AS \"loaders!: Vec<String>\",\n COALESCE(file_hashes.hashes, '{}'::jsonb)\n AS \"hashes!: Json<BTreeMap<String, String>>\"\n FROM delphi_report_issue_details detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report ON report.id = issue.report_id\n LEFT JOIN files file ON file.id = report.file_id\n LEFT JOIN versions version ON version.id = file.version_id\n LEFT JOIN LATERAL (\n SELECT\n ARRAY_AGG(\n DISTINCT project_type.name::text\n ORDER BY project_type.name::text\n ) FILTER (WHERE project_type.name IS NOT NULL)\n AS project_types,\n ARRAY_AGG(\n DISTINCT loader.loader\n ORDER BY loader.loader\n ) AS loaders\n FROM loaders_versions loader_version\n INNER JOIN loaders loader\n ON loader.id = loader_version.loader_id\n LEFT JOIN loaders_project_types loader_project_type\n ON loader_project_type.joining_loader_id = loader_version.loader_id\n LEFT JOIN project_types project_type\n ON project_type.id = loader_project_type.joining_project_type_id\n WHERE loader_version.version_id = version.id\n ) version_metadata ON TRUE\n LEFT JOIN LATERAL (\n SELECT\n jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes\n FROM hashes\n WHERE hashes.file_id = file.id\n ) file_hashes ON TRUE\n WHERE detail.id = $1\n ",
"describe": {
"columns": [
{
@@ -74,6 +74,16 @@
},
{
"ordinal": 11,
"name": "project_types!: Vec<String>",
"type_info": "TextArray"
},
{
"ordinal": 12,
"name": "loaders!: Vec<String>",
"type_info": "VarcharArray"
},
{
"ordinal": 13,
"name": "hashes!: Json<BTreeMap<String, String>>",
"type_info": "Jsonb"
}
@@ -95,8 +105,10 @@
false,
false,
false,
null,
null,
null
]
},
"hash": "1f318b0dda5aaf58b9e403f87fcacfcd223bd67f6d5ba95868ace4373a98c67d"
"hash": "b536d7870ba77ace55e309938c3ba93c7016b2a6f18aa4f22ea57f604d82c1da"
}
@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT status AS \"status: PayoutStatus\" FROM payouts WHERE id = 1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "status: PayoutStatus",
"type_info": "Varchar"
}
],
"parameters": {
"Left": []
},
"nullable": [
false
]
},
"hash": "b92b5bb7d179c4fcdbc45600ccfd2402f52fea71e27b08e7926fcc2a9e62c0f3"
}
@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "DROP SCHEMA public CASCADE;",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "c65295f61b18f7be4fdf30b3e2c2c36b9db3770508be4acbb6b441ed8006d9c7"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE users SET email_verified = TRUE WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "cb626a36deffd73e67de2dc4789bd875675779a173fb2cd41ba365c1acca96f9"
}
@@ -0,0 +1,18 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO payouts (id, method, platform_id, status, user_id, amount, created)\n VALUES ($1, $2, $3, $4, $5, 10.0, NOW())\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Text",
"Text",
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "cd5ccd618fb3cc41646a6de86f9afedb074492b4ec7f2457c14113f5fd13aa02"
}
@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO payouts (id, method, platform_id, status, user_id, amount, created)\n VALUES ($1, $2, NULL, $3, $4, 10.00, NOW())\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Text",
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "cec4240c7c848988b3dfd13e3f8e5c93783c7641b019fdb698a1ec0be1393606"
}
@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT pg_advisory_unlock(1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "pg_advisory_unlock",
"type_info": "Bool"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "fabba52b34e14196b6305671c060d934b9afc6dccc5fc77454fbe45365bdf429"
}
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM notifications WHERE user_id = $1 AND body->>'type' = 'payout_available'",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
null
]
},
"hash": "fd5c773a61d35bcd71503ec4d5f86e8917cfab9679d5064074681663ba467e41"
}
@@ -158,8 +158,9 @@ pub async fn test_rule(
})?;
let rule = request.rule;
let expression = rule.clone();
let program =
tokio::task::spawn_blocking(move || cel::Program::compile(&rule))
tokio::task::spawn_blocking(move || cel::Program::compile(&expression))
.await
.wrap_internal_err("failed to join cel compilation task")?
.map_err(|error| {
@@ -168,12 +169,11 @@ pub async fn test_rule(
let mut effects = Vec::with_capacity(request.inputs.len());
for (index, input) in request.inputs.iter().enumerate() {
let effect = super::rules_scan::evaluate_rule(&program, input)
.map_err(|error| {
ApiError::Request(eyre!(
"failed to evaluate test input {index}: {error}"
))
})?;
let evaluation =
super::rules_scan::evaluate_rule(&program, &rule, input);
let effect = evaluation.wrap_request_err(format!(
"failed to evaluate test input {index}"
))?;
effects.push(effect);
}
@@ -58,7 +58,9 @@ pub struct RuleInput {
pub trace: RuleTrace,
pub scan: RuleScan,
pub artifact: RuleArtifact,
pub scope: RuleScope,
pub project: RuleProject,
pub version: RuleVersion,
pub file: RuleFile,
}
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
@@ -83,14 +85,26 @@ pub struct RuleArtifact {
}
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
pub struct RuleScope {
pub project_id: Option<String>,
pub version_id: Option<String>,
pub file_id: Option<String>,
pub struct RuleProject {
pub id: Option<String>,
pub types: Vec<String>,
}
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
pub struct RuleVersion {
pub id: Option<String>,
pub loaders: Vec<String>,
}
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
pub struct RuleFile {
pub id: Option<String>,
}
struct CompiledRule {
id: DelphiRuleId,
name: String,
expression: String,
program: cel::Program,
}
@@ -197,6 +211,10 @@ pub async fn get_detail_rule_input(
file.id AS "file_id?",
version.id AS "version_id?",
version.mod_id AS "project_id?",
COALESCE(version_metadata.project_types, ARRAY[]::text[])
AS "project_types!: Vec<String>",
COALESCE(version_metadata.loaders, ARRAY[]::text[])
AS "loaders!: Vec<String>",
COALESCE(file_hashes.hashes, '{}'::jsonb)
AS "hashes!: Json<BTreeMap<String, String>>"
FROM delphi_report_issue_details detail
@@ -204,6 +222,26 @@ pub async fn get_detail_rule_input(
INNER JOIN delphi_reports report ON report.id = issue.report_id
LEFT JOIN files file ON file.id = report.file_id
LEFT JOIN versions version ON version.id = file.version_id
LEFT JOIN LATERAL (
SELECT
ARRAY_AGG(
DISTINCT project_type.name::text
ORDER BY project_type.name::text
) FILTER (WHERE project_type.name IS NOT NULL)
AS project_types,
ARRAY_AGG(
DISTINCT loader.loader
ORDER BY loader.loader
) AS loaders
FROM loaders_versions loader_version
INNER JOIN loaders loader
ON loader.id = loader_version.loader_id
LEFT JOIN loaders_project_types loader_project_type
ON loader_project_type.joining_loader_id = loader_version.loader_id
LEFT JOIN project_types project_type
ON project_type.id = loader_project_type.joining_project_type_id
WHERE loader_version.version_id = version.id
) version_metadata ON TRUE
LEFT JOIN LATERAL (
SELECT
jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes
@@ -237,10 +275,16 @@ pub async fn get_detail_rule_input(
size: detail.size,
hashes: detail.hashes.0,
},
scope: RuleScope {
project_id: detail.project_id.map(|id| to_base62(id as u64)),
version_id: detail.version_id.map(|id| to_base62(id as u64)),
file_id: detail.file_id.map(|id| to_base62(id as u64)),
project: RuleProject {
id: detail.project_id.map(|id| to_base62(id as u64)),
types: detail.project_types,
},
version: RuleVersion {
id: detail.version_id.map(|id| to_base62(id as u64)),
loaders: detail.loaders,
},
file: RuleFile {
id: detail.file_id.map(|id| to_base62(id as u64)),
},
}))
}
@@ -331,7 +375,7 @@ pub async fn scan_rules(
}
Err(error) => {
tracing::error!(error = ?error, "delphi rule scan failed");
let message = error.to_string();
let message = format!("{error:#}");
let event = RuleScanErrorEvent { message: &message };
if let Ok(data) = serde_json::to_string(&event) {
let _ = sender.send(Bytes::from(format!(
@@ -402,6 +446,10 @@ async fn run_scan(
file.id AS "file_id?",
version.id AS "version_id?",
version.mod_id AS "project_id?",
COALESCE(version_metadata.project_types, ARRAY[]::text[])
AS "project_types!: Vec<String>",
COALESCE(version_metadata.loaders, ARRAY[]::text[])
AS "loaders!: Vec<String>",
COALESCE(file_hashes.hashes, '{}'::jsonb)
AS "hashes!: Json<BTreeMap<String, String>>"
FROM delphi_report_issue_details detail
@@ -409,6 +457,28 @@ async fn run_scan(
INNER JOIN delphi_reports report ON report.id = issue.report_id
LEFT JOIN files file ON file.id = report.file_id
LEFT JOIN versions version ON version.id = file.version_id
LEFT JOIN (
SELECT
loader_version.version_id,
ARRAY_AGG(
DISTINCT project_type.name::text
ORDER BY project_type.name::text
) FILTER (WHERE project_type.name IS NOT NULL)
AS project_types,
ARRAY_AGG(
DISTINCT loader.loader
ORDER BY loader.loader
) AS loaders
FROM loaders_versions loader_version
INNER JOIN loaders loader
ON loader.id = loader_version.loader_id
LEFT JOIN loaders_project_types loader_project_type
ON loader_project_type.joining_loader_id = loader_version.loader_id
LEFT JOIN project_types project_type
ON project_type.id = loader_project_type.joining_project_type_id
GROUP BY loader_version.version_id
) version_metadata
ON version_metadata.version_id = version.id
LEFT JOIN (
SELECT
file_id,
@@ -458,19 +528,29 @@ async fn run_scan(
size: detail.size,
hashes: detail.hashes.0,
},
scope: RuleScope {
project_id: detail.project_id.map(|id| to_base62(id as u64)),
version_id: detail.version_id.map(|id| to_base62(id as u64)),
file_id: detail.file_id.map(|id| to_base62(id as u64)),
project: RuleProject {
id: detail.project_id.map(|id| to_base62(id as u64)),
types: detail.project_types,
},
version: RuleVersion {
id: detail.version_id.map(|id| to_base62(id as u64)),
loaders: detail.loaders,
},
file: RuleFile {
id: detail.file_id.map(|id| to_base62(id as u64)),
},
};
for rule in &rules {
let effect = evaluate_rule(&rule.program, &input)
let effect = evaluate_rule(
&rule.program,
&rule.expression,
&input,
)
.wrap_err_with(|| {
format!(
"failed to evaluate delphi rule {} for detail {detail_id}",
rule.id.0
"failed to evaluate delphi rule '{}' for detail {detail_id}",
rule.name
)
})?;
if let Some(effect) = effect {
@@ -642,6 +722,10 @@ pub(crate) async fn materialize_current_rule_effects(
file.id AS "file_id?",
version.id AS "version_id?",
version.mod_id AS "project_id?",
COALESCE(version_metadata.project_types, ARRAY[]::text[])
AS "project_types!: Vec<String>",
COALESCE(version_metadata.loaders, ARRAY[]::text[])
AS "loaders!: Vec<String>",
COALESCE(file_hashes.hashes, '{}'::jsonb)
AS "hashes!: Json<BTreeMap<String, String>>"
FROM delphi_report_issue_details detail
@@ -649,6 +733,26 @@ pub(crate) async fn materialize_current_rule_effects(
INNER JOIN delphi_reports report ON report.id = issue.report_id
LEFT JOIN files file ON file.id = report.file_id
LEFT JOIN versions version ON version.id = file.version_id
LEFT JOIN LATERAL (
SELECT
ARRAY_AGG(
DISTINCT project_type.name::text
ORDER BY project_type.name::text
) FILTER (WHERE project_type.name IS NOT NULL)
AS project_types,
ARRAY_AGG(
DISTINCT loader.loader
ORDER BY loader.loader
) AS loaders
FROM loaders_versions loader_version
INNER JOIN loaders loader
ON loader.id = loader_version.loader_id
LEFT JOIN loaders_project_types loader_project_type
ON loader_project_type.joining_loader_id = loader_version.loader_id
LEFT JOIN project_types project_type
ON project_type.id = loader_project_type.joining_project_type_id
WHERE loader_version.version_id = version.id
) version_metadata ON TRUE
LEFT JOIN LATERAL (
SELECT
jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes
@@ -683,19 +787,25 @@ pub(crate) async fn materialize_current_rule_effects(
size: detail.size,
hashes: detail.hashes.0,
},
scope: RuleScope {
project_id: detail.project_id.map(|id| to_base62(id as u64)),
version_id: detail.version_id.map(|id| to_base62(id as u64)),
file_id: detail.file_id.map(|id| to_base62(id as u64)),
project: RuleProject {
id: detail.project_id.map(|id| to_base62(id as u64)),
types: detail.project_types,
},
version: RuleVersion {
id: detail.version_id.map(|id| to_base62(id as u64)),
loaders: detail.loaders,
},
file: RuleFile {
id: detail.file_id.map(|id| to_base62(id as u64)),
},
};
for rule in &rules {
let effect =
evaluate_rule(&rule.program, &input).wrap_err_with(|| {
let effect = evaluate_rule(&rule.program, &rule.expression, &input)
.wrap_err_with(|| {
format!(
"failed to evaluate delphi rule {} for detail {}",
rule.id.0, detail.id
"failed to evaluate delphi rule '{}' for detail {}",
rule.name, detail.id
)
})?;
if let Some(effect) = effect {
@@ -717,7 +827,7 @@ async fn fetch_compiled_rules(
) -> Result<Vec<CompiledRule>> {
let rules = sqlx::query!(
r#"
SELECT id AS "id!: DelphiRuleId", rule
SELECT id AS "id!: DelphiRuleId", name, rule
FROM delphi_rules
WHERE NOT delete_on_next_revision
ORDER BY priority DESC, id
@@ -734,12 +844,14 @@ async fn fetch_compiled_rules(
let program =
cel::Program::compile(&rule.rule).map_err(|error| {
eyre!(
"failed to compile delphi rule {}: {error}",
rule.id.0
"failed to compile delphi rule '{}': {error}",
rule.name
)
})?;
Ok(CompiledRule {
id: rule.id,
name: rule.name,
expression: rule.rule,
program,
})
})
@@ -799,6 +911,21 @@ async fn insert_materialized_effects(
}
pub(super) fn evaluate_rule(
program: &cel::Program,
expression: &str,
input: &RuleInput,
) -> Result<Option<DelphiRuleEffect>> {
evaluate_rule_inner(program, input)
.wrap_err_with(|| {
let input = serde_json::to_string(input).unwrap_or_else(|error| {
format!("<failed to serialize CEL input: {error}>")
});
format!("CEL input: {input}")
})
.wrap_err_with(|| format!("CEL expression: {expression}"))
}
fn evaluate_rule_inner(
program: &cel::Program,
input: &RuleInput,
) -> Result<Option<DelphiRuleEffect>> {
@@ -816,8 +943,14 @@ pub(super) fn evaluate_rule(
.add_variable("artifact", &input.artifact)
.wrap_err("failed to add `artifact` to cel context")?;
context
.add_variable("scope", &input.scope)
.wrap_err("failed to add `scope` to cel context")?;
.add_variable("project", &input.project)
.wrap_err("failed to add `project` to cel context")?;
context
.add_variable("version", &input.version)
.wrap_err("failed to add `version` to cel context")?;
context
.add_variable("file", &input.file)
.wrap_err("failed to add `file` to cel context")?;
let value = program
.execute(&context)