---
name: app-store
description: Microi 应用商城开发、打包、安装和升级规范。用于官方/社区应用、Manifest、源码与构建产物、依赖、后台安装任务、租户隔离、增量升级、回滚和验收。
---

# Microi 应用商城

## 核心原则

应用包是可审计、可重复安装、可增量升级的交付单元。运行类型使用 `ApplicationType`：普通平台包的新建默认值是 `Regular`，既有商城平台应用/通知仍使用 `Platform`，另外还有 `MicroService`、`UniApp`、`Web`；读取端必须兼容 `Regular/Platform`，不能在迁移完成前强制改单值。官方/社区来源使用 `PublisherType`。

`AppType` 是历史复用字段：旧包/接口曾把它用于官方/社区来源，也曾把它作为运行类型回退。新代码不能把 `AppType` 当事实源；只在读取旧数据时回退，写入新数据使用 `ApplicationType + PublisherType`。

客户已有的全局 V8、表单配置、菜单、字段和自定义代码必须保留。升级采用存在性检查、差异合并和包隔离，禁止整表覆盖或把发布方租户数据原样复制到目标租户。

## 平台升级的应用商城边界（强制）

- 表、字段、Tab、菜单、权限、接口引擎、事件、数据源、页面、打印、工作流、任务和可幂等种子数据都应通过应用包升级；不得为这些资源在 `Microi.Server/Microi.Upgrade/` 新增租户定制 .NET 代码。
- 有官方 MCP 权限时，固定使用 `microi_itdos`（`https://api.itdos.com`、`OsClient=iTdos`）更新官方母版、制作并发布对应应用，按内容哈希/包版本回读后，再由目标租户 MCP 安装或更新。无官方权限时只通过当前用户自己的 MCP/Manifest 更新其数据库并回读，不得假借通用升级器越权发布官方应用。
- `Microi.Upgrade` 只保留应用商城/安装器启动前必需的核心物理兼容或协议迁移，并要求持久化版本门、共享租约、幂等、失败不推进版本。禁止每次启动对每个租户重跑不断增长的历史迁移清单。
- 平台通用缺陷的交付证据必须分开记录：源码修复、官方母版资源、商城包发布后回读、目标租户后台安装任务、目标租户资源回读和真实 UI/接口验收；其中任一步未完成都不能笼统称为“已发布并安装”。

## 包内容

- Manifest：应用 Key、版本、兼容平台版本、依赖和资源清单。
- 数据模型：表、字段、菜单、角色权限、接口引擎、事件、数据源、页面、打印、工作流、任务。
- 源码与构建：私有源码包与可部署构建包分离，记录 SHA-256。
- 安装/升级脚本：幂等、可恢复、可回读；不包含租户密钥、Token、连接串或 License keys。
- 迁移采用“先扩展、后迁移、再收缩”，支持新旧节点短暂并存。

## 接口引擎资源所有权（强制）

- 新发布包必须声明 `ResourcePolicies.ApiEngines`，不得再依赖“同 Key 直接覆盖”。官方不可随租户修改的核心使用 `{ Ownership:'Application', UpgradePolicy:'Managed' }`；提供给租户改业务的 Hook 使用 `{ Ownership:'Tenant', UpgradePolicy:'CreateIfMissing' }`。
- 发布器从上一版 `AppPakcet.SysApiEngines` 计算 `BaseHash`；导入成功后把本版摘要写入 `sys_microistoreversion.InstallResult.ResourceState.ApiEngines`。更新判定固定为 Base/Local/Incoming：`Local == Base` 才允许更新；`Local != Base && Local != Incoming` 必须回滚并报告冲突，禁止静默覆盖。
- `CreateIfMissing` 只在目标 Key 不存在时创建，存在时不得对齐 Id、源码、启用状态或其它字段。扩展模板发布后即归租户维护；后续版本禁止把同一 Key 改回 `Managed` 接管，确需新的官方核心时发布新 Key 并显式迁移。
- 官方功能采用“Managed 核心 + CreateIfMissing Hook”。核心只提供稳定协议和默认行为；客户日志、写表、通知和业务动作放 Hook，并以稳定 `EventId`、唯一约束或 outbox 幂等。安全核心冲突不自动三方合并代码。
- 历史包未声明策略时只能按旧兼容流程安装；重新发布时发布器必须生成策略。验收至少覆盖首次安装、未修改核心升级、核心被改后冲突回滚、Hook 被改后保持原样、重复安装和两节点竞态。

