@microi.net/cli 5.7.4 → 5.7.5

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 (38) 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/cordis.patch.yml +2 -2
  10. package/package.json +1 -1
  11. package/scripts/mcp-server.js +101 -101
  12. package/scripts/microi-cli.js +2 -2
  13. package/scripts/microi-codex-broker.js +2 -2
  14. package/scripts/microi-codex-router.js +2 -2
  15. package/scripts/microi-skills.meta.json +219 -216
  16. package/skills/.microi-skills-version.json +6 -6
  17. package/skills/.progressive-disclosure-manifest.json +3566 -3566
  18. package/skills/ai-engine/SKILL.md +37 -37
  19. package/skills/ai-engine/references/ai-employees.md +6 -4
  20. package/skills/app-store/SKILL.md +253 -249
  21. package/skills/job-engine/SKILL.md +21 -20
  22. package/skills/message-notification/SKILL.md +156 -155
  23. package/skills/microi-ai-application/SKILL.md +7 -3
  24. package/skills/microi-client-frontend/SKILL.md +34 -32
  25. package/skills/microi-codex/SKILL.md +8 -8
  26. package/skills/microi-codex-installer/SKILL.md +5 -5
  27. package/skills/microi-microservice/SKILL.md +35 -34
  28. package/skills/module-engine/SKILL.md +59 -46
  29. package/skills/playwright-e2e/SKILL.md +3 -3
  30. package/skills/v8-explorer-tree/SKILL.md +228 -228
  31. package/skills/v8-file-upload/SKILL.md +189 -182
  32. package/skills/v8-mq-mqtt/SKILL.md +2 -1
  33. package/skills/v8-tcp-integration/SKILL.md +2 -2
  34. package/skills/v8-workflow/SKILL.md +93 -66
  35. package/skills/v8-workflow/references/workflow-configuration.md +49 -0
  36. package/skills/workspace-conventions/SKILL.md +261 -261
  37. 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
  38. package/skills/workspace-conventions/references/progressive-02-microi-net-api-/346/234/254/345/234/260/345/220/257/345/212/250/347/272/246/345/256/232.md +7 -7
@@ -18,34 +18,35 @@ description: Microi 定时任务与可靠后台任务规范。用于配置 Micro
18
18
 
19
19
  ## 平台对象
20
20
 
21
- Quartz 管理能力包含查询、添加、更新、暂停、恢复、删除任务。V8 中的 `V8.Method.ManageScheduleJob` 用于能力发现、任务查询和受控管理,必须绑定当前租户的管理员身份。任务配置、接口引擎代码和执行日志属于控制面,只允许 `Level >= 9999` 维护;业务用户只能触发明确授权的后台动作。
21
+ Quartz 管理能力包含查询、添加、更新、暂停、恢复、删除任务。V8 中的 `V8.Method.ManageScheduleJob` 用于能力发现、任务查询和受控管理,必须绑定当前租户的管理员身份。任务配置、接口引擎代码和执行日志属于控制面,只允许 `Level >= 9999` 维护;业务用户只能触发明确授权的后台动作。
22
22
 
23
23
  任务通常调用稳定的 `ApiEngineKey`。接口引擎返回 `Code=1` 只表示本次执行成功,不代表调度系统可忽略重试与幂等。
24
24
 
25
- ## 多节点设计
26
-
27
- ### 执行日志与只读诊断
28
-
29
- - 新执行/失败/跳过记录使用 `ScheduleExecutionLog` 进入系统日志队列,落入 `sys_log_<tenant>/log_yyyyMM`;固定 `TargetType=ScheduledJob`、`TargetId=JobName`,`EventId` 幂等重放。日志队列失败不得改变业务结果,不再回写关系库日志表。
30
- - 表单使用字段级 Tabs,分别放置“运行日志”和“历史日志”只读表格;仅显示的页签发起查询。历史 `diy_schedule_job_log` 保留,不自动删除、搬迁或清空。
31
- - `platform-schedule-job` 的 `logs/historylogs` 要求任务名、月份,每页最多 100 条,按时间+Id 游标多取一条判断 `HasMore`,不计算多年数据总数。Mongo 索引为 `(TargetType,TargetId,CreateTime,EventId)`;历史表索引 `(JobName,CreateTime,Id)` 通过声明式应用包交付并现场回读。
32
- - 用 `microi_query_job_runtime` 或 `microi_run_engine` 的 `Action=diagnostics` 核验启动、待机、实际执行数、领取进展、设置读取失败、触发器与心跳。一个租户的设置读取超时只能关闭该租户,不能拖住其它租户;每租户最多一个在途读,不能按轮询叠加请求。
33
- - MongoDB 连接或协议不兼容必须显示查询失败,不能伪装为空日志;先验证目标 Mongo 与驱动兼容。镜像更新不等于 Mongo 升级,队列接受不等于日志已持久化,spool 应在持久卷并验收恢复重放。
34
-
35
- ### 运行时间刷新与配置版本分离
36
-
37
- - Quartz 周期同步 `LastTime/NextTime` 是运行状态投影,不能调用通用 `UptFormData` 生成配置版本或数据日志;用户保存名称、Cron、代码等配置仍走原表单事件和版本链路。
38
- - 只读取 Id、任务名和两个时间列;时间未变化时不写入。变化时由可信调度内核向同一租户主库执行固定两列参数化 CAS,匹配原时间、任务名、正常状态与未删除条件;并发/陈旧结果命中 0 行时不重放。
39
- - 历史版本保留不自动删除。持续高 CPU 时核对 `mic_data_version` 实际物理索引,不以应用包已声明索引推断旧库已安装;用 MCP 回读 `(TableId,TableRowId,CreateTime)`,并比较 MySQL digest 两次采样的执行数、扫描行数与耗时差值。
40
- - 此修复要求更新调度后端;既有 `platform-schedule-job`、任务元数据及 MCP 查询/保存协议保持兼容。验收覆盖两租户同名任务、两连接竞争、未变化、暂停/软删除/重命名后陈旧写入、主库选择和配置字段不变。
41
-
42
- ### 新旧平台共库过渡:停用新版调度
25
+ ## 多节点设计
26
+
27
+ ### 执行日志与只读诊断
28
+
29
+ - 新执行/失败/跳过记录使用 `ScheduleExecutionLog`,直接等待 MongoDB 确认写入 `sys_log_<tenant>/log_yyyyMM`;固定 `TargetType=ScheduledJob`、`TargetId=JobName`,`EventId` 幂等。MongoDB 确认失败才写当前租户的 `diy_schedule_job_log`,两者均失败不得改变业务结果。`AddSysLog` 仅代表异步入队,不能用作本能力的持久化确认。
30
+ - 表单使用字段级 Tabs,分别放置“运行日志”和“历史日志”只读表格;仅显示的页签发起查询。历史 `diy_schedule_job_log` 保留,不自动删除、搬迁或清空。
31
+ - `platform-schedule-job` 的 `logs/historylogs` 要求任务名、月份,每页最多 100 条,按时间+Id 游标多取一条判断 `HasMore`,不计算多年数据总数。Mongo 索引为 `(TargetType,TargetId,CreateTime,EventId)`;历史表索引 `(JobName,CreateTime,Id)` 通过声明式应用包交付并现场回读。
32
+ - 用 `microi_query_job_runtime` 或 `microi_run_engine` 的 `Action=diagnostics` 核验启动、待机、实际执行数、领取进展、设置读取失败、触发器与心跳。一个租户的设置读取超时只能关闭该租户,不能拖住其它租户;每租户最多一个在途读,不能按轮询叠加请求。
33
+ - `logs` 优先读 MongoDB,MongoDB 查询失败时自动读关系库;成功的空集合不回退。`historylogs` 固定读关系库。两个库都不可用必须显示查询失败,不能伪装为空日志。镜像更新不等于 Mongo 升级;后台系统日志队列的 spool 仍应在持久卷并验收恢复重放。
34
+
35
+ ### 运行时间刷新与配置版本分离
36
+
37
+ - Quartz 周期同步 `LastTime/NextTime` 是运行状态投影,不能调用通用 `UptFormData` 生成配置版本或数据日志;用户保存名称、Cron、代码等配置仍走原表单事件和版本链路。
38
+ - 只读取 Id、任务名和两个时间列;时间未变化时不写入。变化时由可信调度内核向同一租户主库执行固定两列参数化 CAS,匹配原时间、任务名、正常状态与未删除条件;并发/陈旧结果命中 0 行时不重放。
39
+ - 编辑 Cron 重建触发器后,Quartz 的 PreviousFireTime 可能为空;同步时不得因此清空数据库已有的 `LastTime`,新的 `NextTime` 仍应更新。验收需覆盖“已执行任务改到当天已过时间,下一次排到次日”的场景,并分别核对新旧执行日志。
40
+ - 历史版本保留不自动删除。持续高 CPU 时核对 `mic_data_version` 实际物理索引,不以应用包已声明索引推断旧库已安装;用 MCP 回读 `(TableId,TableRowId,CreateTime)`,并比较 MySQL digest 两次采样的执行数、扫描行数与耗时差值。
41
+ - 此修复要求更新调度后端;既有 `platform-schedule-job`、任务元数据及 MCP 查询/保存协议保持兼容。验收覆盖两租户同名任务、两连接竞争、未变化、暂停/软删除/重命名后陈旧写入、主库选择和配置字段不变。
42
+
43
+ ### 新旧平台共库过渡:停用新版调度
43
44
 
44
45
  - 系统设置“开发配置”的 `DisableTaskScheduling`(停用任务调度)默认 `0`,缺字段、空值也按正常调度处理;只有已实现此能力的新版后端识别它。字段由“系统设置”应用和 SaaS 空库基础包交付,不写租户配置值,不逐条 Pause/修改任务状态。
45
46
  - 开启时在 Quartz 领取触发器、处理 Misfire 之前按租户过滤;领取后、真正触发前重新读取系统设置缓存。禁止仅在 `IJob.Execute`、Listener veto 或业务 V8 入口 return:共享 Quartz 可能已经推进 NextFireTime,造成旧版漏执行。
