import ts from 'typescript'; import { readFile, writeFile, access } from 'node:fs/promises'; import { execFileSync } from 'node:child_process'; const catalog = JSON.parse(execFileSync(process.execPath, ['node_modules/tsx/dist/cli.mjs','-e', "import {PROJECT_AGENT_OPERATIONS} from './lib/ai/canvas-agent-runtime/project-operation-catalog';console.log(JSON.stringify(PROJECT_AGENT_OPERATIONS))"], { encoding: 'utf8' })); const productionContracts = JSON.parse(await readFile('lib/ai/canvas-agent-runtime/project-operation-production-contracts.json','utf8')); const contracts = {}; const mismatch = []; const deploymentOnly = []; for (const operation of catalog) { let file; for (const root of ['app/api','app/(chat)/api']) { const path = `${root}/${operation.path.slice('/api/'.length)}/route.ts`; try { await access(path); file = path; break; } catch {} } if (!file) { deploymentOnly.push(operation.id); const contract = productionContracts[operation.id]; if (!contract || !contract.methods.includes(operation.method)) mismatch.push(`${operation.id}: missing verified deployment route`); else contracts[operation.id] = { ...contract, verifiedOnDeployment: true }; continue; } const source = await readFile(file,'utf8'); const methods = [...source.matchAll(/export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)/g)].map(match => match[1]); if (!methods.includes(operation.method)) mismatch.push(`${operation.id}: method not implemented`); const ast = ts.createSourceFile(file,source,ts.ScriptTarget.Latest,true); const schemas = {}, interfaces = {}; function walk(node) { if (ts.isVariableDeclaration(node) && node.initializer && /schema$/i.test(node.name.getText(ast)) && /^z\./.test(node.initializer.getText(ast))) schemas[node.name.getText(ast)] = node.initializer.getText(ast).slice(0,6000); if (ts.isInterfaceDeclaration(node) && /request|payload|body/i.test(node.name.getText(ast))) interfaces[node.name.getText(ast)] = node.getText(ast).slice(0,6000); ts.forEachChild(node,walk); } walk(ast); contracts[operation.id] = { source: file, methods, schemas, interfaces, queryKeys: [...new Set([...source.matchAll(/(?:searchParams|params)\.get\(["']([^"']+)["']\)/g)].map(match=>match[1]))] }; } if (mismatch.length) throw new Error(mismatch.join('\n')); await writeFile('lib/ai/canvas-agent-runtime/project-operation-contracts.json',JSON.stringify(contracts,null,2)+'\n'); console.log(JSON.stringify({ operations: catalog.length, routeContracts: Object.keys(contracts).length, deploymentOnly, unsupportedMethods: mismatch.length })); const canvas = JSON.parse(execFileSync(process.execPath, ['node_modules/tsx/dist/cli.mjs','-e', "import {getCanvasAgentAccessManifest} from './lib/ai/canvas-agent-runtime/capability-access';console.log(JSON.stringify(getCanvasAgentAccessManifest()))"], { encoding: 'utf8' })); const project = catalog.map(item=>({...item, authorized:item.group !== '分镜任务', previouslyExposed:false, execution:'server'})); const utilities = ['getWeather','createDocument','updateDocument','requestSuggestions','loadProjectSkill'].map(id=>({id,title:id,group:'文档、天气与技能',authorized:true,previouslyExposed:false,entry:id,execution:'server'})); const legacyAliases = JSON.parse(execFileSync(process.execPath, ['node_modules/tsx/dist/cli.mjs','-e', "import {LEGACY_AGENT_TOOL_MAP} from './lib/ai/canvas-agent-runtime/legacy-tool-map';console.log(JSON.stringify(LEGACY_AGENT_TOOL_MAP))"], { encoding: 'utf8' })); const legacyNames = [...(await readFile('lib/ai/tools-registry.ts','utf8')).matchAll(/name:\s*"([^"]+)"/g)].map(match=>match[1]); if (legacyNames.some(name=>!legacyAliases[name])) throw new Error('Legacy tool missing from authorization audit'); const report = { legacyAliases, policy:'图片画布及非视频项目业务已授权;视频画布与分镜暂不授权;底层用户权限与服务开关仍生效。管理、密钥、支付、身份认证、系统任务和Claw不属于创作Agent工具。', upstreamCommit:'0a1fc56d279fd1001f78387bd81aec1c26767411', rows:[...canvas,...project,...utilities], verifiedRoutes:Object.keys(contracts).length, deploymentOnly }; await writeFile('docs/specs/zcode-canvas/authorization-audit.json',JSON.stringify(report,null,2)+'\n'); const esc=value=>String(value??'').replace(/[&<>"']/g,char=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char])); const rows=report.rows.map(row=>`${esc(row.workspace||row.group||'项目')}${esc(row.title)}${row.authorized?'已授权':'暂未授权'}${esc(row.entry||row.id)}${esc(row.note||row.hint||'')}`).join(''); await writeFile('docs/specs/zcode-canvas/authorization-audit.html',`ZCode Agent 工具授权清单

ZCode Agent 工具授权清单

${esc(report.policy)}

共 ${report.rows.length} 项授权检查(包含界面入口和接口映射);每一项均保留实际执行方式与限制。接口鉴权不等于业务结果已完成。

${rows}
范围能力授权工具 / 操作编号执行限制
`); console.log(JSON.stringify({auditRows:report.rows.length, authorized:report.rows.filter(row=>row.authorized).length, paused:report.rows.filter(row=>!row.authorized).length, addedProjectOperations:project.filter(row=>row.authorized).length}));