## 安装流程

1. 校验签名/哈希、包版本、平台兼容性、依赖和磁盘/配额。
2. 创建全局唯一 `InstallationId` 和稳定幂等键。
3. 使用后台任务执行，阶段性持久化进度与 checkpoint。
4. 按 Manifest 差异创建缺失资源；已有资源只更新包拥有且允许升级的属性。
5. 写入成功后刷新共享缓存版本。
6. 回读表、字段、引擎、菜单、权限、页面等关键资源。
7. 做 HTTP、UI 和权限冒烟；成功后标记安装版本。

安装中断后从 checkpoint 幂等恢复；不能依赖当前 API 节点内存。

## 权限与租户

- 商城定义、安装、升级、卸载和应用源码只允许 `Level >= 9999`。
- “吾码官方平台”不能只按 `OsClient`、域名或前端变量判断：服务端必须同时校验官方 `OsClient`，并确认当前节点固定只读挂载 `/app/microi_private.pem` 的公钥部分与内嵌官方 License 信任根匹配；本地源码开发可从私有子仓库兼容查找。任意自建私钥不能建立官方身份。官方平台前端隐藏安装、更新、重新安装、离线安装和批量安装入口，后端仍必须拒绝这些写操作。
- 所有资源按目标 `OsClient` 写入；包内不能携带源租户 `OsClient`、数据库、Redis、对象存储、MQ/MQTT、AI 或第三方密钥。
- 按钮调用后台安装接口时，前端只传应用/版本/安装 Id；目标租户和管理员身份由 Token 确定。
- 每次安装、更新、重新安装生成稳定 `OperationId`。官方计数服务用共享数据库事件表唯一约束去重，并在同一事务内登记事件和递增 `InstallCount`；重试、跨节点和响应丢失不得重复计数。
- “全部安装/更新”固定只处理 `ApplicationType=Platform` 的官方平台应用中未安装与存在新版本的项目，不得把 UniApp、Web、MicroService 或其它社区/AI 应用整库安装；已是最新版的应用不重新安装。批量计划、子项状态、checkpoint 和进度必须持久化到共享数据库/后台任务，支持多节点抢占、失败重试和重启恢复，不能依赖进程内集合或浏览器状态。
- 必须随所有后端版本自动落地的平台基础能力，仍要封装成受信任的官方 Platform 应用包，再由升级器调用统一 `import-microi-store-package` 幂等导入；禁止把表、字段、页面或微服务复制成定制 C# 迁移。身份验证、登录方式、个人中心与租户系统设置使用 `app.microi.saas-engine.json`：携带 `mci_system_setting`、`mci_user_external_identity`、默认设置和 `microi-platform-service`。默认行必须使用 `InsertIfMissing + ConfigKey`，只补缺失，不覆盖 `ValueSource=Tenant` 的租户值或租户后来明确关闭的功能。
- 批量任务已经以“一个应用”为外层持久化恢复单元。规模可控的小型官方包应在一个事务中完成，避免对同一包体按 8 个字段反复下载、解析和重新排队；超过字段、表、DDL、流程、随包数据或资产安全阈值的大包继续使用内部 checkpoint 分片。热更新发现旧版批量计划不含 `ApplicationType` 时，必须丢弃旧计划并重新盘点，不能继续安装历史计划中的社区应用。
- MySQL 宽表触发 65,535 字节行内上限时，只允许把不参与索引的 `varchar` 配置列无损提升为 `mediumtext`，并把类型覆盖持久化到后台任务 checkpoint；索引列和非行宽错误必须失败关闭。发布包对长连接串、密钥、回调地址、域名/白名单等字段应直接使用 `mediumtext`，同时更新 `DiyFields` 与建表 DDL，不能长期依赖安装时猜测。
- 卸载是破坏性操作，必须明确列出将删除/保留的资源、二次确认并优先软删除/归档业务数据。

## MCP 工作流

