create-windy 0.6.6 → 0.6.8
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.
- package/README.md +2 -2
- package/dist/cli.js +11 -68
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/apps/agent-server/README.md +4 -4
- package/template/apps/server/Dockerfile +3 -2
- package/template/apps/server/src/ai-runtime-report/actor-handle.test.ts +2 -1
- package/template/apps/server/src/data-access/drizzle-predicate.ts +16 -6
- package/template/apps/web/Dockerfile +1 -0
- package/template/docs/architecture/ai-egress.md +1 -1
- package/template/docs/architecture/ai-runtime-readiness.md +2 -2
- package/template/docs/architecture/ai-runtime.md +15 -15
- package/template/docs/architecture/module-secret-resolution.md +4 -4
- package/template/docs/architecture/report-staging.md +1 -1
- package/template/docs/development/custom-login-page.md +3 -3
- package/template/docs/license/license-offline-activation-code.md +2 -2
- package/template/docs/operations/incremental-delivery.md +213 -0
- package/template/docs/platform/agent-runtime.md +4 -4
- package/template/docs/platform/document-ai-runtime.md +3 -3
- package/template/docs/platform/speech-ai-runtime.md +1 -1
- package/template/docs/platform/vision-ai-runtime.md +1 -1
- package/template/docs/product/built-in-roles.md +9 -1
- package/template/package.json +2 -1
- package/template/packages/delivery/bin/windy-delivery +2 -0
- package/template/packages/delivery/index.ts +14 -0
- package/template/packages/delivery/package.json +23 -0
- package/template/packages/delivery/src/archive-portability.test.ts +84 -0
- package/template/packages/delivery/src/archive.ts +247 -0
- package/template/packages/delivery/src/blob-store.ts +242 -0
- package/template/packages/delivery/src/builder.ts +199 -0
- package/template/packages/delivery/src/checksums.ts +83 -0
- package/template/packages/delivery/src/cli.ts +331 -0
- package/template/packages/delivery/src/delivery.test.ts +332 -0
- package/template/packages/delivery/src/digest.ts +31 -0
- package/template/packages/delivery/src/manifest.ts +255 -0
- package/template/packages/delivery/src/negotiation.ts +68 -0
- package/template/packages/delivery/src/pack.test.ts +165 -0
- package/template/packages/delivery/src/paths.ts +27 -0
- package/template/packages/delivery/src/process-command.ts +49 -0
- package/template/packages/delivery/src/redact.ts +14 -0
- package/template/packages/delivery/src/registry.test.ts +166 -0
- package/template/packages/delivery/src/registry.ts +148 -0
- package/template/packages/delivery/src/security.test.ts +41 -0
- package/template/packages/delivery/src/ssh-capabilities.ts +65 -0
- package/template/packages/delivery/src/ssh-process.ts +349 -0
- package/template/packages/delivery/src/ssh-sync.test.ts +236 -0
- package/template/packages/delivery/src/ssh-sync.ts +167 -0
- package/template/packages/delivery/src/staging.ts +241 -0
- package/template/packages/delivery/src/transfer.ts +54 -0
- package/template/packages/delivery/src/types.ts +78 -0
- package/template/packages/delivery/tsconfig.json +16 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# 增量交付与确定性归档
|
|
2
|
+
|
|
3
|
+
Windy 的交付传输层提供两条增量路径,并保留完整 bundle 作为兼容和真正离线
|
|
4
|
+
fallback。传输层只改变“镜像字节如何到达目标机”,不会替代现有部署事务中的数据库
|
|
5
|
+
备份、volume 冻结、migration、readiness、安装账本原子提交和失败回滚。
|
|
6
|
+
|
|
7
|
+
## 不变量
|
|
8
|
+
|
|
9
|
+
- release manifest 绑定完整 Git SHA、目标平台、镜像角色、镜像 config digest、registry
|
|
10
|
+
digest、Docker archive 文件图和所有内容 blob。
|
|
11
|
+
- 在线镜像事实必须是 `repository@sha256:<64 hex>`;tag 仅可作为发布入口,不能写入
|
|
12
|
+
安装账本或作为已部署版本证明。
|
|
13
|
+
- V2 manifest 自身在传输前计算 SHA-256,目标机在 stage 前重新计算。每个 blob 同时
|
|
14
|
+
校验声明尺寸和 SHA-256;缺失、损坏、重复路径、路径穿越或 schema 不支持都会停止。
|
|
15
|
+
- blob 传输完成不等于部署完成。只有旧部署事务完成备份、镜像 load/pull、migration、
|
|
16
|
+
readiness 和账本提交后,release 才生效。
|
|
17
|
+
- 失败的传输保留已校验完整 blob 和合法 partial,以便续传;失败 stage 会删除自己的
|
|
18
|
+
nonce 目录,不会删除当前 release、备份或其它 owner 的资源。
|
|
19
|
+
|
|
20
|
+
## Manifest V2
|
|
21
|
+
|
|
22
|
+
`DeliveryManifestV2` 使用 `schemaVersion=2` 和 `minimumReaderVersion=2`。核心字段为:
|
|
23
|
+
|
|
24
|
+
- `images[]`:稳定 role、platform、Docker config digest,以及可选的 registry repository
|
|
25
|
+
与 manifest digest、layer digest/size;repository 不允许包含 tag、digest、scheme 或
|
|
26
|
+
凭据。config、manifest 和 layer descriptor 必须绑定归档中的同一内容 blob。
|
|
27
|
+
- `dockerArchive`:格式固定为 `oci-image-layout-v1`;`entries[]` 绑定重建离线 OCI image
|
|
28
|
+
layout 所需的相对路径、内容 digest、size 和 mode。
|
|
29
|
+
- `releaseFiles[]`:除 `images.tar` 外的完整 release 文件图;stage 会连同重建的
|
|
30
|
+
`images.tar` 一起原子落盘,不只生成镜像归档。
|
|
31
|
+
- `blobs[]`:去重后的内容寻址集合;多个 archive entry 可复用同一个 blob。
|
|
32
|
+
- `compatibility.fullBundle`:可选 V1 完整 bundle 的文件名、digest 和 size。
|
|
33
|
+
|
|
34
|
+
reader 必须先协商能力:
|
|
35
|
+
|
|
36
|
+
1. 目标支持 registry digest 且 manifest 中每个镜像都有 registry 事实时,优先在线拉取。
|
|
37
|
+
2. 否则,双方支持 V2、blob delta 和 resumable transfer 时,使用 SSH blob 差量。
|
|
38
|
+
3. 否则,只有 manifest 显式携带 V1 fallback 时才回退完整 bundle。
|
|
39
|
+
4. 没有共同 schema 时直接失败;不得把未知 V2 当成 V1,也不得静默上传完整包。
|
|
40
|
+
|
|
41
|
+
## 在线 registry 路径
|
|
42
|
+
|
|
43
|
+
发布端把镜像推送到标准 OCI Registry(腾讯云 TCR 等),冻结 registry 返回的 manifest
|
|
44
|
+
digest。目标机按 digest pull:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
ccr.ccs.tencentyun.com/example/windy-server@sha256:...
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Docker/Containerd 会根据本地 content store 只下载缺失 layer。pull 后必须再次 inspect,
|
|
51
|
+
确认目标 `repo@digest` 的 image config digest 与 manifest 一致,再把 digest-pinned image
|
|
52
|
+
变量交给现有部署事务。Registry 登录由 Docker credential helper 或目标机受控 Secret
|
|
53
|
+
管理;manifest、日志和统计不得包含用户名、密码、token、签名 URL 或 Docker config。
|
|
54
|
+
|
|
55
|
+
在线统计以 OCI descriptor 为单位记录总 bytes/blob、已存在 bytes/blob 和 Docker 报告
|
|
56
|
+
`Pull complete` 的 descriptor bytes/blob。当前 `pull-registry` 无法从 Docker CLI 审计
|
|
57
|
+
部分缓存、续传及 HTTP/manifest/config 协议开销,因此 `uploadedBytes` 与
|
|
58
|
+
`uploadedBlobs` 明确返回 `null`,另以 `descriptorPulledBytes` /
|
|
59
|
+
`descriptorPulledBlobs` 报告 descriptor 状态;不得把它冒充精确网络字节。无法取得逐
|
|
60
|
+
layer 状态时直接失败,也不用镜像虚拟大小估算。
|
|
61
|
+
|
|
62
|
+
目标机执行示例:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
manifest_digest="$(tr -d '\n' < ./DELIVERY-MANIFEST.sha256)"
|
|
66
|
+
bun run delivery:remote -- pull-registry \
|
|
67
|
+
--manifest-digest "$manifest_digest" \
|
|
68
|
+
< ./delivery-manifest.v2.json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
命令只接受 manifest 中的 `repository@digest`,pull 后用 Docker image config digest 二次
|
|
72
|
+
验真,再输出 descriptor 总量、复用量与上述可审计性标记。镜像推送与
|
|
73
|
+
digest 冻结由发布流水线的 OCI Registry adapter 完成;adapter 必须把 Registry 返回的
|
|
74
|
+
manifest digest 和 descriptor size 写入 `images.json`,不能把输入 tag 当作冻结事实。
|
|
75
|
+
|
|
76
|
+
## SSH / 离线 blob 差量
|
|
77
|
+
|
|
78
|
+
生成物中的 `@southwind-ai/delivery` 是 Starter 私有 workspace package。远端命令入口:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
bun run delivery:remote -- capabilities
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
协调器的协议顺序固定为:
|
|
85
|
+
|
|
86
|
+
1. 调用 `capabilities`,协商 schema 和传输方式。
|
|
87
|
+
2. 在安装根目录用原子 `mkdir` 获取 `.delivery-lock`,写入随机 owner token;并发部署失败。
|
|
88
|
+
3. 把 manifest 的 blob 描述发送给 `inventory`。远端逐个重新计算尺寸和 SHA-256,损坏
|
|
89
|
+
blob 不计为复用。
|
|
90
|
+
4. 对缺失 blob 调用 `partial` 获取已落盘 offset。发送端只从该 offset 继续;接收端要求
|
|
91
|
+
offset 精确相等、每段 `fsync`,完整后校验 digest 并原子 rename。
|
|
92
|
+
5. `stage` 重新验证 manifest 和所有 blob,检查空间预算,在随机 nonce 目录重建确定性
|
|
93
|
+
OCI `images.tar` 与其余 release 文件,并验证 `CHECKSUMS.sha256` 的精确集合和每个
|
|
94
|
+
digest,再把包含 `release/` 与 delivery sidecar 的目录原子 rename 到
|
|
95
|
+
`releases/<releaseId>`。既有部署事务只消费其中的 `release/`,delivery sidecar 不污染
|
|
96
|
+
原 verifier 的文件集合。
|
|
97
|
+
6. 释放 owner 匹配的锁。后续由项目既有 `update-offline` 或生产发布事务消费 stage 目录。
|
|
98
|
+
|
|
99
|
+
远端低层命令使用 stdin 传 manifest/blob 列表和 blob bytes,不把内容放入 shell 参数。
|
|
100
|
+
本地 `SshProcessTransport` 用参数数组调用 `ssh`,目标、二进制和远端路径均先做字符与
|
|
101
|
+
路径边界校验。错误输出会截断并脱敏 credential marker。
|
|
102
|
+
|
|
103
|
+
`packages/delivery/bin/windy-delivery` 是受管的远端 launcher;它不注册为 workspace
|
|
104
|
+
package `bin`,避免 fresh `bun install` 改写受管源码的 POSIX mode。第一次启用 V2 前,
|
|
105
|
+
从已验真的 Starter/release SHA 把整个 `packages/delivery` 安装到版本目录,再原子切换
|
|
106
|
+
稳定入口(以下命令在目标 Linux 上执行):
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
sudo install -d -m 0755 /opt/windy/runtime /opt/windy/bin
|
|
110
|
+
sudo cp -R ./packages/delivery /opt/windy/runtime/delivery-0.6.7.tmp
|
|
111
|
+
sudo chmod 0555 /opt/windy/runtime/delivery-0.6.7.tmp/bin/windy-delivery
|
|
112
|
+
sudo mv /opt/windy/runtime/delivery-0.6.7.tmp \
|
|
113
|
+
/opt/windy/runtime/delivery-0.6.7
|
|
114
|
+
sudo ln -s /opt/windy/runtime/delivery-0.6.7/bin/windy-delivery \
|
|
115
|
+
/opt/windy/bin/windy-delivery.next
|
|
116
|
+
sudo mv -Tf /opt/windy/bin/windy-delivery.next \
|
|
117
|
+
/opt/windy/bin/windy-delivery
|
|
118
|
+
/opt/windy/bin/windy-delivery capabilities
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
版本目录必须来自已经校验的 release,不从 registry tag、临时工作树或目标机网络重新
|
|
122
|
+
生成。若目标还没有 launcher,能力探测只允许按下文显式回退 V1 完整 bundle;SSH
|
|
123
|
+
鉴权、权限、摘要或原子切换失败都不得降级。保留上一版本目录,直到新 launcher 完成
|
|
124
|
+
`capabilities` 与一次部署事务;回滚只需把稳定 symlink 原子切回上一版本。
|
|
125
|
+
|
|
126
|
+
空间预算在上传缺失 blob 前执行,分别检查 blob store 与 install root 所在文件系统;同一
|
|
127
|
+
挂载点按缺失 blob、完整 release、delivery sidecar、重建 OCI archive 的
|
|
128
|
+
header/padding/payload 和 512 MiB 默认安全余量合并预算,不同挂载点分别 fail closed。
|
|
129
|
+
blob store 的保留/回收策略必须以已提交 release、
|
|
130
|
+
previous release 和未过期 partial 的引用闭包为依据;不得用目录 mtime 猜测后直接删除。
|
|
131
|
+
|
|
132
|
+
V2 差量示例(`auto` 在具备 registry facts 时只返回 `registry-preferred`,不会暗中改走
|
|
133
|
+
SSH;需要 SSH 时显式选择 `blob-delta`):
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
manifest_digest="$(tr -d '\n' < ./DELIVERY-MANIFEST.sha256)"
|
|
137
|
+
bun run delivery:remote -- sync-ssh \
|
|
138
|
+
--manifest ./delivery-manifest.v2.json \
|
|
139
|
+
--manifest-digest "$manifest_digest" \
|
|
140
|
+
--store . \
|
|
141
|
+
--target deploy@example.internal \
|
|
142
|
+
--remote-binary /opt/windy/bin/windy-delivery \
|
|
143
|
+
--remote-store /opt/windy/blob-store \
|
|
144
|
+
--remote-install-root /opt/windy \
|
|
145
|
+
--remote-fallback-root /opt/windy/fallback \
|
|
146
|
+
--mode blob-delta
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
旧端或显式兼容模式使用同一命令的 `--mode full-bundle --fallback
|
|
150
|
+
./full-bundle.tar.gz`。fallback 同样持有 owner 锁并预检目标挂载点;完整包先传到
|
|
151
|
+
owner-scoped partial,摘要核对成功后 no-clobber 原子 rename,失败只清理自己的 partial。
|
|
152
|
+
远端已有同 digest 文件会复用,同名不同 digest 会 fail closed。capability 只有明确的旧端
|
|
153
|
+
“命令不存在/不支持”响应才可降级;SSH 鉴权、网络、权限或畸形 JSON 一律中止。
|
|
154
|
+
|
|
155
|
+
## 传输统计
|
|
156
|
+
|
|
157
|
+
每次 blob 计划和实际传输输出以下不含凭据的整数:
|
|
158
|
+
|
|
159
|
+
- `totalBytes` / `totalBlobs`:当前 release 的去重 blob 集合。
|
|
160
|
+
- `reusedBytes` / `reusedBlobs`:远端完整 blob,加上本次从 partial 续传前已存在的 bytes。
|
|
161
|
+
- `uploadedBytes` / `uploadedBlobs`:本次真正写入 SSH 的 bytes 和涉及的 blob 数。
|
|
162
|
+
|
|
163
|
+
统计来自内容 descriptor 和接收端确认,不解析 registry URL,也不输出命令环境变量。
|
|
164
|
+
|
|
165
|
+
## 确定性归档与 macOS
|
|
166
|
+
|
|
167
|
+
正式 `.tar.gz` 和重建的 `images.tar` 由 Windy 的 ustar writer 按排序后的显式文件集合
|
|
168
|
+
生成:uid/gid/mtime 固定为 0,owner/group 固定为 root,mode 归一化,不写 pax xattr、
|
|
169
|
+
resource fork 或 Finder metadata。路径组件以 `._` 开头时直接拒绝。因此 macOS 上即使
|
|
170
|
+
源目录具有 extended attributes,归档也不会产生 AppleDouble `._*`。
|
|
171
|
+
|
|
172
|
+
不要改回普通 `tar -czf` 依赖宿主机默认行为。`COPYFILE_DISABLE=1` 只作为调用系统 tar
|
|
173
|
+
读取/验收时的额外保护,不是正式修复本身。回归测试在 macOS 写入真实 xattr,再把归档
|
|
174
|
+
交给无网络的 Alpine 容器解压并比较精确文件集合。
|
|
175
|
+
|
|
176
|
+
## 构建与旧项目升级
|
|
177
|
+
|
|
178
|
+
`create-windy@0.6.7` 候选把 `packages/delivery`、本说明和 `delivery:remote` 脚本加入
|
|
179
|
+
受管 Starter。`0.6.6 -> 0.6.7` 通过连续 recipe 事务升级;现有 project-owned 发布脚本
|
|
180
|
+
不会被覆盖。正式构建先准备一个完整 release 目录,其中 `images.tar` 必须是 OCI image
|
|
181
|
+
layout(含 `oci-layout`、`index.json` 和 `blobs/sha256/*`),再运行:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
bun run delivery:remote -- pack \
|
|
185
|
+
--release-dir ./.artifacts/release \
|
|
186
|
+
--output-dir ./.artifacts/delivery \
|
|
187
|
+
--product windy \
|
|
188
|
+
--release-id 2026.08.08-1 \
|
|
189
|
+
--git-sha "$release_git_sha" \
|
|
190
|
+
--platform linux/amd64 \
|
|
191
|
+
--created-at 2026-08-08T00:00:00.000Z \
|
|
192
|
+
--images-json ./.artifacts/images.json
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`pack` 不改输入目录:它在临时目录重建确定性的 OCI `images.tar`,重新生成覆盖最终精确
|
|
196
|
+
文件集合的 `CHECKSUMS.sha256`,并以这组相同字节同时构建 V1 与 V2。输出目录独立包含
|
|
197
|
+
`full-bundle.tar.gz`、`delivery-manifest.v2.json`、
|
|
198
|
+
`DELIVERY-MANIFEST.sha256` 和 `blobs/sha256/*`。`images.json` 由 Registry/OCI exporter
|
|
199
|
+
生成,必须记录每个镜像的 role、platform、config digest,以及可选的 repository、
|
|
200
|
+
manifest digest 和 layer digest/size。`pack` 会拒绝普通 Docker save archive、缺失 OCI
|
|
201
|
+
layout、digest 路径不一致、镜像 descriptor 未绑定归档 blob 或输出目录覆盖。
|
|
202
|
+
|
|
203
|
+
旧项目按以下顺序接入:
|
|
204
|
+
|
|
205
|
+
1. 先让现有 build-release 生成 OCI layout,并调用 `pack` 同时产出独立 V1 完整 bundle
|
|
206
|
+
与 V2 manifest/blob store;不要用 rsync 整个 `images.tar` 伪装差量。
|
|
207
|
+
2. 在线发布增加 registry push 与 digest 冻结,目标事务改用 digest-pinned refs。
|
|
208
|
+
3. SSH wrapper 接入 `SshProcessTransport`,stage 成功后仍调用原 `update-offline`。
|
|
209
|
+
4. 至少连续保留一个兼容周期的 V1 bundle;确认所有安装端支持 V2 后,才可由产品版本
|
|
210
|
+
决策是否停止生成 fallback。
|
|
211
|
+
|
|
212
|
+
升级器的三方合并、备份和冲突处理规则不变。客户已有同名 project-owned 文件时必须
|
|
213
|
+
报告真实冲突,不能用 `--force` 或手工复制覆盖。
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
更新时间:2026-07-26。
|
|
4
4
|
|
|
5
5
|
Windy 的 Agent Runtime 是可选平台能力。业务模块声明稳定的 AI Operation 和允许调用的
|
|
6
|
-
Tool;
|
|
6
|
+
Tool;Windy Server 负责身份、Feature、Permission、License、Scope、Execution
|
|
7
7
|
Grant 与审计;独立 Agent Server 只负责模型循环、canonical event 和受限 Tool Host
|
|
8
8
|
回调。业务模块不选择 Provider、模型、Endpoint 或凭据。
|
|
9
9
|
|
|
@@ -94,7 +94,7 @@ host.registerAiOperationHandlers([
|
|
|
94
94
|
放进返回值。`prepare` 只负责把已经过宿主 Guard 的类型化输入转成指令和用户消息。
|
|
95
95
|
|
|
96
96
|
Tool handler 仍通过 `registerToolHandlers` 注册。模型提出 Tool Call 后,Agent Server
|
|
97
|
-
携带短时 opaque grant 回调
|
|
97
|
+
携带短时 opaque grant 回调 Windy Server;受限 Tool Host 会验证:
|
|
98
98
|
|
|
99
99
|
1. grant 是否绑定当前 Run、Operation、Actor 且未过期;
|
|
100
100
|
2. Tool 是否属于 Operation 的 `allowedToolKeys`;
|
|
@@ -106,7 +106,7 @@ Tool 请求不能携带权限列表、License、Scope 或“已授权”标志
|
|
|
106
106
|
|
|
107
107
|
## 跨进程 Limits 契约
|
|
108
108
|
|
|
109
|
-
Manifest 的 Operation limits 同时包含输入、执行时间和输出上限。
|
|
109
|
+
Manifest 的 Operation limits 同时包含输入、执行时间和输出上限。Windy Server
|
|
110
110
|
在调用远端 Agent Server 前先执行文本输入与 Artifact 声明的 `maxInputBytes` 校验;
|
|
111
111
|
跨进程请求逐字段发送 Agent Server 需要再次执行的时长、输入、输出、图片数量与像素
|
|
112
112
|
上限。禁止通过展开 Manifest 对象透传 limits:TypeScript 的结构类型不会在运行时
|
|
@@ -293,7 +293,7 @@ Operation 契约。
|
|
|
293
293
|
|
|
294
294
|
## 客户端协议
|
|
295
295
|
|
|
296
|
-
浏览器继续使用 `@southwind-ai/ai-client` 访问同源
|
|
296
|
+
浏览器继续使用 `@southwind-ai/ai-client` 访问同源 Windy Server:
|
|
297
297
|
|
|
298
298
|
```text
|
|
299
299
|
POST /api/agent/runs
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Windy 的 Document AI 是可选的独立故障域。业务模块声明稳定的
|
|
6
6
|
`kind=document` Operation,并先把当前 Actor 有权读取的业务资源换成短时
|
|
7
|
-
`ArtifactRef`;
|
|
7
|
+
`ArtifactRef`;Windy Server 完成 Feature、RBAC、License、数据范围、业务对象绑定
|
|
8
8
|
和审计后,才把任务交给 Document Server。未安装或未配置 Document Server 时,对应
|
|
9
9
|
Operation 稳定返回 `CAPABILITY_UNAVAILABLE`,普通业务和文本 Agent 仍可启动。
|
|
10
10
|
|
|
@@ -103,7 +103,7 @@ Repository 证明当前 Actor 可访问真实资源并绑定该文件时才会
|
|
|
103
103
|
|
|
104
104
|
## HTTP 与事件协议
|
|
105
105
|
|
|
106
|
-
浏览器通过同源
|
|
106
|
+
浏览器通过同源 Windy Server 调用:
|
|
107
107
|
|
|
108
108
|
```text
|
|
109
109
|
POST /api/document/runs
|
|
@@ -131,7 +131,7 @@ fail closed;Adapter 的单源 multipart 字段为 `file`,多源字段为重
|
|
|
131
131
|
|
|
132
132
|
### Output Artifact 握手
|
|
133
133
|
|
|
134
|
-
Output Sink 启用时,
|
|
134
|
+
Output Sink 启用时,Windy Host 在调用 Runtime 前冻结:
|
|
135
135
|
|
|
136
136
|
- 预分配的 `runId`;
|
|
137
137
|
- 不含 credential 的 Sink Descriptor,包括 scope、output kind、部署/Release/Offer/
|
|
@@ -16,7 +16,7 @@ Agent 启动。
|
|
|
16
16
|
Repository 回查 `resourceId`、`fileId` 与当前 Actor 的真实关系。
|
|
17
17
|
2. 模块路由调用 `context.aiArtifacts.issue(...)` 换取加密、短时、opaque
|
|
18
18
|
`ArtifactRef`。
|
|
19
|
-
3. `@southwind-ai/ai-client/speech` 只把 Artifact 与领域选项提交到同源
|
|
19
|
+
3. `@southwind-ai/ai-client/speech` 只把 Artifact 与领域选项提交到同源 Windy
|
|
20
20
|
Server。
|
|
21
21
|
4. Server 重新检查 Feature、Permission、License、数据范围、能力摘要和 Artifact
|
|
22
22
|
元数据,并按 Actor + Operation 作用域化幂等键。
|
|
@@ -12,7 +12,7 @@ Windy 的首个 Vision 切片在现有 Agent canonical Run 上增加受治理的
|
|
|
12
12
|
|
|
13
13
|
- 浏览器和业务模块只提交平台签发的 opaque `ArtifactRef`,不能提交 URL、Base64、
|
|
14
14
|
BlobStore Key、宿主路径、模型名或 Provider Endpoint。
|
|
15
|
-
-
|
|
15
|
+
- Windy Server 在 Feature、Permission、License、数据范围通过后重新打开资源,
|
|
16
16
|
校验 MIME、字节数与 SHA-256,再为 Agent Server 轮换短时引用。
|
|
17
17
|
- Agent Server 只从固定 `AI_ARTIFACT_URL` 内网入口读取图片,拒绝调用方 URL。
|
|
18
18
|
- 只接受 JPEG、PNG、WebP;解码后强制检查图片数量、总字节与单图像素。
|
|
@@ -21,6 +21,14 @@ Starter 首次安装提供四个稳定角色。角色 ID 和 code 是升级契
|
|
|
21
21
|
|
|
22
22
|
## 安全边界
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
`dataScope` 和 `departmentIds` 已持久化,用户、示例工单、Agent Tool、导出与受支持的
|
|
25
|
+
后台路径已经接入 Repository 级查询范围强制。列表、总数、详情、更新和删除把同一 scope
|
|
26
|
+
predicate 下推到查询,缺少 nominal `ScopeContext` 或 ownership 绑定时 fail closed;完整
|
|
27
|
+
边界见[Repository 级 Data Scope 强制](../architecture/data-scope-query-enforcement.md)。新增
|
|
28
|
+
Repository 或异步资源仍必须显式登记 `dataAccess`,不能因已有基础设施而默认视为已覆盖。
|
|
29
|
+
|
|
30
|
+
部门负责人基线继续不授予 `system.user.manage` 或 `system.department.manage`。这是最小
|
|
31
|
+
权限的产品默认值,不是用前端隐藏替代服务端 Guard;管理员可按部署需求显式配置自定义
|
|
32
|
+
角色,最终仍受 Feature、Permission 和 Repository scope 共同约束。
|
|
25
33
|
|
|
26
34
|
用户敏感字段策略将 `system.user.read`、`system.user.reveal` 和 `system.user.export` 分为三类能力。普通读取只能取得服务端脱敏结果;明文查看与治理导出即使由超级管理员执行,也必须重新授权并写入关键审计。客户端提交的角色 code、权限数组或展示模式不是授权来源。
|
package/template/package.json
CHANGED
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"test:ops:tool-sandbox": "bun run scripts/tool-sandbox-isolation-regression.ts",
|
|
53
53
|
"ops:db": "bun run scripts/production-database.ts",
|
|
54
54
|
"ops:release": "bun run scripts/production-release.ts",
|
|
55
|
+
"delivery:remote": "bun run packages/delivery/src/cli.ts",
|
|
55
56
|
"test:merge": "bun run scripts/merge-gate.ts",
|
|
56
57
|
"release:verify": "bun run scripts/release-verify.ts --full",
|
|
57
58
|
"release:candidate": "bun run scripts/release-candidate.ts",
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
"check:document": "uv run --directory apps/document-server ruff check . && uv run --directory apps/document-server ruff format --check . && uv run --directory apps/document-server mypy",
|
|
66
67
|
"check:speech": "uv run --directory apps/speech-server ruff check . && uv run --directory apps/speech-server ruff format --check . && uv run --directory apps/speech-server mypy",
|
|
67
68
|
"check:embedding": "uv run --directory apps/embedding-server ruff check . && uv run --directory apps/embedding-server ruff format --check . && uv run --directory apps/embedding-server mypy",
|
|
68
|
-
"typecheck": "bun run --cwd apps/web typecheck && bun run --cwd apps/server typecheck && bun run --cwd apps/license typecheck && bun run --cwd apps/signing-service typecheck && bun run --cwd packages/ai-client typecheck && bun run --cwd packages/license-sdk typecheck && bun run --cwd packages/jobs typecheck && bun run --cwd packages/server-sdk typecheck && bun run --cwd packages/storage typecheck && bun run --cwd packages/database typecheck && bun run --cwd packages/crud-generator typecheck && bun run --cwd packages/create-windy typecheck",
|
|
69
|
+
"typecheck": "bun run --cwd apps/web typecheck && bun run --cwd apps/server typecheck && bun run --cwd apps/license typecheck && bun run --cwd apps/signing-service typecheck && bun run --cwd packages/ai-client typecheck && bun run --cwd packages/license-sdk typecheck && bun run --cwd packages/jobs typecheck && bun run --cwd packages/server-sdk typecheck && bun run --cwd packages/storage typecheck && bun run --cwd packages/database typecheck && bun run --cwd packages/crud-generator typecheck && bun run --cwd packages/delivery typecheck && bun run --cwd packages/create-windy typecheck",
|
|
69
70
|
"lint": "oxlint .",
|
|
70
71
|
"format": "oxfmt",
|
|
71
72
|
"check:static": "bun run lint && bun run typecheck && bun run check:agent && bun run check:document && bun run check:speech && bun run check:embedding",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./src/archive.js";
|
|
2
|
+
export * from "./src/blob-store.js";
|
|
3
|
+
export * from "./src/builder.js";
|
|
4
|
+
export * from "./src/checksums.js";
|
|
5
|
+
export * from "./src/digest.js";
|
|
6
|
+
export * from "./src/manifest.js";
|
|
7
|
+
export * from "./src/negotiation.js";
|
|
8
|
+
export * from "./src/registry.js";
|
|
9
|
+
export * from "./src/redact.js";
|
|
10
|
+
export * from "./src/staging.js";
|
|
11
|
+
export * from "./src/ssh-process.js";
|
|
12
|
+
export * from "./src/ssh-sync.js";
|
|
13
|
+
export * from "./src/transfer.js";
|
|
14
|
+
export * from "./src/types.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@southwind-ai/delivery",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"bun": ">=1.3.14"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/windy-delivery",
|
|
12
|
+
"index.ts",
|
|
13
|
+
"src/**/*.ts",
|
|
14
|
+
"!src/**/*.test.ts"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"delivery": "bun run src/cli.ts",
|
|
18
|
+
"typecheck": "tsc --noEmit --project tsconfig.json"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./index.ts"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { afterEach, expect, test } from "bun:test";
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { createDeterministicTarGz, listTarGzEntries } from "./archive.js";
|
|
6
|
+
import { listArchiveFiles } from "./builder.js";
|
|
7
|
+
|
|
8
|
+
const roots: string[] = [];
|
|
9
|
+
const acceptanceTest =
|
|
10
|
+
process.env.WINDY_DELIVERY_LINUX_ACCEPTANCE === "1" &&
|
|
11
|
+
process.platform === "darwin"
|
|
12
|
+
? test
|
|
13
|
+
: test.skip;
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await Promise.all(
|
|
17
|
+
roots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
acceptanceTest(
|
|
22
|
+
"macOS xattr 输入经确定性构建后在 Linux 解压保持精确文件集合",
|
|
23
|
+
async () => {
|
|
24
|
+
const root = await mkdtemp(join(tmpdir(), "windy-archive-portability-"));
|
|
25
|
+
roots.push(root);
|
|
26
|
+
const input = join(root, "input");
|
|
27
|
+
const output = join(root, "linux");
|
|
28
|
+
const archive = join(root, "bundle.tar.gz");
|
|
29
|
+
await mkdir(join(input, "bin"), { recursive: true });
|
|
30
|
+
await writeFile(join(input, "manifest.json"), "{}\n");
|
|
31
|
+
await writeFile(join(input, "bin/delivery"), "#!/bin/sh\n", {
|
|
32
|
+
mode: 0o755,
|
|
33
|
+
});
|
|
34
|
+
await run([
|
|
35
|
+
"xattr",
|
|
36
|
+
"-w",
|
|
37
|
+
"com.southwind.delivery",
|
|
38
|
+
"portable-metadata",
|
|
39
|
+
join(input, "manifest.json"),
|
|
40
|
+
]);
|
|
41
|
+
await createDeterministicTarGz(archive, [
|
|
42
|
+
{ source: join(input, "manifest.json"), path: "manifest.json" },
|
|
43
|
+
{
|
|
44
|
+
source: join(input, "bin/delivery"),
|
|
45
|
+
path: "bin/delivery",
|
|
46
|
+
mode: 0o755,
|
|
47
|
+
},
|
|
48
|
+
]);
|
|
49
|
+
expect(await listTarGzEntries(archive)).toEqual([
|
|
50
|
+
"bin/delivery",
|
|
51
|
+
"manifest.json",
|
|
52
|
+
]);
|
|
53
|
+
await mkdir(output);
|
|
54
|
+
await run([
|
|
55
|
+
"docker",
|
|
56
|
+
"run",
|
|
57
|
+
"--rm",
|
|
58
|
+
"--network",
|
|
59
|
+
"none",
|
|
60
|
+
"-v",
|
|
61
|
+
`${root}:/work`,
|
|
62
|
+
"alpine:3.21",
|
|
63
|
+
"sh",
|
|
64
|
+
"-eu",
|
|
65
|
+
"-c",
|
|
66
|
+
"tar -xzf /work/bundle.tar.gz -C /work/linux",
|
|
67
|
+
]);
|
|
68
|
+
expect(await listArchiveFiles(output)).toEqual([
|
|
69
|
+
"bin/delivery",
|
|
70
|
+
"manifest.json",
|
|
71
|
+
]);
|
|
72
|
+
expect(await readFile(join(output, "manifest.json"), "utf8")).toBe("{}\n");
|
|
73
|
+
},
|
|
74
|
+
30_000,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
async function run(command: string[]): Promise<void> {
|
|
78
|
+
const child = Bun.spawn(command, { stdout: "pipe", stderr: "pipe" });
|
|
79
|
+
const [exitCode, stderr] = await Promise.all([
|
|
80
|
+
child.exited,
|
|
81
|
+
new Response(child.stderr).text(),
|
|
82
|
+
]);
|
|
83
|
+
if (exitCode !== 0) throw new Error(`${command[0]} 失败:${stderr.trim()}`);
|
|
84
|
+
}
|