46
47
  - 集群故障恢复也不得清理停用租户的在途记录或创建其补偿触发器。Quartz 以故障节点为单位清理记录,所以同一故障节点混有被停用租户时,新版推迟该故障节点整组恢复,交给旧节点或关闭开关后处理;其它健康节点仍正常领取。
47
48
  - 正常保存系统设置在事务提交后失效缓存,下一次调度检查生效,不用重启;不是强制中断,已经开始的任务允许完成。缓存/配置读取失败拒绝该租户的新领取,不影响其它健康租户;Redis/失效通知异常时不能承诺硬实时。
48
- - 停用期间只读观察周期计划,不推进共享触发器;每个“租户 + Job + Trigger + 计划时间”向 Mongo 日志队列提交确定性事件,包含 `Status=Skipped`、`Executed=false`、`Reason=SystemTaskSchedulingDisabled` 和中文说明。Redis 原子预留与 Mongo 确定性主键共同防止多新版节点重复记录;日志说明仅代表新版未执行,旧版仍可能正常执行。不补跑业务、不补写进程停机历史;新增/修改计划的日志目录最多约 10 秒更新。
49
+ - 停用期间只读观察周期计划,不推进共享触发器;每个“租户 + Job + Trigger + 计划时间”持久化确定性事件,包含 `Status=Skipped`、`Executed=false`、`Reason=SystemTaskSchedulingDisabled` 和中文说明。Redis 原子预留与日志确定性主键共同防止多新版节点重复记录;日志说明仅代表新版未执行,旧版仍可能正常执行。不补跑业务、不补写进程停机历史;新增/修改计划的日志目录最多约 10 秒更新。
49
50
  - 普通接口调用、MQ 消费、升级/应用安装等持久后台任务不属于此开关的范围。手动触发 Quartz 任务仍经过门禁。
50
51
  - 交付顺序:先安装字段并打开开关,再让新版节点参与调度;确认旧版所有调度进程已停止且在途任务完成后,关闭开关交接。不同业务库分别设置,不能用一个租户的值控制全部租户。
51
52
  - 验收至少覆盖:默认兼容、开关实时读取、租户隔离、共享库旧节点继续领取、领取后开关变化不推进触发器、Misfire 不误推进、多节点日志去重;不得在真实生产任务上制造副作用做测试。
