/** * Page Data review / correction rollout (审查纠错影响分析): * * - Tier A (this module): map failure codes → structured correction suggestions. * Does not change Agent Run success/failure semantics by itself. * - Tier B: MEMIND_ORCHESTRATOR_PAGE_DATA_VALIDATION_GATE_ENABLED * Ops fail-closed switch; local/dev default stays observe-only (0). * - Tier C (minimal): remediable suggestion codes can be turned into a single * goosed repair prompt (finish-guard / agent-run one-shot). No unbounded loops. */ const HTML_ISSUE_TO_CODE = Object.freeze({ forbidden_local_storage: 'FORBIDDEN_LOCAL_STORAGE', forbidden_local_storage_fallback: 'FORBIDDEN_LOCAL_STORAGE', forbidden_browser_storage: 'FORBIDDEN_BROWSER_STORAGE', forbidden_legacy_page_data_api: 'FORBIDDEN_LEGACY_PAGE_DATA_API', forbidden_legacy_owner_api: 'FORBIDDEN_LEGACY_PAGE_DATA_API', }); /** Codes goosed can reasonably fix in one repair turn. */ const REMEDIABLE_CODES = new Set([ 'PAGE_DATA_DELIVERABLE_MISSING', 'PAGE_DATA_DELIVERY_FAILED', 'DELIVERABLE_DATA_STORAGE_FORBIDDEN', 'FORBIDDEN_LOCAL_STORAGE', 'FORBIDDEN_BROWSER_STORAGE', 'FORBIDDEN_LEGACY_PAGE_DATA_API', 'COLUMNS_NOT_ALLOWED', 'TOOL_GATEWAY_VALIDATION_FAILED', 'PAGE_DATA_AIDER_FINISH_UNAVAILABLE', ]); const SUGGESTION_BY_CODE = Object.freeze({ PAGE_DATA_DELIVERABLE_MISSING: Object.freeze({ code: 'PAGE_DATA_DELIVERABLE_MISSING', title: '缺少可交付的公开页面', actions: Object.freeze([ '确认已用 page-data-collect 写出 public/*-survey.html 与(如需要)public/*-admin.html', '检查页面是否落在 workspace 的 public/ 目录,而不是仅聊天草稿', '重新说明需求并要求「完整创建并发布」', ]), }), PAGE_DATA_DELIVERY_FAILED: Object.freeze({ code: 'PAGE_DATA_DELIVERY_FAILED', title: '页面数据绑定或发布失败', actions: Object.freeze([ '确认 dataset 已 private_data_register_dataset 注册', '对每个公开页调用 private_data_bind_workspace_page(含 password 策略时口令 ≥8 位)', '检查 .mindspace/page-data-policies 是否生成且列白名单与表单字段一致', ]), }), DELIVERABLE_DATA_STORAGE_FORBIDDEN: Object.freeze({ code: 'DELIVERABLE_DATA_STORAGE_FORBIDDEN', title: '页面使用了禁止的数据存储方式', actions: Object.freeze([ '删除 localStorage / sessionStorage / IndexedDB 读写', '改用 /assets/page-data-client.js 与 Page Data 公开 API', '禁止回退到 /api/page-data 旧 Owner API', ]), }), FORBIDDEN_LOCAL_STORAGE: Object.freeze({ code: 'FORBIDDEN_LOCAL_STORAGE', title: '检测到 localStorage 回退', actions: Object.freeze([ '移除 localStorage.setItem / getItem', '报名/问卷提交改为 MindSpacePageData.createClient insert', '后台列表改为 Page Data read(口令页先 authenticate)', ]), }), FORBIDDEN_BROWSER_STORAGE: Object.freeze({ code: 'FORBIDDEN_BROWSER_STORAGE', title: '检测到浏览器本地存储', actions: Object.freeze([ '移除 sessionStorage / IndexedDB 等浏览器存储', '结构化数据只走 Page Data API', ]), }), FORBIDDEN_LEGACY_PAGE_DATA_API: Object.freeze({ code: 'FORBIDDEN_LEGACY_PAGE_DATA_API', title: '使用了已移除的 /api/page-data', actions: Object.freeze([ '改用 /api/public/pages/:pageId/data/... 公开 API', '通过 page-data-client.js,不要直连已删除的 Owner API', ]), }), COLUMNS_NOT_ALLOWED: Object.freeze({ code: 'COLUMNS_NOT_ALLOWED', title: '字段不在策略白名单中', actions: Object.freeze([ '对齐 HTML 表单 name 与 dataset 列名', '更新 policy 的 insert/read 白名单后重新 bind', '必要时先 ALTER 表并重新 register_dataset', ]), }), TOOL_GATEWAY_VALIDATION_FAILED: Object.freeze({ code: 'TOOL_GATEWAY_VALIDATION_FAILED', title: '代码任务校验未通过', actions: Object.freeze([ '根据工具网关校验输出修 HTML / 策略不一致项', '简单 Page Data 修复可走 code run + aider;复杂改动再升级执行器', ]), }), REQUIRED_REVIEW_EXECUTOR_MISMATCH: Object.freeze({ code: 'REQUIRED_REVIEW_EXECUTOR_MISMATCH', title: '独立审查执行器不匹配', actions: Object.freeze([ '确认 run 要求的 reviewExecutor 与实际执行器一致', '在能力与 executor binding 中启用对应审查执行器', ]), }), REQUIRED_EXECUTOR_UNAVAILABLE: Object.freeze({ code: 'REQUIRED_EXECUTOR_UNAVAILABLE', title: '所需执行器不可用', actions: Object.freeze([ '检查 aider / openhands / goose 绑定与 API Key', '确认 memindadm 执行器绑定已启用且模型可用', ]), }), PAGE_DATA_AIDER_FINISH_UNAVAILABLE: Object.freeze({ code: 'PAGE_DATA_AIDER_FINISH_UNAVAILABLE', title: 'Aider Page Data 收尾不可用', actions: Object.freeze([ '改回 Goose + page-data-collect 完成 bind/发布', '或检查 code run / Aider 配置后重试', ]), }), WORKFLOW_VALIDATION_GATE_UNAVAILABLE: Object.freeze({ code: 'WORKFLOW_VALIDATION_GATE_UNAVAILABLE', title: 'Page Data 审查服务不可用', actions: Object.freeze([ '确认 Orchestrator(8093)健康且 Shadow wiring 已开', '本地开发可将 MEMIND_ORCHESTRATOR_PAGE_DATA_VALIDATION_GATE_ENABLED=0 改为仅观察', '生产强制闸门需 Ops 批准;故障时应先恢复 Orchestrator 再重试交付', ]), }), WORKFLOW_VALIDATION_GATE_REJECTED: Object.freeze({ code: 'WORKFLOW_VALIDATION_GATE_REJECTED', title: 'Page Data 审查未通过', actions: Object.freeze([ '根据 Native 守卫与失败 check codes 修复页面/策略', '修复后重新发起交付,不要绕过审查闸门', ]), }), WORKFLOW_VALIDATION_GATE_INCONCLUSIVE: Object.freeze({ code: 'WORKFLOW_VALIDATION_GATE_INCONCLUSIVE', title: 'Page Data 审查结果不明确', actions: Object.freeze([ '确认必选 check(completion / binding / storage_policy)均已实际执行而非 skipped', '补齐 syncUserPagesOnSuccess 与 validateRunDeliverables 后再交付', '若仅本地调试,可临时关闭强制闸门改为观察模式', ]), }), }); function normalizeCode(value) { return String(value ?? '') .trim() .toUpperCase() .replace(/[^A-Z0-9]+/g, '_'); } export function listPageDataValidationSuggestionCodes() { return Object.keys(SUGGESTION_BY_CODE); } export function resolvePageDataValidationSuggestions(codeOrCodes) { const values = Array.isArray(codeOrCodes) ? codeOrCodes : [codeOrCodes]; const seen = new Set(); const suggestions = []; for (const raw of values) { const code = normalizeCode(raw); if (!code || seen.has(code)) continue; const item = SUGGESTION_BY_CODE[code]; if (!item) continue; seen.add(code); suggestions.push({ code: item.code, title: item.title, actions: [...item.actions], }); } return suggestions; } export function formatPageDataValidationUserMessage({ code = null, codes = null, message = '', } = {}) { const base = String(message ?? '').trim(); const resolvedCodes = [ ...(Array.isArray(codes) ? codes : []), ...(code ? [code] : []), ]; const suggestions = resolvePageDataValidationSuggestions(resolvedCodes); if (!suggestions.length) return base; const blocks = suggestions.map((item) => { const lines = item.actions.map((action) => `- ${action}`); return `建议(${item.title}):\n${lines.join('\n')}`; }); if (!base) return blocks.join('\n\n'); return `${base}\n\n${blocks.join('\n\n')}`; } export function suggestionCodesFromPageDataHtmlIssues(htmlIssues = []) { const codes = []; for (const item of Array.isArray(htmlIssues) ? htmlIssues : []) { const issue = String(item?.issue ?? item ?? '').trim().toLowerCase(); const mapped = HTML_ISSUE_TO_CODE[issue]; if (mapped) codes.push(mapped); } return codes; } export function isPageDataSuggestionRemediable(codeOrCodes) { const values = Array.isArray(codeOrCodes) ? codeOrCodes : [codeOrCodes]; return values.some((value) => REMEDIABLE_CODES.has(normalizeCode(value))); } /** * Build a goosed-facing repair prompt from Tier A suggestions. * Intended for one-shot finish-guard / agent-run repair, not unbounded retries. */ export function buildPageDataValidationRepairPrompt({ code = null, codes = null, htmlIssues = [], unboundFiles = [], message = '', } = {}) { const resolvedCodes = [ ...(Array.isArray(codes) ? codes : []), ...(code ? [code] : []), ...suggestionCodesFromPageDataHtmlIssues(htmlIssues), ]; const suggestions = resolvePageDataValidationSuggestions(resolvedCodes); const lines = [ '【系统纠正建议 → 请立即修复】Page Data 交付未通过。请按 page-data-collect 技能执行下列纠正建议,完成后不要声称已发布,除非 bind 与公开页已就绪:', '1. load_skill → page-data-collect', '2. 确保 public/*.html 使用 /assets/page-data-client.js 与 MindSpacePageData.createClient({ apiBase: "/api" })', '3. 禁止 localStorage / sessionStorage / IndexedDB 与 /api/page-data 旁路', '4. 对每个 public/*.html 调用 private_data_bind_workspace_page', ]; if (String(message ?? '').trim()) { lines.push('', `失败摘要:${String(message).trim().slice(0, 500)}`); } if (suggestions.length) { lines.push('', '纠正建议:'); for (const item of suggestions) { lines.push(`- ${item.title}(${item.code})`); for (const action of item.actions) { lines.push(` · ${action}`); } } } if (Array.isArray(htmlIssues) && htmlIssues.length) { lines.push('', 'HTML 问题:'); for (const item of htmlIssues) { lines.push(`- ${item.relativePath ?? '?'}: ${item.issue ?? item}`); } } if (Array.isArray(unboundFiles) && unboundFiles.length) { lines.push('', '尚未 bind 的页面:'); for (const file of unboundFiles) { lines.push(`- ${file.relativePath ?? file}`); } } lines.push('', '修复完成前不要告诉用户“已发布/已可提交”。'); return lines.join('\n'); } export const pageDataValidationSuggestionsInternals = { SUGGESTION_BY_CODE, HTML_ISSUE_TO_CODE, REMEDIABLE_CODES, normalizeCode, };