@microi.net/cli 5.6.0 → 5.6.1

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 (54) hide show
  1. package/.codebuddy-plugin/marketplace.json +2 -2
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.mcp.json +1 -1
  5. package/.workbuddy-plugin/marketplace.json +2 -2
  6. package/.workbuddy-plugin/plugin.json +1 -1
  7. package/README.md +71 -71
  8. package/assets/build-meta.json +7 -7
  9. package/assets/feature-matrix.json +129 -129
  10. package/cordis.patch.yml +1 -1
  11. package/package.json +1 -1
  12. package/scripts/codex-marketplace.json +20 -20
  13. package/scripts/mcp-codex-stdio-adapter.js +189 -189
  14. package/scripts/mcp-server.js +99 -99
  15. package/scripts/mcp-trae-windows-launcher.cmd +21 -21
  16. package/scripts/microi-cli-mcp.js +7 -7
  17. package/scripts/microi-cli.js +39 -36
  18. package/scripts/microi-codex-broker.js +447 -447
  19. package/scripts/microi-codex-router.js +582 -582
  20. package/scripts/microi-skills.meta.json +225 -222
  21. package/skills/.microi-skills-version.json +2 -2
  22. package/skills/.progressive-disclosure-manifest.json +37 -37
  23. package/skills/README.md +1 -1
  24. package/skills/app-store/SKILL.md +199 -182
  25. package/skills/dos-orm/SKILL.md +96 -81
  26. package/skills/message-notification/SKILL.md +152 -126
  27. package/skills/microi-client-frontend/SKILL.md +11 -5
  28. package/skills/microi-client-frontend/references/progressive-01-3-/345/212/250/346/200/201/346/214/211/351/222/256/347/263/273/347/273/237.md +2 -2
  29. package/skills/microi-client-frontend/references/progressive-02-8-/350/277/220/350/241/214/346/227/266/351/253/230/351/242/221/345/235/221/345/244/215/347/233/230.md +2 -2
  30. package/skills/microi-client-frontend/references/progressive-03-vue3-/345/211/215/347/253/257/345/276/256/346/234/215/345/212/241/345/256/277/344/270/273/350/247/204/345/210/231.md +2 -2
  31. package/skills/microi-codex/SKILL.md +48 -46
  32. package/skills/microi-docs-coverage/SKILL.md +133 -123
  33. package/skills/microi-docs-coverage/references/capability-map.md +2 -2
  34. package/skills/microi-form-engine/SKILL.md +77 -53
  35. package/skills/microi-microservice/SKILL.md +17 -16
  36. package/skills/microi-sso/SKILL.md +91 -85
  37. package/skills/microi-system-delivery/SKILL.md +5 -4
  38. package/skills/microi-system-delivery/references/progressive-02-/350/207/252/345/212/250/345/214/226/346/265/213/350/257/225/345/277/205/351/241/273/350/246/206/347/233/226/347/232/204/345/235/221.md +27 -22
  39. package/skills/module-engine/SKILL.md +17 -1
  40. package/skills/print-engine/SKILL.md +47 -39
  41. package/skills/scripts/sync-embedded-skills.mjs +46 -0
  42. package/skills/scripts/validate-progressive-disclosure.mjs +57 -52
  43. package/skills/system-observability/SKILL.md +16 -4
  44. package/skills/v8-api-config/SKILL.md +382 -378
  45. package/skills/v8-file-upload/SKILL.md +157 -154
  46. package/skills/v8-file-upload/references/progressive-01-/345/205/254/346/234/211/346/241/266-vs-/347/247/201/346/234/211/346/241/266.md +133 -133
  47. package/skills/v8-formengine-http/SKILL.md +19 -1
  48. package/skills/v8-security/SKILL.md +25 -25
  49. package/skills/v8-sql-query/SKILL.md +130 -128
  50. package/skills/v8-table-event/SKILL.md +29 -22
  51. package/skills/v8-table-event/references/progressive-01-informv8-js-/350/241/250/345/215/225/346/211/223/345/274/200/344/272/213/344/273/266.md +19 -19
  52. package/skills/v8-workflow/SKILL.md +54 -45
  53. package/skills/workspace-conventions/SKILL.md +259 -236
  54. package/skills/workspace-conventions/references/progressive-01-/347/211/210/346/234/254/346/233/264/346/226/260/346/227/245/345/277/227/344/277/235/346/212/244/350/247/204/345/210/231-/345/274/272/345/210/266.md +208 -208
@@ -77,16 +77,16 @@ AppKey 稳定且只含安全字符。`microi.routes.json` 是页面事实源,
77
77
  ### 线上目标在本地 Microi.Client 复现(强制)
78
78
 
79
79
  - 先在一次性隔离 browser context 打开线上友好路由并读取
80
- `window.__MICROI_RUNTIME_ENDPOINT__`;旧版回退读取页面全局、同源缓存和域名解析,禁止猜
81
- ApiBase/OsClient。
80
+ `window.__MICROI_RUNTIME_ENDPOINT__`;旧版回退读取页面全局、同源缓存和域名解析,禁止猜
81
+ ApiBase/OsClient。
82
82
  - 在新的独立 context 打开
83
83
  `http://localhost:61500/?OsClient=<encoded>&ApiBase=<encoded>#/micro-app/{MsKey}/{RoutePath}`。
84
84
  两个 URL 参数是当前页面最高优先级;初始化后回读同一个运行时对象确认命中目标。
85
85
  - 同源普通窗口共享 Token、CurrentUser、ApiBase、OsClient。并行不同目标必须一组目标一个
86
86
  Playwright `browser.newContext()` 或独立浏览器 Profile;多个无痕窗口不保证彼此隔离。
87
- - 本地复现、主框架宿主验收、远端运行产物和生产部署分别报告;本地通过不能替代发布后回读。
88
- - 新租户或历史还原库可能没有 `SysConfig.ApiBase`。微服务安装不得依赖该字段才能写入运行 HTML:按目标 `OsClient` 注入上下文,运行时使用同租户宿主的 `microApp.getData()` 或绑定该租户的 API 稳定入口,保留代理路径;不得沿用发布端上下文、跨租户宿主或把 HDFS/CDN 域名当成 API。独立 Web/UniApp 仍需有效的目标 API 配置。
89
- - 登录用户的角色等历史字段可能返回数组、JSON 字符串或空值。列表运算前必须归一化类型,并优先使用当前接口给出的结构化字段;验收应使用真实旧格式数据验证页面渲染及导航,不能只断言接口成功或没有 `pageerror`,还应检查 Vue 捕获到的控制台异常。
87
+ - 本地复现、主框架宿主验收、远端运行产物和生产部署分别报告;本地通过不能替代发布后回读。
88
+ - 新租户或历史还原库可能没有 `SysConfig.ApiBase`。微服务安装不得依赖该字段才能写入运行 HTML:按目标 `OsClient` 注入上下文,运行时使用同租户宿主的 `microApp.getData()` 或绑定该租户的 API 稳定入口,保留代理路径;不得沿用发布端上下文、跨租户宿主或把 HDFS/CDN 域名当成 API。独立 Web/UniApp 仍需有效的目标 API 配置。
89
+ - 登录用户的角色等历史字段可能返回数组、JSON 字符串或空值。列表运算前必须归一化类型,并优先使用当前接口给出的结构化字段;验收应使用真实旧格式数据验证页面渲染及导航,不能只断言接口成功或没有 `pageerror`,还应检查 Vue 捕获到的控制台异常。
90
90
 
91
91
  迁移 Vue2 定制页到独立 Vite 微服务时,不得假设宿主会提供 Tailwind/UnoCSS 等原子类;
92
92
  页面依赖的宽高、颜色、间距、响应式和打印/下载样式必须由组件自身的语义 class 明确声明,
@@ -116,7 +116,8 @@ AppKey 稳定且只含安全字符。`microi.routes.json` 是页面事实源,
116
116
 
117
117
  - 创建/更新元数据:`microi_create_microservice`。
118
118
  - 同步私有源码:`microi_sync_microservice_source`;本地工程必须优先传 `directory`,不构造 Base64 文件数组。
119
- - 真实编译目录优先 `microi_publish_application_directory_stream` 流式发布。
119
+ - 真实编译目录优先 `microi_publish_application_directory_stream` 流式发布。
120
+ - `changeSummary` 是最多 2000 字符的版本说明文本,不能传对象;v3 在首次 stage 创建版本时存入 `mci_ai_app_version.ChangeSummary`,后续同请求重放和恢复保留原说明。历史节点若发布完成但摘要仍为空,应报告后端版本缺口,不得把编译文件验证成功等同于日志验收通过。
120
121
  - 每次创建、修改、升级或重新发布微服务,必须先为目标精确 `AppVersion` 在 `sys_microistore_changelog` 写入完整日志,再同步源码、stage、finalize 或制作商城包。日志必须关联真实 `StoreId`,包含 `Title / ChangeType / Content / ReleaseTime`;流式发布显式传入与日志含义一致的非空 `changeSummary`。发布后同时回读商城日志、`mci_ai_app_version.ChangeSummary` 与包内 `PackageInfo.ChangeLog`;缺失或版本不一致必须失败关闭。