@@ -1,155 +1,156 @@
1
- ---
2
- name: message-notification
3
- description: 设计、实现、迁移和验收 Microi 多通道消息通知与平台提醒。用于平台提醒、SaaS 系统提醒、试用到期、维护公告、定时弹窗、官方版本提醒、wx_tpl_msg、mic_msgset、mic_msg_event_log、公众号模板消息、短信、邮件、V8.Notification、SignalR、msg_event、消息幂等或通知应用商城交付。
4
- ---
5
-
6
- > **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
7
-
8
- # Microi 消息通知
9
-
10
- ## 统一配置入口与 MCP
11
-
12
- - 安装同一个 `app.microi.message-notification` 应用后,统一从“系统引擎 → 消息通知”进入系统公告、业务通知与投递记录。禁止再建独立“系统提醒”应用或向 SaaS 表单添加入口。旧 `/xiaoxitongzhisz` 配置并入业务通知,原 `mic_msgset` 和历史数据保留。
13
- - 先用当前用户自己的 MCP 调用 `microi_get_notification_context`,读取 `Capabilities / BusinessRules / BusinessRule / Reminders / Reminder / Recipients / Templates / Adapters / Logs / History`。`Adapters` 按关键词分页发现本租户已启用的接口 Key,不读取源码或密钥。用户和角色只在当前租户读取,跨租户/产品版本仅选择 `AllAccounts / SuperAdmins`,不得读取其它服务器角色。
14
- - `microi_configure_business_notification` 的 `Validate` 不写入、不发送;`Save` 写原通知表,通过固定 `platform-message-notification-config` 接口编排。确认串为 `Save:<id或Key>`;修改必须传 `expectedRevision`,不能把密钥写进 `ChannelApiEngineMap`。
15
- - `microi_manage_system_reminder` 提供 `Validate / Save / Publish / Withdraw`,确认串为 `<action>:<id或requestId>`。保存仅草稿;发布前核对用户已经授权的具体内容、范围与时间。保存/发布使用稳定 `requestId`,编辑/发布/撤回传最新 `expectedRevision`,超时先按原 Id/Key 回读,禁止换请求标识盲目新建。
16
- - `AccountScope` 在本租户 `Users` 默认 `AllAccounts`,在 `Tenants / Editions` 默认 `SuperAdmins`;不要让已指定的普通帐号被默认管理员筛选误排除。用户显式指定的帐号范围优先。
17
- - 配置接口只管理原表、校验引用和读取脱敏投递记录;实际发送继续调用 `msg_event`。`ConfigRevision` 缺省按 0 兼容旧记录,更新使用同事务条件写,失败不能覆盖别人修改。停用旧配置允许保留失效引用,再启用时重新核验。
18
- - MCP 缺少上述工具时优先更新本机插件/CLI;当前宿主未重载可复用已有 `microi_run_engine` 调用同一固定接口,不另建临时维护引擎或绕过授权。明确区分本机工具打包成功与用户渠道已发布。
19
-
20
- ## 目标
21
-
22
- 交付“配置可维护、事件先持久化、实时可降级、多节点不重复、租户不串线”的通知能力。支持微信公众号/服务号模板消息、短信、邮件和平台内部通知;小程序是公众号模板消息的跳转目标,不是独立的公众号发送主体。
23
-
24
- ## 开始前
25
-
26
- 1. 读取工作区 `AGENTS.md`,并按任务同时读取 `microi-db-schema`、`v8-api-config`、`v8-frontend-events`、`microi-client-frontend`;涉及商城时再读 `app-store`,涉及浏览器时再读 `playwright-e2e`。
27
- 2. 用用户点名的 MCP 连接读取实时结构,不以本地字典替代远端事实。至少读取 `wx_tpl_msg`、`mic_msgset`、`mic_msg_event_log`,按需读取 `wx_mp`、`wx_mini_program`、`sys_menu` 和接口引擎。
28
- 3. 多租户比较按字段语义合并:保留双方新增字段、控件、说明和数据源,再把并集同步到双方。每次写入后重新读取字段、物理索引和接口源码。
29
- 4. 只有用户明确要求时才复制渠道配置。复制微信公众号/小程序密钥时不在输出中打印秘密;模板、发送主体和小程序跳转引用必须一起回读验证。
30
-
31
- ## 核心模型
32
-
33
- - `mic_msgset`:通知策略。`Key` 是稳定业务键,`Type` 是多选渠道,`ChannelApiEngineMap` 配置短信、邮件或自定义渠道适配器。
34
- - `wx_tpl_msg`:微信公众号/服务号模板。`WxMpId` 决定发送主体;`MiniProgramId`、`MiniProgramAppId`、`MiniProgramPagePath` 仅表示点击模板消息后跳入的小程序。
35
- - `mic_msg_event_log`:每位接收人、每个渠道的权威事件记录。至少包含稳定 `EventId`、`ChannelType`、`ReceiverUserId`、标题、内容、链接、Payload、已读状态和结果。
36
- - 唯一约束:`EventId + ChannelType + ReceiverUserId`。租户使用独立业务库时表内无需虚构 `OsClient` 字段;共享库模型则必须把租户键加入唯一约束。
37
-
38
- 完整字段、接口和可靠性契约见 [references/contracts.md](references/contracts.md)。
39
-
40
- ## 实现流程
41
-
42
- ### 1. 合并结构
43
-
44
- 对两个租户分别读取字段列表,按 `Name` 生成差异表。新增缺失字段后刷新缓存,并回读:
45
-
46
- - `mic_msgset.Type` 包含 `微信公众号模板消息`、`短信`、`邮件`、`平台内部`;
47
- - `mic_msgset.ChannelApiEngineMap` 为 JSON 对象;
48
- - `wx_tpl_msg` 同时有 `WxMpId/WxMpName` 和小程序跳转字段;
49
- - `mic_msg_event_log` 有完整的事件、接收人、渠道、内容和已读字段;
50
- - 业务唯一索引与常用未读查询索引存在。
51
-
52
- 不要用一次性 SQL 修某个租户而跳过通用表单/资源升级路径。应用包与平台升级资源必须携带同一结构。
53
-
54
- ### 2. 配置发送策略
55
-
56
- `mic_msgset.Key` 对业务长期稳定。接收人可以来自固定用户、角色和调用参数,必须去重并限制扇出。渠道适配器统一接收:
57
-
58
- ```js
59
- {
60
- EventId: '业务稳定幂等键',
61
- ChannelType: '短信',
62
- User: { Id: '...', Phone: '...', Email: '...', WxOpenId: '...' },
63
- Title: '审批提醒',
64
- Content: '您有一条待审批记录',
65
- LinkUrl: '/#/approval/123',
66
- Payload: { BusinessId: '123' }
67
- }
68
- ```
69
-
70
- 适配器必须按 `EventId` 幂等。不要把密钥放进 `ChannelApiEngineMap` 或 Payload;密钥保存在对应渠道配置表或租户安全配置中。
71
-
72
- ### 3. 后端发送
73
-
74
- 业务代码优先调用 `msg_event`,由它读取策略、解析接收人、原子登记日志后分发。调用方在重试时保持同一个 `EventId`:
75
-
76
- ```js
77
- return V8.ApiEngine.Run('msg_event', {
78
- MsgKey: 'order_wait_approve',
79
- EventId: 'order-wait-approve-' + V8.Param.OrderId,
80
- ReceiverUserIds: [V8.Param.ApproverId],
81
- Content: '订单 ' + V8.Param.OrderNo + ' 等待审批',
82
- LinkUrl: '/#/orders/detail?id=' + V8.Param.OrderId,
83
- Payload: { OrderId: V8.Param.OrderId }
84
- }, V8.DbTrans);
85
- ```
86
-
87
- `V8.Notification.Send` 是宿主的“平台内部实时提示”原语。它不替代日志 claim,通常只由 `msg_event` 在日志成功登记后调用。事务存在时,推送在提交后进行有界等待;回滚不得推送。
88
-
89
- ### 4. 前端通知中心
90
-
91
- 前端 V8 使用 `V8.Notification.List` 获取当前登录用户的权威快照,使用 `MarkRead` 标记本人通知。SignalR 固定事件 `ReceivePlatformNotification` 只用于低延迟刷新:客户端按 `Id/EventId` 去重,收到后仍以列表接口回读为准。
92
-
93
- ```js
94
- await V8.Notification.Send('order_wait_approve', {
95
- EventId: 'order-wait-approve-' + V8.Form.Id,
96
- ReceiverUserIds: [V8.Form.ApproverId],
97
- Content: '订单等待审批'
98
- });
99
-
100
- var result = await V8.Notification.List({ PageIndex: 1, PageSize: 20 });
101
- await V8.Notification.MarkRead(result.Data[0].Id);
102
- await V8.Notification.MarkRead({ All: true });
103
- ```
104
-
105
- 列表和已读接口必须以 `V8.CurrentUser.Id` 作为服务端过滤条件,不能信任客户端传入的用户 Id。外链只允许站内路径、锚点或 `http/https`。
106
-
107
- ### 5. 多节点可靠性
108
-
109
- - 数据库日志是事实源,SignalR 是可丢失提示;Redis backplane 使任一节点能通知连接在其它节点的用户。
110
- - “先查询再新增”不能防并发;依赖唯一索引抢占。同一事件重复投递只能产生一份 `EventId + 渠道 + 接收人` 记录。
111
- - 外部供应商在“已发送但响应丢失”时无法凭本地状态保证恰好一次。适配器必须把 `EventId` 传给支持幂等的供应商;不支持时进入可审计的人工确认/重试状态。
112
- - 发布中新旧版本短暂共存,先扩展字段与接口,再发布读写代码,最后才收缩旧字段。
113
-
114
- ## 应用商城交付
115
-
116
- “消息通知”应用包必须包含 `mic_msgset`、`mic_msg_event_log`、`wx_tpl_msg`、`wx_mp`、`wx_mini_program` 五张结构资源,以及相关菜单、`msg_event`、`msg_internal_list`、`msg_internal_mark_read`、`platform-chat-system-message`、`platform-chat-runtime`、`platform-message-notification-custom-hook` 和必要索引。`wx_mp`、`wx_mini_program` 只交付物理表结构与表单字段元数据,不得携带数据集;否则既可能泄露真实公众号/小程序密钥,也会覆盖目标租户配置。`sys_user.WxMpId` 和 `wx_tpl_msg` 会读取 `wx_mp`,漏包会使 `/system/diy-user` 等无关页面在加载 Select 数据源时触发 `GetDiyFieldSqlData` 缺表错误。包内不得包含真实公众号 Token/AppSecret、用户接收人、OpenId、历史发送记录或租户专属 URL。先 `ValidateOnly`,再在全新或缺表目标租户真实安装,回读五张表、应用版本和依赖页面;结构校验不能替代真实安装验收。
117
-
118
- 应用包中的 `sys_apiengine.Id` 是跨应用共享物理表的稳定主键,必须在全部官方应用范围内全局唯一;不能只检查单包内 Key/Id。发布前必须同时扫描全部官方包的 `Id` 与 `ApiEngineKey`,任一跨包重复都应阻断发布和离线包生成。
119
-
120
- 系统聊天门面 `platform-chat-system-message`(`Managed`)完成平台超级管理员校验后,只转调本应用单一拥有的 `platform-chat-runtime`(`Managed`)。运行时统一编排 `PersistMessage / PersistSystemMessage / PersistAssistantMessage / GetHistoryAndMarkRead / GetUnreadCount / TouchContact / ListContacts / DeleteContact`;Hub/Controller 旧入口只保留 DiyToken 认证、SignalR 投递和 AI 流式协议,不得直连 MongoDB/FormEngine 复制业务。访问密钥会话、空 Token、伪造租户/发送人必须失败关闭。
121
-
122
- `platform-chat-runtime` 以 `V8.CurrentUser` / `V8.OsClient` 为唯一身份与租户事实源,对“租户 + 稳定 RequestId”生成确定性 Mongo `_id`;只有全部载荷哈希一致才复用旧记录。MongoDB 不参与 `V8.DbTrans`,因此消息/已读/联系人成功后即是已提交事实;SignalR、投影或 After Hook 失败必须保持 `Code=1` 并通过 `DataAppend.HookWarning` / `ProjectionWarnings` 告警,不得伪装未发生而引导盲目重试。调用 `V8.MongoDb.UptFormDataByWhere` / `DelFormDataByWhere` 时必须使用包含当前用户/权威资源边界的非空参数化 `_Where`,规则详见 `../v8-mongodb/SKILL.md`。
123
-
124
- `platform-chat-runtime` 必须保持 `StopHttp=1`、`AllowAnonymous=0`。SignalR Hub 在 DiyToken 与租户核验后,通过宿主一次性可信协议作用域调用,并携带宿主生成的权威当前用户快照;V8 对 `_InvokeType=Client` 的调用必须先执行 `V8.Method.RequireManagedProtocolContext()` 原子消费。不得为修复 Hub 误报“禁止 HTTP 调用”而开放 `StopHttp`,也不得接受 Param 中的信任布尔值、用户或租户覆盖;接口引擎内部 `Server` 嵌套调用保持原有语义。
125
-
126
- 租户个性化仅写入 `platform-message-notification-custom-hook`(`CreateIfMissing`),默认正文必须精确为 `return { Code : 1 };`。运行时在 `BeforeChatRuntime / AfterChatRuntime` 调用 Hook:Before 失败在 Mongo 写前阻断,After 失败只告警。Hook 只接收 `Stage`、`SourceApiEngineKey`、`Action`、`ActorUserId`、`PeerUserId`、`MessageId`、`MessageType`;正文、头像、OpenId、Token 与其它秘密不得进入租户扩展。三项接口的源码顶部都要保留官方恢复/租户不覆盖提示,并在包合同测试中逐字核对独立源码、包内副本、所有权策略和 HTTP/匿名开关。
127
-
128
- ## 平台提醒的使用与交付
129
-
130
- - 居中可关闭的公告、试用到期与定时提醒统一从“系统引擎 → 消息通知 → 系统公告”配置,使用 `platform-reminder-runtime` 和内置 `microi-platform-service` 的 `/platform-reminders` 页面。在同一页面选择单个、多个或全部租户及用户;不得再向 SaaS 引擎添加提醒按钮、提醒 Tab 或嵌入组件,也不得改为 `TableChild` 或在 V8 中拼接复杂 HTML。
131
- - 四张表分别是 `mci_platform_reminder` 草稿、`mci_platform_reminder_batch` 发布快照、`mci_platform_reminder_target` 接收映射和 `mci_platform_reminder_receipt` 关闭回执。普通客户端不能直接写表。保存草稿不发送;版本条件更新、批次稳定主键、接收映射与状态修改必须共享 `V8.DbTrans`,不要混用独立 `V8.Db.FromSql` 写入。
132
- - `Users` 面向当前租户用户,`Tenants` 仅允许主租户选择当前环境和网络的启用子租户,`Editions` 仅由宿主现有 License 发放判断确定官方身份。官方选项需明确选择 `OpenSource / Personal / Enterprise`,不得默认给全部版本发送。请求中的用户、租户、官方标志和产品版本不构成授权。
133
- - 试用提醒只绑定一个子租户,配置到期时间和提前分钟数,不改 License。所有提醒都必须有有效结束时间,支持 `Once / EveryEntry / AfterServerRestart`;定时支持一次、每日、每周和分钟间隔。每日/每周是固定时间间隔;时间传 UTC,界面显示浏览器时区;恢复上线不补弹所有历史周期。
134
- - 后端三个 Managed Key 为 `platform-reminder-runtime / platform-reminder-official-feed / platform-reminder-tick`。运行时动作包括 `Capabilities / Recipients / List / Get / Validate / Save / Publish / Withdraw / History / Inbox / Presented / Acknowledge`。发布需草稿 Id、`ExpectedRevision` 和稳定 `RequestId`;每次进入模式的收件箱和回执需稳定的本页面 `EntryId`。
135
- - `AccountScope=SuperAdmins` 由接收服务的真实 DiyToken、数据库用户 Level >= 9999 和有效本地角色核验,不限 admin;`AllAccounts` 面向全部帐号。缺省字段保留旧公告的全部帐号语义;新 UI 和 MCP 的跨租户/版本默认范围为超级管理员。普通服务器不能伪造官方 License 发放身份。
136
- - 自动授权提醒通过 `LicensePolicyGet/LicensePolicyValidate/LicensePolicySave` 配置,`ScopeType=Editions` 仅官方,`Tenants` 仅主租户。Policy 为 `{Personal:{AdvanceDays:7,Content:''},Enterprise:{AdvanceDays:7,Content:''}}`,天数 1–3650;空文案回退默认,支持 `{版本}/{到期时间}/{倒计时}`,分钟倒计时总会保留。保存携带 ExpectedRevision(首次 0)并回读,不通过普通公告 Save/Withdraw 修改策略。MCP 复用 get_notification_context(recipientScope)和 manage_system_reminder(scopeType、policy),确认串 `LicensePolicySave:<scopeType>`。
137
- - 接收端先检查固定官方源,再独立检查主租户对子租户的授权提醒。签名期限、子租户期限和超级管理员身份只接受宿主可信上下文;每次真实登录分别去重,未确认刷新继续出现,站内“查看授权”不能写确认回执。右上角版本标签固定在不足 7 天时显示秒级倒计时。验收覆盖两层同时触发、各自确认、30 天配置、空默认与非 admin 管理员。
138
- - `AfterServerRestart` 用可信后端进程启动标识和主租户当前运行分区的共享 Redis 最新批次计算 occurrence,`Presented` 按数据库稳定回执主键抢占领取资格;同帐号多页面只有一个领取,刷新和重新登录不再次弹出。展示成功前回执应已持久化;响应丢失以同一个页面 `EntryId` 恢复,不以 localStorage 作为事实源。`ShownAt` 与 `ClosedAt` 分离,当前已领取弹窗需保留到关闭、撤回或过期。
139
- - 超级管理员范围和重启频率的最低接收协议为 2;官方 feed 必须对协议 1 隐去此类公告,接收端出缓存后再次按当前身份裁剪。未知范围、异常协议或无法核验的启动批次失败关闭。重启频率不能再叠加周期计划;多节点滚动发布采用共享最新启动批次,验证负载切换不回退、不重复。
140
- - 定时任务 `platform-reminder-tick` 每分钟只发送唤醒信号;权威计划与回执保存在数据库。前端监听 `ReceivePlatformReminder` 后回读,SignalR 正常约 60 秒对账、断线约 15 秒轮询并对错误退避;关闭失败重试、撤回和过期收回,禁止使用本机定时器或 localStorage 作为已读事实源。
141
- - 跨服务器官方源由 `Microi.net` 固定请求 `api.itdos.com`,不允许任意 URL/重定向、不发送用户或密钥,按真实本地 License 筛选并缓存;官方源降级不得阻断本地提醒。匿名 feed 只暴露已发布的版本公告,不可读草稿、租户目标或回执。
142
- - 官方“消息通知”包单一拥有配置接口 `platform-message-notification-config`、三个提醒接口、四表六索引、统一菜单、每分钟任务与当前内置微服务产物;SaaS 包同步统一菜单、基础空库结构和旧布局退役声明。先更新支持 `DiyFieldRetirements` 的商城,再安装新版 SaaS 包,清理旧入口且保留业务数据。商城包的内置微服务版本需同步。导出母版可能不带物理索引,按已验证 Manifest 补独立 `CREATE INDEX`,由安装器幂等检查;禁止发布测试提醒、真实接收人或回执数据。
143
- - 共享微服务发布闭包还包含独立“系统日志/监控”包,不能只核对 SaaS、商城和消息通知。先读取相关商城选择清单与实际包,确认 `microi-platform-service` 的所有携带者;按 `platform-service-release.json` 校验同一版本、构建字节与路由。监控包从 `system-observability-package-source.json` 和当前共享运行包经 `configure-system-observability-package.mjs` 再生成,元数据变化要先合并官方母版并升独立包版本。发布前验证各包,安装全部平台应用后再验消息通知页面,避免后安装的旧运行包覆盖新页面;不能通过修改安装器的 Managed 覆盖语义规避。
144
- - MCP 复用 `microi_get_db_schema / microi_generate_system / microi_admin_table_data / microi_save_engine_code / microi_run_engine`,以及在线应用发现、源码同步和流式发布工具;入口调整使用 `microi_update_module / microi_update_table / microi_delete_field`,写后回读确认不存在旧入口。
145
- - 源码升级与应用安装缺一不可。验收覆盖正式 SDK 的 JSON 字符串响应、居中/拖动/关闭、每次刷新、单/多/全租户入口、权限隔离、重复发布、事务失败、到点/过期、断线轮询;多节点与实际其它服务器需要独立集成证据,不能由单机或模型测试替代。
146
-
147
- ## 最低验收
148
-
149
- 1. 两个 MCP 租户的字段、数据源、物理索引和三段接口代码回读一致。
150
- 2. 重复 `EventId`、重复接收人和两个 API 节点并发发送,持久副作用仅一次。
151
- 3. 事务回滚不推送;提交后在线用户即时收到,离线/SignalR/Redis 故障后登录仍能回读。
152
- 4. 用户只能查询和标记自己的通知;危险链接、超长正文、跨租户接收人和匿名调用被拒绝。
153
- 5. 公众号/服务号发送主体与小程序跳转目标分别验证,不把 `MiniProgramAppId` 当作模板发送主体。
154
- 6. 源码定向测试、后端编译、远端 MCP 回读、真实浏览器点击和商城安装/校验分别报告;未执行的生产发布不得写成已上线。
155
- 7. 聊天空 Token/访问密钥/伪造租户失败关闭;相同 `RequestId` 并发只有一份 Mongo 事实,不同载荷冲突拒绝;已持久后 SignalR/After Hook 失败仍返回成功并可回读。
1
+ ---
2
+ name: message-notification
3
+ description: 设计、实现、迁移和验收 Microi 多通道消息通知与平台提醒。用于平台提醒、SaaS 系统提醒、试用到期、维护公告、定时弹窗、官方版本提醒、wx_tpl_msg、mic_msgset、mic_msg_event_log、公众号模板消息、短信、邮件、V8.Notification、SignalR、msg_event、消息幂等或通知应用商城交付。
4
+ ---
5
+
6
+ > **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
7
+
8
+ # Microi 消息通知
9
+
10
+ ## 统一配置入口与 MCP
11
+
12
+ - 安装同一个 `app.microi.message-notification` 应用后,统一从“系统引擎 → 消息通知”进入系统公告、业务通知与投递记录。禁止再建独立“系统提醒”应用或向 SaaS 表单添加入口。旧 `/xiaoxitongzhisz` 配置并入业务通知,原 `mic_msgset` 和历史数据保留。
13
+ - 先用当前用户自己的 MCP 调用 `microi_get_notification_context`,读取 `Capabilities / BusinessRules / BusinessRule / Reminders / Reminder / Recipients / Templates / Adapters / Logs / History`。`Adapters` 按关键词分页发现本租户已启用的接口 Key,不读取源码或密钥。用户和角色只在当前租户读取,跨租户/产品版本仅选择 `AllAccounts / SuperAdmins`,不得读取其它服务器角色。
14
+ - `microi_configure_business_notification` 的 `Validate` 不写入、不发送;`Save` 写原通知表,通过固定 `platform-message-notification-config` 接口编排。确认串为 `Save:<id或Key>`;修改必须传 `expectedRevision`,不能把密钥写进 `ChannelApiEngineMap`。
15
+ - `microi_manage_system_reminder` 提供 `Validate / Save / Publish / Withdraw`,确认串为 `<action>:<id或requestId>`。保存仅草稿;发布前核对用户已经授权的具体内容、范围与时间。保存/发布使用稳定 `requestId`,编辑/发布/撤回传最新 `expectedRevision`,超时先按原 Id/Key 回读,禁止换请求标识盲目新建。
16
+ - `AccountScope` 在本租户 `Users` 默认 `AllAccounts`,在 `Tenants / Editions` 默认 `SuperAdmins`;不要让已指定的普通帐号被默认管理员筛选误排除。用户显式指定的帐号范围优先。
17
+ - 配置接口只管理原表、校验引用和读取脱敏投递记录;实际发送继续调用 `msg_event`。`ConfigRevision` 缺省按 0 兼容旧记录,更新使用同事务条件写,失败不能覆盖别人修改。停用旧配置允许保留失效引用,再启用时重新核验。
18
+ - MCP 缺少上述工具时优先更新本机插件/CLI;当前宿主未重载可复用已有 `microi_run_engine` 调用同一固定接口,不另建临时维护引擎或绕过授权。明确区分本机工具打包成功与用户渠道已发布。
19
+
20
+ ## 目标
21
+
22
+ 交付“配置可维护、事件先持久化、实时可降级、多节点不重复、租户不串线”的通知能力。支持微信公众号/服务号模板消息、短信、邮件和平台内部通知;小程序是公众号模板消息的跳转目标,不是独立的公众号发送主体。
23
+
24
+ ## 开始前
25
+
26
+ 1. 读取工作区 `AGENTS.md`,并按任务同时读取 `microi-db-schema`、`v8-api-config`、`v8-frontend-events`、`microi-client-frontend`;涉及商城时再读 `app-store`,涉及浏览器时再读 `playwright-e2e`。
27
+ 2. 用用户点名的 MCP 连接读取实时结构,不以本地字典替代远端事实。至少读取 `wx_tpl_msg`、`mic_msgset`、`mic_msg_event_log`,按需读取 `wx_mp`、`wx_mini_program`、`sys_menu` 和接口引擎。
28
+ 3. 多租户比较按字段语义合并:保留双方新增字段、控件、说明和数据源,再把并集同步到双方。每次写入后重新读取字段、物理索引和接口源码。
29
+ 4. 只有用户明确要求时才复制渠道配置。复制微信公众号/小程序密钥时不在输出中打印秘密;模板、发送主体和小程序跳转引用必须一起回读验证。
30
+
31
+ ## 核心模型
32
+
33
+ - `mic_msgset`:通知策略。`Key` 是稳定业务键,`Type` 是多选渠道,`ChannelApiEngineMap` 配置短信、邮件或自定义渠道适配器。
34
+ - `wx_tpl_msg`:微信公众号/服务号模板。`WxMpId` 决定发送主体;`MiniProgramId`、`MiniProgramAppId`、`MiniProgramPagePath` 仅表示点击模板消息后跳入的小程序。
35
+ - `mic_msg_event_log`:每位接收人、每个渠道的权威事件记录。至少包含稳定 `EventId`、`ChannelType`、`ReceiverUserId`、标题、内容、链接、Payload、已读状态和结果。
36
+ - 唯一约束:`EventId + ChannelType + ReceiverUserId`。租户使用独立业务库时表内无需虚构 `OsClient` 字段;共享库模型则必须把租户键加入唯一约束。
37
+
38
+ 完整字段、接口和可靠性契约见 [references/contracts.md](references/contracts.md)。
39
+
40
+ ## 实现流程
41
+
42
+ ### 1. 合并结构
43
+
44
+ 对两个租户分别读取字段列表,按 `Name` 生成差异表。新增缺失字段后刷新缓存,并回读:
45
+
46
+ - `mic_msgset.Type` 包含 `微信公众号模板消息`、`短信`、`邮件`、`平台内部`;
47
+ - `mic_msgset.ChannelApiEngineMap` 为 JSON 对象;
48
+ - `wx_tpl_msg` 同时有 `WxMpId/WxMpName` 和小程序跳转字段;
49
+ - `mic_msg_event_log` 有完整的事件、接收人、渠道、内容和已读字段;
50
+ - 业务唯一索引与常用未读查询索引存在。
51
+
52
+ 不要用一次性 SQL 修某个租户而跳过通用表单/资源升级路径。应用包与平台升级资源必须携带同一结构。
53
+
54
+ ### 2. 配置发送策略
55
+
56
+ `mic_msgset.Key` 对业务长期稳定。接收人可以来自固定用户、角色和调用参数,必须去重并限制扇出。渠道适配器统一接收:
57
+
58
+ ```js
59
+ {
60
+ EventId: '业务稳定幂等键',
61
+ ChannelType: '短信',
62
+ User: { Id: '...', Phone: '...', Email: '...', WxOpenId: '...' },
63
+ Title: '审批提醒',
64
+ Content: '您有一条待审批记录',
65
+ LinkUrl: '/#/approval/123',
66
+ Payload: { BusinessId: '123' }
67
+ }
68
+ ```
69
+
70
+ 适配器必须按 `EventId` 幂等。不要把密钥放进 `ChannelApiEngineMap` 或 Payload;密钥保存在对应渠道配置表或租户安全配置中。
71
+
72
+ ### 3. 后端发送
73
+
74
+ 业务代码优先调用 `msg_event`,由它读取策略、解析接收人、原子登记日志后分发。调用方在重试时保持同一个 `EventId`:
75
+
76
+ ```js
77
+ return V8.ApiEngine.Run('msg_event', {
78
+ MsgKey: 'order_wait_approve',
79
+ EventId: 'order-wait-approve-' + V8.Param.OrderId,
80
+ ReceiverUserIds: [V8.Param.ApproverId],
81
+ Content: '订单 ' + V8.Param.OrderNo + ' 等待审批',
82
+ LinkUrl: '/#/orders/detail?id=' + V8.Param.OrderId,
83
+ Payload: { OrderId: V8.Param.OrderId }
84
+ }, V8.DbTrans);
85
+ ```
86
+
87
+ `V8.Notification.Send` 是宿主的“平台内部实时提示”原语。它不替代日志 claim,通常只由 `msg_event` 在日志成功登记后调用。事务存在时,推送在提交后进行有界等待;回滚不得推送。
88
+
89
+ ### 4. 前端通知中心
90
+
91
+ 前端 V8 使用 `V8.Notification.List` 获取当前登录用户的权威快照,使用 `MarkRead` 标记本人通知。SignalR 固定事件 `ReceivePlatformNotification` 只用于低延迟刷新:客户端按 `Id/EventId` 去重,收到后仍以列表接口回读为准。
92
+
93
+ ```js
94
+ await V8.Notification.Send('order_wait_approve', {
95
+ EventId: 'order-wait-approve-' + V8.Form.Id,
96
+ ReceiverUserIds: [V8.Form.ApproverId],
97
+ Content: '订单等待审批'
98
+ });
99
+
100
+ var result = await V8.Notification.List({ PageIndex: 1, PageSize: 20 });
101
+ await V8.Notification.MarkRead(result.Data[0].Id);
102
+ await V8.Notification.MarkRead({ All: true });
103
+ ```
104
+
105
+ 列表和已读接口必须以 `V8.CurrentUser.Id` 作为服务端过滤条件,不能信任客户端传入的用户 Id。外链只允许站内路径、锚点或 `http/https`。
106
+
107
+ ### 5. 多节点可靠性
108
+
109
+ - 数据库日志是事实源,SignalR 是可丢失提示;Redis backplane 使任一节点能通知连接在其它节点的用户。
110
+ - “先查询再新增”不能防并发;依赖唯一索引抢占。同一事件重复投递只能产生一份 `EventId + 渠道 + 接收人` 记录。
111
+ - 外部供应商在“已发送但响应丢失”时无法凭本地状态保证恰好一次。适配器必须把 `EventId` 传给支持幂等的供应商;不支持时进入可审计的人工确认/重试状态。
112
+ - 发布中新旧版本短暂共存,先扩展字段与接口,再发布读写代码,最后才收缩旧字段。
113
+
114
+ ## 应用商城交付
115
+
116
+ “消息通知”应用包必须包含 `mic_msgset`、`mic_msg_event_log`、`wx_tpl_msg`、`wx_mp`、`wx_mini_program` 五张结构资源,以及相关菜单、`msg_event`、`msg_internal_list`、`msg_internal_mark_read`、`platform-chat-system-message`、`platform-chat-runtime`、`platform-message-notification-custom-hook` 和必要索引。`wx_mp`、`wx_mini_program` 只交付物理表结构与表单字段元数据,不得携带数据集;否则既可能泄露真实公众号/小程序密钥,也会覆盖目标租户配置。`sys_user.WxMpId` 和 `wx_tpl_msg` 会读取 `wx_mp`,漏包会使 `/system/diy-user` 等无关页面在加载 Select 数据源时触发 `GetDiyFieldSqlData` 缺表错误。包内不得包含真实公众号 Token/AppSecret、用户接收人、OpenId、历史发送记录或租户专属 URL。先 `ValidateOnly`,再在全新或缺表目标租户真实安装,回读五张表、应用版本和依赖页面;结构校验不能替代真实安装验收。
117
+
118
+ 应用包中的 `sys_apiengine.Id` 是跨应用共享物理表的稳定主键,必须在全部官方应用范围内全局唯一;不能只检查单包内 Key/Id。发布前必须同时扫描全部官方包的 `Id` 与 `ApiEngineKey`,任一跨包重复都应阻断发布和离线包生成。
119
+
120
+ 系统聊天门面 `platform-chat-system-message`(`Managed`)完成平台超级管理员校验后,只转调本应用单一拥有的 `platform-chat-runtime`(`Managed`)。运行时统一编排 `PersistMessage / PersistSystemMessage / PersistAssistantMessage / GetHistoryAndMarkRead / GetUnreadCount / TouchContact / ListContacts / DeleteContact`;Hub/Controller 旧入口只保留 DiyToken 认证、SignalR 投递和 AI 流式协议,不得直连 MongoDB/FormEngine 复制业务。访问密钥会话、空 Token、伪造租户/发送人必须失败关闭。
121
+
122
+ `platform-chat-runtime` 以 `V8.CurrentUser` / `V8.OsClient` 为唯一身份与租户事实源,对“租户 + 稳定 RequestId”生成确定性 Mongo `_id`;只有全部载荷哈希一致才复用旧记录。MongoDB 不参与 `V8.DbTrans`,因此消息/已读/联系人成功后即是已提交事实;SignalR、投影或 After Hook 失败必须保持 `Code=1` 并通过 `DataAppend.HookWarning` / `ProjectionWarnings` 告警,不得伪装未发生而引导盲目重试。调用 `V8.MongoDb.UptFormDataByWhere` / `DelFormDataByWhere` 时必须使用包含当前用户/权威资源边界的非空参数化 `_Where`,规则详见 `../v8-mongodb/SKILL.md`。
123
+
124
+ `platform-chat-runtime` 必须保持 `StopHttp=1`、`AllowAnonymous=0`。SignalR Hub 在 DiyToken 与租户核验后,通过宿主一次性可信协议作用域调用,并携带宿主生成的权威当前用户快照;V8 对 `_InvokeType=Client` 的调用必须先执行 `V8.Method.RequireManagedProtocolContext()` 原子消费。不得为修复 Hub 误报“禁止 HTTP 调用”而开放 `StopHttp`,也不得接受 Param 中的信任布尔值、用户或租户覆盖;接口引擎内部 `Server` 嵌套调用保持原有语义。
125
+
126
+ 租户个性化仅写入 `platform-message-notification-custom-hook`(`CreateIfMissing`),默认正文必须精确为 `return { Code : 1 };`。运行时在 `BeforeChatRuntime / AfterChatRuntime` 调用 Hook:Before 失败在 Mongo 写前阻断,After 失败只告警。Hook 只接收 `Stage`、`SourceApiEngineKey`、`Action`、`ActorUserId`、`PeerUserId`、`MessageId`、`MessageType`;正文、头像、OpenId、Token 与其它秘密不得进入租户扩展。三项接口的源码顶部都要保留官方恢复/租户不覆盖提示,并在包合同测试中逐字核对独立源码、包内副本、所有权策略和 HTTP/匿名开关。
127
+
128
+ ## 平台提醒的使用与交付
129
+
130
+ - 居中可关闭的公告、试用到期与定时提醒统一从“系统引擎 → 消息通知 → 系统公告”配置,使用 `platform-reminder-runtime` 和内置 `microi-platform-service` 的 `/platform-reminders` 页面。在同一页面选择单个、多个或全部租户及用户;不得再向 SaaS 引擎添加提醒按钮、提醒 Tab 或嵌入组件,也不得改为 `TableChild` 或在 V8 中拼接复杂 HTML。
131
+ - 四张表分别是 `mci_platform_reminder` 草稿、`mci_platform_reminder_batch` 发布快照、`mci_platform_reminder_target` 接收映射和 `mci_platform_reminder_receipt` 关闭回执。普通客户端不能直接写表。保存草稿不发送;版本条件更新、批次稳定主键、接收映射与状态修改必须共享 `V8.DbTrans`,不要混用独立 `V8.Db.FromSql` 写入。
132
+ - `Users` 面向当前租户用户,`Tenants` 仅允许主租户选择当前环境和网络的启用子租户,`Editions` 仅由宿主现有 License 发放判断确定官方身份。官方选项需明确选择 `OpenSource / Personal / Enterprise`,不得默认给全部版本发送。请求中的用户、租户、官方标志和产品版本不构成授权。
133
+ - 试用提醒只绑定一个子租户,配置到期时间和提前分钟数,不改 License。所有提醒都必须有有效结束时间,支持 `Once / EveryEntry / AfterServerRestart`;定时支持一次、每日、每周和分钟间隔。每日/每周是固定时间间隔;时间传 UTC,界面显示浏览器时区;恢复上线不补弹所有历史周期。
134
+ - 后端三个 Managed Key 为 `platform-reminder-runtime / platform-reminder-official-feed / platform-reminder-tick`。运行时动作包括 `Capabilities / Recipients / List / Get / Validate / Save / Publish / Withdraw / History / Inbox / Presented / Acknowledge`。发布需草稿 Id、`ExpectedRevision` 和稳定 `RequestId`;每次进入模式的收件箱和回执需稳定的本页面 `EntryId`。
135
+ - `AccountScope=SuperAdmins` 的提醒接收资格由接收服务的真实 DiyToken 与主库有效用户 Level >= 9999 双重核验,不限 admin 或指定角色;配置写权限继续额外核验有效管理员角色。`AllAccounts` 面向全部帐号。缺省字段保留旧公告的全部帐号语义;新 UI 和 MCP 的跨租户/版本默认范围为超级管理员。普通服务器不能伪造官方 License 发放身份。
136
+ - 自动授权提醒通过 `LicensePolicyGet/LicensePolicyValidate/LicensePolicySave` 配置,`ScopeType=Editions` 仅官方,`Tenants` 仅主租户。Policy 为 `{Personal:{AdvanceDays:7,Content:''},Enterprise:{AdvanceDays:7,Content:''}}`,天数 1–3650;空文案回退默认,支持 `{版本}/{到期时间}/{倒计时}`,分钟倒计时总会保留。保存携带 ExpectedRevision(首次 0)并回读,不通过普通公告 Save/Withdraw 修改策略。MCP 复用 get_notification_context(recipientScope)和 manage_system_reminder(scopeType、policy),确认串 `LicensePolicySave:<scopeType>`。
137
+ - 接收端先检查固定官方源,再独立检查主租户对子租户的授权提醒。签名期限、子租户期限和超级管理员身份只接受宿主可信上下文;每次真实登录分别去重,未确认刷新继续出现,站内“查看授权”不能写确认回执。右上角版本标签固定在不足 7 天时显示秒级倒计时。验收覆盖两层同时触发、各自确认、30 天配置、空默认与非 admin 管理员。
138
+ - 开源版无付费到期日;`DateTime.MinValue`(`0001-01-01`)不是已到期授权,不得生成“系统授权已到期”提醒或按付费版向官方源请求。官方 OpenSource 欢迎公告沿用已发布的 `Editions + OpenSource + SuperAdmins + AfterServerRestart` 规则,不另建硬编码弹窗;每名有效 `Level >= 9999` 帐号分别领取、确认,同批次不重复,下次 API 进程启动批次重新领取。真实已验签且有有效日期的付费合同到期后,仍保留其付费到期提醒。
139
+ - `AfterServerRestart` 用可信后端进程启动标识和主租户当前运行分区的共享 Redis 最新批次计算 occurrence,`Presented` 按数据库稳定回执主键抢占领取资格;同帐号多页面只有一个领取,刷新和重新登录不再次弹出。展示成功前回执应已持久化;响应丢失以同一个页面 `EntryId` 恢复,不以 localStorage 作为事实源。`ShownAt` 与 `ClosedAt` 分离,当前已领取弹窗需保留到关闭、撤回或过期。
140
+ - 超级管理员范围和重启频率的最低接收协议为 2;官方 feed 必须对协议 1 隐去此类公告,接收端出缓存后再次按当前身份裁剪。未知范围、异常协议或无法核验的启动批次失败关闭。重启频率不能再叠加周期计划;多节点滚动发布采用共享最新启动批次,验证负载切换不回退、不重复。
141
+ - 定时任务 `platform-reminder-tick` 每分钟只发送唤醒信号;权威计划与回执保存在数据库。前端监听 `ReceivePlatformReminder` 后回读,SignalR 正常约 60 秒对账、断线约 15 秒轮询并对错误退避;关闭失败重试、撤回和过期收回,禁止使用本机定时器或 localStorage 作为已读事实源。
142
+ - 跨服务器官方源由 `Microi.net` 固定请求 `api.itdos.com`,不允许任意 URL/重定向、不发送用户或密钥,按真实本地 License 筛选并缓存;官方源降级不得阻断本地提醒。匿名 feed 只暴露已发布的版本公告,不可读草稿、租户目标或回执。
143
+ - 官方“消息通知”包单一拥有配置接口 `platform-message-notification-config`、三个提醒接口、四表六索引、统一菜单、每分钟任务与当前内置微服务产物;SaaS 包同步统一菜单、基础空库结构和旧布局退役声明。先更新支持 `DiyFieldRetirements` 的商城,再安装新版 SaaS 包,清理旧入口且保留业务数据。商城包的内置微服务版本需同步。导出母版可能不带物理索引,按已验证 Manifest 补独立 `CREATE INDEX`,由安装器幂等检查;禁止发布测试提醒、真实接收人或回执数据。
144
+ - 共享微服务发布闭包还包含独立“系统日志/监控”包,不能只核对 SaaS、商城和消息通知。先读取相关商城选择清单与实际包,确认 `microi-platform-service` 的所有携带者;按 `platform-service-release.json` 校验同一版本、构建字节与路由。监控包从 `system-observability-package-source.json` 和当前共享运行包经 `configure-system-observability-package.mjs` 再生成,元数据变化要先合并官方母版并升独立包版本。发布前验证各包,安装全部平台应用后再验消息通知页面,避免后安装的旧运行包覆盖新页面;不能通过修改安装器的 Managed 覆盖语义规避。
145
+ - MCP 复用 `microi_get_db_schema / microi_generate_system / microi_admin_table_data / microi_save_engine_code / microi_run_engine`,以及在线应用发现、源码同步和流式发布工具;入口调整使用 `microi_update_module / microi_update_table / microi_delete_field`,写后回读确认不存在旧入口。
146
+ - 源码升级与应用安装缺一不可。验收覆盖正式 SDK 的 JSON 字符串响应、居中/拖动/关闭、每次刷新、单/多/全租户入口、权限隔离、重复发布、事务失败、到点/过期、断线轮询;多节点与实际其它服务器需要独立集成证据,不能由单机或模型测试替代。
147
+
148
+ ## 最低验收
149
+
150
+ 1. 两个 MCP 租户的字段、数据源、物理索引和三段接口代码回读一致。
151
+ 2. 重复 `EventId`、重复接收人和两个 API 节点并发发送,持久副作用仅一次。
152
+ 3. 事务回滚不推送;提交后在线用户即时收到,离线/SignalR/Redis 故障后登录仍能回读。
153
+ 4. 用户只能查询和标记自己的通知;危险链接、超长正文、跨租户接收人和匿名调用被拒绝。
154
+ 5. 公众号/服务号发送主体与小程序跳转目标分别验证,不把 `MiniProgramAppId` 当作模板发送主体。
155
+ 6. 源码定向测试、后端编译、远端 MCP 回读、真实浏览器点击和商城安装/校验分别报告;未执行的生产发布不得写成已上线。
156
+ 7. 聊天空 Token/访问密钥/伪造租户失败关闭;相同 `RequestId` 并发只有一份 Mongo 事实,不同载荷冲突拒绝;已持久后 SignalR/After Hook 失败仍返回成功并可回读。
@@ -89,9 +89,13 @@ UniApp 使用 Vue 3 + TypeScript 的官方 Vite 工具链,并同时遵守 `mic
89
89
  3. 检查 `dist/build` 不含源码、Token、密钥、localhost、source map 或陈旧 chunk。
90
90
  4. 同步私有源码,再流式发布公有构建目录;源码同步失败不得继续发布。发布前回读并冻结应用的 `CurrentVersion` 与 `AppVersion`,stage 只上传不可变版本资产,finalize 必须同时提交 `ExpectedCurrentVersion` 与 `ExpectedAppVersion` 做 compare-and-set;缺一项、状态漂移或回读不一致都停止,不能自动覆盖较新发布。
91
91
  5. 每次创建、修改、升级或重新发布 AI 应用,必须在任何源码同步、stage、finalize 或商城制包之前,为目标精确 `AppVersion` 写入 `sys_microistore_changelog`。日志的 `StoreId / Version / Title / ChangeType / Content / ReleaseTime` 必须完整;发布工具显式传入含义一致且非空的 `changeSummary`,发布后同时回读商城子表与 `mci_ai_app_version.ChangeSummary`。缺日志或版本不一致必须停止发布。
92
- 6. Web/UniApp 使用 `/{OsClient}/ai-app-publish/{AppKey}/index.html`;MicroService 使用 `/micro-app/{OsClient}/{AppKey}/index.html`。不要因技术栈相同而混淆运行类型。
93
- 7. 官网、二维码、分享链接和商城“立即体验”只能使用不含 `/releases/`、`/requests/`、`/versions/` 与语义版本号的稳定当前入口;不得使用 `SharedPublicRuntime.EntryUrl` 或发布结果中的不可变版本 URL。固定入口必须以代理或全屏加载壳保持浏览器地址不变,不能用 30x、`meta refresh` 或 `location.replace` 把地址栏跳到版本产物。
94
- 8. `SharedPublicRuntime.EntryUrl` 和 `/versions/{Version}/index.html` 仅用于历史记录、回滚、摘要校验与审计。回读应用、版本、active 文件清单和 SHA-256;旧清单文件只能可逆归档,不能删除。再分别直接请求稳定当前入口、不可变版本入口及主要 JS/CSS,并断言前者完成加载后地址栏仍不含版本段。
92
+ 6. 官方 Web、UniApp、MicroService 的体验地址统一为 `https://static.itdos.com/{OsClient小写}/micro-app/{AppKey}/index.html`,公有桶对象键与域名后的路径完全一致;不再按运行类型分叉到 `ai-app-publish`,也不把 v3 内部 API resolver 用作公开体验地址。当前版本的全部编译文件写入该应用固定根,历史版本写入同根的 `/{Version}/` 目录;历史目录一旦验证不得覆写成不同字节。
93
+ 7. 同一版本私有源码文件使用相同的租户、应用、版本相对路径写入私有桶;固定根保存最近一次已完成发布的源码。确实不含源码的编译包在包声明中记录 `Source=NotIncluded`,运行时版本的 `SourceSnapshotPath` 保持空值,不得从公有产物伪造源码。先校验完整公有版本与私有源码快照,再提升固定根的非入口资产和 `index.html`;固定入口切换后提交 CDN 精确路径刷新,回读刷新任务终态和公有入口及引用资源,再更新商城 `PreviewUrl/PublicPublishPath`。刷新任务仅提交成功、单个 CDN 节点 200 或本地构建成功都不算完成。
94
+ 8. 官网、二维码、分享链接和商城“立即体验”只使用固定根 `index.html`;版本目录仅供回滚与显式历史预览。CDN 直接读取公有桶对象,不要求其做动态版本解析或反向代理。发布器必须使 HTML 引用的 JS/CSS 在切换时已存在,并验证从 `static.itdos.com` 打开的应用仍把业务 API 请求发往目标租户的 `ApiBase`。
95
+ v3 的 `sys_microistore.PreviewUrl/PublicPublishPath` 和版本 `PreviewUrl` 在数据库内保留以 `/` 开头的对象路径,后端完成态检查会与投影路径逐字比较;官网接口与商城工作台对外展示时使用租户 `FileServer` 转为完整 CDN URL。不得为统一展示直接把这些 v3 内部字段改写成绝对 URL。
96
+ 9. 新的官方 Web、UniApp、MicroService 发布统一使用支持固定 CDN 投影的 v3 目录流式发布;旧 `ai_app_build` 只保留历史兼容和迁移读取,不作为新版本发布入口。目标 API 的 `ApplicationCdnProjectionSupported` 未启用时先部署后端并停止新发布,不回退到 `ai-app-publish`。
97
+ 10. 官方 `static.itdos.com` 的刷新凭据从当前租户后端系统设置 `Integration.Cdn.Aliyun.*` 读取,兼容旧的 `Integration.Dns.Aliyun.*` 与 SaaS `AlidnsKeyId/AlidnsKeySecret`;必须成对配置并具备刷新及任务查询权限。刷新任务可能合并多个 URL 到同一任务号,应以全部任务 `Complete` 和 CDN 文件哈希回读为准;不得仅凭提交成功切换商城入口。批量发布须考虑 CDN 每日刷新配额。
98
+ 11. `SharedPublicRuntime.EntryUrl` 和历史版本目录仅用于历史记录、回滚、摘要校验与审计。回读应用、版本、active 文件清单和 SHA-256;旧清单文件只能可逆归档,不能删除。再分别直接请求稳定当前入口、不可变版本入口及主要 JS/CSS,并断言前者完成加载后地址栏仍不含版本段。
95
99
 
