diff --git a/packages/api/src/router/policy.ts b/packages/api/src/router/policy.ts index 2e2332caf..373b9d6b5 100644 --- a/packages/api/src/router/policy.ts +++ b/packages/api/src/router/policy.ts @@ -1,156 +1,168 @@ import { openai } from "@ai-sdk/openai"; +import { TRPCError } from "@trpc/server"; import { generateText } from "ai"; import _ from "lodash"; +import { isPresent } from "ts-is-present"; import { z } from "zod"; -import { and, asc, desc, eq, ilike, takeFirst } from "@ctrlplane/db"; import { - createPolicy, - createPolicyRuleDenyWindow, - createPolicyTarget, - policy, - policyRuleDenyWindow, - policyTarget, - updatePolicy, - updatePolicyRuleDenyWindow, - updatePolicyTarget, -} from "@ctrlplane/db/schema"; + and, + asc, + desc, + eq, + ilike, + inArray, + isNull, + selector, + takeFirst, +} from "@ctrlplane/db"; +import { db } from "@ctrlplane/db/client"; import * as schema from "@ctrlplane/db/schema"; +import { policy } from "@ctrlplane/db/schema"; import { Channel, getQueue } from "@ctrlplane/events"; +import { + FilterRule, + mergePolicies, + Policy, + Version, + versionAnyApprovalRule, + versionRoleApprovalRule, + versionUserApprovalRule, +} from "@ctrlplane/rule-engine"; import { createPolicyInTx, updatePolicyInTx } from "@ctrlplane/rule-engine/db"; import { Permission } from "@ctrlplane/validators/auth"; import { createTRPCRouter, protectedProcedure } from "../trpc"; -export const policyRouter = createTRPCRouter({ - ai: createTRPCRouter({ - generateName: protectedProcedure - .input( - z.record(z.string(), z.any()).and( - z.object({ - workspaceId: z.string().uuid(), - }), - ), - ) - .meta({ - authorizationCheck: ({ canUser, input }) => - canUser - .perform(Permission.PolicyCreate) - .on({ type: "workspace", id: input.workspaceId }), - }) - .mutation(async ({ input }) => { - const { text } = await generateText({ - model: openai("gpt-4-turbo"), - messages: [ - { - role: "system", - content: ` - You are a devops engineer assistant that generates names for policies. - Based on the provided object for a Policy, generate a short title that describes - what the policy is about. - - The policy configuration can include: - - Targets: Deployment and environment selectors that determine what this policy applies to - - Deny Windows: Time windows when deployments are not allowed - - Version Selector: Rules about which versions can be deployed - - Approval Requirements: Any approvals needed from users or roles before deployment - - If there are no targets, that means it won't be applied to any deployments - - All approval rules are and operations. All conditions must be met before the policy allows a deployment - - Generate a concise name that captures the key purpose of the policy based on its configuration. - The name should be no more than 50 characters. - `, - }, - { - role: "user", - content: JSON.stringify( - _.omit(input, [ - "workspaceId", - "id", - "description", - "createdAt", - "updatedAt", - "name", - "enabled", - ]), - ), - }, - ], - }); +export const policyAiRouter = createTRPCRouter({ + generateName: protectedProcedure + .input( + z.record(z.string(), z.any()).and( + z.object({ + workspaceId: z.string().uuid(), + }), + ), + ) + .meta({ + authorizationCheck: ({ canUser, input }) => + canUser + .perform(Permission.PolicyCreate) + .on({ type: "workspace", id: input.workspaceId }), + }) + .mutation(async ({ input }) => { + const { text } = await generateText({ + model: openai("gpt-4-turbo"), + messages: [ + { + role: "system", + content: ` + You are a devops engineer assistant that generates names for policies. + Based on the provided object for a Policy, generate a short title that describes + what the policy is about. + + The policy configuration can include: + - Targets: Deployment and environment selectors that determine what this policy applies to + - Deny Windows: Time windows when deployments are not allowed + - Version Selector: Rules about which versions can be deployed + - Approval Requirements: Any approvals needed from users or roles before deployment + - If there are no targets, that means it won't be applied to any deployments + - All approval rules are and operations. All conditions must be met before the policy allows a deployment + + Generate a concise name that captures the key purpose of the policy based on its configuration. + The name should be no more than 50 characters. + `, + }, + { + role: "user", + content: JSON.stringify( + _.omit(input, [ + "workspaceId", + "id", + "description", + "createdAt", + "updatedAt", + "name", + "enabled", + ]), + ), + }, + ], + }); - return text - .trim() - .replaceAll("`", "") - .replaceAll("'", "") - .replaceAll('"', ""); - }), + return text + .trim() + .replaceAll("`", "") + .replaceAll("'", "") + .replaceAll('"', ""); + }), - generateDescription: protectedProcedure - .input( - z.record(z.string(), z.any()).and( - z.object({ - workspaceId: z.string().uuid(), - }), - ), - ) - .meta({ - authorizationCheck: ({ canUser, input }) => - canUser - .perform(Permission.PolicyCreate) - .on({ type: "workspace", id: input.workspaceId }), - }) - .mutation(async ({ input }) => { - const { text } = await generateText({ - model: openai("gpt-4-turbo"), - messages: [ - { - role: "system", - content: ` - You are a devops engineer assistant that generates descriptions for policies. - Based on the provided object for a Policy, generate a description that explains - the purpose and configuration. The description should cover: - - - Target deployments and environments - - Time-based restrictions (deny windows) - - Version deployment rules and requirements - - Required approvals from users or roles - - If there are no targets, that means it won't be applied to any deployments - - All approval rules are and operations. All conditions must be met before the - policy allows a deployment - - Focus on stating active policy configurations. Only describe features with enabled restrictions. - - Keep the description under 60 words and write it in a technical style suitable - for DevOps engineers and platform users. Focus on being clear and precise about - the controls and enforcement mechanisms. It is already clear that you are talking - about the policy in question. - - Do not include phrases like "The policy...", "This policy...". - `, - }, - { - role: "user", - content: JSON.stringify( - _.omit(input, [ - "workspaceId", - "id", - "createdAt", - "updatedAt", - "enabled", - "priority", - ]), - ), - }, - ], - }); + generateDescription: protectedProcedure + .input( + z.record(z.string(), z.any()).and( + z.object({ + workspaceId: z.string().uuid(), + }), + ), + ) + .meta({ + authorizationCheck: ({ canUser, input }) => + canUser + .perform(Permission.PolicyCreate) + .on({ type: "workspace", id: input.workspaceId }), + }) + .mutation(async ({ input }) => { + const { text } = await generateText({ + model: openai("gpt-4-turbo"), + messages: [ + { + role: "system", + content: ` + You are a devops engineer assistant that generates descriptions for policies. + Based on the provided object for a Policy, generate a description that explains + the purpose and configuration. The description should cover: - return text - .trim() - .replaceAll("`", "") - .replaceAll("'", "") - .replaceAll('"', ""); - }), - }), + - Target deployments and environments + - Time-based restrictions (deny windows) + - Version deployment rules and requirements + - Required approvals from users or roles + - If there are no targets, that means it won't be applied to any deployments + - All approval rules are and operations. All conditions must be met before the + policy allows a deployment + - Focus on stating active policy configurations. Only describe features with enabled restrictions. + Keep the description under 60 words and write it in a technical style suitable + for DevOps engineers and platform users. Focus on being clear and precise about + the controls and enforcement mechanisms. It is already clear that you are talking + about the policy in question. + + Do not include phrases like "The policy...", "This policy...". + `, + }, + { + role: "user", + content: JSON.stringify( + _.omit(input, [ + "workspaceId", + "id", + "createdAt", + "updatedAt", + "enabled", + "priority", + ]), + ), + }, + ], + }); + + return text + .trim() + .replaceAll("`", "") + .replaceAll("'", "") + .replaceAll('"', ""); + }), +}); + +export const policyRouter = createTRPCRouter({ list: protectedProcedure .meta({ authorizationCheck: ({ canUser, input }) => @@ -168,9 +180,9 @@ export const policyRouter = createTRPCRouter({ .query(({ ctx, input }) => ctx.db.query.policy.findMany({ where: and( - eq(policy.workspaceId, input.workspaceId), + eq(schema.policy.workspaceId, input.workspaceId), input.search != null - ? ilike(policy.name, `%${input.search}%`) + ? ilike(schema.policy.name, `%${input.search}%`) : undefined, ), with: { @@ -181,7 +193,7 @@ export const policyRouter = createTRPCRouter({ versionUserApprovals: true, versionRoleApprovals: true, }, - orderBy: [desc(policy.priority), asc(policy.name)], + orderBy: [desc(schema.policy.priority), asc(schema.policy.name)], limit: input.limit, }), ), @@ -201,7 +213,7 @@ export const policyRouter = createTRPCRouter({ ) .query(({ ctx, input }) => ctx.db.query.policy.findFirst({ - where: eq(policy.id, input.policyId), + where: eq(schema.policy.id, input.policyId), with: { targets: true, denyWindows: true, @@ -282,7 +294,7 @@ export const policyRouter = createTRPCRouter({ .perform(Permission.PolicyCreate) .on({ type: "workspace", id: input.workspaceId }), }) - .input(createPolicy) + .input(schema.createPolicy) .mutation(async ({ ctx, input }) => { const policy = await ctx.db.transaction((tx) => createPolicyInTx(tx, input), @@ -298,7 +310,7 @@ export const policyRouter = createTRPCRouter({ .perform(Permission.PolicyUpdate) .on({ type: "policy", id: input.id }), }) - .input(z.object({ id: z.string().uuid(), data: updatePolicy })) + .input(z.object({ id: z.string().uuid(), data: schema.updatePolicy })) .mutation(async ({ ctx, input }) => { const policy = await ctx.db.transaction((tx) => updatePolicyInTx(tx, input.id, input.data), @@ -317,8 +329,8 @@ export const policyRouter = createTRPCRouter({ .input(z.string().uuid()) .mutation(({ ctx, input }) => ctx.db - .delete(policy) - .where(eq(policy.id, input)) + .delete(schema.policy) + .where(eq(schema.policy.id, input)) .returning() .then(takeFirst), ), @@ -331,9 +343,13 @@ export const policyRouter = createTRPCRouter({ .perform(Permission.PolicyCreate) .on({ type: "policy", id: input.policyId }), }) - .input(createPolicyTarget) + .input(schema.createPolicyTarget) .mutation(({ ctx, input }) => - ctx.db.insert(policyTarget).values(input).returning().then(takeFirst), + ctx.db + .insert(schema.policyTarget) + .values(input) + .returning() + .then(takeFirst), ), updateTarget: protectedProcedure @@ -341,8 +357,8 @@ export const policyRouter = createTRPCRouter({ authorizationCheck: async ({ canUser, input, ctx }) => { const target = await ctx.db .select() - .from(policyTarget) - .where(eq(policyTarget.id, input.id)) + .from(schema.policyTarget) + .where(eq(schema.policyTarget.id, input.id)) .then(takeFirst); return canUser @@ -350,12 +366,12 @@ export const policyRouter = createTRPCRouter({ .on({ type: "policy", id: target.policyId }); }, }) - .input(z.object({ id: z.string().uuid(), data: updatePolicyTarget })) + .input(z.object({ id: z.string().uuid(), data: schema.updatePolicyTarget })) .mutation(({ ctx, input }) => ctx.db - .update(policyTarget) + .update(schema.policyTarget) .set(input.data) - .where(eq(policyTarget.id, input.id)) + .where(eq(schema.policyTarget.id, input.id)) .returning() .then(takeFirst), ), @@ -365,8 +381,8 @@ export const policyRouter = createTRPCRouter({ authorizationCheck: async ({ canUser, input, ctx }) => { const target = await ctx.db .select() - .from(policyTarget) - .where(eq(policyTarget.id, input)) + .from(schema.policyTarget) + .where(eq(schema.policyTarget.id, input)) .then(takeFirst); return canUser @@ -377,8 +393,8 @@ export const policyRouter = createTRPCRouter({ .input(z.string().uuid()) .mutation(({ ctx, input }) => ctx.db - .delete(policyTarget) - .where(eq(policyTarget.id, input)) + .delete(schema.policyTarget) + .where(eq(schema.policyTarget.id, input)) .returning() .then(takeFirst), ), @@ -391,10 +407,10 @@ export const policyRouter = createTRPCRouter({ .perform(Permission.PolicyCreate) .on({ type: "policy", id: input.policyId }), }) - .input(createPolicyRuleDenyWindow) + .input(schema.createPolicyRuleDenyWindow) .mutation(({ ctx, input }) => { return ctx.db - .insert(policyRuleDenyWindow) + .insert(schema.policyRuleDenyWindow) .values(input) .returning() .then(takeFirst); @@ -405,8 +421,8 @@ export const policyRouter = createTRPCRouter({ authorizationCheck: async ({ canUser, input, ctx }) => { const denyWindow = await ctx.db .select() - .from(policyRuleDenyWindow) - .where(eq(policyRuleDenyWindow.id, input.id)) + .from(schema.policyRuleDenyWindow) + .where(eq(schema.policyRuleDenyWindow.id, input.id)) .then(takeFirst); return canUser @@ -415,13 +431,16 @@ export const policyRouter = createTRPCRouter({ }, }) .input( - z.object({ id: z.string().uuid(), data: updatePolicyRuleDenyWindow }), + z.object({ + id: z.string().uuid(), + data: schema.updatePolicyRuleDenyWindow, + }), ) .mutation(({ ctx, input }) => { return ctx.db - .update(policyRuleDenyWindow) + .update(schema.policyRuleDenyWindow) .set(input.data) - .where(eq(policyRuleDenyWindow.id, input.id)) + .where(eq(schema.policyRuleDenyWindow.id, input.id)) .returning() .then(takeFirst); }), @@ -431,8 +450,8 @@ export const policyRouter = createTRPCRouter({ authorizationCheck: async ({ canUser, input, ctx }) => { const denyWindow = await ctx.db .select() - .from(policyRuleDenyWindow) - .where(eq(policyRuleDenyWindow.id, input)) + .from(schema.policyRuleDenyWindow) + .where(eq(schema.policyRuleDenyWindow.id, input)) .then(takeFirst); return canUser @@ -443,9 +462,218 @@ export const policyRouter = createTRPCRouter({ .input(z.string().uuid()) .mutation(({ ctx, input }) => ctx.db - .delete(policyRuleDenyWindow) - .where(eq(policyRuleDenyWindow.id, input)) + .delete(schema.policyRuleDenyWindow) + .where(eq(schema.policyRuleDenyWindow.id, input)) .returning() .then(takeFirst), ), + + /** + * Router for handling environment-specific policy evaluations. This router + * provides endpoints for evaluating how policies apply to specific + * environments and versions within those environments. + */ + environmentPolicy: createTRPCRouter({ + /** + * Evaluates whether a specific version can be deployed to a given + * environment based on all applicable policies. This is a critical security + * and compliance check that determines if a deployment should be allowed to + * proceed. + * + * @param environmentId - The ID of the environment to evaluate against + * @param versionId - The ID of the version being evaluated + * @returns Object containing all applicable policies and their evaluation + * results + */ + evaluateVersion: protectedProcedure + .input( + z.object({ + environmentId: z.string().uuid(), + versionId: z.string().uuid(), + }), + ) + .query(async ({ ctx, input: { environmentId, versionId } }) => { + // First, find the environment and its associated workspace This is + // needed to scope the policy search to the correct workspace + const environment = await ctx.db.query.environment.findFirst({ + where: eq(schema.environment.id, environmentId), + with: { system: { with: { workspace: true } } }, + }); + if (environment == null) throw new Error("Environment not found"); + + const workspace = environment.system.workspace; + + // Find all policies that apply to this environment. This complex query + // joins through the policy target chain to find policies that are + // specifically targeted at this environment's release target + const applicablePolicyIds = await ctx.db + .selectDistinctOn([schema.policy.id], { policyId: schema.policy.id }) + .from(schema.policy) + .innerJoin( + schema.policyTarget, + eq(schema.policy.id, schema.policyTarget.policyId), + ) + .innerJoin( + schema.computedPolicyTargetReleaseTarget, + eq( + schema.policyTarget.id, + schema.computedPolicyTargetReleaseTarget.policyTargetId, + ), + ) + .innerJoin( + schema.releaseTarget, + eq( + schema.computedPolicyTargetReleaseTarget.releaseTargetId, + schema.releaseTarget.id, + ), + ) + .where( + and( + isNull(schema.policyTarget.resourceSelector), + isNull(schema.policyTarget.deploymentSelector), + eq(schema.policy.workspaceId, workspace.id), + eq(schema.policy.enabled, true), + ), + ) + .then((r) => r.map((r) => r.policyId)); + + // Load the full policy details including all their rules and conditions + const policies = await ctx.db.query.policy.findMany({ + where: inArray(schema.policy.id, applicablePolicyIds), + with: { + denyWindows: true, + deploymentVersionSelector: true, + versionAnyApprovals: true, + versionUserApprovals: true, + versionRoleApprovals: true, + }, + }); + + // Get the version details including its metadata This is needed to + // evaluate version-specific rules + const candidateVersion = await ctx.db.query.deploymentVersion.findFirst( + { + where: eq(schema.deploymentVersion.id, versionId), + with: { + metadata: true, + }, + }, + ); + if (candidateVersion == null) + throw new TRPCError({ + code: "NOT_FOUND", + message: "Version not found", + }); + + // Format the version data for rule evaluation + const version = [ + { + ...candidateVersion, + metadata: Object.fromEntries( + candidateVersion.metadata.map((m) => [m.key, m.value]), + ), + }, + ]; + + // Evaluate each type of approval rule These checks determine if the + // version has the required approvals + const userApprovals = await getApprovalReasons( + policies, + version, + versionId, + (policy) => versionUserApprovalRule(policy.versionUserApprovals), + ); + + const roleApprovals = await getApprovalReasons( + policies, + version, + versionId, + (policy) => versionRoleApprovalRule(policy.versionRoleApprovals), + ); + + const anyApprovals = await getApprovalReasons( + policies, + version, + versionId, + (policy) => versionAnyApprovalRule(policy.versionAnyApprovals), + ); + + // Return all evaluation results + return { + policies, + rules: { + userApprovals, + roleApprovals, + anyApprovals, + versionSelector: Object.fromEntries( + await Promise.all( + policies.map( + async (p) => + [p.id, await getVersionSelector(p, versionId)] as const, + ), + ), + ), + }, + }; + }), + }), }); + +/** + * Evaluates whether a version matches a policy's version selector rules. + * This is used to determine if a version is allowed to be deployed based on + * policy-specific criteria like version numbers, tags, or other metadata. + * + * @param policy - The policy containing the version selector rules + * @param versionId - The ID of the version being evaluated + * @returns true if the version matches the selector rules, false otherwise + */ +const getVersionSelector = (policy: Policy, versionId: string) => { + const selectorQuery = + policy.deploymentVersionSelector?.deploymentVersionSelector; + if (selectorQuery == null) return true; + return db + .select() + .from(schema.deploymentVersion) + .where( + and( + eq(schema.deploymentVersion.id, versionId), + selector().query().deploymentVersions().where(selectorQuery).sql(), + ), + ) + .then((r) => r.length > 0); +}; + +/** + * Evaluates approval rules for a set of policies and returns any rejection reasons. + * This function is used to check if a version has the required approvals from + * users, roles, or any other specified approvers. + * + * @param policies - The policies to evaluate + * @param version - The version being evaluated + * @param versionId - The ID of the version + * @param ruleGetter - Function that extracts the relevant approval rules from a policy + * @returns Object mapping policy IDs to arrays of rejection reasons + */ +const getApprovalReasons = async ( + policies: Policy[], + version: Version[], + versionId: string, + ruleGetter: (policy: Policy) => Array>, +) => { + return Object.fromEntries( + await Promise.all( + policies.map(async (policy) => { + const rules = ruleGetter(policy); + const rejectionReasons = await Promise.all( + rules.map(async (rule) => { + const result = await rule.filter(version); + return result.rejectionReasons?.get(versionId) || null; + }), + ); + const o = rejectionReasons.filter(isPresent); + return [policy.id, o] as const; + }), + ), + ); +}; diff --git a/packages/rule-engine/src/manager/version-manager-rules.ts b/packages/rule-engine/src/manager/version-manager-rules.ts index 3d722e37d..3d45f0373 100644 --- a/packages/rule-engine/src/manager/version-manager-rules.ts +++ b/packages/rule-engine/src/manager/version-manager-rules.ts @@ -20,7 +20,7 @@ export const denyWindows = (policy: Policy | null) => }), ); -const versionAnyApprovalRule = ( +export const versionAnyApprovalRule = ( approvalRules?: Policy["versionAnyApprovals"] | null, ) => { if (approvalRules == null) return []; @@ -32,7 +32,7 @@ const versionAnyApprovalRule = ( ]; }; -const versionRoleApprovalRule = ( +export const versionRoleApprovalRule = ( approvalRules?: Policy["versionRoleApprovals"] | null, ) => { if (approvalRules == null) return []; @@ -45,7 +45,7 @@ const versionRoleApprovalRule = ( ); }; -const versionUserApprovalRule = ( +export const versionUserApprovalRule = ( approvalRules?: Policy["versionUserApprovals"] | null, ) => { if (approvalRules == null) return []; diff --git a/packages/rule-engine/src/manager/version-manager.ts b/packages/rule-engine/src/manager/version-manager.ts index 086f1baaa..94fca1432 100644 --- a/packages/rule-engine/src/manager/version-manager.ts +++ b/packages/rule-engine/src/manager/version-manager.ts @@ -24,7 +24,6 @@ import type { import type { ReleaseManager, ReleaseTarget } from "./types.js"; import { getApplicablePolicies } from "../db/get-applicable-policies.js"; import { VersionRuleEngine } from "../manager/version-rule-engine.js"; -import { ConstantMap, isFilterRule, isPreValidationRule } from "../types.js"; import { mergePolicies } from "../utils/merge-policies.js"; import { getRules } from "./version-manager-rules.js"; @@ -190,7 +189,7 @@ export class VersionReleaseManager implements ReleaseManager { const versions = options?.versions ?? (await this.findVersionsForEvaluate()); - const result = await engine.evaluate(ctx, versions); + const result = await engine.evaluate(versions); return result; } } diff --git a/packages/rule-engine/src/manager/version-rule-engine.ts b/packages/rule-engine/src/manager/version-rule-engine.ts index 98365a938..2b2b0219d 100644 --- a/packages/rule-engine/src/manager/version-rule-engine.ts +++ b/packages/rule-engine/src/manager/version-rule-engine.ts @@ -61,13 +61,10 @@ export class VersionRuleEngine implements RuleEngine { * @returns A promise resolving to the evaluation result, including chosen version * and any rejection reasons */ - async evaluate( - context: RuleEngineContext, - candidates: Version[], - ): Promise> { + async evaluate(candidates: Version[]): Promise> { const preValidationRules = this.rules.filter(isPreValidationRule); for (const rule of preValidationRules) { - const result = rule.passing(context); + const result = rule.passing(); if (!result.passing) { return { @@ -83,7 +80,7 @@ export class VersionRuleEngine implements RuleEngine { const filterRules = this.rules.filter(isFilterRule); for (const rule of filterRules) { - const result = await rule.filter(context, candidates); + const result = await rule.filter(candidates); // If the rule yields no candidates, we must stop. if (result.allowedCandidates.length === 0) { @@ -105,7 +102,7 @@ export class VersionRuleEngine implements RuleEngine { } // Once all rules pass, select the final version - const chosen = this.selectFinalRelease(context, candidates); + const chosen = this.selectFinalRelease(candidates); return chosen == null ? { chosenCandidate: null, @@ -133,10 +130,7 @@ export class VersionRuleEngine implements RuleEngine { * @param candidates - The list of version candidates that passed all rules * @returns The selected version, or undefined if no suitable version can be chosen */ - private selectFinalRelease( - __: RuleEngineContext, - candidates: Version[], - ): Version | undefined { + private selectFinalRelease(candidates: Version[]): Version | undefined { if (candidates.length === 0) { return undefined; } diff --git a/packages/rule-engine/src/rules/deployment-deny-rule.ts b/packages/rule-engine/src/rules/deployment-deny-rule.ts index 525156b8b..0dc9b623f 100644 --- a/packages/rule-engine/src/rules/deployment-deny-rule.ts +++ b/packages/rule-engine/src/rules/deployment-deny-rule.ts @@ -91,7 +91,7 @@ export class DeploymentDenyRule implements PreValidationRule { return new Date(); } - passing(_: RuleEngineContext): PreValidationResult { + passing(): PreValidationResult { const now = this.getCurrentTime(); // Check if current time matches one of the rrules diff --git a/packages/rule-engine/src/rules/version-approval-rule.ts b/packages/rule-engine/src/rules/version-approval-rule.ts index 83c4ccca4..2549a63b6 100644 --- a/packages/rule-engine/src/rules/version-approval-rule.ts +++ b/packages/rule-engine/src/rules/version-approval-rule.ts @@ -19,7 +19,6 @@ type Record = { }; export type GetApprovalRecordsFunc = ( - context: RuleEngineContext, versionIds: string[], ) => Promise; @@ -34,19 +33,13 @@ export class VersionApprovalRule implements FilterRule { constructor(private readonly options: VersionApprovalRuleOptions) {} - async filter( - context: RuleEngineContext, - candidates: Version[], - ): Promise> { + async filter(candidates: Version[]): Promise> { const rejectionReasons = new Map(); const versionIds = _(candidates) .map((r) => r.id) .uniq() .value(); - const approvalRecords = await this.options.getApprovalRecords( - context, - versionIds, - ); + const approvalRecords = await this.options.getApprovalRecords(versionIds); const allowedCandidates = candidates.filter((release) => { const records = approvalRecords.filter((r) => r.versionId === release.id); @@ -70,7 +63,6 @@ export class VersionApprovalRule implements FilterRule { } export const getAnyApprovalRecords: GetApprovalRecordsFunc = async ( - _: RuleEngineContext, versionIds: string[], ) => { const records = await db.query.policyRuleAnyApprovalRecord.findMany({ @@ -86,7 +78,6 @@ export const getAnyApprovalRecords: GetApprovalRecordsFunc = async ( }; export const getRoleApprovalRecords: GetApprovalRecordsFunc = async ( - _: RuleEngineContext, versionIds: string[], ) => { const records = await db.query.policyRuleRoleApprovalRecord.findMany({ @@ -102,7 +93,6 @@ export const getRoleApprovalRecords: GetApprovalRecordsFunc = async ( }; export const getUserApprovalRecords: GetApprovalRecordsFunc = async ( - _: RuleEngineContext, versionIds: string[], ) => { const records = await db.query.policyRuleUserApprovalRecord.findMany({ diff --git a/packages/rule-engine/src/types.ts b/packages/rule-engine/src/types.ts index bbfff2ce4..2c02ae2fc 100644 --- a/packages/rule-engine/src/types.ts +++ b/packages/rule-engine/src/types.ts @@ -38,7 +38,6 @@ export type RuleEngineRuleResult = { export interface FilterRule { name: string; filter( - context: RuleEngineContext, candidates: T[], ): RuleEngineRuleResult | Promise>; } @@ -50,7 +49,7 @@ export type PreValidationResult = { export interface PreValidationRule { name: string; - passing(context: RuleEngineContext): PreValidationResult; + passing(): PreValidationResult; } /** @@ -91,10 +90,7 @@ export type RuleSelectionResult = { }; export type RuleEngine = { - evaluate: ( - context: RuleEngineContext, - candidates: T[], - ) => Promise>; + evaluate: (candidates: T[]) => Promise>; }; export class ConstantMap extends Map {