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.
- package/dist/cli.js +9 -1
- package/package.json +1 -1
- package/template/.agents/skills/windy-business-module/SKILL.md +10 -1
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +11 -0
- package/template/apps/agent-server/README.md +10 -0
- package/template/apps/agent-server/src/southwind_agent_server/app.py +62 -1
- package/template/apps/agent-server/src/southwind_agent_server/contracts.py +35 -0
- package/template/apps/agent-server/src/southwind_agent_server/engine.py +2 -0
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +31 -3
- package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +22 -9
- package/template/apps/agent-server/src/southwind_agent_server/provider_audit.py +144 -0
- package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +227 -0
- package/template/apps/agent-server/src/southwind_agent_server/service.py +1 -0
- package/template/apps/agent-server/tests/test_api.py +16 -1
- package/template/apps/agent-server/tests/test_openai_provider.py +79 -0
- package/template/apps/agent-server/tests/test_provider_governance.py +215 -0
- package/template/apps/server/src/agent/provider-input-governance.test.ts +82 -0
- package/template/apps/server/src/agent/provider-input-governance.ts +140 -0
- package/template/apps/server/src/agent/remote-runtime.test.ts +22 -0
- package/template/apps/server/src/agent/remote-runtime.ts +1 -0
- package/template/apps/server/src/agent/run-contracts.ts +2 -0
- package/template/apps/server/src/agent/run-facade.test.ts +52 -2
- package/template/apps/server/src/agent/run-facade.ts +36 -2
- package/template/apps/server/src/index.ts +28 -45
- package/template/apps/server/src/module-host/initialize-contracts.ts +6 -0
- package/template/apps/server/src/module-host/initialize.ts +30 -44
- package/template/apps/server/src/module-host/request-context.ts +23 -17
- package/template/apps/server/src/module-host/resource-file-policies.test.ts +197 -0
- package/template/apps/server/src/module-host/resource-file-policies.ts +108 -0
- package/template/apps/server/src/module-host/route-declaration.ts +45 -0
- package/template/apps/server/src/module-host/route-installer.ts +27 -3
- package/template/apps/server/src/module-host/storage-port.test.ts +170 -1
- package/template/apps/server/src/module-host/storage-port.ts +134 -1
- package/template/apps/server/src/module-host/storage-scope.ts +96 -0
- package/template/apps/server/src/module-host/tool-handlers.test.ts +305 -0
- package/template/apps/server/src/module-host/tool-handlers.ts +38 -4
- package/template/apps/server/src/module-host/user-directory-composition.test.ts +126 -0
- package/template/apps/server/src/module-host/user-directory-port.test.ts +238 -0
- package/template/apps/server/src/module-host/user-directory-port.ts +143 -0
- package/template/apps/server/src/module-host.ts +4 -0
- package/template/apps/server/src/runtime-development.ts +1 -0
- package/template/apps/server/src/settings/drizzle-watermark-repository.integration.test.ts +4 -0
- package/template/apps/server/src/settings/drizzle-watermark-repository.ts +6 -0
- package/template/apps/server/src/settings/watermark-routes.test.ts +13 -3
- package/template/apps/server/src/settings/watermark-routes.ts +41 -3
- package/template/apps/server/src/system/built-in-roles.ts +1 -0
- package/template/apps/web/src/app/auth/login-page.ts +4 -0
- package/template/apps/web/src/composables/useLoginFlow.webtest.ts +91 -0
- package/template/apps/web/src/composables/useWatermarkSettings.ts +7 -3
- package/template/apps/web/src/composables/useWatermarkSettings.webtest.ts +14 -0
- package/template/apps/web/src/composables/watermark-settings.ts +131 -9
- package/template/apps/web/src/composables/watermark-settings.webtest.ts +73 -7
- package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +3 -1
- package/template/apps/web/src/layout/GlobalWatermark.vue +24 -11
- package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +25 -11
- package/template/apps/web/src/pages/auth/login-page-extension.ts +9 -0
- package/template/apps/web/src/pages/auth/login-page-extension.webtest.ts +10 -0
- package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +102 -4
- package/template/apps/web/src/router/index.ts +2 -2
- package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +3 -2
- package/template/docker-compose.yml +2 -0
- package/template/docs/architecture/ai-egress.md +49 -7
- package/template/docs/architecture/ai-runtime.md +20 -1
- package/template/docs/architecture/data-scope-query-enforcement.md +4 -0
- package/template/docs/architecture/object-storage.md +22 -2
- package/template/docs/development/custom-login-page.md +90 -0
- package/template/docs/platform/agent-runtime.md +44 -1
- package/template/docs/platform/business-user-directory.md +82 -0
- package/template/docs/platform/platform-shell-settings.md +14 -9
- package/template/docs/platform/web-system-crud.md +1 -1
- package/template/packages/database/drizzle/0034_funny_mesmero.sql +5 -0
- package/template/packages/database/drizzle/meta/0034_snapshot.json +7253 -0
- package/template/packages/database/drizzle/meta/_journal.json +7 -0
- package/template/packages/database/src/schema/platform-settings.ts +13 -0
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/ai-operation-composition.test.ts +68 -0
- package/template/packages/modules/src/ai-operation-composition.ts +115 -0
- package/template/packages/modules/src/manifest.ts +26 -0
- package/template/packages/modules/src/provider-data-policy-composition.test.ts +74 -0
- package/template/packages/modules/src/system-audit-actions.ts +5 -0
- package/template/packages/modules/src/system-features.ts +5 -0
- package/template/packages/modules/src/system-module-catalog.test.ts +16 -0
- package/template/packages/modules/src/system-module-catalog.ts +26 -3
- package/template/packages/modules/src/system-permissions.ts +2 -0
- package/template/packages/server-sdk/index.ts +11 -0
- package/template/packages/server-sdk/package.json +3 -2
- package/template/packages/server-sdk/src/ai-operation-registration.ts +2 -1
- package/template/packages/server-sdk/src/module-host.ts +8 -0
- package/template/packages/server-sdk/src/request-context.ts +2 -0
- package/template/packages/server-sdk/src/storage-port.ts +66 -0
- package/template/packages/server-sdk/src/tool-registration.ts +6 -0
- package/template/packages/server-sdk/src/user-directory-port.ts +42 -0
- package/template/packages/shared/src/platform-settings.ts +17 -1
|
@@ -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
|
},
|
|
@@ -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",
|
|
@@ -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: [
|
|
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
|
|
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
|
+
"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
|
}
|