create-windy 0.2.34 → 0.3.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/dist/cli.js +167 -17
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +5 -0
- package/template/README.md +3 -1
- package/template/apps/agent-server/Dockerfile +1 -0
- package/template/apps/agent-server/README.md +18 -5
- package/template/apps/agent-server/adk_web_agents/__init__.py +1 -0
- package/template/apps/agent-server/adk_web_agents/safe_debug/__init__.py +3 -0
- package/template/apps/agent-server/adk_web_agents/safe_debug/agent.py +40 -0
- package/template/apps/agent-server/pyproject.toml +1 -1
- package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +191 -16
- package/template/apps/agent-server/src/southwind_agent_server/adk_failure.py +13 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +225 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +76 -0
- package/template/apps/agent-server/src/southwind_agent_server/app.py +1 -2
- package/template/apps/agent-server/tests/test_adk_adapter.py +305 -0
- package/template/apps/agent-server/tests/test_adk_web_agent.py +12 -0
- package/template/apps/server/package.json +4 -1
- package/template/apps/server/src/agent/run-facade.ts +2 -7
- package/template/apps/server/src/agent/tool-registry.ts +2 -6
- package/template/apps/server/src/application-services.ts +50 -0
- package/template/apps/server/src/audit/drizzle-log-store.ts +25 -0
- package/template/apps/server/src/audit/search-summary.ts +2 -6
- package/template/apps/server/src/auth/credential-restriction.ts +1 -0
- package/template/apps/server/src/auth/password.ts +30 -21
- package/template/apps/server/src/auth/routes.test.ts +44 -3
- package/template/apps/server/src/auth/routes.ts +6 -1
- package/template/apps/server/src/auth/runtime.ts +4 -1
- package/template/apps/server/src/auth/service.ts +25 -7
- package/template/apps/server/src/configuration/bootstrap.ts +20 -1
- package/template/apps/server/src/configuration/definition.test.ts +71 -0
- package/template/apps/server/src/configuration/definition.ts +242 -12
- package/template/apps/server/src/configuration/drizzle-repository.ts +36 -2
- package/template/apps/server/src/configuration/repository.ts +27 -2
- package/template/apps/server/src/configuration/routes.ts +4 -1
- package/template/apps/server/src/configuration/service.test.ts +100 -0
- package/template/apps/server/src/configuration/service.ts +50 -10
- package/template/apps/server/src/data-access/scoped-repository.integration.test.ts +3 -2
- package/template/apps/server/src/data-governance/export/service.ts +2 -6
- package/template/apps/server/src/example-modules.ts +4 -1
- package/template/apps/server/src/guards.ts +18 -5
- package/template/apps/server/src/http/request-source.test.ts +85 -0
- package/template/apps/server/src/http/request-source.ts +90 -0
- package/template/apps/server/src/index.ts +23 -6
- package/template/apps/server/src/license/routes.ts +2 -0
- package/template/apps/server/src/license/runtime-service.ts +10 -11
- package/template/apps/server/src/module-bootstrap.ts +2 -2
- package/template/apps/server/src/module-host/request-context.ts +6 -6
- package/template/apps/server/src/module-host/storage-scope.ts +2 -9
- package/template/apps/server/src/module-host/user-directory-port.ts +2 -6
- package/template/apps/server/src/notification/channel-adapter.ts +46 -0
- package/template/apps/server/src/notification/delivery-policy.test.ts +59 -0
- package/template/apps/server/src/notification/delivery-policy.ts +37 -0
- package/template/apps/server/src/notification/delivery-repository.ts +198 -0
- package/template/apps/server/src/notification/delivery-worker.test.ts +87 -0
- package/template/apps/server/src/notification/delivery-worker.ts +88 -0
- package/template/apps/server/src/notification/drizzle-management.ts +180 -0
- package/template/apps/server/src/notification/drizzle-query.ts +160 -0
- package/template/apps/server/src/notification/drizzle-repository.integration.test.ts +301 -0
- package/template/apps/server/src/notification/drizzle-repository.ts +156 -90
- package/template/apps/server/src/notification/extension-routes.test.ts +119 -0
- package/template/apps/server/src/notification/extension-routes.ts +111 -0
- package/template/apps/server/src/notification/memory-delivery-repository.ts +137 -0
- package/template/apps/server/src/notification/memory-support.ts +72 -0
- package/template/apps/server/src/notification/preference-repository.ts +95 -0
- package/template/apps/server/src/notification/recipient-directory.ts +104 -0
- package/template/apps/server/src/notification/repository.test.ts +171 -0
- package/template/apps/server/src/notification/repository.ts +179 -32
- package/template/apps/server/src/notification/revision-routes.test.ts +186 -0
- package/template/apps/server/src/notification/route-support.ts +225 -0
- package/template/apps/server/src/notification/routes.test.ts +182 -0
- package/template/apps/server/src/notification/routes.ts +225 -132
- package/template/apps/server/src/notification/service.test.ts +104 -0
- package/template/apps/server/src/notification/service.ts +140 -0
- package/template/apps/server/src/notification/smtp-adapter.test.ts +65 -0
- package/template/apps/server/src/notification/smtp-adapter.ts +68 -0
- package/template/apps/server/src/persistence.integration.test.ts +41 -2
- package/template/apps/server/src/route-guards.ts +1 -0
- package/template/apps/server/src/runtime-audit-list.test.ts +39 -0
- package/template/apps/server/src/runtime-development.ts +1 -0
- package/template/apps/server/src/runtime.test.ts +23 -9
- package/template/apps/server/src/runtime.ts +39 -2
- package/template/apps/server/src/scheduler/audit.ts +2 -11
- package/template/apps/server/src/scheduler/definitions.test.ts +6 -0
- package/template/apps/server/src/scheduler/definitions.ts +7 -0
- package/template/apps/server/src/scheduler/recovery-service.ts +2 -11
- package/template/apps/server/src/scheduler/routes.ts +2 -7
- package/template/apps/server/src/search/opensearch-client.test.ts +45 -0
- package/template/apps/server/src/search/opensearch-client.ts +145 -0
- package/template/apps/server/src/search/service.ts +2 -6
- package/template/apps/server/src/settings/routes.ts +15 -3
- package/template/apps/server/src/settings/runtime.ts +10 -1
- package/template/apps/server/src/storage/runtime.ts +13 -0
- package/template/apps/server/src/system/audit-query.ts +33 -4
- package/template/apps/server/src/system/audit-routes.test.ts +58 -4
- package/template/apps/server/src/system/audit-routes.ts +6 -5
- package/template/apps/server/src/system/department-organization.ts +320 -0
- package/template/apps/server/src/system/department-routes.test.ts +207 -48
- package/template/apps/server/src/system/department-routes.ts +114 -33
- package/template/apps/server/src/system/drizzle-repository.ts +15 -7
- package/template/apps/server/src/system/drizzle-scoped-repository.ts +5 -3
- package/template/apps/server/src/system/drizzle-system.ts +37 -9
- package/template/apps/server/src/system/drizzle-transaction-context.ts +14 -0
- package/template/apps/server/src/system/identity-route-config.ts +7 -2
- package/template/apps/server/src/system/mutation-runner.ts +23 -0
- package/template/apps/server/src/system/organization-postgres.integration.test.ts +176 -0
- package/template/apps/server/src/system/resource-access-response.ts +12 -6
- package/template/apps/server/src/system/role-department-scope-policy.ts +55 -0
- package/template/apps/server/src/system/route-helpers.ts +2 -5
- package/template/apps/server/src/system/route-types.ts +12 -3
- package/template/apps/server/src/system/routes-validation.test.ts +26 -0
- package/template/apps/server/src/system/routes.test.ts +10 -2
- package/template/apps/server/src/system/routes.ts +70 -36
- package/template/apps/server/src/system/seed.ts +7 -0
- package/template/apps/server/src/work-order/data-scope.integration.test.ts +9 -2
- package/template/apps/server/src/work-order/opensearch-index.test.ts +97 -0
- package/template/apps/server/src/work-order/opensearch-index.ts +313 -0
- package/template/apps/server/src/work-order/search-provider.ts +60 -1
- package/template/apps/server/src/work-order/service.ts +42 -10
- package/template/apps/web/src/app/error-pages.ts +9 -0
- package/template/apps/web/src/components/auth/PasswordChangeForm.vue +16 -11
- package/template/apps/web/src/components/auth/PasswordChangeForm.webtest.ts +14 -0
- package/template/apps/web/src/components/common/FormDialog.vue +16 -2
- package/template/apps/web/src/components/common/ModalLayout.webtest.ts +20 -1
- package/template/apps/web/src/composables/useGlobalSearch.ts +68 -0
- package/template/apps/web/src/composables/usePlatformSettings.ts +8 -7
- package/template/apps/web/src/layout/GlobalSearchResults.vue +58 -0
- package/template/apps/web/src/layout/NavigationSearchDialog.vue +42 -6
- package/template/apps/web/src/layout/NavigationSearchDialog.webtest.ts +71 -0
- package/template/apps/web/src/layout/NotificationMenu.vue +73 -31
- package/template/apps/web/src/layout/NotificationMenu.webtest.ts +44 -1
- package/template/apps/web/src/layout/NotificationMenuItem.vue +61 -0
- package/template/apps/web/src/layout/NotificationPreferencesDialog.vue +181 -0
- package/template/apps/web/src/layout/NotificationPreferencesDialog.webtest.ts +80 -0
- package/template/apps/web/src/lib/date-time.ts +22 -10
- package/template/apps/web/src/lib/date-time.webtest.ts +9 -1
- package/template/apps/web/src/main.ts +14 -1
- package/template/apps/web/src/pages/auth/ChangePasswordPage.vue +16 -1
- package/template/apps/web/src/pages/configuration/components/ModuleConfigVersionList.vue +1 -0
- package/template/apps/web/src/pages/configuration/components/ModuleConfigurationEditor.vue +10 -0
- package/template/apps/web/src/pages/configuration/components/ModuleConfigurationWorkbench.vue +37 -6
- package/template/apps/web/src/pages/errors/PlatformErrorPage.vue +115 -0
- package/template/apps/web/src/pages/errors/PlatformErrorPage.webtest.ts +44 -0
- package/template/apps/web/src/pages/errors/error-page-extension.ts +26 -0
- package/template/apps/web/src/pages/system/SystemNotificationsPage.vue +136 -68
- package/template/apps/web/src/pages/system/SystemNotificationsPage.webtest.ts +224 -12
- package/template/apps/web/src/pages/system/audit-log-presenter.ts +86 -0
- package/template/apps/web/src/pages/system/audit-time-range.ts +28 -0
- package/template/apps/web/src/pages/system/components/AuditActionCell.vue +10 -69
- package/template/apps/web/src/pages/system/components/AuditActionCell.webtest.ts +20 -27
- package/template/apps/web/src/pages/system/components/AuditLogDetailDialog.vue +134 -0
- package/template/apps/web/src/pages/system/components/AuditLogDetailDialog.webtest.ts +76 -0
- package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +116 -52
- package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +52 -15
- package/template/apps/web/src/pages/system/components/NotificationHistoryDialog.vue +129 -0
- package/template/apps/web/src/pages/system/components/NotificationManagementCard.vue +91 -0
- package/template/apps/web/src/pages/system/components/NotificationPublishDialog.vue +121 -0
- package/template/apps/web/src/pages/system/components/NotificationPublishForm.vue +162 -1
- package/template/apps/web/src/pages/system/components/SystemResourceActions.vue +68 -0
- package/template/apps/web/src/pages/system/components/SystemResourceEditor.vue +74 -0
- package/template/apps/web/src/pages/system/components/SystemResourceFilters.vue +3 -4
- package/template/apps/web/src/pages/system/components/SystemResourcePage.vue +91 -98
- package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +68 -0
- package/template/apps/web/src/pages/system/components/SystemResourceTable.vue +20 -1
- package/template/apps/web/src/pages/system/components/SystemResourceTable.webtest.ts +28 -7
- package/template/apps/web/src/pages/system/components/department/DepartmentOrganizationForm.vue +210 -0
- package/template/apps/web/src/pages/system/components/department/DepartmentTransitionDialog.vue +112 -0
- package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.vue +17 -4
- package/template/apps/web/src/pages/system/composables/useDepartmentTransitions.ts +79 -0
- package/template/apps/web/src/pages/system/composables/useNotificationPermissions.ts +14 -0
- package/template/apps/web/src/pages/system/composables/useSystemResource.ts +2 -0
- package/template/apps/web/src/pages/system/composables/useSystemResource.webtest.ts +15 -2
- package/template/apps/web/src/pages/system/resource-config.ts +12 -4
- package/template/apps/web/src/router/error-navigation.ts +50 -0
- package/template/apps/web/src/router/error-navigation.webtest.ts +106 -0
- package/template/apps/web/src/router/index.ts +71 -0
- package/template/apps/web/src/router/index.webtest.ts +29 -0
- package/template/apps/web/src/router/manifest-routes.webtest.ts +4 -0
- package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +12 -0
- package/template/apps/web/src/services/auth-api.ts +5 -0
- package/template/apps/web/src/services/http.ts +43 -6
- package/template/apps/web/src/services/http.webtest.ts +70 -0
- package/template/apps/web/src/services/notification-api.ts +85 -2
- package/template/apps/web/src/services/notification-api.webtest.ts +110 -0
- package/template/apps/web/src/services/search-api.ts +39 -0
- package/template/apps/web/src/services/system-api.ts +18 -0
- package/template/apps/web/vitest.config.ts +1 -0
- package/template/docker-compose.yml +66 -0
- package/template/docs/architecture/ai-runtime.md +14 -5
- package/template/docs/architecture/search-provider-protocol.md +38 -2
- package/template/docs/development/custom-error-pages.md +78 -0
- package/template/docs/development/custom-login-page.md +3 -3
- package/template/docs/platform/agent-runtime.md +32 -6
- package/template/docs/platform/audit-reliability.md +46 -2
- package/template/docs/platform/module-configuration.md +13 -4
- package/template/docs/platform/notifications.md +67 -11
- package/template/docs/platform/organization-membership.md +17 -3
- package/template/docs/platform/system-crud-api.md +16 -0
- package/template/docs/platform/web-system-crud.md +6 -2
- package/template/package.json +4 -0
- package/template/packages/config/index.test.ts +45 -0
- package/template/packages/config/package.json +1 -1
- package/template/packages/config/src/platform.ts +169 -48
- package/template/packages/database/drizzle/0035_material_nightshade.sql +10 -0
- package/template/packages/database/drizzle/0036_hesitant_speed.sql +35 -0
- package/template/packages/database/drizzle/0037_absent_nick_fury.sql +2 -0
- package/template/packages/database/drizzle/0038_uneven_wasp.sql +38 -0
- package/template/packages/database/drizzle/meta/0035_snapshot.json +7331 -0
- package/template/packages/database/drizzle/meta/0036_snapshot.json +7436 -0
- package/template/packages/database/drizzle/meta/0037_snapshot.json +7457 -0
- package/template/packages/database/drizzle/meta/0038_snapshot.json +7749 -0
- package/template/packages/database/drizzle/meta/_journal.json +28 -0
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/schema/identity.ts +7 -0
- package/template/packages/database/src/schema/notifications.ts +135 -0
- package/template/packages/database/src/schema-notification-delivery.test.ts +54 -0
- package/template/packages/database/src/schema-workflow-notification.test.ts +40 -1
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/system-api-permissions.ts +54 -0
- package/template/packages/modules/src/system-audit-actions.ts +10 -0
- package/template/packages/modules/src/system-features.ts +1 -1
- package/template/packages/modules/src/system-module-catalog.ts +2 -1
- package/template/packages/modules/src/system-modules.test.ts +13 -1
- package/template/packages/modules/src/system-modules.ts +12 -0
- package/template/packages/modules/src/system-permissions.ts +9 -5
- package/template/packages/server-sdk/package.json +1 -1
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/audit.ts +25 -0
- package/template/packages/shared/src/module-configuration.ts +5 -0
- package/template/packages/shared/src/notification.ts +92 -1
- package/template/packages/shared/src/password.ts +7 -2
- package/template/packages/shared/src/platform-settings.ts +2 -0
- package/template/packages/storage/package.json +1 -1
- package/template/packages/storage/src/upload-service.test.ts +16 -0
- package/template/packages/storage/src/upload-service.ts +5 -1
- package/template/scripts/notification-postgres-regression.ts +27 -0
|
@@ -81,6 +81,106 @@ describe("模块配置版本服务", () => {
|
|
|
81
81
|
expect(serialized).not.toContain("secret-value-marker");
|
|
82
82
|
expect(serialized).not.toContain("SecretRef");
|
|
83
83
|
});
|
|
84
|
+
|
|
85
|
+
test("重启字段保存为等待状态并在下一次启动提升为生效版本", async () => {
|
|
86
|
+
const repository = new InMemoryModuleConfigVersionRepository();
|
|
87
|
+
const current = { host: "old.smtp.local" };
|
|
88
|
+
const definition: ManagedModuleConfigDefinition = {
|
|
89
|
+
moduleKey: "mail.runtime",
|
|
90
|
+
label: "邮件传输配置",
|
|
91
|
+
fields: [
|
|
92
|
+
{
|
|
93
|
+
path: "host",
|
|
94
|
+
label: "SMTP 主机",
|
|
95
|
+
description: "重启后应用",
|
|
96
|
+
input: "text",
|
|
97
|
+
applyMode: "restart",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
validate(values) {
|
|
101
|
+
const input = values as Record<string, unknown>;
|
|
102
|
+
return { host: String(input.host || current.host) };
|
|
103
|
+
},
|
|
104
|
+
apply(values) {
|
|
105
|
+
current.host = values.host as string;
|
|
106
|
+
},
|
|
107
|
+
baseValues: () => ({ ...current }),
|
|
108
|
+
};
|
|
109
|
+
const service = new ModuleConfigurationService(repository, [definition]);
|
|
110
|
+
|
|
111
|
+
const created = await service.create(
|
|
112
|
+
"mail.runtime",
|
|
113
|
+
{ host: "new.smtp.local" },
|
|
114
|
+
"user-1",
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
expect(created).toMatchObject({
|
|
118
|
+
ok: true,
|
|
119
|
+
version: { applyStatus: "pending-restart" },
|
|
120
|
+
});
|
|
121
|
+
expect(current.host).toBe("old.smtp.local");
|
|
122
|
+
|
|
123
|
+
const replacement = await service.create(
|
|
124
|
+
"mail.runtime",
|
|
125
|
+
{ host: "corrected.smtp.local" },
|
|
126
|
+
"user-1",
|
|
127
|
+
);
|
|
128
|
+
expect(replacement).toMatchObject({
|
|
129
|
+
ok: true,
|
|
130
|
+
version: { version: 2, applyStatus: "pending-restart" },
|
|
131
|
+
});
|
|
132
|
+
expect((await service.summaries())[0]?.pendingValues).toEqual({
|
|
133
|
+
host: "corrected.smtp.local",
|
|
134
|
+
});
|
|
135
|
+
expect(
|
|
136
|
+
(await service.list("mail.runtime"))?.map((item) => item.applyStatus),
|
|
137
|
+
).toEqual(["pending-restart", "superseded"]);
|
|
138
|
+
|
|
139
|
+
await new ModuleConfigurationService(repository, [definition]).initialize();
|
|
140
|
+
|
|
141
|
+
expect(current.host).toBe("corrected.smtp.local");
|
|
142
|
+
expect((await repository.latest("mail.runtime"))?.applyStatus).toBe(
|
|
143
|
+
"effective",
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("旧版本缺少新增字段时使用当前基线补齐", async () => {
|
|
148
|
+
const current = { enabled: false, timeoutMs: 300 };
|
|
149
|
+
const schema = z.strictObject({
|
|
150
|
+
enabled: z.boolean(),
|
|
151
|
+
timeoutMs: z.number().int(),
|
|
152
|
+
});
|
|
153
|
+
const definition: ManagedModuleConfigDefinition = {
|
|
154
|
+
moduleKey: "compatible.runtime",
|
|
155
|
+
label: "兼容配置",
|
|
156
|
+
fields: [],
|
|
157
|
+
validate(values) {
|
|
158
|
+
return schema.parse({
|
|
159
|
+
...current,
|
|
160
|
+
...(values as Record<string, unknown>),
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
apply(values) {
|
|
164
|
+
current.enabled = values.enabled as boolean;
|
|
165
|
+
current.timeoutMs = values.timeoutMs as number;
|
|
166
|
+
},
|
|
167
|
+
baseValues: () => ({ ...current }),
|
|
168
|
+
};
|
|
169
|
+
const repository = new InMemoryModuleConfigVersionRepository();
|
|
170
|
+
const version = await repository.begin({
|
|
171
|
+
moduleKey: definition.moduleKey,
|
|
172
|
+
values: { enabled: true },
|
|
173
|
+
diff: [],
|
|
174
|
+
actorId: "legacy",
|
|
175
|
+
});
|
|
176
|
+
await repository.markEffective(version.id, new Date());
|
|
177
|
+
const service = new ModuleConfigurationService(repository, [definition]);
|
|
178
|
+
|
|
179
|
+
await service.initialize();
|
|
180
|
+
|
|
181
|
+
expect(current).toEqual({ enabled: true, timeoutMs: 300 });
|
|
182
|
+
expect((await service.summaries())[0]?.effectiveValues).toEqual(current);
|
|
183
|
+
});
|
|
84
184
|
});
|
|
85
185
|
|
|
86
186
|
function createFixture(failApply = false) {
|
|
@@ -36,14 +36,21 @@ export class ModuleConfigurationService {
|
|
|
36
36
|
|
|
37
37
|
async initialize(): Promise<void> {
|
|
38
38
|
for (const definition of this.definitions.values()) {
|
|
39
|
-
const
|
|
40
|
-
|
|
39
|
+
const latest = await this.repository.latest(definition.moduleKey);
|
|
40
|
+
const candidate =
|
|
41
|
+
latest?.applyStatus === "pending-restart"
|
|
42
|
+
? latest
|
|
43
|
+
: await this.repository.effective(definition.moduleKey);
|
|
44
|
+
if (!candidate) continue;
|
|
41
45
|
try {
|
|
42
|
-
const values = definition.validate(
|
|
46
|
+
const values = definition.validate(candidate.values);
|
|
43
47
|
await definition.apply(values);
|
|
48
|
+
if (candidate.applyStatus === "pending-restart") {
|
|
49
|
+
await this.repository.markEffective(candidate.id, new Date());
|
|
50
|
+
}
|
|
44
51
|
} catch (error) {
|
|
45
52
|
await this.repository.markFailed(
|
|
46
|
-
|
|
53
|
+
candidate.id,
|
|
47
54
|
sanitizeApplyError(error),
|
|
48
55
|
new Date(),
|
|
49
56
|
);
|
|
@@ -62,9 +69,13 @@ export class ModuleConfigurationService {
|
|
|
62
69
|
moduleKey: definition.moduleKey,
|
|
63
70
|
label: definition.label,
|
|
64
71
|
fields: definition.fields.map((field) => ({ ...field })),
|
|
65
|
-
effectiveValues:
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
effectiveValues: definition.validate(
|
|
73
|
+
effective?.values || definition.baseValues(),
|
|
74
|
+
),
|
|
75
|
+
pendingValues:
|
|
76
|
+
latest?.applyStatus === "pending-restart"
|
|
77
|
+
? definition.validate(latest.values)
|
|
78
|
+
: undefined,
|
|
68
79
|
effectiveSource: effective ? "version" : "environment",
|
|
69
80
|
effectiveVersion: effective?.version,
|
|
70
81
|
latestVersion: latest?.version,
|
|
@@ -129,16 +140,33 @@ export class ModuleConfigurationService {
|
|
|
129
140
|
reason?: string,
|
|
130
141
|
rollbackOfVersion?: number,
|
|
131
142
|
): Promise<ConfigVersionResult> {
|
|
132
|
-
const effective = await
|
|
133
|
-
|
|
143
|
+
const [effective, latest] = await Promise.all([
|
|
144
|
+
this.repository.effective(definition.moduleKey),
|
|
145
|
+
this.repository.latest(definition.moduleKey),
|
|
146
|
+
]);
|
|
147
|
+
const before = definition.validate(
|
|
148
|
+
latest?.applyStatus === "pending-restart"
|
|
149
|
+
? latest.values
|
|
150
|
+
: effective?.values || definition.baseValues(),
|
|
151
|
+
);
|
|
152
|
+
const diff = configDiff(before, values);
|
|
134
153
|
const version = await this.repository.begin({
|
|
135
154
|
moduleKey: definition.moduleKey,
|
|
136
155
|
values,
|
|
137
|
-
diff
|
|
156
|
+
diff,
|
|
138
157
|
rollbackOfVersion,
|
|
139
158
|
reason,
|
|
140
159
|
actorId,
|
|
141
160
|
});
|
|
161
|
+
if (
|
|
162
|
+
latest?.applyStatus === "pending-restart" ||
|
|
163
|
+
requiresRestart(definition, diff)
|
|
164
|
+
) {
|
|
165
|
+
return {
|
|
166
|
+
ok: true,
|
|
167
|
+
version: await this.repository.markPendingRestart(version.id),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
142
170
|
try {
|
|
143
171
|
await definition.apply(values);
|
|
144
172
|
return {
|
|
@@ -158,6 +186,18 @@ export class ModuleConfigurationService {
|
|
|
158
186
|
}
|
|
159
187
|
}
|
|
160
188
|
|
|
189
|
+
function requiresRestart(
|
|
190
|
+
definition: ManagedModuleConfigDefinition,
|
|
191
|
+
diff: ModuleConfigDiffEntry[],
|
|
192
|
+
): boolean {
|
|
193
|
+
const restartPaths = new Set(
|
|
194
|
+
definition.fields
|
|
195
|
+
.filter((field) => field.applyMode === "restart")
|
|
196
|
+
.map((field) => field.path),
|
|
197
|
+
);
|
|
198
|
+
return diff.some((entry) => restartPaths.has(entry.path));
|
|
199
|
+
}
|
|
200
|
+
|
|
161
201
|
export function configDiff(
|
|
162
202
|
before: ModuleConfigValues,
|
|
163
203
|
after: ModuleConfigValues,
|
|
@@ -133,9 +133,10 @@ function createRepositories(pool: Pool) {
|
|
|
133
133
|
async function seedDepartments(
|
|
134
134
|
repository: ReturnType<typeof createRepositories>["departments"],
|
|
135
135
|
) {
|
|
136
|
-
await repository.create(department("
|
|
136
|
+
await repository.create(department("org_root"), setupActor);
|
|
137
|
+
await repository.create(department("dept_a", "org_root"), setupActor);
|
|
137
138
|
await repository.create(department("dept_a_child", "dept_a"), setupActor);
|
|
138
|
-
await repository.create(department("dept_b"), setupActor);
|
|
139
|
+
await repository.create(department("dept_b", "org_root"), setupActor);
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
async function seedUsers(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { GovernedExportQuerySnapshot } from "@southwind-ai/database";
|
|
2
2
|
import type { AuditEvent } from "@southwind-ai/shared";
|
|
3
|
-
import type
|
|
3
|
+
import { auditActor, type RequestGuardContext } from "../../guards.js";
|
|
4
4
|
import {
|
|
5
5
|
DurableArtifactError,
|
|
6
6
|
type DurableArtifactService,
|
|
@@ -310,11 +310,7 @@ function event(
|
|
|
310
310
|
id: Bun.randomUUIDv7(),
|
|
311
311
|
action,
|
|
312
312
|
outcome: "success",
|
|
313
|
-
actor:
|
|
314
|
-
id: context.actor.id,
|
|
315
|
-
type: context.actor.type === "anonymous" ? "user" : context.actor.type,
|
|
316
|
-
name: context.actor.name,
|
|
317
|
-
},
|
|
313
|
+
actor: auditActor(context),
|
|
318
314
|
target: { type: "system.user.governed-export", id },
|
|
319
315
|
requestId: context.requestId,
|
|
320
316
|
metadata,
|
|
@@ -17,6 +17,7 @@ import { createWorkOrderRepository } from "./work-order/repository.js";
|
|
|
17
17
|
import { createDrizzleWorkOrderRepository } from "./work-order/drizzle-repository.js";
|
|
18
18
|
import { createWorkOrderRouteDefinitions } from "./work-order/routes.js";
|
|
19
19
|
import { WorkOrderSearchProvider } from "./work-order/search-provider.js";
|
|
20
|
+
import { createWorkOrderOpenSearchIndex } from "./work-order/opensearch-index.js";
|
|
20
21
|
import { WorkOrderService } from "./work-order/service.js";
|
|
21
22
|
|
|
22
23
|
export interface WorkOrderModuleDependencies {
|
|
@@ -42,6 +43,7 @@ export interface WorkOrderModule {
|
|
|
42
43
|
*/
|
|
43
44
|
export function createWorkOrderModule(): WorkOrderModule {
|
|
44
45
|
let service: WorkOrderService | undefined;
|
|
46
|
+
const searchIndex = createWorkOrderOpenSearchIndex(process.env);
|
|
45
47
|
const requireService = () => {
|
|
46
48
|
if (!service) throw new Error("示例工单模块尚未挂载");
|
|
47
49
|
return service;
|
|
@@ -52,7 +54,7 @@ export function createWorkOrderModule(): WorkOrderModule {
|
|
|
52
54
|
requireService().operationalSnapshot(
|
|
53
55
|
platformDataActor("task_work_order_snapshot", "工单运营快照任务"),
|
|
54
56
|
),
|
|
55
|
-
searchProvider: new WorkOrderSearchProvider(requireService),
|
|
57
|
+
searchProvider: new WorkOrderSearchProvider(requireService, searchIndex),
|
|
56
58
|
});
|
|
57
59
|
return {
|
|
58
60
|
...serverModule,
|
|
@@ -67,6 +69,7 @@ export function createWorkOrderModule(): WorkOrderModule {
|
|
|
67
69
|
? createDrizzleWorkOrderRepository(dependencies.db)
|
|
68
70
|
: createWorkOrderRepository()),
|
|
69
71
|
dependencies.departments,
|
|
72
|
+
searchIndex,
|
|
70
73
|
);
|
|
71
74
|
},
|
|
72
75
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ApiPermissionBinding,
|
|
3
|
+
AuditActor,
|
|
3
4
|
AuditEvent,
|
|
4
5
|
EntitlementKey,
|
|
5
6
|
FeatureFlagDefinition,
|
|
@@ -33,6 +34,7 @@ export interface RequestGuardContext {
|
|
|
33
34
|
actor: RequestActor;
|
|
34
35
|
startedAt: string;
|
|
35
36
|
clientIp?: string;
|
|
37
|
+
userAgent?: string;
|
|
36
38
|
license?: {
|
|
37
39
|
versionKey?: LicenseVersionKey;
|
|
38
40
|
/** V1 兼容字段。新 License 不再包含 Entitlement。 */
|
|
@@ -61,6 +63,7 @@ export interface GuardDecision {
|
|
|
61
63
|
|
|
62
64
|
export interface RequestGuardContextOptions {
|
|
63
65
|
clientIp?: string;
|
|
66
|
+
userAgent?: string;
|
|
64
67
|
resolveActor?: (request: Request) => RequestActor | undefined;
|
|
65
68
|
resolveLicense?: (
|
|
66
69
|
request: Request,
|
|
@@ -86,10 +89,24 @@ export function createRequestGuardContext(
|
|
|
86
89
|
actor: options.resolveActor?.(request) || anonymousActor(),
|
|
87
90
|
startedAt: new Date().toISOString(),
|
|
88
91
|
clientIp: options.clientIp,
|
|
92
|
+
userAgent: options.userAgent,
|
|
89
93
|
license: options.resolveLicense?.(request),
|
|
90
94
|
};
|
|
91
95
|
}
|
|
92
96
|
|
|
97
|
+
export function auditActor(
|
|
98
|
+
context: Pick<RequestGuardContext, "actor" | "clientIp" | "userAgent">,
|
|
99
|
+
): AuditActor {
|
|
100
|
+
return {
|
|
101
|
+
id: context.actor.id,
|
|
102
|
+
type: context.actor.type === "anonymous" ? "user" : context.actor.type,
|
|
103
|
+
name: context.actor.name,
|
|
104
|
+
roleCodes: context.actor.roleCodes,
|
|
105
|
+
ip: context.clientIp,
|
|
106
|
+
userAgent: context.userAgent,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
export function evaluatePermissionGuard(
|
|
94
111
|
context: RequestGuardContext,
|
|
95
112
|
binding: PermissionGuardRequirement,
|
|
@@ -207,11 +224,7 @@ function deniedDecision(
|
|
|
207
224
|
id: randomUUIDv7(),
|
|
208
225
|
action: "security.denied",
|
|
209
226
|
outcome: "denied",
|
|
210
|
-
actor:
|
|
211
|
-
id: context.actor.id,
|
|
212
|
-
type: context.actor.type === "anonymous" ? "user" : context.actor.type,
|
|
213
|
-
name: context.actor.name,
|
|
214
|
-
},
|
|
227
|
+
actor: auditActor(context),
|
|
215
228
|
target: {
|
|
216
229
|
type: "guard",
|
|
217
230
|
id: targetId,
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
requestSourcePolicyFromEnv,
|
|
4
|
+
resolveRequestSource,
|
|
5
|
+
} from "./request-source.js";
|
|
6
|
+
|
|
7
|
+
describe("平台请求来源信任边界", () => {
|
|
8
|
+
test("默认使用直接对端并忽略客户端伪造的转发头", () => {
|
|
9
|
+
const source = resolveRequestSource(
|
|
10
|
+
request("203.0.113.66"),
|
|
11
|
+
"198.51.100.10",
|
|
12
|
+
requestSourcePolicyFromEnv({}),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
expect(source).toEqual({
|
|
16
|
+
clientIp: "198.51.100.10",
|
|
17
|
+
userAgent: "Windy-Test/1.0",
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("仅显式启用且直接对端受信时采用单一合法转发来源", () => {
|
|
22
|
+
const policy = requestSourcePolicyFromEnv({
|
|
23
|
+
WINDY_TRUST_PROXY: "true",
|
|
24
|
+
WINDY_TRUSTED_PROXY_IPS: "10.0.0.8",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
expect(
|
|
28
|
+
resolveRequestSource(request("203.0.113.66"), "::1", policy).clientIp,
|
|
29
|
+
).toBe("203.0.113.66");
|
|
30
|
+
expect(
|
|
31
|
+
resolveRequestSource(request("203.0.113.67"), "10.0.0.8", policy)
|
|
32
|
+
.clientIp,
|
|
33
|
+
).toBe("203.0.113.67");
|
|
34
|
+
expect(
|
|
35
|
+
resolveRequestSource(request("203.0.113.68"), "10.0.0.9", policy)
|
|
36
|
+
.clientIp,
|
|
37
|
+
).toBe("10.0.0.9");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("多段或非法转发来源退回直接对端", () => {
|
|
41
|
+
const policy = requestSourcePolicyFromEnv({
|
|
42
|
+
WINDY_TRUST_PROXY: "true",
|
|
43
|
+
});
|
|
44
|
+
for (const forwardedFor of [
|
|
45
|
+
"203.0.113.66, 198.51.100.9",
|
|
46
|
+
"not-an-ip",
|
|
47
|
+
"203.0.113.66:443",
|
|
48
|
+
]) {
|
|
49
|
+
expect(
|
|
50
|
+
resolveRequestSource(request(forwardedFor), "127.0.0.1", policy)
|
|
51
|
+
.clientIp,
|
|
52
|
+
).toBe("127.0.0.1");
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("代理配置默认关闭且错误配置 fail closed", () => {
|
|
57
|
+
expect(requestSourcePolicyFromEnv({})).toEqual({
|
|
58
|
+
trustProxy: false,
|
|
59
|
+
trustedProxyIps: [],
|
|
60
|
+
});
|
|
61
|
+
expect(() =>
|
|
62
|
+
requestSourcePolicyFromEnv({
|
|
63
|
+
WINDY_TRUSTED_PROXY_IPS: "10.0.0.8",
|
|
64
|
+
}),
|
|
65
|
+
).toThrow("WINDY_TRUST_PROXY=true");
|
|
66
|
+
expect(() =>
|
|
67
|
+
requestSourcePolicyFromEnv({ WINDY_TRUST_PROXY: "yes" }),
|
|
68
|
+
).toThrow("只允许 true 或 false");
|
|
69
|
+
expect(() =>
|
|
70
|
+
requestSourcePolicyFromEnv({
|
|
71
|
+
WINDY_TRUST_PROXY: "true",
|
|
72
|
+
WINDY_TRUSTED_PROXY_IPS: "10.0.0.999",
|
|
73
|
+
}),
|
|
74
|
+
).toThrow("无效 IP");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function request(forwardedFor: string): Request {
|
|
79
|
+
return new Request("https://windy.example.test/api/system/users", {
|
|
80
|
+
headers: {
|
|
81
|
+
"user-agent": "Windy-Test/1.0",
|
|
82
|
+
"x-forwarded-for": forwardedFor,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
|
|
3
|
+
export interface RequestSourcePolicy {
|
|
4
|
+
trustProxy: boolean;
|
|
5
|
+
trustedProxyIps: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface RequestSource {
|
|
9
|
+
clientIp?: string;
|
|
10
|
+
userAgent?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function requestSourcePolicyFromEnv(
|
|
14
|
+
env: Record<string, string | undefined>,
|
|
15
|
+
): RequestSourcePolicy {
|
|
16
|
+
const trustProxy = readBoolean(env.WINDY_TRUST_PROXY);
|
|
17
|
+
const configuredIps = (env.WINDY_TRUSTED_PROXY_IPS || "")
|
|
18
|
+
.split(",")
|
|
19
|
+
.map((value) => value.trim())
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
if (!trustProxy && configuredIps.length) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"配置 WINDY_TRUSTED_PROXY_IPS 时必须显式设置 WINDY_TRUST_PROXY=true",
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
trustProxy,
|
|
28
|
+
trustedProxyIps: configuredIps.map((value) => {
|
|
29
|
+
const normalized = normalizeIp(value);
|
|
30
|
+
if (!normalized) {
|
|
31
|
+
throw new Error(`WINDY_TRUSTED_PROXY_IPS 包含无效 IP:${value}`);
|
|
32
|
+
}
|
|
33
|
+
return normalized;
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveRequestSource(
|
|
39
|
+
request: Request,
|
|
40
|
+
peerIp: string | undefined,
|
|
41
|
+
policy: RequestSourcePolicy,
|
|
42
|
+
): RequestSource {
|
|
43
|
+
const normalizedPeerIp = normalizeIp(peerIp);
|
|
44
|
+
const forwardedIp = resolveForwardedIp(request, normalizedPeerIp, policy);
|
|
45
|
+
return {
|
|
46
|
+
clientIp: forwardedIp || normalizedPeerIp,
|
|
47
|
+
userAgent: normalizeUserAgent(request.headers.get("user-agent")),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveForwardedIp(
|
|
52
|
+
request: Request,
|
|
53
|
+
peerIp: string | undefined,
|
|
54
|
+
policy: RequestSourcePolicy,
|
|
55
|
+
): string | undefined {
|
|
56
|
+
if (!policy.trustProxy || !peerIp || !isTrustedProxy(peerIp, policy)) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const header = request.headers.get("x-forwarded-for")?.trim();
|
|
60
|
+
if (!header || header.includes(",")) return undefined;
|
|
61
|
+
return normalizeIp(header);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isTrustedProxy(peerIp: string, policy: RequestSourcePolicy): boolean {
|
|
65
|
+
if (isLoopback(peerIp)) return true;
|
|
66
|
+
return policy.trustedProxyIps.some((value) => normalizeIp(value) === peerIp);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isLoopback(ip: string): boolean {
|
|
70
|
+
return ip === "::1" || (isIP(ip) === 4 && ip.startsWith("127."));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function normalizeIp(value: string | undefined): string | undefined {
|
|
74
|
+
const candidate = value?.trim().toLowerCase();
|
|
75
|
+
if (!candidate) return undefined;
|
|
76
|
+
const mapped = candidate.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/)?.[1];
|
|
77
|
+
const normalized = mapped && isIP(mapped) === 4 ? mapped : candidate;
|
|
78
|
+
return isIP(normalized) ? normalized : undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function normalizeUserAgent(value: string | null): string | undefined {
|
|
82
|
+
const normalized = value?.trim();
|
|
83
|
+
return normalized ? normalized.slice(0, 1_024) : undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function readBoolean(value: string | undefined): boolean {
|
|
87
|
+
if (value === undefined || value === "" || value === "false") return false;
|
|
88
|
+
if (value === "true") return true;
|
|
89
|
+
throw new Error("WINDY_TRUST_PROXY 只允许 true 或 false");
|
|
90
|
+
}
|
|
@@ -141,7 +141,11 @@ platformLicenseRuntime.setAuditSink(runtime.auditSink);
|
|
|
141
141
|
// @windy-module system.license end
|
|
142
142
|
const { authRepository, authService } = createAuthRuntime({
|
|
143
143
|
repository: persistence?.authRepository,
|
|
144
|
-
sessionTtlSeconds: platformConfig.security.sessionTtlSeconds,
|
|
144
|
+
sessionTtlSeconds: () => platformConfig.security.sessionTtlSeconds,
|
|
145
|
+
passwordPolicy: () => ({
|
|
146
|
+
minLength: platformConfig.security.passwordMinLength,
|
|
147
|
+
maxLength: platformConfig.security.passwordMaxLength,
|
|
148
|
+
}),
|
|
145
149
|
auditWriter: (event) => runtime.auditSink.recordCriticalEvent(event),
|
|
146
150
|
// @windy-module system.audit begin
|
|
147
151
|
auditReader: persistence?.auditReader,
|
|
@@ -153,12 +157,17 @@ const storageRuntime = createStorageRuntime(
|
|
|
153
157
|
persistence?.db,
|
|
154
158
|
moduleHosts.uploadPolicies,
|
|
155
159
|
);
|
|
160
|
+
configurationRuntime.bindStorage(storageRuntime.uploads);
|
|
156
161
|
// @windy-module system.storage end
|
|
157
162
|
// @windy-module system.settings begin
|
|
158
|
-
const platformSettings = createPlatformSettingsRuntime(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
const platformSettings = createPlatformSettingsRuntime(
|
|
164
|
+
persistence?.db,
|
|
165
|
+
{
|
|
166
|
+
softwareName: platformConfig.name,
|
|
167
|
+
logoUrl: platformConfig.logoUrl,
|
|
168
|
+
},
|
|
169
|
+
() => ({ timeZone: platformConfig.regional.timeZone }),
|
|
170
|
+
);
|
|
162
171
|
// @windy-module system.settings end
|
|
163
172
|
// @windy-module system.search begin
|
|
164
173
|
const searchHost = createSearchHost(
|
|
@@ -177,6 +186,7 @@ const {
|
|
|
177
186
|
scheduler,
|
|
178
187
|
taskDefinitions: scheduledTaskDefinitions,
|
|
179
188
|
notifications: notificationRepository,
|
|
189
|
+
notificationService,
|
|
180
190
|
workflow: workflowService,
|
|
181
191
|
governedExportService,
|
|
182
192
|
durable: durableJobRuntime,
|
|
@@ -188,6 +198,7 @@ const {
|
|
|
188
198
|
storage: storageRuntime,
|
|
189
199
|
moduleTasks: moduleTaskDefinitions,
|
|
190
200
|
bulkRetentionDays: readPositiveInt(process.env.BULK_DATA_RETENTION_DAYS, 7),
|
|
201
|
+
mailConfig: platformConfig.mail,
|
|
191
202
|
installModuleDurableHandlers: moduleHosts.installDurableHandlers,
|
|
192
203
|
});
|
|
193
204
|
const schedulerWorker = new SchedulerWorker(
|
|
@@ -220,7 +231,13 @@ registerPlatformLicenseRoutes(app, platformLicenseRuntime);
|
|
|
220
231
|
platformSettings.register(app, runtime, features);
|
|
221
232
|
// @windy-module system.settings end
|
|
222
233
|
// @windy-module system.notification begin
|
|
223
|
-
registerNotificationRoutes(
|
|
234
|
+
registerNotificationRoutes(
|
|
235
|
+
app,
|
|
236
|
+
notificationRepository,
|
|
237
|
+
runtime,
|
|
238
|
+
features,
|
|
239
|
+
notificationService,
|
|
240
|
+
);
|
|
224
241
|
// @windy-module system.notification end
|
|
225
242
|
// @windy-module system.workflow begin
|
|
226
243
|
registerWorkflowRoutes(app, workflowService, runtime);
|
|
@@ -52,6 +52,8 @@ export function registerPlatformLicenseRoutes(
|
|
|
52
52
|
const installed = await licenseRuntime.install(rawLicense, {
|
|
53
53
|
actor: context.guardContext.actor,
|
|
54
54
|
requestId: context.guardContext.requestId,
|
|
55
|
+
clientIp: context.guardContext.clientIp,
|
|
56
|
+
userAgent: context.guardContext.userAgent,
|
|
55
57
|
});
|
|
56
58
|
if (installed.result.status === "rejected") {
|
|
57
59
|
return Response.json(
|
|
@@ -17,7 +17,11 @@ import type {
|
|
|
17
17
|
LicenseFailureReason,
|
|
18
18
|
SignedLicense,
|
|
19
19
|
} from "@southwind-ai/shared";
|
|
20
|
-
import
|
|
20
|
+
import {
|
|
21
|
+
auditActor,
|
|
22
|
+
type RequestActor,
|
|
23
|
+
type RequestGuardContext,
|
|
24
|
+
} from "../guards.js";
|
|
21
25
|
import type { InMemoryAuditSink } from "../runtime.js";
|
|
22
26
|
import type {
|
|
23
27
|
LicenseEntitlementResolver,
|
|
@@ -57,10 +61,10 @@ export interface PlatformLicenseRuntimeOptions {
|
|
|
57
61
|
now?: () => Date;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
export
|
|
61
|
-
|
|
62
|
-
requestId
|
|
63
|
-
|
|
64
|
+
export type LicenseInstallActorContext = Pick<
|
|
65
|
+
RequestGuardContext,
|
|
66
|
+
"actor" | "requestId" | "clientIp" | "userAgent"
|
|
67
|
+
>;
|
|
64
68
|
|
|
65
69
|
export class PlatformLicenseRuntime implements LicenseEntitlementResolver {
|
|
66
70
|
private readonly stateStorage: LicenseStorage;
|
|
@@ -285,12 +289,7 @@ function installAuditEvent(
|
|
|
285
289
|
id: crypto.randomUUID(),
|
|
286
290
|
action: "license.update",
|
|
287
291
|
outcome: success ? "success" : "failure",
|
|
288
|
-
actor:
|
|
289
|
-
id: context.actor.id,
|
|
290
|
-
type: "user",
|
|
291
|
-
name: context.actor.name,
|
|
292
|
-
roleCodes: context.actor.roleCodes,
|
|
293
|
-
},
|
|
292
|
+
actor: auditActor(context),
|
|
294
293
|
target: {
|
|
295
294
|
type: "runtime-license",
|
|
296
295
|
id: result.license?.claims.licenseId || candidate?.claims.licenseId,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "./module-host.js";
|
|
18
18
|
import type { ServerPersistence } from "./persistence.js";
|
|
19
19
|
// @windy-module system.storage begin
|
|
20
|
-
import {
|
|
20
|
+
import { createPlatformUploadPolicies } from "./storage/runtime.js";
|
|
21
21
|
// @windy-module system.storage end
|
|
22
22
|
import {
|
|
23
23
|
createSystemRepositories,
|
|
@@ -51,7 +51,7 @@ export async function bootstrapServerModules(
|
|
|
51
51
|
installations: installedBusinessModules,
|
|
52
52
|
baseUploadPolicies: [
|
|
53
53
|
// @windy-module system.storage begin
|
|
54
|
-
...
|
|
54
|
+
...createPlatformUploadPolicies(platformConfig),
|
|
55
55
|
// @windy-module system.storage end
|
|
56
56
|
],
|
|
57
57
|
});
|
|
@@ -15,7 +15,11 @@ import {
|
|
|
15
15
|
} from "@southwind-ai/server-sdk";
|
|
16
16
|
import type { AuditEvent } from "@southwind-ai/shared";
|
|
17
17
|
import type { ModuleManifest } from "@southwind-ai/modules";
|
|
18
|
-
import
|
|
18
|
+
import {
|
|
19
|
+
auditActor,
|
|
20
|
+
type RequestActor,
|
|
21
|
+
type RequestGuardContext,
|
|
22
|
+
} from "../guards.js";
|
|
19
23
|
import type { createServerRuntime } from "../runtime.js";
|
|
20
24
|
import { unavailableModuleStoragePort } from "./storage-port.js";
|
|
21
25
|
|
|
@@ -130,11 +134,7 @@ function buildAuditEvent(
|
|
|
130
134
|
id: randomUUIDv7(),
|
|
131
135
|
action,
|
|
132
136
|
outcome: "success",
|
|
133
|
-
actor:
|
|
134
|
-
id: context.actor.id,
|
|
135
|
-
type: context.actor.type === "anonymous" ? "user" : context.actor.type,
|
|
136
|
-
name: context.actor.name,
|
|
137
|
-
},
|
|
137
|
+
actor: auditActor(context),
|
|
138
138
|
target: input.target,
|
|
139
139
|
requestId: context.requestId,
|
|
140
140
|
metadata: input.metadata,
|