create-windy 0.3.18 → 0.4.0
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 +28 -13
- package/dist/cli.js +788 -280
- package/package.json +1 -1
- package/template/.agents/skills/windy-business-module/SKILL.md +34 -10
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +18 -8
- package/template/README.md +32 -22
- package/template/apps/agent-server/src/southwind_agent_server/adk_debug.py +38 -5
- package/template/apps/agent-server/tests/test_adk_debug_result_polling.py +139 -0
- package/template/apps/server/Dockerfile +1 -0
- package/template/apps/server/package.json +1 -0
- package/template/apps/server/scripts/validate-module-composition-once.ts +4 -0
- package/template/apps/server/scripts/validate-module-composition.ts +28 -0
- package/template/apps/server/src/agent/data-scope-routes.test.ts +20 -5
- package/template/apps/server/src/agent/routes.test.ts +20 -7
- package/template/apps/server/src/agent/routes.ts +14 -0
- package/template/apps/server/src/agent/runtime-bootstrap.ts +22 -6
- package/template/apps/server/src/agent/sandbox-worker.ts +2 -2
- package/template/apps/server/src/agent/unavailable-routes.ts +33 -0
- package/template/apps/server/src/app/server-composition.ts +56 -0
- package/template/apps/server/src/application-services.ts +2 -0
- package/template/apps/server/src/bulk-data/service.ts +14 -0
- package/template/apps/server/src/data-governance/export/authorization-port.ts +1 -1
- package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +18 -2
- package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +10 -9
- package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +6 -8
- package/template/apps/server/src/data-governance/user-plaintext-routes.test.ts +4 -2
- package/template/apps/server/src/feature/routes.authorization.test.ts +26 -1
- package/template/apps/server/src/feature/routes.ts +33 -15
- package/template/apps/server/src/foundation-activation.test.ts +60 -0
- package/template/apps/server/src/foundation.ts +64 -5
- package/template/apps/server/src/guards.test.ts +23 -0
- package/template/apps/server/src/guards.ts +12 -0
- package/template/apps/server/src/index.ts +28 -34
- package/template/apps/server/src/installed-business-modules.ts +13 -24
- package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -0
- package/template/apps/server/src/jobs/legacy-adapter.test.ts +106 -0
- package/template/apps/server/src/jobs/legacy-service-runtime.ts +40 -0
- package/template/apps/server/src/license/activation.test.ts +30 -0
- package/template/apps/server/src/license/activation.ts +26 -0
- package/template/apps/server/src/license/restriction.test.ts +16 -0
- package/template/apps/server/src/license/routes-offline-code.test.ts +6 -2
- package/template/apps/server/src/license/routes.ts +20 -4
- package/template/apps/server/src/license/runtime-service.test.ts +10 -2
- package/template/apps/server/src/module-bootstrap.ts +14 -23
- package/template/apps/server/src/module-composition.test.ts +43 -2
- package/template/apps/server/src/module-composition.ts +29 -0
- package/template/apps/server/src/module-host/durable-context-guard.test.ts +172 -0
- package/template/apps/server/src/module-host/durable-context-unavailable.test.ts +50 -1
- package/template/apps/server/src/module-host/durable-context.test.ts +42 -25
- package/template/apps/server/src/module-host/durable-context.ts +42 -2
- package/template/apps/server/src/module-host/durable-handler-registration.ts +96 -5
- package/template/apps/server/src/module-host/implementation-completeness.test.ts +185 -0
- package/template/apps/server/src/module-host/implementation-completeness.ts +132 -0
- package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
- package/template/apps/server/src/module-host/initialize.test.ts +78 -2
- package/template/apps/server/src/module-host/initialize.ts +15 -4
- package/template/apps/server/src/module-host/project-attachments.test.ts +92 -0
- package/template/apps/server/src/module-host/project-attachments.ts +58 -0
- package/template/apps/server/src/module-host/project-composition-package.test.ts +186 -0
- package/template/apps/server/src/module-host/project-composition.test.ts +260 -0
- package/template/apps/server/src/module-host/project-composition.ts +311 -0
- package/template/apps/server/src/module-host/request-context.ts +3 -0
- package/template/apps/server/src/module-host/route-declaration.ts +15 -6
- package/template/apps/server/src/module-host/route-installer.test.ts +15 -2
- package/template/apps/server/src/module-host/route-installer.ts +12 -2
- package/template/apps/server/src/module-host/route-registration.test.ts +13 -4
- package/template/apps/server/src/module-host/runtime-composition-compatibility.test.ts +63 -0
- package/template/apps/server/src/module-host/runtime-composition-compatibility.ts +46 -0
- package/template/apps/server/src/module-host/runtime-composition.ts +34 -0
- package/template/apps/server/src/module-host/search-provider-registration.test.ts +64 -0
- package/template/apps/server/src/module-host/search-provider-registration.ts +35 -0
- package/template/apps/server/src/module-host/static-route-authorization.test.ts +99 -0
- package/template/apps/server/src/module-host/static-route-authorization.ts +90 -0
- package/template/apps/server/src/module-host/task-definitions.ts +1 -0
- package/template/apps/server/src/module-host/user-directory-composition.test.ts +4 -1
- package/template/apps/server/src/module-host.test.ts +2 -0
- package/template/apps/server/src/module-preflight.test.ts +289 -0
- package/template/apps/server/src/module-preflight.ts +96 -0
- package/template/apps/server/src/module-runtime-validation.ts +1 -0
- package/template/apps/server/src/platform-app-bootstrap.ts +50 -0
- package/template/apps/server/src/platform-http-runtime.ts +32 -7
- package/template/apps/server/src/route-guards.test.ts +22 -0
- package/template/apps/server/src/route-guards.ts +10 -3
- package/template/apps/server/src/runtime-audit-query.ts +50 -0
- package/template/apps/server/src/runtime-feature.test.ts +46 -0
- package/template/apps/server/src/runtime-feature.ts +5 -0
- package/template/apps/server/src/runtime.ts +10 -49
- package/template/apps/server/src/scheduler/audit-action.test.ts +49 -0
- package/template/apps/server/src/scheduler/audit.ts +1 -1
- package/template/apps/server/src/scheduler/execution-recorder.ts +10 -5
- package/template/apps/server/src/scheduler/recovery-service.test.ts +4 -3
- package/template/apps/server/src/scheduler/service-idle.test.ts +4 -3
- package/template/apps/server/src/scheduler/service.test.ts +1 -0
- package/template/apps/server/src/scheduler/service.ts +18 -21
- package/template/apps/server/src/scheduler/types.ts +5 -1
- package/template/apps/server/src/startup-composition-validation.test.ts +35 -0
- package/template/apps/server/src/startup-composition-validation.ts +54 -0
- package/template/apps/server/src/startup-preflight.test.ts +36 -0
- package/template/apps/server/src/startup-preflight.ts +37 -0
- package/template/apps/server/src/system/audit-routes.test.ts +9 -3
- package/template/apps/server/src/system/department-routes.test.ts +5 -3
- package/template/apps/server/src/system/feature-routes.test.ts +4 -3
- package/template/apps/server/src/system/interface-catalog.test.ts +5 -3
- package/template/apps/server/src/system/interface-catalog.ts +3 -0
- package/template/apps/server/src/system/menu-routes.test.ts +5 -3
- package/template/apps/server/src/system/route-test-runtime.ts +24 -0
- package/template/apps/server/src/system/routes-validation.test.ts +7 -3
- package/template/apps/server/src/system/routes.test.ts +21 -11
- package/template/apps/server/src/system/routes.ts +11 -2
- package/template/apps/server/src/work-order/foundation-modules.test.ts +6 -4
- package/template/apps/server/src/work-order/routes.test.ts +8 -0
- package/template/apps/server/src/work-order/task.test.ts +6 -1
- package/template/apps/web/Dockerfile +5 -0
- package/template/apps/web/package.json +1 -0
- package/template/apps/web/scripts/validate-admin-ui-composition.ts +106 -0
- package/template/apps/web/src/app/web-composition.ts +24 -0
- package/template/apps/web/src/app/web-composition.webtest.ts +15 -0
- package/template/apps/web/src/layout/AppSidebar.vue +8 -1
- package/template/apps/web/src/layout/NavigationSearchDialog.vue +8 -1
- package/template/apps/web/src/layout/navigation.ts +29 -4
- package/template/apps/web/src/layout/navigation.webtest.ts +51 -0
- package/template/apps/web/src/pages/dashboard/dashboard-presenter.ts +6 -1
- package/template/apps/web/src/pages/work-orders/WorkOrderListPage.vue +202 -0
- package/template/apps/web/src/pages/work-orders/WorkOrderListPage.webtest.ts +51 -0
- package/template/apps/web/src/router/admin-extension-loader.ts +44 -0
- package/template/apps/web/src/router/admin-extension-loader.webtest.ts +49 -0
- package/template/apps/web/src/router/admin-ui-composition-plugin.ts +33 -0
- package/template/apps/web/src/router/admin-ui-composition-plugin.webtest.ts +20 -0
- package/template/apps/web/src/router/admin-ui-composition.ts +313 -0
- package/template/apps/web/src/router/admin-ui-composition.webtest.ts +225 -0
- package/template/apps/web/src/router/admin-ui-module-registry-plugin.ts +55 -0
- package/template/apps/web/src/router/admin-ui-module-registry-plugin.webtest.ts +46 -0
- package/template/apps/web/src/router/business-routes.ts +32 -45
- package/template/apps/web/src/router/business-routes.webtest.ts +40 -44
- package/template/apps/web/src/router/index.ts +64 -54
- package/template/apps/web/src/router/manifest-routes.ts +27 -7
- package/template/apps/web/src/router/manifest-routes.webtest.ts +10 -2
- package/template/apps/web/src/router/platform-admin-route-bindings.ts +58 -0
- package/template/apps/web/src/router/reserved-route-paths.ts +14 -0
- package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +10 -4
- package/template/apps/web/src/router/web-composition-host.ts +20 -0
- package/template/apps/web/src/services/work-order-api.ts +16 -0
- package/template/apps/web/src/services/work-order-api.webtest.ts +21 -0
- package/template/apps/web/src/virtual-admin-ui-modules.d.ts +5 -0
- package/template/apps/web/vite.config.ts +22 -1
- package/template/docs/architecture/migration-bundle.md +10 -7
- package/template/docs/architecture/module-manifest-composition.md +85 -3
- package/template/docs/architecture/search-provider-protocol.md +6 -1
- package/template/docs/architecture/server-module-interface.md +70 -7
- package/template/docs/development/business-web-routes.md +53 -50
- package/template/docs/development/module-lifecycle.md +48 -54
- package/template/docs/platform/agent-runtime.md +2 -0
- package/template/docs/platform/navigation-registry.md +5 -4
- package/template/package.json +1 -1
- package/template/packages/config/package.json +1 -1
- package/template/packages/database/package.json +1 -1
- package/template/packages/example-work-order/package.json +6 -0
- package/template/packages/example-work-order/src/admin-page.ts +78 -0
- package/template/packages/example-work-order/src/admin.ts +35 -0
- package/template/packages/example-work-order/src/manifest.test.ts +62 -5
- package/template/packages/example-work-order/src/manifest.ts +54 -7
- package/template/packages/example-work-order/src/migration-bundle.ts +8 -0
- package/template/packages/example-work-order/src/server-module.ts +2 -2
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/modules/index.ts +4 -0
- package/template/packages/modules/package.json +3 -2
- package/template/packages/modules/src/admin-ui-entry.ts +8 -0
- package/template/packages/modules/src/admin-ui-manifest.test.ts +52 -0
- package/template/packages/modules/src/admin-ui-manifest.ts +53 -0
- package/template/packages/modules/src/capability-audit-validation.test.ts +127 -0
- package/template/packages/modules/src/capability-audit-validation.ts +38 -0
- package/template/packages/modules/src/capability-guard-validation.ts +148 -0
- package/template/packages/modules/src/capability-key-validation.test.ts +150 -0
- package/template/packages/modules/src/composition-fixtures.test-support.ts +7 -5
- package/template/packages/modules/src/composition.test.ts +74 -9
- package/template/packages/modules/src/composition.ts +8 -0
- package/template/packages/modules/src/manifest.ts +1 -0
- package/template/packages/modules/src/migration-bundle-validation.test.ts +23 -0
- package/template/packages/modules/src/module-host-range.test.ts +57 -0
- package/template/packages/modules/src/module-host-range.ts +125 -0
- package/template/packages/modules/src/project-module-assembly.test.ts +133 -0
- package/template/packages/modules/src/project-module-assembly.ts +126 -0
- package/template/packages/modules/src/system-admin-routes.ts +6 -5
- package/template/packages/modules/src/system-admin-ui-catalog.ts +72 -0
- package/template/packages/modules/src/system-agent-tools.ts +2 -2
- package/template/packages/modules/src/system-api-binding.ts +43 -9
- package/template/packages/modules/src/system-api-control-plane.ts +202 -0
- package/template/packages/modules/src/system-api-data-governance.ts +4 -2
- package/template/packages/modules/src/system-api-operational.ts +57 -8
- package/template/packages/modules/src/system-api-permissions.ts +115 -21
- package/template/packages/modules/src/system-api-watermark.ts +3 -0
- package/template/packages/modules/src/system-audit-actions.ts +7 -0
- package/template/packages/modules/src/system-data-governance.ts +5 -2
- package/template/packages/modules/src/system-features.ts +31 -0
- package/template/packages/modules/src/system-governed-export-bindings.ts +8 -4
- package/template/packages/modules/src/system-module-catalog.test.ts +39 -4
- package/template/packages/modules/src/system-module-catalog.ts +68 -7
- package/template/packages/modules/src/system-module-tasks.ts +8 -1
- package/template/packages/modules/src/system-modules-access.test.ts +4 -0
- package/template/packages/modules/src/system-modules.test.ts +10 -2
- package/template/packages/modules/src/system-modules.ts +6 -5
- package/template/packages/modules/src/system-permissions.ts +4 -0
- package/template/packages/modules/user/module.ts +19 -2
- package/template/packages/server-sdk/index.ts +8 -0
- package/template/packages/server-sdk/package.json +2 -1
- package/template/packages/server-sdk/src/durable-handler.ts +16 -1
- package/template/packages/server-sdk/src/module-adapter.test.ts +44 -0
- package/template/packages/server-sdk/src/module-adapter.ts +59 -0
- package/template/packages/server-sdk/src/route-definition.ts +3 -2
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/audit.test.ts +7 -0
- package/template/packages/shared/src/audit.ts +16 -0
- package/template/packages/shared/src/migration-bundle-validation.ts +9 -11
- package/template/packages/shared/src/rbac.ts +2 -0
- package/template/apps/web/src/app/business-routes.ts +0 -22
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ModuleAdminRouteDefinition, ModuleManifest } from "./manifest.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 浏览器可见的最小 Module 契约。Server Manifest 的 Migration、Task、Provider、
|
|
5
|
+
* Audit 和持久化元数据不得进入这个类型或 Web bundle。
|
|
6
|
+
*/
|
|
7
|
+
export interface AdminUiManifest {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly version: string;
|
|
10
|
+
readonly adminRoutes: readonly ModuleAdminRouteDefinition[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type AdminPageLoader<TPage = unknown> = () => Promise<{
|
|
14
|
+
readonly default: TPage;
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
export interface AdminPageBinding<TPage = unknown> {
|
|
18
|
+
readonly routeKey: string;
|
|
19
|
+
readonly load: AdminPageLoader<TPage>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface WindyAdminUiModule<TPage = unknown> {
|
|
23
|
+
readonly manifest: AdminUiManifest;
|
|
24
|
+
readonly hostRange: string;
|
|
25
|
+
readonly pages: readonly AdminPageBinding<TPage>[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type AdminUiManifestSource = Pick<
|
|
29
|
+
ModuleManifest,
|
|
30
|
+
"name" | "version" | "adminRoutes"
|
|
31
|
+
>;
|
|
32
|
+
|
|
33
|
+
export function defineAdminUiManifest(
|
|
34
|
+
manifest: AdminUiManifest,
|
|
35
|
+
): AdminUiManifest {
|
|
36
|
+
return manifest;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function defineWindyAdminUiModule<TPage>(
|
|
40
|
+
module: WindyAdminUiModule<TPage>,
|
|
41
|
+
): WindyAdminUiModule<TPage> {
|
|
42
|
+
return module;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function projectAdminUiManifest(
|
|
46
|
+
manifest: AdminUiManifestSource,
|
|
47
|
+
): AdminUiManifest {
|
|
48
|
+
return {
|
|
49
|
+
name: manifest.name,
|
|
50
|
+
version: manifest.version,
|
|
51
|
+
adminRoutes: manifest.adminRoutes.map((route) => ({ ...route })),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { fixture } from "./composition-fixtures.test-support.js";
|
|
3
|
+
import { validateModuleComposition } from "./composition.js";
|
|
4
|
+
import { systemFoundationModule } from "./system-modules.js";
|
|
5
|
+
|
|
6
|
+
describe("API 与定时任务 Audit Action 组合门禁", () => {
|
|
7
|
+
test("非匿名 API 缺少 Audit Action 时 fail closed", () => {
|
|
8
|
+
const module = fixture("api-audit");
|
|
9
|
+
Reflect.deleteProperty(module.apiPermissions[0]!, "auditAction");
|
|
10
|
+
|
|
11
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
12
|
+
"API Route GET /api/api-audit 必须声明 Audit Action",
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("定时任务缺少 Audit Action 时 fail closed", () => {
|
|
17
|
+
const module = fixture("task-audit");
|
|
18
|
+
Reflect.deleteProperty(module.tasks[0]!, "auditAction");
|
|
19
|
+
|
|
20
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
21
|
+
"定时任务 task-audit.task 必须声明 Audit Action",
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test.each([
|
|
26
|
+
[
|
|
27
|
+
"API Route GET /api/api-audit",
|
|
28
|
+
(module: ReturnType<typeof fixture>) => {
|
|
29
|
+
module.apiPermissions[0]!.auditAction = "audit.unknown";
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
[
|
|
33
|
+
"定时任务 task-audit.task",
|
|
34
|
+
(module: ReturnType<typeof fixture>) => {
|
|
35
|
+
module.tasks[0]!.auditAction = "audit.unknown";
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
] as const)("%s 引用未知 Audit Action 时 fail closed", (label, mutate) => {
|
|
39
|
+
const module = fixture(
|
|
40
|
+
label.startsWith("API") ? "api-audit" : "task-audit",
|
|
41
|
+
);
|
|
42
|
+
mutate(module);
|
|
43
|
+
|
|
44
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
45
|
+
`${label} 引用了未注册审计动作:audit.unknown`,
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("显式匿名 API 可以不声明 Audit Action", () => {
|
|
50
|
+
const module = fixture("public");
|
|
51
|
+
module.apiPermissions[0]!.anonymous = true;
|
|
52
|
+
Reflect.deleteProperty(module.apiPermissions[0]!, "auditAction");
|
|
53
|
+
|
|
54
|
+
expect(() => validateModuleComposition([module])).not.toThrow();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("Provider 不虚构执行 Audit Action", () => {
|
|
58
|
+
const module = fixture("provider");
|
|
59
|
+
|
|
60
|
+
expect("auditAction" in module.providers[0]!).toBe(false);
|
|
61
|
+
expect(() => validateModuleComposition([module])).not.toThrow();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("系统 API 与任务引用的 Audit Action 均由 Manifest 注册", () => {
|
|
65
|
+
const manifest = systemFoundationModule();
|
|
66
|
+
const registered = new Set(
|
|
67
|
+
manifest.auditActions.map((action) =>
|
|
68
|
+
typeof action === "string" ? action : action.key,
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
const used = [
|
|
72
|
+
...manifest.apiPermissions
|
|
73
|
+
.filter(({ anonymous }) => anonymous !== true)
|
|
74
|
+
.map(({ auditAction }) => auditAction),
|
|
75
|
+
...manifest.tasks.map(({ auditAction }) => auditAction),
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
expect(used.every((action) => action && registered.has(action))).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("系统资源 API 按读取、导入、导出和 CRUD 语义声明 Audit Action", () => {
|
|
82
|
+
const bindings = systemFoundationModule().apiPermissions.filter(
|
|
83
|
+
({ path }) => path.startsWith("/api/system/roles"),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
expect(bindings.map(({ auditAction }) => auditAction)).toEqual([
|
|
87
|
+
"data.read",
|
|
88
|
+
"data.export",
|
|
89
|
+
"data.read",
|
|
90
|
+
"data.import",
|
|
91
|
+
"crud.create",
|
|
92
|
+
"crud.update",
|
|
93
|
+
"crud.delete",
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test.each([
|
|
98
|
+
// @windy-module system.search begin
|
|
99
|
+
["POST", "/api/search", "search.query"],
|
|
100
|
+
// @windy-module system.search end
|
|
101
|
+
["PUT", "/api/system/menus/placements", "menu.structure.update"],
|
|
102
|
+
["PUT", "/api/system/features/:id", "feature.update"],
|
|
103
|
+
// @windy-module system.scheduler begin
|
|
104
|
+
["POST", "/api/system/scheduled-tasks/:id/trigger", "task.trigger"],
|
|
105
|
+
// @windy-module system.scheduler end
|
|
106
|
+
// @windy-module system.bulk-data begin
|
|
107
|
+
[
|
|
108
|
+
"POST",
|
|
109
|
+
"/api/system/bulk-data/dictionaries/import",
|
|
110
|
+
"data.bulk-import.create",
|
|
111
|
+
],
|
|
112
|
+
// @windy-module system.bulk-data end
|
|
113
|
+
// @windy-module system.storage begin
|
|
114
|
+
[
|
|
115
|
+
"PUT",
|
|
116
|
+
"/api/system/storage/uploads/:id/parts/:partNumber",
|
|
117
|
+
"file.upload.part.write",
|
|
118
|
+
],
|
|
119
|
+
// @windy-module system.storage end
|
|
120
|
+
] as const)("%s %s 声明真实动作 %s", (method, path, auditAction) => {
|
|
121
|
+
expect(
|
|
122
|
+
systemFoundationModule().apiPermissions.find(
|
|
123
|
+
(binding) => binding.method === method && binding.path === path,
|
|
124
|
+
),
|
|
125
|
+
).toMatchObject({ auditAction });
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AuditActionType } from "@southwind-ai/shared";
|
|
2
|
+
import type { ModuleManifest } from "./manifest.js";
|
|
3
|
+
|
|
4
|
+
export function validateCapabilityAuditActions(
|
|
5
|
+
modules: readonly ModuleManifest[],
|
|
6
|
+
auditActions: ReadonlySet<string>,
|
|
7
|
+
): void {
|
|
8
|
+
for (const module of modules) {
|
|
9
|
+
for (const route of module.apiPermissions) {
|
|
10
|
+
if (route.anonymous === true) continue;
|
|
11
|
+
validateAuditAction(
|
|
12
|
+
`API Route ${route.method} ${route.path}`,
|
|
13
|
+
route.auditAction,
|
|
14
|
+
auditActions,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
for (const task of module.tasks) {
|
|
18
|
+
validateAuditAction(
|
|
19
|
+
`定时任务 ${task.key}`,
|
|
20
|
+
task.auditAction,
|
|
21
|
+
auditActions,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function validateAuditAction(
|
|
28
|
+
label: string,
|
|
29
|
+
auditAction: AuditActionType | undefined,
|
|
30
|
+
auditActions: ReadonlySet<string>,
|
|
31
|
+
): void {
|
|
32
|
+
if (!auditAction?.trim()) {
|
|
33
|
+
throw new Error(`${label} 必须声明 Audit Action`);
|
|
34
|
+
}
|
|
35
|
+
if (!auditActions.has(auditAction)) {
|
|
36
|
+
throw new Error(`${label} 引用了未注册审计动作:${auditAction}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { MenuNode } from "@southwind-ai/shared";
|
|
2
|
+
import type { ModuleManifest } from "./manifest.js";
|
|
3
|
+
|
|
4
|
+
interface CapabilityGuard {
|
|
5
|
+
label: string;
|
|
6
|
+
permissionKey?: string;
|
|
7
|
+
featureKey?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function validateCapabilityGuardCompleteness(
|
|
11
|
+
modules: readonly ModuleManifest[],
|
|
12
|
+
): void {
|
|
13
|
+
for (const module of modules) {
|
|
14
|
+
const guards: CapabilityGuard[] = [
|
|
15
|
+
...flattenMenus(module.menus)
|
|
16
|
+
.filter(({ type }) => type !== "catalog")
|
|
17
|
+
.map((menu) => guard("菜单", menu)),
|
|
18
|
+
...module.adminRoutes.map((route) => guard("Admin Route", route)),
|
|
19
|
+
...module.apiPermissions
|
|
20
|
+
.filter(({ anonymous }) => anonymous !== true)
|
|
21
|
+
.map((route) =>
|
|
22
|
+
guard("API Route", {
|
|
23
|
+
...route,
|
|
24
|
+
key: `${route.method} ${route.path}`,
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
...module.tasks.map((task) => guard("定时任务", task)),
|
|
28
|
+
...module.tools.map((tool) => guard("Agent Tool", tool)),
|
|
29
|
+
...(module.aiOperations || []).map((operation) =>
|
|
30
|
+
guard("AI Operation", operation),
|
|
31
|
+
),
|
|
32
|
+
...module.providers.map((provider) => guard("Provider", provider)),
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
for (const item of guards) {
|
|
36
|
+
if (!item.permissionKey?.trim() || !item.featureKey?.trim()) {
|
|
37
|
+
throw new Error(`${item.label} 必须同时声明 Permission 与 Feature`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function validateCapabilityKeyAlignment(
|
|
44
|
+
modules: readonly ModuleManifest[],
|
|
45
|
+
): void {
|
|
46
|
+
const featureKeys = new Set<string>(
|
|
47
|
+
modules.flatMap((module) => module.features.map(({ key }) => key)),
|
|
48
|
+
);
|
|
49
|
+
const permissionKeys = new Set(
|
|
50
|
+
modules.flatMap((module) => module.permissions.map(({ key }) => key)),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
for (const module of modules) {
|
|
54
|
+
for (const permission of module.permissions) {
|
|
55
|
+
const expectedFeatureKey = permissionFeatureKey(permission.key);
|
|
56
|
+
if (!featureKeys.has(expectedFeatureKey)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`权限 ${permission.key} 缺少同基础 Key Feature:${expectedFeatureKey}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const guards: CapabilityGuard[] = [
|
|
64
|
+
...flattenMenus(module.menus)
|
|
65
|
+
.filter(({ type }) => type !== "catalog")
|
|
66
|
+
.map((menu) => guard("菜单", menu)),
|
|
67
|
+
...module.adminRoutes.map((route) => guard("Admin Route", route)),
|
|
68
|
+
...module.apiPermissions
|
|
69
|
+
.filter(({ anonymous }) => anonymous !== true)
|
|
70
|
+
.map((route) =>
|
|
71
|
+
guard("API Route", {
|
|
72
|
+
...route,
|
|
73
|
+
key: `${route.method} ${route.path}`,
|
|
74
|
+
}),
|
|
75
|
+
),
|
|
76
|
+
...module.tasks.map((task) => guard("定时任务", task)),
|
|
77
|
+
...module.tools.map((tool) => guard("Agent Tool", tool)),
|
|
78
|
+
...(module.aiOperations || []).map((operation) =>
|
|
79
|
+
guard("AI Operation", operation),
|
|
80
|
+
),
|
|
81
|
+
...module.providers.map((provider) => guard("Provider", provider)),
|
|
82
|
+
...(module.dataPolicies || []).flatMap((policy) =>
|
|
83
|
+
[
|
|
84
|
+
policy.readPermissionKey,
|
|
85
|
+
policy.revealPermissionKey,
|
|
86
|
+
policy.exportPermissionKey,
|
|
87
|
+
].map((permissionKey) =>
|
|
88
|
+
guard("数据治理策略", {
|
|
89
|
+
key: policy.key,
|
|
90
|
+
permissionKey,
|
|
91
|
+
featureKey: policy.featureKey,
|
|
92
|
+
}),
|
|
93
|
+
),
|
|
94
|
+
),
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
for (const item of guards) {
|
|
98
|
+
validateKeyAlignment(item.label, item, permissionKeys, featureKeys);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function validateKeyAlignment(
|
|
104
|
+
label: string,
|
|
105
|
+
input: { permissionKey?: string; featureKey?: string },
|
|
106
|
+
permissionKeys: ReadonlySet<string>,
|
|
107
|
+
featureKeys: ReadonlySet<string>,
|
|
108
|
+
): void {
|
|
109
|
+
if (
|
|
110
|
+
!input.permissionKey ||
|
|
111
|
+
!input.featureKey ||
|
|
112
|
+
!permissionKeys.has(input.permissionKey) ||
|
|
113
|
+
!featureKeys.has(input.featureKey)
|
|
114
|
+
) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const expectedFeatureKey = permissionFeatureKey(input.permissionKey);
|
|
118
|
+
if (input.featureKey !== expectedFeatureKey) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`${label} 的 Permission 基础 Key ${expectedFeatureKey} 与 Feature ${input.featureKey} 不一致`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function permissionFeatureKey(permissionKey: string): string {
|
|
126
|
+
const separator = permissionKey.lastIndexOf(".");
|
|
127
|
+
return separator > 0 ? permissionKey.slice(0, separator) : "";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function guard(
|
|
131
|
+
kind: string,
|
|
132
|
+
input: {
|
|
133
|
+
key?: string;
|
|
134
|
+
path?: string;
|
|
135
|
+
permissionKey?: string;
|
|
136
|
+
featureKey?: string;
|
|
137
|
+
},
|
|
138
|
+
): CapabilityGuard {
|
|
139
|
+
return {
|
|
140
|
+
label: `${kind} ${input.key || input.path || "unknown"}`,
|
|
141
|
+
permissionKey: input.permissionKey,
|
|
142
|
+
featureKey: input.featureKey,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function flattenMenus(menus: readonly MenuNode[]): MenuNode[] {
|
|
147
|
+
return menus.flatMap((menu) => [menu, ...flattenMenus(menu.children || [])]);
|
|
148
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { featureKey } from "@southwind-ai/shared";
|
|
3
|
+
import {
|
|
4
|
+
dataPolicy,
|
|
5
|
+
fixture,
|
|
6
|
+
permission,
|
|
7
|
+
} from "./composition-fixtures.test-support.js";
|
|
8
|
+
import { validateModuleComposition } from "./composition.js";
|
|
9
|
+
import type { ModuleManifest } from "./manifest.js";
|
|
10
|
+
import userModule from "../user/module.js";
|
|
11
|
+
|
|
12
|
+
describe("Capability 基础 Key 组合门禁", () => {
|
|
13
|
+
test("Permission 缺少同基础 Key Feature 时 fail closed", () => {
|
|
14
|
+
const module = fixture("permission");
|
|
15
|
+
module.permissions.push(
|
|
16
|
+
permission("permission", "permission.orphan.read", "read"),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
20
|
+
"权限 permission.orphan.read 缺少同基础 Key Feature:permission.orphan",
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test.each([
|
|
25
|
+
[
|
|
26
|
+
"菜单 binding.page",
|
|
27
|
+
(module: ModuleManifest) => {
|
|
28
|
+
module.menus[0]!.featureKey = featureKey("binding.other");
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
"Admin Route binding.page",
|
|
33
|
+
(module: ModuleManifest) => {
|
|
34
|
+
module.adminRoutes[0]!.featureKey = featureKey("binding.other");
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
"API Route GET /api/binding",
|
|
39
|
+
(module: ModuleManifest) => {
|
|
40
|
+
module.apiPermissions[0]!.featureKey = featureKey("binding.other");
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
"定时任务 binding.task",
|
|
45
|
+
(module: ModuleManifest) => {
|
|
46
|
+
module.tasks[0]!.featureKey = featureKey("binding.other");
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
"Agent Tool binding.tool",
|
|
51
|
+
(module: ModuleManifest) => {
|
|
52
|
+
module.tools[0]!.featureKey = featureKey("binding.other");
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
"AI Operation binding.ai-operation",
|
|
57
|
+
(module: ModuleManifest) => {
|
|
58
|
+
module.aiOperations = [aiOperation()];
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
"Provider binding.provider",
|
|
63
|
+
(module: ModuleManifest) => {
|
|
64
|
+
module.providers[0]!.featureKey = featureKey("binding.other");
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
"数据治理策略 binding.feature.fields",
|
|
69
|
+
(module: ModuleManifest) => {
|
|
70
|
+
addDataPolicy(module);
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
] as const)("%s 的 Guard 基础 Key 漂移时 fail closed", (label, mutate) => {
|
|
74
|
+
const module = fixture("binding");
|
|
75
|
+
module.features.push({
|
|
76
|
+
...module.features[0]!,
|
|
77
|
+
key: featureKey("binding.other"),
|
|
78
|
+
});
|
|
79
|
+
mutate(module);
|
|
80
|
+
|
|
81
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
82
|
+
`${label} 的 Permission 基础 Key binding.feature 与 Feature binding.other 不一致`,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("显式匿名 API 不要求 Permission 与 Feature 使用同一基础 Key", () => {
|
|
87
|
+
const module = fixture("public");
|
|
88
|
+
module.features.push({
|
|
89
|
+
...module.features[0]!,
|
|
90
|
+
key: featureKey("public.other"),
|
|
91
|
+
});
|
|
92
|
+
module.apiPermissions[0]!.anonymous = true;
|
|
93
|
+
module.apiPermissions[0]!.featureKey = featureKey("public.other");
|
|
94
|
+
|
|
95
|
+
expect(() => validateModuleComposition([module])).not.toThrow();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("示例用户模块通过完整 Capability Guard 门禁", () => {
|
|
99
|
+
expect(() => validateModuleComposition([userModule()])).not.toThrow();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
function aiOperation(): NonNullable<ModuleManifest["aiOperations"]>[number] {
|
|
104
|
+
return {
|
|
105
|
+
key: "binding.ai-operation",
|
|
106
|
+
kind: "agent",
|
|
107
|
+
featureKey: featureKey("binding.other"),
|
|
108
|
+
permissionKey: "binding.feature.read",
|
|
109
|
+
auditAction: "agent.tool.execute",
|
|
110
|
+
inputSchemaKey: "binding.tool.input.v1",
|
|
111
|
+
outputSchemaKey: "binding.tool.output.v1",
|
|
112
|
+
requiredCapabilities: ["text-generation"],
|
|
113
|
+
allowedToolKeys: [],
|
|
114
|
+
dataClassification: "internal",
|
|
115
|
+
execution: "interactive",
|
|
116
|
+
providerEgress: { mode: "forbidden" },
|
|
117
|
+
toolNetworkPolicy: { mode: "forbidden" },
|
|
118
|
+
limits: {
|
|
119
|
+
maxDurationSeconds: 10,
|
|
120
|
+
maxInputBytes: 4_096,
|
|
121
|
+
maxOutputBytes: 4_096,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function addDataPolicy(module: ModuleManifest): void {
|
|
127
|
+
module.permissions.push(
|
|
128
|
+
{
|
|
129
|
+
...permission("binding", "binding.feature.reveal", "manage"),
|
|
130
|
+
action: "reveal",
|
|
131
|
+
accessRisk: "sensitive-disclosure",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
...permission("binding", "binding.feature.export", "manage"),
|
|
135
|
+
action: "export",
|
|
136
|
+
accessRisk: "governed-export",
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
module.dataPolicies = [
|
|
140
|
+
{
|
|
141
|
+
...dataPolicy(),
|
|
142
|
+
key: "binding.feature.fields",
|
|
143
|
+
resourceType: "binding.feature",
|
|
144
|
+
featureKey: featureKey("binding.other"),
|
|
145
|
+
readPermissionKey: "binding.feature.read",
|
|
146
|
+
revealPermissionKey: "binding.feature.reveal",
|
|
147
|
+
exportPermissionKey: "binding.feature.export",
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
}
|
|
@@ -6,8 +6,8 @@ export function fixture(
|
|
|
6
6
|
dependencies: string[] = [],
|
|
7
7
|
): ModuleManifest {
|
|
8
8
|
const featureKey = `${name}.feature`;
|
|
9
|
-
const readPermission = `${
|
|
10
|
-
const managePermission = `${
|
|
9
|
+
const readPermission = `${featureKey}.read`;
|
|
10
|
+
const managePermission = `${featureKey}.manage`;
|
|
11
11
|
return {
|
|
12
12
|
name,
|
|
13
13
|
version: "0.1.0",
|
|
@@ -46,6 +46,7 @@ export function fixture(
|
|
|
46
46
|
path: `/api/${name}`,
|
|
47
47
|
permissionKey: readPermission,
|
|
48
48
|
featureKey,
|
|
49
|
+
auditAction: "data.search",
|
|
49
50
|
},
|
|
50
51
|
],
|
|
51
52
|
features: [
|
|
@@ -83,6 +84,7 @@ export function fixture(
|
|
|
83
84
|
description: name,
|
|
84
85
|
permissionKey: managePermission,
|
|
85
86
|
featureKey,
|
|
87
|
+
auditAction: "task.execute",
|
|
86
88
|
intervalSeconds: 60,
|
|
87
89
|
maxAttempts: 3,
|
|
88
90
|
retryDelaySeconds: 10,
|
|
@@ -131,9 +133,9 @@ export function dataPolicy(): NonNullable<
|
|
|
131
133
|
resourceType: "system.user",
|
|
132
134
|
version: "1",
|
|
133
135
|
featureKey: featureKey("system.feature"),
|
|
134
|
-
readPermissionKey: "system.read",
|
|
135
|
-
revealPermissionKey: "system.reveal",
|
|
136
|
-
exportPermissionKey: "system.export",
|
|
136
|
+
readPermissionKey: "system.feature.read",
|
|
137
|
+
revealPermissionKey: "system.feature.reveal",
|
|
138
|
+
exportPermissionKey: "system.feature.export",
|
|
137
139
|
display: {
|
|
138
140
|
read: "masked",
|
|
139
141
|
reveal: "plaintext",
|
|
@@ -35,11 +35,11 @@ describe("Module Composition 契约", () => {
|
|
|
35
35
|
const duplicate = fixture("duplicate");
|
|
36
36
|
duplicate.permissions[0] = {
|
|
37
37
|
...duplicate.permissions[0]!,
|
|
38
|
-
key: "system.read",
|
|
38
|
+
key: "system.feature.read",
|
|
39
39
|
};
|
|
40
40
|
expect(() =>
|
|
41
41
|
validateModuleComposition([fixture("system"), duplicate]),
|
|
42
|
-
).toThrow("权限 Key 重复:system.read");
|
|
42
|
+
).toThrow("权限 Key 重复:system.feature.read");
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
test("未知 Permission、Feature 与 Audit 引用会被拒绝", () => {
|
|
@@ -58,6 +58,71 @@ describe("Module Composition 契约", () => {
|
|
|
58
58
|
expect(() => validateModuleComposition([audit])).toThrow("未注册审计动作");
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
test.each([
|
|
62
|
+
[
|
|
63
|
+
"菜单",
|
|
64
|
+
(module: ReturnType<typeof fixture>) => {
|
|
65
|
+
module.menus[0]!.path = undefined;
|
|
66
|
+
module.menus[0]!.featureKey = undefined;
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
"Admin Route",
|
|
71
|
+
(module: ReturnType<typeof fixture>) => {
|
|
72
|
+
module.menus = [];
|
|
73
|
+
module.adminRoutes[0]!.featureKey = undefined;
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
"API Route",
|
|
78
|
+
(module: ReturnType<typeof fixture>) => {
|
|
79
|
+
module.apiPermissions[0]!.featureKey = undefined;
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
"定时任务",
|
|
84
|
+
(module: ReturnType<typeof fixture>) => {
|
|
85
|
+
Reflect.deleteProperty(module.tasks[0]!, "featureKey");
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
"Agent Tool",
|
|
90
|
+
(module: ReturnType<typeof fixture>) => {
|
|
91
|
+
Reflect.deleteProperty(module.tools[0]!, "featureKey");
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
"Provider",
|
|
96
|
+
(module: ReturnType<typeof fixture>) => {
|
|
97
|
+
module.providers[0]!.featureKey = undefined;
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
] as const)("%s 缺少 Feature Guard 时 fail closed", (_, removeFeature) => {
|
|
101
|
+
const module = fixture("guard");
|
|
102
|
+
removeFeature(module);
|
|
103
|
+
|
|
104
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
105
|
+
"必须同时声明 Permission 与 Feature",
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("业务入口缺少 Permission Guard 时 fail closed", () => {
|
|
110
|
+
const module = fixture("guard");
|
|
111
|
+
Reflect.deleteProperty(module.apiPermissions[0]!, "permissionKey");
|
|
112
|
+
|
|
113
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
114
|
+
"必须同时声明 Permission 与 Feature",
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("显式匿名 API 可以不声明 Feature Guard", () => {
|
|
119
|
+
const module = fixture("public");
|
|
120
|
+
module.apiPermissions[0]!.anonymous = true;
|
|
121
|
+
module.apiPermissions[0]!.featureKey = undefined;
|
|
122
|
+
|
|
123
|
+
expect(() => validateModuleComposition([module])).not.toThrow();
|
|
124
|
+
});
|
|
125
|
+
|
|
61
126
|
test("业务模块可注册带展示信息的审计动作目录", () => {
|
|
62
127
|
const module = fixture("ticket");
|
|
63
128
|
module.auditActions.push({
|
|
@@ -133,7 +198,7 @@ describe("Module Composition 契约", () => {
|
|
|
133
198
|
|
|
134
199
|
test("菜单 Guard 必须与 Admin Route 一致", () => {
|
|
135
200
|
const module = fixture("system");
|
|
136
|
-
module.adminRoutes[0]!.permissionKey = "system.manage";
|
|
201
|
+
module.adminRoutes[0]!.permissionKey = "system.feature.manage";
|
|
137
202
|
expect(() => validateModuleComposition([module])).toThrow(
|
|
138
203
|
"Admin Route Guard 不一致",
|
|
139
204
|
);
|
|
@@ -144,12 +209,12 @@ describe("Module Composition 契约", () => {
|
|
|
144
209
|
module.adminRoutes[0] = {
|
|
145
210
|
...module.adminRoutes[0]!,
|
|
146
211
|
directAccess: "admin-admission",
|
|
147
|
-
permissionKey: "system.manage",
|
|
212
|
+
permissionKey: "system.feature.manage",
|
|
148
213
|
};
|
|
149
214
|
|
|
150
215
|
expect(() => validateModuleComposition([module])).not.toThrow();
|
|
151
216
|
expect(module.menus[0]).toMatchObject({
|
|
152
|
-
permissionKey: "system.read",
|
|
217
|
+
permissionKey: "system.feature.read",
|
|
153
218
|
featureKey: "system.feature",
|
|
154
219
|
});
|
|
155
220
|
});
|
|
@@ -197,12 +262,12 @@ describe("Module Composition 契约", () => {
|
|
|
197
262
|
test("字段治理策略校验唯一资源、权限风险和已注册字段", () => {
|
|
198
263
|
const module = fixture("system");
|
|
199
264
|
module.permissions.push({
|
|
200
|
-
...permission("system", "system.reveal", "manage"),
|
|
265
|
+
...permission("system", "system.feature.reveal", "manage"),
|
|
201
266
|
action: "reveal",
|
|
202
267
|
accessRisk: "sensitive-disclosure",
|
|
203
268
|
});
|
|
204
269
|
module.permissions.push({
|
|
205
|
-
...permission("system", "system.export", "manage"),
|
|
270
|
+
...permission("system", "system.feature.export", "manage"),
|
|
206
271
|
action: "export",
|
|
207
272
|
accessRisk: "governed-export",
|
|
208
273
|
});
|
|
@@ -220,8 +285,8 @@ describe("Module Composition 契约", () => {
|
|
|
220
285
|
module.dataPolicies = [dataPolicy()];
|
|
221
286
|
expect(() => validateModuleComposition([module])).toThrow("未注册权限");
|
|
222
287
|
|
|
223
|
-
module.dataPolicies[0]!.revealPermissionKey = "system.manage";
|
|
224
|
-
module.dataPolicies[0]!.exportPermissionKey = "system.manage";
|
|
288
|
+
module.dataPolicies[0]!.revealPermissionKey = "system.feature.manage";
|
|
289
|
+
module.dataPolicies[0]!.exportPermissionKey = "system.feature.manage";
|
|
225
290
|
expect(() => validateModuleComposition([module])).toThrow(
|
|
226
291
|
"明文权限必须标记 sensitive-disclosure",
|
|
227
292
|
);
|