@microi.net/cli 4.6.2 → 4.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/mcp-server.js +101 -93
  2. package/dist/microi-cli.js +198 -47
  3. package/dist/microi-skills.meta.json +148 -142
  4. package/dist/microi.skills/.microi-skills-version.json +2 -2
  5. package/dist/microi.skills/README.md +3 -2
  6. package/dist/microi.skills/ai-engine/SKILL.md +38 -11
  7. package/dist/microi.skills/app-store/SKILL.md +134 -99
  8. package/dist/microi.skills/message-notification/agents/openai.yaml +0 -1
  9. package/dist/microi.skills/microi-ai-application/SKILL.md +8 -0
  10. package/dist/microi.skills/microi-client-frontend/SKILL.md +401 -394
  11. package/dist/microi.skills/microi-db-schema/SKILL.md +165 -164
  12. package/dist/microi.skills/microi-deployment/SKILL.md +29 -3
  13. package/dist/microi.skills/microi-docs-coverage/references/capability-map.md +4 -3
  14. package/dist/microi.skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +10 -3
  15. package/dist/microi.skills/microi-form-engine/SKILL.md +165 -159
  16. package/dist/microi.skills/microi-system-delivery/SKILL.md +205 -196
  17. package/dist/microi.skills/microi-ui/SKILL.md +330 -321
  18. package/dist/microi.skills/microi.v8.js +1818 -1758
  19. package/dist/microi.skills/ocr-engine/SKILL.md +111 -0
  20. package/dist/microi.skills/ocr-engine/agents/openai.yaml +4 -0
  21. package/dist/microi.skills/page-engine/SKILL.md +2 -0
  22. package/dist/microi.skills/performance-testing/SKILL.md +2 -2
  23. package/dist/microi.skills/playwright-e2e/SKILL.md +14 -40
  24. package/dist/microi.skills/print-engine/SKILL.md +9 -3
  25. package/dist/microi.skills/report-engine/SKILL.md +1 -1
  26. package/dist/microi.skills/translate-engine/SKILL.md +47 -5
  27. package/dist/microi.skills/ui-design/SKILL.md +1596 -1575
  28. package/dist/microi.skills/ui-design/assets/templates/MCI-DESIGN.md +199 -98
  29. package/dist/microi.skills/ui-design/references/design-pattern-library.md +184 -171
  30. package/dist/microi.skills/ui-design/references/mci-design-contract.md +163 -84
  31. package/dist/microi.skills/v8-file-upload/SKILL.md +8 -0
  32. package/dist/microi.skills/v8-frontend-events/SKILL.md +4 -1
  33. package/dist/microi.skills/v8-frontend-events/references/bluetooth-print.md +28 -22
  34. package/dist/microi.skills/v8-http-integration/SKILL.md +22 -1
  35. package/dist/microi.skills/v8-saas-multi-tenant/SKILL.md +2 -1
  36. package/dist/microi.skills/v8-security/SKILL.md +7 -6
  37. package/dist/microi.skills/v8-utilities/references/server-api-index.md +1 -0
  38. package/dist/microi.skills/workspace-conventions/SKILL.md +22 -23
  39. package/package.json +1 -1
@@ -199,6 +199,9 @@ V8.RefreshTable({ _PageIndex: 1 });
199
199
  | `V8.Method.ScanCode({...})` | 调用当前终端支持的扫码能力 |
200
200
  | `V8.Print.isConnected()` | 检查当前蓝牙写特征是否仍可用 |
201
201
  | `V8.Print.OpenBluetoothPage()` | 在用户手势中打开蓝牙连接页,返回 Promise |
202
+ | `V8.Print.reconnect()` | 使用已记住的设备授权或设备 ID 尝试重连 |
203
+ | `V8.Print.getConnectionState()` | 获取连接、设备、记忆和错误状态快照 |
204
+ | `V8.Print.subscribeConnection(listener)` | 订阅应用级共享连接状态,返回取消订阅函数 |
202
205
  | `V8.Print.prepareSend(bytes)` | 串行分包发送 TSC 或 ESC/POS 字节,必须 `await` |
203
206
 
204
207
  `V8.OpenAnyForm` 只发起打开动作,不返回“用户关闭后的 Promise”。需要替换
@@ -234,7 +237,7 @@ try {
234
237
  }