96
100
  ## 完成定义
97
101
 
@@ -5,13 +5,13 @@ description: Microi.Client 源码架构指南。用于修改 Microi.Client Vue
5
5
 
6
6
  > **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
7
7
 
8
- # Microi.Client 前台源码架构说明
9
-
10
- 表单设计保存请求必须使用 `DiyCommon.GetOsClient()`,不能将导入表定义残留的 `OsClient`
11
- 作为传输租户;元数据来源与当前会话租户是不同概念。回归应覆盖从另一租户迁入的表定义,
12
- 验证表和字段保存成功且会话保持有效,不通过放宽后端租户鉴权修复。
13
- 右上角授权类型优先读取公开启动配置 `SysConfig.PlatformEdition`,兼容旧版会话查询;
14
- `SysConfig.HideSystemLicenseVersion` 缺省关闭,标签可点击跳转 `/license`,不能因一次请求失败永久消失。
8
+ # Microi.Client 前台源码架构说明
9
+
10
+ 表单设计保存请求必须使用 `DiyCommon.GetOsClient()`,不能将导入表定义残留的 `OsClient`
11
+ 作为传输租户;元数据来源与当前会话租户是不同概念。回归应覆盖从另一租户迁入的表定义,
12
+ 验证表和字段保存成功且会话保持有效,不通过放宽后端租户鉴权修复。
13
+ 右上角授权类型优先读取公开启动配置 `SysConfig.PlatformEdition`,兼容旧版会话查询;
14
+ `SysConfig.HideSystemLicenseVersion` 缺省关闭,标签可点击跳转 `/license`,不能因一次请求失败永久消失。
15
15
 
