@microi.net/cli 6.1.7 → 6.1.9

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 (58) 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/assets/build-meta.json +4 -4
  8. package/cordis.patch.yml +1 -1
  9. package/package.json +1 -1
  10. package/scripts/codex-marketplace.json +20 -20
  11. package/scripts/mcp-codex-stdio-adapter.js +189 -189
  12. package/scripts/mcp-server.js +101 -96
  13. package/scripts/mcp-trae-windows-launcher.cmd +21 -21
  14. package/scripts/microi-cli-mcp.js +7 -7
  15. package/scripts/microi-skills.meta.json +257 -239
  16. package/skills/.microi-skills-version.json +2 -2
  17. package/skills/.progressive-disclosure-manifest.json +90 -68
  18. package/skills/ai-engine/SKILL.md +303 -293
  19. package/skills/ai-engine/references/ai-employees.md +4 -0
  20. package/skills/app-store/SKILL.md +3 -1
  21. package/skills/job-engine/SKILL.md +12 -0
  22. package/skills/message-notification/SKILL.md +2 -0
  23. package/skills/microi-client-frontend/SKILL.md +260 -252
  24. package/skills/microi-codex/SKILL.md +8 -0
  25. package/skills/microi-datasource-mapping/SKILL.md +4 -0
  26. package/skills/microi-deployment/SKILL.md +85 -5
  27. package/skills/microi-deployment/references/deployment-matrix.md +27 -0
  28. package/skills/microi-deployment/references/offline-installer.md +64 -0
  29. package/skills/microi-docs-coverage/references/capability-map.md +3 -3
  30. package/skills/microi-form-engine/SKILL.md +13 -0
  31. package/skills/microi-form-engine/references/component-catalog.md +1 -1
  32. package/skills/microi-frontend-sdk/SKILL.md +12 -0
  33. package/skills/microi-microservice/SKILL.md +18 -1
  34. package/skills/microi-system-delivery/SKILL.md +8 -6
  35. package/skills/microi-system-delivery/references/delivery-pitfalls.md +50 -0
  36. package/skills/microi-system-delivery/references/progressive-01-/346/240/207/345/207/206/345/267/245/344/275/234/346/265/201.md +11 -9
  37. package/skills/microi-uniapp-frontend/SKILL.md +18 -3
  38. package/skills/microi.v8.js +445 -0
  39. package/skills/module-engine/SKILL.md +4 -5
  40. package/skills/page-engine/SKILL.md +233 -215
  41. package/skills/print-engine/SKILL.md +5 -0
  42. package/skills/report-engine/SKILL.md +9 -0
  43. package/skills/report-engine/scripts/check-demo-coverage.mjs +88 -0
  44. package/skills/report-engine/scripts/check-demo-coverage.test.mjs +52 -0
  45. package/skills/scripts/sync-embedded-skills.mjs +4 -1
  46. package/skills/scripts/sync-public-resource-router.mjs +29 -0
  47. package/skills/system-observability/SKILL.md +18 -2
  48. package/skills/ui-design/SKILL.md +1 -1
  49. package/skills/uniapp-mall-assets/SKILL.md +5 -0
  50. package/skills/v8-file-upload/SKILL.md +32 -31
  51. package/skills/v8-file-upload/references/hotspot-offload-operations.md +118 -0
  52. package/skills/v8-saas-multi-tenant/SKILL.md +17 -4
  53. package/skills/v8-security/SKILL.md +20 -5
  54. package/skills/vision-engine/SKILL.md +114 -3
  55. package/skills/vision-engine/references/architecture-and-acceptance.md +8 -0
  56. package/skills/workspace-conventions/SKILL.md +9 -3
  57. 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 +4 -4
  58. package/skills/workspace-conventions/references/progressive-04-/345/267/245/344/275/234/345/214/272/344/270/216/345/271/263/345/217/260/344/270/223/351/241/271/344/277/235/351/232/234.md +22 -1
@@ -0,0 +1,52 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { checkDemoCoverage as check } from './check-demo-coverage.mjs';
9
+
10
+ const fixture = (asOf, dates, extra = {}) => ({ asOf, dateField: 'OrderDate', rows: dates.map(OrderDate => ({ OrderDate })), ...extra });
11
+
12
+ test('historic volume cannot satisfy a new default month', () => {
13
+ const result = check(fixture('2026-11-01', Array(120).fill('2026-10-31 09:00:00')));
14
+ assert.equal(result.ok, false); assert.equal(result.periodCounts.month, 0); assert.equal(result.periodCounts.week, 120);
15
+ });
16
+ test('new data fills month and each period uses its own boundary', () => {
17
+ const result = check(fixture('2026-11-01', ['2026-10-25', '2026-10-26', '2026-10-31', '2026-11-01', '2026-11-02']));
18
+ assert.equal(result.ok, true); assert.deepEqual(result.periodCounts, { today: 1, week: 3, month: 1 });
19
+ });
20
+ test('Monday and new year do not count preceding Sunday as current week', () => {
21
+ const result = check(fixture('2027-01-04', ['2026-12-31', '2027-01-03', '2027-01-04'], { periods: ['week', 'month', 'quarter', 'year', 'lastYear'] }));
22
+ assert.equal(result.ok, true); assert.deepEqual(result.periodCounts, { week: 1, month: 2, quarter: 2, year: 2, lastYear: 1 });
23
+ });
24
+ test('leap dates and midnight are valid; impossible dates/times are rejected', () => {
25
+ assert.equal(check(fixture('2028-02-29', ['2028-02-29T00:00:00+08:00'])).ok, true);
26
+ for (const date of ['2027-02-29', '2028-02-30', '2028-02-29 24:00:00', '2028-02-29T00:60:00Z', '2028-02-29T00:00:00+99:00', '']) {
27
+ assert.equal(check(fixture('2028-02-29', [date])).ok, false, date);
28
+ }
29
+ });
30
+ test('explicitly legitimate empty periods remain empty', () => {
31
+ const result = check(fixture('2026-11-01', ['2026-11-01'], { periods: ['month', 'lastYear'], allowEmptyPeriods: ['lastYear'] }));
32
+ assert.equal(result.ok, true); assert.equal(result.periodCounts.lastYear, 0);
33
+ assert.equal(check(fixture('2026-11-01', [], { allowEmptyPeriods: ['today', 'week', 'month'] })).ok, true);
34
+ });
35
+ test('state and invalid contract checks fail closed', () => {
36
+ const base = fixture('2026-11-01', ['2026-11-01'], { stateField: 'Status', requiredStates: ['Draft', 'Signed'] });
37
+ base.rows[0].Status = 'Draft'; assert.equal(check(base).ok, false);
38
+ base.rows.push({ OrderDate: '2026-11-01', Status: 'Signed' }); assert.equal(check(base).ok, true);
39
+ for (const change of [{ periods: [] }, { periods: ['all'] }, { allowEmptyPeriods: 'month' }, { asOf: 'invalid' }, { rows: {} }, { requiredStates: 'Draft' }]) assert.equal(check({ ...base, ...change }).ok, false);
40
+ });
41
+ test('CLI exit status preserves failure and success, and teardown removes its fixture', () => {
42
+ const folder = mkdtempSync(path.join(tmpdir(), 'microi-report-coverage-'));
43
+ try {
44
+ const input = path.join(folder, 'input.json');
45
+ const cli = fileURLToPath(new URL('./check-demo-coverage.mjs', import.meta.url));
46
+ for (const [date, expected] of [['2026-10-31', 1], ['2026-11-01', 0]]) {
47
+ writeFileSync(input, JSON.stringify(fixture('2026-11-01', [date])));
48
+ const run = spawnSync(process.execPath, [cli, input], { encoding: 'utf8' });
49
+ assert.equal(run.status, expected, run.stderr); assert.equal(JSON.parse(run.stdout).ok, expected === 0);
50
+ }
51
+ } finally { rmSync(folder, { recursive: true }); }
52
+ });
@@ -9,7 +9,10 @@ const serverRoot = path.resolve(skillsRoot, '../Microi.Server/Microi.AI');
9
9
  const project = fs.readFileSync(path.join(serverRoot, 'Microi.AI.csproj'), 'utf8');
10
10
  const manifest = JSON.parse(fs.readFileSync(path.join(skillsRoot, '.progressive-disclosure-manifest.json'), 'utf8'));
11
11
  const check = process.argv.includes('--check');
12
- const names = process.argv.slice(2).filter(value => value !== '--check');
12
+ const allRegistered = process.argv.includes('--all-registered');
13
+ const names = allRegistered
14
+ ? [...new Set([...project.matchAll(/Resource\\skill-([a-z0-9-]+)\.md/g)].map(match => match[1]))]
15
+ : process.argv.slice(2).filter(value => value !== '--check');
13
16
  if (!names.length) throw new Error('请显式指定本次需要同步的 Skill 名称。');
14
17
  const stripMarkers = text => text.replace(/<!--\s*\/?microi-progressive:[^>]*-->\r?\n?/g, '');
15
18
  const digest = text => crypto.createHash('sha256').update(text).digest('hex');
