create-windy 0.3.4 → 0.3.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 (101) hide show
  1. package/README.md +20 -3
  2. package/dist/cli.js +582 -109
  3. package/package.json +1 -1
  4. package/template/.windy-template.json +2 -2
  5. package/template/AGENTS.md +4 -0
  6. package/template/README.md +18 -11
  7. package/template/apps/server/src/application-services.ts +16 -0
  8. package/template/apps/server/src/data-governance/export/authorization-port.test.ts +5 -1
  9. package/template/apps/server/src/data-governance/export/drizzle-repository.integration.test.ts +2 -0
  10. package/template/apps/server/src/data-governance/export/drizzle-repository.ts +1 -0
  11. package/template/apps/server/src/data-governance/export/job-runtime.test.ts +3 -1
  12. package/template/apps/server/src/data-governance/export/job-runtime.ts +53 -14
  13. package/template/apps/server/src/data-governance/export/ndjson-stream.ts +8 -0
  14. package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +2 -0
  15. package/template/apps/server/src/data-governance/export/renderer-job-runtime.test.ts +235 -0
  16. package/template/apps/server/src/data-governance/export/renderer-runtime.ts +293 -0
  17. package/template/apps/server/src/data-governance/export/renderer-selection.test.ts +67 -0
  18. package/template/apps/server/src/data-governance/export/repository.test.ts +2 -0
  19. package/template/apps/server/src/data-governance/export/request-fingerprint.test.ts +13 -0
  20. package/template/apps/server/src/data-governance/export/request-fingerprint.ts +2 -0
  21. package/template/apps/server/src/data-governance/export/routes.ts +11 -1
  22. package/template/apps/server/src/data-governance/export/service-support.ts +61 -4
  23. package/template/apps/server/src/data-governance/export/service.ts +8 -2
  24. package/template/apps/server/src/data-governance/export/types.ts +27 -0
  25. package/template/apps/server/src/index.ts +1 -0
  26. package/template/apps/server/src/jobs/artifact-service.test.ts +14 -2
  27. package/template/apps/server/src/jobs/artifact-service.ts +10 -0
  28. package/template/apps/server/src/jobs/drizzle-execution-repository.ts +17 -18
  29. package/template/apps/server/src/jobs/drizzle-execution.integration.test.ts +19 -1
  30. package/template/apps/server/src/jobs/drizzle-mappers.ts +3 -1
  31. package/template/apps/server/src/jobs/drizzle-recovery-repository.ts +49 -2
  32. package/template/apps/server/src/jobs/execution-repository.ts +17 -0
  33. package/template/apps/server/src/jobs/in-memory-control.ts +135 -0
  34. package/template/apps/server/src/jobs/in-memory-execution.ts +34 -65
  35. package/template/apps/server/src/jobs/worker.ts +27 -3
  36. package/template/apps/server/src/module-host/governed-export-route-support.ts +24 -5
  37. package/template/apps/server/src/module-host/governed-export-routes.test.ts +5 -1
  38. package/template/apps/server/src/module-host/governed-export-routes.ts +1 -2
  39. package/template/apps/server/src/module-host/governed-renderer-registration.test.ts +247 -0
  40. package/template/apps/server/src/module-host/governed-resources.test.ts +1 -0
  41. package/template/apps/server/src/module-host/governed-resources.ts +56 -0
  42. package/template/apps/server/src/module-host/http-runtime.ts +3 -0
  43. package/template/apps/server/src/module-host/job-port.test.ts +262 -0
  44. package/template/apps/server/src/module-host/job-port.ts +334 -0
  45. package/template/apps/server/src/module-host/request-context.ts +14 -26
  46. package/template/apps/server/src/module-host/route-installer.ts +11 -0
  47. package/template/apps/web/src/app/business-routes.ts +22 -0
  48. package/template/apps/web/src/components/common/AccessSnapshotBoundary.vue +112 -0
  49. package/template/apps/web/src/components/common/AccessSnapshotBoundary.webtest.ts +130 -0
  50. package/template/apps/web/src/components/common/AccessSnapshotNotice.vue +117 -0
  51. package/template/apps/web/src/composables/access-snapshot-change.ts +131 -0
  52. package/template/apps/web/src/composables/access-snapshot-change.webtest.ts +103 -0
  53. package/template/apps/web/src/composables/useAccessSnapshot.ts +48 -2
  54. package/template/apps/web/src/composables/useAccessSnapshot.webtest.ts +54 -0
  55. package/template/apps/web/src/composables/useAccessSnapshotMonitor.ts +42 -0
  56. package/template/apps/web/src/layout/AppShell.vue +28 -25
  57. package/template/apps/web/src/layout/AppShell.webtest.ts +1 -0
  58. package/template/apps/web/src/router/business-routes.ts +68 -0
  59. package/template/apps/web/src/router/business-routes.webtest.ts +85 -0
  60. package/template/apps/web/src/router/index.ts +7 -0
  61. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +12 -0
  62. package/template/apps/web/src/services/access-api.ts +5 -1
  63. package/template/apps/web/src/services/access-api.webtest.ts +35 -0
  64. package/template/docs/architecture/ai-egress.md +12 -9
  65. package/template/docs/architecture/durable-jobs.md +7 -1
  66. package/template/docs/architecture/foundation-infrastructure.md +163 -0
  67. package/template/docs/architecture/module-host.md +115 -0
  68. package/template/docs/architecture/server-module-interface.md +39 -0
  69. package/template/docs/architecture/web-access-snapshot-resilience.md +60 -0
  70. package/template/docs/development/business-web-routes.md +66 -0
  71. package/template/docs/development/custom-login-page.md +3 -3
  72. package/template/docs/development/module-lifecycle.md +71 -0
  73. package/template/docs/operations/observability.md +5 -1
  74. package/template/docs/platform/agent-runtime.md +6 -6
  75. package/template/docs/platform/agent-tool-sandbox.md +58 -0
  76. package/template/docs/platform/document-ai-runtime.md +204 -0
  77. package/template/docs/platform/governed-export-renderers.md +132 -0
  78. package/template/docs/platform/navigation-registry.md +5 -0
  79. package/template/docs/platform/organization-membership.md +2 -1
  80. package/template/docs/platform/speech-ai-runtime.md +156 -0
  81. package/template/docs/platform/system-crud-api.md +7 -1
  82. package/template/docs/platform/vision-ai-runtime.md +97 -0
  83. package/template/docs/platform/web-system-crud.md +6 -0
  84. package/template/packages/config/package.json +1 -1
  85. package/template/packages/database/drizzle/0042_puzzling_rumiko_fujikawa.sql +27 -0
  86. package/template/packages/database/drizzle/meta/0042_snapshot.json +8541 -0
  87. package/template/packages/database/drizzle/meta/_journal.json +7 -0
  88. package/template/packages/database/package.json +1 -1
  89. package/template/packages/database/src/schema/durable-jobs.ts +1 -0
  90. package/template/packages/database/src/schema/governed-exports.ts +15 -0
  91. package/template/packages/database/src/schema-governed-exports.test.ts +21 -1
  92. package/template/packages/jobs/package.json +1 -1
  93. package/template/packages/jobs/src/handlers.ts +1 -0
  94. package/template/packages/jobs/src/types.ts +1 -0
  95. package/template/packages/modules/package.json +1 -1
  96. package/template/packages/server-sdk/index.ts +11 -0
  97. package/template/packages/server-sdk/package.json +1 -1
  98. package/template/packages/server-sdk/src/data-governance-registration.ts +46 -0
  99. package/template/packages/server-sdk/src/jobs-port.ts +69 -0
  100. package/template/packages/server-sdk/src/request-context.ts +2 -2
  101. package/template/packages/shared/package.json +1 -1