16
16
  <!-- microi-progressive:begin -->
17
17
  <!-- microi-progressive:chunk id=microi-client-frontend-000 sha256=9b949c68b0867fc1ecf2e6cb1fd1bec45d22c01ad3be63485e0376d0183d1795 -->
@@ -28,17 +28,17 @@ description: Microi.Client 源码架构指南。用于修改 Microi.Client Vue
28
28
 
29
29
  <!-- /microi-progressive:chunk -->
30
30
  <!-- microi-progressive:chunk id=microi-client-frontend-001 sha256=e9667a3a534e09b964c4a11796c003b48edc4093c66ed0c8777aefd4188a4ba8 -->
31
- ## 1. 技术栈和源码入口
32
-
33
- ### 详情评论与代码版本的按需读取
34
-
35
- - 评论用 `/api/FormEngine/AddFormComment`,只传 `ParentFormEngineKey/ParentTableRowId/_SysMenuId/Content/ParentCommentId/RequestId`。服务端固定作者、父表及回复上下文;同一提交重试必须复用 RequestId,兼容 GUID 与平台 `DiyCommon.NewGuid()` 实际生成的 ULID。
36
- - 评论归属为 `diy_comment.ParentTableId + TableRowId`,新表字段与复合索引通过表单引擎应用交付,主库回读保证写后可见。已有 TableId 绑定兼容,空归属历史评论禁止仅按记录 Id 自动猜测或后台回填。
37
- - `/api/FormEngine/GetFormRelatedData` 的版本列表只含元信息;`HistoryContentMode=OnDemand` 时,点击动作再传 `VersionId` 读取一条,并要求返回 `Authorized`。不要把 Data 缓存到列表,不要并发预取全部正文。
38
- - 代码历史只向当前有效的平台管理员开放代码字段白名单;当前行可读不等于历史秘密可读。局部版本比较/加载只涉及快照中的字段,保存继续走正常表单鉴权和事件。
39
- - 不得为了修复右栏恢复调度器通用 FormEngine 状态写回、启动历史回填或无变化版本写入。必须同时运行评论归属/去重、按需读取以及 ScheduleRuntimeTimeWriter、V8CodeVersionService 的性能回归。
40
-
41
- - Vue 3 + Options API + mixins,构建工具是 Vite。
31
+ ## 1. 技术栈和源码入口
32
+
33
+ ### 详情评论与代码版本的按需读取
34
+
35
+ - 评论用 `/api/FormEngine/AddFormComment`,只传 `ParentFormEngineKey/ParentTableRowId/_SysMenuId/Content/ParentCommentId/RequestId`。服务端固定作者、父表及回复上下文;同一提交重试必须复用 RequestId,兼容 GUID 与平台 `DiyCommon.NewGuid()` 实际生成的 ULID。
36
+ - 评论归属为 `diy_comment.ParentTableId + TableRowId`,新表字段与复合索引通过表单引擎应用交付,主库回读保证写后可见。已有 TableId 绑定兼容,空归属历史评论禁止仅按记录 Id 自动猜测或后台回填。
37
+ - `/api/FormEngine/GetFormRelatedData` 的版本列表只含元信息;`HistoryContentMode=OnDemand` 时,点击动作再传 `VersionId` 读取一条,并要求返回 `Authorized`。不要把 Data 缓存到列表,不要并发预取全部正文。
38
+ - 代码历史只向当前有效的平台管理员开放代码字段白名单;当前行可读不等于历史秘密可读。局部版本比较/加载只涉及快照中的字段,保存继续走正常表单鉴权和事件。
39
+ - 不得为了修复右栏恢复调度器通用 FormEngine 状态写回、启动历史回填或无变化版本写入。必须同时运行评论归属/去重、按需读取以及 ScheduleRuntimeTimeWriter、V8CodeVersionService 的性能回归。
40
+
41
+ - Vue 3 + Options API + mixins,构建工具是 Vite。
42
42
  - UI 主要使用 Element Plus、FontAwesome、项目内 `dynamic-icon`。