1. 安装/更新现成商城应用时，先调用 `microi_install_store_application` / `microi_update_store_application` 且不传 `confirmExecution`，核对目标租户、商城源、StoreId 和幂等请求 Id 的预检结果。
2. 用户明确确认后，把 `confirmExecution` 精确设为 `StoreId`，提交真实持久化后台任务；只传 StoreId、版本和商城源定位信息，不通过 MCP/HTTP 传完整 `AppPakcet`。
3. 自建应用或低代码系统先用 `microi_list_applications` / `microi_get_application_context` 盘点源码，再用 `microi_get_manifest_schema`、`microi_plan_system` 与 `microi_generate_system(dryRun:true)` 干跑。
4. 安装任务必须回读至 `Succeeded`，再执行 `microi_validate_system`、远端资源回读和真实 UI 验收；仅返回 TaskId 不代表安装成功。

已有 MicroService 优先新增页面/路由；没有时才创建、同步源码、发布构建。复杂安装交互使用 `V8.OpenAppDialog`，后台任务上报进度。

## VS Code 本地应用与发布边界

- `AI应用` 本地树按每个一级目录的 `.microi-micro-app.json` 发现项目；只要 `osClient/apiBaseUrl` 与当前连接一致，就必须显示 `Web / UniApp / MicroService`，不得用 `runtime === "micro-app"` 过滤掉其它应用类型。无效清单应记录诊断，不能静默吞掉整个目录。
- “安装到当前租户”和“发布到应用商城（不安装）”是两个独立动作。商城发布只能同步 `sys_microistore`、应用源码/构建/版本元数据及安装包，不得新增或修改当前租户的 `sys_microiservice / sys_microiservice_page`；操作前后必须回读运行态确认未变化。
- 应用项目行必须直接显示商城发布入口，不能只藏在右键菜单；同时保留构建安装和源码同步状态入口。

## 版本与回滚

- 版本号单调递增，保存变更清单和前后哈希。
- 公有发布必须同时保留两套入口：`/{OsClient}/ai-app-publish/{AppKey}/index.html` 永远指向最新版，`.../versions/{Version}/index.html` 永远保留该历史版本。先完整上传并逐字节验签不可变版本目录，再切换稳定入口。带 `data-microi-immutable-runtime` 的新入口通过版本目录解析全部资源，可先切换入口；旧入口仍在其它稳定资产之后切换。不能机械固定“入口永远最后”而破坏两种契约。
- stage 前回读并冻结应用的 `CurrentVersion + AppVersion`；finalize 必须同时传 `ExpectedCurrentVersion + ExpectedAppVersion`，在以不可变 AppId 加锁后再次 compare-and-set。缺失前置条件、AppId/AppKey 漂移或旧请求晚到一律失败关闭；重新发布必须重新盘点，不能静默回退。
- 新清单发布成功时，同应用 `dist/` 下不再出现的 active 文件元数据只能条件式改为可逆归档 scope，条件必须包含 AppId、路径、旧 scope 与版本；禁止删除 HDFS/数据库记录，也不得触碰 Private、非 `dist/` 或另一应用的行。
- 官网、二维码和用户分享只使用无版本号根入口，不追加 `v/apiBase/OsClient`。目标租户的 `ApiBase/OsClient` 在发布或安装时写入入口 HTML 的 `window.__MICROI_APP_CONTEXT__ / MICROI_API_BASE / MICROI_OS_CLIENT`；安装包不得沿用发布端运行上下文。
- 数据迁移通常只向前；回滚应用版本不能假设自动回滚业务数据。
- 更新失败保留原版本可运行资源，记录失败阶段；不要清空客户 V8 后再尝试恢复。
- 私有源码仓库、`Microi.net/License/keys` 和部署密钥不进入公开应用包。

## 验收清单

- [ ] 同一包重复安装无重复表/字段/菜单/任务
- [ ] 客户自定义 V8 与非包拥有配置保持不变
- [ ] 源码包、构建包、Manifest 和数据库版本一致且哈希可核
- [ ] 无版本号根入口与当前商城版本一致，历史版本 URL 仍可独立访问
- [ ] 安装到不同 ApiBase/OsClient 后，入口 HTML 使用目标租户上下文且分享 URL 无运行参数
- [ ] 中断/重启后可恢复，两个节点不会重复副作用
- [ ] 普通角色不能安装、升级、卸载或读取私有源码
- [ ] 缓存刷新后远端 API 与真实 UI 通过
- [ ] 卸载范围明确、可审计、可恢复或已提示不可恢复