@@ -0,0 +1,66 @@
1
+ # project-owned 业务 Web 路由
2
+
3
+ Windy Starter 把平台 Router 和访问快照 Guard 保留在
4
+ `apps/web/src/router/`,客户业务路由只注册到
5
+ `apps/web/src/app/business-routes.ts`。整个 `apps/web/src/app/` 在
6
+ `.windy/files.json` 中属于 `project-owned`:更新器不会覆盖客户新增、修改或删除的
7
+ 路由注册与页面源码。
8
+
9
+ ## 注册一个页面
10
+
11
+ 先在业务 Module Manifest 中用同一个能力基础 Key 声明 Feature、Permission、菜单和
12
+ Admin Route,再在 `installedBusinessAdminRoutes` 中注册页面实现:
13
+
14
+ ```ts
15
+ import CaseRecordsPage from "./case-records/CaseRecordsPage.vue";
16
+
17
+ export const installedBusinessAdminRoutes = [
18
+ {
19
+ key: "case.record",
20
+ path: "/cases",
21
+ name: "case-records",
22
+ title: "案件",
23
+ component: CaseRecordsPage,
24
+ featureKey: "case.record",
25
+ permissionKey: "case.record.read",
26
+ },
27
+ ] satisfies readonly BusinessAdminRouteRegistration[];
28
+ ```
29
+
30
+ 注册表必须与 Module Manifest 使用相同的 `key`、`path`、`featureKey` 和
31
+ `permissionKey`。服务端仍是最终授权边界;Web Router 会把注册项统一转换为
32
+ `layout: "admin"` 路由,并在每次导航时使用访问快照检查 Feature 与 Permission。
33
+ 空 Guard、重复业务 Route Key、相对路径和业务兜底路径会在启动组合时被拒绝。
34
+
35
+ 页面组件及其 composable、表单和状态放在 `apps/web/src/app/` 下。不要为了增加业务
36
+ 页面修改以下 shared-scaffold:
37
+
38
+ - `apps/web/src/router/index.ts`
39
+ - `apps/web/src/router/business-routes.ts`
40
+ - `apps/web/src/layout/AppSidebar.vue`
41
+ - `apps/web/src/layout/navigation.ts`
42
+
43
+ ## 导航来源
44
+
45
+ 侧栏和导航搜索不会读取 Web 注册表来复制一份菜单。服务端启动时从已安装业务 Module
46
+ Manifest 同步菜单,`/api/foundation` 返回经过 License、Feature 和 RBAC 裁剪的访问
47
+ 快照,Web 外壳据此渲染导航。这样路径、权限、Feature 和菜单身份仍由 Manifest
48
+ 统一治理,客户只在 project-owned 注册表绑定 Vue 页面实现。
49
+
50
+ 如果页面可直达但侧栏没有入口,优先检查服务端
51
+ `apps/server/src/installed-business-modules.ts` 是否已经安装对应业务 Module、Manifest
52
+ 是否声明菜单,以及当前账号是否具有 Permission;不要在 AppSidebar 手写菜单。
53
+
54
+ ## 升级行为
55
+
56
+ 首次生成时,`business-routes.ts` 会记为 `project-owned`。后续 target 即使提供了新的
57
+ 默认内容,也不能把 source manifest 中的该路径降级成 `shared-scaffold` 或
58
+ `windy-managed`:
59
+
60
+ - 客户修改继续保留;
61
+ - 客户删除会以 tombstone 跨版本保留;
62
+ - 客户重新创建后,清单刷新采纳新正文 hash,但仍保持 `project-owned`;
63
+ - 平台只升级 Router Adapter 与 Guard,实现变更通过 shared-scaffold 三方合并进入。
64
+
65
+ 升级前可运行 `bun run windy update --dry-run` 查看所有权和冲突计划。纯业务页面与
66
+ 注册表变更不应产生平台侧源码覆盖。
@@ -78,9 +78,9 @@ composable。
78
78
  先提交当前业务改动并保持 Git 工作区干净,然后执行:
