import { randomBytes } from "node:crypto"; import { dbAll, dbGet, dbRun, withTransaction } from "./db.mjs"; import { addAudit, httpError } from "./tenant.mjs"; const now = () => new Date().toISOString(); const DEFAULT_FORBIDDEN_LAYOUT_TERMS = [ "split-screen", "comic panel", "collage", "contact sheet", "storyboard", "multi panel", "grid layout", "多格", "拼图", "分屏", "故事板", "多时间点" ]; const DEFAULT_MOUTH_FALLBACK_PLAN = ["侧脸", "背影", "低头", "远景", "反应镜头", "环境插入镜头"]; export const ORGANIZATION_POLICY_DEFINITIONS = [ { key: "model.local_runner_default", category: "model", label: "自有/本地模型优先", description: "默认把生成、解析、配音和检索任务路由到本地或自有模型平台。", status: "enforced", enforcement: "block", severity: "critical", value: { defaultLocalOnly: true, allowedCostModes: ["local", "mixed-with-approval"], preferredAdapter: "owned-model-platform", comfyuiAdapter: "optional" }, appliesTo: ["generation_jobs", "model_routes", "connectors"] }, { key: "model.external_connector_approval", category: "model", label: "外部/付费连接器审批", description: "第三方官方 API、公网中转和混合成本连接器必须先经过审批,密钥只能用环境变量引用。", status: "enforced", enforcement: "approval", severity: "critical", approvalRequired: true, value: { requireApprovalForExternal: true, requireSecretRefs: true, allowPublicEndpoints: false, approvedWindowHours: 24 }, appliesTo: ["model_connectors", "model_route_approvals", "generation_jobs"] }, { key: "generation.single_frame_contract", category: "production", label: "一图一完整单画面", description: "每次图片生成只能输出一张完整单画面,禁止分屏、多格、漫画拼图、故事板和 contact sheet。", status: "enforced", enforcement: "block", severity: "critical", value: { enabled: true, imageOutputCount: 1, batchSize: 1, forbiddenTerms: DEFAULT_FORBIDDEN_LAYOUT_TERMS, requiredPhrase: "one single complete frame" }, appliesTo: ["image_generation", "image_to_video", "prompt_packs", "qa"] }, { key: "voice.fixed_voice_required", category: "voice", label: "固定声线与口型规避", description: "最终角色声音必须来自固定参考声线;口型无法同步时,使用侧脸、背影、低头、远景和反应镜头规避。", status: "enforced", enforcement: "block", severity: "critical", value: { finalVoiceMode: "fixed-reference-required", forbidRandomNativeVoice: true, ttsPreference: "local-or-owned-first", fallbackMouthPlan: DEFAULT_MOUTH_FALLBACK_PLAN }, appliesTo: ["voice_lines", "tts_jobs", "generation_contracts"] }, { key: "delivery.actual_last_frame_required", category: "delivery", label: "连续镜头实际末帧", description: "非开场视频镜头必须引用上一段真实生成出来的末帧,不允许只用占位帧或评审拼图。", status: "enforced", enforcement: "block", severity: "high", value: { requireActualLastFrameForVideo: true, blockPlaceholderFrames: true, acceptedEvidenceKinds: ["last-frame", "media-qa", "delivery-clearance"] }, appliesTo: ["image_to_video", "shot_versions", "delivery_clearance"] }, { key: "compliance.rights_evidence_required", category: "compliance", label: "素材权利证据必填", description: "小说、角色图、道具图、声音参考和上传素材必须有原创或授权证据,才能进入商业生成与交付。", status: "enforced", enforcement: "block", severity: "high", value: { requiredForKinds: ["knowledge", "asset", "voice", "delivery"], acceptedStatuses: ["approved", "original"], rejectUnknownSource: true }, appliesTo: ["knowledge_documents", "asset_versions", "voice_references", "deliveries"] }, { key: "security.admin_mfa_required", category: "security", label: "管理账号 MFA", description: "组织所有者、组织管理员、模型管理员、账单管理员和系统管理员建议或强制启用 MFA。", status: "monitor", enforcement: "warn", severity: "medium", value: { requireForAdmins: true, requireForAll: false, methods: ["totp"] }, appliesTo: ["users", "sessions", "admin_actions"] }, { key: "retention.production_evidence", category: "storage", label: "生产证据留存", description: "保留生成合同、QA 结果、实际末帧、权利证据和交付清算记录,临时缓存按保留周期治理。", status: "enforced", enforcement: "warn", severity: "medium", value: { generationContractDays: 365, deliveryEvidenceDays: 730, temporaryMediaDays: 30, keepAuditLogs: true }, appliesTo: ["generation_jobs", "qa_results", "delivery_releases", "audit_logs"] } ]; function parseJson(value, fallback) { try { return JSON.parse(value); } catch { return fallback; } } function privateHost(hostname) { const host = String(hostname || "").toLowerCase(); if (["localhost", "127.0.0.1", "::1"].includes(host) || host.endsWith(".local")) return true; const octets = host.split(".").map(Number); if (octets.length !== 4 || octets.some((value) => !Number.isInteger(value) || value < 0 || value > 255)) return false; return octets[0] === 10 || octets[0] === 127 || (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) || (octets[0] === 192 && octets[1] === 168); } function hasPolicyPermission(context, permission) { return Boolean(context?.systemAdmin || (context?.permissions || []).includes(permission)); } function requirePolicyRead(context) { if (hasPolicyPermission(context, "policy:read") || hasPolicyPermission(context, "policy:manage") || hasPolicyPermission(context, "organization:manage") || hasPolicyPermission(context, "compliance:manage")) return; throw httpError(403, "permission_denied", "当前角色没有查看组织策略的权限"); } function requirePolicyManage(context) { if (hasPolicyPermission(context, "policy:manage") || hasPolicyPermission(context, "organization:manage")) return; throw httpError(403, "permission_denied", "当前角色没有管理组织策略的权限"); } function assertOrganizationScope(context, organizationId) { if (context?.organization?.id === organizationId) return; throw httpError(403, "organization_policy_scope_denied", "不能访问其他组织的策略", { organizationId }); } function policyId(organizationId, policyKey) { return `policy-${organizationId}-${policyKey.replace(/[^a-z0-9]+/gi, "-")}`; } function policyPayload(row) { return { id: row.id, organizationId: row.organization_id, policyKey: row.policy_key, category: row.category, label: row.label, description: row.description || "", status: row.status, enforcement: row.enforcement, severity: row.severity, value: parseJson(row.value_json, {}), appliesTo: parseJson(row.applies_to_json, []), approvalRequired: Boolean(row.approval_required), updatedBy: row.updated_by || "", createdAt: row.created_at, updatedAt: row.updated_at }; } function evaluationPayload(row) { return { id: row.id, organizationId: row.organization_id, workspaceId: row.workspace_id || "", projectId: row.project_id || "", policyKey: row.policy_key, subjectType: row.subject_type, subjectId: row.subject_id || "", result: row.result, reason: row.reason || "", evidence: parseJson(row.evidence_json, {}), createdBy: row.created_by || "", createdAt: row.created_at }; } export function ensureOrganizationPolicies(organizationId) { const organization = dbGet("SELECT id, owner_user_id FROM organizations WHERE id = ?", [organizationId]); if (!organization) throw httpError(404, "organization_not_found", "组织不存在", { organizationId }); const timestamp = now(); for (const definition of ORGANIZATION_POLICY_DEFINITIONS) { dbRun( `INSERT OR IGNORE INTO organization_policies( id, organization_id, policy_key, category, label, description, status, enforcement, severity, value_json, applies_to_json, approval_required, updated_by, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ policyId(organizationId, definition.key), organizationId, definition.key, definition.category, definition.label, definition.description, definition.status, definition.enforcement, definition.severity, JSON.stringify(definition.value || {}), JSON.stringify(definition.appliesTo || []), definition.approvalRequired ? 1 : 0, organization.owner_user_id || null, timestamp, timestamp ] ); } } function allPolicyRows(organizationId) { ensureOrganizationPolicies(organizationId); return dbAll("SELECT * FROM organization_policies WHERE organization_id = ? ORDER BY category, policy_key", [organizationId]); } function policyRow(organizationId, policyKey) { ensureOrganizationPolicies(organizationId); return dbGet("SELECT * FROM organization_policies WHERE organization_id = ? AND policy_key = ?", [organizationId, policyKey]); } function recentEvaluations(organizationId, limit = 40) { return dbAll( `SELECT * FROM organization_policy_evaluations WHERE organization_id = ? ORDER BY created_at DESC LIMIT ?`, [organizationId, Math.max(1, Math.min(120, Number(limit || 40)))] ).map(evaluationPayload); } function categorySummary(policies) { const summary = { total: policies.length, enforced: policies.filter((policy) => policy.status === "enforced").length, monitor: policies.filter((policy) => policy.status === "monitor").length, approval: policies.filter((policy) => policy.enforcement === "approval").length, disabled: policies.filter((policy) => policy.status === "disabled").length, critical: policies.filter((policy) => policy.severity === "critical").length, byCategory: [] }; const categories = [...new Set(policies.map((policy) => policy.category))]; summary.byCategory = categories.map((category) => ({ category, total: policies.filter((policy) => policy.category === category).length, enforced: policies.filter((policy) => policy.category === category && policy.status === "enforced").length })); return summary; } function findEffectivePolicy(policies, key) { const row = policies.find((policy) => policy.policyKey === key); if (row && row.status !== "disabled") return row; const fallback = ORGANIZATION_POLICY_DEFINITIONS.find((definition) => definition.key === key); return fallback ? { policyKey: fallback.key, category: fallback.category, label: fallback.label, status: fallback.status, enforcement: fallback.enforcement, severity: fallback.severity, value: fallback.value || {}, appliesTo: fallback.appliesTo || [], approvalRequired: Boolean(fallback.approvalRequired) } : null; } export function organizationPolicySnapshot(contextOrOrganizationId) { const organizationId = typeof contextOrOrganizationId === "string" ? contextOrOrganizationId : contextOrOrganizationId?.organization?.id; if (!organizationId) throw httpError(400, "organization_required", "组织策略需要组织上下文"); const policies = allPolicyRows(organizationId).map(policyPayload); const localRunner = findEffectivePolicy(policies, "model.local_runner_default"); const externalApproval = findEffectivePolicy(policies, "model.external_connector_approval"); const singleFrame = findEffectivePolicy(policies, "generation.single_frame_contract"); const voice = findEffectivePolicy(policies, "voice.fixed_voice_required"); const lastFrame = findEffectivePolicy(policies, "delivery.actual_last_frame_required"); const rights = findEffectivePolicy(policies, "compliance.rights_evidence_required"); const security = findEffectivePolicy(policies, "security.admin_mfa_required"); const retention = findEffectivePolicy(policies, "retention.production_evidence"); const singleFrameValue = singleFrame?.value || {}; const voiceValue = voice?.value || {}; return { organizationId, generatedAt: now(), policies, model: { defaultLocalOnly: Boolean(localRunner?.value?.defaultLocalOnly ?? true), allowedCostModes: Array.isArray(localRunner?.value?.allowedCostModes) && localRunner.value.allowedCostModes.length ? localRunner.value.allowedCostModes : ["local", "mixed-with-approval"], preferredAdapter: localRunner?.value?.preferredAdapter || "owned-model-platform", comfyuiAdapter: localRunner?.value?.comfyuiAdapter || "optional", externalApprovalRequired: Boolean(externalApproval?.value?.requireApprovalForExternal ?? true), requireSecretRefs: Boolean(externalApproval?.value?.requireSecretRefs ?? true), allowPublicEndpoints: Boolean(externalApproval?.value?.allowPublicEndpoints ?? false), approvedWindowHours: Number(externalApproval?.value?.approvedWindowHours || 24) }, generation: { singleFrameOnly: true, imageOutputCount: 1, batchSize: 1, forbiddenTerms: [...new Set([...(Array.isArray(singleFrameValue.forbiddenTerms) ? singleFrameValue.forbiddenTerms : []), ...DEFAULT_FORBIDDEN_LAYOUT_TERMS])], requiredPhrase: singleFrameValue.requiredPhrase || "one single complete frame" }, voice: { finalVoiceMode: voiceValue.finalVoiceMode || "fixed-reference-required", forbidRandomNativeVoice: true, ttsPreference: voiceValue.ttsPreference || "local-or-owned-first", fallbackMouthPlan: Array.isArray(voiceValue.fallbackMouthPlan) && voiceValue.fallbackMouthPlan.length ? voiceValue.fallbackMouthPlan : DEFAULT_MOUTH_FALLBACK_PLAN }, delivery: { requireActualLastFrameForVideo: Boolean(lastFrame?.value?.requireActualLastFrameForVideo ?? true), blockPlaceholderFrames: Boolean(lastFrame?.value?.blockPlaceholderFrames ?? true), acceptedEvidenceKinds: Array.isArray(lastFrame?.value?.acceptedEvidenceKinds) ? lastFrame.value.acceptedEvidenceKinds : ["last-frame", "media-qa", "delivery-clearance"] }, compliance: { rightsEvidenceRequired: Boolean(rights?.value?.rejectUnknownSource ?? true), requiredForKinds: Array.isArray(rights?.value?.requiredForKinds) ? rights.value.requiredForKinds : ["knowledge", "asset", "voice", "delivery"], acceptedStatuses: Array.isArray(rights?.value?.acceptedStatuses) ? rights.value.acceptedStatuses : ["approved", "original"] }, security: { adminMfaRequired: Boolean(security?.value?.requireForAdmins ?? true), allUsersMfaRequired: Boolean(security?.value?.requireForAll ?? false), methods: Array.isArray(security?.value?.methods) ? security.value.methods : ["totp"] }, retention: { generationContractDays: Number(retention?.value?.generationContractDays || 365), deliveryEvidenceDays: Number(retention?.value?.deliveryEvidenceDays || 730), temporaryMediaDays: Number(retention?.value?.temporaryMediaDays || 30), keepAuditLogs: Boolean(retention?.value?.keepAuditLogs ?? true) } }; } function normalizeObject(value, fallback = {}) { if (value === undefined) return fallback; if (typeof value === "string") { const parsed = parseJson(value, null); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed; throw httpError(400, "policy_json_invalid", "策略 value 必须是合法 JSON 对象"); } if (!value || typeof value !== "object" || Array.isArray(value)) throw httpError(400, "policy_value_invalid", "策略 value 必须是对象"); return value; } function normalizeArray(value, fallback = []) { if (value === undefined) return fallback; if (typeof value === "string") { const parsed = parseJson(value, null); if (Array.isArray(parsed)) return parsed; throw httpError(400, "policy_json_invalid", "策略 appliesTo 必须是合法 JSON 数组"); } if (!Array.isArray(value)) throw httpError(400, "policy_applies_to_invalid", "策略 appliesTo 必须是数组"); return value.map((item) => String(item || "").trim()).filter(Boolean); } function validateHardGate(policyKey, next) { if (policyKey === "generation.single_frame_contract") { if (next.status !== "enforced" || next.enforcement !== "block") { throw httpError(409, "policy_hard_gate_locked", "一图一完整单画面是生产硬门,不能关闭或降级"); } if (next.value.enabled === false || Number(next.value.imageOutputCount) !== 1 || Number(next.value.batchSize) !== 1) { throw httpError(409, "policy_hard_gate_locked", "一图一完整单画面必须保持 imageOutputCount=1 且 batchSize=1"); } } if (policyKey === "voice.fixed_voice_required") { if (next.status === "disabled" || next.enforcement === "off" || next.value.forbidRandomNativeVoice === false) { throw httpError(409, "policy_hard_gate_locked", "固定声线策略不能允许随机原生声音作为最终方案"); } } if (policyKey === "model.external_connector_approval") { if (next.status === "disabled" || next.enforcement === "off" || next.value.requireApprovalForExternal === false) { throw httpError(409, "policy_hard_gate_locked", "外部/付费连接器必须保留显式审批策略"); } } } export function listOrganizationPolicies(context, organizationId, options = {}) { assertOrganizationScope(context, organizationId); requirePolicyRead(context); const policies = allPolicyRows(organizationId).map(policyPayload); return { organizationId, summary: categorySummary(policies), policies, effective: organizationPolicySnapshot(organizationId), evaluations: recentEvaluations(organizationId, options.limit || 40) }; } export function updateOrganizationPolicy(context, organizationId, policyKey, body = {}) { assertOrganizationScope(context, organizationId); requirePolicyManage(context); const current = policyRow(organizationId, policyKey); if (!current) throw httpError(404, "organization_policy_not_found", "组织策略不存在", { policyKey }); const currentPayload = policyPayload(current); const status = String(body.status ?? current.status).trim(); const enforcement = String(body.enforcement ?? current.enforcement).trim(); const severity = String(body.severity ?? current.severity).trim(); if (!["draft", "enforced", "monitor", "disabled"].includes(status)) throw httpError(400, "policy_status_invalid", "策略状态无效"); if (!["block", "approval", "warn", "off"].includes(enforcement)) throw httpError(400, "policy_enforcement_invalid", "策略执行方式无效"); if (!["critical", "high", "medium", "low"].includes(severity)) throw httpError(400, "policy_severity_invalid", "策略级别无效"); const next = { label: String(body.label ?? current.label).trim(), description: String(body.description ?? current.description ?? "").trim(), status, enforcement, severity, value: normalizeObject(body.value ?? body.valueJson, currentPayload.value), appliesTo: normalizeArray(body.appliesTo ?? body.applies_to, currentPayload.appliesTo), approvalRequired: body.approvalRequired === undefined ? Boolean(current.approval_required) : Boolean(body.approvalRequired) }; if (!next.label) throw httpError(400, "policy_label_required", "策略名称不能为空"); validateHardGate(policyKey, next); const timestamp = now(); dbRun( `UPDATE organization_policies SET label = ?, description = ?, status = ?, enforcement = ?, severity = ?, value_json = ?, applies_to_json = ?, approval_required = ?, updated_by = ?, updated_at = ? WHERE organization_id = ? AND policy_key = ?`, [ next.label, next.description, next.status, next.enforcement, next.severity, JSON.stringify(next.value), JSON.stringify(next.appliesTo), next.approvalRequired ? 1 : 0, context.user.id, timestamp, organizationId, policyKey ] ); addAudit({ context, action: "organization.policy.updated", targetType: "organization_policy", targetId: `${organizationId}:${policyKey}`, metadata: { previous: currentPayload, value: next } }); return listOrganizationPolicies(context, organizationId, { limit: 40 }); } function costModeAllowed(costMode, allowedCostModes) { if (allowedCostModes.includes(costMode)) return true; if (costMode === "mixed" && allowedCostModes.includes("mixed-with-approval")) return true; if (costMode === "cloud" && allowedCostModes.includes("cloud-with-approval")) return true; return false; } function endpointIsPublic(endpoint) { if (!endpoint) return false; try { const url = new URL(String(endpoint)); return !privateHost(url.hostname); } catch { return false; } } function checkResult(priority) { if (priority.blockers.length) return "block"; if (priority.approvals.length) return "approval_required"; if (priority.warnings.length) return "warn"; return "pass"; } function addCheck(target, check) { target.checks.push(check); if (check.result === "block") target.blockers.push(check); else if (check.result === "approval_required") target.approvals.push(check); else if (check.result === "warn") target.warnings.push(check); } export function evaluateOrganizationPolicies(context, organizationId, input = {}, options = {}) { assertOrganizationScope(context, organizationId); if (options.requireRead !== false) requirePolicyRead(context); const effective = organizationPolicySnapshot(organizationId); const checks = { checks: [], blockers: [], approvals: [], warnings: [] }; const subjectType = String(input.subjectType || "generation_request"); const subjectId = String(input.subjectId || input.jobId || ""); const promptText = [ input.prompt, input.positivePrompt, input.title, input.shot?.prompt, input.shot?.videoPrompt, input.shot?.action, input.shot?.camera ].filter(Boolean).join(" ").toLowerCase(); const forbiddenHits = effective.generation.forbiddenTerms.filter((term) => promptText.includes(String(term).toLowerCase())); const imageOutputCount = Number(input.imageOutputCount ?? input.image_output_count ?? effective.generation.imageOutputCount); const batchSize = Number(input.batchSize ?? input.batch_size ?? effective.generation.batchSize); if (forbiddenHits.length || imageOutputCount !== 1 || batchSize !== 1) { addCheck(checks, { policyKey: "generation.single_frame_contract", result: "block", severity: "critical", reason: forbiddenHits.length ? `检测到一图多画面风险词:${forbiddenHits.join("、")}` : "图片生成必须保持 imageOutputCount=1 且 batchSize=1", evidence: { forbiddenHits, imageOutputCount, batchSize } }); } else { addCheck(checks, { policyKey: "generation.single_frame_contract", result: "pass", severity: "critical", reason: "单画面输出数量与 prompt 风险通过", evidence: { imageOutputCount: 1, batchSize: 1 } }); } const costMode = String(input.costMode || input.adapter?.costMode || input.adapter?.cost_mode || input.routing?.connectorCostMode || "local").toLowerCase(); const endpoint = input.endpoint || input.adapter?.endpoint || input.adapter?.baseUrl || ""; const hasApprovalGrant = Boolean(input.approvalGrant || input.approvedExternal || input.modelRouteApproval); if (!costModeAllowed(costMode, effective.model.allowedCostModes)) { addCheck(checks, { policyKey: "model.local_runner_default", result: "block", severity: "critical", reason: `当前成本策略 ${costMode} 不在组织允许范围内`, evidence: { costMode, allowedCostModes: effective.model.allowedCostModes } }); } else if (costMode !== "local" && effective.model.externalApprovalRequired && !hasApprovalGrant) { addCheck(checks, { policyKey: "model.external_connector_approval", result: "approval_required", severity: "critical", reason: "外部或混合成本连接器需要显式审批后才能执行", evidence: { costMode, approvedWindowHours: effective.model.approvedWindowHours } }); } else { addCheck(checks, { policyKey: costMode === "local" ? "model.local_runner_default" : "model.external_connector_approval", result: "pass", severity: "critical", reason: costMode === "local" ? "命中本地/自有模型策略" : "外部连接器已有审批授权", evidence: { costMode } }); } if (endpointIsPublic(endpoint) && !effective.model.allowPublicEndpoints && !hasApprovalGrant) { addCheck(checks, { policyKey: "model.external_connector_approval", result: "approval_required", severity: "critical", reason: "公网模型端点需要审批后才能接入", evidence: { endpointPolicy: "public-network" } }); } const randomNativeVoice = Boolean(input.randomNativeVoice || input.voice?.randomNativeVoice || String(input.voiceMode || input.voice?.mode || "").toLowerCase() === "random-native"); if (randomNativeVoice) { addCheck(checks, { policyKey: "voice.fixed_voice_required", result: "block", severity: "critical", reason: "随机原生声音不能作为最终角色声线", evidence: { finalVoiceMode: effective.voice.finalVoiceMode, fallbackMouthPlan: effective.voice.fallbackMouthPlan } }); } else { addCheck(checks, { policyKey: "voice.fixed_voice_required", result: "pass", severity: "critical", reason: "固定声线策略通过", evidence: { finalVoiceMode: effective.voice.finalVoiceMode } }); } const kind = String(input.kind || input.jobKind || "").toLowerCase(); const transitionFromPrevious = input.transitionFromPrevious || input.shot?.transitionFromPrevious; const firstFrame = input.firstFrame || input.shot?.firstFrame || ""; if (effective.delivery.requireActualLastFrameForVideo && (kind.includes("视频") || kind.includes("video") || kind.includes("i2v")) && transitionFromPrevious && transitionFromPrevious !== "episode-start" && (!firstFrame || /pending|auto_previous/i.test(firstFrame))) { addCheck(checks, { policyKey: "delivery.actual_last_frame_required", result: "block", severity: "high", reason: "连续视频镜头缺少上一段实际末帧证据", evidence: { transitionFromPrevious, firstFrame: firstFrame || "missing" } }); } if (input.rightsEvidenceApproved === false && effective.compliance.rightsEvidenceRequired) { addCheck(checks, { policyKey: "compliance.rights_evidence_required", result: "block", severity: "high", reason: "商业生成或交付缺少素材权利证据", evidence: { requiredForKinds: effective.compliance.requiredForKinds } }); } const result = checkResult(checks); const evaluation = { organizationId, workspaceId: context.workspace?.id || "", projectId: context.project?.id || "", subjectType, subjectId, result, checkedAt: now(), effective, checks: checks.checks, blockers: checks.blockers, approvals: checks.approvals, warnings: checks.warnings }; if (options.record) recordPolicyEvaluation(context, evaluation); return evaluation; } export function evaluateGenerationPolicy(context, { body = {}, adapter = {}, routing = null, shot = null, approvalGrant = null } = {}) { return evaluateOrganizationPolicies(context, context.organization.id, { subjectType: "generation_request", subjectId: body.jobId || body.shotId || "", kind: body.kind, prompt: body.prompt, imageOutputCount: body.imageOutputCount, batchSize: body.batchSize, adapter, routing, costMode: routing?.connectorCostMode || adapter?.costMode, endpoint: adapter?.endpoint || adapter?.baseUrl, shot, approvalGrant }, { requireRead: false, record: false }); } export function recordPolicyEvaluation(context, evaluation) { const timestamp = now(); const subjectId = evaluation.subjectId || `${evaluation.subjectType}-${Date.now()}`; const checks = evaluation.checks?.length ? evaluation.checks : [{ policyKey: "organization.policy.bundle", result: evaluation.result || "pass", reason: "组织策略评估", evidence: {} }]; withTransaction(() => { for (const check of checks) { dbRun( `INSERT INTO organization_policy_evaluations( id, organization_id, workspace_id, project_id, policy_key, subject_type, subject_id, result, reason, evidence_json, created_by, created_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ `pol-eval-${Date.now()}-${randomBytes(4).toString("hex")}`, evaluation.organizationId || context.organization.id, evaluation.workspaceId || context.workspace?.id || null, evaluation.projectId || context.project?.id || null, check.policyKey || "organization.policy.bundle", evaluation.subjectType || "generation_request", subjectId, check.result || evaluation.result || "pass", check.reason || "", JSON.stringify(check.evidence || {}), context.user?.id || null, timestamp ] ); } }); addAudit({ context, action: "organization.policy.evaluated", targetType: evaluation.subjectType || "generation_request", targetId: subjectId, result: evaluation.result || "pass", metadata: { result: evaluation.result, blockers: evaluation.blockers?.map((item) => item.policyKey) || [], approvals: evaluation.approvals?.map((item) => item.policyKey) || [] } }); } export function evaluateAndRecordOrganizationPolicies(context, organizationId, body = {}) { assertOrganizationScope(context, organizationId); requirePolicyRead(context); const evaluation = evaluateOrganizationPolicies(context, organizationId, body, { requireRead: false, record: true }); return { organizationId, evaluation, evaluations: recentEvaluations(organizationId, 40) }; }