## 复盘：商城按钮已到达但依赖接口引擎缺失

- 触发场景：子租户更新应用商城后能够看到“全部安装/更新”，点击却提示 `sys_apiengine` 中不存在按钮调用的接口；继续临时补引擎后，还可能因目标租户缺少批量计划表再次失败。
- 根因：商城元数据版本、`AppPakcet.PackageInfo.Version` 与真实包正文发生分叉，页面按钮被单独更新；同时应用导入器把接口引擎新增/更新失败只写进 Debug 后继续返回成功，没有做写后回读，批量引擎又依赖未随同一应用包交付的表。
- 通用规则：按钮及其调用的接口引擎、表和权限必须属于同一个单调版本应用包；任何依赖写入失败或回读内容不一致都要让安装事务失败。可复用的批量状态优先写入平台后台任务 `CheckpointJson`，避免仅为批量编排增加未交付的租户表。商城行版本、包内版本和包内容哈希必须同时回读一致，禁止单独提升商城行版本或只更新菜单。
- 自动化检查：应用包契约测试必须从按钮代码提取接口 Key，断言包内存在启用且配置正确的引擎并与独立源码逐字一致；导入器测试覆盖新增失败、更新失败、缓存清理后回读缺失和源码不一致均返回 `Code=0`；真实子租户更新后回读 `sys_menu + sys_apiengine + 后台任务`，再实际执行一次“无需更新”和至少一个安装/更新计划。

## 复盘：可信后台任务被 StopHttp 提前拦截

- 触发场景：`V8.ApiEngine.RunBackground` 已成功创建任务，但 Worker 执行 `StopHttp=1` 的批量引擎时立即失败并提示“此接口已禁止http调用”。
- 根因：Worker 为保留按钮调用、权限和审计语义继续使用 `_InvokeType=Client`，旧版 ApiEngine 只按该字段判断 `StopHttp`，没有识别服务端恢复的持久化任务来源。
- 通用规则：核心 ApiEngine 仅在“服务端可信用户作用域 + `_TrustedServerInvocation=true` + 非空任务 Id + 匹配任务信封 + 正数 fencing token”全部成立时豁免 `StopHttp`；少一项都按普通 HTTP 拒绝。不得把所有后台调用粗暴改成 `Server`，也不得只凭客户端可伪造的任务 Id 或信封放行。
- 旧节点兼容：应用资源尚需覆盖未部署核心修复的服务，可暂将批量引擎设为 `StopHttp=0`，但 V8 首行安全门必须执行同样的全条件校验；HTTP 控制器必须剥离 `_TrustedServerInvocation`。契约测试同时断言严格 `AND` 校验、独立源码与包内副本一致、普通调用失败关闭。

## 复盘：后台任务基础包的自举与索引幂等

- `app.microi.background-task` 自身负责创建/修复后台任务表，首次安装和离线安装必须以前台接口完成；不能先调用 `RunBackground`，否则旧库缺少 `OsClient` 等字段时会在导入开始前失败。
- 后台任务可用性不能只检查表存在，还要检查运行时必需列；部分升级的旧表必须返回“能力尚未就绪”，不能继续拼接包含缺失列的 SQL。
- 应用包中的独立 `CREATE INDEX` 必须按表名和索引名做执行前回读；并发创建失败后再次回读，已存在则按幂等成功处理。索引 DDL 不能重复触发整张表的字段同步。
- 基础包必须同时携带 `OsClient` 的 `PhysicalColumns` 定义、建表内联索引与 4 条独立索引 DDL；新表靠建表一次成型，旧表靠物理列同步和独立 DDL 修复。安装前先把“应用商城”更新到包含 `BACKGROUND_TASK_BOOTSTRAP_READINESS_V1`、`APPLICATION_ASSET_BACKGROUND_CHUNKS_V1` 的 v1.8.0+ 导入器；安装成功前必须回读全部运行字段及索引，验收覆盖首次安装、部分旧表修复、重复安装和两节点竞态。
- 其它用户更新吾码 VS Code 插件并执行“初始化 AI 配置/拉取 Skills”后，AI 应自动识别本规范：大任务优先提交真实后台任务；若基础能力未就绪，先指导用户更新应用商城并以前台方式安装 `app.microi.background-task`，不得伪造进度或让基础包自举入队。
- 重复安装先比较应用包拥有的字段定义，完全一致就跳过 `UptFormData`。Jint 的 `LimitMemory` 统计累计托管分配而非当前存活堆；大量无效字段更新即使被 GC 回收也会耗尽预算。v1.8.0 导入器每个后台片最多实际上传 8 个文件，并以约 32 MB Base64 为分片目标；为避免单个大文件无限空转，每片至少允许处理一个文件。片末返回 `HasMore + Checkpoint`，下一片按 `AppId + FilePath + Hash` 复用已提交资产，并禁止为统计再次解码整文件 Base64。
- 只有固定 Key `import-microi-store-package`、服务端可信身份、`Level >= 9999`、当前进程主租户、持久化后台 TaskId 五项同时成立时，后端才使用 `ResidentMemoryGuardOnly`，跳过会永久累计已回收分配的 Jint 内存约束。该特例仍受容器优先的进程 RSS 防线保护：95% 停止接收新工作，98% 有界停机并由持久任务恢复。普通/前台/子租户/其它 V8 不得借用此特例。

