create-windy 0.2.32 → 0.2.34

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 (94) hide show
  1. package/dist/cli.js +9 -1
  2. package/package.json +1 -1
  3. package/template/.agents/skills/windy-business-module/SKILL.md +10 -1
  4. package/template/.windy-template.json +2 -2
  5. package/template/AGENTS.md +11 -0
  6. package/template/apps/agent-server/README.md +10 -0
  7. package/template/apps/agent-server/src/southwind_agent_server/app.py +62 -1
  8. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +35 -0
  9. package/template/apps/agent-server/src/southwind_agent_server/engine.py +2 -0
  10. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +31 -3
  11. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +22 -9
  12. package/template/apps/agent-server/src/southwind_agent_server/provider_audit.py +144 -0
  13. package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +227 -0
  14. package/template/apps/agent-server/src/southwind_agent_server/service.py +1 -0
  15. package/template/apps/agent-server/tests/test_api.py +16 -1
  16. package/template/apps/agent-server/tests/test_openai_provider.py +79 -0
  17. package/template/apps/agent-server/tests/test_provider_governance.py +215 -0
  18. package/template/apps/server/src/agent/provider-input-governance.test.ts +82 -0
  19. package/template/apps/server/src/agent/provider-input-governance.ts +140 -0
  20. package/template/apps/server/src/agent/remote-runtime.test.ts +22 -0
  21. package/template/apps/server/src/agent/remote-runtime.ts +1 -0
  22. package/template/apps/server/src/agent/run-contracts.ts +2 -0
  23. package/template/apps/server/src/agent/run-facade.test.ts +52 -2
  24. package/template/apps/server/src/agent/run-facade.ts +36 -2
  25. package/template/apps/server/src/index.ts +28 -45
  26. package/template/apps/server/src/module-host/initialize-contracts.ts +6 -0
  27. package/template/apps/server/src/module-host/initialize.ts +30 -44
  28. package/template/apps/server/src/module-host/request-context.ts +23 -17
  29. package/template/apps/server/src/module-host/resource-file-policies.test.ts +197 -0
  30. package/template/apps/server/src/module-host/resource-file-policies.ts +108 -0
  31. package/template/apps/server/src/module-host/route-declaration.ts +45 -0
  32. package/template/apps/server/src/module-host/route-installer.ts +27 -3
  33. package/template/apps/server/src/module-host/storage-port.test.ts +170 -1
  34. package/template/apps/server/src/module-host/storage-port.ts +134 -1
  35. package/template/apps/server/src/module-host/storage-scope.ts +96 -0
  36. package/template/apps/server/src/module-host/tool-handlers.test.ts +305 -0
  37. package/template/apps/server/src/module-host/tool-handlers.ts +38 -4
  38. package/template/apps/server/src/module-host/user-directory-composition.test.ts +126 -0
  39. package/template/apps/server/src/module-host/user-directory-port.test.ts +238 -0
  40. package/template/apps/server/src/module-host/user-directory-port.ts +143 -0
  41. package/template/apps/server/src/module-host.ts +4 -0
  42. package/template/apps/server/src/runtime-development.ts +1 -0
  43. package/template/apps/server/src/settings/drizzle-watermark-repository.integration.test.ts +4 -0
  44. package/template/apps/server/src/settings/drizzle-watermark-repository.ts +6 -0
  45. package/template/apps/server/src/settings/watermark-routes.test.ts +13 -3
  46. package/template/apps/server/src/settings/watermark-routes.ts +41 -3
  47. package/template/apps/server/src/system/built-in-roles.ts +1 -0
  48. package/template/apps/web/src/app/auth/login-page.ts +4 -0
  49. package/template/apps/web/src/composables/useLoginFlow.webtest.ts +91 -0
  50. package/template/apps/web/src/composables/useWatermarkSettings.ts +7 -3
  51. package/template/apps/web/src/composables/useWatermarkSettings.webtest.ts +14 -0
  52. package/template/apps/web/src/composables/watermark-settings.ts +131 -9
  53. package/template/apps/web/src/composables/watermark-settings.webtest.ts +73 -7
  54. package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +3 -1
  55. package/template/apps/web/src/layout/GlobalWatermark.vue +24 -11
  56. package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +25 -11
  57. package/template/apps/web/src/pages/auth/login-page-extension.ts +9 -0
  58. package/template/apps/web/src/pages/auth/login-page-extension.webtest.ts +10 -0
  59. package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +102 -4
  60. package/template/apps/web/src/router/index.ts +2 -2
  61. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +3 -2
  62. package/template/docker-compose.yml +2 -0
  63. package/template/docs/architecture/ai-egress.md +49 -7
  64. package/template/docs/architecture/ai-runtime.md +20 -1
  65. package/template/docs/architecture/data-scope-query-enforcement.md +4 -0
  66. package/template/docs/architecture/object-storage.md +22 -2
  67. package/template/docs/development/custom-login-page.md +90 -0
  68. package/template/docs/platform/agent-runtime.md +44 -1
  69. package/template/docs/platform/business-user-directory.md +82 -0
  70. package/template/docs/platform/platform-shell-settings.md +14 -9
  71. package/template/docs/platform/web-system-crud.md +1 -1
  72. package/template/packages/database/drizzle/0034_funny_mesmero.sql +5 -0
  73. package/template/packages/database/drizzle/meta/0034_snapshot.json +7253 -0
  74. package/template/packages/database/drizzle/meta/_journal.json +7 -0
  75. package/template/packages/database/src/schema/platform-settings.ts +13 -0
  76. package/template/packages/modules/package.json +1 -1
  77. package/template/packages/modules/src/ai-operation-composition.test.ts +68 -0
  78. package/template/packages/modules/src/ai-operation-composition.ts +115 -0
  79. package/template/packages/modules/src/manifest.ts +26 -0
  80. package/template/packages/modules/src/provider-data-policy-composition.test.ts +74 -0
  81. package/template/packages/modules/src/system-audit-actions.ts +5 -0
  82. package/template/packages/modules/src/system-features.ts +5 -0
  83. package/template/packages/modules/src/system-module-catalog.test.ts +16 -0
  84. package/template/packages/modules/src/system-module-catalog.ts +26 -3
  85. package/template/packages/modules/src/system-permissions.ts +2 -0
  86. package/template/packages/server-sdk/index.ts +11 -0
  87. package/template/packages/server-sdk/package.json +3 -2
  88. package/template/packages/server-sdk/src/ai-operation-registration.ts +2 -1
  89. package/template/packages/server-sdk/src/module-host.ts +8 -0
  90. package/template/packages/server-sdk/src/request-context.ts +2 -0
  91. package/template/packages/server-sdk/src/storage-port.ts +66 -0
  92. package/template/packages/server-sdk/src/tool-registration.ts +6 -0
  93. package/template/packages/server-sdk/src/user-directory-port.ts +42 -0
  94. package/template/packages/shared/src/platform-settings.ts +17 -1