43
43
  - 状态入口:`src/pinia`,常用 `useDiyStore()` 读取 `GetCurrentUser`、`OsClient`、终端类型等。
44
44
  - 低代码主入口集中在 `src/views/form-engine/`,不要只看一个 `.vue` 文件就下结论。
@@ -58,7 +58,7 @@ description: Microi.Client 源码架构指南。用于修改 Microi.Client Vue
58
58
  ---
59
59
 
60
60
  <!-- /microi-progressive:chunk -->
61
- <!-- microi-progressive:chunk id=microi-client-frontend-002 sha256=9fae6ea3c3b238d5d830f308599d7512d1ed6364cb495d432278ada8680f769e -->
61
+ <!-- microi-progressive:chunk id=microi-client-frontend-002 sha256=c8e47f78e3835be000ad627874908d644025f81e5047ee5c9fd9c14161c9fea5 -->
62
62
  ## 2. 表单引擎三层结构
63
63
 
64
64
  ### 模块级跨端视图
@@ -155,6 +155,8 @@ description: Microi.Client 源码架构指南。用于修改 Microi.Client Vue
155
155
 
156
156
  ### 用户级界面偏好(强制)
157
157
 
158
+ 右上角主题设置的边角风格默认圆角;已安装 `sys_user.CornerStyle` 时从账号偏好恢复,旧租户才使用浏览器本地兼容值。根元素 `data-mci-corner-style` 控制全局角半径,覆盖动态挂载的 Element Plus 弹层。顶栏入口统一使用 40px 触控区域和 20px 图标,AI 入口使用图标组件,不使用图片。模块 `HideTableBanner`、`HideFormBanner` 只在值明确为 `1/true` 时隐藏对应 Banner,并跳过其专属统计请求;未安装开关字段的旧菜单继续显示。
159
+
158
160
  - 主题色、浅色/深色、菜单子级展开方式等需要“换设备仍生效”的选择必须保存到当前 DiyToken 用户的 `sys_user` 白名单字段;`localStorage` 只作为未安装新字段租户和匿名启动阶段的兼容回退,不能作为跨设备事实源。