@@ -0,0 +1,29 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ // Single-file SDKs retain their delivery contract; only this generated router block is synchronized.
7
+ const args = process.argv.slice(2), write = args.includes('--write');
8
+ function option(name, fallback) { const at = args.indexOf(name); return at < 0 ? fallback : args[at + 1] || (() => { throw Error('Missing ' + name); })(); }
9
+ const root = path.resolve(option('--root', path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')));
10
+ const routerPath = path.resolve(option('--router', path.join(root, 'Microi.Client/src/utils/public-resource-router.mjs')));
11
+ const bytes = fs.readFileSync(routerPath), router = bytes.toString('utf8');
12
+ if (/^import\s/m.test(router) || /^export (?!function )/m.test(router)) throw Error('RouterModuleShapeChanged');
13
+ const sha = crypto.createHash('sha256').update(bytes).digest('hex');
14
+ const generated = '// BEGIN GENERATED PUBLIC RESOURCE ROUTER; source SHA256 ' + sha + '\n'
15
+ + 'const createSdkPublicResourceClient = (() => {\n' + router.replace(/^export function /gm, 'function ')
16
+ + '\nreturn createPublicResourceClient;\n})();\n// END GENERATED PUBLIC RESOURCE ROUTER';
17
+ const marker = /\/\/ BEGIN GENERATED PUBLIC RESOURCE ROUTER; source SHA256 [a-f0-9]{64}\r?\n[\s\S]*?\/\/ END GENERATED PUBLIC RESOURCE ROUTER/g;
18
+ const paths = ['microi.skills/microi.v8.js', 'microi.uniapp/src/utils/microi.v8.js', 'Microi.Server/OfficialApplications/PlatformService/src/utils/microi.v8.js'];
19
+ const pending = paths.map(relative => {
20
+ const full = path.join(root, relative), text = fs.readFileSync(full, 'utf8');
21
+ const blocks = [...text.matchAll(marker)]; if (blocks.length !== 1) throw Error('ExpectedSingleRouterBlock:' + relative);
22
+ const nl = text.includes('\r\n') ? '\r\n' : '\n';
23
+ const block = generated.replace(/\r?\n/g, nl);
24
+ return { path: relative, full, text, next: text.replace(marker, () => block) };
25
+ });
26
+ const changed = pending.filter(item => item.text !== item.next);
27
+ if (!write && changed.length) throw Error('SdkRouterOutOfDate:' + changed.map(item => item.path).join(','));
28
+ if (write) for (const item of changed) fs.writeFileSync(item.full, item.next);
29
+ console.log(JSON.stringify({ mode: write ? 'write' : 'check', routerSha256: sha, sdkCount: paths.length, changed: changed.map(item => item.path) }));
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: system-observability
3
- description: Microi 系统日志/监控查询、诊断与治理规范。用于分析系统日志、Trace、热点接口、CPU/内存吃满、OOM、异常分配、接口引擎/V8 事件归因、重启事故恢复、网络流量、安全事件和监控性能成本。
3
+ description: Microi 系统日志/监控查询、诊断与治理规范。用于日志、Trace、CPU/内存/OOM、网络流量、安全事件,以及 OSS/CDN 高下载文件归因、NAS 热点分流与完整日费用验收。
4
4
  ---
5
5
 
6
6
  > **Microi吾码基础规范(强制):** 任何 AI 模型与宿主每次新建或接续吾码任务,先完整读取 `../workspace-conventions/SKILL.md`,必须执行版本播报、`@microi.net/cli` 后台自动升级、Skills/MCP 同步和进度播报。安装与诊断读取 `../microi-codex-installer/SKILL.md`;更新失败延后重试,不阻断当前工作。
@@ -201,6 +201,12 @@ UnblockIp:<ip>
201
201
 
202
202
  ## 接口引擎归因规范
203
203
 
204
+ OSS/CDN 高费用、文件下载榜单和 NAS 分流还须读取
205
+ [文件资源分流操作流程](../v8-file-upload/references/hotspot-offload-operations.md)。
206
+ 通过受管 `mci-cdn-manager` 核对流量来源及账单,通过 `mci-file-resource-runtime`
207
+ 检查资格、复制及实际路由;完整自然日、当天部分账单和一次性复制成本分开记录。
208
+ 已有通用 `microi_run_engine` 复用当前租户鉴权,不另建云凭据或旁路 HTTP 工具。
209
+
204
210
  - PC、UniApp、内置微服务、MCP 和应用包中的固定接口必须调用 `/apiengine/{ApiEngineKey}` 或该引擎唯一 `ApiAddress`;新增代码禁止调用 `/api/ApiEngine/Run`。
205
211
  - SDK 的普通 `ApiEngine.Run` 必须自动生成真实引擎路径;旧通用入口只能封装在显式 `RunLegacy` 中,供不能立即升级的历史客户端使用。
206
212
  - 监控中保留通用入口的兼容识别,但不能依赖读取请求正文才能知道 Key;真实路由、访问日志、限流和流量排行应直接显示接口引擎 Key。
@@ -231,8 +237,16 @@ UnblockIp:<ip>
231
237
 
232
238
  ### CDN 模块
233
239
 
240
+ - 官方 Chromium 分流仅限 `static.itdos.com/openclaw/chromium/` 的 137.0.7151.68 三平台精确 ZIP。先核三份真实 native Ready/raw SHA/大小/SourceRevision/TargetConfigHash 与独立公有回退,再以 `ChromiumRedirectState` 和逐平台 `Preview → Submit` 安装;各平台保留原 RequestKey/Id/ConfigId/RequestId。Unknown 只回读,回滚必须使用本平台原成功操作 Id,禁止同形认领或全域改写。外部云控制台写者不受域租约约束;真实旧 URL 两跳、NAS HEAD/Range 和完整日账单分别验收。
241
+ - 七种官方空库的 `mci-file-resource-empty-database-download` 只映射固定 Database 枚举和公开 `install/microi_empty_*.sql.zip`,当前可信原生副本可用才返回 NAS 与完整性 Header,否则回官方 CDN。它不复制、不签私链、不接收任意 URL。MCP 复用 run/save/官方包发布工具,不新增通用代理。
242
+
243
+ - 回源路径只读诊断使用管理员入口 `mci-cdn-manager` 的固定 `OriginRouting` 动作,仅传 `Provider:aliyun` 与一个精确 `Domain`;先确认 `Capabilities.Providers` 内阿里云 `Features.OriginRouting=true`。旧 `Configs` 五项不变,禁止开放云 Action、FunctionNames、Endpoint、凭据或分页任意覆盖。
244
+ - `ConfigurationReadComplete` 只表示固定配置集合读取完整;缺失、云失败、分页/截断或超限不能解释成零规则。保留规则 `ConfigId/ParentId/Status/Index/sequence`。条件、源站组、EdgeScript/EdgeRoutine 与未知参数作为待审证据,绝不回传脚本、敏感 Header/查询值或原始 IP。
245
+ - 同一 OSS 源不代表同对象路径;`EffectiveObjectMappingVerified/AutomaticAliasRoutingAuthorized` 固定为 false。必须另验 Host、路径、条件关联、当前公开权限及内容身份后才可能配置别名;本动作不复制、不公开、不分流。三条 Managed 引擎经系统日志应用包交付,通用 MCP 已可调用,不为诊断另发 C#、npm 或后端镜像。
246
+
247
+
234
248
  - 现有 `microi-platform-service` 的系统日志页增加 CDN Tab;不另建同名应用。固定管理员入口为 `mci-cdn-manager`,内部 `mci-cdn-core / mci-cdn-aliyun / mci-cdn-worker` 均 `StopHttp=1`,四个引擎归 `app.microi.sys-log` 的 Managed 资源。
235
- - 通过当前租户 MCP 的 `microi_run_engine` 先查 `Capabilities`,再使用 `Domains / Trend / Usage / CacheHit / OriginTraffic / Billing / StorageHealth / StorageLogs / Logs / LogDelivery / ReportList / Reports / Sls / Configs / Config / Rules / Operations`。现有执行、源码、元数据及发布工具已覆盖此模块,不为 CDN 新增旁路鉴权或通用 HTTP 代理。
249
+ - 通过当前租户 MCP 的 `microi_run_engine` 先查 `Capabilities`,再使用 `OriginRouting / Domains / Trend / Usage / CacheHit / OriginTraffic / Billing / StorageHealth / StorageLogs / Logs / LogDelivery / ReportList / Reports / Sls / Configs / Config / Rules / Operations`。现有执行、源码、元数据及发布工具已覆盖此模块,不为 CDN 新增旁路鉴权或通用 HTTP 代理。
236
250
  - 凭据优先级为当前租户 `Integration.Cdn.Aliyun.*` 完整私密组 → `Integration.Dns.Aliyun.*` 私密组 → 当前租户 SaaS 的兼容 DNS 字段。半组配置必须失败,不能混组或回退主租户。新增密钥写 `mci_system_setting` Secret,由可信后端解密到后端 `ServerPrivateSettings`;不返回原文。
237
251
  - 用量与金额分开:`Trend` 带宽点不能按小时积分冒充流量;`Usage(Field=traf)` 的字节量才用于流量合计,金额读取 `Billing`,禁止统一乘固定单价标为实际费用。`Usage` 接受 `UsageType`(不是平台保留参数 `Type`)、`DataProtocol`、`Field`、`Area`;流量/带宽只允许 all 类型与协议,请求数只允许 all 地区。UTC 起止时间与实际 `DataInterval` 必须保留;300/3600/86400 秒的 Usage 单次跨度分别不超过3/7/31天。`CacheHit/OriginTraffic` 同粒度跨度为3/31/366天,不传 Area,单位分别为百分比/字节。
238
252
  - `Billing.Kind` 固定为 `Overview/Instance/Packages/PackageUsage`,返回 `Data.Result`。先用 `BillingCycle=YYYY-MM` 概览发现产品代码,再按 `ProductCode` 查 `Instance` 的 MONTHLY/DAILY 计费项;DAILY 的 BillingDate 必须属于账期。MaxResults不超过300,完整消费NextToken;Packages用Page/PageSize,PackageUsage绑定InstanceId、以中国站账单时间格式原样查询并将NextToken映射LastToken。应付PretaxAmount、原价PretaxGrossAmount、实付PaymentAmount和资源包抵扣不混用;当前包列表不证明历史抵扣,当月暂计、数据延迟、权限失败不伪造为零。
@@ -269,3 +283,5 @@ UnblockIp:<ip>
269
283
  - 对请求热路径做开关前后压测,确认观测开启后 P95/P99、CPU、内存和分配率没有不可接受回退;故障 Mongo 不得阻塞业务请求。
270
284
  - 多节点、网卡重置/回绕、服务重启、匿名/登录、大上传/下载、敏感字段脱敏、时间桶幂等和缓存失效均有验证证据。
271
285
  - 内存专项至少覆盖:V8 限制关闭的复杂执行、真实 Kestrel TraceId、500 个活动请求、异步嵌套归属、独立采集栈、Mongo 故障、执行中强杀、改名重启、跨节点补传、旧快照重放和租户隔离。
286
+
287
+ - 原生签名状态必须先对原始 UTF-8 JSON 字节验 HMAC,再以保留小数精度及字符串日期的设置解析;禁止先解析重序列化后验签,否则 SaaS decimal 阈值 1.000000 变为 1.0 会使后台泵停摆。修复读取器时保留既有回执,不手工改写状态或 Ready。
@@ -216,7 +216,7 @@ Microi 项目必须支持用户或项目级形态偏好:`data-mci-shape="round
216
216
  - **平台 Dialog 统一壳**:除 Drawer 外,框架弹层、微应用弹层、系统设置、商城、通知与业务表单默认使用 20–24px 大圆角、上下左右居中、标题栏可拖动的 Dialog;禁止页面自行恢复方角壳或顶部主题色粗线。标题栏左侧依次提供语义图标、英文眉题、大字标题和可选小字副标题;右上关闭按钮与标题区垂直居中并必须有可辨识的按钮底色。内容区使用连续、固定的柔和背景和整齐栅格;底部操作区右对齐大号关闭/保存按钮,两者都带语义图标。拖动与窗口缩放必须按弹层真实宽高重新约束,四边至少保留 8–18px 可视余量,不得把内容拖出视口。Teleport 到 `body` 的微应用弹层必须在弹层根同步 `data-mci-ui-root`、主题与必要 token,不能只依赖原祖先 CSS 变量。遮罩毛玻璃由 `sys_config.FormMaskBlur=1/true` 全局显式开启,缺失或 `0/false` 默认关闭;`prefers-reduced-motion` 只能停止动画与过渡,不得擅自反转静态毛玻璃配置。
217
217
  - **Dialog 关闭按钮可见性**:右上关闭按钮固定使用 40×40px(移动端不低于 40px)中性灰底、细边框和 10–12px 圆角,不能只显示悬空的细线 X;亮色和暗色主题都必须保持可辨识背景。hover/focus-visible 时使用更深一级灰底、主题色图标和清晰焦点环。底部关闭/取消/保存按钮高度不低于 42px、最小宽度 96px,并保持右对齐与 12px 间距。
218
218
  - **Tabs 统一视觉**:顶部工作区、数据列表 PageTabs、表单 Tabs 与右侧关联数据 Tabs 使用同一套清爽、独立标签视觉。横向 Tabs 禁止铺设贯穿整行的有色轨道、边框底板、下划线或其它装饰线;未选项保持透明,悬停与选中只使用克制的文字色、浅色独立表面和轻阴影。纵向 Tabs 可使用截图式中性浅色分组容器,选中项是独立浅色卡片。数字角标放在标签文本之后且不得挤压标题;过渡只用于交互,并尊重 `prefers-reduced-motion`。框架 `Top/TopSide` 的一级主导航属于站点导航,使用独立短底线标识当前位置,不将该样式扩散到上述 Tabs。
219
- - **表格与卡片行为完整**:桌面数据卡片采用连续白色表面、弱边界和轻阴影,操作区不得再叠加有色底板或硬分割线;表格模块门头的标题/副标题卡片与每个动态统计卡片必须彼此独立,父布局保持透明,禁止用一个大卡片或外框把标题和全部统计项包在一起。表格默认取消无意义的纵向网格,只保留浅表头与轻量横向行节奏,工具栏和分页不得层层套框。`PageBtns`、`BatchSelect`、行内/行外 V8 按钮均不可因改版丢失;需多选而当前未选中时,应先把当前记录纳入选中集再执行。
219
+ - **表格与卡片行为完整**:桌面数据卡片采用连续白色表面、弱边界和轻阴影,操作区不得再叠加有色底板或硬分割线;表格模块门头的标题/副标题卡片与每个动态统计卡片必须彼此独立,父布局保持透明,禁止用一个大卡片或外框把标题和全部统计项包在一起。DiyTable 默认保留 Element Plus 的浅色纵横网格,让列边界与调整宽度位置清晰可见;使用主题边框变量适配明暗模式,装饰分隔线不得拦截表头原生拖动命中区域。工具栏和分页不层层套框。`PageBtns`、`BatchSelect`、行内/行外 V8 按钮均不可因改版丢失;需多选而当前未选中时,应先把当前记录纳入选中集再执行。
220
220
  - **启动状态可理解、可恢复**:Vue 挂载和业务就绪是两个状态。进度到 100% 后若仍在初始化,应显示“正在连接/后端响应较慢/仍在等待”等阶段文字;明确失败时提供原因摘要与刷新重试,禁止撤掉启动层后留下空白页。
221
221
  - **侧栏清爽但层级清晰**:压缩无效间距,保留足够点击区域、选中态、父子层级和键盘焦点;颜色、圆角、阴影必须来自主题 token。
222
222
  - **纵向导航使用短圆角侧标**:侧栏、纵向 Tab、分组导航等选中态的左/右侧强调线统一参照平台左侧菜单:宽 3–4px、高度约为选中项的 45%–60%,垂直居中,端点使用小圆角;禁止铺满 100% 高度,也禁止做折线或包边。右侧 Tab 镜像到右边;横向 Tabs 不使用这条侧标,也不额外增加上下指示线。
@@ -40,6 +40,7 @@ Microi 上传字段存在四种必须长期兼容的存储形态,不能假设
40
40
 
41
41
  - 控件读取嵌套 `Config.ImgUpload/FileUpload.Multiple` 与 `MaxCount`;`Multiple=false` 保存单图对象,不能因数量上限或默认九图值而保存数组。已有单元素数组在单图控件中按第一项读取,不在查看时回写业务数据。
42
42
  - 上传时透传字段授权上下文与配置的 `Limit`,后端仍独立校验字段策略。上传响应中的实际 `Limit` 属于可持久化元数据,必须保留;临时 `Url/PreviewUrl` 与本地预览地址不得入库。
43
+ - 内容安全审核引擎调用 `V8.Method.Upload` 时必须显式传入 `ContentSecurityRequired:true`,不能只传 `Limit:true`:字段的权威公有配置会覆盖普通 `Limit`,审核标记才会使存储策略强制使用私有桶。送审签名地址与实际存储桶必须一致,返回元数据保留实际 `Limit:true`,以便公有字段中的已审图片仍按私有方式预览。回归必须同时覆盖公有和私有字段,以及关闭审核后的普通上传;不能把微信接收送审请求等同于审核通过。
43
44
  - 历史对象缺少 `Limit` 时,以字段配置作为取址依据。已有对象明确 `Limit=true/1` 时,即使字段当前为公有,也必须签名;私有字段上下文与实际私有标记优先于公有提示。签发失败保持空地址,禁止尝试把私有路径拼到公有 CDN。
44
45
 
45
46
  ## 统一资源解析函数
@@ -160,6 +161,10 @@ function imageUrl(path) {
160
161
  - 对象 Key 必须由服务端生成并包含租户隔离前缀,不能信任客户端提交的完整路径。
161
162
  - SVG、HTML、脚本、压缩包和办公文档按风险策略处理;可执行内容不在同源页面直接渲染。
162
163
  - 预览失败时显示受控占位,不回退到未经校验的原始 URL。
164
+ - 跨端附件有持久化 `Path` 但没有 PC 队列 `State` 时,展示层按已上传兼容;明确的等待/失败状态必须保留,不为修复展示而批量改写业务记录。
165
+ - 小程序 `downloadFile` 得到的私有文档临时路径可能无后缀;`openDocument` 必须从原始 `Name/Path` 推导支持的 `fileType`,不从签名代理 URL 推断。覆盖 DOCX、PDF、下载失败、不支持格式和原生预览错误码回归。
166
+ - 未保存表单中的本地临时文档(`wxfile://`、开发者工具的 `http(s)://tmp/`)直接交给 `openDocument`,不重新下载或要求先保存业务记录。普通附件按原始名称/存储路径分流图片、视频和文档,MP4 不能误交文档预览;视频播放器可关闭并在删除/切换附件时释放。保留已保存文件的授权取址、审核拦截和单对象/多数组 JSON;回归断言预览不改业务值、不持久化临时 URL,也不回退拼接私有路径。
167
+ - PC 预览按附件元数据识别类型;视频支持页内播放,未配置 OnlyOffice 的 DOCX 可复用现有只读组件。不得把私有文档发送给外部在线转换服务;配置了 OnlyOffice 时保留现有授权、编辑与版本流程。
163
168
 
164
169
  ## 验收清单
165
170
 
@@ -1,12 +1,22 @@
1
1
  ---
2
2
  name: v8-file-upload
3
- description: Microi V8 与 MCP 文件上传下载指南。用于处理流式 AI 应用发布、V8.FilesByteBase64、V8.Method.Upload、私有文件 URL、文件响应、HDFS、OSS、MinIO 和 S3 存储。
3
+ description: Microi V8 与 MCP 文件上传下载指南。用于流式发布、私有文件、HDFS/OSS/MinIO/S3,以及 OSS/CDN 费用治理、FileServerBak 热点自动复制与持续 NAS 分流、实际消费验收。
4
4
  ---
5
5
 
6
6
  > **Microi吾码基础规范(强制):** 任何 AI 模型与宿主每次新建或接续吾码任务,先完整读取 `../workspace-conventions/SKILL.md`,必须执行版本播报、`@microi.net/cli` 后台自动升级、Skills/MCP 同步和进度播报。安装与诊断读取 `../microi-codex-installer/SKILL.md`;更新失败延后重试,不阻断当前工作。
7
7
 
8
8
  # Microi V8 文件上传下载
9
9
 
10
+ ## OSS / CDN 费用与 NAS 分流入口
11
+
12
+ 用户要求降低 OSS/CDN 流量费用、把高下载模型/JS/图片/视频转到 NAS,或询问
13
+ “1 GiB / 24 小时配置在哪里”时,使用已有热点分流能力。先读
14
+ [热点分流操作流程](references/hotspot-offload-operations.md),再核对本文的原生权限和依赖边界。
15
+ 系统设置 `/system-config` 的 `FileServerBakThresholdGiB`、`FileServerBakWindowHours`
16
+ 控制完整日流量资格;`FileServerBakSizeMiB` 控制独立单文件大小资格,需原生能力回读。
17
+ `FileServerBak` 是备用 HTTPS origin。可信达标后低流量不撤回 NAS 偏好。
18
+ 只复制、保存配置或发布页面都不等于实际入口已走 NAS,也不等于完整日费用已下降。
19
+
10
20
  ## 源码仓库资源隔离(默认强制)
11
21
 
12
22
  截图、配图、PDF/PPT、音视频、压缩包、模型、安装包和其它大体积交付资源统一通过 MCP 上传 CDN,禁止提交到 Gitee/GitHub 源码历史,即使压缩到小于 1 MB 也不能把截图或附件改放源码仓库。官方资源使用已校验的 `microi_itdos`(`https://api.microi.net + OsClient=iTdos`)公有桶与 `https://file.microi.net` 前缀;私有资料按租户实际权限保持私有。源码只存 URL、字节数、SHA-256、MIME 等轻量清单。上传后独立回读实际字节和类型,PDF 验证 Range;超过 Base64 协议容量使用流式/分片工具。暂存区及每个待推送提交都须排除资源本体,详细门禁遵循 [工作区默认 CDN 规范](../workspace-conventions/SKILL.md#截图与大体积资源必须走-cdn默认强制)。
@@ -21,12 +31,22 @@ description: Microi V8 与 MCP 文件上传下载指南。用于处理流式 AI
21
31
 
22
32
  ## MCP stdio 与完整包的传输边界
23
33
 
24
- - 标准 MCP 服务的 stdio 使用固定 `128 MiB`(`134217728` 字节)未解析 JSON-RPC 缓冲,超过边界立即拒绝并关闭连接;不读取动态环境变量放大。SDK 调用端若仍使用
25
- 默认 `10 MiB` 缓冲,大响应仍可能关闭客户端,必须核对调用端支持的真实边界。
26
- - 该限制计算 UTF-8 wire 字节,包含完整 JSON 信封、Base64 和重复 `Data/Result` 正文。单个 Base64 字段的理论原始字节上限约 `96 MiB` 减信封,不能据此保证
27
- 任意 `64 MiB` 包响应都通过,也不能把它当成 HDFS `256 MiB` 应用包能力的新上限。
28
- - 已存在的正规完整包协议仍可使用 `PreparedPersistPackageByteBase64`;先按精确正文计算编码和整个请求/响应字节数,保留版本、CAS、哈希、HDFS 回读及快照 finalize。
29
- 连接关闭属于结果未知,先回读同版本、同请求身份,不能换请求键盲重传。
34
+ ### 普通本机文件上传与备用公私目标
35
+
36
+ - 先回读 `microi_get_status.PrivateFileUploadProtocol` 或 `microi_manage_file_stream_upload action=Capabilities`;支持值为 `PrivateFileUpload/1`。工具已安装不等于 API 已升级,旧 API 不支持时失败关闭,不能改走公有应用发布。
37
+ - `microi_upload_file_stream.localFilePath` 是 MCP 客户端电脑上的普通文件,由客户端读取原始字节、固定 16 MiB 分片;32 GiB 只是协议硬上限,实际仍受 SaaS FileUploadMaxFileMB/MaxRequestMB、DisableFileUpload 与共享日用户/租户额度约束。首次写入分片走现有 UploadQuota 原子额度,确认分片重放不重复预留,失败不退款。不要与旧 `microi_import_external_attachment.sourcePath` 混淆:后者是 API 服务账号能访问的服务器绝对路径/UNC,目标仍是主 HDFS。
38
+ - `target` 默认 `Primary`,`Primary` / `BackupPrivate` 均为私有;显式 `BackupPublic` 只把最终文件写入当前租户 SaaS MinIO 公桶。两种备用目标都核对 `FileServerBak` 公网绑定,暂存私桶不同于公桶且只读策略检查通过;不改 HDFS/桶策略,不允许自由 Endpoint/Bucket/凭据。只接受交互式平台管理员,禁止访问密钥会话。
39
+ - 只有用户明确授权选定文件可由所有人访问时才使用 `BackupPublic`,必须 `publishPublic=true`、`confirmExecution="PUBLIC:" + requestId`;私有目标确认仍是原 requestId。没有公开意图的旧调用保持私有;图库/人脸/其它私密资料不随示例公开。公开会话分片、签名清单和回执始终私有,目标和公开意图绑定签名,不能把既有私有会话换目标公开。
40
+ - 该入口对 MinIO 检查桶策略、OSS 检查私有 ACL 与桶策略;S3 尚缺完整私有 ACL 证明,`StorageProviderSupported=false` 时不上传。Capabilities 仅展示协议/配置,不代替实际桶权限验证。32 GiB 协议格式不放宽普通上传绝对 2 GiB 单文件/总请求保护。
41
+ - 稳定 `requestId` 固定原始 SHA-256/大小/每片哈希、操作者、租户及存储绑定;不确定结果先 Status 后同键续传。MCP 本机检查点不含凭据;跨客户端带原回执 `expectedStorageBindingHash`。不能取新 binding 伪装旧会话恢复。
42
+ - 最终返回 Completed 前服务端合并并回读原始字节 SHA-256;独立验收调用 Verify,不拿 ETag、200、元数据或上传结束代替原片完整性。会话清单/块/回执始终私有并 HMAC 绑定精确对象键,底层使用既有 HDFS 流、原生鉴权和 renewable lease,不经过 V8 大对象。
43
+ - 未完成会话有效期 7 天,Abort 只清理拥有的 staging;完成自动清理失败显示 CleanupComplete=false,重复 Complete/Abort 可补清。不得删除用户最终原片。签名清单/终态回执保留审计;暂存磁盘有界内存流式,最终合并需要与原片等量的临时空间。
44
+ - BackupPrivate 回执的 FilePathName 不能当主 HDFS 路径交给旧 GetPrivateFileUrl。没有备用私有下载票据能力时仅交付后端 Verify 证据,不能拼匿名 URL 或改公桶。当前公开热点分流仍只处理公开资源,与本协议隔离。
45
+ - `BackupPublic` 完成后客户端执行 `ProbePublic`:全量存储 Verify + 可信 FileServerBak 对象匿名 HEAD/首段 Range 校验,成功才返回 PublicUrl;独立 Complete 仅完成存储,没有匿名链接承诺。匿名范围最多16MiB,不冒称全文件已匿名下载;服务端全文件 SHA 与匿名可用性证据分别记录。无 Cookie/凭据/重定向,大文件错误200只读头拒绝,不下载全片。探测失败 Code0 仍保留 Completed/字节证据,沿原 requestId 或原 sessionId/binding 执行 ProbePublic 重试,不换请求号重复上传。管理 Complete 的公开确认是 `PUBLIC:` + sessionId,Abort 仍原 sessionId且只清理自有暂存。
46
+
47
+ - 标准 MCP 服务的 stdio 使用固定 `128 MiB`(`134217728` 字节)未解析 JSON-RPC 缓冲,超过边界立即拒绝并关闭连接;不读取动态环境变量放大。SDK 调用端若仍使用默认 `10 MiB` 缓冲,大响应仍可能关闭客户端,必须核对调用端支持的真实边界。
48
+ - 该限制计算 UTF-8 wire 字节,包含完整 JSON 信封、Base64 和重复 `Data/Result` 正文。单个 Base64 字段的理论原始字节上限约 `96 MiB` 减信封,不能据此保证任意 `64 MiB` 包响应都通过,也不能把它当成 HDFS `256 MiB` 应用包能力的新上限。
49
+ - 已存在的正规完整包协议仍可使用 `PreparedPersistPackageByteBase64`;先按精确正文计算编码和整个请求/响应字节数,保留版本、CAS、哈希、HDFS 回读及快照 finalize。连接关闭属于结果未知,先回读同版本、同请求身份,不能换请求键盲重传。
30
50
  - 超过 wire 能力的源码、运行资产优先使用已有目录/文件 stream 或分片协议及其
31
51
  小型指针/回执,不把私有源码、超大资产常态化塞入 JSON/Jint;支持更大正文的其它
32
52
  连接也须有明确边界。不能用增大stdio缓冲绕过能力鉴权、业务配额或包完整性检查。
@@ -147,7 +167,7 @@ MinIO SDK 7 的复制签名不匹配还可能来自带参数 MIME:SDK 对源 `
147
167
  可信后端 V8 可用 `V8.Http.GetResponse({ Url: url }).RawBytes` 下载,再用 `System.Convert.ToBase64String` 和 `V8.Method.Upload` 上传。该路径同样必须校验域名、大小、Content-Type、后缀和最终重定向目标。
148
168
 
149
169
  <!-- /microi-progressive:chunk -->
150
- <!-- microi-progressive:chunk id=v8-file-upload-002 sha256=176e9f91705f20416e177d7dbdbd32fb8f81dff6ba0da1a45b533795d187f0e0 -->
170
+ <!-- microi-progressive:chunk id=v8-file-upload-002 sha256=587102654ab519a0a97ffe204a0b69c06d868865f4193bfa0546365aa1fa0344 -->
151
171
  ## 接收前端上传的文件
152
172
 
153
173
  前端发起文件上传时,平台自动把文件以 base64 形式注入到 `V8.FilesByteBase64`:
@@ -230,8 +250,8 @@ Unity `Data`、WASM、Windows 安装包、视频模型等发布资产不得进
230
250
 
231
251
  用户明确授权修改某个租户的上传额度时,AI 可以直接使用标准 MCP 完成,不要把应用层提示误判成阿里云 OSS、MinIO 或 S3 的存储配额,也不要先清 Redis:
232
252
 
233
- 1. `microi_get_table_data(tableName: "sys_osclients")` 按 `OsClient`、`IsEnable=1` 查询,选择 `Id/OsClientType/OsClientNetwork`、`DisableFileUpload` 和五个现行 `FileUpload*` 配额字段;`FileUploadEnabled` 只用于诊断未升级旧节点。
234
- 2. 先以当前服务器的 `OsClientType + OsClientNetwork` 收窄到实际生效记录;只有用户明确要求多个环境保持一致时才扩展范围。逐条调用 `microi_update_form_data`,`row` 必须包含 `Id`,并传 `confirmExecution: "sys_osclients"`。
253
+ 1. `microi_admin_table_data(operation: "query", tableName: "sys_osclients")` 按当前 `OsClient + OsClientType + OsClientNetwork` 查询,仅选择 `Id/OsClientType/OsClientNetwork`、`DisableFileUpload` 和五个现行 `FileUpload*` 配额字段;`FileUploadEnabled` 只用于诊断未升级旧节点,不读取秘密。
254
+ 2. 只更新上一步唯一实际生效记录,只有用户明确要求多个环境保持一致时才扩展。调用 `microi_admin_table_data(operation: "update")`,`id/row.Id` 固定已回读 Id,并传 `confirmExecution: "UPDATE:sys_osclients:<Id>"`,其余字段省略。
235
255
  3. MB 是存储单位:`20 GB = 20480 MB`。可修改字段为 `DisableFileUpload`、`FileUploadMaxFileMB`、`FileUploadMaxRequestMB`、`FileUploadMaxCount`、`FileUploadDailyUserQuotaMB`、`FileUploadDailyTenantQuotaMB`;不要再写旧 `FileUploadEnabled`。
236
256
  4. 保存后逐条远程回读;FormEngine 会排队重载 SaaS 运行配置,再用真实小文件上传做生效冒烟。只看到 MCP 返回“更新成功”不算验收。
237
257
  5. 提高每日配额保留当天已用计数,剩余额度为新上限减已用量。计数按 UTC 日期,失败上传不退款;除非用户明确授权事故处置,不得删除 Redis 配额 Key。
@@ -249,27 +269,8 @@ Unity `Data`、WASM、Windows 安装包、视频模型等发布资产不得进
249
269
  - 生产 H5 不能只依赖 `uni.uploadFile`。页面从 `uni.chooseImage` 得到的 `tempFiles[0].file`、`tempFiles[0]`、`blob:` / `data:` 临时路径都要传给 `V8.uploadFile`,并设置 `preferFetch:true`;SDK 必须能用 `fetch + FormData` 兜底,否则线上可能报 `未找到 MicroiV8 上传适配器。`。
250
270
 
251
271
  <!-- /microi-progressive:chunk -->
252
- <!-- microi-progressive:chunk id=v8-file-upload-003 sha256=9d8f1e54ec058d9624a051cc55270019fa1b4a88dadc20f03b2cfa1b856a5bc3 -->
253
- ## 跨平台文件同步登录会话
254
-
255
- 文件柜、文件同步等需要连接另一套 Microi API 的工具,必须把远程平台视为独立登录会话:
256
-
257
- - 用户必须先完成远程登录,登录成功后显示远程用户名称、帐号、ApiBase、OsClient 和登录状态,并提供明确的退出登录操作。
258
- - 历史远程连接通过 `mci_` 前缀表保存,并按 `V8.CurrentUser.Id` 做行级隔离;不得把帐号、密码或 Token 放入 `localStorage`。
259
- - 密码和 Token 只能由受保护的接口引擎写入、读取和清理。数据库必须保存可校验的加密密文,普通 FormEngine 列表不得返回密文字段。
260
- - 密码和 Token 使用 `V8.Method.ProtectApiEngineSecret/UnprotectApiEngineSecret`,由宿主把密文绑定当前 `OsClient + ApiEngineKey`;不得从已脱敏的 `V8.OsClientModel` 读取 `AuthSecret/DbConn`,也不得使用进程级临时密钥。接口引擎 Key 必须稳定,确保服务重启和应用升级后仍能解密历史连接。
261
- - 历史连接列表只返回脱敏元数据;一键重连时再按记录 Id 和当前用户读取凭据。删除连接必须同时清除保存的密码和 Token。
262
- - 远程目标登录后必须调用文件柜能力探针 `mci_file_sync_capability` 检查同步协议版本,并使用其 `Data.FileManagerSysMenuId` 作为目标租户权威文件柜菜单;禁止硬编码发布端菜单 Id。接口不存在、未返回菜单 Id、返回 404/非标准结果或协议版本过低时,提示目标平台更新【文件柜】应用,不得继续同步。
263
- - 验收至少覆盖:登录成功显示身份、退出后 Token 清空、历史连接一键重连、删除连接、密文落库、服务重启后仍可解密、目标平台缺少能力接口时的升级提示。
264
-
265
- ### 热点公开资源的可信分流
266
-
267
- - `PublicResourceOffload/2` 与旧目录/路由第一版分别验收。系统日志应用拥有 `mci-file-resource-runtime`(管理员控制面)与 `mci-file-resource-public-url`(匿名只读公开选择);目录的 Ready、流量和副本行不作为运行授权,不能通过普通 CRUD 制造成功。
268
- - 从当前 SaaS 引擎读取 MinIO 凭据,第二版 FileServerBak 使用 HTTPS origin 加后端绑定的公有桶。主 HDFS 保留 Aliyun;不全桶迁移、不删源文件、不把私有桶改公开。只复制当前已发布公开运行清单,JS/CSS/模型依赖完整成组;复制、签名回读与匿名 SHA/MIME/Range/CORS 全部成功后才允许消费者采用 NAS。
269
- - 完整北京时间自然日的 CDN 运营报表及已接入 SDK 的实际响应字节可证明超过阈值的正向下界;榜单缺行、计量缺失和空报表不是零。迁移验证单独排除;历史人工指定单独记录,不能伪造当前24小时阈值通过。阈值触发后的同版本副本持续保留,避免源流量下降反复切回。
270
- - HTTP/MCP只登记复制意图;共享签名状态、租约栅栏、逐文件回读恢复与退避负责副作用。未知上传先对账,异内容不覆盖。当前版本/完整清单或配置变化、撤为私有、NAS健康失效均回退原地址;先确认真正消费路径,再分别验收实际下载下降与完整日账单节省。主壳预配置 JS、旧绝对 CDN 书签和非受管资源须明确列出未接入范围,不能声称所有资源自动切换。
271
-
272
- <!-- /microi-progressive:chunk -->
272
+ 热点分流与跨平台会话的完整规则见
273
+ [操作与原生边界](references/hotspot-offload-operations.md)。
273
274
  ## 详细参考路由(渐进披露)
274
275
  仅在当前任务涉及对应主题时读取;下列文件合计保留了原 SKILL.md 的全部详细知识。
275
276
  - [references/progressive-01-公有桶-vs-私有桶.md](references/progressive-01-公有桶-vs-私有桶.md):公有桶 vs 私有桶;接口直接响应文件(下载/导出);通过 URL 列表批量下载并入库
@@ -0,0 +1,118 @@
1
+ # 热点公开资源分流操作流程
2
+
3
+ <!-- microi-progressive:chunk id=v8-file-upload-011 sha256=816ab538fe14a65f9f37aefb91dfcb0d8cbfaaa463fa7529cb7455780f96ed04 -->
4
+ ## 从费用需求到实际分流
5
+
6
+ 用户希望降低 OSS/CDN 下载费用时,先核对当前连接、运行能力、配置和真实消费入口。
7
+ 不全桶迁移,不删除云端源对象,不把私有文件改公有,不承诺费用降到零。
8
+
9
+ ### 配置与计量
10
+
11
+ - 系统设置路由 /system-config:备用文件服务器 FileServerBak,备用分流阈值(GiB)
12
+ FileServerBakThresholdGiB,备用分流统计窗口(小时)FileServerBakWindowHours。
13
+ - 大文件分流阈值(MiB)为 FileServerBakSizeMiB;需实际 Capabilities.SizePolicyProtocol
14
+ 等于 PublicResourceSizeQualification/1。100 表示单文件严格超过 104857600 字节;
15
+ 与完整日流量为或关系,空/0 关闭新大小晋级,不能把文件大小记为下载流量。
16
+ 已确认同源偏好不随调高阈值或低流量取消;新版本、权限、健康与停用门禁不变。
17
+ 原调度器每轮最多200条公有桶元数据、5个候选、25秒发现预算,主库签名游标续跑;
18
+ 整轮元数据扫描完成6小时再扫,调节阈值重置游标,不重下有效副本、不创建第二任务。
19
+ Status.SourceObservation.LargeObjectInventory 查看进度及排除计数;列表不是对象ACL证明,
20
+ 源头逐一公开重验、完整依赖和SHA通过后才路由。2 GiB/128对象限制与私有拒绝仍有效。
21
+ - 示例 1 / 24 表示一个对象或完整公开依赖组在已结束的完整北京时间自然日内,
22
+ 可信实际下载字节达到 1073741824;不是文件大小、当前部分日或滚动窗口。
23
+ - 当前 FileServerBak 使用 HTTPS origin(允许端口,不带桶路径);SaaS MinIO 绑定
24
+ 内外端点、公私桶与凭据。HDFS 保持 Aliyun,原 FileServer 保留作权威来源/回退。
25
+ - 首次可信达标后 LowTrafficRevokesPreference=false:后续低流量、重启或缓存丢失
26
+ 不撤回同源偏好。内容更新须重新验证;来源/权限改变、健康失败或管理员停用优先。
27
+
28
+ ### AI / MCP 执行顺序
29
+
30
+ 1. profiles 核对 ApiBase、OsClient 与工具链版本;读取当前管理能力、Schema 及唯一
31
+ sys_config 记录,仅投影三个备用配置字段。先发现、describe_tool 再调用写工具。
32
+ 2. microi_run_engine 调用 mci-file-resource-runtime,先 Action=Capabilities、Status,
33
+ 检查原生协议和实际部署;配套系统设置、系统日志/监控应用通过官方应用包安装。
34
+ 3. 非敏感阈值通过标准配置保存与独立回读。MinIO 只用 microi_manage_saas_minio:
35
+ Inspect 取得 Revision,Save 绑定 expectedRevision 与 SAVE_MINIO:<tenant>:<revision>,
36
+ 再 Inspect / Probe。凭据只写不读;超时先回读,不盲重放,不向通用表或临时 V8 写密钥。
37
+ 当前工具列表缺失时核对发行包与已加载 MCP,更新后重新发现;不能猜不存在的工具。
38
+ 4. 读取固定任务 microiPublicResourceOffloadScheduler / mci-file-resource-scheduler,
39
+ 保留其启用/暂停意图;不能新建第二套采集复制循环。任务配置需按 job-engine 规范。
40
+ 5. 在资源状态读取 Collector、WindowEvaluations、来源绑定、复制与 LastPump,按真实
41
+ Id/对象键定位失败。采集受 30 分钟到期门禁,日报迟到补最近两个完整日;未知不是零。
42
+ 6. 已有历史热点可在授权范围通过 PinHistoricalHotspot、PinHistoricalPublicObject 或
43
+ PinHistoricalAsset 明确选择;先读取当前引擎参数,按各自精确身份传参。
44
+ 人工指定单独留因,不伪造当日流量、不直接写 Ready、不自动恢复已停用资源。
45
+ 7. 验证当前发布的完整依赖组,等原生复制、摘要和匿名门禁全部通过。应用 JS/CSS
46
+ 必须有可信清单;模型包括贴图依赖。独立 JS/CSS 缺清单不能强当无依赖叶文件。
47
+ 8. 通过 mci-file-resource-public-url 或应用稳定入口验证选址,再从真实官网/应用
48
+ 核对最终 URL 与 NAS 字节、MIME、CORS、HEAD/Range。静态主域仍 200 可能是保留
49
+ 回退入口,不能仅据此推断全部失败,也不能据 NAS 副本 200 推断所有客户端已接入。
50
+
51
+
52
+ ### 固定旧下载入口补漏
53
+
54
+ 已达标且副本 Ready 不等于旧 CDN URL 已切换。对仍有下载量的硬编码地址,先核对精确对象、公有权限、来源/目标绑定、摘要、健康期和实际消费者。系统日志包 v8.6.25 的既有 mci-cdn-manager 只对白名单 static.itdos.com、file.microi.net 上三个 Chromium 137.0.7151.68 安装包支持精确重定向:ChromiumRedirectState 明确传 Domain,Preview / Submit 的 Command 明确传同一 Domain 与 Platform,沿原 RequestKey、共享域名租约和供应商 ConfigId 回执执行;结果未知只 Reconcile 原 Id。不能把它扩张为任意域名、正则或全目录替换。
55
+
56
+ 两个入口都重定向时,故障回退必须离开这两个入口,使用逐个严格 TLS / HEAD 验证过的固定公共源,保留原对象 ACL;不签发私有地址。验证旧 URL → 选择接口 → NAS 的完整链、文件大小和有界 Range,同时确认其它云配置及已有规则未变。低流量不撤回偏好,健康/权限/版本失败仍可暂时回源;按每条入口实际生效时间另取完整日账单。HTTP 请求链与 HTML 相对引用证据不能冒充浏览器 Network/渲染验收。
57
+
58
+ ### 请求批次与覆盖范围
59
+
60
+ 前端接入读取 `microi-frontend-sdk/SKILL.md`。独立项目的 SDK 实例使用
61
+ `V8.configure({apiBase,osClient,fileServer,fileServerBak})` 同步当前租户配置,
62
+ 在首个资源请求前等待 `V8.resolvePublicResource(value,{private:false})`;
63
+ 明确公有上传元数据也可由 `resolveFileUrl` 解析。NAS 加载实际失败时,
64
+ `V8.reportPublicResourceFailure(value)` 提供一次原址回退。
65
+ 主后台使用 `DiyCommon.GetPublicResourceRoute`;后端固定原子为
66
+ `V8.Method.GetPublicResourceBackupUrls({ObjectKeys})`,由受管接口编排。
67
+ 独立 SDK 的 `V8` 实例不是主后台 V8 事件新增的同名全局函数,不能混用。
68
+
69
+ ObjectKeys 协议最多 100 项,不代表一轮可确认任意数量的独立组。
70
+ 原生实际选择另受独立组数与时间预算约束;只采用每行 UseBackup=true 且回执完整的结果。
71
+ 诊断多组可按小批(例如 5 项)读取,并保留每项状态;未选择不是零流量或文件不存在。
72
+ 旧硬编码 URL、平台 bootstrap、私有对象和未接入 loader 单独列为缺口,不能全局字符串
73
+ 替换,也不能把公开编译组路由扩大到私有源码。
74
+
75
+ ### 归因与财务验收
76
+
77
+ - 用 mci-cdn-manager 的 Reports / StorageLogs 等受管动作定位具体对象、请求数和字节;
78
+ 公私桶、域名、日期、原始窗口、覆盖边界与 TOP 截断保留,旧报告不冒充当前全集。
79
+ - 高请求数不等于同等次数的完整成功下载;GiB 与账单 GB 分开,不据样本分摊精确人民币。
80
+ - Billing 采用相同币种/税口径,完整分页;OSS NetworkOut、CdnOut 与 CDN 下行分别核对,
81
+ CdnOut 已在 OSS 总额中时不再次相加。完整旧代码日与完整新代码可用日才能比较。
82
+ - 首次复制成本、混合部署日、欠费停用日和当天部分账单分开;缺少可比日时实际节省为空。
83
+ 按每个源的实际切换时间记录,不能用单节点健康或页面发布替代全部覆盖证明。
84
+
85
+ <!-- /microi-progressive:chunk -->
86
+
87
+ <!-- microi-progressive:chunk id=v8-file-upload-003 sha256=18bfbc6466abb62cec99d226b3bdd5091669a8317ab02f5045b7913f366c4334 -->
88
+ ## 跨平台文件同步登录会话
89
+
90
+ 文件柜、文件同步等需要连接另一套 Microi API 的工具,必须把远程平台视为独立登录会话:
91
+
92
+ - 用户必须先完成远程登录,登录成功后显示远程用户名称、帐号、ApiBase、OsClient 和登录状态,并提供明确的退出登录操作。
93
+ - 历史远程连接通过 `mci_` 前缀表保存,并按 `V8.CurrentUser.Id` 做行级隔离;不得把帐号、密码或 Token 放入 `localStorage`。
94
+ - 密码和 Token 只能由受保护的接口引擎写入、读取和清理。数据库必须保存可校验的加密密文,普通 FormEngine 列表不得返回密文字段。
95
+ - 密码和 Token 使用 `V8.Method.ProtectApiEngineSecret/UnprotectApiEngineSecret`,由宿主把密文绑定当前 `OsClient + ApiEngineKey`;不得从已脱敏的 `V8.OsClientModel` 读取 `AuthSecret/DbConn`,也不得使用进程级临时密钥。接口引擎 Key 必须稳定,确保服务重启和应用升级后仍能解密历史连接。
96
+ - 历史连接列表只返回脱敏元数据;一键重连时再按记录 Id 和当前用户读取凭据。删除连接必须同时清除保存的密码和 Token。
97
+ - 远程目标登录后必须调用文件柜能力探针 `mci_file_sync_capability` 检查同步协议版本,并使用其 `Data.FileManagerSysMenuId` 作为目标租户权威文件柜菜单;禁止硬编码发布端菜单 Id。接口不存在、未返回菜单 Id、返回 404/非标准结果或协议版本过低时,提示目标平台更新【文件柜】应用,不得继续同步。
98
+ - 验收至少覆盖:登录成功显示身份、退出后 Token 清空、历史连接一键重连、删除连接、密文落库、服务重启后仍可解密、目标平台缺少能力接口时的升级提示。
99
+
100
+ ### 热点公开资源的可信分流
101
+
102
+ 8.7.11 原生后端把 5–64 MiB 既有公开对象(包括只有一个 8 MiB 捕获块的对象)交给同一原生 multipart 完成协议;保留源权限、签名状态、私有块和整文件 SHA、匿名 MIME/CORS/Range/健康门禁。未知结果先对账,不重新拉取已确认的 OSS 正文;停用仍先撤权再精确清理,不可手改 Ready。回归与实际 NAS 消费分别验收。
103
+
104
+ - SaaS MinIO 绑定的凭据不能通过通用表工具写入。使用 `microi_manage_saas_minio` 的 Inspect → Save(Revision CAS)→ Probe;旧后端缺少入口先升级。它不改变 HDFS/OSS, 不复制对象或改变桶策略;签名桶探测成功与副本校验、实际分流是不同验收项。
105
+
106
+ - `PublicResourceOffload/2` 与旧目录/路由第一版分别验收。系统日志应用拥有 `mci-file-resource-runtime`(管理员控制面)与 `mci-file-resource-public-url`(匿名只读公开选择);目录的 Ready、流量和副本行不作为运行授权,不能通过普通 CRUD 制造成功。
107
+ - 从当前 SaaS 引擎读取 MinIO 凭据,FileServerBak 使用 HTTPS origin 加后端绑定的公有桶。主 HDFS 保留 Aliyun;不全桶迁移、不删源、不公开私桶。第二版应用使用当前已发布公开运行清单完整分组;通用 v3 支持公开图片/音视频等叶子及 GLB/glTF 同桶相对依赖;包含 EXT_texture_webp 解析修复的原生后端仅允许扩展 source 为 images 的合法整数索引,内嵌图片无外部依赖,外链与普通回退图片均保留完整 URI/ACL/版本校验,未知扩展仍拒绝;此兼容不改变应用可见性、不接通未调用解析器的 loader,8.7.7 单对象 64 MiB、组 256 MiB;只有实际 Capabilities.GenericLargeCopyProtocol=PublicResourcePinnedByteCapture/1 的后续后端才将单对象/组分别扩至 2 GiB,含入口仍最多 128 对象,8 MiB 私有暂存块有签名 SHA 回执,恢复复用已确认块;依赖由暂存解析,完整 SHA 由 NAS 组装计算。源 ACL/版本与当前绑定是权威,ETag 不是 SHA。8.7.7 配套 Managed runtime 的 PinHistoricalPublicObject 仅传 ObjectKey,当前公开 HEAD 后保存人工历史选择,worker 重验相同源绑定/版本才纳入;不造流量、不直写 Ready、不恢复暂停。JSON 原样作为叶文件,不解析改写包内 URL,限额按实际原生能力回读,不放宽商城解析/安装正文限制;商城消费须对比返回 VerifiedSha256/VerifiedSize 与权威精确包版本,再验同响应 Binding,旧后端缺证明回源。未接可信提交清单的独立 JS/CSS 拒绝;完整复制、签名回读、匿名 SHA/MIME/Range/CORS 才 Ready,未包含精确 Origin 修复的通用后端仅接受 CORS *;包含本修复后亦接受与本次配置 ApiBase 的 HTTPS Origin 完全相同的单一值,错误域名、多个值、null 或缺失值仍拒绝。
108
+ - 历史安装微服务的商城 `IsPublic=0` 不等同于其已发布编译文件是私有对象。只有原生 `InstalledPublicBuild/1` 提供者从主库独立读取当前应用、唯一协议 0–2 历史版本、唯一启用服务及完整 `dist/` 文件集合,复用安装别名四方一致性校验,并确认匹配且未到期的 `InstalledAliasReconciliation/1` 三次稳定检查点后,才可把整个公开构建依赖组纳入分流。每个文件必须标记 `PublicBuildOnly` 或 `PrivateSource+PublicBuild`,源对象当前公有 ACL、版本、原始 SHA 与同一复制 Run 的目标回执都要核验;单个 JS、私有源码、陈旧检查点、缺失成员或跨租户路径均不准入。首次开放目标前和健康续期复查全组,实际选择时再复查所选源对象;失败继续原授权下载链,不把 `IsPublic`、`LegacyUnverified` 或协议号改成公开/V3 成功。该接口只供原生宿主内部调用,不开放新的 HTTP、MCP 参数或可编辑 V8 能力。探测链最后一个 NAS 响应返回后,准备与续期都通过同一个完成门重新读取当前整组权威和公有 ACL,再检查租约并提交 Ready 或健康时间;变化则走现有失败撤权。两种真实访问入口使用同一个 5 秒总预算覆盖绑定、Redis、主库权威、ACL 和 HTML HEAD,原生权威读取最多 4 个并发,超时停止等待但占用槽位直到已启动读取结束,后续 SQL 不再启动;后台任务仍使用宿主原有取消期限。 稳定微应用资产入口的原生叶资源兼容(须部署包含此修复的后端):既有 Controller 完成当前版本/清单选择后,若应用组路由不可用,可只读查询该清单**精确对象键、原始 SHA-256 与大小**匹配的通用公开 Ready 副本。公开对象仍须通过源 ACL/版本、源与目标绑定、健康期和受管 NAS URL 校验;不会把应用 IsPublic=0 改为公开,也不公开私有源码或私有对象。HTML、JS、CSS、JSON、SVG 及 OriginalStorage=1 保留原链。root/latest/版本路径即使同名同 SHA 也不能互借 Ready;历史榜单对象已复制不等于当前版本路径已接通。直接 static/CDN URL 绕过稳定入口时仍不受本兼容影响。该能力不新增 V8/MCP 动作或可由调用方覆盖的 owner/摘要参数。
109
+ - 当前权威公开清单中的 `Sha256` / `ContentHash` 与历史 `Hash`(含小写别名)统一按原始文件 SHA-256 校验,仅接受 64 位十六进制值;现代字段优先,任一非空别名无效或多个别名冲突均拒绝。此兼容不改变文件大小、完整原始字节 SHA 回读、公私权限或发布/检查点状态,不能把 ETag、MD5 或目录声明当作可信摘要。
110
+ - 1 GiB / 24 小时按最新已结束的完整北京时间自然日计算,不是滚动或当天部分窗口。CDN/已接 SDK 只证明正向下界,缺行/空报表不是零,版本内分摊未知。迟到日报只补采最近两个完整日;同日同域同 key 快照取最大值,跨域分开、跨日不合并;每个日期先保存指向另一日的签名游标再做 SDK/云读取,避免预算饿死旧日。未知计数为 null,WindowEvaluations 仅两条摘要,诊断明细超 3 MiB 显式截断并保留总数;超签名预算则拒绝签名。固定 microiPublicResourceOffloadScheduler / mci-file-resource-scheduler 使用主库鉴权及共享 tick 租约;旧 Disabled/已签 handoff 须租约迁移,不添 Pin/Ready。升级保留 Redis/索引、建事实表并统一全部节点;handoff 持久化后暂停、删除任务或 Redis 丢失不恢复旧宿主。统一 30 分钟采集;4 分钟 BudgetReached/Partial 不代表全体 Ready。首次资格进入主库签名 CAS,低流量、重启/Redis 丢失不取消;新版本重验权限和副本,显式停用优先。旧 v2 缺源桶/端点不能从 SourceRevision 反推:缺 ConfiguredSourceBinding 拒继承与路由;仅管理员新 PinHistoricalHotspot 对当前完整同清单/版本/SHA/大小/目标明确重申可复用旧 Ready,先健康回读,不造新自动资格。已有绑定不一致须新运行。CDN 只取当前源首次观察之后的整日,旧未绑定 SDK 累计保留但不合并。源码回归不替代生产采集、真实请求下降或完整日账单验收。 别名自动采集须含 NativePublicSourceAlias/1 的后端:固定云只读重验唯一同源 OSS 与显式同源 Host,无 rewrite/条件/未知配置;独立签名 ObservedSince 覆盖整日,本轮现查成功才归属,失败/变化/超过24小时观察间隔重建。只用既有source-observation-v1签名行的CdnAliasCollector子结构(50条/128KiB),不增物理StateKey;同源同目标保留,源/目标变化或旧节点丢字段只重置观察、不追认旧日。每轮最多2域、先存公平游标、每元数据调用15秒,共享总预算;原 FileServer/SDK路径保持。Status.LastCollection.AliasCollection/CdnAliasAuthority展示有限范围,不能把公有桶名当对象ACL,也不改变旧static消费者或SourceMappings;普通V8不能自报映射/流量。 实际旧URL消费须另具PublicSourceUrlRoutes/1:AliasCandidates仅发现;明确公有客户端提交精确无签名PublicUrls,native固定来源重验后走原Generic公开ACL/依赖/Ready选择,60秒有界证明不当永久授权。回执须精确SourceUrl/ObjectKey/目标前缀和最短健康期,tenant变化/异常行/过期回原地址;不得把同步assetUrl、启动JS、旧HTML或任意Three.js都称已覆盖。
111
+ - HTTP/MCP只登记意图,目录 CRUD 不能制造 Ready;先通过拥有方应用包安装 mci_file_offload_state 及 Owner/StateKey 唯一索引,原生主库签名/CAS、租约栅栏与检查点负责副作用。Web/UniApp 按类型取 committed V3,旧 service 不遮蔽;源码草稿 Changed 仅在当前应用/版本均 Completed V3、精确提交归属/正栅栏/请求前缀/完整重算清单与双方摘要一致时沿用已验证运行副本,微服务还须精确匹配该提交投影及不可变对象键。Legacy/Failed/Building 不放宽;只改草稿不改 SourceRevision、不重读 OSS 正文,真正权限/版本/源绑定漂移仍撤权,禁止手工改 Success/Ready;保留权威 AppKey 原大小写,索引按状态键去重。未知上传先对账,异内容不覆盖。源 ACL/版本/依赖或配置变化停止新路由,签名前仍重验源权威。先确认消费路径,再分别验收实际下载下降与完整日费用,文档和离线测试不证明部署或省费。
112
+
113
+ - 公开之前先持久化签名状态的 PublishedRuns 账本;停用/源变私有/版本或目标变化按账本撤自有前缀 GetObject,策略回读后须逐对象匿名 HEAD 403/404,不能把“移除自有策略”当匿名已撤销。旧目标绑定未知则 PreviousTargetBindingUnavailable 阻断,不借新目标凭据称旧 URL 已失效;历史缓存字节不能召回。
114
+ - 已提交公开应用及受权威版本约束的历史安装包复用 PublicResourceLargeAssetTransfer/1:2 GiB 内按 8 MiB 私有块、NAS server-side multipart Copy 和持久完整 raw SHA 续传;每步 120 秒受宿主预算/租约取消,未知结果先对账。匿名 Range 逐段签名续验,ETag 不是 SHA;旧 MIME 仅在完整 SHA 相符后条件 COPY 修复并重验,不能放宽类型。停用/换版先撤权,精确清理自有 upload/chunk,保留失败账本。具备 PublicResourcePinnedByteCapture/1 时,普通公开叶文件 64 MiB–2 GiB 从 pinned 源块回读推进 SHA,再移交同一私有块,不二次下载 OSS;GLB 只读头与至多 4 MiB JSON,不整读 binary 或大临时文件。旧预知 SHA 入口保持。临时失败保留分块;显式停用/来源失效先记退休意图、撤权再断点清理,重新启用按新运行验证。完整日首次达标的同源长期偏好不随低流量撤回。>2 GiB、私有、缺可信清单的独立脚本和未接消费者仍不覆盖。
115
+ - 历史大安装包使用独立 `PinHistoricalAsset / EnableHistoricalAsset / DisableHistoricalAsset / ListHistoricalAssets`,经标准 MCP 调用拥有方 Managed mci-file-resource-runtime。只传 SourceTenant/AppKey/VersionId/AssetPath;当前主库重新校验公开 Web/UniApp、已完成 V3 历史版本、请求前缀与完整清单 SHA。文件名版本不等于发布版本;不换 latest,不接收调用方 URL/摘要/Ready,不借用通用字节捕获替代历史版本授权。仅精确 exe/dmg/zip,单文件最多 2 GiB。
116
+ - 历史 archive 状态独立主库签名/CAS,人工选择不伪造当前流量;重复 Pin 保留停用。8 MiB 原生分块、NAS multipart 与逐段匿名校验都保存可恢复回执,完整权威 SHA 才 Ready;源/目标变化先撤旧公开授权,私有暂存所有权保留待清理。匿名 Managed HTTP mci-file-resource-archive-download 只读重验后 302 到已验 NAS 或同一历史版本原云地址;私有/权威缺失拒绝。先部署接口再发官网,按 more/hdfs.md 分层验收。
117
+ - 七文件消费者为 public-resource-router.mjs、diy.common.js、diy-imgupload.vue、diy-fileupload.vue、DiyTableSpecialCell.vue、diy-common.mixin.js、diy-richtext.vue:显式 Limit:false 初始 src/链接/新富文本插入前 await 批量原生 GenericPublicResourceRoutes/1,校验绑定、源/行/字段/租户身份,可观测错误最多一次原址回退。卡片 helper 无活动调用点不能算额外页面覆盖;私有、旧富文本 HTML、Three.js/AI 硬编码 URL、平台 bootstrap、独立 JS 缺清单、ZIP 聚合/下游 Office 仍未覆盖;完整入口和 HEAD 后失败限制见官网 more/hdfs.md。
118
+ <!-- /microi-progressive:chunk -->
@@ -57,6 +57,19 @@ V8.OsClientModel.AliOssPublicDomain // 可公开的文件域名
57
57
 
58
58
  ## 平台级配置与主租户规则
59
59
 
60
+ ### 管理员开通的租户访问地址
61
+
62
+ `sys_osclients.DomainName` 的显示名称为“租户访问地址/域名/Url”,支持域名、
63
+ IP:端口及 HTTP(S) URL。同一局域网 IP 的不同端口可登记不同租户。
64
+ 创建页从可信宿主的当前浏览器 WebBase 生成默认值:域名用 OsClient 加注册域
65
+ (按公共后缀表处理 com.cn/co.uk),IP 用 `{ip}:手动指定端口号`;中文提示必须
66
+ 改为 1–65535 的实际端口,用户手动填写后不得被 OsClient 联动覆盖。不回退固定官方域。
67
+
68
+ IP、端口和完整 URL 只登记入口,不提交官方 ESA 域名绑定。仍需配置客户 Web 网关;
69
+ 登记成功不证明可访问。前端、Managed `admin_create_empty_saas_tenant` 和
70
+ `SaasPromotionSecurity.TryResolveProvisioningDomain` 必须同时兼容,单发微服务不能
71
+ 绕过旧 API 的裸域校验。公开试用继续保持独立域名为空,不扩大匿名开通权限。
72
+
60
73
  ### 推广中心与匿名空库开通
61
74
 
62
75
  - 主租户现有 `V8.FormEngine` 允许显式指定子租户;子租户被 `V8TenantContext.EnforceOsClient`
@@ -130,8 +143,8 @@ V8.OsClientModel.AliOssPublicDomain // 可公开的文件域名
130
143
  - 主租户由运行环境决定:优先读取环境变量 `OsClient`,其次读取 `appsettings.json` 的 `AppSettings:OsClient`。只有这条主租户 `sys_osclients` 数据中的平台级字段会作为全局配置生效。
131
144
  - 同一个主租户可以同时运行在 `Product/Internal`、`Product/Internet` 等多个分区;租户目录、后台任务领取、通知和批量平台应用维护都必须按当前节点的 `OsClientType + OsClientNetwork` 隔离。一次全量维护只证明当前分区,事故恢复必须先盘点所有活跃分区,再从各分区对应节点分别执行并等待 `SucceededCount=ExpectedCount、FailedCount=0`,随后各自以新幂等键做零安装/零更新复跑。禁止临时改租户网络字段、跨分区借用任务或并发维护可能指向同一物理库的重复租户记录。
132
145
  - 大范围启动事故只允许受信后台编排器使用 `MaintenanceScope=StartupDependencies`,精确闭包固定为 `app.microi.store + app.microi.saas-engine`,并可显式启用 `StartupDependencyBootstrapOnly=true`。编排器必须先用权威当前分区目录校验 `TargetOsClients`,再在子任务仍为 `Pending` 时原子写入并强回读两应用范围与只自举标志;失败即取消,禁止退化为完整安装。工作器只从官方不可变包补齐 `platform-sys-menu` 和六项 SaaS 启动门面,七项物理契约强回读通过后终止,不写安装版本。该模式只恢复可登录性,不代表 API 进程的完整运行时已经就绪;新版后端接收流量前还必须从九个内置官方基础应用包补齐并回读全部接口引擎,后续完整应用更新仍走普通商城任务。未带标志的任务行为不得改变。
133
- - API 启动配置只有十项白名单:`OsClient`、`OsClientType`、`OsClientNetwork`、`OsClientDbType`、`OsClientDbConn`、`OsClientRedisHost`、`OsClientRedisPort`、`OsClientRedisPwd`、`OsClientRedisDataBase`、`OsClientDbMongoConn`。除这十项外,部署/节点级运行参数与基础设施秘密从主控 `sys_osclients` 读取;允许子租户自行维护且需要浏览器判断的业务开关、入口显示和公开交互配置使用该租户 `sys_config` 实体字段,OAuth/第三方集成的凭据、RP/Origin/Issuer/Scope 与仅后端参数使用 `mci_system_setting`,未配置时使用代码安全默认值。官方 License 恢复次数/间隔与固定私钥挂载 `/app/microi_private.pem` 是信任链例外。禁止再增加 `MICROI_*`、`DOS_ORM_*`、自定义 `AppSettings` 节点或动态名称的环境变量读取。节点身份由平台自动生成。
134
- - 存量畅捷通/微信 OAuth C# 协议网关有一组已发布到 SaaS 引擎的兼容字段:`OAuthReturnUrlOrigins`、`ChanjetOAuthState`、`ChanjetAesKey`、`ChanjetAppKey`、`WeChatTemplateAppId`、`WeChatTemplateAppSecret`、`WeChatTemplateId`、`WeChatMiniProgramAppId`。只能通过 Core 的租户绑定协议设置原子按请求权威 `OsClient` 读取,禁止使用主租户 `ConfigHelper` RuntimeConfigurationReader;整组字段不得复制给新租户,其中 OAuthState/AES Key/AppKey/AppSecret 不得进入 `V8.OsClientModel` 或前端投影且必须在审计中掩码。新集成不得继续扩展该兼容集合,仍使用当前租户 `mci_system_setting` + Managed ApiEngine。
146
+ - API 启动配置只有十项白名单:`OsClient`、`OsClientType`、`OsClientNetwork`、`OsClientDbType`、`OsClientDbConn`、`OsClientRedisHost`、`OsClientRedisPort`、`OsClientRedisPwd`、`OsClientRedisDataBase`、`OsClientDbMongoConn`。除这十项外,既有部署/节点级运行参数与基础设施秘密从主控 `sys_osclients` 读取;允许各租户维护的业务配置按内容敏感性归属:开关、公开 HTTP(S) 服务地址、公开 ClientId/回调、RP/Origin/Issuer/Scope 使用该租户 `sys_config` 实体字段与普通系统设置,Secret/Token/密码/私钥、含认证信息的 URL 及确需隐藏的内部拓扑使用 `mci_system_setting`。不能仅因第三方或后端使用而私密化;公开配置仍需管理员写入权限、租户隔离、字段/地址白名单和既有 SSRF 校验。未配置时使用代码安全默认值,不因此自动迁移旧字段。官方 License 恢复次数/间隔与固定私钥挂载 `/app/microi_private.pem` 是信任链例外。禁止再增加 `MICROI_*`、`DOS_ORM_*`、自定义 `AppSettings` 节点或动态名称的环境变量读取。节点身份由平台自动生成。
147
+ - 存量畅捷通/微信 OAuth C# 协议网关有一组已发布到 SaaS 引擎的兼容字段:`OAuthReturnUrlOrigins`、`ChanjetOAuthState`、`ChanjetAesKey`、`ChanjetAppKey`、`WeChatTemplateAppId`、`WeChatTemplateAppSecret`、`WeChatTemplateId`、`WeChatMiniProgramAppId`。只能通过 Core 的租户绑定协议设置原子按请求权威 `OsClient` 读取,禁止使用主租户 `ConfigHelper` RuntimeConfigurationReader;整组字段不得复制给新租户,其中 OAuthState/AES Key/AppKey/AppSecret 不得进入 `V8.OsClientModel` 或前端投影且必须在审计中掩码。新集成不得继续扩展该兼容集合,由 Managed ApiEngine 编排,并按内容敏感性分别使用当前租户 `sys_config` 实体字段或 `mci_system_setting`;不批量迁移这些存量字段。
135
148
  - `ASPNETCORE_*`、`DOTNET_*` 仅用于 .NET 宿主;构建、安装、测试、MCP、发布脚本可使用自身进程变量,但 API 生产代码不得把它们当业务配置。新增 SaaS 运行字段必须配套独立或既有 Tab、幂等升级、缓存刷新、敏感字段脱敏、子租户不继承和源码扫描测试。
136
149
  - 文件上传的租户业务开关与额度按“当前租户 `sys_osclients` → 代码默认值”解析;现行负向字段 `DisableFileUpload` 默认关闭/空值即允许上传,只有 `1/true` 才禁止。旧 `FileUploadEnabled` 只在新物理字段尚不存在的滚动升级节点回退读取,并须在新版 SaaS 表单隐藏。平台固定灾难保护、HTTP/Multipart/Form 和反向代理上限不可由租户覆盖,也不要求安装者维护额外上传环境变量。
137
150
  - 类似 MQTT 端口、PressureGuard、V8Limits、OrmLimits、StartupLimits、SecurityGuard 这类影响整进程资源的配置,不能让每个子租户各自抬高全局上限。子租户同名隔离字段只能降低自己的并发、等待时间或资源额度,用于隔离弱租户、试用租户或异常租户。
@@ -247,7 +260,7 @@ var secret = privateSettings['Login.Gitee.ClientSecret'];
247
260
  // 禁止 return secret、console.log(secret) 或写入前端可读字段。
248
261
  ```
249
262
 
250
- > `mci_system_setting` 位于每个租户自己的数据库,只保存不能公开或仅供后端执行的配置;普通值与 Secret 都不会下发浏览器。Secret 保存认证密文,只在后端 V8 的当前租户 `ServerPrivateSettings` 中解密使用。能力开关和入口显示必须建成 `sys_config` 实体字段,禁止通过 `IsPublic` 或其它运行时勾选把私密记录公开。前端 V8、普通 FormEngine HTTP、匿名/访问密钥会话不能读取私密设置,后端 V8 也不能获得通用解密器。`sys_osclients` 自定义业务字段只保留存量兼容;共享基础设施字段由服务端强制移除,不能用自定义同义字段绕过安全代理。
263
+ > `mci_system_setting` 位于每个租户自己的数据库,保存 Secret 与确需保密的配置;仅由后端使用不是保密理由。此表普通私密值与 Secret 都不会下发浏览器;Secret 保存认证密文,只在后端 V8 的当前租户 `ServerPrivateSettings` 中解密使用。能力开关、入口显示及可公开服务地址等建成 `sys_config` 实体字段,在普通系统设置按授权维护,不能通过 `IsPublic` 或运行时勾选把旧私密记录公开。新旧配置迁移只处理明确范围,完整分类与回退规则见 `../v8-security/SKILL.md`。前端 V8、普通 FormEngine HTTP、匿名/访问密钥会话不能读取私密设置,后端 V8 也不能获得通用解密器。`sys_osclients` 自定义业务字段只保留存量兼容;共享基础设施字段由服务端强制移除,不能用自定义同义字段绕过安全代理。
251
264
 
252
265
  ## 用户扩展字段访问(同理)
253
266
 
@@ -280,7 +293,7 @@ SELECT * FROM Contact WHERE OwnerId = $CurrentUser.Id$ AND Spouse = $CurrentUser
280
293
 
281
294
  - [ ] 缓存只通过 `V8.Cache` 使用逻辑 Key 或当前租户完整 Key
282
295
  - [ ] 所有 MongoDB DbName 都包含 `V8.OsClient`
283
- - [ ] 当前租户自有的业务集成密钥可放 `V8.OsClientModel`,共享 Redis/存储/MQ/MQTT/Search 凭据只能由服务端托管
296
+ - [ ] 新增租户业务集成 Secret 使用 `mci_system_setting`,不再扩展 `V8.OsClientModel` 存量兼容字段;共享 Redis/存储/MQ/MQTT/Search 凭据只能由服务端托管
284
297
  - [ ] 跨租户操作前校验权限
285
298
  - [ ] 不向前端返回 `V8.OsClientModel`
286
299
  - [ ] 子租户数据库账号只授权本租户库,MQ/MQTT/Search 独立凭据已真实创建
@@ -17,9 +17,9 @@ description: Microi V8 安全指南。用于审查 DiyToken 与权限、可逆
17
17
 
18
18
  Microi 官方账号登录属于固定外部 Provider `Microi`,复用个人中心外部账号绑定与本租户 DiyToken。
19
19
  先核对 `sys_config.MicroiLoginEnabled`(默认 `0`)和 `DisableLoginMicroi`(缺失/空值/`0` 显示,显式 `1` 隐藏),
20
- 再读取 `Login.Microi.ClientId` 私有配置。每个部署在官方身份服务登记独立公共客户端,
20
+ 再读取当前已发布入口兼容的 `Login.Microi.ClientId` 配置。该公开客户端标识历史上保存在私密 Key 中,不代表 ClientId 本身是秘密;新配置归属按下节的内容敏感性规则判断,不因修订 Skill 自动迁移已有登录配置。每个部署在官方身份服务登记独立公共客户端,
21
21
  `ClientAuthMethod=none`、PKCE S256、nonce、精确回调;不生成、配置或分发共享 ClientSecret。
22
- `Login.Microi.Scope` 默认 `openid profile email`,Name/Description 可配置;四项模板默认停用且仅供后端。
22
+ `Login.Microi.Scope` 默认 `openid profile email`,Name/Description 可配置;当前兼容的四项模板默认停用且由后端读取。
23
23
  端点固定为 `https://api.microi.net/sso/iTdos/authorize`、`/token`、`/userinfo`,
24
24
  不能从租户参数替换为任意主机。此入口使用 OAuth 2.0 授权码、PKCE S256 与官方 UserInfo,不读取或信任 `id_token`;
25
25
  官方校验客户端、精确回调、授权码期限和 verifier,本地校验一次性 state、Bearer 类型及非空稳定 UserInfo `sub`,
@@ -57,14 +57,29 @@ Preview 和确认均重验当前客户端、回调及 Scope;账号变化通过
57
57
  密码注册、重置与登录要保留相同原始输入,不能在单个环节独自 `trim`。登录及重置包装器把 `_Lang`(无值时沿用 `lang` Header,再默认 `cn`)传至认证内核;缓存未预热也应返回本地化错误而不是内部 Key。手机号作为登录别名时精确账号优先,手机号重复则拒绝;验证后的身份只按认证结果 Id 回读。账号必须 `State=1` 且未删除;历史 `State=2` 是禁用,不能为了排障绕过或自动启用。
58
58
 
59
59
  <!-- microi-progressive:begin -->
60
- <!-- microi-progressive:chunk id=v8-security-000 sha256=a6a4311b1a799ea4ed005b22c01cb8230509aaf09aeead5a17c8c46da74772c8 -->
60
+ <!-- microi-progressive:chunk id=v8-security-000 sha256=fd796e46d74942e9e8723bd8ac9a0e631bef3430120b3a942a1f5fd1ff68b05c -->
61
61
  ## 0. 租户动态系统设置与密钥边界
62
62
 
63
63
  **AI 默认处理路径:** 用户交付第三方 Secret 并授权配置时,优先调用 `microi_manage_server_private_secret`:List 只确认 Key/HasSecret;Save 使用 `confirmExecution=SAVE:<ConfigKey>`,固定 `IsSecret=true / IsPublic=false` 并自动脱敏回读。例如畅捷通使用 `Integration.Changjet.AppSecret`,后端从 `V8.SysConfig.ServerPrivateSettings` 使用。不要仅拒绝硬编码后停止,也不要因为 `OsClientModel` 不暴露密钥而误判服务端 V8 无法调用第三方。工具不提供原文揭示;更换已有 Key 前回读现状,超时只回读、不盲目重写。
64
64
 
65
- 第三方密钥(微信、支付宝、OpenAI、阿里云、ERP、SMTP)**禁止**硬编码在 V8 代码或前端。公开的租户配置必须建成当前租户 `sys_config` 的实体字段;敏感或仅供后端使用的租户业务配置保存到 `mci_system_setting`。数据库、Redis、MongoDB、MinIO、MQ 等部署控制面仍由主库 `sys_osclients` 托管,子租户不能修改。
65
+ 第三方密钥(微信、支付宝、OpenAI、阿里云、ERP、SMTP)**禁止**硬编码在 V8 代码或前端。配置存放位置按内容敏感性判断,不以“第三方集成”或“仅后端调用”作为私密依据:可公开的租户业务配置建成当前租户 `sys_config` 的实体字段,在普通系统设置维护;Secret、凭据及确需保密的内容保存到 `mci_system_setting`。数据库、Redis、MongoDB、MinIO、MQ 等已有部署控制面仍由主库 `sys_osclients` 托管,子租户不能修改,不借本规则自动搬迁基础设施字段。
66
66
 
67
- 能力是否启用、入口是否显示、公开交互模式等 Bool/Enum 配置即使属于登录或第三方集成,也必须放在 `sys_config`;API Key、ClientSecret、RP ID、Origin、Issuer、Scope、供应商地址等后端参数才放在 `mci_system_setting`。禁止给同一个新配置双写两张表。迁移旧开关时采用“新 `sys_config` 显式值 → 旧私密 Key → 存量安全默认”的只读回退,并从私密设置的列表、保存和删除入口移除旧 Key。
67
+ MinIO 部署凭据使用专用 `microi_manage_saas_minio`,不能误存到普通第三方业务 Secret,
68
+ 也不能为此放宽 `microi_admin_table_data` 的秘密字段限制。先 Inspect 当前主租户及运行分区,
69
+ 再以其 Revision 和 `SAVE_MINIO:<tenant>:<revision>` 确认保存;省略凭据保留,候选先探测、
70
+ 事务行锁 CAS、提交后缓存刷新及运行回读。只返回 HasAccessKey/HasSecretKey,拒绝子租户和
71
+ 访问密钥会话,不修改 HDFS/OSS 或桶权限。超时和 Persisted=true 的失败先 Inspect/Probe,
72
+ 不能盲重放;旧后端不支持时升级正式后端,禁止用 SQL、普通表单或临时 V8 绕过。
73
+
74
+ | 配置内容 | 存放与维护方式 |
75
+ | --- | --- |
76
+ | 能力开关、入口显示、公开交互模式、可公开的 HTTP(S) 服务地址、公开 ClientId / AppId、回调地址、RP ID、Origin、Issuer、Scope | 当前租户 `sys_config` 实体字段与普通系统设置;不能仅因供应商或使用端是后端而放入私密设置 |
77
+ | API Key、ClientSecret、Token、密码、签名密钥、私钥,或 URL 中的账号密码、认证查询参数/签名票据 | 当前租户 `mci_system_setting` 的 Secret;优先把公开基础地址与认证材料拆开保存,不能将整个含认证 URL 投影给浏览器 |
78
+ | 确有隐藏要求的内部网络拓扑、未公开管理端点或其它敏感但非凭据的值 | `mci_system_setting` 的后端私密项;说明其实际保密理由,不能只凭地址含内网 IP 或字段名作一刀切判断 |
79
+
80
+ “可公开”不等于普通用户可以修改,也不等于整条配置记录匿名下发。普通系统设置仍校验管理员/已授权角色、当前租户、字段写入白名单和审计;前端只读取经审阅的安全投影。服务地址、回调、RP/Origin/Issuer 的协议规则、精确主机/来源白名单、租户绑定及各能力既有 SSRF 校验继续生效,不能借调整存放位置放宽信任边界。
81
+
82
+ 同一个新配置只保留一个写入事实源,不双写两张表。本规则不自动迁移所有旧配置,也不把已有 `mci_system_setting` 记录改为 `IsPublic`。确需迁移已授权范围内的单个配置时,先检查旧值是否含认证信息/敏感拓扑;实现新实体字段、普通设置入口、权限与投影白名单、幂等迁移和缓存回读。滚动升级可采用“新 `sys_config` 显式值 → 经核验的旧私密 Key → 存量安全默认”的只读回退,明确处理关闭/清空值;完成后从私密设置列表、保存和删除入口移除该旧 Key,不批量重写未纳入本次迁移的登录或集成配置。
68
83
 
69
84
  ```javascript
70
85
  // ✅ 浏览器/前端 V8 只读取 sys_config 的浏览器安全投影