235
238
  ```
236
239
 
237
- `prepareSend` 成功只证明字节已经写入蓝牙特征,不代表打印机已走纸、无缺纸或无硬件故障。批量打印必须逐条 `await`,不得用固定 `setTimeout` 猜测完成时间,也不得用 `Promise.all` 并发写同一设备。完整挂载范围、连接语义、批量恢复、安全与硬件验收见 `references/bluetooth-print.md`;源码级 TSC/ESC 方法表见 `references/bluetooth-print-api.md`。
240
+ PC/平板顶部导航与移动端【我的】页共用同一个应用级 `V8.Print` 实例,用户可先在全局入口连接,再进入任意模块打印。`prepareSend` 内部会把不同 V8 上下文排入同一发送队列;成功只证明字节已经写入蓝牙特征,不代表打印机已走纸、无缺纸或无硬件故障。批量打印仍应逐条 `await`,不得用固定 `setTimeout` 猜测完成时间,也不要用 `Promise.all` 表达同一设备的并行打印。完整挂载范围、连接语义、批量恢复、安全与硬件验收见 `references/bluetooth-print.md`;源码级 TSC/ESC 方法表见 `references/bluetooth-print-api.md`。
238
241
 
239
242
  ### 常用上下文差异
240
243
 
@@ -29,9 +29,12 @@
29
29
  Microi 浏览器/5+App 前端 V8 中可用,不属于后端接口引擎、后端表单事件或微信小程序
30
30
  原生 BLE API。
31
31
 
32
- 基础 V8 对象会把同一个 `Print` 状态复制给多个前端 V8 上下文。分包游标、打印份数和
33
- 连接引用均为可变状态,所以同一页面的所有打印任务必须共用一条串行队列,不能认为
34
- 不同按钮或不同 V8 对象彼此隔离。
32
+ 三个入口都会取得同一个应用级 `Print` 单例。分包游标、打印份数和连接引用仍是可变状态,
33
+ 但 `prepareSend` 已把所有前端 V8 上下文排进同一条运行时发送队列。业务代码仍应逐次
34
+ `await` 保持明确的结果顺序,不能认为不同按钮或不同 V8 对象彼此隔离。
35
+
36
+ PC/平板顶部导航和移动端【我的】页的蓝牙入口也使用该单例:它们展示实时连接状态和设备名,
37
+ 点击后复用 `OpenBluetoothPage()`,因此用户可以先在全局入口连接,再进入任意模块执行 V8 打印。
35
38
 
36
39
  ## 运行环境与能力判断
37
40
 
@@ -55,22 +58,23 @@ ESC/POS 原生字节通过 BLE 写入才属于 `V8.Print`。
55
58
  |---|---|
56
59
  | `createNew()` | 新建 TSC/TSPL 标签指令构建器 |
57
60
  | `createNewESC()` | 新建 ESC/POS 小票指令构建器 |
58
- | `OpenBluetoothPage()` | 返回 `Promise<boolean>`;在连接弹窗关闭时解析,值表示关闭时是否有连接信息 |
59
- | `isConnected()` | Web 端检查实时 GATT 与写特征;5+App 端只检查已保存的设备/写特征 ID |
60
- | `prepareSend(bytes)` | 未连接时先打开连接页,然后按包串行写入;必须 `await` 并捕获失败 |
61
+ | `OpenBluetoothPage()` | 返回 `Promise<boolean>`;在连接弹窗关闭时解析,重复打开复用同一个 Promise |
62
+ | `isConnected()` | Web 端检查实时 GATT 与写特征;5+App 结合连接事件在线标记与设备/写特征 ID |
63
+ | `reconnect()` | 使用已记住的设备 ID 或浏览器保留的设备授权重连,不弹选择框 |
64
+ | `getConnectionState()` | 返回可展示的连接、记忆、设备、错误和重连状态快照 |
65
+ | `subscribeConnection(listener)` | 立即回调当前快照并持续通知状态变化,返回取消订阅函数 |
66
+ | `prepareSend(bytes)` | 先尝试恢复连接,再进入应用级队列按包串行写入;必须 `await` 并捕获失败 |
61
67
  | `Send(bytes)` | 依赖 `prepareSend` 已设置的内部游标,属于内部状态机入口,业务代码不要直接调用 |
62
- | `setOneTimeData(bytes)` | 设置 BLE 包长;源码不校验,内置候选为 20–190、步长 10,默认 20 |
63
- | `setPrinterNum(num)` | 重复发送同一缓冲区;源码不校验,内置候选为整数 1–9 |
64
- | `disconnect()` | 主动断开并清理当前设备、写特征和会话元数据 |
68
+ | `setOneTimeData(bytes)` | 设置 BLE 包长;只接受 1–512 整数,连接页候选 20–190,默认 20 |
69
+ | `setPrinterNum(num)` | 重复发送同一缓冲区;只接受 1–99 整数,连接页候选 1–9 |
70
+ | `disconnect()` | 主动断开、停止自动重连并忘记当前设备 |
65
71
  | `BLEInformation` | 最近设备/服务/特征元数据,只用于诊断,不代表实时连接或打印回执 |
66
72
 
67
- `OpenBluetoothPage()` 已打开时再次调用会返回 `false`。它不是“连接成功事件”;用户连上
68
- 设备后仍要关闭弹窗,调用方才能继续。`prepareSend` 虽会自动打开连接页,但业务按钮主动
69
- 建立连接更容易给出清晰提示。
70
-
71
- 连接元数据会写入 `sessionStorage`,但当前 `restoreBLEInfo()` 没有进入初始化调用链,页面
72
- 刷新后不会自动恢复可发送的 GATT/特征引用。刷新、跨页面重建或断线后应重新连接。
73
- 5+App 的 `isConnected()` 只验证 ID 是否存在,因此发送仍可能因物理断线失败。
73
+ `OpenBluetoothPage()` 不是“连接成功事件”;用户连上设备后仍要关闭弹窗,调用方才能继续。
74
+ 设备元数据会写入 `localStorage` 与兼容用 `sessionStorage`。应用初始化、页面恢复、重新获得
75
+ 焦点和意外断线时会做有限次数自动重连:5+App 使用设备 ID;Web 端只有浏览器保留授权且
76
+ 支持 `navigator.bluetooth.getDevices()` 时才可无弹窗恢复。系统蓝牙、浏览器权限、设备电源、
77
+ 休眠、距离等仍会造成真实断线;重试结束后必须让用户从全局入口重新选择。
74
78
 
75
79
  ## 最小安全流程
76
80
 
@@ -85,7 +89,8 @@ async function ensurePrinterConnected() {
85
89
  if (!V8.Print) throw new Error('当前前端未加载蓝牙打印能力');
86
90
  if (V8.Print.isConnected()) return;
87
91
 
88
- var connected = await V8.Print.OpenBluetoothPage();
92
+ var connected = await V8.Print.reconnect();
93
+ if (!connected) connected = await V8.Print.OpenBluetoothPage();
89
94
  if (!connected || !V8.Print.isConnected()) {
90
95
  throw new Error('未连接蓝牙打印机');
91
96
  }
@@ -141,7 +146,8 @@ async function printBatch(rows, startIndex) {
141
146
  ```
142
147
 
143
148
  - 不用固定 `setTimeout(3000)` 猜测上一张是否完成。