79
79
 
80
80
  ```bash
81
- bunx --bun create-windy@0.3.2 update --check --cwd .
82
- bunx --bun create-windy@0.3.2 update --dry-run --cwd .
83
- bunx --bun create-windy@0.3.2 update --cwd .
81
+ bunx --bun create-windy@latest update --check --cwd .
82
+ bunx --bun create-windy@latest update --dry-run --cwd .
83
+ bunx --bun create-windy@latest update --cwd .
84
84
  bun install
85
85
  ```
86
86
 
@@ -0,0 +1,71 @@
1
+ # 部署期模块生命周期
2
+
3
+ Windy 已部署项目通过项目锁定版本的 `create-windy` 管理模块,不重新运行创建命令,
4
+ 也不手工复制源码:
5
+
6
+ ```bash
7
+ bun run windy module list
8
+ bun run windy module add work-order --dry-run
9
+ bun run windy module add work-order
10
+ bun run windy module update work-order
11
+ bun run windy module remove work-order --dry-run
12
+ bun run windy module remove work-order --confirm-data-disposition
13
+ ```
14
+
15
+ ## 计划与事务
16
+
17
+ 模块命令读取 `.windy/generation.json` 的当前集合,再用
18
+ `ModuleDistributionManifest` 解析依赖、冲突、Windy 兼容范围、package、文件、
19
+ Schema、migration 与卸载策略。目标组合仍由 immutable template 生成,之后进入与
20
+ `windy update` 相同的 Base/Ours/Theirs 计划、文件 expectation、workspace 检查、
21
+ CAS 写入、验证、Project Lock/受管清单提交和 `doctor --repair` 恢复流程。
22
+
23
+ 模块命令要求项目的 `windyVersion`、`generatorVersion` 与当前 CLI 的 template 版本
24
+ 完全一致;旧项目先运行 `bun run windy update`。这样目录、recipe 和目标模板不会跨
25
+ 版本混用。
26
+
27
+ `add` 与 `remove` 是幂等操作:集合已满足时不启动事务。`update` 不改变集合,用当前
28
+ CLI 重建所选模块并应用目录声明的更新 recipe。正式操作要求 Git 工作区干净;客户
29
+ 修改过的受管文件或模块 project-owned 扩展点会形成冲突,不会被静默覆盖。
30
+
31
+ ## 数据处置
32
+
33
+ 卸载首先输出数据处置计划。`--dry-run` 只展示文件和冲突;正式卸载必须显式传入
34
+ `--confirm-data-disposition`。
35
+
36
+ | 策略 | 行为 |
37
+ | -------------- | ------------------------------------------------------------------ |
38
+ | `retain-data` | 默认;保留业务表与记录,不执行 down migration |
39
+ | `archive-data` | 目录要求外部先归档;通过 `--data-plan <文件>` 绑定已审阅的处置文档 |
40
+ | `manual` | 目录要求人工决定;同样必须绑定处置文档 |
41
+
42
+ CLI 不连接生产数据库,确认参数表示部署操作人已完成并审阅相应动作,不代表 Windy
43
+ 自动执行 SQL 或数据库归档。即使策略名为 `archive-data`,CLI 对数据库本身仍是
44
+ retain:数据删除或归档由处置文档约束的外部运维流程负责。目录策略只能被提高,不能
45
+ 从 `archive-data` 或 `manual` 降为 `retain-data`。
46
+
47
+ 卸载不会删除已发布 migration。模块自有 migration 源文件按原相对路径保存到
48
+ `.windy/module-history/<module>/`;模块重装时历史仍保留,数据库运行期继续以
49
+ `windy_migration_history` 的 migration id 与 checksum 判定,不会因为代码重装而
50
+ 重复执行已应用迁移。历史目录受清单 hash 保护,缺失登记、符号链接或内容修改都会
51
+ fail closed。
52
+
53
+ ## 物理残留边界
54
+
55
+ 目标组合会重新物化 package、workspace、宿主 Adapter、Composition、Module
56
+ Manifest、Feature、Permission、菜单、任务、Provider、Tool、Schema 和文档。卸载后
57
+ 这些运行时或生成期引用不得残留;允许保留的只有:
58
+
59
+ - 数据库中的业务数据与 migration ledger;
60
+ - `.windy/module-history/` 下的不可执行迁移证据;
61
+ - `.windy/reports/` 下的生命周期报告。
62
+
63
+ 如果其它已安装模块仍依赖待卸载模块,计划在写文件前拒绝。`system` 与
64
+ `system.identity` 是 profile 核心,不支持卸载。
65
+
66
+ ## H3 证据
67
+
68
+ 通用 CLI 的存在不自动提升所有模块可靠度。`work-order` 的 H3 回归从纯核心项目开始,
69
+ 覆盖安装、重复安装、更新、保留数据卸载、重复卸载和重装,并检查 package、
70
+ Project Lock 与 migration history artifact。其它模块保持 H2,直到分别通过同等级的
71
+ 安装、数据库、Server、卸载残留和恢复门禁。
@@ -15,7 +15,11 @@
15
15
 
