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 contracts = {}; const mismatch = []; 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) { if (operation.id !== 'smart-edit.get') mismatch.push(`${operation.id}: missing route`); 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: 'smart-edit.get', 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 report = { policy:'图片画布及非视频项目业务已授权;视频画布与分镜暂不授权;底层用户权限与服务开关仍生效。管理、密钥、支付、身份认证、系统任务和Claw不属于创作Agent工具。', upstreamCommit:'0a1fc56d279fd1001f78387bd81aec1c26767411', rows:[...canvas,...project,...utilities], verifiedRoutes:Object.keys(contracts).length, deploymentOnly:['smart-edit.get'] }; 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(report.policy)}
共 ${report.rows.length} 项能力;每一项均保留实际执行方式与限制。接口鉴权不等于业务结果已完成。
| 范围 | 能力 | 授权 | 工具 / 操作编号 | 执行限制 |
|---|