@@ -239,6 +239,13 @@
239
239
  "when": 1784925586891,
240
240
  "tag": "0033_rich_george_stacy",
241
241
  "breakpoints": true
242
+ },
243
+ {
244
+ "idx": 34,
245
+ "version": "7",
246
+ "when": 1784976420447,
247
+ "tag": "0034_funny_mesmero",
248
+ "breakpoints": true
242
249
  }
243
250
  ]
244
251
  }
@@ -19,11 +19,16 @@ import {
19
19
  // @windy-module system.settings begin
20
20
  import {
21
21
  boolean,
22
+ jsonb as settingsJsonb,
22
23
  pgTable as settingsTable,
23
24
  text,
24
25
  varchar as settingsVarchar,
25
26
  } from "drizzle-orm/pg-core";
26
27
  import { auditColumns } from "./common.js";
28
+ import type {
29
+ WatermarkContentField,
30
+ WatermarkDensity,
31
+ } from "@southwind-ai/shared";
27
32
  // @windy-module system.settings end
28
33
 
29
34
  // @windy-module system.settings begin
@@ -44,6 +49,14 @@ export const platformWatermarkPolicies = settingsTable(
44
49
  id: settingsVarchar("id", { length: 64 }).primaryKey(),
45
50
  enabled: boolean("enabled").notNull(),
46
51
  businessPagesEnabled: boolean("business_pages_enabled").notNull(),
52
+ density: settingsVarchar("density", { length: 16 })
53
+ .$type<WatermarkDensity>()
54
+ .default("medium")
55
+ .notNull(),
56
+ contentFields: settingsJsonb("content_fields")
57
+ .$type<WatermarkContentField[]>()
58
+ .default(["username", "displayName"])
59
+ .notNull(),
47
60
  customContent: settingsVarchar("custom_content", { length: 80 }).notNull(),
48
61
  ...auditColumns,
49
62
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@southwind-ai/modules",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "engines": {
@@ -112,6 +112,7 @@ describe("AI Operation Composition", () => {
112
112
  mode: "configured-endpoint",
113
113
  deploymentKey: "ai.openai-compatible",
114
114
  },
115
+ providerDataPolicy: providerDataPolicy(),
115
116
  toolNetworkPolicy: { mode: "forbidden" },
116
117
  },
117
118
  ];
@@ -133,6 +134,7 @@ describe("AI Operation Composition", () => {
133
134
  mode: "configured-endpoint",
134
135
  deploymentKey: " ",
135
136
  },
137
+ providerDataPolicy: providerDataPolicy(),
136
138
  },
137
139
  ];