## 复盘：官网升级资源同步的三道门

- 官网当前资源只是三方合并的一侧，允许暂时落后于本地发布候选。下载阶段只校验固定白名单、稳定资源身份、JSON 可解析性及服务端返回 SHA-256；不能用“必须已包含本地最新功能标记”的规则提前拒绝旧官网，否则会形成“官网不够新所以永远无法发布新版”的循环依赖。
- 本地输入和三方合并后的最终候选必须继续执行最低版本、功能标记、内嵌逻辑副本一致性等严格校验；发布成功并按内容哈希回读一致后才能推进 `.resource-sync-base`，不能把放宽读取门误做成放宽发布门。
- 应用包版本与平台发布版本分别单调递增。包正文需要写回而本地包版本、平台版本均未高于官网时，应基于官网包版本自动递增补丁号；内容无变化时不得递增，必须用连续两次同步验证第二次为零变更。
- MCP 配置中的官方 API、`OsClient` 与 Token 文件是鉴权事实源，但编辑器可执行文件、插件版本目录和 `cwd` 都是易漂移的启动信息。发布器应保留鉴权配置、固定校验 `https://api.itdos.com + itdos`，并从已配置入口、同级最新已安装插件或当前工作区依次发现可信 `mcp-server.js`，使用正在运行发布脚本的 Node 启动；插件升级或旧目录清理不能再次阻塞后端发布。
- Token 文件的新键固定为 `ApiBase|OsClient|OsClientType|OsClientNetwork` 四段身份；Type/Network 为空时仍保留空段。读取和回写必须先用四段精确键，再兼容旧版紧凑键；旧键在多环境配置下可能被有意保留且已经失效，禁止优先读取。自动化测试要覆盖“精确键已刷新、旧键仍过期”以及 type-only/network-only 不碰撞。
- VS Code 的 SecretStorage 恢复 broker 必须使用确定性激活事件启动；不得只依赖大型工作区可能超时的递归 `workspaceContains`。签名密钥变化时旧 Token 本身不能以旧换新，必须由已激活 broker 使用本机 SecretStorage 静默重登并回写精确身份键。
- 本地资源同步的官网读取、发布和发布后回读应使用同一个已校验的 `microi_itdos` MCP 链路；CI 无 MCP 时才允许凭显式 Token 使用 HTTP。V8 独立文件与应用包内嵌副本合并时，先把文件头说明和 `Version` 与可执行正文分离：两端独立升版不能算代码冲突，不同正文安全合成后应基于两端最高版本再升一版；只有同一段可执行逻辑出现不同实现才失败关闭。
- 发布验收至少包含：定向合并测试、真实 `PublishBatch`、六项资源逐项 SHA-256 回读，以及立即执行第二次幂等重跑。任何一步失败都不得宣称官网已同步，也不得推进共同基线。

## 复盘：官网模板库误报平台应用可更新

