create-windy 0.7.0 → 0.8.1
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 +6 -1
- package/dist/cli.js +54 -44
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +1 -1
- package/template/apps/agent-server/Dockerfile +1 -1
- package/template/apps/agent-server/README.md +1 -1
- package/template/apps/agent-server/src/southwind_agent_server/asgi.py +8 -0
- package/template/apps/agent-server/src/southwind_agent_server/runtime_logging.py +57 -0
- package/template/apps/agent-server/src/southwind_agent_server/structured_logging.py +134 -0
- package/template/apps/agent-server/src/southwind_agent_server/telemetry.py +4 -0
- package/template/apps/agent-server/tests/test_run_operational_logging.py +114 -0
- package/template/apps/agent-server/tests/test_structured_logging.py +105 -0
- package/template/apps/server/src/agent/isolated-execution-guard.test.ts +194 -0
- package/template/apps/server/src/agent/isolated-execution-guard.ts +202 -0
- package/template/apps/server/src/agent/run-definition-support.ts +11 -1
- package/template/apps/server/src/agent/run-facade.ts +7 -11
- package/template/apps/server/src/agent/run-operation-policy.ts +11 -0
- package/template/apps/server/src/agent/workflow-durable-runtime-input.ts +8 -5
- package/template/apps/server/src/jobs/legacy-service-runtime.ts +4 -0
- package/template/apps/server/src/jobs/runtime-logging.test.ts +212 -0
- package/template/apps/server/src/jobs/runtime-logging.ts +58 -0
- package/template/apps/server/src/jobs/worker.ts +54 -12
- package/template/apps/server/src/observability/bootstrap.ts +1 -4
- package/template/apps/server/src/observability/http-observability.ts +2 -3
- package/template/apps/server/src/observability/structured-logger.test.ts +31 -0
- package/template/apps/server/src/observability/structured-logger.ts +27 -0
- package/template/apps/server/src/platform-bootstrap.ts +9 -3
- package/template/apps/server/src/startup-finalization.ts +3 -3
- package/template/apps/server/src/system/built-in-roles.test.ts +23 -0
- package/template/apps/server/src/system/built-in-roles.ts +10 -4
- package/template/apps/web/src/pages/system/composables/useResourceEditor.ts +16 -5
- package/template/apps/web/src/pages/system/composables/useResourceEditor.webtest.ts +4 -0
- package/template/docker-compose.yml +3 -3
- package/template/docs/architecture/log-governance-implementation.md +79 -0
- package/template/docs/architecture/log-governance-plan.md +39 -0
- package/template/docs/operations/observability.md +36 -4
- package/template/docs/platform/module-configuration.md +1 -1
- package/template/docs/platform/organization-membership.md +1 -1
- package/template/docs/platform/platform-authentication.md +1 -1
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/ai-isolated-execution-policy.test.ts +70 -0
- package/template/packages/modules/src/ai-isolated-execution-policy.ts +76 -0
- package/template/packages/modules/src/ai-operation-composition.ts +2 -0
- package/template/packages/modules/src/manifest.ts +23 -0
- package/template/packages/modules/src/system-features.ts +11 -10
- package/template/packages/server-sdk/package.json +1 -1
- package/template/packages/shared/index.ts +1 -0
- package/template/packages/shared/package.json +3 -2
- package/template/packages/shared/src/structured-log.test.ts +147 -0
- package/template/packages/shared/src/structured-log.ts +215 -0
- package/template/scripts/lib/postgres-regression.ts +1 -1
|
@@ -40,6 +40,10 @@ describe("账户编辑保存闭环", () => {
|
|
|
40
40
|
expect(editor.item.value).toBeUndefined();
|
|
41
41
|
expect(save.mock.calls[0]?.[1]).toBeUndefined();
|
|
42
42
|
expect(mutated).not.toHaveBeenCalled();
|
|
43
|
+
expect(await editor.submit({ ...payload, temporaryPassword: "" })).toBe(
|
|
44
|
+
false,
|
|
45
|
+
);
|
|
46
|
+
expect(save).toHaveBeenCalledTimes(1);
|
|
43
47
|
expect(await editor.submit(payload)).toBe(true);
|
|
44
48
|
expect(save.mock.calls[1]?.[1]).toBe("user_created");
|
|
45
49
|
expect(api.reset).toHaveBeenLastCalledWith(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
services:
|
|
2
2
|
postgres:
|
|
3
|
-
image:
|
|
3
|
+
image: pgvector/pgvector:pg18
|
|
4
4
|
environment:
|
|
5
5
|
POSTGRES_DB: windy
|
|
6
6
|
POSTGRES_USER: windy
|
|
@@ -8,7 +8,7 @@ services:
|
|
|
8
8
|
ports:
|
|
9
9
|
- "15432:5432"
|
|
10
10
|
volumes:
|
|
11
|
-
-
|
|
11
|
+
- postgres18-data:/var/lib/postgresql
|
|
12
12
|
healthcheck:
|
|
13
13
|
test: ["CMD-SHELL", "pg_isready -U windy -d windy"]
|
|
14
14
|
interval: 10s
|
|
@@ -324,7 +324,7 @@ services:
|
|
|
324
324
|
- server
|
|
325
325
|
|
|
326
326
|
volumes:
|
|
327
|
-
|
|
327
|
+
postgres18-data:
|
|
328
328
|
server-audit-data:
|
|
329
329
|
agent-run-data:
|
|
330
330
|
tool-sandbox-ipc:
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# 结构化运行日志技术实现
|
|
2
|
+
|
|
3
|
+
状态:SDK、平台适配与利剑四条链的本地定向联调已实现;正式发布/升级及集中查询/告警平台未验收。
|
|
4
|
+
对应业务方案见
|
|
5
|
+
[日志治理方案](./log-governance-plan.md)。
|
|
6
|
+
|
|
7
|
+
## 当前调用链
|
|
8
|
+
|
|
9
|
+
- `packages/shared/src/structured-log.ts` 提供跨服务字段契约与 TypeScript SDK。调用者提交稳定
|
|
10
|
+
`event`、关联 ID、结果、错误码和少量明确允许的数值字段;序列化器生成时间、等级、组件和
|
|
11
|
+
`retentionClass=operational`,只输出白名单字段,并限制单行 2048 UTF-8 字节。任意 message、
|
|
12
|
+
原始 Error、请求内容、Token 与未知字段不会进入记录。
|
|
13
|
+
公共入口 `@southwind-ai/shared/structured-log` 可独立消费日志 SDK;利剑用真实打包候选的
|
|
14
|
+
同一入口验证,保留原 workspace shared,避免同名同版本 package identity 的类型去重冲突。
|
|
15
|
+
字段同时支持 `diagnosticId`、登记的 `route` 模板、`handlerKey/queue/attemptNumber` 及
|
|
16
|
+
有界 `completedUnits/totalUnits/count`。路由不能传实际 URL、query、fragment 或对象路径;
|
|
17
|
+
SDK 做词法校验,调用方负责从框架登记模板取得它。
|
|
18
|
+
- `apps/server/src/observability/structured-logger.ts` 将该契约接入 Pino。
|
|
19
|
+
`platform-bootstrap.ts` 创建同一 logger,HTTP 请求完成、启动、审计降级及 Workflow continuation
|
|
20
|
+
的现有日志路径继续使用它。Workflow continuation 只把原始异常字段改为稳定错误码,不改派发和
|
|
21
|
+
错误处理行为。
|
|
22
|
+
- `createLegacyServiceRuntime` 将统一 Logger 注入 `DurableJobWorker`;每次实际 claim 记录
|
|
23
|
+
`job.attempt.started` 和唯一的 `job.attempt.completed`,包括未注册 Handler、失败、取消和租约
|
|
24
|
+
丢失。只读 canonical claim 的 Job/阶段/重试/Handler/Queue 坐标,不扫描 payload 或记录租约
|
|
25
|
+
Token。idle 不产生日志。
|
|
26
|
+
- `apps/agent-server/src/southwind_agent_server/structured_logging.py` 为 Python 包级日志安装对等
|
|
27
|
+
适配器。`asgi.py` 保留原 FastAPI app 对象,在生产 ASGI 入口安装 handler;Dockerfile 与开发
|
|
28
|
+
启动命令均指向该入口。恢复轮询和报表写入失败映射为稳定事件/错误码,不输出异常堆栈或任意
|
|
29
|
+
`extra`。Uvicorn 自身 access/error 日志仍由 Uvicorn 配置管理。
|
|
30
|
+
`AgentRunTrace.start/finish` 同时调用 `AgentOperationalAttempt`,记录固定的
|
|
31
|
+
`agent.run.attempt.started/completed`、Run/Trace、耗时及安全结果;幂等重放不重新启动 attempt。
|
|
32
|
+
进程停止等非终态结束标为 interrupted,不能伪装成 canonical Run failed。详细输入输出仍由
|
|
33
|
+
Run/Artifact 受管查询负责。
|
|
34
|
+
|
|
35
|
+
业务审计继续走 `system.audit` 的 Manifest 与 Audit Port;Agent Run/Provider/Workflow 的持久
|
|
36
|
+
事实仍由各自 canonical store 维护。运行日志只用于排障,不能替代这两类记录。
|
|
37
|
+
|
|
38
|
+
## 利剑候选消费与四条链
|
|
39
|
+
|
|
40
|
+
利剑先 fetch 并 fast-forward `origin/main` 至 `3a51d6edf`,再创建
|
|
41
|
+
`rayjason/logging-integration`。变更仅在日志、Host 装配、候选依赖和验证文档;矛盾分析和聊天
|
|
42
|
+
记录分析的业务文件没有修改。利剑方案在 `docs/architecture/logging-integration-plan.md`,
|
|
43
|
+
最终实现和证据在 `docs/operations/windy-logging-integration.md`。
|
|
44
|
+
|
|
45
|
+
| 链路 | 当前本地联调证据 | 业务结果 |
|
|
46
|
+
| ------------ | -------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
47
|
+
| HTTP 写请求 | 真实 `createPlatformHttpApp` / Elysia 生命周期与候选 SDK/Pino | 一次完成记录、请求关联、正文和身份明细不入日志 |
|
|
48
|
+
| 慢请求与失败 | 慢 GET、错误诊断 Hook 和完成去重 | 保留阈值采样、诊断编号与登记路由模板 |
|
|
49
|
+
| Durable Job | 真实 Worker + 隔离内存 Repository | 开始、进度、终态按 Job/阶段/重试串联;Sink 故障不改状态机 |
|
|
50
|
+
| Agent | 利剑 Remote Runtime 到本地 HTTP 协议 fixture;独立安装的 Python 候选 | Host 请求到 Trace/Run 的关联、当前进程采样去重、canonical 结果保持 |
|
|
51
|
+
|
|
52
|
+
利剑 `ObservedAgentRuntime` 的终态观测最多保留 1000 个 Run,仅用于进程内采样,重启或淘汰
|
|
53
|
+
后可能再次观测同一 Run;它不承诺全局 exactly-once。Run store 和业务审计仍是权威事实。
|
|
54
|
+
本地 fixture 不调用真实模型 Provider,也没有写入生产案件数据。
|
|
55
|
+
|
|
56
|
+
## 验证与限制
|
|
57
|
+
|
|
58
|
+
- Bun 定向测试:共享契约、Server Pino 输出与 HTTP 观测共 8 项通过,包含 Sink 故障不影响业务。
|
|
59
|
+
- Agent Server Python 定向测试:4 项通过,包含日志输出失败不影响 Run;ASGI 导入确认复用原
|
|
60
|
+
app 对象。
|
|
61
|
+
- Server/Shared 类型检查及相关格式、Lint 检查通过;提交钩子的工作区类型检查作为静态证据。
|
|
62
|
+
- SDK 新字段及 Job 旁路定向检查各 4 项通过;Agent 日志、真实 ASGI Run、坏 Sink 与幂等重放
|
|
63
|
+
共 11 项 Python 定向检查通过,Ruff/Mypy 通过。
|
|
64
|
+
平台最终合并的 SDK/Job/Pino/HTTP 定向检查共 12 项通过。
|
|
65
|
+
- Python 候选源码 `8c045684` 用 `uv build apps/agent-server --wheel` 打包,安装到独立 venv,
|
|
66
|
+
依赖由 `uv export --locked --no-dev --no-emit-project` 固定。`scripts/agent-log-wheel-consumer.py`
|
|
67
|
+
用独立解释器的 `-I` 模式在仓库外执行,验证导入来自 site-packages,正常和坏 stdout 两种
|
|
68
|
+
场景均完成真实 ASGI Run、canonical 结果、幂等重放、关联/脱敏与大小预算。
|
|
69
|
+
该 wheel SHA256 为 `8fd5bbb3fb5f6d92cc6c4dd9d7237df8c3dd1d9afe425448c0b61f7997ad2a95`。
|
|
70
|
+
|
|
71
|
+
利剑消费 SDK 源码 `11df0eb4`,归档 SHA256 为
|
|
72
|
+
`aa48ef04927edd9245ce7dde2b6a6415d35ac4c911c3535fd10192f859010e3b`。实际 Elysia/Worker/Remote
|
|
73
|
+
协议链及既有授权定向检查 26 项通过;交付构建输入/增量角色映射检查 22 项通过。候选包的
|
|
74
|
+
本地依赖必须在 Docker 的 frozen install 前复制,Server/Web/Seeder 已补齐该输入及 SDK 导入。
|
|
75
|
+
容器依赖层安装和已有静态失败在利剑最终文档分别记录。
|
|
76
|
+
候选 SDK 用真实本地归档消费,没有改 `.windy` 元数据或手改版本号冒充正式升级。
|
|
77
|
+
|
|
78
|
+
本轮完成日志契约与四条真实适配链的本地定向联调;未完成真实 Provider/完整 Agent 部署、正式
|
|
79
|
+
Windy 发布和 `windy update` 事务、生产采集留存、集中查询或告警验收。这些不能由定向检查替代。
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# 日志治理方案
|
|
2
|
+
|
|
3
|
+
状态:运行日志 SDK、平台适配及利剑四条链的本地定向联调已实现;正式发布、下游升级与部署留存
|
|
4
|
+
另行验收。集中管理界面尚未实施。
|
|
5
|
+
|
|
6
|
+
## 业务需求先行
|
|
7
|
+
|
|
8
|
+
| 场景 | 使用者与问题 | 权威记录 | 成功标准 |
|
|
9
|
+
| -------- | --------------------------------------------- | ------------------------ | -------------------------------------- |
|
|
10
|
+
| 故障定位 | 运维定位写请求、慢请求、Job 或 Agent Run 失败 | 有界结构化运行日志与指标 | 用关联 ID 找到失败阶段,不暴露敏感输入 |
|
|
11
|
+
| 责任追溯 | 审计人员确认谁在什么权限下执行了业务动作 | `system.audit` 审计事实 | 权限控制、完整性和导出可验证 |
|
|
12
|
+
| 合规取证 | 管理人员按部署约定查询、留存和导出 | 审计事实及受管保留策略 | 保留期、访问授权和交付责任明确 |
|
|
13
|
+
|
|
14
|
+
## 模块边界
|
|
15
|
+
|
|
16
|
+
`system.audit` 已有 Manifest、Module Audit Port、可靠写入、权限控制及管理端查询/导出。
|
|
17
|
+
业务/安全审计继续由它维护;普通 stdout、HTTP 访问日志和 Agent Trace 不写成它的审计事实。
|
|
18
|
+
Agent Run/Provider/Workflow 仍保留各自 canonical 事实,借关联 ID 串联。
|
|
19
|
+
|
|
20
|
+
Windy 平台层提供结构化运行日志契约和 TypeScript SDK;Python Agent Server 使用对等适配器。
|
|
21
|
+
它们统一 `timestamp`、`level`、`component`、`event`、`requestId`、`runId`、`jobId`、
|
|
22
|
+
`traceId`、`outcome` 和稳定错误码,限定字段白名单、脱敏、采样、大小预算与 retention 分类。
|
|
23
|
+
日志内容不成为业务授权来源,也不替代 Trace Event。对客户数据、密钥、模型输入输出、Tool 参数、
|
|
24
|
+
stdout/stderr 采用默认不记录的规则;确需详查时走当前权限复核和有 TTL 的受管 payload/Artifact。
|
|
25
|
+
|
|
26
|
+
集中采集、索引、备份和保留由部署层管理。只有在运维确有统一查询、告警配置和多租户隔离的
|
|
27
|
+
需求与责任人时,新增独立可选 `system.observability` module,提供受权的控制面和查询代理;
|
|
28
|
+
不把日志引擎塞进 `system.audit`,也不复制一份日志事实数据库。
|
|
29
|
+
|
|
30
|
+
## 迁移与验证
|
|
31
|
+
|
|
32
|
+
1. 清点 Windy 的 Server Pino/HTTP、Job、Agent 日志及利剑已有写请求、慢请求和 Docker 轮转字段。
|
|
33
|
+
2. 固定跨语言 schema、上下文传播、等级/采样、脱敏和稳定错误码;SDK 只负责产出,不控制业务审计。
|
|
34
|
+
3. 对接 Windy 后,用利剑的四条真实链路做兼容映射:写请求、慢请求、Durable Job、Agent Run。
|
|
35
|
+
检查敏感字段泄漏、重复事件、丢关联 ID 和日志系统不可用时业务是否按既定审计策略处理。
|
|
36
|
+
4. 和 Agent 平台候选共同发布;利剑在官方更新事务中升级,分别验收排障、审计导出和部署保留。
|
|
37
|
+
|
|
38
|
+
集中管理平台是否实施以这些业务验收后的缺口决定。当前任务状态见
|
|
39
|
+
[上游任务清单](https://github.com/RayJason/windy-platform/blob/main/docs/product/tasks.md)。
|
|
@@ -29,14 +29,41 @@ Web、Server、License 和 Signing 接受 `x-request-id`。只保留 1 至 128
|
|
|
29
29
|
|
|
30
30
|
## 结构化日志
|
|
31
31
|
|
|
32
|
-
应用启动、运行模式警告和 HTTP 完成事件使用单行 JSON。
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
32
|
+
应用启动、运行模式警告和 HTTP 完成事件使用单行 JSON。Server 使用
|
|
33
|
+
`@southwind-ai/shared` 的结构化日志契约清洗字段,再交给 Pino 输出;Agent Server
|
|
34
|
+
生产 ASGI 入口使用同一契约的 Python 适配器处理运行时异常。单行上限为 2048 UTF-8
|
|
35
|
+
字节,自由文本 message 和原始异常不会输出。`retentionClass=operational` 表示排障日志,
|
|
36
|
+
不赋予它审计事实或六个月留存语义。
|
|
37
|
+
|
|
38
|
+
通用稳定字段是 `timestamp`、`level`、`component`、`event`、`retentionClass`;
|
|
39
|
+
可选 `requestId`、`runId`、`jobId`、`traceId`、`diagnosticId`、`outcome`、`errorCode` 和
|
|
40
|
+
`stageKey`。关联 ID 只接受 1 至 128 位安全字符,不合法就省略,不猜测或生成假的关联。
|
|
41
|
+
`event`、`errorCode` 和阶段只接受有界稳定 Key。未列字段包括 body、headers、Token、
|
|
42
|
+
Prompt、Tool 参数和原始 Error 均被丢弃;超出大小预算时先丢弃次要关联字段。
|
|
43
|
+
运行日志输出失败时由 SDK/适配器吞掉写出异常,不改变原请求、Run 或审计结果;
|
|
44
|
+
日志丢失需由部署层的采集健康和告警发现。
|
|
45
|
+
HTTP 完成事件另外允许:
|
|
46
|
+
|
|
47
|
+
- `method`、`statusCode`、`durationMs`;
|
|
48
|
+
- `route` 只允许框架登记的模板,由调用方提供;SDK 拒绝 query、fragment、编码或纯数字路径段,
|
|
49
|
+
不以词法校验代替模板来源保证;
|
|
36
50
|
- Server 可额外记录粗粒度 `actorType`,不记录用户或资源 ID。
|
|
37
51
|
|
|
38
52
|
请求完成日志禁止记录完整 URL、query、请求或响应 body、Cookie、Authorization、Secret、连接串和原始异常。需要增加字段时,必须先判断其基数和敏感性,并补充防泄漏测试。生产日志采集器应按 JSON 字段解析,按 `requestId` 关联,不依赖自由文本。
|
|
39
53
|
|
|
54
|
+
Agent Server 的生产入口为 `southwind_agent_server.asgi:app`,它不改动 FastAPI
|
|
55
|
+
应用对象,而是在现有包级 Python logging 上安装结构化输出。当前恢复轮询和报表写入失败
|
|
56
|
+
映射到稳定事件/错误码;报表失败可携带合法的 `runId` 与 `stageKey`。实际执行的
|
|
57
|
+
`AgentRunTrace` 同时发出 `agent.run.attempt.started/completed`、Run/Trace、耗时与安全结果,
|
|
58
|
+
幂等重放不重发,非终态执行中断标记 interrupted。异常堆栈、
|
|
59
|
+
Python `extra` 中的任意字段和报表正文都不进入 stdout。直接使用旧的
|
|
60
|
+
`southwind_agent_server.app:app` 入口不会安装该适配器。
|
|
61
|
+
|
|
62
|
+
后台 Durable Worker 每次实际 claim 发出 `job.attempt.started/completed`,字段只取 canonical
|
|
63
|
+
`jobId/stageKey/attemptNumber/handlerKey/queue`;失败只记录稳定码。`completedUnits/totalUnits/count`
|
|
64
|
+
支持进度或清理计数,均限制为 0 至 10 亿的整数。日志不记录 payload、租约 Token 或进度正文,
|
|
65
|
+
也不从业务 payload 猜测关联 ID;Worker idle 不产生日志。
|
|
66
|
+
|
|
40
67
|
请求观测在全局 `onRequest` 建立,在响应完成时原子取出并结束。未匹配路由、Guard
|
|
41
68
|
拒绝和 handler 异常与正常请求遵循同一生命周期;即使框架同时触发 error 与
|
|
42
69
|
after-response 路径,同一个请求也只能扣减一次活跃计数并写一条完成日志。业务路由
|
|
@@ -58,5 +85,10 @@ after-response 路径,同一个请求也只能扣减一次活跃计数并写
|
|
|
58
85
|
Server/License 请求完成日志字段、Web 到 Server 的标识透传,以及 Server 未匹配路由、
|
|
59
86
|
异常路由只完成一次观测且不会终止进程,均有 Bun 单元或边界测试;工作区 typecheck 和
|
|
60
87
|
Oxlint 已通过。Dockerfile 已声明 readiness `HEALTHCHECK`。
|
|
88
|
+
新增结构化日志切片的共享契约、Pino 输出和 HTTP 观测定向测试共 8 项通过;Agent Server
|
|
89
|
+
Python 适配定向测试 4 项通过,ASGI 导入复用原 app 对象。完整记录见
|
|
90
|
+
[结构化运行日志技术实现](../architecture/log-governance-implementation.md)。
|
|
91
|
+
利剑四条真实适配链已完成候选包本地定向联调;平台 Job 定向检查 4 项、Agent 日志/ASGI
|
|
92
|
+
检查 11 项及独立 wheel 消费通过。实际 Provider、正式更新事务和部署采集留存仍须独立验收。
|
|
61
93
|
|
|
62
94
|
尚未由本页证明:生产 Compose、新环境部署、备份恢复、升级回滚、原生 Linux amd64 runner、告警规则、长稳运行、多实例和大容量。这些项必须由后续生产恢复演练和目标环境记录单独闭环;Docker Desktop 或 QEMU 上的 amd64 运行不能写成原生 Linux amd64 已认证。
|
|
@@ -96,7 +96,7 @@ bun test apps/server/src/configuration packages/modules/src/system-modules.test.
|
|
|
96
96
|
bun run --cwd apps/web test -- ModuleConfigurationEditor.webtest.ts router/index.webtest.ts
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
`test:db:migrations` 在 fresh PostgreSQL
|
|
99
|
+
`test:db:migrations` 在 fresh PostgreSQL 18 上执行全部正式 migration 并二次执行验证 tracker 指纹不变;`test:db:config` 使用真实 PostgreSQL 验证并发版本分配、生效替代、失败状态、Repository 重建后的恢复、回滚新版本和 Secret marker 不落库。
|
|
100
100
|
|
|
101
101
|
## 验证
|
|
102
102
|
|
|
@@ -36,7 +36,7 @@ Windy V1 只允许一个未删除根组织。`0037_absent_nick_fury.sql` 通过
|
|
|
36
36
|
|
|
37
37
|
管理端处置部门使用 `POST /api/system/departments/:id/transition`。请求明确给出 `disable` 或 `delete`,存在关联数据时还须提供启用的迁移目标;Server 会拒绝当前节点和其下级作为目标,然后把直接下级的 `parentId`、用户主部门和角色 `departmentIds` 一起迁移,再完成停用或软删除。操作写入 `organization.department.transition` 审计事件,元数据只记录动作、目标 ID 和迁移计数。
|
|
38
38
|
|
|
39
|
-
用户、角色和部门写操作共享同一个组织 mutation coordinator。无数据库开发模式使用进程内串行;PostgreSQL 使用固定 advisory transaction lock,因此多个 Server 实例的互相移动、用户归属写入和部门处置也按同一事实顺序判定。`bun run test:db:organization` 在 PostgreSQL
|
|
39
|
+
用户、角色和部门写操作共享同一个组织 mutation coordinator。无数据库开发模式使用进程内串行;PostgreSQL 使用固定 advisory transaction lock,因此多个 Server 实例的互相移动、用户归属写入和部门处置也按同一事实顺序判定。`bun run test:db:organization` 在 PostgreSQL 18 覆盖唯一根约束、双实例循环竞态和用户归属竞态;`bun run test:db:data-scope` 继续验证组织树调整后数据范围查询不漂移。
|
|
40
40
|
|
|
41
41
|
## 权限边界
|
|
42
42
|
|
|
@@ -115,7 +115,7 @@ Bearer 方案,也不会把内部服务 Token 下发给浏览器。
|
|
|
115
115
|
bun run test:db:auth
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
-
该命令会临时启动 `
|
|
118
|
+
该命令会临时启动 `pgvector/pgvector:pg18`、执行正式 migration、写入使用真实 Argon2id 密码的 fixture,并启动两次正式 Server 进程完成以下验证:
|
|
119
119
|
|
|
120
120
|
- Admin Web 的 `POST /api/auth/login` 响应不返回 Token、只签发 `HttpOnly` Cookie;独立 `POST /api/auth/api/login` 返回 Bearer Token 且不设置 Cookie,数据库在两条路径都只保存密码 hash 和 Session Token 的 SHA-256 hash;
|
|
121
121
|
- Bearer Token 经 Actor Resolver 解析授权快照:`users.role_ids` 只要是数组(包括 `[]`)就是用户角色的 canonical 真相,只有字段为 `NULL` 或非数组旧值时才回退 `user_roles`;对选中的每个启用角色,`roles.permission_keys` 同样只要是数组(包括 `[]`)就覆盖该角色的兼容关系,只有字段缺失时才回退 `role_permissions`;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { AiIsolatedExecutionPolicyV1 } from "./manifest.js";
|
|
3
|
+
import { validateModuleComposition } from "./composition.js";
|
|
4
|
+
import { fixture, operation } from "./ai-operation-composition.test-support.js";
|
|
5
|
+
|
|
6
|
+
const policy: AiIsolatedExecutionPolicyV1 = {
|
|
7
|
+
version: 1,
|
|
8
|
+
mode: "isolated",
|
|
9
|
+
languages: ["python", "node"],
|
|
10
|
+
inputArtifactTypes: ["text/plain", "application/json"],
|
|
11
|
+
network: "forbidden",
|
|
12
|
+
limits: {
|
|
13
|
+
maxWorkspaceBytes: 8_388_608,
|
|
14
|
+
maxFileBytes: 2_097_152,
|
|
15
|
+
maxOutputBytes: 1_048_576,
|
|
16
|
+
maxDurationSeconds: 30,
|
|
17
|
+
maxCpuMillis: 30_000,
|
|
18
|
+
maxMemoryBytes: 268_435_456,
|
|
19
|
+
maxPids: 8,
|
|
20
|
+
maxConcurrentProcesses: 1,
|
|
21
|
+
maxToolCalls: 8,
|
|
22
|
+
maxToolRounds: 4,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe("Agent 隔离执行 Manifest", () => {
|
|
27
|
+
test("缺省关闭;显式策略可通过组合校验", () => {
|
|
28
|
+
const module = fixture("business");
|
|
29
|
+
module.aiOperations = [operation("business.answer")];
|
|
30
|
+
expect(() => validateModuleComposition([module])).not.toThrow();
|
|
31
|
+
module.aiOperations = [
|
|
32
|
+
{
|
|
33
|
+
...module.aiOperations[0]!,
|
|
34
|
+
toolNetworkPolicy: { mode: "forbidden" },
|
|
35
|
+
isolatedExecution: policy,
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
expect(() => validateModuleComposition([module])).not.toThrow();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("未知语言、网络和越界预算在装配时拒绝", () => {
|
|
42
|
+
const module = fixture("business");
|
|
43
|
+
const base = operation("business.answer");
|
|
44
|
+
const check = (candidate: AiIsolatedExecutionPolicyV1) => {
|
|
45
|
+
module.aiOperations = [
|
|
46
|
+
{
|
|
47
|
+
...base,
|
|
48
|
+
toolNetworkPolicy: { mode: "forbidden" },
|
|
49
|
+
isolatedExecution: candidate,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
expect(() => validateModuleComposition([module])).toThrow();
|
|
53
|
+
};
|
|
54
|
+
check({
|
|
55
|
+
...policy,
|
|
56
|
+
languages: [
|
|
57
|
+
"ruby",
|
|
58
|
+
] as unknown as AiIsolatedExecutionPolicyV1["languages"],
|
|
59
|
+
});
|
|
60
|
+
check({
|
|
61
|
+
...policy,
|
|
62
|
+
network: "allowed" as AiIsolatedExecutionPolicyV1["network"],
|
|
63
|
+
});
|
|
64
|
+
check({
|
|
65
|
+
...policy,
|
|
66
|
+
limits: { ...policy.limits, maxMemoryBytes: 2_147_483_648 },
|
|
67
|
+
});
|
|
68
|
+
check({ ...policy, limits: { ...policy.limits, maxToolRounds: 9 } });
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ModuleAiOperationDefinition } from "./manifest.js";
|
|
2
|
+
|
|
3
|
+
const ceilings = {
|
|
4
|
+
maxWorkspaceBytes: 256 * 1024 * 1024,
|
|
5
|
+
maxFileBytes: 64 * 1024 * 1024,
|
|
6
|
+
maxOutputBytes: 16 * 1024 * 1024,
|
|
7
|
+
maxDurationSeconds: 300,
|
|
8
|
+
maxCpuMillis: 300_000,
|
|
9
|
+
maxMemoryBytes: 1024 * 1024 * 1024,
|
|
10
|
+
maxPids: 64,
|
|
11
|
+
maxConcurrentProcesses: 4,
|
|
12
|
+
maxToolCalls: 128,
|
|
13
|
+
maxToolRounds: 32,
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export function validateAiIsolatedExecutionPolicy(
|
|
17
|
+
operation: ModuleAiOperationDefinition,
|
|
18
|
+
): void {
|
|
19
|
+
const policy = operation.isolatedExecution;
|
|
20
|
+
if (policy === undefined) return;
|
|
21
|
+
if (
|
|
22
|
+
!policy ||
|
|
23
|
+
operation.kind !== "agent" ||
|
|
24
|
+
policy.version !== 1 ||
|
|
25
|
+
policy.mode !== "isolated" ||
|
|
26
|
+
Object.keys(policy).sort().join(",") !==
|
|
27
|
+
"inputArtifactTypes,languages,limits,mode,network,version"
|
|
28
|
+
) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`${operation.key} 隔离执行只允许 Agent Operation 的 V1 策略`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
if (
|
|
34
|
+
!Array.isArray(policy.languages) ||
|
|
35
|
+
!policy.languages.length ||
|
|
36
|
+
new Set(policy.languages).size !== policy.languages.length ||
|
|
37
|
+
policy.languages.some((language) => !["python", "node"].includes(language))
|
|
38
|
+
) {
|
|
39
|
+
throw new Error(`${operation.key} 隔离执行语言无效`);
|
|
40
|
+
}
|
|
41
|
+
if (
|
|
42
|
+
!Array.isArray(policy.inputArtifactTypes) ||
|
|
43
|
+
!policy.inputArtifactTypes.length ||
|
|
44
|
+
new Set(policy.inputArtifactTypes).size !==
|
|
45
|
+
policy.inputArtifactTypes.length ||
|
|
46
|
+
policy.inputArtifactTypes.some(
|
|
47
|
+
(type) =>
|
|
48
|
+
typeof type !== "string" ||
|
|
49
|
+
!/^[a-z][a-z0-9.+-]*\/[a-z0-9.+-]+$/u.test(type),
|
|
50
|
+
)
|
|
51
|
+
) {
|
|
52
|
+
throw new Error(`${operation.key} 隔离执行输入 Artifact 类型无效`);
|
|
53
|
+
}
|
|
54
|
+
if (
|
|
55
|
+
policy.network !== "forbidden" ||
|
|
56
|
+
operation.toolNetworkPolicy?.mode !== "forbidden"
|
|
57
|
+
) {
|
|
58
|
+
throw new Error(`${operation.key} 隔离执行必须禁止 Tool 网络`);
|
|
59
|
+
}
|
|
60
|
+
const limits = policy.limits;
|
|
61
|
+
if (
|
|
62
|
+
!limits ||
|
|
63
|
+
Object.keys(limits).length !== Object.keys(ceilings).length ||
|
|
64
|
+
Object.entries(ceilings).some(([key, ceiling]) => {
|
|
65
|
+
const value = limits[key as keyof typeof ceilings];
|
|
66
|
+
return !Number.isSafeInteger(value) || value <= 0 || value > ceiling;
|
|
67
|
+
}) ||
|
|
68
|
+
limits.maxFileBytes > limits.maxWorkspaceBytes ||
|
|
69
|
+
limits.maxOutputBytes > limits.maxWorkspaceBytes ||
|
|
70
|
+
limits.maxDurationSeconds > operation.limits.maxDurationSeconds ||
|
|
71
|
+
limits.maxOutputBytes > operation.limits.maxOutputBytes ||
|
|
72
|
+
limits.maxToolRounds > limits.maxToolCalls
|
|
73
|
+
) {
|
|
74
|
+
throw new Error(`${operation.key} 隔离执行预算越过 Operation 或 Host 上限`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -6,6 +6,7 @@ import { validateAiNetworkPolicy } from "./ai-network-policy.js";
|
|
|
6
6
|
import { validateAiOperationReadinessContract } from "./ai-operation-readiness-contract.js";
|
|
7
7
|
import { validateAiScreenshotContext } from "./ai-screenshot-context.js";
|
|
8
8
|
import { validateClientExecution } from "./ai-client-execution-policy.js";
|
|
9
|
+
import { validateAiIsolatedExecutionPolicy } from "./ai-isolated-execution-policy.js";
|
|
9
10
|
|
|
10
11
|
interface AiOperationCatalogs {
|
|
11
12
|
permissions: ReadonlySet<string>;
|
|
@@ -107,6 +108,7 @@ function validateOperation(
|
|
|
107
108
|
validateAiNetworkPolicy(operation);
|
|
108
109
|
validateProviderDataPolicy(operation);
|
|
109
110
|
validateClientExecution(operation);
|
|
111
|
+
validateAiIsolatedExecutionPolicy(operation);
|
|
110
112
|
validateLimits(operation);
|
|
111
113
|
validateAiOperationReadinessContract(operation);
|
|
112
114
|
validateAiScreenshotContext(operation);
|
|
@@ -167,6 +167,27 @@ export interface AiOperationLimits {
|
|
|
167
167
|
maxPixelsPerArtifact?: number;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
/** Opt-in policy for a future, separate Python/Node Execution Runtime. Absence disables execution. */
|
|
171
|
+
export interface AiIsolatedExecutionPolicyV1 {
|
|
172
|
+
readonly version: 1;
|
|
173
|
+
readonly mode: "isolated";
|
|
174
|
+
readonly languages: readonly ("python" | "node")[];
|
|
175
|
+
readonly inputArtifactTypes: readonly string[];
|
|
176
|
+
readonly network: "forbidden";
|
|
177
|
+
readonly limits: {
|
|
178
|
+
readonly maxWorkspaceBytes: number;
|
|
179
|
+
readonly maxFileBytes: number;
|
|
180
|
+
readonly maxOutputBytes: number;
|
|
181
|
+
readonly maxDurationSeconds: number;
|
|
182
|
+
readonly maxCpuMillis: number;
|
|
183
|
+
readonly maxMemoryBytes: number;
|
|
184
|
+
readonly maxPids: number;
|
|
185
|
+
readonly maxConcurrentProcesses: number;
|
|
186
|
+
readonly maxToolCalls: number;
|
|
187
|
+
readonly maxToolRounds: number;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
170
191
|
/**
|
|
171
192
|
* 允许受治理的宿主客户端在本地编排模型循环。
|
|
172
193
|
*
|
|
@@ -234,6 +255,8 @@ export interface ModuleAiOperationDefinition {
|
|
|
234
255
|
providerDataPolicy?: AiProviderDataPolicy;
|
|
235
256
|
/** 缺省不允许浏览器或其它宿主客户端执行本地 Agent 循环。 */
|
|
236
257
|
clientExecution?: AiClientExecutionPolicy;
|
|
258
|
+
/** Missing means no workspace or process execution capability. */
|
|
259
|
+
isolatedExecution?: AiIsolatedExecutionPolicyV1;
|
|
237
260
|
toolNetworkPolicy?: AiToolNetworkPolicy;
|
|
238
261
|
limits: AiOperationLimits;
|
|
239
262
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as shared from "@southwind-ai/shared";
|
|
2
2
|
import type { ModuleManifest } from "./manifest.js";
|
|
3
3
|
|
|
4
4
|
// @windy-module system.scheduler begin
|
|
5
|
-
const standardAndAdvanced = ["standard", "advanced"].map(
|
|
5
|
+
const standardAndAdvanced = ["standard", "advanced"].map(
|
|
6
|
+
shared.licenseVersionKey,
|
|
7
|
+
);
|
|
6
8
|
// @windy-module system.scheduler end
|
|
7
|
-
const advancedOnly = [licenseVersionKey("advanced")];
|
|
8
9
|
|
|
9
10
|
export const systemFeatures: ModuleManifest["features"] = [
|
|
10
11
|
feature(
|
|
@@ -109,7 +110,7 @@ export const systemFeatures: ModuleManifest["features"] = [
|
|
|
109
110
|
"Agent 长期会话",
|
|
110
111
|
"恢复受治理的长期 Session,并解释 Context 与 Memory 来源。",
|
|
111
112
|
),
|
|
112
|
-
dependencies: [featureKey("system.agent")],
|
|
113
|
+
dependencies: [shared.featureKey("system.agent")],
|
|
113
114
|
},
|
|
114
115
|
{
|
|
115
116
|
...feature(
|
|
@@ -117,7 +118,7 @@ export const systemFeatures: ModuleManifest["features"] = [
|
|
|
117
118
|
"AI Runtime 报表",
|
|
118
119
|
"查看受数据范围约束的 Agent Run、模型用量、缓存与失败事实。",
|
|
119
120
|
),
|
|
120
|
-
dependencies: [featureKey("system.agent")],
|
|
121
|
+
dependencies: [shared.featureKey("system.agent")],
|
|
121
122
|
},
|
|
122
123
|
{
|
|
123
124
|
...feature(
|
|
@@ -125,8 +126,8 @@ export const systemFeatures: ModuleManifest["features"] = [
|
|
|
125
126
|
"Agent Workflow",
|
|
126
127
|
"查看和运行具备预算、恢复、委派与持久审计边界的不可变 Agent Workflow Release。",
|
|
127
128
|
),
|
|
128
|
-
dependencies: [featureKey("system.agent")],
|
|
129
|
-
allowedLicenseVersions:
|
|
129
|
+
dependencies: [shared.featureKey("system.agent")],
|
|
130
|
+
allowedLicenseVersions: [shared.licenseVersionKey("advanced")],
|
|
130
131
|
},
|
|
131
132
|
// @windy-module system.agent end
|
|
132
133
|
// @windy-module system.operations begin
|
|
@@ -167,7 +168,7 @@ export const systemFeatures: ModuleManifest["features"] = [
|
|
|
167
168
|
"批量数据任务",
|
|
168
169
|
"受控的大批量导入、导出、失败明细和结果留存。",
|
|
169
170
|
),
|
|
170
|
-
allowedLicenseVersions:
|
|
171
|
+
allowedLicenseVersions: [shared.licenseVersionKey("advanced")],
|
|
171
172
|
},
|
|
172
173
|
// @windy-module system.bulk-data end
|
|
173
174
|
];
|
|
@@ -178,7 +179,7 @@ function feature(
|
|
|
178
179
|
description?: string,
|
|
179
180
|
): ModuleManifest["features"][number] {
|
|
180
181
|
return {
|
|
181
|
-
key: featureKey(key),
|
|
182
|
+
key: shared.featureKey(key),
|
|
182
183
|
label,
|
|
183
184
|
module: "system",
|
|
184
185
|
description,
|
|
@@ -195,6 +196,6 @@ function rbacFeature(
|
|
|
195
196
|
): ModuleManifest["features"][number] {
|
|
196
197
|
return {
|
|
197
198
|
...feature(key, label, description),
|
|
198
|
-
dependencies: [featureKey("system.rbac")],
|
|
199
|
+
dependencies: [shared.featureKey("system.rbac")],
|
|
199
200
|
};
|
|
200
201
|
}
|
|
@@ -17,6 +17,7 @@ export * from "./src/migration-bundle-validation.js";
|
|
|
17
17
|
export * from "./src/mcp.js";
|
|
18
18
|
export * from "./src/module-configuration.js";
|
|
19
19
|
export * from "./src/correlation.js";
|
|
20
|
+
export * from "./src/structured-log.js";
|
|
20
21
|
export * from "./src/notification.js";
|
|
21
22
|
export * from "./src/password.js";
|
|
22
23
|
export * from "./src/platform-settings.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@southwind-ai/shared",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"typecheck": "tsc --noEmit --project tsconfig.json"
|
|
16
16
|
},
|
|
17
17
|
"exports": {
|
|
18
|
-
".": "./index.ts"
|
|
18
|
+
".": "./index.ts",
|
|
19
|
+
"./structured-log": "./src/structured-log.ts"
|
|
19
20
|
},
|
|
20
21
|
"publishConfig": {
|
|
21
22
|
"access": "public"
|