138
140
  expect(() => validateModuleComposition([module])).toThrow(
@@ -148,6 +150,48 @@ describe("AI Operation Composition", () => {
148
150
  );
149
151
  });
150
152
 
153
+ test("Provider 字段策略必须完整、唯一且与出网授权绑定", () => {
154
+ const module = fixture("business");
155
+ module.aiOperations = [
156
+ {
157
+ ...operation("business.generate"),
158
+ providerEgress: {
159
+ mode: "configured-endpoint",
160
+ deploymentKey: "ai.openai-compatible",
161
+ },
162
+ },
163
+ ];
164
+ expect(() => validateModuleComposition([module])).toThrow(
165
+ "字段发送 allowlist 缺失",
166
+ );
167
+
168
+ module.aiOperations[0] = {
169
+ ...module.aiOperations[0],
170
+ providerDataPolicy: {
171
+ ...providerDataPolicy(),
172
+ envelopeFields: [
173
+ ...providerDataPolicy().envelopeFields,
174
+ {
175
+ field: "instructions",
176
+ classification: "internal",
177
+ dlpAction: "reject",
178
+ },
179
+ ],
180
+ },
181
+ };
182
+ expect(() => validateModuleComposition([module])).toThrow(
183
+ "字段发送规则重复",
184
+ );
185
+
186
+ module.aiOperations[0] = {
187
+ ...operation("business.generate"),
188
+ providerDataPolicy: providerDataPolicy(),
189
+ };
190
+ expect(() => validateModuleComposition([module])).toThrow(
191
+ "禁止 Provider 出网时不能声明字段发送策略",
192
+ );
193
+ });
194
+
151
195
  test("Operation Handler 必须与 Manifest 声明一一对应", () => {
152
196
  const module = fixture("business");
153
197
  module.aiOperations = [operation("business.generate")];
@@ -190,6 +234,30 @@ function operation(
190
234
  };
191
235
  }
192
236
 
237
+ function providerDataPolicy() {
238
+ return {
239
+ inputFields: [
240
+ {
241
+ jsonPointer: "/question",
242
+ classification: "internal" as const,
243
+ dlpAction: "redact" as const,
244
+ },
245
+ ],
246
+ envelopeFields: [
247
+ {
248
+ field: "instructions" as const,
249
+ classification: "internal" as const,
250
+ dlpAction: "reject" as const,
251
+ },
252
+ {
253
+ field: "user-prompt" as const,
254
+ classification: "internal" as const,
255
+ dlpAction: "redact" as const,
256
+ },
257
+ ],
258
+ };
259
+ }
260
+
193
261
  function fixture(name: string): ModuleManifest {
194
262
  const permissionKey = `${name}.ai.execute`;
195
263
  const featureKey = `${name}.ai`;
@@ -102,9 +102,124 @@ function validateOperation(
102
102
  validateCapabilities(operation);
103
103
  validateTools(operation, catalogs.tools);
104
104
  validateAiNetworkPolicy(operation);
105
+ validateProviderDataPolicy(operation);
105
106
  validateLimits(operation);
106
107
  }
107
108
 
109
+ const providerDataFields = new Set([
110
+ "instructions",
111
+ "user-prompt",
112
+ "tool-definitions",
113
+ "provider-output",
114
+ "tool-results",
115
+ ]);
116
+
117
+ export function validateProviderDataPolicy(
118
+ operation: ModuleAiOperationDefinition,
119
+ ): void {
120
+ const policy = operation.providerDataPolicy;
121
+ if (operation.providerEgress?.mode !== "configured-endpoint") {
122
+ if (policy) {
123
+ throw new Error(
124
+ `${operation.key} 禁止 Provider 出网时不能声明字段发送策略`,
125
+ );
126
+ }
127
+ return;
128
+ }
129
+ if (!policy?.inputFields.length || !policy.envelopeFields.length) {
130
+ throw new Error(`${operation.key} Provider 字段发送 allowlist 缺失`);
131
+ }
132
+ validateInputFields(operation.key, policy.inputFields);
133
+ const fields = new Set<string>();
134
+ for (const rule of policy.envelopeFields) {
135
+ if (!providerDataFields.has(rule.field)) {
136
+ throw new Error(
137
+ `${operation.key} Provider 字段发送 allowlist 含未知字段`,
138
+ );
139
+ }
140
+ if (fields.has(rule.field)) {
141
+ throw new Error(
142
+ `${operation.key} Provider 字段发送规则重复:${rule.field}`,
143
+ );
144
+ }
145
+ fields.add(rule.field);
146
+ if (!["public", "internal", "sensitive"].includes(rule.classification)) {
147
+ throw new Error(`${operation.key} Provider 字段分类无效:${rule.field}`);
148
+ }
149
+ if (!["reject", "redact"].includes(rule.dlpAction)) {
150
+ throw new Error(`${operation.key} Provider DLP 动作无效:${rule.field}`);
151
+ }
152
+ }
153
+ for (const field of ["instructions", "user-prompt"]) {
154
+ if (!fields.has(field)) {
155
+ throw new Error(
156
+ `${operation.key} Provider 字段发送 allowlist 缺少 ${field}`,
157
+ );
158
+ }
159
+ }
160
+ if (operation.allowedToolKeys.length) {
161
+ for (const field of [
162
+ "tool-definitions",
163
+ "provider-output",
164
+ "tool-results",
165
+ ]) {
166
+ if (!fields.has(field)) {
167
+ throw new Error(
168
+ `${operation.key} 使用 Tool 时 Provider 字段发送 allowlist 缺少 ${field}`,
169
+ );
170
+ }
171
+ }
172
+ }
173
+ }
174
+
175
+ function validateInputFields(
176
+ operationKey: string,
177
+ fields: NonNullable<
178
+ ModuleAiOperationDefinition["providerDataPolicy"]
179
+ >["inputFields"],
180
+ ): void {
181
+ const pointers = new Set<string>();
182
+ for (const rule of fields) {
183
+ const segments = rule.jsonPointer
184
+ .slice(1)
185
+ .split("/")
186
+ .map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~"));
187
+ if (
188
+ !rule.jsonPointer.startsWith("/") ||
189
+ rule.jsonPointer === "/" ||
190
+ rule.jsonPointer.endsWith("/") ||
191
+ rule.jsonPointer.includes("//") ||
192
+ /~(?:[^01]|$)/u.test(rule.jsonPointer) ||
193
+ segments.some((segment) =>
194
+ ["__proto__", "prototype", "constructor"].includes(segment),
195
+ )
196
+ ) {
197
+ throw new Error(`${operationKey} Provider 输入字段 JSON Pointer 无效`);
198
+ }
199
+ if (
200
+ [...pointers].some(
201
+ (pointer) =>
202
+ pointer.startsWith(`${rule.jsonPointer}/`) ||
203
+ rule.jsonPointer.startsWith(`${pointer}/`),
204
+ )
205
+ ) {
206
+ throw new Error(`${operationKey} Provider 输入字段规则不能父子重叠`);
207
+ }
208
+ if (pointers.has(rule.jsonPointer)) {
209
+ throw new Error(
210
+ `${operationKey} Provider 输入字段规则重复:${rule.jsonPointer}`,
211
+ );
212
+ }
213
+ pointers.add(rule.jsonPointer);
214
+ if (!["public", "internal", "sensitive"].includes(rule.classification)) {
215
+ throw new Error(`${operationKey} Provider 输入字段分类无效`);
216
+ }
217
+ if (!["reject", "redact"].includes(rule.dlpAction)) {
218
+ throw new Error(`${operationKey} Provider 输入字段 DLP 动作无效`);
219
+ }
220
+ }
221
+ }
222
+
108
223
  function validateCapabilities(operation: ModuleAiOperationDefinition): void {
109
224
  if (!operation.requiredCapabilities.length) {
110
225
  throw new Error(`${operation.key} 至少需要一个 capability`);
@@ -98,6 +98,31 @@ export interface AiToolNetworkPolicy {
98
98
  mode: "forbidden";
99
99
  }
100
100
 
101
+ export type AiProviderDataField =
102
+ | "instructions"
103
+ | "user-prompt"
104
+ | "tool-definitions"
105
+ | "provider-output"
106
+ | "tool-results";
107
+ export type AiProviderDlpAction = "reject" | "redact";
108
+
109
+ export interface AiProviderFieldPolicy {
110
+ field: AiProviderDataField;
111
+ classification: AiOperationDataClassification;
112
+ dlpAction: AiProviderDlpAction;
113
+ }
114
+
115
+ export interface AiProviderInputFieldPolicy {
116
+ jsonPointer: string;
117
+ classification: AiOperationDataClassification;
118
+ dlpAction: AiProviderDlpAction;
119
+ }
120
+
121
+ export interface AiProviderDataPolicy {
122
+ inputFields: readonly AiProviderInputFieldPolicy[];
123
+ envelopeFields: readonly AiProviderFieldPolicy[];
124
+ }
125
+
101
126
  export interface AiOperationLimits {
102
127
  maxDurationSeconds: number;
103
128
  maxInputBytes: number;
@@ -119,6 +144,7 @@ export interface ModuleAiOperationDefinition {
119
144
  /** @deprecated 使用 providerEgress 与 toolNetworkPolicy 拆分两类网络边界。 */
120
145
  publicNetworkAccess?: AiOperationPublicNetworkAccess;
121
146
  providerEgress?: AiProviderEgressPolicy;
147
+ providerDataPolicy?: AiProviderDataPolicy;
122
148
  toolNetworkPolicy?: AiToolNetworkPolicy;
123
149
  limits: AiOperationLimits;
124
150
  }
@@ -0,0 +1,74 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { validateProviderDataPolicy } from "./ai-operation-composition.js";
3
+ import type { ModuleAiOperationDefinition } from "./manifest.js";
4
+
5
+ describe("Provider 输入字段 Schema", () => {
6
+ test.each(["/bad~2escape", "/__proto__/polluted", "/constructor/value"])(
7
+ "拒绝危险或非法 JSON Pointer:%s",
8
+ (jsonPointer) => {
9
+ expect(() =>
10
+ validateProviderDataPolicy(operation([jsonPointer])),
11
+ ).toThrow("JSON Pointer 无效");
12
+ },
13
+ );
14
+
15
+ test("拒绝父子重叠路径,避免规则顺序改变结果", () => {
16
+ expect(() =>
17
+ validateProviderDataPolicy(operation(["/case", "/case/name"])),
18
+ ).toThrow("不能父子重叠");
19
+ });
20
+
21
+ test("使用 Tool 时要求声明定义、上一轮输出和 Tool 结果", () => {
22
+ expect(() =>
23
+ validateProviderDataPolicy(operation(["/question"], ["business.query"])),
24
+ ).toThrow("使用 Tool 时");
25
+ });
26
+ });
27
+
28
+ function operation(
29
+ inputPointers: readonly string[],
30
+ allowedToolKeys: readonly string[] = [],
31
+ ): ModuleAiOperationDefinition {
32
+ return {
33
+ key: "business.answer",
34
+ kind: "agent",
35
+ featureKey: "business.ai",
36
+ permissionKey: "business.ai.execute",
37
+ auditAction: "business.ai.execute",
38
+ inputSchemaKey: "business.input.v1",
39
+ outputSchemaKey: "business.output.v1",
40
+ requiredCapabilities: ["text-generation"],
41
+ allowedToolKeys,
42
+ dataClassification: "internal",
43
+ execution: "streaming",
44
+ providerEgress: {
45
+ mode: "configured-endpoint",
46
+ deploymentKey: "ai.openai-compatible",
47
+ },
48
+ providerDataPolicy: {
49
+ inputFields: inputPointers.map((jsonPointer) => ({
50
+ jsonPointer,
51
+ classification: "internal",
52
+ dlpAction: "redact",
53
+ })),
54
+ envelopeFields: [
55
+ {
56
+ field: "instructions",
57
+ classification: "internal",
58
+ dlpAction: "reject",
59
+ },
60
+ {
61
+ field: "user-prompt",
62
+ classification: "internal",
63
+ dlpAction: "redact",
64
+ },
65
+ ],
66
+ },
67
+ toolNetworkPolicy: { mode: "forbidden" },
68
+ limits: {
69
+ maxDurationSeconds: 60,
70
+ maxInputBytes: 4096,
71
+ maxOutputBytes: 4096,
72
+ },
73
+ };
74
+ }
@@ -8,6 +8,11 @@ export const systemAuditActions: ModuleManifest["auditActions"] = [
8
8
  "crud.create",
9
9
  "crud.update",
10
10
  "crud.delete",
11
+ {
12
+ key: "system.user-directory.query",
13
+ label: "查询业务用户目录",
14
+ description: "业务模块在平台授权与数据范围内查询启用用户最小投影。",
15
+ },
11
16
  // @windy-module system.configuration begin
12
17
  "config.update",
13
18
  "config.rollback",
@@ -17,6 +17,11 @@ export const systemFeatures: ModuleManifest["features"] = [
17
17
  "RBAC 权限链路",
18
18
  "用户、角色、权限点与数据范围的统一访问控制链路。",
19
19
  ),
20
+ feature(
21
+ "system.user-directory",
22
+ "业务用户目录",
23
+ "向获授权业务模块提供受数据范围约束的启用用户最小只读投影。",
24
+ ),
20
25
  feature(
21
26
  "system.profile",
22
27
  "个人设置",
@@ -116,6 +116,22 @@ describe("系统能力模块目录", () => {
116
116
  "system.workflow.timeout-dispatch",
117
117
  ]);
118
118
  expect(identity.dataPolicies).toEqual([]);
119
+ expect(identity.features.map(({ key }) => key)).toContain(
120
+ "system.user-directory",
121
+ );
122
+ expect(identity.permissions.map(({ key }) => key)).toContain(
123
+ "system.user-directory.read",
124
+ );
125
+ expect(
126
+ identity.auditActions.map((action) =>
127
+ typeof action === "string" ? action : action.key,
128
+ ),
129
+ ).toContain("system.user-directory.query");
130
+ expect(
131
+ systemFoundationModule().auditActions.map((action) =>
132
+ typeof action === "string" ? action : action.key,
133
+ ),
134
+ ).toContain("system.user-directory.query");
119
135
  expect(
120
136
  systemModuleManifest("system.data-governance").dataPolicies?.map(
121
137
  ({ key }) => key,
@@ -27,9 +27,17 @@ export const systemModuleDefinitions = [
27
27
  version: "0.1.0",
28
28
  label: "身份与权限",
29
29
  description: "用户、角色、部门、菜单、字典、个人资料和数据范围。",
30
- resources: ["user", "role", "department", "menu", "dict", "profile"],
30
+ resources: [
31
+ "user",
32
+ "user-directory",
33
+ "role",
34
+ "department",
35
+ "menu",
36
+ "dict",
37
+ "profile",
38
+ ],
31
39
  excludedPermissionKeys: ["system.user.reveal", "system.user.export"],
32
- featureKeys: ["system.rbac", "system.profile"],
40
+ featureKeys: ["system.rbac", "system.user-directory", "system.profile"],
33
41
  },
34
42
  // @windy-module system.settings begin
35
43
  {
@@ -226,11 +234,26 @@ function createSystemCapabilityModule(
226
234
  definition.name === "system.data-governance"
227
235
  ? aggregate.dataPolicies
228
236
  : [],
229
- auditActions: isCore ? aggregate.auditActions : [],
237
+ auditActions: isCore
238
+ ? aggregate.auditActions.filter(
239
+ (action) => auditActionKey(action) !== "system.user-directory.query",
240
+ )
241
+ : definition.name === "system.identity"
242
+ ? aggregate.auditActions.filter(
243
+ (action) =>
244
+ auditActionKey(action) === "system.user-directory.query",
245
+ )
246
+ : [],
230
247
  metadata: { kind: "platform-capability" },
231
248
  };
232
249
  }
233
250
 
251
+ function auditActionKey(
252
+ action: ModuleManifest["auditActions"][number],
253
+ ): string {
254
+ return typeof action === "string" ? action : action.key;
255
+ }
256
+
234
257
  function permissionResource(permissionKey: string): string {
235
258
  const parts = permissionKey.split(".");
236
259
  return parts.length >= 3 ? parts.slice(1, -1).join(".") : "";
@@ -3,6 +3,7 @@ import type { ModuleManifest } from "./manifest.js";
3
3
  const systemResourceLabels = {
4
4
  admin: "管理面准入",
5
5
  user: "用户管理",
6
+ "user-directory": "业务用户目录",
6
7
  role: "角色与权限",
7
8
  department: "部门组织",
8
9
  menu: "菜单管理",
@@ -75,6 +76,7 @@ export const systemPermissions: ModuleManifest["permissions"] = [
75
76
  permission("user", "export", "governed-export"),
76
77
  // @windy-module system.data-governance end
77
78
  permission("user", "manage"),
79
+ permission("user-directory", "read"),
78
80
  permission("role", "read"),
79
81
  permission("role", "manage"),
80
82
  permission("department", "read"),
@@ -32,9 +32,20 @@ export type {
32
32
  ModuleSearchQuery,
33
33
  } from "./src/search-provider-port.js";
34
34
  export type {
35
+ ModuleOpenedResourceFile,
36
+ ModuleResourceFileAccessContext,
37
+ ModuleResourceFileAccessPolicy,
38
+ ModuleResourceFileRequest,
35
39
  ModuleStoragePort,
36
40
  ModuleUploadSessionInput,
37
41
  } from "./src/storage-port.js";
42
+ export type {
43
+ ModuleUserDirectoryEntry,
44
+ ModuleUserDirectoryPage,
45
+ ModuleUserDirectoryPort,
46
+ ModuleUserDirectoryQuery,
47
+ } from "./src/user-directory-port.js";
48
+ export { ModuleUserDirectoryError } from "./src/user-directory-port.js";
38
49
  export type {
39
50
  ModuleTaskHandler,
40
51
  ModuleTaskHandlerRegistration,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@southwind-ai/server-sdk",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "engines": {
@@ -21,7 +21,8 @@
21
21
  "src/storage-port.ts",
22
22
  "src/task-registration.ts",
23
23
  "src/tool-registration.ts",
24
- "src/tool-host-port.ts"
24
+ "src/tool-host-port.ts",
25
+ "src/user-directory-port.ts"
25
26
  ],
26
27
  "scripts": {
27
28
  "test": "bun test",
@@ -9,7 +9,8 @@ export interface ModuleAiOperationContext {
9
9
  }
10
10
 
11
11
  /**
12
- * 业务模块只负责把类型化输入收敛为模型无关的指令和用户消息。
12
+ * 业务模块只接收 Manifest `providerDataPolicy.inputFields` allowlist 选出并完成
13
+ * DLP 的最小输入,再将其收敛为模型无关的指令和用户消息。
13
14
  * Provider、Deployment、Secret、Execution Grant 与 Tool 授权均由宿主持有。
14
15
  */
15
16
  export interface ModuleAiOperationPlan {
@@ -3,6 +3,7 @@ import type { ModuleAiOperationHandlerRegistration } from "./ai-operation-regist
3
3
  import type { ModuleRolePreset } from "./role-preset.js";
4
4
  import type { ModuleRouteDefinition } from "./route-definition.js";
5
5
  import type { ModuleSearchProvider } from "./search-provider-port.js";
6
+ import type { ModuleResourceFileAccessPolicy } from "./storage-port.js";
6
7
  import type { ModuleTaskHandlerRegistration } from "./task-registration.js";
7
8
  import type { ModuleToolHandlerRegistration } from "./tool-registration.js";
8
9
 
@@ -25,6 +26,13 @@ export interface ModuleUploadPolicy {
25
26
  export interface ModuleHost {
26
27
  readonly moduleName: string;
27
28
  registerUploadPolicy(policy: ModuleUploadPolicy): void;
29
+ /**
30
+ * 注册跨上传者资源文件读取策略;purpose 必须由本模块或 Manifest 显式依赖模块的
31
+ * Upload Policy 声明,Feature、Permission 与 Audit Action 必须来自当前 Manifest。
32
+ */
33
+ registerResourceFileAccessPolicies(
34
+ policies: readonly ModuleResourceFileAccessPolicy[],
35
+ ): void;
28
36
  registerDurableHandler<T>(definition: DurableHandlerDefinition<T>): void;
29
37
  /** 注册 Guarded API 路由;须与 Manifest `apiPermissions` 声明一致。 */
30
38
  registerRoutes(routes: readonly ModuleRouteDefinition[]): void;
@@ -2,6 +2,7 @@ import type { ModuleActor } from "./actor.js";
2
2
  import type { ModuleAuditEmitter } from "./audit-port.js";
3
3
  import type { ModuleJobSubmitter } from "./jobs-port.js";
4
4
  import type { ModuleStoragePort } from "./storage-port.js";
5
+ import type { ModuleUserDirectoryPort } from "./user-directory-port.js";
5
6
 
6
7
  /**
7
8
  * 模块路由 Handler 的受限请求上下文。
@@ -18,4 +19,5 @@ export interface ModuleRequestContext {
18
19
  readonly audit: ModuleAuditEmitter;
19
20
  readonly jobs: ModuleJobSubmitter;
20
21
  readonly storage: ModuleStoragePort;
22
+ readonly userDirectory: ModuleUserDirectoryPort;
21
23
  }
@@ -1,4 +1,5 @@
1
1
  import type { OpenedFile, UploadSession } from "@southwind-ai/storage";
2
+ import type { ModuleActor } from "./actor.js";
2
3
 
3
4
  export interface ModuleUploadSessionInput {
4
5
  readonly purpose: string;
@@ -9,6 +10,62 @@ export interface ModuleUploadSessionInput {
9
10
  readonly fileExpiresAt?: Date;
10
11
  }
11
12
 
13
+ export interface ModuleResourceFileRequest {
14
+ /** 平台文件元数据 ID;不能是 ArtifactRef、对象存储 Key 或主机路径。 */
15
+ readonly fileId: string;
16
+ /**
17
+ * 必须与本模块或 Manifest 显式依赖模块注册的 Upload Policy,以及当前模块的
18
+ * 资源文件访问策略完全一致。
19
+ */
20
+ readonly purpose: string;
21
+ /** 本模块命名空间内的稳定业务资源类型,例如 `evidence.file`。 */
22
+ readonly resourceType: string;
23
+ /** 由授权策略回查业务 Repository 的稳定资源 ID。 */
24
+ readonly resourceId: string;
25
+ }
26
+
27
+ /** 资源授权读取仅返回业务所需的最小元数据与受限文件流。 */
28
+ export interface ModuleOpenedResourceFile {
29
+ readonly id: string;
30
+ readonly purpose: string;
31
+ readonly filename: string;
32
+ readonly mimeType: string;
33
+ readonly byteSize: number;
34
+ readonly sha256: string;
35
+ readonly body: ReadableStream<Uint8Array>;
36
+ }
37
+
38
+ export interface ModuleResourceFileAccessContext {
39
+ readonly actor: ModuleActor;
40
+ readonly requestId: string;
41
+ readonly signal: AbortSignal;
42
+ }
43
+
44
+ /**
45
+ * 业务模块注册的资源到平台文件关联授权策略。
46
+ *
47
+ * `authorize` 必须从 project-owned Repository 回查 resourceId,确认当前 Actor
48
+ * 可访问该业务资源且该资源实际绑定 fileId;客户端或 Tool 输入不是授权证据。
49
+ */
50
+ export interface ModuleResourceFileAccessPolicy {
51
+ /**
52
+ * 本模块或 Manifest 显式依赖模块拥有的 Upload Policy purpose;
53
+ * 平台基础 purpose 与未声明跨模块 purpose 均不可引用。
54
+ */
55
+ readonly purpose: string;
56
+ readonly resourceType: string;
57
+ readonly featureKey: string;
58
+ readonly permissionKey: string;
59
+ readonly auditAction: string;
60
+ authorize(
61
+ input: Readonly<{
62
+ fileId: string;
63
+ resourceId: string;
64
+ }>,
65
+ context: ModuleResourceFileAccessContext,
66
+ ): Promise<boolean> | boolean;
67
+ }
68
+
12
69
  /**
13
70
  * 模块受限 Upload / BlobRead Port。
14
71
  *
@@ -22,4 +79,13 @@ export interface ModuleStoragePort {
22
79
  createUploadSession(input: ModuleUploadSessionInput): Promise<UploadSession>;
23
80
  openFile(fileId: string): Promise<OpenedFile>;
24
81
  openPublicFile(fileId: string, purpose: string): Promise<OpenedFile>;
82
+ /**
83
+ * 经 Host Feature / Permission 与业务资源策略双重授权后读取文件。
84
+ *
85
+ * 允许同一业务范围内跨上传者读取,但不授予任意 manage、Repository、BlobStore
86
+ * 或 ArtifactRef 能力。
87
+ */
88
+ openResourceFile(
89
+ input: ModuleResourceFileRequest,
90
+ ): Promise<ModuleOpenedResourceFile>;
25
91
  }