144
- - 不用 `Promise.all`,也不要让两个按钮同时调用 `prepareSend`。
149
+ - 不用 `Promise.all` 表达同一设备的并行打印。运行时会把同时到达的调用排队,但业务仍应逐条
150
+ `await`,以便准确记录哪一条成功或失败。
145
151
  - 大批次分段并持久化 `NextIndex`;页面关闭、断连或写失败后从失败位置人工确认再恢复。
146
152
  - `setPrinterNum(n)` 只适合同一缓冲区重复发送,不适合每张内容不同的批次。
147
153
  - 业务落库与蓝牙打印不是原子事务。用稳定业务单号支持受控重打,不重复执行业务写入。
@@ -153,14 +159,14 @@ async function printBatch(rows, startIndex) {
153
159
  当前没有公开的自定义服务配置,并选择枚举到的第一个可写特征;其它型号可能需要扩展源码。
154
160
  - `prepareSend` 默认每包 20 字节、包间约 20ms;同一缓冲区多份打印间约 100ms。这只是
155
161
  BLE 写节奏,不是打印完成等待时间。包长必须是已实测的正整数,空缓冲区不得发送。
156
- - 当前分包公式是 `floor(length / packetSize) + 1`。长度恰好整除包长时会尝试额外写一个
157
- 0 字节末包;某些 BLE 栈会拒绝。实机出现此问题时应修复适配器并回归,不要靠并发或吞错绕过。
162
+ - 当前分包公式使用 `Math.ceil(length / packetSize)`,长度恰好整除时不会产生 0 字节末包;
163
+ 空数据、非法包长和非法份数会直接抛错。合法数值仍须按目标打印机实测。
158
164
  - TSC 与 ESC 文本使用仓库内置 `encoding.js` + `encoding-indexes.js` 转为 GB18030,运行时
159
165
  不请求网络。编码成功不等于打印机字体、代码页和固件支持全部字符;Emoji 等仍需实机验证。
160
166
  - `setBitmap` 接受 ImageData 风格 `{ width, height, data }` RGBA 数据。当前黑白转换较简单,
161
167
  大图可能产生大缓冲区;先缩放、二值化并用小图测试。
162
- - `V8.Print` 没有任务锁和队列。跨 V8 上下文并发会互相覆盖 `currentTime`、`looptime`、
163
- `lastData` 等共享状态,必须由业务侧全局串行化。
168
+ - `V8.Print` 使用应用级共享发送队列,跨 V8 上下文不会再并发覆盖 `currentTime`、`looptime`、
169
+ `lastData` 等共享状态。队列只保证写入顺序,不提供打印机 ACK、业务事务或自动重打语义。
164
170
 
165
171
  ## 安全边界
166
172
 
@@ -63,7 +63,28 @@ if (resp.StatusCode < 200 || resp.StatusCode >= 300) {
63
63
  | `FilesByteBase64` / `FilesByteString` | 文件字段对象,键同时作为字段名和文件名。 |
64
64
 
65
65
  `GetResponse/PostResponse/PatchResponse` 返回 `Content`、`Headers`、`RawBytes`、`StatusCode`、`ErrorMessage`。后端 `RawBytes` 是 `.NET byte[]`,前端是 `Uint8Array`。
66
-
66
+
67
+ ## V8.AI 与底层 HTTP
68
+
69
+ - 前端 V8 的平台 AI 普通调用优先 `await V8.AI.Chat(...)`。它自动使用当前 ApiBase 和平台登录头、接收响应 Token 轮换,并清除调用参数中的租户、身份、Endpoint、ApiKey 和认证头覆盖;只有确认问题适合进入 URL 日志时才用 `V8.AI.ChatGet(...)`。
70
+ - 浏览器打字机效果使用 `await V8.AI.ChatStream(param, onChunk, { Signal })`。它解析 `message/result/error/done` SSE,`onChunk` 接收真实增量;页面关闭时通过 `AbortController` 取消读取。
71
+ - 后端 V8 直接使用 `await V8.AI.Chat(...)`、`ChatStream(...)`、`NL2SQL(...)` 或管理员限定的 `NL2V8(...)`。对象在服务端绑定当前 `OsClient` 与认证用户,匿名上下文拒绝;禁止退回到自请求当前 API、转发 Token 或接受用户指定 Endpoint/ApiKey 的包装方式。
72
+ - MCP 使用专用 `microi_chat`,由 MCP 连接提供 Token 与租户,只接受对话白名单参数并返回最终 `DosResult`。它不是逐 token MCP 流;其它平台写操作继续使用对应写 Tool 的确认与回读规则。
73
+ - `Chat/ChatStream` 虽兼容 GET/POST,含问题、附件和会话上下文时一律优先 POST,避免敏感内容进入 URL、代理日志和浏览器历史。`V8.Http` 继续用于通用第三方 HTTP 集成,不要重复实现平台 AI 的认证或 SSE 解析器。
74
+
75
+ ```javascript
76
+ // 前端或后端 V8:普通 AI 对话
77
+ var result = await V8.AI.Chat({
78
+ UserChatMsg: '归纳当前工单',
79
+ AiModel: 'MiniMax-M3',
80
+ AiModelId: '当前租户启用的 mic_ai 记录Id'
81
+ });
82
+ if (result.Code != 1) V8.Tips(result.Msg || 'AI调用失败', false);
83
+ else V8.Result = result.Data;
84
+ ```
85
+
86
+ 完整授权矩阵、SSE、后端安全边界与 MCP 示例维护在官网现有 `system-engine/ai-engine.md`,不要新建重复文档。
87
+
67
88
  ## POST 请求(对象参数格式)
68
89
 
69
90
  > V8 接口引擎中必须使用对象参数格式。尤其禁止 `V8.Http.Get(url)`:当前 .NET 同名重载包含 `Task<string> Get(string)`,Jint 可能把字符串调用解析为异步重载,脚本最终拿到 `[object Promise]`。GET 必须写成 `V8.Http.Get({ Url: url })`;第三方登录、微信 `jscode2session`、AccessToken 等链路保存后必须用无效 code 烟测,确认返回的是第三方明确错误而不是 Promise。
@@ -53,7 +53,8 @@ V8.OsClientModel.AliOssPublicDomain // 可公开的文件域名
53
53
 
54
54
  - 所有可变业务逻辑默认必须由接口引擎编排,包括但不限于租户开通、开库、初始化、归属修复、官网个人中心、付费额度等 SaaS 业务流程。C# 后端只暴露原子 V8 能力,例如建库、导入空库模板、复制 `sys_config`、刷新 SaaS 缓存、补偿回滚、字段兜底等;不要把可变业务分支写死到 Controller 或 `TenantProvisioningService` 这类后端定制代码里。接口引擎缺少能力时,优先扩展 `V8.Method`/V8 引擎原子函数,再由接口引擎调用。
55
55
  - 主租户由运行环境决定:优先读取环境变量 `OsClient`,其次读取 `appsettings.json` 的 `AppSettings:OsClient`。只有这条主租户 `sys_osclients` 数据中的平台级字段会作为全局配置生效。
56
- - 普通业务与运行参数统一从主租户 `sys_osclients` `sys_config` 读取,未配置时使用代码安全默认值;不要再为同一参数增加 `MICROI_*` `DOS_ORM_*` 环境变量。数据库、Redis 和必要密钥属于启动基础设施,继续使用现有专用安全配置;节点身份由平台自动生成。
56
+ - API 启动配置只有十项白名单:`OsClient`、`OsClientType`、`OsClientNetwork`、`OsClientDbType`、`OsClientDbConn`、`OsClientRedisHost`、`OsClientRedisPort`、`OsClientRedisPwd`、`OsClientRedisDataBase`、`OsClientDbMongoConn`。除这十项外,普通业务、运行参数、密钥路径、重试、限额和安全策略统一从主租户 `sys_osclients` 或按租户从 `sys_config` 读取,未配置时使用代码安全默认值;官方 License 恢复次数/间隔与固定私钥挂载 `/app/microi_private.pem` 是信任链例外,禁止创建对应 SaaS 字段。禁止再增加 `MICROI_*`、`DOS_ORM_*`、自定义 `AppSettings` 节点或动态名称的环境变量读取。节点身份由平台自动生成。
57
+ - `ASPNETCORE_*`、`DOTNET_*` 仅用于 .NET 宿主;构建、安装、测试、MCP、发布脚本可使用自身进程变量,但 API 生产代码不得把它们当业务配置。新增 SaaS 运行字段必须配套独立或既有 Tab、幂等升级、缓存刷新、敏感字段脱敏、子租户不继承和源码扫描测试。
57
58
  - 文件上传的租户业务开关与额度按“当前租户 `sys_osclients` → 代码默认值”解析;平台固定灾难保护、HTTP/Multipart/Form 和反向代理上限不可由租户覆盖,也不要求安装者维护额外上传环境变量。
58
59
  - 类似 MQTT 端口、PressureGuard、V8Limits、OrmLimits、StartupLimits、SecurityGuard 这类影响整进程资源的配置,不能让每个子租户各自抬高全局上限。子租户同名隔离字段只能降低自己的并发、等待时间或资源额度,用于隔离弱租户、试用租户或异常租户。
59
60
  - 修改 `sys_osclients` 的表、字段、数据源或配置值后,必须刷新 SaaS 引擎运行缓存,并回读验证字段 `Component`、`Data`、`Config`、实际数据值和前端真实消费结果。不要只看 MCP 写入成功。
@@ -29,7 +29,7 @@ var openaiKey = 'sk-xxxxxxxxxx';
29
29
 
30
30
  子租户缺少 RabbitMQ/MQTT/Search 独立凭据时必须失败关闭,禁止回退主租户账号。新租户开通只有在外部 broker/search 中真实创建 user、vhost、ACL 或 API Key 后,才能标记对应服务可用。
31
31
 
32
- 登录和管理端必须强制 HTTPS。登录 RSA 只用于避免密码在请求体、代理调试界面中直接显示,不能替代 HTTPS,也不能作为身份认证或密码存储密钥。平台为兼容已发布客户、旧前端和浏览器缓存,保留历史登录 RSA 密钥对作为缺省回退;安全修复不得直接删除该回退并造成全量客户无法登录。需要部署专属密钥时,服务端通过 `MICROI_LOGIN_RSA_PRIVATE_KEY` 或受限密钥文件注入私钥,同时通过 `MICROI_LOGIN_RSA_PUBLIC_KEY` / `Security:LoginRsaPublicKey` 向匿名 `GetSysConfig` 提供匹配公钥;两端必须成对切换。源码、V8、前端业务代码和日志中仍禁止新增或输出其它真正的业务私钥、JWT 密钥、支付密钥及对象存储凭据。
32
+ 登录和管理端必须强制 HTTPS。登录 RSA 只用于避免密码在请求体、代理调试界面中直接显示,不能替代 HTTPS,也不能作为身份认证或密码存储密钥。平台为兼容已发布客户、旧前端和浏览器缓存,保留历史登录 RSA 密钥对作为缺省回退;安全修复不得直接删除该回退并造成全量客户无法登录。需要部署专属密钥时,在主租户 SaaS 引擎【后端运行配置】中成对维护 `BackendLoginRsaPrivateKey` `BackendLoginRsaPublicKey`;私钥只允许可信服务端读取,匿名 `GetSysConfig` 只返回匹配公钥。源码、环境变量、普通 V8、前端业务代码和日志中仍禁止新增或输出真正的业务私钥、JWT 密钥、支付密钥及对象存储凭据。
33
33
 
34
34
  吾码现有多端兼容约定是:主 SaaS 引擎 `sys_osclients.CorsAllowOrigins` 为空时默认允许全部跨域,便于本地开发、独立前端、H5 和不同租户域名访问;配置了来源后才按精确来源或通配符限制。安全修复不得把“未配置”改成默认拒绝,否则会造成所有存量部署和本地调试突然失效。CORS 不是鉴权边界,权限仍必须依赖 Token、租户隔离、菜单/表权限和服务端数据范围。
35
35
 
@@ -98,8 +98,9 @@ V8.Db.FromSql("SELECT * FROM " + V8.Param.table).ToArray();
98
98
  - 菜单 `SqlWhere`、`SqlJoin` / `JoinTables` 数据范围必须在服务端形成的**真实列表、计数和导出查询**中执行,不能只用于界面展示或查询后过滤。单行详情只校验同表菜单访问权,不应用这些模块列表过滤;它们也不是行级写权限。
99
99
  - 主表新增、修改、删除分别由当前角色的 `Add`、`Edit`、`Del` 权限控制;不得把查询 SqlWhere 追加到写入 SQL,也不得因为查询包含跨表 Join 拒绝已获授权的写入。需要“仅可修改本人数据”等业务限制时,在 `SubmitBeforeServerV8` 或专用接口引擎中以可信服务器代码校验,并统一写入 `TenantId`、负责人、创建人等归属字段。
100
100
  - 导入、导出必须携带真实菜单上下文,并分别拥有 `Import`、`Export`;不能用 Table 级直接授权绕过。
101
- - SaaS 配置、接口引擎、表/字段元数据、菜单角色、系统用户、任务、数据源、MQ/MQTT、页面/打印/工作流、扩展数据库等平台敏感表,对 `Level < 9999` 的通用客户端 FormEngine 硬拒绝。错误的菜单或 Table 授权不能覆盖。
102
- - 匿名读取/新增仅适用于 `diy_table` 明确开启匿名能力的普通业务表;敏感平台表必须先于匿名开关拒绝。
101
+ - 平台表必须按后端 `PlatformResourceSecurity` 分级:账号/角色/权限、SaaS 配置、接口引擎、表字段元数据、任务、数据源、密钥和基础设施等管理员专用表,对 `Level < 9999` 的通用客户端 FormEngine 全操作硬拒绝;工作流、微服务/商店、蓝图和微应用运行元数据只允许显式授权后的 `Read/List`,写入仍硬拒绝;`mic_page/mic_print` 按真实菜单或 Table 的 `Read/Add/Edit/Del` 权限管理。
102
+ - 匿名读取/新增仅适用于 `diy_table` 明确开启匿名能力的普通业务表;上述三类平台表必须先于匿名开关拒绝。
103
+ - 角色增删改接口不能只相信 Token 缓存或前端禁用状态,必须覆盖请求中的 `_CurrentUser/OsClient`,并从租户主库复核活动用户、数据库 Level 与有效角色 Level。角色降级要先同步受影响用户 Level,再提升共享授权 `epoch`,避免旧令牌窗口;Postman 伪造 `_IsAdmin/Level/RoleIds` 必须失败。
103
104
  - 权限 JSON、角色 Id 或菜单上下文解析失败时必须失败关闭;角色 Id 使用精确集合匹配,禁止 `Contains` 子串判断。
104
105
 
105
106
  标准菜单模块继承菜单权限,不需要维护“角色 × 全部业务表”的巨大矩阵,也不能要求所有历史前端 V8 立即补传菜单 Id。新前端在当前表上下文应由平台 facade 自动携带真实菜单;历史无菜单请求继续由后端安全推断。【高级表权限】只用于确实没有任何菜单入口的定制页面/SDK,并按最小权限授予。
@@ -353,7 +354,7 @@ try {
353
354
  - 普通访问默认阈值为 10 秒 600 请求/120 异常。VS Code 多服务器源码拉取不能通过减少并发或请求量规避;符合条件的只读 V8Debug `Get/List` 使用独立桶,默认 10 秒 6000 请求/1200 异常。
354
355
  - 受信判断必须同时满足:服务端共享登录态确认 `Level >= 9999`、请求 Token 是活动 `ClientType=VSCode` Token、请求 `did` 与该 Token 保存的 Did 完全一致、路由为只读 `/api/V8Debug/Get*` 或 `/api/V8Debug/List*`。自报 `ClientType`、User-Agent、`X-User-Level`、单独伪造 did 都不可信;Update/Create/Execute/Upload/Finalize 和 FormEngine 写入不放宽。
355
356
  - 普通请求的计数 scope 固定为当前 API 主运行实例,不能使用请求方可控的 `OsClient` Query/Header 选择 Redis 桶;只有已通过上述联合校验的 VS Code profile 才能使用活动 Token 服务端绑定的租户 scope。测试必须覆盖轮换两个真实租户 Key 仍落入同一普通桶。
356
- - SecurityGuard 只读取 `Connection.RemoteIpAddress`,不得直接解析 `X-Forwarded-For`/`X-Real-IP`。宿主在 `UseForwardedHeaders` 中只信任 `ForwardedHeaders:KnownProxies` 的精确 IP 和 `KnownNetworks` 的受控 CIDR(环境变量可用 `ForwardedHeaders__KnownProxies__0` / `ForwardedHeaders__KnownNetworks__0`);禁止 `0.0.0.0/0`、`::/0`。历史 `SecurityRespectForwardedHeaders` 字段不能建立 Header 信任。测试必须覆盖公网 Remote IP 携带伪造 `X-Forwarded-For: 127.0.0.1` 时仍识别公网 IP。
357
+ - SecurityGuard 只读取 `Connection.RemoteIpAddress`,不得直接解析 `X-Forwarded-For`/`X-Real-IP`。宿主在 `UseForwardedHeaders` 中只信任主租户 SaaS 引擎【后端运行配置】的 `BackendForwardedKnownProxies` 精确 IP 和 `BackendForwardedKnownNetworks` 受控 CIDR;禁止使用环境变量、自定义 `appsettings` 节点、`0.0.0.0/0` 或 `::/0`。历史 `SecurityRespectForwardedHeaders` 字段不能建立 Header 信任。该宿主配置变更需滚动重启节点,测试必须覆盖公网 Remote IP 携带伪造 `X-Forwarded-For: 127.0.0.1` 时仍识别公网 IP。
357
358
  - 自动封禁按 `ExpiresAtUtc` 到期解除。立即解除只能从未被封禁的管理网络,以平台超级管理员进入【系统日志 → 安全防护】操作 `/api/SecurityGuard/UnblockIp`;同一被封出口不能给自己解封。
358
359
  - 固定可信出口才可精确加入当前服务器匹配 `sys_osclients.SecurityWhitelistIps`。禁止全网段、动态用户 IP 或请求参数自动入白名单,也不要关闭安全防护或全局放大普通阈值。
359
360
  - 多节点封禁、解封和到期状态必须进入共享 Redis/数据库;本机静态字典只能做缓存。Redis 可用且共享 block 不存在就是权威已解封,节点必须删除本机旧 block,禁止把旧状态回写复活;Redis 不可用时才允许本机降级。验收要让同一出口分别命中至少两个 API 节点,覆盖普通阈值、受信读取、伪造 Header、手动解除和自动到期。
@@ -373,7 +374,7 @@ try {
373
374
  - [ ] 所有数据库查询使用参数化(`_Where` 或 `@p0`)
374
375
  - [ ] 把 Token 认证与表/菜单/操作授权分开;列表/写入显式菜单严格精确校验,历史唯一详情只校验同表已授权菜单访问权
375
376
  - [ ] 当前表前端 facade 自动注入真实菜单,跨表不借用主菜单;可信后端 V8 由服务端标记且不要求菜单
376
- - [ ] 敏感平台表仅限 `Level >= 9999` 的可信管理链路,Import/Export 必须携带真实菜单并有专项权限
377
+ - [ ] 平台表按管理员专用、只读委托、按角色管理三级执行;全部拒绝匿名,Import/Export 必须携带真实菜单并有专项权限
377
378
  - [ ] 菜单 `SqlWhere` / `SqlJoin` 覆盖真实列表、计数和导出查询;详情只校验同表菜单访问;主表新增/修改/删除/导入只按专项操作权限,不把查询范围带入写 SQL;行级写业务限制由后端 V8/接口引擎校验
378
379
  - [ ] 授权缓存按租户使用 Redis `epoch` 和用户级快照;冷加载读主库,权限变更递增 `epoch`
379
380
  - [ ] 关键操作校验 `V8.CurrentUser` 权限
@@ -392,7 +393,7 @@ try {
392
393
  - 触发场景:`Level=9999` 管理员在表单设计器保存时,外层 `UptFormData`、内部 `UptDiyFieldList` 或 `AddDiyField` 返回 `NoAuth`;无 HTTP 用户的升级任务写 `sys_apiengine/sys_menu/diy_field` 也被拒绝。
393
394
  - 根因:Redis 保留了旧结构的授权快照,新字段反序列化为 `0/false`;同时更新前旧记录读取或动态新增字段把已校验上下文降成裸 `JObject` / 匿名参数,丢失管理员或可信服务端来源。批量字段保存若逐字段调用完整 CRUD,还会把授权、V8 和缓存工作放大 N 倍。
394
395
  - 通用规则:授权快照使用独立契约版本;内部嵌套调用必须显式传递原始客户端管理员上下文,或由真正的服务端任务构造不可伪造的强类型可信参数,不能依赖 `_InvokeType` 或 CLR/JObject 猜测。
395
- - 自动化检查:预置缺少新字段的旧 Redis 快照后验证新版本 Key 不命中;分别覆盖管理员设计器批量字段保存/新增字段、普通用户直接写保护表被拒绝、升级程序可信写入成功,以及 HTTP JSON 伪造可信字段仍失败。
396
+ - 自动化检查:预置缺少新字段的旧 Redis 快照后验证新版本 Key 不命中;分别覆盖管理员设计器批量字段保存/新增字段、普通用户直接写管理员专用表被拒绝、只读委托表写入被拒绝、`mic_print` 有权读取成功、升级程序可信写入成功,以及 HTTP JSON 伪造可信字段仍失败。
396
397
 
397
398
  ## 浏览器访问密钥
398
399
 
@@ -108,6 +108,7 @@ MD5/SHA1 仅为兼容摘要;任何摘要都不能直接作为新密码存储
108
108
  | HTTP | `V8.Http.Get/Post/Patch`、`GetResponse/PostResponse/PatchResponse` 及真实 `*Async` 版本 |
109
109
  | 图片 | `V8.Image.Create/Merge/Overlay/Watermark/Resize/Crop/Rotate/Flip/Draw/Convert/GetInfo/CreateQRCode` |
110
110
  | Office | `V8.Office.ExportExcel/ExcelToList/ExportWord/ExportPowerPoint/SendEmail` |
111
+ | OCR | `await V8.OCR.Recognize({...})`;服务端租户配置与调用参数隔离,详见 `ocr-engine` |
111
112
  | 文件 | `V8.HDFS`、`V8.Method.Upload/GetPrivateFileUrl` |
112
113
  | MQ | `V8.MQ.SendMsg` |
113
114
  | 短信 | `V8.Sms.Send` |
@@ -48,7 +48,7 @@ AI 在用户本机启动 Node.js、Vite、Webpack、dotnet build、Java、Docker
48
48
 
49
49
  - 启动前检查物理内存总量、当前占用率、可用内存,并检查是否已有同类 dev server/构建进程。已有可复用服务时禁止重复启动。
50
50
  - 默认只允许一个高资源任务运行;显式限制 Worker/并发数,优先按项目、包、模块、测试分组或文件分片执行,不得并行启动多个全量构建。
51
- - 启动重任务时仍以保留至少 `max(6 GB, 物理内存的 20%)` VS Code、Codex 和操作系统为规划目标;低于该目标时应停止新增并发重任务、优先降级为定向验证,但不再把它作为强制终止已启动任务的硬门限。机器总内存占用达到 95% 时,立即终止 AI 启动的重任务及其子进程,不得等待 OOM。
51
+ - 启动重任务前按“当前阶段进程树预算 + 系统安全余量”判断:阶段预算优先采用实测峰值;尚无实测时,用已限制的堆/容器上限加明确的原生进程、Worker 与缓冲开销。系统安全余量取 `max(1.5 GB, 物理内存的 5%)`,不得再按固定 20% 将大内存机器的启动门槛线性放大。顺序执行的阶段分别计算,禁止把不会并发的阶段峰值相加。机器总内存占用达到 95% 时,立即暂停或终止 AI 启动的重任务及其子进程,不得等待 OOM。
52
52
  - 禁止将 `--max-old-space-size`、JVM heap、Docker memory 或类似上限设为接近物理内存总量。除非用户明确授权独占构建窗口,单个 AI 启动的进程树不得持续占用超过物理内存的 25%。
53
53
  - 后台/长任务必须记录根 PID、子进程、启动时间和独立日志,每 15-30 秒监测一次进程树内存与全机可用内存。任务失败、中断或达阈值时必须停止整个子进程树,不得遗留孤儿 Node/dotnet/Java 进程。
54
54
  - 全量构建无法在上述阈值内完成时,先停止并改用定向 lint、类型检查、按模块构建或按测试文件验证。如仍必须进行全量验收,应明确报告资源瓶颈,交由 CI/专用构建机或经用户明确同意的独占时段执行,禁止在用户正在使用的 VS Code 会话里硬跑。
@@ -157,6 +157,14 @@ AI 新增或修改 Microi 配置文件时,凡是面向开发者、部署人员
157
157
  - 如果配置面向海外交付,才可以在中文说明后补充英文括注;不要整段只写英文。
158
158
  - 修改配置说明后,必须确认 JSON/YAML 仍可解析,不能因为中文标点或注释方式导致配置文件失效。
159
159
 
160
+ ## 后端 API 配置白名单与 SaaS 单一事实源(强制)
161
+
162
+ - `Microi.net.Api` 的 `AppSettings` 与同名容器环境变量只允许:`OsClient`、`OsClientType`、`OsClientNetwork`、`OsClientDbType`、`OsClientDbConn`、`OsClientRedisHost`、`OsClientRedisPort`、`OsClientRedisPwd`、`OsClientRedisDataBase`、`OsClientDbMongoConn`。
163
+ - 除上述十项外,任何业务开关、重试、超时、限额、安全策略、密钥或可执行文件路径通常都必须进入 SaaS 引擎 `sys_osclients` 的合适 Tab,并提供幂等升级、默认值、缓存刷新、敏感字段脱敏和子租户隔离。官方 License 信任链是固定例外:恢复重试次数/间隔使用代码常量,签发私钥固定只读挂载 `/app/microi_private.pem`,不得建立对应 SaaS 字段。禁止新增 `MICROI_*`、`DOS_ORM_*`、额外 `AppSettings` 节点或通用动态环境变量读取。
164
+ - `ASPNETCORE_*`、`DOTNET_*` 是框架宿主配置;`PW_*`、MCP、构建、安装器和发布脚本变量只服务各自工具进程。它们不能成为生产 API 的业务配置入口。
165
+ - 修改后必须用源码测试扫描生产 `.cs`、API `appsettings.json` 及在线/离线 Compose,精确断言十项白名单。不能用注释约定代替自动化守卫。
166
+ - 一键安装恢复客户旧库时只允许定位精确主租户三元组;缺失则幂等创建,重复则停止,不能批量重写其它子租户。新主租户行不得持久化数据库、MongoDB 或 Redis 连接,安装器对 MinIO/OCR 等业务配置的后续更新也必须带同一三元组、活动状态条件并做唯一回读。
167
+
160
168
  ## 多语言优先约定
161
169
 
162
170
  Microi 平台默认支持多语言。AI 修改 `Microi.Client`、`Microi.Server`、`Microi-V8-Engine`、MCP 建模数据、菜单按钮、接口引擎或表单 V8 事件时,凡是用户可见文字都必须先考虑多语言,不要把中文提示、按钮名、Tab 名、菜单名、字段名、Toast/Msg 等硬写死后结束任务。
@@ -317,29 +325,13 @@ AI 在本地启动后端、跑 Playwright、做登录态页面截图或调用需
317
325
 
318
326
  1. 读取 `Microi.Server/Microi.net.Api/.microi-local`,得到当前环境名,例如 `<Environment>`。
319
327
  2. 读取 `Microi.Server/Microi.net.Api/appsettings.<Environment>.json`,或测试脚本传入的 `PW_APPSETTINGS_PATH`。
320
- 3. `DevLoginBypass.Accounts` 中按 `OsClient` 匹配账号密码;没有匹配时使用 `DevLoginBypass.DefaultAccount` / `DefaultPassword`。
321
- 4. 如果环境变量 `MICROI_OSCLIENT`、`PW_OS_CLIENT`、`PW_TEST_ACCOUNT`、`PW_TEST_PASSWORD` 已显式设置,以环境变量为准。
322
- 5. `appsettings.*.json`、`.microi-local`、Token、数据库连接串、Redis 密码都视为本地敏感配置。可以读取并用于自动化,但最终回复、日志摘要和测试报告中不得输出真实值,只能写 `<redacted>`、`本地配置账号` 或 `本地配置凭据`。
323
-
324
- ## DevLoginBypass 多租户约定
325
-
326
- 当本地 E2E/API 自动化需要对多个租户免验证码登录时,在当前生效的 `appsettings.{Environment}.json` 中配置 `DevLoginBypass:Accounts`:
327
-
328
- ```json
329
- "DevLoginBypass": {
330
- "Enabled": true,
331
- "SkipCaptcha": true,
332
- "OnlyLoopback": true,
333
- "DefaultAccount": "admin",
334
- "DefaultPassword": "<default-password>",
335
- "Accounts": [
336
- { "OsClient": "<tenant-a>", "Account": "<account>", "Password": "<password>" },
337
- { "OsClient": "<tenant-b>", "Account": "<account>", "Password": "<password>" }
338
- ]
339
- }
340
- ```
328
+ 3. 测试账号密码只从用户本轮明确提供、受保护的测试进程变量 `PW_TEST_ACCOUNT` / `PW_TEST_PASSWORD`、CI Secret 或既有安全登录态取得;不得把凭据写入 `appsettings.*.json`、源码或测试报告。
329
+ 4. `MICROI_OSCLIENT`、`PW_OS_CLIENT` 等只属于自动化工具进程,不是 API 生产环境变量;显式设置时可用于选择测试租户。
330
+ 5. `.microi-local`、Token、数据库连接串、Redis 密码和测试凭据都视为本地敏感配置。最终回复、日志摘要和测试报告中不得输出真实值,只能写 `<redacted>`、`本地配置账号` 或 `本地配置凭据`。
341
331
 
342
- 本地旁路配置必须保留 `OnlyLoopback=true`,并且只允许在 `ASPNETCORE_ENVIRONMENT/DOTNET_ENVIRONMENT=Development` 且请求来自本机回环地址时生效。自动化脚本可传 `_AutomationTestLogin=true` 或本地 Dev Key 来跳过验证码,但任何方式都不能跳过密码校验;`Pwd="_DEV_BYPASS_"` 只能在本机 DevLoginBypass 下替换为本地配置密码后继续走真实密码校验。远端自动化若要免验证码,必须先在 `sys_config.AutoTestSkipCaptcha`(中文 Label:允许自动化测试登录时绕开验证码)开启开关,再传 `_AutomationTestLogin=true`,仍使用真实账号密码。不要把具体项目租户名或密码写进 skill;真实值只放环境配置文件。
332
+ ## 自动化登录约定
333
+
334
+ 本地和远端 E2E 统一传真实 `Account` / `Pwd`。需要跳过图形验证码时,只能在目标租户 `sys_config.AutoTestSkipCaptcha=true` 后传 `_AutomationTestLogin=true`;它只跳过验证码,绝不能绕过密码校验。禁止恢复 `DevLoginBypass`、`X-Microi-Dev-Key`、`_DEV_BYPASS_` 或让脚本自动改写后端 `appsettings`。测试完成后不持久化账号密码。
343
335
 
344
336
  ## V8 远端/本地同步收尾约定
345
337
 
@@ -477,3 +469,10 @@ AI 只要修改了 `Microi.Server/**` 下会影响 `Microi.net.Api` 运行结果
477
469
  - 根因:发布脚本把三个 registry 都视为同一个全局硬门禁,并把最容易受账号、scope 和 2FA 影响的 npm 放在扩展市场之前,没有区分必选目标、可选目标和严格发布模式。
478
470
  - 通用规则:默认发布按目标隔离;先完成并回读必选目标,再独立尝试可选目标。可选目标失败应保留同版本不可变产物并输出补发入口;只有显式严格模式才要求所有目标预检通过后继续。
479
471
  - 自动化检查:模拟 npm 未登录、scope 404 和 npm publish 非零退出,断言两个扩展市场的发布调用与回读仍会执行;另测严格模式在版本递增前停止,补发命令不递增版本且复用同版本产物。
472
+
473
+ ### 复盘:npm 已接收新版本但公共回读短暂 404
474
+
475
+ - 触发场景:`npm publish` 已成功返回,npmjs.com 包页面也已出现新包或新版本,但紧随其后的 `npm view <package>@<version> version` 在数十秒内连续返回 E404,联合发布脚本因此把成功发布误报为失败。
476
+ - 根因:新 scope/新版本在 npm 网站、写入节点和公共 registry 读取节点之间存在短暂传播窗口;固定少量、短间隔轮询不足以区分“尚未发布”和“已经接收但尚未公开传播”。
477
+ - 通用规则:发布命令成功和公共回读确认必须作为两个阶段记录。npm 新版本回读使用 `--prefer-online` 和分钟级有限重试;重试结束仍为 E404 时标记 `pending-propagation`,禁止自动重发同一不可变版本,并提供独立只读验证命令稍后确认。只有发布命令本身失败且公共 registry 也始终不存在时,才进入补发流程。
478
+ - 自动化检查:模拟 `npm publish` 成功后前几次 `npm view` 返回 E404、随后返回期望版本,断言不会重复发布;再模拟重试窗口结束仍为 E404,断言输出待传播状态和只读验证命令,而不是提示重新上传同一版本。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microi.net/cli",
3
- "version": "4.6.2",
3
+ "version": "4.6.7",
4
4
  "description": "Microi吾码 AI 开发命令行:服务器连接、登录、AI/MCP 初始化与 V8 资源同步",
5
5
  "license": "MIT",
6
6
  "homepage": "https://microi.net/doc/v8-engine/vs-code-plugin.html",