159
161
  - 已安装用户偏好字段时优先级固定为“当前用户显式值 → 租户 `sys_config` → 平台安全默认”;个人菜单值 `System` 表示继承租户配置。不得让上一位用户的浏览器本地主题覆盖下一位已登录用户。
160
162
  - 自助保存优先使用官方 `Managed` 接口引擎,由 `V8.CurrentUser.Id` 与 `V8.OsClient` 推导用户和租户,并在服务端构造固定白名单更新对象;接口参数禁止决定目标 Id/OsClient,也禁止写入 Account、Phone、Tenant、Dept、Role、Level、State、Pwd、认证因子和登录审计字段。只有缺少可复用可信原子能力时才新增 C# DTO/端点。保存成功后调用 `V8.Method.RefreshLoginUser` 刷新登录缓存,并让微服务宿主重新同步 `CurrentUser`。
@@ -188,24 +190,24 @@ description: Microi.Client 源码架构指南。用于修改 Microi.Client Vue
188
190
  <!-- microi-progressive:chunk id=microi-client-frontend-004 sha256=174028eca01dc0987236803041ab5ac1bf18705a6609a4ba640df0c553533c1e -->
189
191
  ## 7. 验证建议
190
192
 
191
- ### 本地 ApiBase 与 OsClient 解析(强制)
192
-
193
- - 域名发现先于租户确定,固定 `platform-os-client-by-domain` 请求不能自动注入默认或缓存租户,也不能携带旧 Token;显式调用租户仍保持。没有 URL/index.html 租户时,每次冷启动重新发现域名,成功后更新缓存,失败立即停止,不写入 `iTdos` 继续请求配置。配置业务失败不是连接故障,健康检查成功不能触发循环重载;只有确认连接故障后的恢复才重载失败启动页。回归覆盖无缓存、错误缓存、显式租户及失败不重载,使用 `domain-tenant-bootstrap.spec.mjs`。
194
- - 登录回跳使用 Vue Router 解析完整深链接后合并 query/hash;对象式 `push({path, query})` 的 query 会覆盖 path 中原有参数,不能直接传空对象导致记录 Id 丢失。使用 `login-deep-link-query.spec.mjs` 验证编号、编码参数、重复参数和锚点。
193
+ ### 本地 ApiBase 与 OsClient 解析(强制)
194
+
195
+ - 域名发现先于租户确定,固定 `platform-os-client-by-domain` 请求不能自动注入默认或缓存租户,也不能携带旧 Token;显式调用租户仍保持。没有 URL/index.html 租户时,每次冷启动重新发现域名,成功后更新缓存,失败立即停止,不写入 `iTdos` 继续请求配置。配置业务失败不是连接故障,健康检查成功不能触发循环重载;只有确认连接故障后的恢复才重载失败启动页。回归覆盖无缓存、错误缓存、显式租户及失败不重载,使用 `domain-tenant-bootstrap.spec.mjs`。
196
+ - 登录回跳使用 Vue Router 解析完整深链接后合并 query/hash;对象式 `push({path, query})` 的 query 会覆盖 path 中原有参数,不能直接传空对象导致记录 Id 丢失。使用 `login-deep-link-query.spec.mjs` 验证编号、编码参数、重复参数和锚点。
197
+
198
+ - `app.use(router)` 会立即触发首个路由守卫。守卫中的 SSO、认证、菜单请求必须等待 `initApp()` 完成真实租户和系统配置初始化;初始化失败时取消导航并保留启动错误界面。释放初始化等待必须早于 `router.isReady()`,避免彼此等待;禁止将缓存尚未建立时的 `GetOsClient()` 默认值 `iTdos` 发给客户服务器。
199
+
200
+ - 身份和菜单只读初始化的短暂失败最多读取三次,间隔 300/1000ms;认证失效立即回登录页并保留原深链接,HTTP 403 和已耗尽的菜单依赖重试不得继续重试。重试期间退出登录应停止后续读取,不得用清空 Token、伪造角色或放行保护路由掩盖错误。持续失败用 `next(error)` 保留身份/菜单阶段和原始原因,禁止用 `next(false)` 把它替换为 `Navigation aborted`;启动界面不能据此断言后端断网。当前用户请求的传输失败必须 reject,不能留下永久 pending 的 Promise。修改这条链路时运行 `route-bootstrap-recovery.spec.mjs`,并用真实路由验证健康登录、短暂失败恢复、持续失败提示和过期会话跳转。
195
201
 
