feat(v2): 实现SQLite对公租赁经验加工与卡片预审轨迹闭环

This commit is contained in:
2026-10-01 13:38:03 +08:00
parent 9c351b08d3
commit fa4163b2d0
60 changed files with 2111 additions and 59 deletions
+13
View File
@@ -0,0 +1,13 @@
# Superdesign 本轮拟发送的设计说明(可审阅文本)
此文件是准备发送给 Superdesign 的全部新增内容;只含产品概念与虚构示例,不含业务源码、密钥、数据库记录、真实客户资料或原始内部文档。另有 1 份既有概念视觉文件 `.superdesign/rebuild-concept-design-system.md`。
沿用已有兴邦金租原型的深绿左侧导航与浅色工作区,保留概率环、动态证据雷达、证据抽屉和沙盘。只保留客户经理、配置管理员两个角色。
历史经验加工:提供不少于200条金融租赁公司对公租赁模拟案例,禁止使用个人信贷、消费金融、普通企业贷款或通用破产数据冒充。案例覆盖承租企业、实物租赁物、直租/售后回租、金额、期限、租金、增信、问询、补件和模拟会议结果。顶部自然语言筛选,展示结构化条件、命中数、勾选案例与全部筛选结果;两颗独立按钮「提炼 Skills」「提炼 Rules」。Skill 描述核查方法、材料、适用范围;Rule 描述触发条件、例外、建议动作。成功后进入对应卡片池:红色边框、淡红背景和「AI 提炼」文字;人工卡蓝色与「人工创建」文字;来源与生效状态分别展示。客户经理提议,配置管理员管理生效,卡片不自动执行。
项目预审:装配资料文件夹、数据卡片、Skills卡片、Rules卡片及自然语言说明,不开放Tools卡。结果首屏突出意见、演示概率、概率出处、动态证据雷达、关键问题和下一步。miniOpenClaw按选定卡片与只读工具边界编排。概率为人工演示表,42/58/51/64随沙盘完整选择即时变化;证据阶段不能换算成概率,原结果不可修改。
轨迹复盘:三个业务视图「看判断过程」「比较两次预审」「核对会议结果」。可点击节点从材料→核查→规则适用→意见→补件→复审→会议结果,每节点可看为何判断与所用依据。比较视图展示新增材料、解决/新增/未解决的问题、概率与组件版本变化;多种配置变化不得只归因于补件。会议结果记录通过/未通过/待定/未送审;偏差可转为Skill/Rule改进草稿,回到卡片池,不能自动发布或虚构准确率。
这是界面交互原型,持续标「全部为模拟,未连接真实后台;概率为演示值」。不得把原型完成写成业务功能已完成。
+78
View File
@@ -0,0 +1,78 @@
# 三模块新版原型与历史案例筛选
日期:2026-10-01。状态:**交互原型交付;生产业务实现、真实数据库接入和浏览器验收未完成**。
本轮由用户要求重建 Superdesign 项目并生成原型,随后追加「展示历史数据、增加筛选按钮、支持 SQL 筛选」。沿用 [三模块经验闭环](14-experience-loop.md)、[视觉契约](DESIGN.md) 和 [原型概率边界](13-probability-prototype.md)。本文件不变更旧业务 API,不作为生产实施阶段门禁通过的证据。
## 可审阅产物
- [Superdesign 可点击预览](https://p.superdesign.dev/draft/c58e9d1b-4813-44af-9b1a-344a47eda928),草稿 `c58e9d1b-4813-44af-9b1a-344a47eda928`,当前版本 **3**(新增Skills文件夹操作见[增量说明](20-skill-folder-prototype.md))。
- [新项目画布](https://superdesign.dev/teams/5ca13ca1-19e1-4adb-9066-bf395d561075/projects/7e801260-0fc6-4a34-8db8-14a4ef205fcb)。旧账号的项目、草稿与恢复记录保留,不覆盖。
- [本地 HTML](prototypes/xingbang-v2-rebuild.html) 与 [逻辑检查](prototypes/check-rebuild.cjs)。字体使用系统中文字体;Tailwind 与图标来自公开 CDN,首次打开需网络。
版本1由 Superdesign Gemini 3.1 Pro 生成,只有历史经验视觉页及静态导航。版本2依据已经确认的概念补齐三模块与共享演示状态,精确导入同一草稿,不再消耗生成额度。云端回读与本地 HTML 在换行归一化后相同。
## 使用方式
1. **历史经验加工**:查看240条虚构金融租赁公司对公案例,点击企业查看租赁物、金额、期限、租金、增信、问询、补件与模拟会议结果。每页10条。
2. 使用行业 / 租赁方式 / 会议结果快捷条件;或输入自然语言,先解析再确认应用。未知或歧义条件提示错误,不自动改为全部案例。
3. 展开 **SQL 筛选**,查看表字段、写查询、预览解析条件及命中数量,再确认应用。可勾选本页案例或选择全部筛选结果;SQL 的 LIMIT 属于筛选结果边界。
4. 点击独立的「提炼 Skills」「提炼 Rules」按钮,生成红色「AI 提炼」演示草稿进入对应池。人工创建是蓝色「人工创建」草稿。**这里未调用大模型,固定模板只用于原型,不能作为真实提炼验收。**
5. 切换配置管理员,在草稿详情中确认演示生效;客户经理没有该操作。进入项目预审选择已生效卡片。演示角色切换不是后端鉴权。
6. **项目预审**:装配资料文件夹示意、数据卡、Skills、Rules与自然语言说明;没有 Tools 配置卡。选择卡片或4轴/6轴方案,雷达随维度变化;不足3轴显示列表。证据按钮可打开材料→Skill→只读工具→Rule→建议的依据抽屉。
7. 勾选沙盘的租金材料、权属材料,立即得到42/58/51/64及百分点变化。数字来自完整人工情景表,原始42基线固定;雷达阶段和面积不计算概率。
8. 保存情景为复审演示,进入 **轨迹复盘**:「看判断过程」「比较两次预审」「核对会议结果」。录入模拟会议结果,已观察到的结果可转为Skill/Rule改进草稿回到卡片池;待定 / 未送审不是未通过,也不会产生可发布结论。
所有编辑和记录仅保留本页内存,刷新恢复初始状态。初始包含两次虚构预审快照;轨迹不是真实 Agent trajectory。
## 新增规则(生产要求与原型展示分开)
- **BR-52** 历史案例域限定为金融租赁公司的对公租赁业务,至少200条明确标识的模拟案例;个人信贷、消费金融、普通企业贷款或破产数据不得冒充租赁历史。本原型240条仅在内存;生产数据入库仍待实现。
- **BR-53** 案例明细、快捷筛选、自然语言筛选与SQL筛选共享一个已确认的结果集;条件变化清空旧选择。显示真实命中数及选中数,明确行选择和全部结果选择。
- **BR-54** SQL只允许授权域内的只读查询。正式版须在后端执行字段/表白名单、只读权限、行范围和数量限制、查询预算与审计;前端校验不能代替后端控制。本原型仅对模拟数组进行受限语法解析,不执行数据库SQL。
- **BR-55** 原型不能冒充模型提炼、数据库导入、Agent核查、正式审批或效果验证。草稿来源、审核生效和风险状态分别表示;改进草稿不自动发布。
新增异常,沿用已有E编号并追加:
| 编号 | 当前原型错误码 | 用户文案 / 行为 |
|---|---|---|
| E-29 | FILTER_UNSUPPORTED | 暂不能解析该条件;提示使用示例,原筛选保持不变 |
| E-30 | FILTER_AMBIGUOUS | 同一字段存在多个条件,请明确选择一个;原筛选保持不变 |
| E-31 | SQL_READONLY_UNSUPPORTED | 不支持该SQL、未授权字段或错误类型;明确支持范围,原筛选保持不变 |
上述为UI演示错误码,尚未成为生产API契约。提炼为空时禁用按钮;权限依已有客户经理 / 管理员边界展示,不声称真正鉴权。
## 验收映射(本轮只检验演示逻辑)
| 编号 | Given / When / Then | 原型检查与限制 |
|---|---|---|
| AC-58 | Given 模拟库,When 查看与翻页,Then 有不少于200条独立对公租赁案例及对应字段 | 240条生成与去重、字段域检查;数据库接入未完成 |
| AC-59 | Given 当前结果集,When 解析筛选,Then 先预览条件,确认后应用;未知条件不改变原筛选 | 自然语言与SQL确认、真实计数、未知条件反例 |
| AC-60 | Given SQL输入,When 解析,Then 仅授权只读子集可通过,写入、多语句、未知字段及不匹配常量拒绝 | 受限解析测试;后端数据库安全机制未实现 |
| AC-61 | Given 选中案例,When 提炼演示或人工创建,Then 对应来源草稿进入卡片池,客户经理不能生效 | 提炼来源、草稿与角色逻辑;真实LLM和后台鉴权未实现 |
| AC-62 | Given 已选卡片和四种情景,When 改卡片 / 勾选补件,Then 维度动态变化、概率按完整情景表变化,原快照不变 | 4/6轴与小于3轴列表、四组合、冻结快照;真实概率未校准 |
| AC-63 | Given 模拟会议结果,When 记录与改进,Then 待定/未送审不当作否决,改进回池仍为草稿 | 结果可观测性与草稿回流;真实会议/发布链未接入 |
本轮AC仅有逻辑检查证据,不能标为生产验收通过。继承旧原型P-01至P-17的适用边界,不覆盖旧原型文件。
## SQL 原型支持范围
表:`lease_cases`。查询投影仅支持 `SELECT *`,支持 `WHERE` 常量比较、`AND`、一个 `ORDER BY` 字段与 `LIMIT 1–240`。文本字段支持 `=`、`!=`,数值字段支持六种比较。字段说明通过界面「查看表字段」展示。
```sql
SELECT * FROM lease_cases
WHERE industry = '制造业' AND lease_type = '售后回租'
ORDER BY amount_wan DESC LIMIT 20;
```
SQL本轮新增要求来自用户在原型生成后的追加指令。正式后端查询的详细契约、授权、性能预算、索引和数据库引擎仍需后续设计;不能把此内存解析器迁入生产后当作任意SQL安全执行器。
## 验证与交付边界
- 15组逻辑检查通过:SQL反例、筛选确认、选择数量、来源/权限、动态轴、四情景、快照隔离、会议标签与草稿回流。使用Node VM与DOM stub,**不是浏览器E2E或视觉验收**。
- 使用项目backend/venv执行 `scripts/verify.py`,9项检查全部通过(后端、既有UI守卫、前端构建、两份Compose与diff);日志:`test-evidence/20261001-113911-7080729/REPORT.md`。这些是原仓基线检查,不代表生产新版通过。文档最后修改后的diff检查另行复验。
- 内置浏览器自动化连接失败,原因是本机执行环境的 `windows sandbox ... helper_unknown_error`。未借其他浏览器绕过;预览已打开供用户审阅。
- 证据目录:`test-evidence/20261001-rebuild-prototype/`;包含逻辑日志、云端回读内容哈希和交付绑定。独立视觉评审未完成。
- 未修改 `src/`、`backend/` 业务模块,未部署镜像、未合并主线、未执行Gitea推送。本轮本地文件和Superdesign设计服务副本不等于Gitea备份。
Superdesign外发最初两次被自动审批拒绝。用户随后明确回复「允许」,生成时仅发送两份概念说明;没有上传现有前后端源码、数据库、密钥或真实客户材料。SQL交互后按用户追加要求导入纯概念原型HTML。
+57
View File
@@ -0,0 +1,57 @@
# Skills 文件夹导入与标准文件视图(原型增量)
2026-10-01。范围:当前Superdesign三模块原型,不修改生产前后端。继承[上一轮原型](19-rebuild-prototype.md)的模拟、权限与概率边界,走原型增量轻量路径;生产文件系统服务仍待契约与授权设计。
## 需求与验收
- **BR-56** Skill卡片是技能包的索引。每个技能包以目录内的 `SKILL.md` 为入口;YAML frontmatter包含 `name` / `description`,正文是Markdown。可附带references/scripts/assets,但不创建空占位目录。
- **BR-57** 支持选择一个技能目录或包含多个技能目录的父目录;先发现与校验,再选择确认导入。缺失入口、错误frontmatter、名称与目录不匹配、重复技能名均明确提示,不静默覆盖。
- **BR-58** 人工文件夹导入保留蓝色人工来源及「文件夹导入」子类型;AI提炼保留红色来源。进入卡片池默认待审核草稿,不自动执行脚本或提升工具权限。
- **BR-59** 每张Skill卡均可打开技能文件视图,展示相对目录树与原始 `SKILL.md`;导入文件保留原文,不将摘要冒充原文件。AI与人工生成的演示Skill也有标准文件包。
- **BR-60** 网页文件视图、保存/导出文件包和打开系统资源管理器分别标注能力。原型未接本地文件管理服务,不伪称已打开Windows目录;正式版通过授权本地适配器定位受管目录,不接受任意路径作为终端命令。
| 异常 | 原型错误码 | 文案与行为 |
|---|---|---|
| E-32 | SKILL_PACKAGE_INVALID | 找不到SKILL.md或格式不符合要求;列出具体原因,禁止导入该包 |
| E-33 | SKILL_NAME_DUPLICATE | 技能名已存在,需另行管理版本;不覆盖原卡片 |
| E-34 | SKILL_FOLDER_READ_FAILED | 无法读取所选文件夹或超过限制;保留原卡片 |
| E-35 | SKILL_SYSTEM_FOLDER_UNAVAILABLE | 网页原型未接本地文件管理器;使用文件视图或导出技能包 |
| 验收 | Given / When / Then |
|---|---|
| AC-64 | Given 一个有效Skill目录,When 选择文件夹,Then 展示名称、描述、相对树与校验结果,确认前不新增卡片 |
| AC-65 | Given 多包父目录含无效/重复包,When 预览并确认,Then 仅导入用户勾选的有效包,其他包显示原因且不覆盖 |
| AC-66 | Given 文件夹导入,When 入池,Then 人工来源蓝色、文件夹来源文本、待审核草稿;没有执行脚本或模型调用 |
| AC-67 | Given 任一Skill卡,When 打开技能文件夹,Then 有SKILL.md原文与文件树;导入原文不被改写,已有包可导出 |
| AC-68 | Given 已导出的演示包,When 解压并验证,Then 目录名匹配frontmatter.name,name/description有效,引用文件实际存在 |
| AC-69 | Given 网页原型,When 请求打开系统目录,Then 显示能力边界,不宣称已打开电脑目录;取消选择不改变原集合 |
## 详细设计与UI
新增Skill卡片字段:`skillPackage = { name, description, directory, files:[{path, text?, browserFile?}], imported }`。这是原型内存结构,不是生产API。来源字段、状态及执行白名单保持独立。
卡片池Skills页签新增「从文件夹添加」。流程:未选择 → 浏览器选择目录 → 读取/校验 → 有效/部分无效/空/错误 → 勾选预览 → 确认导入 → 草稿入池。取消操作不改变已有数据,解析时禁用重复导入。
打开技能文件夹:宽屏双栏(左目录树,右原文件),窄屏上下排列。支持逐文件查看、返回卡片详情、导出技能包;文本用转义的纯文本显示,不运行HTML、脚本或Markdown内指令。网页不获取绝对路径;来源只记录浏览器提供的相对目录。
浏览器选择目录使用 `webkitdirectory` + `webkitRelativePath`。最多500个文件、20MiB合计、入口1MiB;脚本只预览,不执行。YAML用受限schema解析,不把frontmatter权限字段作为平台授权。样例均为自编对公租赁演示内容。
标准依据:[Agent Skills规范](https://agentskills.io/specification);目录选择依据:[MDN相对目录路径](https://developer.mozilla.org/en-US/docs/Web/API/File/webkitRelativePath)。格式通过不代表Skill业务正确、安全审核完成或已投入运行。
## 交付状态
此增量制作标准示例目录、可点击原型及逻辑检查。数据库存储、真实知识发布、miniOpenClaw运行加载与Windows文件管理器集成不在原型中完成;本次未改生产服务。
## 已交付与验证证据
- [交互预览](https://p.superdesign.dev/draft/c58e9d1b-4813-44af-9b1a-344a47eda928)同一草稿第3版;云端回读与本地HTML一致。第2版及恢复副本保留。
- 路径:历史经验加工 → 经验卡片池 → Skills → 从文件夹添加 → 选择目录 → 预览 → 确认导入。点击任一Skill的「打开技能文件夹」可查看原始文件。
- [可直接导入的示例SKILL.md](prototypes/skill-import-example/xingbang-material-completeness/SKILL.md):选择 `docs/v2/prototypes/skill-import-example` 父目录或其中的单个Skill目录,即可预览并添加一个新技能。另有[基线入口文件](prototypes/skill-examples/xingbang-sk-01/SKILL.md)及同级4个完整包,其名称已在基线卡片池中,重复导入会明确拒绝覆盖。
- [文件夹检查](prototypes/check-skill-folders.cjs)包含30组逻辑检查(15组既有回归与15组增量);覆盖原文、YAML、多包/重复、确认前不入池、草稿、取消读取与ZIP实际字节。Node VM与文件列表fixture,不是系统文件选择器或浏览器E2E。
- 4个基线示例目录及1个新增导入示例通过skill-creator提供的格式校验;ZIP用Python标准库解包、CRC与逐文件字节比较通过,导入样例CRLF原文保留。
- 按项目虚拟环境运行统一验证,9项全部通过:`test-evidence/20261001-120815-7080729/REPORT.md`。统一检查证明原仓基线,不证明新版生产功能实现。
- 完整增量证据:`test-evidence/20261001-skill-folder-prototype/`。浏览器自动验收和独立视觉审核仍未完成;本机浏览器连接此前因执行环境失败,未据逻辑测试推断UI验收通过。
- YAML解析器使用仓库现成js-yaml 4.3.2,连同MIT许可内嵌原型,目录导入无需再加载解析器CDN;它是格式解析器,不代表技能安全审核器。
网页「在系统文件夹打开」目前提示 E-35,不会执行资源管理器命令。可用「导出标准技能包」下载ZIP、解压后打开目录;标准目录与源文件均已提供。真实本地文件管理器能力须在后续生产实施阶段接入。
+60
View File
@@ -0,0 +1,60 @@
# SQLite 工作台实施基线
日期:2026-10-01。需求来源:用户确认 Superdesign 重建原型,并授权「直接开发,数据库用 SQLite」。恢复点:9c351b0;开发分支 codex/sqlite-experience-workbench。本记录是新增实施切片,不把旧草案当成真实部署或概率验证。
## 1. 范围与术语
三模块沿用现有独立路由与 miniOpenClaw 受控执行链。第一期使用 240 条虚构金融租赁公司对公案例;承租人是法人,标的是设备、工程机械、医疗设备、能源设备、物流车辆,不混入个人贷款。正式通过是评审会结果。案例库是经验来源;已发布卡片是运行配置;草稿不执行;概率演示是人工情景表,真实概率仍未校准。评审员退出新版登录与配置入口,独立审核由另一位配置管理员承担;历史评审身份保留为审计事实。
## 2. 数据与接口契约
数据库复用 backend/xingbang_data/project_assistant.sqlite3。新增幂等迁移 lease_library_v1:lease_cases(id TEXT PRIMARY KEY, lessee TEXT, industry TEXT, mode TEXT, asset TEXT, amount REAL 万元, term INTEGER 月, outcome TEXT, year INTEGER, guarantee TEXT, issue TEXT, supplements TEXT, meeting_reason TEXT, data_domain TEXT固定demo)。所有字段非空。模拟来源与生成版本固定保留;交易金额是生成值,不用于真实财务计算。
API 前缀 /api/xingbang/risk-agent/project-assistant/library:GET /cases 返回案例和 count、total、schema、模拟说明;POST /queries {mode:filters|natural|sql,filters?:object,text?:string}返回preview_id、实际结果items、count、规范化条件;POST /selections {preview_id,selected_ids?:string[]}保存不可变确认selection;POST /extractions {project_id,selection_id,kind:skill|rule_set,requirement:string}真实调用既有模型,返回status、candidate_id|null、degradations;GET /cards?project_id 返回现有注册表当前草稿/发布索引。POST /skills/import {project_id,files:[{path,content_base64}]},返回注册表草稿;GET /skills/{draft_id}/package 返回清单和标准文件文本;GET /skills/{draft_id}/download返回ZIP;POST /skills/{draft_id}/open仅允许本机预览打开托管包;GET /cards/{draft_id}获取治理体与示例测试;发布/评测沿用既有prescreen transitions。权限、项目范围、域每次验证。历史库仅启用演示身份且demo开关开启;真实身份不能从模拟提炼出production卡片。
SQL仅一条SELECT、仅lease_cases、无PRAGMA/ATTACH/写入/系统表;authorizer与进度预算兜底,上限240行。查询确认由服务器保留完整匹配ID与主体/时间,30分钟过期;提炼只能取确认后的非空集合。自然语言先转成已知枚举/金额条件,未知要求报错,不静默扩大。
Skills磁盘根 backend/xingbang_data/skill_packages/{UUID},数据库保存版本哈希、文件清单与来源;导入保留完整标准SKILL.md及可选references/scripts/assets。路径无绝对、穿越、链接;≤100文件、单文件≤2MiB、总计≤10MiB;SKILL.md UTF-8、合法YAML且name/description非空,name与所选目录名一致。脚本只保存不运行;模型Skill的工具集合由服务端固定,导入不能扩大白名单。
## 3. 表面规格与状态
历史页:深绿导航+浅色工作区,上方数据库指标,中间筛选/可见表格/详情,右侧单独提炼Skills/Rules与自然语言要求,下方红色AI/蓝色人工卡片池及文件树。保留旧项目过程加工为折叠高级入口。客户经理只读案例、使用已发布卡片;配置管理员筛选、确认、提炼、人工导入及评测,发布须独立身份与当前哈希评测。
预审:项目上下文+五种输入+已发布方案冻结+既有执行结果、动态雷达、概率环与完整情景变化依据。轨迹:看判断过程/比较两次预审/核对会议结果,展示实际步骤与版本差异。
状态:idle→loading→ready|empty|error;ready→submitting→ready|error。提交中保留旧内容并禁用重复操作。筛选或项目变化清空确认/选择;过期重新预览。请求晚到不覆盖新查询。按钮有文字状态、错误role=alert、焦点可见、reduced-motion。
设计 token沿用批准原型:nav #102B27,workspace #F3F5F4,primary #17634C,AI #A33C38/#FBEFEC,manual #226A7A/#EDF5F7。新增样式限定 .xb-v2,不改无关页面。
## 4. 业务规则
- BR-61:240条对公金融租赁案例经幂等迁移入SQLite,每条能查看来源与会议依据。
- BR-62:三种筛选只读且确认完整匹配集合,提炼不跨选择/项目/主体。
- BR-63:两个提炼动作调用真实模型并产出有来源的独立草稿;无模型失败、无候选。Rule默认advisory,不从频次制造制度。
- BR-64:人工文件夹导入保留标准包、不可覆盖同名版本,能下载及在托管文件夹查看,任何脚本不执行。
- BR-65:新版界面仅客户经理和配置管理员;经理不能加工/发布,管理员发布仍须独立身份与隔离评测。
- BR-66:实际预审硬规则命中且result=issue才阻断,失败/缺证不能建议无条件推进;条件和例外由受控执行器验证。
- BR-67:新版三页实际连接数据库和既有运行,动态雷达面积不换算概率,情景不覆盖基线,真实域不显示演示概率。
## 5. 异常
- E-36 / LIBRARY_QUERY_INVALID:筛选无法解析或SQL越权;「筛选条件不受支持,请修改后重新预览」。无数据更改。
- E-37 / LIBRARY_SELECTION_INVALID:无确认/越权/过期/空集合;「请重新预览并确认要加工的案例」。拒绝提炼。
- E-38 / SKILL_PACKAGE_INVALID:非法路径/超限/非法标准格式/同名重复;「Skills 文件夹格式不正确,请检查 SKILL.md 与目录」。清理本次未完成暂存,旧包不改。
- E-39 / LIBRARY_MODEL_UNAVAILABLE:模型未配置/失败/输出非法;「本次未能完成经验提炼,请稍后重试或改为人工创建」。失败记录保留,无候选。
- E-40 / LIBRARY_FORBIDDEN:无角色/域/项目权限;「当前身份无权进行经验加工」。HTTP403。
- E-41 / SKILL_FOLDER_UNAVAILABLE:远程部署无本机打开能力;「当前部署不支持打开本机文件夹,请下载标准包」。包仍可下载。
## 6. Given/When/Then 验收映射
- AC-70(BR-61):Given空/已初始化SQLite When重复初始化 Then始终240条且全为对公租赁,重开连接数据仍在。
- AC-71(BR-62/E-36):GivenSQL/自然语言筛选 WhenSELECT或未知条件/写入/系统表 Then返回真实集合或明确拒绝,数据库无变更。
- AC-72(BR-62/E-37):Given查询确认 When越主体/空/过期提炼 Then拒绝;有效确认仅用选中ID。
- AC-73(BR-63/E-39):Given配置/未配置模型 When分别提炼Skills/Rules Then真实调用并可追溯草稿,失败不产候选、不发布。
- AC-74(BR-64/E-38/E-41):Given标准/非法文件夹 When导入 Then保留文件和哈希或拒绝;重开SQLite后可读取下载,导入脚本不执行。
- AC-75(BR-65/E-40):Given经理/管理员/旧reviewer When加工或发布 Then仅管理员可加工,独立发布与评测继续验证。
- AC-76(BR-66):Given失败步骤/硬规则/条件例外 When运行 Then保留失败、不误判推进;只有适用且已确认的硬规则阻断。
- AC-77(BR-67):Given实际项目运行 When查看三个页面/切换情景 Then调用真实接口、动态维度与情景对照可定位来源;缺证/未校准如实呈现。
测试所有权:backend/test_lease_library.py、test_skill_packages.py、现有预审执行/策略测试、前端生产构建与源码守卫;实际浏览器视觉验收需单独证据,构建不等于视觉通过。
## 7. 决策与阶段证据
SQLite用于当前单机本地磁盘,短事务、busy_timeout,文件单独存盘;不把模型耗时放在写事务内。WAL须在可控连接初始化并测试后启用,不使用网络共享盘;以后多实例大量并发写再迁移数据库。[SQLite适用场景](https://www.sqlite.org/whentouse.html)、[WAL](https://www.sqlite.org/wal.html)。实际数据未获取,真实模型、概率校准、Jev接入、生产发布均独立验收,不作本切片完成证据。
需求/架构/详细设计/UI:以上合同、规则与批准原型构成定案;用户2026-10-01授权实施。实现/验证/交付:完成后在22-implementation-delivery.md记录真实测试结果、入口与限制,当前尚未完成。无阻塞本切片的未决项。
## 8. 本轮补充的数据与执行事实
SQLite 迁移采用 lease_library_v1 / lease_library_v2 两个标记。第二次迁移仅追加经营现金流、租赁物估值、收入、负债、资产与初始材料快照字段,不清空既有记录。财务金额单位为万元,快照单独保存初始可得信息;正式评审结果不能混入初始输入。240条均为明确标注的虚构对公金融租赁案例。
新版界面仅提供客户经理、配置管理员两个身份;新案例库拒绝旧 reviewer 身份。旧业务 API 的历史权限兼容仍保留,不能据此宣称全平台旧身份已删除。Skill 本机打开仅限受控本机演示或 preview,远程环境使用标准 ZIP 下载。实际文件按哈希校验,脚本只存储、不执行。
新增轨迹对照使用两个冻结运行的材料、组件版本与核查结果;配置改变时提示不能直接归因于补件。模型提炼是真实调用链,但本轮模型输送测试使用替身,不证明真实提供商已接通或结果质量已校准。
+29
View File
@@ -0,0 +1,29 @@
# SQLite 三模块实施交付记录
日期:2026-10-01。代码目录:`F:\mini-OpenClaw-v2`。分支:`codex/sqlite-experience-workbench`,修改前恢复点:`9c351b0`。本记录对应 [实施规格](21-sqlite-implementation.md) 的 BR-61~67 / E-36~41 / AC-70~77。
## 实现范围
- 历史经验:240条虚构对公金融租赁案例入 SQLite,含初始快照、财务字段、正式会议结果和补件记录;表格、详情、分页、条件筛选、自然语言条件预览、受限只读 SQL、确认集合均通过真实接口执行。
- Skills / Rules:分别提炼,模型未配置或调用失败不生成假结果。候选进入既有治理池,AI来源红色、人工/文件夹来源蓝色,来源与发布状态分开。标准 Skill 文件夹支持 SKILL.md、references/scripts/assets、原文树、哈希校验、ZIP下载和受控本机打开。脚本不执行。
- 预审:资料文件夹、数据卡、Skills、Rules、自然语言需求装配既有 miniOpenClaw 执行链。新增对公权属核查方案,受控工具白名单继续有效。动态雷达、概率环、沙盘前后变化都读取实际运行或明确的演示情景。
- 轨迹:核查过程、冻结运行对照、正式会议结果三个入口。过程展示动作、依据、输入、核查结果与失败;不展示模型隐性思维。变化不直接冒充因果。
- 正确性修复:失败/缺证不得误判推进;已确认且适用的硬规则才阻断;Skill 条件与例外实际执行。
## 验证与证据
首次完整验证:`test-evidence/20261001-132738-9c351b0/REPORT.md`。593项后端测试、10项前端源码检查、生产构建、两套 Compose 配置均通过;仅格式检查发现一处空白,已修正,最终完整复验结果另行追加。
实际运行 API:`test-evidence/live-api-smoke.log`,健康正常、240条、制造业已通过案例 SQL 实际匹配12条,确认集合保存成功。文档链接与编号检查通过。源码检查和构建不能替代浏览器视觉验收。
## 运行与恢复
本地后端隔离端口8020,前端计划隔离端口3220,绑定127.0.0.1。前端需设置 `RISK_AGENT_BACKEND_URL=http://127.0.0.1:8020` 后启动。旧版及现有3212服务没有停止。
SQLite位于 `backend/xingbang_data/project_assistant.sqlite3`;Skill包位于同级 `skill_packages/`。初始化前已使用 SQLite backup API 留下 `test-evidence/pre-sqlite-workbench.sqlite3`,含可能尚未合并的 WAL 内容。迁移只追加字段;生产迁移另行审核。本轮未发布镜像、未合并默认分支。
## 未验证与后续边界
浏览器工具在启动时遇到 Windows sandbox helper 故障,因此交互和视觉验收保持未完成。本轮不能宣称真实模型提供商接通、Jev生效、真实审批概率校准或持续学习效果已验证。演示概率与真实结果分开;真实概率未校准返回空值。240条模拟数据不是兴邦真实经验,也不是公开真实业务数据集。
新版界面仅客户经理和配置管理员;旧业务 API 的 reviewer 历史兼容权限仍保留,新历史库接口拒绝该身份。卡片依旧需要测试和独立管理员发布。
## 沉淀与长期验证
本次修复的不变量由 `test_v2_workbench_chain.py` 承担:冻结快照不变、失败保留、硬规则不误触发、适用条件/例外有效。每次行为变更通过统一 verify 触发;回归失败即停止发布。真实效果 owner 为概率校准与经验治理模块,需要对之后独立时间段的真实会议结果进行验证,本窗口不宣称准确率提升。
最终统一验证:test-evidence/20261001-133341-9c351b0/REPORT.md 全部15项检查通过(后端593项测试)。前端生产构建通过;本轮不再重复扩大测试范围。
实际本机入口已启动:[历史经验](http://127.0.0.1:3220/experience-center)、[AI预审](http://127.0.0.1:3220/prescreen-workbench)、[轨迹复盘](http://127.0.0.1:3220/trajectory-center)。三个页面HTTP200、前端代理返回240条(test-evidence/live-frontend-smoke.log)。这是HTTP检查,不是浏览器视觉验收。
+3 -1
View File
@@ -6,7 +6,7 @@
## 当前工作边界
原版源码恢复点已保存;已制作独立 Superdesign 交互原型,本轮补齐三模块闭环与开发交接。未创建独立 v2 代码目录、未实施新版 API / Jev / 历史过程提炼、未部署或覆盖现有服务。迁移仍是后续任务,见实施计划;不能把现有名字含 v2 的旧模块误认为本轮新版已完成。
原版源码恢复点已保存;已制作独立 Superdesign 交互原型,本轮补齐三模块闭环与开发交接。独立代码目录现为 F:\mini-OpenClaw-v2;本轮实现 SQLite 对公租赁案例库、标准 Skill 文件夹、卡片装配与轨迹对照,详见 [实施规格](21-sqlite-implementation.md)。Jev、真实概率校准与生产部署仍未完成;旧服务未覆盖。
备份基线:`4d5a420671cb073a56335bd617e7d0812b8179a9`,Gitea `codex/backup-before-ai-prescreen-v2-20260928`;已通过远程 SHA 比对。此前主线:`b10093f9d8a949c6a6f8f8ed6d5b9c1bb044b569`。详情见交付记录。
@@ -49,3 +49,5 @@
## 下一步
从 T00 完成首个实施切片的契约、依赖白名单、隔离路径和门禁审阅;随后按 T01–T11 分批实现模拟闭环。Jev接入可选,Laya须按任务验证。真实数据/概率留到T13,不能因文档完备就宣称已接通或已校准。
当前实施与验证:[SQLite实施规格](21-sqlite-implementation.md)、[本轮交付记录](22-implementation-delivery.md)。历史条目保留为当时事实,后续状态以本轮代码和证据为准。
+29
View File
@@ -0,0 +1,29 @@
const fs=require('node:fs'),vm=require('node:vm'),assert=require('node:assert/strict'),path=require('node:path');
const html=fs.readFileSync(path.join(__dirname,'xingbang-v2-rebuild.html'),'utf8');
const scripts=[...html.matchAll(/<script(?:\s[^>]*)?>([\s\S]*?)<\/script>/g)].map(m=>m[1]).filter(x=>x.trim());
const elements=new Map(),handlers={},timers=[];
function element(id){if(!elements.has(id)){const classes=new Set();elements.set(id,{id,innerHTML:'',textContent:'',value:'',checked:false,disabled:false,isConnected:true,style:{},dataset:{},classList:{add:k=>classes.add(k),remove:k=>classes.delete(k),contains:k=>classes.has(k),toggle(k,force){const yes=force===undefined?!classes.has(k):force;yes?classes.add(k):classes.delete(k);return yes;}},setAttribute(){},removeAttribute(){},addEventListener(k,h){this[k]=h;},focus(){document.activeElement=this;},scrollIntoView(){}});}return elements.get(id);}
const nav=['history','review','trajectory'].map(p=>{const b=element('nav-'+p);b.dataset.page=p;return b;});
const document={getElementById:element,body:{style:{}},activeElement:null,addEventListener(k,h){handlers[k]=h;},querySelectorAll(q){return q==='[data-page]'?nav:[];},querySelector(q){return q==='.nav-button.active'?nav[0]:element(q);}};
const context=vm.createContext({document,window:{scrollTo(){}},setTimeout(h){timers.push(h);return timers.length;},clearTimeout(){},matchMedia(){return {matches:true};},FormData:class{constructor(data){this.data=data.values;}get(k){return this.data[k];}},console});
for(const s of scripts){new vm.Script(s);vm.runInContext(s,context);}
const evaluate=s=>vm.runInContext(s,context),D=evaluate('Domain');
let checks=0;function check(name,fn){fn();checks++;console.log('PASS '+name);}
function click(attrs){const b={dataset:attrs,disabled:false};handlers.click({target:{closest(){return b;}}});}
function change(id,checked,value,dataset={}){const t=element(id);t.checked=checked;t.value=value??t.value;t.dataset=dataset;handlers.change({target:t});}
check('dataset: 240 unique simulated corporate leasing cases',()=>{const rows=D.cases();assert.equal(rows.length,240);assert.equal(new Set(rows.map(r=>r.id)).size,240);for(const r of rows){assert.ok(r.simulated&&r.company.endsWith('有限公司')&&r.asset&&r.rent>0&&r.term>0);assert.ok(['直租','售后回租'].includes(r.type));}});
check('natural filter resolves structured subset with explicit unknown failure',()=>{const p=D.parse('制造业,售后回租,金额大于3000万元');assert.equal(p.ok,true);const rows=D.filter(D.cases(),p.conditions);assert.ok(rows.length>0&&rows.length<240);assert.ok(rows.every(r=>r.sector==='制造业'&&r.type==='售后回租'&&r.amount>3000));assert.equal(D.parse('有价值的客户').ok,false);assert.equal(D.parse('制造业,新能源').ok,false);assert.equal(D.filter(D.cases(),D.parse('未通过').conditions).every(r=>r.outcome==='未通过'),true);});
check('SQL query accepts whitelisted comparisons, order, limit and quoted strings',()=>{const p=D.sql("SELECT * FROM lease_cases WHERE industry = '制造业' AND amount_wan >= 3000 ORDER BY amount_wan DESC LIMIT 20;");assert.equal(p.ok,true);assert.equal(p.limit,20);assert.equal(p.order.field,'amount');const rows=D.filter(D.cases(),p.conditions);assert.ok(rows.every(r=>r.sector==='制造业'&&r.amount>=3000));assert.equal(D.sql("SELECT * FROM lease_cases WHERE company = 'O''Brien有限公司'").ok,true);assert.equal(D.sql('SELECT * FROM lease_cases').ok,true);});
check('SQL rejects writes, multistatements, forbidden fields, functions and malformed conditions',()=>{for(const sql of ['DELETE FROM lease_cases','SELECT * FROM lease_cases; DROP TABLE lease_cases','SELECT * FROM secret','SELECT * FROM lease_cases WHERE secret = 1','SELECT * FROM lease_cases WHERE amount_wan = 1 OR 1 = 1','SELECT * FROM lease_cases WHERE company = 1','SELECT * FROM lease_cases WHERE amount_wan > 1 AND','SELECT * FROM lease_cases LIMIT 0','SELECT * FROM lease_cases LIMIT 999','SELECT * FROM lease_cases ORDER BY secret','SELECT * FROM lease_cases WHERE amount_wan > SLEEP(1)','SELECT * FROM lease_cases -- comment'])assert.equal(D.sql(sql).ok,false,sql);});
check('history renders real rows, selection starts empty and extraction is disabled',()=>{assert.match(element('main-content').innerHTML,/DEMO-0001/);assert.match(element('main-content').innerHTML,/SQL 筛选/);assert.equal(element('extract-skills').disabled,true);assert.equal(evaluate('selectionIds().length'),0);});
check('SQL preview requires confirmation and then selection respects limit',()=>{element('sql-input').value="SELECT * FROM lease_cases WHERE industry = '制造业' ORDER BY amount_wan DESC LIMIT 7;";click({action:'parse-sql'});assert.equal(evaluate('visibleRows().length'),240);click({action:'apply-sql'});assert.equal(evaluate('visibleRows().length'),7);assert.equal(evaluate('visibleRows().every((r,i,a)=>!i||a[i-1].amount>=r.amount)'),true);change('select-all-filtered',true);assert.equal(evaluate('selectionIds().length'),7);assert.equal(element('extract-skills').disabled,false);});
check('extraction creates red-origin draft with correct selected source IDs, not active',()=>{click({extract:'skill'});assert.equal(evaluate('state.extracting'),true);timers.splice(0).forEach(h=>h());const c=evaluate('state.cards[0]');assert.equal(c.source,'ai');assert.equal(c.status,'draft');assert.equal(c.sourceIds.length,7);assert.equal(evaluate('state.selectedCards.has(state.cards[0].id)'),false);assert.match(element('main-content').innerHTML,/AI 提炼/);});
check('role guard rejects manager activation; administrator activates without changing origin',()=>{const draft=D.makeCard('rule',['DEMO-0001']);assert.throws(()=>D.activate(draft,'manager'),/ROLE_FORBIDDEN/);assert.equal(D.activate(draft,'admin').source,'ai');const id=evaluate('state.cards[0].id');click({activate:id});assert.equal(evaluate('state.cards[0].status'),'draft');change('role-select',false,'admin');click({activate:id});assert.equal(evaluate('state.cards[0].status'),'active');assert.equal(evaluate('state.cards[0].source'),'ai');});
check('dynamic radar changes with selected skill/rule sets and falls back below 3 axes',()=>{click({page:'review'});assert.equal(evaluate('axes().length'),6);change('plan-preset',false,'basic');assert.equal(evaluate('axes().length'),4);evaluate("state.selectedCards=new Set(['SK-01']);updateScenario()");assert.equal(evaluate('axes().length'),2);assert.match(element('radar').innerHTML,/阶段/);change('plan-preset',false,'full');});
check('four scenario outcomes are immediate, nonlinear and preserve 42% original result',()=>{for(const [a,b,p] of [[false,false,42],[true,false,58],[false,true,51],[true,true,64],[false,false,42]]){element('scenario-cash').checked=a;element('scenario-title').checked=b;handlers.change({target:element('scenario-cash')});assert.equal(element('scenario-prob').textContent,p+'%');assert.equal(evaluate('state.runs[0].probability'),42);assert.match(element('main-content').innerHTML,/>42<span>%/);}assert.equal(D.scenario(true,true),64);assert.notEqual(64,42+16+9);});
check('saved scenario creates frozen new run and produces comparison without overwriting baseline',()=>{element('scenario-cash').checked=true;element('scenario-title').checked=false;handlers.change({target:element('scenario-cash')});click({action:'save-review'});assert.equal(evaluate('state.runs.length'),3);assert.equal(evaluate('state.runs[0].probability'),42);assert.equal(evaluate('state.runs.at(-1).probability'),58);assert.equal(evaluate('Object.isFrozen(state.runs[0])&&Object.isFrozen(state.runs[0].selected)'),true);assert.match(element('main-content').innerHTML,/比较两次预审/);});
check('meeting pending/unsubmitted remain unobserved and cannot create improvement draft',()=>{for(const status of ['待定','未送审'])assert.equal(D.meeting(status).observed,false);assert.equal(D.meeting('通过').observed,true);evaluate("state.meeting={...Domain.meeting('未送审'),run:'DEMO-R03'}");const count=evaluate('state.cards.length');click({improve:'rule'});assert.equal(evaluate('state.cards.length'),count);});
check('meeting feedback creates provenance-linked draft and returns to pool',()=>{evaluate("state.meeting={...Domain.meeting('未通过'),run:'DEMO-R03',reason:'权属材料需补充'}");click({improve:'rule'});const c=evaluate('state.cards[0]');assert.equal(c.kind,'rule');assert.equal(c.status,'draft');assert.equal(c.source,'ai');assert.equal(c.sourceIds[0],'DEMO-R03');assert.equal(evaluate('state.page'),'history');});
check('unrecognized filter cannot clear or broaden previous conditions',()=>{const before=evaluate('JSON.stringify(state.conditions)');element('filter-input').value='随便看看';click({action:'parse-filter'});click({action:'apply-filter'});assert.equal(evaluate('JSON.stringify(state.conditions)'),before);});
check('HTML escapes manual inputs and declares accessibility and demo boundaries',()=>{assert.equal(evaluate("esc('<script>')"),'&lt;script&gt;');assert.match(html,/aria-modal="true"/);assert.match(html,/prefers-reduced-motion/);assert.match(html,/focus-visible/);assert.match(html,/未连接真实后台/);assert.match(html,/未经真实审批数据校准/);assert.doesNotMatch(html,/评审员/);});
console.log(JSON.stringify({checks,status:'passed',boundary:'Node VM logic and DOM stub; not browser E2E or visual acceptance'},null,2));
@@ -0,0 +1,41 @@
const fs=require('node:fs'),vm=require('node:vm'),assert=require('node:assert/strict'),path=require('node:path');
const html=fs.readFileSync(path.join(__dirname,'xingbang-v2-rebuild.html'),'utf8'),base=fs.readFileSync(path.join(__dirname,'check-rebuild.cjs'),'utf8');
const evidence=path.resolve(__dirname,'../../../test-evidence/20261001-skill-folder-prototype');fs.mkdirSync(evidence,{recursive:true});
const augmented=base.replace('FormData:class','TextEncoder,TextDecoder,Uint8Array,DataView,ArrayBuffer,Blob,FormData:class')+String.raw`
(async()=>{
const S=evaluate('SkillFiles');
check('every seeded Skill has a standard package with matching frontmatter name',()=>{for(const c of evaluate("state.cards.filter(c=>c.kind==='skill')")){const pack=evaluate('skillPackage(state.cards.find(c=>c.id==='+JSON.stringify(c.id)+'))');const text=pack.files.find(f=>f.path==='SKILL.md').text;assert.equal(S.validate(text,pack.name).ok,true);assert.ok(pack.files.some(f=>f.path==='references/checklist.md'));}});
check('invalid/missing fields, duplicate YAML, bad name, mismatched folder and unsafe tags are rejected',()=>{for(const text of ['# no frontmatter','---\nname: abc\n---\n# body','---\nname: Bad\ndescription: hello\n---\nbody','---\nname: abc\nname: abc\ndescription: hello\n---\nbody','---\nname: abc\ndescription: !!js/function >\n evil\n---\nbody'])assert.equal(S.validate(text,'abc').ok,false);assert.equal(S.validate('---\nname: abc\ndescription: hello\n---\nbody','other').ok,false);assert.equal(S.validate('---\nname: abc\ndescription: hello\n---\n','abc').ok,false);});
const source='---\r\nname: local-check\r\ndescription: |\r\n 使用于对公设备租赁,\r\n 保留原始多行说明。\r\nmetadata:\r\n version: "1.0"\r\nallowed-tools: Read\r\n---\r\n# 本地 Skill\r\n\r\n仅核对材料,不增加平台权限。\r\n';
check('standard multiline YAML and optional metadata parse without rewriting original',()=>{const p=S.validate(source,'local-check');assert.equal(p.ok,true);assert.match(p.description,/保留原始/);assert.equal(p.metadata['allowed-tools'],'Read');});
check('folder discovery retains original files and rejects duplicate names and traversal',()=>{const records=[{path:'skills/local-check/SKILL.md',text:source},{path:'skills/local-check/scripts/check.py',text:'raise Exception("never run")'}];const packages=S.discover(records,[]);assert.equal(packages.length,1);assert.equal(packages[0].ok,true);assert.equal(packages[0].files[0].text,source);assert.equal(packages[0].files[1].path,'scripts/check.py');assert.equal(S.discover(records,['local-check'])[0].code,'SKILL_NAME_DUPLICATE');assert.equal(S.discover([{path:'../../SKILL.md',text:source}],[])[0].ok,false);assert.equal(S.discover([{path:'empty/file.txt',text:'x'}],[])[0].ok,false);const duplicate=[...records,{path:'other/local-check/SKILL.md',text:source}];assert.equal(S.discover(duplicate,[]).filter(p=>p.ok).length,0);});
check('card pool exposes folder import and Skill file views while Rules remain separate',()=>{evaluate("state.poolTab='skill';navigate('history')");assert.match(element('main-content').innerHTML,/从文件夹添加/);assert.match(element('main-content').innerHTML,/打开技能文件夹/);click({action:'import-skill-folder'});assert.match(element('drawer-content').innerHTML,/webkitdirectory/);assert.match(element('drawer-content').innerHTML,/确认前不新增卡片/);evaluate("state.poolTab='rule';render()");assert.doesNotMatch(element('main-content').innerHTML,/从文件夹添加/);});
const toFile=(p,text)=>({webkitRelativePath:p,name:p.split('/').at(-1),size:Buffer.byteLength(text),async text(){return text;},async arrayBuffer(){return new TextEncoder().encode(text).buffer;}});
check('directory cancel preserves existing cards',()=>{const n=evaluate('state.cards.length');evaluate('state.skillImport.packages=[]');assert.equal(evaluate('state.cards.length'),n);});
const before=evaluate('state.cards.length');const files=[toFile('skills/local-check/SKILL.md',source),toFile('skills/local-check/scripts/check.py','raise Exception("never run")')];
await evaluate('readSkillFolder')(files);
check('reading a real file list creates preview only; script is never executed',()=>{assert.equal(evaluate('state.cards.length'),before);assert.match(element('skill-import-preview').innerHTML,/local-check/);assert.match(element('skill-import-preview').innerHTML,/基础格式通过/);});
click({action:'confirm-skill-import'});
check('confirmed folder import adds manual draft, exact original and no automatic dimensions/activation',()=>{const c=evaluate('state.cards[0]');assert.equal(c.title,'local-check');assert.equal(c.status,'draft');assert.equal(c.source,'manual');assert.equal(c.origin,'人工文件夹导入');assert.equal(c.dimensions.length,0);assert.equal(c.skillPackage.files.find(f=>f.path==='SKILL.md').text,source);assert.equal(evaluate('state.selectedCards.has(state.cards[0].id)'),false);assert.match(element('main-content').innerHTML,/人工 · 文件夹导入/);});
const cardId=evaluate('state.cards[0].id');click({skillFolder:cardId});
check('opening Skill shows directory tree, original YAML, and escaped content without executing',()=>{assert.match(element('drawer-content').innerHTML,/skills\/local-check/);assert.match(element('drawer-content').innerHTML,/SKILL.md/);assert.match(element('drawer-content').innerHTML,/allowed-tools: Read/);assert.match(element('drawer-content').innerHTML,/scripts\/check.py/);assert.equal(element('.drawer').classList.contains('skill-wide'),true);});
click({systemSkill:cardId});check('system folder action declares missing local adapter',()=>{assert.match(element('toast').textContent,/未接本地文件管理器/);});
const count=evaluate('state.cards.length');await evaluate('readSkillFolder')(files);click({action:'confirm-skill-import'});
check('reimported duplicate does not overwrite or create card',()=>assert.equal(evaluate('state.cards.length'),count));
await evaluate('readSkillFolder')([{webkitRelativePath:'too-large/SKILL.md',size:30*1024*1024}]);
check('oversize folder fails without disturbing pool',()=>{assert.equal(evaluate('state.cards.length'),count);assert.match(element('skill-import-preview').innerHTML,/超过500个文件或20MiB/);});
let releaseRead;const slow={webkitRelativePath:'skills/slow-skill/SKILL.md',size:100,text(){return new Promise(resolve=>{releaseRead=resolve;});}};
const pending=evaluate('readSkillFolder')([slow]);const previousPackages=evaluate('JSON.stringify(state.skillImport.packages)');element('skill-import-preview').isConnected=false;releaseRead('---\nname: slow-skill\ndescription: later\n---\nbody');await pending;
check('closing the preview during file read cannot apply a stale import',()=>{assert.equal(evaluate('JSON.stringify(state.skillImport.packages)'),previousPackages);assert.equal(evaluate('state.skillImport.busy'),false);});element('skill-import-preview').isConnected=true;
check('optional metadata types and tool declaration cannot bypass validation',()=>{assert.equal(S.validate('---\nname: abc\ndescription: hello\nmetadata: [one]\n---\nbody','abc').ok,false);assert.equal(S.validate('---\nname: abc\ndescription: hello\nallowed-tools: [Bash]\n---\nbody','abc').ok,false);});
const seed=evaluate('initialCards.filter(c=>c.kind===\'skill\').map(c=>SkillFiles.generated(c))');
for(const p of seed){const entries=[];for(const f of p.files){const out=path.resolve(SAMPLE_ROOT,p.name,f.path);assert.ok(out.startsWith(SAMPLE_ROOT+path.sep));fs.mkdirSync(path.dirname(out),{recursive:true});fs.writeFileSync(out,f.text,'utf8');entries.push({path:p.name+'/'+f.path,bytes:new TextEncoder().encode(f.text)});}fs.writeFileSync(path.join(EVIDENCE_ROOT,p.name+'.zip'),S.zip(entries));}
const pack=evaluate('state.cards.find(c=>c.id==='+JSON.stringify(cardId)+').skillPackage'),entries=[];
for(const f of pack.files){entries.push({path:pack.name+'/'+f.path,bytes:f.text!==undefined?new TextEncoder().encode(f.text):new Uint8Array(await f.browserFile.arrayBuffer())});}
fs.writeFileSync(path.join(EVIDENCE_ROOT,'imported-local-check.zip'),S.zip(entries));
check('exports generated and imported packages as actual ZIP bytes',()=>assert.equal(fs.readFileSync(path.join(EVIDENCE_ROOT,'imported-local-check.zip')).readUInt32LE(0),0x04034b50));
console.log(JSON.stringify({checks,status:'passed',boundary:'Node VM and file-list fixtures; not browser picker or OS integration'},null,2));
})().catch(e=>{console.error(e);process.exitCode=1;});
`;
const SAMPLE_ROOT=path.resolve(__dirname,'skill-examples'),EVIDENCE_ROOT=evidence;
const sandbox={require,module:{},__dirname,console,Buffer,process,SAMPLE_ROOT,EVIDENCE_ROOT,TextEncoder,TextDecoder,Uint8Array,DataView,ArrayBuffer,Blob};vm.runInNewContext(augmented,sandbox,{filename:__filename});
@@ -0,0 +1,34 @@
---
name: xingbang-sk-01
description: "财务报表、经营流水与租金计划交叉核验。"
metadata:
origin: "人工演示基线"
source-kind: "manual"
governance-version: "demo-v1"
---
# 租金覆盖核查
## 何时使用
对公设备租赁演示方案
## 材料与核查步骤
按同一期间核对现金流来源及租金计划,列出不一致与缺证。
## 例外与缺证
存在专项制度例外时需人工明确适用范围。
## 输出要求
列出核查项、证据定位、缺证与建议,不把证据阶段直接换算为审批概率。
## 运行边界
由 miniOpenClaw 在所选方案及授权只读工具范围内编排;本包不扩大工具权限、不执行系统命令。当前是未验证的演示技能,须审核后才可纳入真实业务。
## 参考
见 [材料核查清单](references/checklist.md)。
@@ -0,0 +1,7 @@
# 租金覆盖核查材料清单(演示)
- 对公承租主体与业务材料
- 实物租赁物清单及权属/价值材料
- 财务报表、经营流水与租金计划
缺失材料保持待核,不推断满足通过条件。
@@ -0,0 +1,34 @@
---
name: xingbang-sk-02
description: "核对实物租赁物、权属凭证与估值材料。"
metadata:
origin: "人工演示基线"
source-kind: "manual"
governance-version: "demo-v1"
---
# 租赁物核验
## 何时使用
对公设备租赁演示方案
## 材料与核查步骤
核对设备清单、权属主体、评估文件和实物对应关系。
## 例外与缺证
存在专项制度例外时需人工明确适用范围。
## 输出要求
列出核查项、证据定位、缺证与建议,不把证据阶段直接换算为审批概率。
## 运行边界
由 miniOpenClaw 在所选方案及授权只读工具范围内编排;本包不扩大工具权限、不执行系统命令。当前是未验证的演示技能,须审核后才可纳入真实业务。
## 参考
见 [材料核查清单](references/checklist.md)。
@@ -0,0 +1,7 @@
# 租赁物核验材料清单(演示)
- 对公承租主体与业务材料
- 实物租赁物清单及权属/价值材料
- 财务报表、经营流水与租金计划
缺失材料保持待核,不推断满足通过条件。
@@ -0,0 +1,34 @@
---
name: xingbang-sk-03
description: "核对保证主体、担保文件与适用范围。"
metadata:
origin: "人工演示基线"
source-kind: "manual"
governance-version: "demo-v1"
---
# 增信方案核查
## 何时使用
对公设备租赁演示方案
## 材料与核查步骤
核验担保方案覆盖范围;材料不足时输出补件建议。
## 例外与缺证
存在专项制度例外时需人工明确适用范围。
## 输出要求
列出核查项、证据定位、缺证与建议,不把证据阶段直接换算为审批概率。
## 运行边界
由 miniOpenClaw 在所选方案及授权只读工具范围内编排;本包不扩大工具权限、不执行系统命令。当前是未验证的演示技能,须审核后才可纳入真实业务。
## 参考
见 [材料核查清单](references/checklist.md)。
@@ -0,0 +1,7 @@
# 增信方案核查材料清单(演示)
- 对公承租主体与业务材料
- 实物租赁物清单及权属/价值材料
- 财务报表、经营流水与租金计划
缺失材料保持待核,不推断满足通过条件。
@@ -0,0 +1,34 @@
---
name: xingbang-sk-04
description: "检查不同材料的主体、期间和金额是否一致。"
metadata:
origin: "人工演示基线"
source-kind: "manual"
governance-version: "demo-v1"
---
# 材料一致性核查
## 何时使用
对公设备租赁演示方案
## 材料与核查步骤
逐项比对主体名称、财务期间和设备标识,记录引用与差异。
## 例外与缺证
存在专项制度例外时需人工明确适用范围。
## 输出要求
列出核查项、证据定位、缺证与建议,不把证据阶段直接换算为审批概率。
## 运行边界
由 miniOpenClaw 在所选方案及授权只读工具范围内编排;本包不扩大工具权限、不执行系统命令。当前是未验证的演示技能,须审核后才可纳入真实业务。
## 参考
见 [材料核查清单](references/checklist.md)。
@@ -0,0 +1,7 @@
# 材料一致性核查材料清单(演示)
- 对公承租主体与业务材料
- 实物租赁物清单及权属/价值材料
- 财务报表、经营流水与租金计划
缺失材料保持待核,不推断满足通过条件。
@@ -0,0 +1,32 @@
---
name: xingbang-material-completeness
description: 在金融租赁公司对公设备项目初步材料阶段核对材料完整性,列出具体缺证与补件需求;仅用于原型导入演示。
metadata:
origin: 人工文件夹导入示例
version: "1.0"
---
# 对公租赁初步材料完整性核查
## 何时使用
客户经理收集对公设备租赁的初步材料时使用。此示例不代表兴邦金租真实制度。
## 核查步骤
1. 列出已提供的文件、材料期间与承租主体。
2. 对照[材料清单](references/materials.md),逐项标注已提供、待核或缺失。
3. 检查租赁物清单、租金计划与主体信息的对应关系;记录原文件和页码。
4. 输出明确补件清单,说明每项材料用于解决哪个问题。
## 例外与限制
专项业务存在经确认的例外时,先列出例外及来源,不自行放宽要求。缺证只记待核,不推断项目能够通过。
## 输出要求
返回核查项、已提供材料、证据定位、缺证及补件建议。该Skill不计算审批通过概率。
## 运行边界
仅在miniOpenClaw方案与授权只读工具范围内核查。导入后先进入草稿,需管理员审核;本示例未投入真实业务,不扩大工具权限。
@@ -0,0 +1,8 @@
# 初步材料清单(演示)
- 承租企业主体及经营说明
- 实物设备清单、权属及价值材料
- 财务报表与经营流水
- 租金计划与增信方案
以上仅为虚构演示核查结构,真实清单以企业审核制度为准。
File diff suppressed because one or more lines are too long