16
16
  `/api/health` 和 `/health` 只为已有调用方保留;新编排必须分别使用 liveness 和 readiness。Dockerfile 的 `HEALTHCHECK` 使用 readiness;根目录开发 Compose 也用 readiness 控制服务依赖,但它仍然不是生产编排。
17
17
 
18
- Server readiness 同时返回审计 writer 状态,便于对 `degraded`、队列积压或丢弃计数告警。审计降级当前不会自动把实例摘流;关键写入仍由业务 Guard fail closed,处置方式见[平台审计可靠写入](../platform/audit-reliability.md)。
18
+ Server readiness 同时返回审计 writer 状态,便于对 `degraded`、队列积压或丢弃计数告警。审计降级当前不会自动把实例摘流;关键写入仍由业务 Guard fail closed。
19
+ <!-- @windy-module system.audit begin -->
20
+
21
+ 处置方式见[平台审计可靠写入](../platform/audit-reliability.md)。
22
+ <!-- @windy-module system.audit end -->
19
23
 
20
24
  ## Request 与 Job 关联
21
25
 
@@ -222,17 +222,17 @@ SSE 事件包括 `output.delta`、`tool.requested`、`tool.completed`、usage,
222
222
  `run.completed`、`run.failed` 或 `run.cancelled` 终态。断线后使用已确认 sequence 作为
223
223
  cursor 恢复;不要重新创建 Run 或重复执行 Tool。
224
224
 
225
- ## 从 create-windy 0.2.34 升级到 0.3.2
225
+ ## 升级既有 Starter
226
226
 
227
- 目标 CLI 会一次解析 `0.2.34 → 0.3.0 → 0.3.1 → 0.3.2` 连续 recipe,不需要逐个安装中间
228
- CLI。确认 npm registry 已能解析精确的 `0.3.2` artifact 后,先检查计划,再应用:
227
+ 目标 CLI 会一次解析 source 到 target 的连续 recipe,不需要逐个安装中间 CLI。确认
228
+ npm registry 已能解析目标 artifact 后,先检查计划,再应用:
229
229
 
230
230
  ```bash
231
- bunx --bun create-windy@0.3.2 update --check
232
- bunx --bun create-windy@0.3.2 update
231
+ bunx --bun create-windy@latest update --check
232
+ bunx --bun create-windy@latest update
233
233
  bun install
234
234
  ```
235
235
 
236
236
  升级器会更新受管模板文件;本地修改产生冲突时会 fail closed 并保留事务备份,不会静默
237
237
  覆盖。升级后按上面的环境变量启用 `agent` profile;`agent-debug` 仍只用于无业务 Tool