121
122
  - 发布动作必须明确区分两种模式:默认“源码+编译产物”先把完整工程同步到私有桶并逐文件回读 SHA-256,再把 `dist` 流式发布到公有桶;显式“仅编译产物”只更新公有桶,必须在界面中告知其他用户仍会拉取上一次私有源码,禁止暗示源码已同步。
122
123
  - 私有源码同步使用 `ReplacePrivateSourceOnly` 精确清理过期源码;兼容调用可以继续接受 `replace`,但实现不得用旧式全表 `Replace=true` 删除同一应用的公有运行产物元数据。
@@ -266,16 +267,16 @@ window.addEventListener('appstate-change', (event) => {
266
267
  吾码宿主独立预览时的回退值。不得直接写死 `min-height: 100vh`、`calc(100vh - 100px)` 等
267
268
  只适配浏览器视口或某个主站布局的高度,否则嵌入 TagsView、弹窗或移动端时会被裁剪或产生双滚动。
268
269
 
269
- 菜单型微服务必须只有一个纵向滚动所有者:宿主外层隐藏溢出,`<micro-app>` 边界提供默认
270
- `overflow-y:auto` 兜底。普通长页面保持自然高度即可;子应用若需要 sticky 工具栏、虚拟列表等
271
- 内部滚动,则自己的滚动容器必须具有基于 `--micro-app-available-height` 的确定高度和
272
- `overflow-y:auto`。真实子滚动容器会把内容约束在边界内,框架兜底条自动不出现;只给自动高度
273
- 根节点写 `overflow:auto` 不算自滚动容器。验收需分别覆盖框架兜底和子应用自滚动,断言任一时刻
274
- 只有一个 `scrollHeight > clientHeight` 的纵向滚动所有者。
275
-
276
- 相机扫描、收银称台、扫码、签到等高频工具页必须另设“一屏操作”断点:在宿主下发的可用高度内同时显示输入/取景、主操作、处理状态、核心结果和业务要求的最近记录入口,完成主流程不得依赖页面纵向滚动;竖屏优先上下紧凑分区,短横屏切换左右或多列分区。历史明细可在自己的卡片内滚动,但不能隐藏错误、当前结果、暂停/继续/恢复等关键动作。桌面应占满宿主可用宽度,通过有界预览容器和 `object-fit` 防止上传竖图把页面撑高。真实浏览器验收至少覆盖一个常见手机竖屏和一个短横屏,并断言根容器不溢出、取景区/结果区/关键历史入口同时在视口内、平台底部菜单与安全区不遮挡按钮;桌面断点必须回归,不能为移动端压缩破坏 PC 布局。
277
-
278
- 摄像头微服务必须把媒体异步生命周期当作竞态处理:每次打开、关闭和卸载都递增操作令牌或取消版本;`getUserMedia`、设置 `srcObject`、`video.play()` 每个异步边界返回后都要验证令牌仍有效。关闭时先同步切换 UI 状态并使旧操作失效,再停止全部轨道和清空 `srcObject`,防止旧 Promise 把已关闭状态反写为开启。条件渲染或点击后会移动的关闭按钮可在 `pointerdown` 捕获阶段完成停止,再短时去重后续 `click`。自动识别可以按画面指纹去重,但用户点击主识别按钮必须能强制处理当前帧;同一页面不要再放置语义重复且易被宿主底栏遮挡的第二个“再次识别当前画面”。
270
+ 菜单型微服务必须只有一个纵向滚动所有者:宿主外层隐藏溢出,`<micro-app>` 边界提供默认
271
+ `overflow-y:auto` 兜底。普通长页面保持自然高度即可;子应用若需要 sticky 工具栏、虚拟列表等
272
+ 内部滚动,则自己的滚动容器必须具有基于 `--micro-app-available-height` 的确定高度和
273
+ `overflow-y:auto`。真实子滚动容器会把内容约束在边界内,框架兜底条自动不出现;只给自动高度
274
+ 根节点写 `overflow:auto` 不算自滚动容器。验收需分别覆盖框架兜底和子应用自滚动,断言任一时刻
275
+ 只有一个 `scrollHeight > clientHeight` 的纵向滚动所有者。
276
+
277
+ 相机扫描、收银称台、扫码、签到等高频工具页必须另设“一屏操作”断点:在宿主下发的可用高度内同时显示输入/取景、主操作、处理状态、核心结果和业务要求的最近记录入口,完成主流程不得依赖页面纵向滚动;竖屏优先上下紧凑分区,短横屏切换左右或多列分区。历史明细可在自己的卡片内滚动,但不能隐藏错误、当前结果、暂停/继续/恢复等关键动作。桌面应占满宿主可用宽度,通过有界预览容器和 `object-fit` 防止上传竖图把页面撑高。真实浏览器验收至少覆盖一个常见手机竖屏和一个短横屏,并断言根容器不溢出、取景区/结果区/关键历史入口同时在视口内、平台底部菜单与安全区不遮挡按钮;桌面断点必须回归,不能为移动端压缩破坏 PC 布局。
278
+
279
+ 摄像头微服务必须把媒体异步生命周期当作竞态处理:每次打开、关闭和卸载都递增操作令牌或取消版本;`getUserMedia`、设置 `srcObject`、`video.play()` 每个异步边界返回后都要验证令牌仍有效。关闭时先同步切换 UI 状态并使旧操作失效,再停止全部轨道和清空 `srcObject`,防止旧 Promise 把已关闭状态反写为开启。条件渲染或点击后会移动的关闭按钮可在 `pointerdown` 捕获阶段完成停止,再短时去重后续 `click`。自动识别可以按画面指纹去重,但用户点击主识别按钮必须能强制处理当前帧;同一页面不要再放置语义重复且易被宿主底栏遮挡的第二个“再次识别当前画面”。
279
280
 
280
281
  加载/协议/运行时异常只由宿主兜底显示;子应用业务错误标记 `handled=true` 后宿主不得重复提示。
281
282
  加载失败页至少显示 AppKey、PageKey、路由、版本、入口、HTTP 状态、发布状态、资产来源、挂载状态和安全原因码,并提供重试、返回与复制诊断。
@@ -1,85 +1,91 @@
1
- ---
2
- name: microi-sso
3
- description: 设计、实现、配置、迁移、发布和验收 Microi 吾码双向 SSO 身份联邦。用于外部系统通过 OIDC、SAML2、CAS 登录吾码,或吾码作为 OIDC OP、SAML IdP、CAS Server 集成其它系统,以及 diy_sso、账号/角色映射、Secret/证书、官方应用 app.microi.sso 和真实伙伴联调。
4
- ---
5
-
6
- > **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
7
-
8
- # Microi SSO 身份联邦
9
-
10
- ## 何时使用
11
-
12
- 以下任务必须使用本 Skill:
13
-
14
- - Keycloak、Entra ID、ADFS、CAS Server、企业统一身份中心登录吾码。
15
- - 吾码账号登录 ERP、OA、BI、门户或其它第三方系统。
16
- - 修改 `diy_sso`、SSO 登录页、OIDC/SAML/CAS 接口引擎端点、Claim/角色映射或旧 Token SSO。
17
- - 发布、安装、升级或验收官方商城应用 `app.microi.sso`。
18
-
19
- 固定 Gitee、微信、GitHub 登录与 Passkey/TOTP 仍以 `v8-security` 为主;当需求是可配置企业身份联邦时转到本 Skill。
20
-
21
- ## 先读取什么
22
-
23
- 按任务读取,不要一次加载全部参考:
24
-
25
- - 外部身份源登录吾码:读 [references/inbound.md](references/inbound.md)。
26
- - 吾码给第三方提供登录:读 [references/outbound.md](references/outbound.md)。
27
- - 表字段、Secret、证书、安全与存量迁移:读 [references/configuration-and-security.md](references/configuration-and-security.md)。
28
- - 测试、商城发布、目标安装与交付结论:读 [references/acceptance.md](references/acceptance.md)。
29
-
30
- 源码修改前同时完整读取 `workspace-conventions/SKILL.md`;认证、DiyToken、秘密或权限任务再读 `v8-security/SKILL.md`;商城任务再读 `app-store/SKILL.md`。
31
-
32
- ## 不可破坏的事实
33
-
34
- 1. DiyToken 是进入吾码后的唯一平台会话入口。SSO 认证成功后签发 DiyToken,不创建第二套用户/权限 Token。
35
- 2. 吾码向外提供的 OIDC/SAML/CAS 协议票据独立、短期、可撤销,绝不把 DiyToken 给第三方。
36
- 3. `OsClient + ConnectionKey + Subject` 是外部主体隔离边界;所有回调、票据、缓存和审计都绑定租户。
37
- 4. 新连接只用 OIDC、SAML2、CAS;URL Token 是迁移兼容项,不是推荐协议。
38
- 5. Redirect URI、ACS、Destination、Audience 和 CAS service 必须精确匹配;生产端点必须 HTTPS。
39
- 6. Secret/私钥只存 `mci_system_setting` 的受控值;`diy_sso` 只存 Setting Key。匿名接口只返回登录入口白名单投影。
40
- 7. 外部角色、邮箱或昵称不能直接获得管理员权限。默认 `BoundOnly`,JIT 必须显式默认角色、唯一性、回收和审计。
41
- 8. HTTP 200、构建成功、商城任务入队或包可下载都不是完整 SSO 验收。
42
- 9. SSO 公开 HTTP 路由和业务逻辑都必须由接口引擎承载:连接投影、绑定/JIT、角色与 Claim 映射、审计、登录完成、租户扩展以及 OIDC/SAML/CAS 路由不得重新写进 Controller。只有协议编解码、签名验签、Secret/私钥隔离、一次性票据与 DiyToken 等可信原子可以保留在独立 `Microi.SSO` 类库,并且只能由精确官方 Managed Key 调用。
43
- 10. 客户端调用固定应用接口必须优先使用 `/apiengine/{ApiEngineKey}?OsClient=`,让系统日志/监控按真实接口引擎归因;新版宿主即使接口尚未安装也会返回结构化缺失错误。`/api/ApiEngine/Run` 只保留给无法预知 Key 的旧版兼容调用,禁止新增固定业务依赖;同样禁止调用已删除的 `/api/Sso/Capabilities` 等定制路由。
44
-
45
- ## 标准工作流
46
-
47
- 1. 识别方向、协议、租户、身份伙伴、用户生命周期、退出和密钥/证书责任人。
48
- 2. 读取当前 `diy_sso` Schema、菜单、真实连接和已部署 Server/Client 版本;不要从旧截图猜测。
49
- 3. 先选标准协议和安全 profile,再配置字段、Secret/证书 Key、Claim/角色映射。
50
- 4. 先把业务编排写成可随应用发布的接口引擎;仅当现有 V8 无法安全完成底层原子能力时,才扩展最小 `V8.Method`,并把调用权限制到精确官方接口 Key。禁止把整个流程放进新 Controller,也禁止让原子方法接受任意租户、回调地址或 Secret。
51
- 5. 分别验证源码/单测、后端构建、前端构建、运行时端点、浏览器 UI、真实伙伴和多节点。
52
- 6. 平台级资源通过官方应用包发布;官方源用 `microi_itdos` 更新并发布,普通目标租户走商城安装/升级任务。
53
- 7. 最终明确已验证与未验证边界,尤其是“配置包已发布”与“运行时代码已部署”的区别。
54
-
55
- ## 官方应用合同
56
-
57
- - AppId:`app.microi.sso`
58
- - 名称:`SSO 身份联邦`
59
- - 资源:`diy_sso`、`/system/sso`、字段/布局/视图、34 个 Managed 核心接口引擎和 1 个 CreateIfMissing 租户 Hook;其中 24 个 `sso_http_*` 接管原 SSO Controller 的全部公开路由,不发布用户绑定、连接实例、Secret、证书或示例账号。
60
- - `ResourcePolicies.ApiEngines` 必须逐 Key 显式声明。核心使用 `Managed/Platform`;`sso_event_hook` 使用 `CreateIfMissing/Tenant`,安装后永不被官方覆盖。
61
- - 官方 `iTdos` 是发布源,保护性拒绝安装属于正确行为;普通租户安装才必须轮询到 `Succeeded`。
62
-
63
- ## C# 与接口引擎责任线
64
-
65
- 接口引擎固定承载:
66
-
67
- - `sso_capabilities`、`sso_legacy_capabilities` 的匿名白名单投影;
68
- - `sso_connection_runtime`、`sso_user_runtime` 的内部最小投影;
69
- - `sso_resolve_federated_identity` 的绑定、JIT 与角色映射;
70
- - `sso_outbound_claims`、`sso_protocol_event`、`sso_event_hook`;
71
- - `sso_complete_login`、`sso_rotate_client_secret`、`sso_legacy_token_login` 的业务编排。
72
- - 24 个 `sso_http_*` Managed 端点:`/api/Sso/Begin`、`/api/Sso/CompleteAuthorization`、OIDC 回调/Discovery/JWKS/Authorize/Token/UserInfo/Introspect/Revoke/Logout、CAS 回调/Login/Validate/Logout、SAML Begin/ACS/Login/Complete/Metadata/Logout。
73
-
74
- C# 只保留:
75
-
76
- - `Microi.SSO` 的 `SsoProtocolRuntime` 中 OIDC/SAML/CAS 报文编解码、签名验签和协议响应构造;该类型不是 MVC Controller、没有 Route/Http 特性,不直接拥有公开地址;
77
- - Secret、私钥、证书和协议 Token 的可信隔离;
78
- - 高熵一次性 code/ticket、重放保护和 DiyToken 签发;
79
- - 仅允许精确 Managed Key 调用的 `RunSsoProtocol`、`CreateFederatedUser`、`CreateSsoLoginTicket`、`CompleteSsoLogin`、`RotateSsoClientSecret` 原子。
80
-
81
- 公开协议路由使用 `ApiAddress`(包括 `{OsClient}`、`{ConnectionKey}` 路径模板)和 `ResponseType=HTTP`。接口引擎可以返回状态码、Content-Type、XML/纯文本、重定向和安全响应头;`Set-Cookie` 等高风险响应头只能来自 `RunSsoProtocol` 签名的可信响应。不得因为协议需要原始 HTTP 而恢复任何 `Sso*Controller`。
82
-
83
- 新增 SSO 需求先判断是否只需修改上述接口引擎。只有缺少不可伪造、不可泄露的底层原子时才增加 V8 方法;增加后同时更新应用 `RequiredPlatformCapabilities`、后端文档、测试与最低版本。
84
-
85
- 详细用户文档:`microi.doc/docs/doc/more/sso.md`。
1
+ ---
2
+ name: microi-sso
3
+ description: 设计、实现、配置、迁移、发布和验收 Microi 吾码双向 SSO 身份联邦。用于外部系统通过 OIDC、SAML2、CAS 登录吾码,或吾码作为 OIDC OP、SAML IdP、CAS Server 集成其它系统,以及 diy_sso、账号/角色映射、Secret/证书、官方应用 app.microi.sso 和真实伙伴联调。
4
+ ---
5
+
6
+ > **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
7
+
8
+ # Microi SSO 身份联邦
9
+
10
+ ## 何时使用
11
+
12
+ 以下任务必须使用本 Skill:
13
+
14
+ - Keycloak、Entra ID、ADFS、CAS Server、企业统一身份中心登录吾码。
15
+ - 吾码账号登录 ERP、OA、BI、门户或其它第三方系统。
16
+ - 修改 `diy_sso`、SSO 登录页、OIDC/SAML/CAS 接口引擎端点、Claim/角色映射或旧 Token SSO。
17
+ - 发布、安装、升级或验收官方商城应用 `app.microi.sso`。
18
+
19
+ 固定 Gitee、微信、GitHub 登录与 Passkey/TOTP 仍以 `v8-security` 为主;当需求是可配置企业身份联邦时转到本 Skill。
20
+
21
+ ## 先读取什么
22
+
23
+ 按任务读取,不要一次加载全部参考:
24
+
25
+ - 外部身份源登录吾码:读 [references/inbound.md](references/inbound.md)。
26
+ - 吾码给第三方提供登录:读 [references/outbound.md](references/outbound.md)。
27
+ - 表字段、Secret、证书、安全与存量迁移:读 [references/configuration-and-security.md](references/configuration-and-security.md)。
28
+ - 测试、商城发布、目标安装与交付结论:读 [references/acceptance.md](references/acceptance.md)。
29
+
30
+ 源码修改前同时完整读取 `workspace-conventions/SKILL.md`;认证、DiyToken、秘密或权限任务再读 `v8-security/SKILL.md`;商城任务再读 `app-store/SKILL.md`。
31
+
32
+ ## 不可破坏的事实
33
+
34
+ 1. DiyToken 是进入吾码后的唯一平台会话入口。SSO 认证成功后签发 DiyToken,不创建第二套用户/权限 Token。
35
+ 2. 吾码向外提供的 OIDC/SAML/CAS 协议票据独立、短期、可撤销,绝不把 DiyToken 给第三方。
36
+ 3. `OsClient + ConnectionKey + Subject` 是外部主体隔离边界;所有回调、票据、缓存和审计都绑定租户。
37
+ 4. 新连接只用 OIDC、SAML2、CAS;URL Token 是迁移兼容项,不是推荐协议。
38
+ 5. Redirect URI、ACS、Destination、Audience 和 CAS service 必须精确匹配;生产端点必须 HTTPS。
39
+ 6. Secret/私钥只存 `mci_system_setting` 的受控值;`diy_sso` 只存 Setting Key。匿名接口只返回登录入口白名单投影。
40
+ 7. 外部角色、邮箱或昵称不能直接获得管理员权限。默认 `BoundOnly`,JIT 必须显式默认角色、唯一性、回收和审计。
41
+ 8. HTTP 200、构建成功、商城任务入队或包可下载都不是完整 SSO 验收。
42
+ 9. SSO 公开 HTTP 路由和业务逻辑都必须由接口引擎承载:连接投影、绑定/JIT、角色与 Claim 映射、审计、登录完成、租户扩展以及 OIDC/SAML/CAS 路由不得重新写进 Controller。只有协议编解码、签名验签、Secret/私钥隔离、一次性票据与 DiyToken 等可信原子可以保留在独立 `Microi.SSO` 类库,并且只能由精确官方 Managed Key 调用。
43
+ 10. 客户端调用固定应用接口必须优先使用 `/apiengine/{ApiEngineKey}?OsClient=`,让系统日志/监控按真实接口引擎归因;新版宿主即使接口尚未安装也会返回结构化缺失错误。`/api/ApiEngine/Run` 只保留给无法预知 Key 的旧版兼容调用,禁止新增固定业务依赖;同样禁止调用已删除的 `/api/Sso/Capabilities` 等定制路由。
44
+
45
+ ## 标准工作流
46
+
47
+ ### 存量 TokenLogin 与深链接兼容
48
+
49
+ - 已启用且精确配置 `ClientSsoApi=/api/SysUser/TokenLogin` 的旧连接使用平台原生 DiyToken 验证;不能错误过滤掉,也不能改走外部身份源的 `sso_legacy_token_login`。未配置、已停用、任意 URL 或非法 Token 必须继续拒绝。
50
+ - SSO 成功后优先保留用户指定的站内深链接及业务参数(如 `ShowClassicLeft/ShowClassicTop`);只有没有指定目标页才采用用户/系统默认首页。菜单/数据权限仍由正常路由守卫与后端校验。
51
+ - URL Token 使用后从地址栏和下一次 Router 导航对象同时清除,包括 hash query;单独 `history.replaceState` 不够,动态路由重匹配会把旧 query 带回。验收必须检查 TokenLogin 成功、目标页真实可见、URL 无凭据、布局参数保留;不能仅断言 HTTP 302 或 Code=1。
52
+
53
+ 1. 识别方向、协议、租户、身份伙伴、用户生命周期、退出和密钥/证书责任人。
54
+ 2. 读取当前 `diy_sso` Schema、菜单、真实连接和已部署 Server/Client 版本;不要从旧截图猜测。
55
+ 3. 先选标准协议和安全 profile,再配置字段、Secret/证书 Key、Claim/角色映射。
56
+ 4. 先把业务编排写成可随应用发布的接口引擎;仅当现有 V8 无法安全完成底层原子能力时,才扩展最小 `V8.Method`,并把调用权限制到精确官方接口 Key。禁止把整个流程放进新 Controller,也禁止让原子方法接受任意租户、回调地址或 Secret。
57
+ 5. 分别验证源码/单测、后端构建、前端构建、运行时端点、浏览器 UI、真实伙伴和多节点。
58
+ 6. 平台级资源通过官方应用包发布;官方源用 `microi_itdos` 更新并发布,普通目标租户走商城安装/升级任务。
59
+ 7. 最终明确已验证与未验证边界,尤其是“配置包已发布”与“运行时代码已部署”的区别。
60
+
61
+ ## 官方应用合同
62
+
63
+ - AppId:`app.microi.sso`
64
+ - 名称:`SSO 身份联邦`
65
+ - 资源:`diy_sso`、`/system/sso`、字段/布局/视图、34 个 Managed 核心接口引擎和 1 个 CreateIfMissing 租户 Hook;其中 24 个 `sso_http_*` 接管原 SSO Controller 的全部公开路由,不发布用户绑定、连接实例、Secret、证书或示例账号。
66
+ - `ResourcePolicies.ApiEngines` 必须逐 Key 显式声明。核心使用 `Managed/Platform`;`sso_event_hook` 使用 `CreateIfMissing/Tenant`,安装后永不被官方覆盖。
67
+ - 官方 `iTdos` 是发布源,保护性拒绝安装属于正确行为;普通租户安装才必须轮询到 `Succeeded`。
68
+
69
+ ## C# 与接口引擎责任线
70
+
71
+ 接口引擎固定承载:
72
+
73
+ - `sso_capabilities`、`sso_legacy_capabilities` 的匿名白名单投影;
74
+ - `sso_connection_runtime`、`sso_user_runtime` 的内部最小投影;
75
+ - `sso_resolve_federated_identity` 的绑定、JIT 与角色映射;
76
+ - `sso_outbound_claims`、`sso_protocol_event`、`sso_event_hook`;
77
+ - `sso_complete_login`、`sso_rotate_client_secret`、`sso_legacy_token_login` 的业务编排。
78
+ - 24 个 `sso_http_*` Managed 端点:`/api/Sso/Begin`、`/api/Sso/CompleteAuthorization`、OIDC 回调/Discovery/JWKS/Authorize/Token/UserInfo/Introspect/Revoke/Logout、CAS 回调/Login/Validate/Logout、SAML Begin/ACS/Login/Complete/Metadata/Logout。
79
+
80
+ C# 只保留:
81
+
82
+ - `Microi.SSO` 的 `SsoProtocolRuntime` 中 OIDC/SAML/CAS 报文编解码、签名验签和协议响应构造;该类型不是 MVC Controller、没有 Route/Http 特性,不直接拥有公开地址;
83
+ - Secret、私钥、证书和协议 Token 的可信隔离;
84
+ - 高熵一次性 code/ticket、重放保护和 DiyToken 签发;
85
+ - 仅允许精确 Managed Key 调用的 `RunSsoProtocol`、`CreateFederatedUser`、`CreateSsoLoginTicket`、`CompleteSsoLogin`、`RotateSsoClientSecret` 原子。
86
+
87
+ 公开协议路由使用 `ApiAddress`(包括 `{OsClient}`、`{ConnectionKey}` 路径模板)和 `ResponseType=HTTP`。接口引擎可以返回状态码、Content-Type、XML/纯文本、重定向和安全响应头;`Set-Cookie` 等高风险响应头只能来自 `RunSsoProtocol` 签名的可信响应。不得因为协议需要原始 HTTP 而恢复任何 `Sso*Controller`。
88
+
89
+ 新增 SSO 需求先判断是否只需修改上述接口引擎。只有缺少不可伪造、不可泄露的底层原子时才增加 V8 方法;增加后同时更新应用 `RequiredPlatformCapabilities`、后端文档、测试与最低版本。
90
+
91
+ 详细用户文档:`microi.doc/docs/doc/more/sso.md`。
@@ -16,7 +16,7 @@ Manifest 使用 `tables[].formBanner`;未显式配置时仍按字段类型选
16
16
  模块引擎或 `sys_menu`。逐步建模在字段完成后调用 `microi_configure_form_banner` 回读验收。
17
17
 
18
18
  <!-- microi-progressive:begin -->
19
- <!-- microi-progressive:chunk id=microi-system-delivery-000 sha256=088aaa73360be7d63b64ca476e884371140c7807d118c05f04d87a12de0c7701 -->
19
+ <!-- microi-progressive:chunk id=microi-system-delivery-000 sha256=acb3a22e04ba26f9fda584956e1eff38c9deebaeb827a7fc3a947abdac4214a6 -->
20
20
  ## 交付总原则
21
21
 
22
22
  1. **先事实源,后建模**:先读需求文档、截图、现有蓝图、数据库结构和菜单结构,形成业务蓝图;不要边猜边建表、边猜边写接口。
@@ -26,7 +26,8 @@ Manifest 使用 `tables[].formBanner`;未显式配置时仍按字段类型选
26
26
  5. **测试不等于接口能通**:交付前必须跑构建、接口契约、真实登录、核心写流程、截图、人眼复核、网络 404/5xx 守卫、远端同步状态。
27
27
  6. **生产数据谨慎优先**:涉及真实会员、余额、积分、订单、库存、卡券、收益、佣金、分润等资金/资产数据时,先确认服务器、租户、目标表、筛选条件和影响范围,再执行 MCP、接口引擎或 SQL 写操作。除用户明确要求清理/重置或必须修复错误数据外,不得擅自批量改真实数据。
28
28
  7. **默认按分布式交付**:任何后端能力都假设会有至少两个节点连接同一数据库和 Redis,并经历滚动升级、重复投递与节点硬重启。定时任务/消费者使用分布式租约且业务本身幂等;会话、票据和任务状态使用共享存储;新旧版本并存时采用“先扩展、后迁移、再收缩”。只在单节点验证通过不能视为完整交付。
29
- 8. **业务逻辑默认接口引擎、元数据升级默认应用商城**:先用低代码 CRUD/事件,再用接口引擎;只缺底层原子能力时先扩展 V8,只有协议/鉴权/密钥隔离/运行时内核才进入 C#。表、字段、Tab、菜单、权限、引擎、页面和任务等可打包资源不得新增 `Microi.Upgrade` 定制类;官方开发者通过 `microi_itdos` 发布官方应用后再由目标租户 MCP 更新,无官方权限时只升级自己的租户。
29
+ 8. **业务逻辑默认接口引擎、元数据升级默认应用商城**:先用低代码 CRUD/事件,再用接口引擎;只缺底层原子能力时先扩展 V8,只有协议/鉴权/密钥隔离/运行时内核才进入 C#。表、字段、Tab、菜单、权限、引擎、页面和任务等可打包资源不得新增 `Microi.Upgrade` 定制类;官方开发者通过 `microi_itdos` 发布官方应用后再由目标租户 MCP 更新,无官方权限时只升级自己的租户。
30
+ 9. **平台能力四项同步**:按 `workspace-conventions/SKILL.md` 首部识别创始人源码工作区;每项平台新增、增强或修复都逐项判断应用商城、官方中文文档、Skills、MCP 是否需要完善,交付时列出证据或无需修改的依据。复用已有 MCP 能力,不机械新增工具;镜像发布不等于客户部署,客户手动更新边界必须保留。
30
31
 
31
32
  <!-- /microi-progressive:chunk -->
32
33
  <!-- microi-progressive:chunk id=microi-system-delivery-001 sha256=174d99145946baf94a5eee934fc1453921fa7eed544b853f86be515582dc1343 -->
@@ -45,7 +46,7 @@ Manifest 使用 `tables[].formBanner`;未显式配置时仍按字段类型选
45
46
  最终回复必须按原始编号逐项汇总:哪些已实现、哪些未实现、是否通过全自动化测试、是否通过截图验证。不能只给总括性“都完成了”。如果某项没有测试或没有截图,必须明说“未覆盖/未截图”,并说明原因。
46
47
 
47
48
  <!-- /microi-progressive:chunk -->
48
- <!-- microi-progressive:chunk id=microi-system-delivery-002 sha256=5b929c8ab1cf588601fb0e27149ef05893bc5000eecf05e58961c4f785251e31 -->
49
+ <!-- microi-progressive:chunk id=microi-system-delivery-002 sha256=65a36ed6c448bbfb0226acedaaa334046be68278b2e89eb301cd99b4e141da3e -->
49
50
  ## 平台安全与存量兼容验收(强制)
50
51
 
51
52
  AI 零代码交付不能只验证管理员帐号和页面能打开。任何涉及 FormEngine、菜单、角色、子表、文件、SaaS 或登录协议的交付,都必须按以下服务端边界设计和验收:
@@ -70,7 +71,7 @@ AI 零代码交付不能只验证管理员帐号和页面能打开。任何涉
70
71
  ### 上传、私有文件与 SaaS
71
72
 
72
73
  - 上传业务默认值为 100 MB/文件、200 MB/次、10 文件、2 GB/帐号/日、20 GB/租户/日。有效值只按当前租户 `sys_osclients` → 代码安全默认值解析,租户可以提高或降低业务默认值;不得为上传开关、额度或硬上限新增 API 环境变量或 `appsettings` 节点。最终仍受代码内不可突破的灾难保护、HTTP/Multipart/Form 和反向代理上限约束。共享 Redis 原子预留,Redis 故障失败关闭。
73
- - 普通交互式上传强制私有桶,一级目录只能是 `file`、`img`、`avatar`、`editor`。可信后端 V8 仍受全局文件大小硬上限。
74
+ - 无字段上下文的普通交互式上传默认私有桶,一级安全目录为 `file`、`img`、`avatar`、`editor`;可由管理员在普通 `sys_config.HdfsUploadRules` 按真实角色授权业务目录与公有权限。通配符必须有界并先校验实际路径,保留目录与跨租户不能放行。标准表单仍按权威字段配置执行。可信后端 V8 仍受全局文件大小硬上限。
74
75
  - 普通客户端私有文件签名必须提交 `FormEngineKey`、`FormDataId`、`FieldId`、`SysMenuId` 并验证记录字段真实引用;不能把后端 `V8.Method.GetPrivateFileUrl({FilePathName})` 的可信调用方式照搬到浏览器。
75
76
  - Upgrade16 六个上传字段全部可空,空值保持老租户兼容;升级后回读字段元数据、租户值并刷新 SaaS 缓存。
76
77
  - `V8.OsClientModel` / `V8.SysConfig` 只使用脱敏副本,不返回整个对象。新租户不能复制主租户整条 `sys_osclients`;数据库、认证、Redis、存储、MQ/MQTT、搜索凭据必须独立创建或由服务端托管。
@@ -2,10 +2,15 @@
2
2
 
3
3
  > 按需读取;本文件由 SKILL.md 的原章节无损拆分。
4
4
 
5
- <!-- microi-progressive:chunk id=microi-system-delivery-006 sha256=08abeb4fe10b00cc374a316a939081860c64edd53f2a95161de9170dca13596a -->
5
+ <!-- microi-progressive:chunk id=microi-system-delivery-006 sha256=7e836f587a670e0be56abf880f6164a739ef1a31cfdb3864c09697a1980a3910 -->
6
6
  ## 自动化测试必须覆盖的坑
7
7
 
8
- ### 后端统一测试与发布门禁
8
+ ### 后端统一测试与发布门禁
9
+
10
+ - 每一个新功能或 Bug 修复必须提供对应回归用例;C# 进入 `Microi.Tests`,前端与资源/V8 测试进入统一 Node 自动发现范围。修复先保留失败证据,再跑成功、边界、异常、权限及旧格式兼容用例;不能仅添加字符串存在断言替代行为测试。
11
+ - `run-node-regressions.mjs` 自动发现 `Microi.Tests`、`Microi.Upgrade/Resource`、`Microi.Client/tests` 的确定性测试。新文件无法分类即失败;Playwright 用例必须走真实浏览器入口,不能用 Node 执行后把跳过当通过。所有计数需回读,0 用例、失败、取消、skipped、todo 均拒绝发布。
12
+ - 仅推送模式也必须取得发布锁并执行 Full。源码候选哈希、构建上下文哈希与 `release-artifact.mjs` 回执必须一致,且每个 Docker push 前再验证;无回执的历史产物不允许上传。文档专用模式不运行无关后端业务测试。
13
+ - 测试评审按身份/权限、多租户隔离、CRUD/事务、V8 同步异步与 `return/V8.Result`、存储安全、缓存/任务幂等、升级回滚、应用包幂等、浏览器登录/目标路由、发布脚本失败关闭分类盘点。新增能力补所在分类,不承诺一轮测试覆盖所有未知业务。
9
14
 
10
15
  - 吾码后端统一测试入口是 `Microi.Server/Microi.Tests/Microi.Tests.csproj`,
11
16
  不得重新创建 `Dos.Common.Tests`、`Dos.ORM.Tests` 等分散入口。
@@ -102,7 +107,7 @@
102
107
  ### 资源与隐私文件
103
108
 
104
109
  - 公开图片走 FileServer/CDN。
105
- - 私有图片必须先通过 `/apiengine/platform-private-file-url` 携带权威资源上下文换取短期签名 URL;签发失败时禁止回退裸路径。
110
+ - 私有图片必须先通过 `/apiengine/platform-private-file-url` 携带权威资源上下文换取短期签名 URL;签发失败时禁止回退裸路径。
106
111
  - 上传接口必须通用,不能在平台 HDFS 控制器写某项目“只允许支付凭证或实名认证图片”这类硬编码。
107
112
  - iOS Safari 上传后要验证表单字段不丢失,上传组件不能重置整张表单。
108
113
 
@@ -148,20 +153,20 @@
148
153
  - 通用规则:宿主必须提供唯一的 DI 授权服务,负责缓存刷新、HID、有效期和 RSA 签名验证;所有 Controller、AI、工作流等付费功能只读取这个实例,插件禁止自行缓存或再次创建 `LicenseValidator`。授权验证接口必须返回功能实际读取的 `OnlineFeatureLicensed/ProductType` 与宿主、插件程序集版本。核心插件接口应包含授权状态契约,使旧插件混入新发布包时直接启动/类型加载失败并暴露版本不一致,禁止静默退回开源版。
149
154
  - 自动化检查:先以无授权状态启动,再写入或挂载有效签名文件,验证统一服务限频刷新后授权接口与真实付费功能同时恢复;替换为旧插件 DLL 时启动必须失败或健康检查明确报版本不兼容,不能出现“Verify=Enterprise、功能=OpenSource”。无效签名、错误 HID、过期文件必须继续拒绝。
150
155
 
151
- ### 复盘:全局版本已升级但基础应用并未完整安装
156
+ ### 复盘:全局版本已升级但基础应用并未完整安装
152
157
 
153
158
  - 触发场景:客户旧库的 `ServerVersion` 已被后续步骤推进,但应用商城、导入器、菜单权限或关键元数据仍缺失;客户服务器不通外网时,重启也无法自愈。
154
159
  - 根因:升级器把全局版本号当成所有步骤成功的证明,单步失败后仍继续执行并更新版本;基础应用只依赖在线资源,且完整性检查继续走可能已经失真的 FormEngine 元数据。
155
160
  - 通用规则:后端发布包必须内置可启动的基础应用基线,在线资源只能整组校验成功后作为最新版覆盖,断网或任一资源失败时整组回退;迁移步骤必须失败即停且失败时禁止推进版本。导入器、必要表、菜单和权限等启动前置能力要独立做幂等完整性检查,老库检查以参数化物理表只读查询为准,不能只依赖全局版本或低代码元数据。
156
- - 自动化检查:选择一个版本号偏高但故意缺基础应用的旧库,以禁止外网模式启动,验证后端自动安装基础应用、管理员可见并打开应用商城、插件列表接口可拉取;再次冷启动不得重复导入。再注入任一步骤失败,验证后续步骤不执行且 `ServerVersion` 不前进。
157
-
158
- 流式应用发布等当前运行时入口若会直接查询新增物理字段,其扩展型结构不能只挂在
159
- `ServerVersion` 条件升级链里。必须在后台启动服务的共享分布式升级租约内、入口接流量前
160
- 独立幂等维护,并在版本链之前回读验证;历史版本号漂移或更早无关迁移失败时也要自愈。
161
- 自动化至少构造“版本号已高但发布字段缺失”的旧库并并发启动两个节点,断言只扩展一次、
162
- 没有永久锁,随后流式发布预检和首个文件写入不再出现缺列错误。
163
-
164
- ### 复盘:平台升级覆盖客户菜单的移动端显隐
161
+ - 自动化检查:选择一个版本号偏高但故意缺基础应用的旧库,以禁止外网模式启动,验证后端自动安装基础应用、管理员可见并打开应用商城、插件列表接口可拉取;再次冷启动不得重复导入。再注入任一步骤失败,验证后续步骤不执行且 `ServerVersion` 不前进。
162
+
163
+ 流式应用发布等当前运行时入口若会直接查询新增物理字段,其扩展型结构不能只挂在
164
+ `ServerVersion` 条件升级链里。必须在后台启动服务的共享分布式升级租约内、入口接流量前
165
+ 独立幂等维护,并在版本链之前回读验证;历史版本号漂移或更早无关迁移失败时也要自愈。
166
+ 自动化至少构造“版本号已高但发布字段缺失”的旧库并并发启动两个节点,断言只扩展一次、
167
+ 没有永久锁,随后流式发布预检和首个文件写入不再出现缺列错误。
168
+
169
+ ### 复盘:平台升级覆盖客户菜单的移动端显隐
165
170
 
166
171
  - 触发场景:旧租户切换到新版后端并执行自动升级后,移动端工作台整棵菜单消失;顶级 `sys_menu.AppDisplay` 为 `NULL` 或被升级包写成 `0` 时,所有下级菜单即使为 `1` 也无法展示。
167
172
  - 根因:旧 `UptSysMenu` 部分更新接口读取旧菜单后又丢弃旧实体,重新创建非空 `int` 字段默认为 `0` 的 `SysMenu` 再全字段更新;仅修改排序或父级也会把未传的 `Display/AppDisplay` 清零。历史加列迁移的不可重入多语句 SQL、应用包全量覆盖目标菜单,以及移动端把 `NULL/未配置` 当作隐藏,都会进一步放大影响。
@@ -180,19 +185,19 @@
180
185
  - 通用规则:需要保留旧版本并并行安装新版本时,新版必须使用独立 Compose 文件、独立 project 和独立 service 名;旧编排与旧容器在安装路径中只读。新版若必须保持原内网通信,应从旧容器回读并校验实际 Docker 网络,再通过 `external` 网络引用其真实名称,禁止由第二个 project 重复声明同一 IPAM 子网。动态端口必须写回 API 自身公开地址及前端 `ApiBase`;先启动并确认 API,再启动前端。失败清理只能 `down` 新 project,完成后还要回读旧端口对应的容器 ID 和运行状态,确认未被替换。并行项目后续更新必须使用另一份专用命令,固定读取新版 Compose/project/service,保留现场动态端口,备份新版 API/前端两个运行镜像 ID 并打不可变标签;先更新 API、再更新前端,任一步失败时只恢复新版双镜像,禁止复用会读取旧编排的历史日常更新脚本。进程、端口和 project 隔离不代表数据隔离;若新旧后端共用数据库、Redis 或租户,新版迁移、缓存与基础数据变化仍可能影响旧版,交付时必须明确警告并准备数据库级恢复方案。
181
186
  - 自动化检查:模拟新版起始端口连续占用、新版 API 启动失败和前端健康检查失败;断言旧 yml 字节不变、旧容器未执行 stop/rm/recreate、新版使用独立 project、实际 API 端口同时写入 `AuthServer` 与 `ApiBase`、新版 API 先于前端启动,失败时只清理新 project,并保存端口、网络、镜像 ID 和失败日志。并行日常更新另测动态端口不变、API/前端按序重建、任一服务失败后两个镜像都恢复到本次更新前的 ID,且旧端口容器 ID 始终不变。
182
187
 
183
- ### 复盘:容器已删除但残留 Compose 文件阻止安全重装
188
+ ### 复盘:容器已删除但残留 Compose 文件阻止安全重装
184
189
 
185
190
  - 触发场景:用户删除某个独立 Compose project 的全部容器后重新执行一键安装,脚本只因目标 yml 仍存在就报错退出;用户不得不手工删除文件,且容易误删其它编排。
186
191
  - 根因:安装保护只检查文件存在性,没有区分“仍有运行/停止容器的活跃环境”“容器已清空但本交付 yml 残留”和“身份不明的其它 yml”。
187
192
  - 通用规则:可重装脚本应先用 `docker ps -a` 按 project 标签检查全部运行及停止容器;只要存在任何容器就拒绝覆盖。容器为零但目标 yml 存在时,必须确认它是普通文件,校验预期 service、镜像和 `docker compose config`,保存 SHA-256 并归档原文件后才允许重新生成;身份不符、符号链接或语法错误一律停止。不得直接 `rm -f` 未确认身份的生产 yml。
188
- - 自动化检查:先安装并留下有效 yml,再模拟容器全部被删除,断言重复安装会归档旧 yml、保存哈希、重新选端口并保持旧项目不变;再模拟仍有停止容器,断言安装拒绝继续且原 yml 字节不变。
189
-
190
- ### 复盘:现场 Compose 文件名或 project 漂移导致专用更新脚本误报不存在
191
-
192
- - 触发场景:运维人员手工调整端口、外网 API 地址或重启脚本后,Compose 文件出现历史拼写变体,或容器被目录默认 project 启动;更新脚本仍硬编码标准文件名和初始 project,先报编排不存在,修正文件名后又可能报找不到容器。
193
- - 根因:把首次安装时的文件名和 project 当成长期不变事实,没有从当前磁盘与运行容器标签回读现场状态。
194
- - 通用规则:更新脚本应优先采用显式参数;未显式指定时可兼容已知历史文件名,但两个候选同时存在必须停止,禁止猜测。Compose 文件负责配置事实,运行容器的 `com.docker.compose.project` 与 `com.docker.compose.service` 标签负责运行态事实;应先按 service 标签唯一定位运行容器,再分别回读真实 project,并用各自 project 执行 `compose ps/port/up`。若 API 与前端历史上分属不同 project,更新和回滚可在服务身份唯一、project 合法且未显式限定 project 的前提下分别原地执行,禁止通过删除、迁移或改名强行合并拓扑。更新只拉镜像与重建,不得为了适配现场漂移而重新生成或覆盖端口、AuthServer、ApiBase 和网络配置。
195
- - 自动化检查:覆盖仅标准文件、仅历史拼写文件、两个文件并存、运行 project 使用安装默认值、运行 project 使用目录默认值、API/前端 project 不一致六类场景;断言兼容分支保留现场 yml 字节和动态端口,分别记录 API/前端 project,并在跨 project 成功与失败回滚路径中都使用各自 project;文件歧义及显式 project 不匹配仍应失败关闭。
193
+ - 自动化检查:先安装并留下有效 yml,再模拟容器全部被删除,断言重复安装会归档旧 yml、保存哈希、重新选端口并保持旧项目不变;再模拟仍有停止容器,断言安装拒绝继续且原 yml 字节不变。
194
+
195
+ ### 复盘:现场 Compose 文件名或 project 漂移导致专用更新脚本误报不存在
196
+
197
+ - 触发场景:运维人员手工调整端口、外网 API 地址或重启脚本后,Compose 文件出现历史拼写变体,或容器被目录默认 project 启动;更新脚本仍硬编码标准文件名和初始 project,先报编排不存在,修正文件名后又可能报找不到容器。
198
+ - 根因:把首次安装时的文件名和 project 当成长期不变事实,没有从当前磁盘与运行容器标签回读现场状态。
199
+ - 通用规则:更新脚本应优先采用显式参数;未显式指定时可兼容已知历史文件名,但两个候选同时存在必须停止,禁止猜测。Compose 文件负责配置事实,运行容器的 `com.docker.compose.project` 与 `com.docker.compose.service` 标签负责运行态事实;应先按 service 标签唯一定位运行容器,再分别回读真实 project,并用各自 project 执行 `compose ps/port/up`。若 API 与前端历史上分属不同 project,更新和回滚可在服务身份唯一、project 合法且未显式限定 project 的前提下分别原地执行,禁止通过删除、迁移或改名强行合并拓扑。更新只拉镜像与重建,不得为了适配现场漂移而重新生成或覆盖端口、AuthServer、ApiBase 和网络配置。
200
+ - 自动化检查:覆盖仅标准文件、仅历史拼写文件、两个文件并存、运行 project 使用安装默认值、运行 project 使用目录默认值、API/前端 project 不一致六类场景;断言兼容分支保留现场 yml 字节和动态端口,分别记录 API/前端 project,并在跨 project 成功与失败回滚路径中都使用各自 project;文件歧义及显式 project 不匹配仍应失败关闭。
196
201
 
197
202
  ### 复盘:一键安装脚本仍直接拉取海外镜像或工具二进制
198
203
 
@@ -115,7 +115,23 @@ Iframe 不把长期 Token、密码或连接串放 URL。第三方单点登录使
115
115
  - 不复制官网旧“Redis 文本进度 + 长事务循环”导入示例作为新实现;必须有稳定
116
116
  幂等键、业务任务状态、真实 Current/Total、失败恢复和必要的 checkpoint 分片。
117
117
 
118
- ### 菜单启动查询与字段元数据兼容
118
+ ### 原生 SQL 身份占位符与统计安全
119
+
120
+ - 模块 SqlWhere/SqlJoin 及字段选项 SQL 中的 `$CurrentUser.Id$`、
121
+ `$CurrentUser.RoleIds$`、`$CurrentUser.Level$` 必须使用本次可信后端授权快照。
122
+ 角色集合只包含当前有效 RoleId,不能按旧 Token、角色名称或请求中的同名属性授予权限。
123
+ `IN ($CurrentUser.RoleIds$)` 继续支持;角色撤销后即使仍拥有同表菜单,也须验证计数与汇总不越权。
124
+ - SQL 替换使用局部克隆;部门等其它扩展保留兼容语义,不覆盖 `V8.CurrentUser`。
125
+ HTTP JSON 不能注入授权快照或可信调用标记,父用户与当前身份不符、用户禁用均失败关闭。
126
+ - 用户访问密钥仍绑定有效 sys_user,沿“账号有效角色 + 密钥 scope/路由/表范围收窄”授权;
127
+ 不把访问密钥伪装成无所属账号的独立角色身份,账号不存在或停用时拒绝查询。
128
+ - Count、SUM 在逐行数据过滤之前执行,列表/计数/导出必须共享真正的查询范围;
129
+ 不能以 DataFilter 已拒绝详情为由宣称统计安全。验收包含旧 Token 撤角、角色删除、
130
+ 管理员降级但仍有菜单、不同父身份和用户对象未被修改。
131
+ - 本能力依赖对应后端二进制;应用包无需复制用户角色数据。它不改变 DbRead 或父事务的隔离语义,
132
+ 联查授权表的副本延迟与旧 RR 快照需要独立验证,不将 SQL 生成通过称为真实数据库验收。
133
+
134
+ ### 菜单启动查询与字段元数据兼容
119
135
 
120
136
  - 菜单树是登录后的启动控制面。读取 `sys_menu` 时,不能把浏览器传入的
121
137
  `_SelectFields` 直接交给依赖 `diy_field` 的通用查询投影:旧库、空库或升级后缓存
@@ -1,36 +1,36 @@
1
1
  ---
2
2
  name: print-engine
3
- description: 生成和审查 Microi 打印引擎 Print Engine 模板 JSON。用于创建 mic_print 模板、PageObj 布局、PrintObj 数据绑定、可打印报告、标签或合同;蓝牙 TSPL/CPCL/ESC-POS、GP-M322 或 CC4 需求转交 v8-frontend-events 蓝牙参考。
3
+ description: 生成和审查 Microi 打印引擎 Print Engine 模板 JSON。用于创建 mic_print 模板、PageObj 布局、PrintObj 数据绑定、可打印报告、标签或合同;蓝牙 TSPL/CPCL/ESC-POS、GP-M322 或 CC4 需求转交 v8-frontend-events 蓝牙参考。
4
4
  ---
5
5
 
6
6
  > **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
7
7
 
8
- # Microi 打印引擎(Print Engine)模板 JSON 生成
9
-
10
- 你正在为 Microi 吾码平台生成打印引擎模板的 JSON 数据。打印模板包含 PageObj(模板定义)和 PrintObj(打印数据)。
11
-
12
- ## 设计器源码事件
13
-
14
- 扩展打印设计器源码时,可通过 `EventBus.on('savePrintJson', handler)` 接收
15
- 保存后的模板 JSON,并在组件卸载时调用 `EventBus.off('savePrintJson')`。
16
- 这是设计器内部生命周期接口,不属于运行时打印模板 JSON,也不能替代模板的
17
- 服务端保存与回读。
18
-
19
- ## 与蓝牙直连打印的边界
20
-
21
- 本 Skill 负责 `mic_print`、`PageObj`、`PrintObj`、浏览器预览和模板化纸张输出。
22
- 如果需求明确是蓝牙标签机/小票机、TSC/TSPL、CPCL、ESC/POS、佳博 GP-M322、
23
- ZICOX CC4、BLE/SPP 或
24
- `V8.Print.prepareSend`,应同时读取
25
- `../v8-frontend-events/references/bluetooth-print.md`:
26
-
27
- - Print Engine 决定“页面/模板如何排版”,适合 A4、PDF、浏览器打印和统一模板。
28
- - `V8.Print` 生成/适配打印机原生命令并通过 BLE 或 Android SPP 写入,适合标签和热敏小票;GP-M322 保持 TSPL 原字节,CC4 可自动生成 CPCL。
29
- - MicroService 不能跨 iframe 访问父页面打印组件;普通打印使用 `microi.host.v1` 的
30
- `openPlatformPrint`,只传当前租户 `mic_print.Id` 和同源 `/apiengine/` 数据地址。该动作
31
- 不是蓝牙代理,不发送 BLE/SPP 字节。详见 `../microi-microservice/references/runtime-delivery.md`。
32
- - 两者可以由同一个按钮按设备能力选择,但不能把 `PageObj` 直接交给
33
- `V8.Print.prepareSend`,也不能把 TSPL/CPCL/ESC-POS 字节当作 Print Engine JSON。
8
+ # Microi 打印引擎(Print Engine)模板 JSON 生成
9
+
10
+ 你正在为 Microi 吾码平台生成打印引擎模板的 JSON 数据。打印模板包含 PageObj(模板定义)和 PrintObj(打印数据)。
11
+
12
+ ## 设计器源码事件
13
+
14
+ 扩展打印设计器源码时,可通过 `EventBus.on('savePrintJson', handler)` 接收
15
+ 保存后的模板 JSON,并在组件卸载时调用 `EventBus.off('savePrintJson')`。
16
+ 这是设计器内部生命周期接口,不属于运行时打印模板 JSON,也不能替代模板的
17
+ 服务端保存与回读。
18
+
19
+ ## 与蓝牙直连打印的边界
20
+
21
+ 本 Skill 负责 `mic_print`、`PageObj`、`PrintObj`、浏览器预览和模板化纸张输出。
22
+ 如果需求明确是蓝牙标签机/小票机、TSC/TSPL、CPCL、ESC/POS、佳博 GP-M322、
23
+ ZICOX CC4、BLE/SPP 或
24
+ `V8.Print.prepareSend`,应同时读取
25
+ `../v8-frontend-events/references/bluetooth-print.md`:
26
+
27
+ - Print Engine 决定“页面/模板如何排版”,适合 A4、PDF、浏览器打印和统一模板。
28
+ - `V8.Print` 生成/适配打印机原生命令并通过 BLE 或 Android SPP 写入,适合标签和热敏小票;GP-M322 保持 TSPL 原字节,CC4 可自动生成 CPCL。
29
+ - MicroService 不能跨 iframe 访问父页面打印组件;普通打印使用 `microi.host.v1` 的
30
+ `openPlatformPrint`,只传当前租户 `mic_print.Id` 和同源 `/apiengine/` 数据地址。该动作
31
+ 不是蓝牙代理,不发送 BLE/SPP 字节。详见 `../microi-microservice/references/runtime-delivery.md`。
32
+ - 两者可以由同一个按钮按设备能力选择,但不能把 `PageObj` 直接交给
33
+ `V8.Print.prepareSend`,也不能把 TSPL/CPCL/ESC-POS 字节当作 Print Engine JSON。
34
34
 
35
35
  ## 数据模型
36
36
 
@@ -40,15 +40,15 @@ ZICOX CC4、BLE/SPP 或
40
40
  |------|------|
41
41
  | PageObj | 页面模板定义(面板 + 元素布局) |
42
42
  | PrintObj | 打印数据(运行时填充到模板中) |
43
- | DataApi | 关联的接口引擎 Id(动态数据) |
44
-
45
- ### `mic_print` 权限边界
46
-
47
- - `mic_print` 是按角色管理的运行资源,不是只能由 `Level >= 9999` 读取的控制面表。打印渲染器通过 FormEngine 读取模板时,当前角色必须拥有菜单权限或高级表权限中的 `Read`;否则应明确返回 `NoAuth`,不能为兼容改成匿名读取。
48
- - 模板新增、修改、删除分别要求明确的 `Add`、`Edit`、`Del`。只需要打印的业务角色通常只授予 `Read`,不要顺带授予设计权限。
49
- - 角色授权界面只负责展示服务端策略。角色增删改接口必须从主库复核当前操作者确为活动平台管理员,不能相信 Postman 请求体中的 `_IsAdmin`、`Level`、`RoleIds` 或 `OsClient`。
50
-
51
- ## PageObj 模板结构
43
+ | DataApi | 关联的接口引擎 Id(动态数据) |
44
+
45
+ ### `mic_print` 权限边界
46
+
47
+ - `mic_print` 是按角色管理的运行资源,不是只能由 `Level >= 9999` 读取的控制面表。打印渲染器通过 FormEngine 读取模板时,当前角色必须拥有菜单权限或高级表权限中的 `Read`;否则应明确返回 `NoAuth`,不能为兼容改成匿名读取。
48
+ - 模板新增、修改、删除分别要求明确的 `Add`、`Edit`、`Del`。只需要打印的业务角色通常只授予 `Read`,不要顺带授予设计权限。
49
+ - 角色授权界面只负责展示服务端策略。角色增删改接口必须从主库复核当前操作者确为活动平台管理员,不能相信 Postman 请求体中的 `_IsAdmin`、`Level`、`RoleIds` 或 `OsClient`。
50
+
51
+ ## PageObj 模板结构
52
52
 
53
53
  ```json
54
54
  {
@@ -106,9 +106,17 @@ ZICOX CC4、BLE/SPP 或
106
106
  }
107
107
  ```
108
108
 
109
- ### table — 表格元素
110
-
111
- ```json
109
+ 文本 `text` 与长文本 `longText` 的 `options.formatter` 接收
110
+ `function(title, value, options, templateData)`;文本控件还可能传第五个 `target`。
111
+ 第一参数是标题,绑定数据在第二参数。不要直接传入 `function(value)` 编码器,否则会把
112
+ 标题当业务值打印。表格单元格的 `formatter2(title, field, row, index, options)` 是另一合同。
113
+ 金额应由后端精确计算后作为字符串输出,不能在打印页转换为 `Number` 再求和。
114
+ 业务文本经过 formatter 进入 HTML 时必须编码 `& < > " '`,并用含标签的值与大额金额
115
+ 调用真实函数签名做回归。模板保存成功只证明存储,仍须用实际数据预览分页、合计与长条款。
116
+
117
+ ### table — 表格元素
118
+
119
+ ```json
112
120
  {
113
121
  "options": {
114
122
  "left": 30, "top": 150, "height": 56, "width": 511.5,
@@ -0,0 +1,46 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ // 后端只持有自包含的知识快照;规则正文仍以 microi.skills 为唯一可编辑事实源。
7
+ const skillsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
8
+ const serverRoot = path.resolve(skillsRoot, '../Microi.Server/Microi.AI');
9
+ const project = fs.readFileSync(path.join(serverRoot, 'Microi.AI.csproj'), 'utf8');
10
+ const manifest = JSON.parse(fs.readFileSync(path.join(skillsRoot, '.progressive-disclosure-manifest.json'), 'utf8'));
11
+ const check = process.argv.includes('--check');
12
+ const names = process.argv.slice(2).filter(value => value !== '--check');
13
+ if (!names.length) throw new Error('请显式指定本次需要同步的 Skill 名称。');
14
+ const stripMarkers = text => text.replace(/<!--\s*\/?microi-progressive:[^>]*-->\r?\n?/g, '');
15
+ const digest = text => crypto.createHash('sha256').update(text).digest('hex');
16
+ const results = [];
17
+
18
+ for (const name of names) {
19
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) throw new Error(`无效 Skill 名称:${name}`);
20
+ const file = `skill-${name}.md`;
21
+ // 不擅自新增程序集资源;只有现有项目明确注册的知识快照可以由此脚本刷新。
22
+ if (!project.includes(`Resource\\${file}`)) throw new Error(`后端尚未注册 ${file},请先审阅资源归属。`);
23
+ let text = fs.readFileSync(path.join(skillsRoot, name, 'SKILL.md'), 'utf8');
24
+ const references = manifest.skills[name]?.references || [];
25
+ if (references.length) {
26
+ const route = text.indexOf('## 详细参考路由(渐进披露)');
27
+ const end = text.indexOf('<!-- microi-progressive:end -->', route);
28
+ if (route < 0 || end < route) throw new Error(`${name} 缺少受控参考路由,停止覆盖快照。`);
29
+ text = text.slice(0, route) + text.slice(end + '<!-- microi-progressive:end -->'.length);
30
+ for (const reference of references) {
31
+ const source = path.resolve(skillsRoot, name, reference.path);
32
+ const relative = path.relative(path.join(skillsRoot, name), source);
33
+ if (relative.startsWith('..') || path.isAbsolute(relative)) throw new Error('参考文件越过 Skill 目录。');
34
+ // 平台 AI 读取程序集时不能打开工作区相对路径,必须带上渐进参考的完整正文。
35
+ text += `\n\n${fs.readFileSync(source, 'utf8')}`;
36
+ }
37
+ }
38
+ text = `${stripMarkers(text).replace(/\r\n/g, '\n').trimEnd()}\n`;
39
+ const target = path.join(serverRoot, 'Resource', file);
40
+ const before = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : '';
41
+ const changed = before.replace(/\r\n/g, '\n') !== text;
42
+ if (changed && !check) fs.writeFileSync(target, text, 'utf8');
43
+ results.push({ name, changed, sha256: digest(text) });
44
+ }
45
+ console.log(JSON.stringify({ check, results }, null, 2));
46
+ if (check && results.some(item => item.changed)) process.exitCode = 1;