- 触发场景：官网 `sys_microistore` 的平台应用版本持续发布，但 `sys_microistoreversion` 仍保留旧安装版本；官网自身会显示“可更新”，从官网数据库复制出来的新租户也继承同一误报，尽管这些平台能力实际已经随官网主库维护完成。
- 身份边界：只能在服务端同时满足 `OsClient=iTdos` 且当前节点私钥公钥部分与内嵌官方 License 公钥完全一致时执行。只使用同名租户、域名、配置开关或任意自建私钥都不得触发；客户租户的安装版本仍是其独立事实源。
- 对齐规则：每次后端启动都在现有租户升级分布式租约内盘点 `ApplicationType=Platform + IsApprove=1` 的商城行，只处理 `Installed/Success/Succeeded/已安装` 的既有安装记录，按 `StoreId -> AppId -> 唯一 AppName` 匹配。只条件更新 `AppVersion/AppVersionInstall/PackageVersion`；稳定键仅在目标键未被其它安装记录占用时补齐。不得新增“已安装”记录、覆盖失败/异常状态、修改安装时间或删除重复历史行。
- 分布式与验收：更新是确定值条件写入，节点中途退出后下次启动继续，第二次执行必须零更新；验收同时回读版本表三个字段，并执行官网商城列表接口，确认 `Outdated=0`、`Abnormal=0`。真实未安装应用仍应保留 `Uninstalled`，不能为了清空通知伪造成已安装。

## 复盘：共同基线已有引擎但官网内嵌副本缺失

- 触发场景：本地 `app.microi.store.json`、独立接口脚本和 `.resource-sync-base` 都已有新引擎，但官网商城包尚未收录或被单侧删除；同步器在三方合并前强制读取官网内嵌代码，直接报“缺少接口引擎”，导致新版永远无法通过一键发布补到官网。
- 根因：旧状态机只区分“共同基线不存在的首次新增”和“三端都存在的正常合并”，漏掉“共同基线已登记、官网副本缺失”的历史基线漂移；严格 getter 在合并前抛错，使已有的首次发布与逻辑副本修复规则没有机会运行。
- 通用规则：只要副本映射仍属于当前发布契约，本地独立源码和内嵌副本能从共同基线安全合并，官网单独缺少内嵌引擎应视为不完整副本。JSON 合并前只从共同基线恢复该引擎的结构骨架；有官网独立文件时继续把它作为官网代码侧，无独立文件时把共同基线代码视为官网未修改，再执行正文三方合并并将结果写回内嵌副本。写回后必须按 `SysApiEngines.length` 重算 `PackageInfo.ApiEngineCount`。若共同基线引擎 Id 已被其它 Key 占用，或任意两侧同一代码位置实现冲突，仍必须失败关闭；不得通过覆盖/重建整个共同基线来掩盖问题。
- 自动化检查：同时覆盖共同基线不存在的首次新增、共同基线存在但官网缺失且本地升级、官网内嵌缺失但官网独立源码升级、共同基线 Id 被其它 Key 占用四种状态；断言声明引擎数等于实际数组长度；再用真实 `microi_itdos` 只读资源重放，确认候选含目标引擎且独立源码与内嵌代码相同，正式发布后执行 SHA 回读和零变更幂等重跑。

## 复盘：老租户 NULL 数据阻止物理列收紧

- 触发场景：商城应用包把既有字段从可空升级为 `NOT NULL DEFAULT ...`，老租户的物理列已经存在，但历史记录仍为 `NULL`；导入器直接执行 `ALTER TABLE ... MODIFY COLUMN ... NOT NULL` 时，MySQL 报 `Invalid use of NULL value`，整个安装分片失败。
- 根因：物理列同步只比较类型、可空性、默认值和注释，没有在收紧可空性前迁移存量数据。列默认值只影响后续写入，不会自动修复既有 `NULL`。
- 通用规则：当包要求 `NOT NULL`、目标列仍可空且存在历史 `NULL` 时，必须先用包内明确声明的默认值参数化回填，再执行列约束变更；包未声明默认值时失败关闭并报告影响行数，禁止猜测业务值。该步骤必须可重复执行，并允许多节点并发重试后得到同一结果。
- 自动化检查：构造可空旧列及多条 `NULL` 行，断言导入器按“统计 NULL → 参数化回填 → `MODIFY ... NOT NULL`”顺序执行；覆盖字符串、整数、零行、缺失默认值、重复执行，以及商城包中全部发布协议状态字段。