238
- 的本机调试。自动化部署应固定精确版本,不能用未核验的 `latest` 替代。
238
+ 的本机调试。自动化部署应把已审阅的 `latest` 替换为精确版本。
@@ -0,0 +1,58 @@
1
+ # Agent Tool 隔离执行
2
+
3
+ ## 目标与边界
4
+
5
+ Tool Sandbox 用于执行不需要业务状态和外部服务的纯计算 Tool。它降低模型生成参数触发
6
+ 进程级能力的风险,但不替代 Manifest、License、Feature、RBAC、Data Scope、Schema、
7
+ 超时和审计 Guard。
8
+
9
+ 只有通过 `InstallableServerModule.setupSandbox()` 注册的 Handler 会进入隔离 Worker。
10
+ 普通 `setup()` 中的 Tool 仍在 Server 进程执行,不自动获得操作系统隔离保证。业务必须
11
+ 逐个确认依赖边界后显式迁移,不能用名称或配置开关假装已经隔离。
12
+
13
+ ## 执行链
14
+
15
+ 1. Server 的统一 Tool Registry 完成 Actor、License、Feature、Permission、Scope 和
16
+ Schema 校验。
17
+ 2. Sandbox Client 为调用生成随机 Invocation ID,通过 Worker 每次启动生成的 32 字节
18
+ 密钥签发短时 HMAC Grant。
19
+ 3. 请求经只挂载 IPC Volume 的 Unix Domain Socket 进入 Worker。Actor、权限、数据范围、
20
+ Secret、Repository 和原始审计上下文都不会进入 Worker。
21
+ 4. Worker 只加载 `setupSandbox()` 注册表,复验 Grant、截止时间、Tool Key 和输入大小,
22
+ 再执行 Handler。
23
+ 5. Server 复验输出大小和 Manifest Schema,并写入不包含原始输入输出的审计摘要。
24
+
25
+ Worker 重启会轮换密钥。Client 在连接中断或密钥轮换竞态时用相同 Invocation ID
26
+ 重试一次;已经收到的业务失败或执行超时不会在传输层自动重放。Sandbox Handler 只能
27
+ 使用输入、`idempotencyKey` 和 `AbortSignal`,并应保持确定性和无外部副作用。
28
+
29
+ ## 运行时隔离
30
+
31
+ 开发和生产 Compose 都将 Tool Worker 配置为:
32
+
33
+ - `network_mode: none`,不连接应用、数据库或外部网络;
34
+ - 只读 RootFS、移除全部 Linux capabilities、启用 `no-new-privileges`;
35
+ - 不注入数据库、模型 Provider、内部服务 Token 或 Artifact 加密密钥;
36
+ - 不挂载 Server 数据、源码目录或宿主业务文件,只挂载
37
+ `/run/windy-tool-sandbox` IPC Volume;
38
+ - `/tmp` 为小容量、`noexec`、`nosuid`、`nodev` 的临时文件系统。
39
+
40
+ 镜像中仍包含应用程序本身。模块构造和 `setupSandbox()` 因此必须同步、无副作用,不能
41
+ 读取宿主路径或依赖导入时初始化的外部连接。
42
+
43
+ 命名 Volume 初始由 root 持有。一次性的 `tool-sandbox-init` 仅挂载该 IPC Volume,
44
+ 使用 `CHOWN` capability 将目录交给镜像内固定的 `bun` 用户后退出;它同样无网络、
45
+ 无 Secret、只读 RootFS。常驻 Worker 与 Server 随后以相同非 root UID 访问权限为
46
+ `0700` 的目录,HMAC 密钥文件权限为 `0600`。
47
+
48
+ ## 失败与审计
49
+
50
+ - 未注册 Tool、出网失败等隔离边界拒绝记为 `policy-denied`;
51
+ - Handler 抛错、返回非对象或输出超限记为 `business-failure`;
52
+ - Worker 不可用、授权材料轮换和协议错误使用稳定的 `SANDBOX_*` 错误码;
53
+ - 审计只记录 Tool Key、执行模式、结果分类和受控摘要,不记录 Tool 原始参数、结果、
54
+ HMAC 密钥或 Grant。
55
+
56
+ 发版门禁的 `test:ops:tool-sandbox` 会从实际 Compose 启动 Worker,检查无网络、无平台
57
+ Secret、最小挂载和 Worker 重启后的 Socket 恢复。该检查属于发布验证,不在普通提交
58
+ 门禁中运行。
@@ -0,0 +1,204 @@
1
+ # Document AI Runtime
2
+
3
+ 更新时间:2026-07-27。
4
+
5
+ Windy 的 Document AI 是可选的独立故障域。业务模块声明稳定的
6
+ `kind=document` Operation,并先把当前 Actor 有权读取的业务资源换成短时
7
+ `ArtifactRef`;Southwind Server 完成 Feature、RBAC、License、数据范围、业务对象绑定
8
+ 和审计后,才把任务交给 Document Server。未安装或未配置 Document Server 时,对应
9
+ Operation 稳定返回 `CAPABILITY_UNAVAILABLE`,普通业务和文本 Agent 仍可启动。
10
+
11
+ 首个实现返回受输出 Schema 约束的结构化 JSON:页级文本、坐标、Block、版面树、表格和
12
+ 标量元数据。搜索型 PDF 等二进制派生物必须等平台输出 Artifact Sink 签发后再作为
13
+ `ArtifactRef` 返回;Document Adapter 不得自行返回文件 URL、主机路径或伪造引用。
14
+
15
+ ## 责任边界
16
+
17
+ 业务模块负责:
18
+
19
+ - 在 Manifest 注册 Operation、Feature、Permission、Audit、输入/输出 Schema、
20
+ capability、数据分类、出网策略和 limits;
21
+ - 注册业务资源到平台文件的授权策略,从 project-owned Repository 回查
22
+ `resourceId` 与 `fileId` 的真实绑定;
23
+ - 使用 `context.aiArtifacts.issue(...)` 换取 opaque `ArtifactRef`;
24
+ - 校验领域选项、解析领域结果并保存人工校正、来源回链和业务状态。
25
+
26
+ 平台负责:
27
+
28
+ - 每次签发和回取 Artifact 时重新解析 Actor、Feature、Permission、License、数据范围
29
+ 与业务对象绑定;
30
+ - Artifact 引用加密、完整性元数据、短 TTL 和内部认证下载;
31
+ - Operation capability 摘要校验、Actor + Operation 隔离的幂等键、页级进度、取消、
32
+ 稳定错误和启动审计;
33
+ - 输入字节、输出字节、页数/处理单元、时长、Deployment 与出网策略。
34
+
35
+ Document Server 负责:
36
+
37
+ - 有界下载 Artifact 并再次核对字节数和 SHA-256;
38
+ - 持久化 Run、事件 cursor、幂等指纹和终态结果;
39
+ - 调用已配置的本地/内网 Document Adapter;
40
+ - 校验页级结构、坐标、版面树、表格、输出大小和页数;
41
+ - 重启时把未完成任务稳定终止,防止把半成品误报为成功。
42
+
43
+ 业务不得导入 OCR SDK、启动模型进程、传 Base64、Blob Key、宿主绝对路径或任意公网
44
+ URL,也不得把文本 Agent 或浏览器文本提取宣称为 OCR。
45
+
46
+ ## Module 接入
47
+
48
+ Document Operation 的 `limits.maxUnits` 必填;对文档表示最大页数。离线或内网
49
+ Deployment 使用 `providerEgress: { mode: "forbidden" }`。若受控 Provider 出网,
50
+ Manifest 还必须声明稳定 `deploymentKey` 和最小 `providerDataPolicy`,业务不能提交
51
+ Endpoint、模型名或 Secret。
52
+
53
+ ```ts
54
+ const operation = {
55
+ key: "evidence.transcript.ocr",
56
+ kind: "document",
57
+ featureKey: "evidence.transcript.ocr",
58
+ permissionKey: "evidence.transcript.ocr.execute",
59
+ auditAction: "evidence.transcript.ocr.execute",
60
+ inputSchemaKey: "evidence.transcript.ocr.input.v1",
61
+ outputSchemaKey: "evidence.transcript.ocr.output.v1",
62
+ requiredCapabilities: ["document.ocr", "document.layout"],
63
+ allowedToolKeys: [],
64
+ dataClassification: "sensitive",
65
+ execution: "background",
66
+ providerEgress: { mode: "forbidden" },
67
+ limits: {
68
+ maxDurationSeconds: 600,
69
+ maxInputBytes: 64 * 1024 * 1024,
70
+ maxOutputBytes: 16 * 1024 * 1024,
71
+ maxUnits: 500,
72
+ },
73
+ } as const;
74
+ ```
75
+
76
+ 模块路由先换取 Artifact:
77
+
78
+ ```ts
79
+ const source = await context.aiArtifacts.issue({
80
+ fileId,
81
+ purpose: "evidence.transcript-source",
82
+ resourceType: "evidence.transcript",
83
+ resourceId: transcriptId,
84
+ });
85
+ ```
86
+
87
+ 该调用只有在同一模块已注册对应 `ModuleResourceFileAccessPolicy`,且策略从业务
88
+ Repository 证明当前 Actor 可访问真实资源并绑定该文件时才会成功。客户端只把返回值交给
89
+ `@southwind-ai/ai-client/document`,不能解析或持久化其中的内部位置。
90
+
91
+ 模块还要为同一 Operation 注册 `prepareDocument`。受控出网 Operation 的 Handler 只
92
+ 接收经过 `providerDataPolicy.inputFields` 裁剪/DLP 的选项;完全断网 Operation 接收
93
+ 领域选项。两者都不接收 Provider、Artifact Token 或平台 Repository。
94
+
95
+ ## HTTP 与事件协议
96
+
97
+ 浏览器通过同源 Southwind Server 调用:
98
+
99
+ ```text
100
+ POST /api/document/runs
101
+ GET /api/document/runs/:runId/events?after=<sequence>
102
+ GET /api/document/runs/:runId/result
103
+ POST /api/document/runs/:runId/cancel
104
+ ```
105
+
106
+ 单文档任务传 `source`;图片合并等多源任务传 `sources`。二者不能同时出现。平台会逐个
107
+ 轮换、核对并下载 Artifact,按 Operation 的 `limits.maxArtifacts` 和总输入字节预算
108
+ fail closed;Adapter 的单源 multipart 字段为 `file`,多源字段为重复的 `files`。
109
+
110
+ 创建请求需要 `idempotency-key`。平台把外部键按 Actor 与 Operation 做不可逆作用域化;
111
+ 相同作用域、Artifact 哈希、选项和 capability 重放返回原 Run,语义漂移返回
112
+ `RUN_CONFLICT`。事件包含:
113
+
114
+ - `run.started`
115
+ - `document.page.completed`
116
+ - `run.completed`
117
+ - `run.failed`
118
+ - `run.cancelled`
119
+
120
+ 每个事件都有单调 `sequence`。客户端断线后使用最后确认的 sequence 恢复,不重新提交
121
+ 任务。取消会记录终态并取消当前 Adapter 协程;已进入终态的 Run 不会再次完成。
122
+
123
+ ## Document Adapter 契约
124
+
125
+ `DOCUMENT_ADAPTER_URL` 只允许 localhost、私网 IP 或 `.internal` 主机。Document Server
126
+ 以 `multipart/form-data` 调用:
127
+
128
+ ```text
129
+ POST <DOCUMENT_ADAPTER_URL>/v1/document/process
130
+ ```
131
+
132
+ 请求包含单个 `file` 以及 `operationKey`、`options`、`dataClassification`、
133
+ `providerEgress`、`providerDataPolicy` 和 `limits`。Adapter 返回 JSON:
134
+
135
+ ```json
136
+ {
137
+ "pages": [
138
+ {
139
+ "pageNumber": 1,
140
+ "text": "第一页文本",
141
+ "blocks": [
142
+ {
143
+ "id": "b1",
144
+ "text": "第一页文本",
145
+ "confidence": 0.98,
146
+ "bounds": {
147
+ "x": 0.1,
148
+ "y": 0.1,
149
+ "width": 0.8,
150
+ "height": 0.1,
151
+ "unit": "normalized"
152
+ }
153
+ }
154
+ ],
155
+ "layout": []
156
+ }
157
+ ],
158
+ "tables": [],
159
+ "artifacts": [],
160
+ "metadata": { "engine": "intranet-ocr" }
161
+ }
162
+ ```
163
+
164
+ 页码必须唯一且不超过 `maxUnits`;坐标非负,置信度在 0–1,版面树深度最多 32。表格只能
165
+ 引用已存在页码,单元格必须是字符串。当前 `artifacts` 必须为空,防止 Adapter 绕过
166
+ 平台签发链。
167
+
168
+ ## 配置与启动
169
+
170
+ 选择 `system.agent` 的 Starter 会分发 Agent 与 Document 两个独立进程。`.env` 至少
171
+ 配置:
172
+
173
+ ```dotenv
174
+ PLATFORM_ENABLE_AI=true
175
+ AGENT_SERVER_INTERNAL_TOKEN=<随机内部令牌>
176
+ AI_ARTIFACT_ENCRYPTION_SECRET=<至少 32 字符的随机值>
177
+ DOCUMENT_SERVER_URL=http://document-server:8090
178
+ DOCUMENT_ARTIFACT_URL=http://server:9747/api/internal/ai/artifacts
179
+ DOCUMENT_ADAPTER_URL=http://ocr.internal:9000
180
+ DOCUMENT_ADAPTER_TOKEN=<可选内部令牌>
181
+ DOCUMENT_ADAPTER_CAPABILITIES=document.ocr,document.layout,document.table
182
+ DOCUMENT_MAX_UNITS=500
183
+ ```
184
+
185
+ 受控 Provider Deployment 还要设置 `DOCUMENT_DEPLOYMENT_KEY`,并与 Manifest 的稳定
186
+ Key 完全一致。启动:
187
+
188
+ ```bash
189
+ bun run dev:document
190
+ ```
191
+
192
+ Document Server 的 `/health/ready` 需要内部 Bearer Token,只返回 ready/unavailable、
193
+ capability 和非敏感 limits。没有 Adapter 时返回 unavailable;不会回显 Token、内部
194
+ 文件 URL、业务正文或底层异常。
195
+
196
+ ## 运维与取舍
197
+
198
+ - Run 与事件当前使用 Document Server 自有 SQLite,适合单副本;多副本持久化与平台侧
199
+ Run 授权恢复由统一 Agent 持久化能力继续收敛。
200
+ - Artifact 默认短时有效,并在每次读取时重算授权;任务时长不能超过 3600 秒。
201
+ - Adapter 地址被限制为本地/内网。需要云 Provider 时由受审计的内网 Adapter 承接,
202
+ 不允许业务直接配置公网 URL。
203
+ - 当前选择结构化 JSON 作为首个稳定结果,不伪造搜索型 PDF Artifact。后续输出文件必须
204
+ 通过平台 Artifact Sink、TTL、下载权限和审计后再开放。
@@ -0,0 +1,132 @@
1
+ # 受治理导出 Renderer
2
+
3
+ 更新时间:2026-07-27。
4
+
5
+ ## 适用边界
6
+
7
+ 业务 Module 可以在 `registerGovernedResources()` 的 `export.renderers` 中注册服务端
8
+ Renderer,把同一条受治理导出链生成 PDF、DOCX、XLSX 或其它受控格式。Renderer 是版式
9
+ 实现,不是新的授权或存储入口:
10
+
11
+ - 平台固化请求 Actor、Policy、查询、Scope 与 Renderer 版本;
12
+ - 平台处理审批、Durable Job、重试、取消、Artifact、TTL、失败清理和下载审计;
13
+ - Worker 在开始和读取每一页前重新检查用户、权限、Feature、License 与 Scope;
14
+ - Renderer 只能读取平台提供的 `AsyncIterable` 记录、水印、`AbortSignal` 和
15
+ `heartbeat()`,不能取得 Repository、BlobStore、Workflow、平台 Secret 或下载凭据。
16
+
17
+ 未声明 `renderers` 的已有 Module 保持 NDJSON 契约。声明 Renderer 后,请求必须显式提供
18
+ 已注册的 `formatKey`;缺少、未知或执行时未绑定的格式均 fail closed,不回退到 NDJSON。
19
+
20
+ ## 注册契约
21
+
22
+ 每个 Renderer 声明以下不可变元数据:
23
+
24
+ - `formatKey`:资源内稳定、唯一的小写格式 Key;
25
+ - `templateKey`:当前 Module 命名空间内的模板 Key;
26
+ - `templateVersion`:不可变模板版本;修改版式时递增,不覆盖历史版本;
27
+ - `inputSchemaKey`:当前 Manifest `jsonSchemas` 已登记的记录输入 Schema;
28
+ - `contentType` 与不含点号的 `filenameExtension`;
29
+ - `maxRecords`、`maxOutputBytes` 和 `timeoutSeconds`;
30
+ - `render()`:消费平台记录流并返回 `ReadableStream<Uint8Array>`。
31
+
32
+ 示例:
33
+
34
+ ```ts
35
+ host.registerGovernedResources([
36
+ {
37
+ policyKey: "reports.official.fields",
38
+ readPath: "/api/reports/official/:id",
39
+ revealPath: "/api/reports/official/:id/plaintext",
40
+ revealAuditAction: "reports.official.reveal",
41
+ authorize,
42
+ read,
43
+ export: {
44
+ basePath: "/api/reports/official/exports",
45
+ validateQuery,
46
+ readPage,
47
+ renderers: [
48
+ {
49
+ formatKey: "pdf",
50
+ templateKey: "reports.official.pdf",
51
+ templateVersion: "2026.07",
52
+ inputSchemaKey: "reports.official.input.v1",
53
+ contentType: "application/pdf",
54
+ filenameExtension: "pdf",
55
+ limits: {
56
+ maxRecords: 10_000,
57
+ maxOutputBytes: 20 * 1024 * 1024,
58
+ timeoutSeconds: 120,
59
+ },
60
+ async render({ records, watermark }, { signal, heartbeat }) {
61
+ return renderOfficialPdf({
62
+ records,
63
+ watermark,
64
+ signal,
65
+ heartbeat,
66
+ });
67
+ },
68
+ },
69
+ ],
70
+ },
71
+ },
72
+ ]);
73
+ ```
74
+
75
+ `renderOfficialPdf()` 属于业务 package,但必须是纯服务端流式 Renderer。它可以使用随
76
+ 业务 package 分发的模板和字体,不得在执行时绕回业务 Repository 补查数据,也不得自行
77
+ 上传、发布或签发下载链接。
78
+
79
+ ## 请求与版本固化
80
+
81
+ 创建请求的 body 在原有 `idempotencyKey`、`reason`、`query` 之外接受 `formatKey`。
82
+ 平台在校验 Host 注册后,把以下快照持久化并纳入请求 fingerprint:
83
+
84
+ - 格式、模板 Key 与模板版本;
85
+ - 输入 Schema Key;
86
+ - MIME 与扩展名;
87
+ - 记录、输出字节和超时上限。
88
+
89
+ 相同幂等键改选格式、模板或上限会形成语义冲突。Worker 执行时把持久化快照与当前 Host
90
+ 注册逐项比对;部署遗漏 Renderer 或模板版本漂移会终止任务,避免恢复任务静默套用另一套
91
+ 版式。
92
+
93
+ ## 流、取消与资源限制
94
+
95
+ 平台逐页调用业务 `readPage()`,在每页读取前重新授权,再把记录逐条交给 Renderer。
96
+ Renderer 应使用 `for await` 消费记录,不应先汇总完整数组。Renderer 未完整消费记录流
97
+ 即返回产物时,平台拒绝发布。
98
+
99
+ 平台同时施加以下硬边界:
100
+
101
+ - 超过 `maxRecords` 时终止记录流;
102
+ - 读取输出流时累计实际字节数,超过 `maxOutputBytes` 时终止;
103
+ - `timeoutSeconds` 到期或 Durable Job 取消时中止同一个 `AbortSignal`;
104
+ - 每页完成后更新 heartbeat 与 Durable Job progress;
105
+ - 渲染、流读取或 Artifact 发布失败时删除已经写入但尚未发布的 Blob。
106
+
107
+ Renderer 必须把 `signal` 继续传给支持取消的模板、压缩或文档库。忽略 Signal 的库仍会在
108
+ 平台读取输出时被终止,但可能延迟释放其内部 CPU 或内存,因此不应作为生产 Adapter。
109
+
110
+ ## Artifact 与审计
111
+
112
+ 发布后的 Durable Artifact 记录真实文件名、MIME、SHA-256、字节数,并在受控 metadata
113
+ 中记录 `formatKey`、`templateKey`、`templateVersion`、`inputSchemaKey` 和 Renderer
114
+ 返回的有限 `contentMetadata`。`contentMetadata` 只允许有界 JSON,不得包含记录原文、
115
+ 敏感字段、完整 ID 集或模板生成的正文。
116
+
117
+ 请求审计记录格式和模板版本;生成 intent 审计还记录输入 Schema 与 MIME;下载审计记录
118
+ 同一格式、模板版本和内容摘要。下载前仍重新检查 owner、Permission、Feature、审批、
119
+ TTL、Artifact 引用和 Blob 完整性,Renderer 不能改变这些顺序。
120
+
121
+ ## 测试要求
122
+
123
+ 业务 Renderer 至少覆盖:
124
+
125
+ - 正文、来源引用和水印内容,而不只断言文件存在;
126
+ - 请求缺少格式、未知格式、注册丢失和模板版本漂移;
127
+ - 多页记录按流消费,记录/输出上限与超时;
128
+ - 取消、权限撤销、Scope 变化、渲染失败和发布失败不留下可下载半成品;
129
+ - 下载 MIME、文件名、SHA-256、字节数与模板元数据一致。
130
+
131
+ PDF、DOCX、XLSX 应使用对应解析库回读断言正文和水印。平台的测试 Renderer 只证明 Port
132
+ 及治理生命周期;具体文档格式正确性由实现该格式的业务 Module 负责。
@@ -34,3 +34,8 @@ PUT /api/system/menus/:id/placement
34
34
  ## 扩展约束