196
- - `app.use(router)` 会立即触发首个路由守卫。守卫中的 SSO、认证、菜单请求必须等待 `initApp()` 完成真实租户和系统配置初始化;初始化失败时取消导航并保留启动错误界面。释放初始化等待必须早于 `router.isReady()`,避免彼此等待;禁止将缓存尚未建立时的 `GetOsClient()` 默认值 `iTdos` 发给客户服务器。
197
-
198
- - 身份和菜单只读初始化的短暂失败最多读取三次,间隔 300/1000ms;认证失效立即回登录页并保留原深链接,HTTP 403 和已耗尽的菜单依赖重试不得继续重试。重试期间退出登录应停止后续读取,不得用清空 Token、伪造角色或放行保护路由掩盖错误。持续失败用 `next(error)` 保留身份/菜单阶段和原始原因,禁止用 `next(false)` 把它替换为 `Navigation aborted`;启动界面不能据此断言后端断网。当前用户请求的传输失败必须 reject,不能留下永久 pending 的 Promise。修改这条链路时运行 `route-bootstrap-recovery.spec.mjs`,并用真实路由验证健康登录、短暂失败恢复、持续失败提示和过期会话跳转。
199
-
200
- - `ApiServiceUnavailable` 确认连接故障后,每轮探测结束 5 秒后继续请求固定匿名健康接口;单次请求超时 5 秒,自动与手动检测共用在途请求。后端返回有效健康正文后停止轮询,启动失败的页面只重载一次原 URL,已经就绪的业务页面原地撤掉异常层并保留输入,禁止重放失败的业务写入。旧版健康接口也必须返回有效的 Healthy 正文,不能把反向代理 HTML/404 当作恢复。安全拦截继续按后端解除时间检查,不用连接故障轮询缩短封禁。定向回归使用 `api-service-recovery.spec.mjs` 与 `api-service-status.spec.mjs`,浏览器必须验证断连到自动恢复全过程。
202
+ - `ApiServiceUnavailable` 确认连接故障后,每轮探测结束 5 秒后继续请求固定匿名健康接口;单次请求超时 5 秒,自动与手动检测共用在途请求。后端返回有效健康正文后停止轮询,启动失败的页面只重载一次原 URL,已经就绪的业务页面原地撤掉异常层并保留输入,禁止重放失败的业务写入。旧版健康接口也必须返回有效的 Healthy 正文,不能把反向代理 HTML/404 当作恢复。安全拦截继续按后端解除时间检查,不用连接故障轮询缩短封禁。定向回归使用 `api-service-recovery.spec.mjs` 与 `api-service-status.spec.mjs`,浏览器必须验证断连到自动恢复全过程。
201
203
 
202
204
  - `src/config.json.ApiBaseDev` 是本地默认 API;URL 中 `#` 之前的 `ApiBase`、`OsClient` 必须同时
203
205
  高于 `index.html`、config、Pinia 与 localStorage。解析统一走
204
206
  `src/utils/runtime-endpoint-query.js`,禁止在新入口另写正则形成不同优先级。
205
- - URL 只允许有效的 HTTP(S) ApiBase 和安全 OsClient。页面初始化后通过不含 Token 的
206
- `window.__MICROI_RUNTIME_ENDPOINT__` 暴露实际值,便于 AI/自动化确认没有误连配置文件中的服务器。
207
- - 页面已解析的租户必须进入真实 HTTP 请求。`DiyCommon.UseAxios/UseAxiosAll` 与共享 axios 请求层统一通过 `request-tenant-context.js` 补当前 API 的 `osclient` 请求头;保留调用者显式的路径、Query、参数或 Header 租户,不给其它服务器或相邻 API 子路径自动添加页面租户。JSON 正文在动态路由阶段可能尚未读取,不能只依赖正文或旧 Token 选择租户。验收必须增加同源旧 Token 指向其它/不存在租户的真实后端场景:`platform-current-user` 应走页面租户并返回登录失效,保留原深链接进入登录页,不得显示“无法确认接口配置”或让 SSO 发现返回错误租户的 404;定向回归为 `request-tenant-context.spec.mjs`。
208
- - 首次身份读取的业务/传输失败交给路由守卫处理,`getInfo` 不调用带全局通知副作用的 `DiyCommon.Result`,同时抑制请求层通知和抢先登录跳转;`App.PageInit` 等待首次导航成功后再启动续签/用户刷新,导航失败时结束。既要验证干净会话,也要验证旧登录缓存和失败后重复刷新,不能以隔离浏览器成功代替用户当前会话验收。
207
+ - URL 只允许有效的 HTTP(S) ApiBase 和安全 OsClient。页面初始化后通过不含 Token 的
208
+ `window.__MICROI_RUNTIME_ENDPOINT__` 暴露实际值,便于 AI/自动化确认没有误连配置文件中的服务器。
209
+ - 页面已解析的租户必须进入真实 HTTP 请求。`DiyCommon.UseAxios/UseAxiosAll` 与共享 axios 请求层统一通过 `request-tenant-context.js` 补当前 API 的 `osclient` 请求头;保留调用者显式的路径、Query、参数或 Header 租户,不给其它服务器或相邻 API 子路径自动添加页面租户。JSON 正文在动态路由阶段可能尚未读取,不能只依赖正文或旧 Token 选择租户。验收必须增加同源旧 Token 指向其它/不存在租户的真实后端场景:`platform-current-user` 应走页面租户并返回登录失效,保留原深链接进入登录页,不得显示“无法确认接口配置”或让 SSO 发现返回错误租户的 404;定向回归为 `request-tenant-context.spec.mjs`。
210
+ - 首次身份读取的业务/传输失败交给路由守卫处理,`getInfo` 不调用带全局通知副作用的 `DiyCommon.Result`,同时抑制请求层通知和抢先登录跳转;`App.PageInit` 等待首次导航成功后再启动续签/用户刷新,导航失败时结束。既要验证干净会话,也要验证旧登录缓存和失败后重复刷新,不能以隔离浏览器成功代替用户当前会话验收。
209
211
  - 同源浏览器窗口仍共享 Token、CurrentUser 等持久化状态。不同 `ApiBase + OsClient` 并行测试必须
210
212
  使用独立 browser context/profile;URL 最高优先级不等于登录态隔离。
211
213
  - 修改该链路时运行 `node --test tests/runtime-endpoint-query.spec.mjs`,再使用两个独立