35
35
 
36
36
  新增页面时先在所属 Module Manifest 用同一能力 Key 注册 Feature、权限、菜单和 API 绑定,再新增 Router 页面。Router 契约测试会比较所有 `type=menu` 的 Manifest 路径与 Web 路由,避免出现“菜单有入口但页面不存在”或“页面绕过注册表”的漂移。
37
+
38
+ 客户业务页面不直接修改 shared-scaffold Router 或侧栏。页面实现注册到
39
+ project-owned 的 `apps/web/src/app/business-routes.ts`,平台 Router Adapter 统一补齐
40
+ Feature/RBAC Guard;侧栏仍从服务端 Manifest 快照生成。完整接入与升级边界见
41
+ [project-owned 业务 Web 路由](../development/business-web-routes.md)。
@@ -9,7 +9,8 @@ Windy V1 采用单组织、单租户的企业私有化模型。canonical schema
9
9
  - `roles.data_scope` 和 `roles.department_ids` 表示角色数据范围策略;
10
10
  - 岗位、兼职部门和多组织成员关系属于后续独立模型,不复用 `department_id` 暗中表达。
11
11
 
12
- 这些边界延续 [RuoYi-Vue 兼容与迁移设计](../adapters/ruoyi-vue-compatibility.md) 的 Adapter 原则,也与 [Starter 产品缺口分析](../product/starter-product-gap-analysis.md) 中“单组织 V1”和“数据权限必须进入查询约束”的结论一致。
12
+ 这些边界延续 [RuoYi-Vue 兼容与迁移设计](../adapters/ruoyi-vue-compatibility.md) 的
13
+ Adapter 原则;数据权限必须进入查询约束,不能只依赖 Web 菜单或按钮隐藏。
13
14
 
14
15
  ## 用户表单闭环
15
16