Remove SCUM sqlite user projections
This commit is contained in:
@@ -397,6 +397,12 @@ describe("PlatformApiClient AI providers", () => {
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/administrators/user-2") && init?.method === "DELETE") {
|
||||
return jsonResponse({ ...server, adminUserIds: [] });
|
||||
}
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/scum/users?limit=10&online=true")) {
|
||||
return jsonResponse({ items: [{ id: "scum-user-1", steamId: "steam-1", online: true }], count: 1 });
|
||||
}
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/scum/vehicle-locks?limit=10&gameVehicleId=veh-1")) {
|
||||
return jsonResponse({ items: [{ id: "lock-1", gameVehicleId: "veh-1", scumUserId: "scum-user-1" }], count: 1 });
|
||||
}
|
||||
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/runtime/actions")) {
|
||||
return jsonResponse(runtimeActions);
|
||||
@@ -602,6 +608,8 @@ describe("PlatformApiClient AI providers", () => {
|
||||
await expect(client.listServerAdministratorCandidates(server.id)).resolves.toMatchObject({ count: 1 });
|
||||
await expect(client.addServerAdministrator(server.id, { userId: "user-2" })).resolves.toMatchObject({ adminUserIds: ["user-admin-1", "user-2"] });
|
||||
await expect(client.removeServerAdministrator(server.id, "user-2")).resolves.toMatchObject({ adminUserIds: [] });
|
||||
await expect(client.listScumUsers(server.id, { limit: 10, online: true })).resolves.toMatchObject({ items: [{ steamId: "steam-1" }], count: 1 });
|
||||
await expect(client.listScumVehicleLocks(server.id, { limit: 10, gameVehicleId: "veh-1" })).resolves.toMatchObject({ items: [{ scumUserId: "scum-user-1" }], count: 1 });
|
||||
|
||||
const runtime = await client.getServerRuntimeActions(server.id);
|
||||
expect(runtime.runStatus).toBe("online");
|
||||
@@ -629,7 +637,7 @@ describe("PlatformApiClient AI providers", () => {
|
||||
client.invokeAI({ requestId: "ai-1", serverInstanceId: server.id, purpose: "config.suggest", prompt: "Tune PVP safely", currentConfig: "server.name=Example Survival #1\n" })
|
||||
).resolves.toMatchObject({ status: "ok", usage: { mocked: true }, configRecommendation: { diffSummary: "review required" } });
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(47);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(49);
|
||||
});
|
||||
|
||||
it("normalizes server file workspace null arrays from older platform responses", async () => {
|
||||
|
||||
@@ -87,6 +87,12 @@ import type {
|
||||
ServerMemberListResponse,
|
||||
ServerMemberRequest,
|
||||
ServerMetricsListResponse,
|
||||
ScumListRequest,
|
||||
ScumUserListResponse,
|
||||
ScumUserTrajectoryListResponse,
|
||||
ScumVehicleLockListResponse,
|
||||
ScumVehicleListResponse,
|
||||
ScumVehicleTrajectoryListResponse,
|
||||
SourceRCONCommandRequest,
|
||||
SourceRCONCommandResponse,
|
||||
MetricSampleListResponse,
|
||||
@@ -511,6 +517,26 @@ export class PlatformApiClient {
|
||||
return this.request<RemoteAdapterDeclarationListResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/remote-adapters`);
|
||||
}
|
||||
|
||||
async listScumUsers(serverInstanceId: string, request: ScumListRequest = {}): Promise<ScumUserListResponse> {
|
||||
return this.request<ScumUserListResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/scum/users${scumListQuery(request)}`);
|
||||
}
|
||||
|
||||
async listScumUserTrajectories(serverInstanceId: string, request: ScumListRequest = {}): Promise<ScumUserTrajectoryListResponse> {
|
||||
return this.request<ScumUserTrajectoryListResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/scum/user-trajectories${scumListQuery(request)}`);
|
||||
}
|
||||
|
||||
async listScumVehicles(serverInstanceId: string, request: ScumListRequest = {}): Promise<ScumVehicleListResponse> {
|
||||
return this.request<ScumVehicleListResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/scum/vehicles${scumListQuery(request)}`);
|
||||
}
|
||||
|
||||
async listScumVehicleTrajectories(serverInstanceId: string, request: ScumListRequest = {}): Promise<ScumVehicleTrajectoryListResponse> {
|
||||
return this.request<ScumVehicleTrajectoryListResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/scum/vehicle-trajectories${scumListQuery(request)}`);
|
||||
}
|
||||
|
||||
async listScumVehicleLocks(serverInstanceId: string, request: ScumListRequest = {}): Promise<ScumVehicleLockListResponse> {
|
||||
return this.request<ScumVehicleLockListResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/scum/vehicle-locks${scumListQuery(request)}`);
|
||||
}
|
||||
|
||||
async listPluginData(serverInstanceId: string, collection: string, key?: string): Promise<{ items: Array<{ key: string; value: Record<string, unknown> }>; count: number }> {
|
||||
const params = new URLSearchParams();
|
||||
if (key) params.set("key", key);
|
||||
@@ -931,6 +957,21 @@ function jobListQuery(serverInstanceId: string | undefined, filter: JobListFilte
|
||||
return query ? `?${query}` : "";
|
||||
}
|
||||
|
||||
function scumListQuery(request: ScumListRequest): string {
|
||||
const params = new URLSearchParams();
|
||||
if (request.limit !== undefined) params.set("limit", String(request.limit));
|
||||
if (request.changedAfter) params.set("changedAfter", request.changedAfter);
|
||||
if (request.after) params.set("after", request.after);
|
||||
if (request.online !== undefined) params.set("online", String(request.online));
|
||||
if (request.exists !== undefined) params.set("exists", String(request.exists));
|
||||
if (request.steamId) params.set("steamId", request.steamId);
|
||||
if (request.scumUserId) params.set("scumUserId", request.scumUserId);
|
||||
if (request.gameVehicleId) params.set("gameVehicleId", request.gameVehicleId);
|
||||
if (request.scumVehicleId) params.set("scumVehicleId", request.scumVehicleId);
|
||||
const query = params.toString();
|
||||
return query ? `?${query}` : "";
|
||||
}
|
||||
|
||||
function serverFileListQuery(request: Partial<ServerFileListRequest>): string {
|
||||
const params = new URLSearchParams();
|
||||
if (request.directoryKey) params.set("directoryKey", request.directoryKey);
|
||||
|
||||
@@ -83,14 +83,14 @@ const manifestDeclaration: GameClientBridgeManifestResponse = {
|
||||
timeoutSeconds: 30,
|
||||
maxPayloadBytes: 4096
|
||||
}],
|
||||
snapshots: [{ type: "scum.players", schemaVersion: "1", schemaRef: "schemas/bridge/snapshots/players.json", keepForSeconds: 3600, maxRecords: 24 }],
|
||||
snapshots: [{ type: "game.players", schemaVersion: "1", schemaRef: "schemas/bridge/snapshots/players.json", keepForSeconds: 3600, maxRecords: 24 }],
|
||||
queryTemplates: [{
|
||||
key: "scum.player.search",
|
||||
key: "game.player.search",
|
||||
title: "Search players",
|
||||
permission: "server.game-client.read",
|
||||
engine: "sqlite",
|
||||
transportKey: "scum-database",
|
||||
targetKey: "scum-db",
|
||||
transportKey: "sqlite-db",
|
||||
targetKey: "game-db",
|
||||
parameterSchemaRef: "schemas/bridge/queries/player-search.request.json",
|
||||
resultSchemaRef: "schemas/bridge/queries/player-search.result.json",
|
||||
maxRows: 50,
|
||||
@@ -98,7 +98,7 @@ const manifestDeclaration: GameClientBridgeManifestResponse = {
|
||||
}],
|
||||
commandRetentionSeconds: 86400,
|
||||
maxCommands: 1000,
|
||||
pages: [{ pageKey: "operations", commandTypes: ["scum.diagnostic.ping"], snapshotTypes: ["scum.players"], queryTemplateKeys: ["scum.player.search"] }]
|
||||
pages: [{ pageKey: "operations", commandTypes: ["scum.diagnostic.ping"], snapshotTypes: ["game.players"], queryTemplateKeys: ["game.player.search"] }]
|
||||
};
|
||||
|
||||
const pluginBridgeProjection: Pick<GamePluginResponse, "gameClientBridge"> & Pick<MarketplacePluginResponse, "gameClientBridge"> = {
|
||||
|
||||
@@ -149,6 +149,132 @@ export interface GameClientBridgeCommandListResponse {
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface ScumListRequest {
|
||||
limit?: number;
|
||||
changedAfter?: string;
|
||||
after?: string;
|
||||
online?: boolean;
|
||||
exists?: boolean;
|
||||
steamId?: string;
|
||||
scumUserId?: string;
|
||||
gameVehicleId?: string;
|
||||
scumVehicleId?: string;
|
||||
}
|
||||
|
||||
export interface ScumUserResponse {
|
||||
id: string;
|
||||
serverInstanceId: string;
|
||||
steamId: string;
|
||||
gamePlayerId?: string;
|
||||
displayName?: string;
|
||||
squadId?: number;
|
||||
registeredAt?: string;
|
||||
lastLoginAt?: string;
|
||||
lastSeenAt?: string;
|
||||
lastActivityAt?: string;
|
||||
lastLoginAgeSeconds?: number;
|
||||
lastLoginIp?: string;
|
||||
loginIp?: string;
|
||||
online: boolean;
|
||||
x?: number;
|
||||
y?: number;
|
||||
z?: number;
|
||||
position?: { x: number; y: number; z: number };
|
||||
bankBalance?: number;
|
||||
normalBalance?: number;
|
||||
goldBars?: number;
|
||||
goldBalance?: number;
|
||||
riddenVehicleId?: string;
|
||||
gameVehicleId?: string;
|
||||
source?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface ScumUserListResponse { items: ScumUserResponse[]; count: number; }
|
||||
|
||||
export interface ScumUserTrajectoryResponse {
|
||||
id: string;
|
||||
serverInstanceId: string;
|
||||
scumUserId: string;
|
||||
subjectType: "player";
|
||||
subjectId: string;
|
||||
steamId: string;
|
||||
displayName?: string;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
riddenVehicleId?: string;
|
||||
gameVehicleId?: string;
|
||||
observedAt?: string;
|
||||
sampledAt?: string;
|
||||
source?: string;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface ScumUserTrajectoryListResponse { items: ScumUserTrajectoryResponse[]; count: number; }
|
||||
|
||||
export interface ScumVehicleResponse {
|
||||
id: string;
|
||||
serverInstanceId: string;
|
||||
vehicleId: string;
|
||||
gameVehicleId: string;
|
||||
entityId?: string;
|
||||
vehicleClass?: string;
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
label?: string;
|
||||
exists: boolean;
|
||||
status?: string;
|
||||
locked?: boolean;
|
||||
x?: number;
|
||||
y?: number;
|
||||
z?: number;
|
||||
position?: { x: number; y: number; z: number };
|
||||
lastObservedAt?: string;
|
||||
vehicleObservedAt?: string;
|
||||
source?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface ScumVehicleListResponse { items: ScumVehicleResponse[]; count: number; }
|
||||
|
||||
export interface ScumVehicleTrajectoryResponse {
|
||||
id: string;
|
||||
serverInstanceId: string;
|
||||
scumVehicleId: string;
|
||||
subjectType: "vehicle";
|
||||
subjectId: string;
|
||||
vehicleId: string;
|
||||
gameVehicleId: string;
|
||||
vehicleClass?: string;
|
||||
className?: string;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
observedAt?: string;
|
||||
sampledAt?: string;
|
||||
source?: string;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface ScumVehicleTrajectoryListResponse { items: ScumVehicleTrajectoryResponse[]; count: number; }
|
||||
|
||||
export interface ScumVehicleLockResponse {
|
||||
id: string;
|
||||
serverInstanceId: string;
|
||||
scumVehicleId: string;
|
||||
gameVehicleId: string;
|
||||
scumUserId: string;
|
||||
steamId: string;
|
||||
lockedAt?: string;
|
||||
source?: string;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface ScumVehicleLockListResponse { items: ScumVehicleLockResponse[]; count: number; }
|
||||
|
||||
export interface GameClientBridgeCommandFilterRequest {
|
||||
profileKey?: string;
|
||||
state?: GameClientBridgeCommandState;
|
||||
|
||||
@@ -23,7 +23,7 @@ Default landing page for server owners and server administrators. Shows searchab
|
||||
|
||||
## 服务器详情
|
||||
|
||||
Daily operations hub for one server. Status header shows online state, player count, TPS, latency, CPU/memory/disk progress, metric freshness, and confirmed start/stop lifecycle actions. Plugin-declared pages render as first-class server tabs before platform sections, so each game owns its safe menu surface; SCUM pages use projection-backed users, squads, map, gifts, and workflows. Built-in sections are 管理 (deployment status, metadata, administrators) and AI 助手 (LLM suggestions produce reviewable config diffs or typed workflow drafts; no raw AI keys reach the frontend). Raw logs, management terminal/RCON input, arbitrary config workbench, generic operation history, runtime-binding, and generic plugin-control tabs must not be exposed in server detail.
|
||||
Daily operations hub for one server. Status header shows online state, player count, TPS, latency, CPU/memory/disk progress, metric freshness, and confirmed start/stop lifecycle actions. Plugin-declared pages render as first-class server tabs before platform sections, so each game owns its safe menu surface; SCUM user and vehicle pages read platform-maintained SCUM tables while plugin-owned squads, map settings, gifts, and workflows stay in scoped plugin records. Built-in sections are 管理 (deployment status, metadata, administrators) and AI 助手 (LLM suggestions produce reviewable config diffs or typed workflow drafts; no raw AI keys reach the frontend). Raw logs, management terminal/RCON input, arbitrary config workbench, generic operation history, runtime-binding, and generic plugin-control tabs must not be exposed in server detail.
|
||||
|
||||
## 插件市场
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PluginBridgeExecuteEnvelope, PluginBridgeExecutionResult } from "./pluginBridge";
|
||||
import type { GameClientBridgeCommandFilterRequest, GameClientBridgeQueueRequest, GameClientBridgeSnapshotQuery, LogStreamCursorRequest, LogStreamCursorResponse, LogStreamListResponse } from "../api/types";
|
||||
import type { GameClientBridgeCommandFilterRequest, GameClientBridgeQueueRequest, GameClientBridgeSnapshotQuery, LogStreamCursorRequest, LogStreamCursorResponse, LogStreamListResponse, ScumListRequest, ScumUserListResponse, ScumUserTrajectoryListResponse, ScumVehicleLockListResponse, ScumVehicleListResponse, ScumVehicleTrajectoryListResponse } from "../api/types";
|
||||
|
||||
export interface PluginDataMutation {
|
||||
operation: "put" | "delete";
|
||||
@@ -24,5 +24,12 @@ export interface PluginPageWorkspaceActions {
|
||||
listStreams: () => Promise<LogStreamListResponse>;
|
||||
query: (request: LogStreamCursorRequest) => Promise<LogStreamCursorResponse>;
|
||||
};
|
||||
scum?: {
|
||||
users: (request?: ScumListRequest) => Promise<ScumUserListResponse>;
|
||||
userTrajectories: (request?: ScumListRequest) => Promise<ScumUserTrajectoryListResponse>;
|
||||
vehicles: (request?: ScumListRequest) => Promise<ScumVehicleListResponse>;
|
||||
vehicleTrajectories: (request?: ScumListRequest) => Promise<ScumVehicleTrajectoryListResponse>;
|
||||
vehicleLocks: (request?: ScumListRequest) => Promise<ScumVehicleLockListResponse>;
|
||||
};
|
||||
dispatch?: (envelope: PluginBridgeExecuteEnvelope, signal?: AbortSignal) => Promise<PluginBridgeExecutionResult>;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ const apiMocks = vi.hoisted(() => ({
|
||||
listGamePlugins: vi.fn(),
|
||||
listLogStreams: vi.fn(),
|
||||
listPluginData: vi.fn(),
|
||||
listScumUsers: vi.fn(),
|
||||
listScumUserTrajectories: vi.fn(),
|
||||
listScumVehicles: vi.fn(),
|
||||
listScumVehicleTrajectories: vi.fn(),
|
||||
listScumVehicleLocks: vi.fn(),
|
||||
putPluginData: vi.fn(),
|
||||
queueGameClientBridgeCommand: vi.fn(),
|
||||
queryLogStream: vi.fn(),
|
||||
@@ -99,6 +104,11 @@ beforeEach(() => {
|
||||
apiMocks.getGameClientBridgeCommand.mockResolvedValue({ id: "command-1" });
|
||||
apiMocks.listGameClientBridgeCommands.mockResolvedValue({ items: [], count: 0 });
|
||||
apiMocks.listGameClientBridgeSnapshots.mockResolvedValue({ items: [], count: 0 });
|
||||
apiMocks.listScumUsers.mockResolvedValue({ items: [], count: 0 });
|
||||
apiMocks.listScumUserTrajectories.mockResolvedValue({ items: [], count: 0 });
|
||||
apiMocks.listScumVehicles.mockResolvedValue({ items: [], count: 0 });
|
||||
apiMocks.listScumVehicleTrajectories.mockResolvedValue({ items: [], count: 0 });
|
||||
apiMocks.listScumVehicleLocks.mockResolvedValue({ items: [], count: 0 });
|
||||
apiMocks.listLogStreams.mockResolvedValue({ items: [{ id: "stream-login", serverInstanceId: "server-1", source: "file", streamKey: "scum.login", latestSeq: 2, storageBackend: "local-segments", retentionPolicy: "default", createdAt: "2026-08-15T12:00:00Z", updatedAt: "2026-08-15T12:00:00Z" }], count: 1 });
|
||||
apiMocks.queryLogStream.mockResolvedValue({ logStreamId: "stream-login", entries: [], nextSeq: 2, latestSeq: 2 });
|
||||
apiMocks.executePluginBridge.mockResolvedValue({ requestId: "request-1", action: "server.instances.read", status: "ok", result: { state: "ready" } });
|
||||
@@ -208,28 +218,32 @@ describe("PluginPageHostPage", () => {
|
||||
const snapshotQuery = { profileKey: "example-client", type: "example.positions", streamKey: "current", limit: 50 };
|
||||
const dispatchEnvelope = { requestId: "request-1", action: "server.instances.read" as const, payload: { view: "summary" } };
|
||||
|
||||
await actions?.pluginData?.list("scum_users", "user-1");
|
||||
await actions?.pluginData?.put("scum_users", "user-1", { name: "Ada" });
|
||||
await actions?.pluginData?.delete("scum_users", "user-2");
|
||||
await actions?.pluginData?.transact("scum_users", mutations);
|
||||
await actions?.pluginData?.list("plugin_users", "user-1");
|
||||
await actions?.pluginData?.put("plugin_users", "user-1", { name: "Ada" });
|
||||
await actions?.pluginData?.delete("plugin_users", "user-2");
|
||||
await actions?.pluginData?.transact("plugin_users", mutations);
|
||||
await actions?.gameClient?.queue(queueRequest);
|
||||
await actions?.gameClient?.get("command-1");
|
||||
await actions?.gameClient?.list(commandFilter);
|
||||
await actions?.gameClient?.snapshots(snapshotQuery);
|
||||
await actions?.logs?.listStreams();
|
||||
await actions?.logs?.query({ logStreamId: "stream-login", afterSeq: 1, limit: 50 });
|
||||
await actions?.scum?.users({ limit: 10, online: true });
|
||||
await actions?.scum?.vehicleLocks({ limit: 10, gameVehicleId: "veh-1" });
|
||||
await expect(actions?.dispatch?.(dispatchEnvelope)).resolves.toMatchObject({ status: "ok", result: { state: "ready" } });
|
||||
|
||||
expect(apiMocks.listPluginData).toHaveBeenCalledWith("server-1", "scum_users", "user-1");
|
||||
expect(apiMocks.putPluginData).toHaveBeenCalledWith("server-1", "scum_users", "user-1", { name: "Ada" });
|
||||
expect(apiMocks.deletePluginData).toHaveBeenCalledWith("server-1", "scum_users", "user-2");
|
||||
expect(apiMocks.transactPluginData).toHaveBeenCalledWith("server-1", "scum_users", mutations);
|
||||
expect(apiMocks.listPluginData).toHaveBeenCalledWith("server-1", "plugin_users", "user-1");
|
||||
expect(apiMocks.putPluginData).toHaveBeenCalledWith("server-1", "plugin_users", "user-1", { name: "Ada" });
|
||||
expect(apiMocks.deletePluginData).toHaveBeenCalledWith("server-1", "plugin_users", "user-2");
|
||||
expect(apiMocks.transactPluginData).toHaveBeenCalledWith("server-1", "plugin_users", mutations);
|
||||
expect(apiMocks.queueGameClientBridgeCommand).toHaveBeenCalledWith("server-1", queueRequest);
|
||||
expect(apiMocks.getGameClientBridgeCommand).toHaveBeenCalledWith("server-1", "command-1");
|
||||
expect(apiMocks.listGameClientBridgeCommands).toHaveBeenCalledWith("server-1", commandFilter);
|
||||
expect(apiMocks.listGameClientBridgeSnapshots).toHaveBeenCalledWith("server-1", snapshotQuery);
|
||||
expect(apiMocks.listLogStreams).toHaveBeenCalledWith("server-1");
|
||||
expect(apiMocks.queryLogStream).toHaveBeenCalledWith({ logStreamId: "stream-login", afterSeq: 1, limit: 50 });
|
||||
expect(apiMocks.listScumUsers).toHaveBeenCalledWith("server-1", { limit: 10, online: true });
|
||||
expect(apiMocks.listScumVehicleLocks).toHaveBeenCalledWith("server-1", { limit: 10, gameVehicleId: "veh-1" });
|
||||
expect(apiMocks.executePluginBridge).toHaveBeenCalledWith({
|
||||
requestId: "request-1",
|
||||
pluginId: "game.scum",
|
||||
|
||||
@@ -93,6 +93,13 @@ export function PluginPageHostPage({ params, onNavigate, initialPlugin, embedded
|
||||
return platformApiClient.queryLogStream(request);
|
||||
}
|
||||
},
|
||||
scum: {
|
||||
users: (request) => platformApiClient.listScumUsers(serverId, request),
|
||||
userTrajectories: (request) => platformApiClient.listScumUserTrajectories(serverId, request),
|
||||
vehicles: (request) => platformApiClient.listScumVehicles(serverId, request),
|
||||
vehicleTrajectories: (request) => platformApiClient.listScumVehicleTrajectories(serverId, request),
|
||||
vehicleLocks: (request) => platformApiClient.listScumVehicleLocks(serverId, request)
|
||||
},
|
||||
dispatch: (envelope, signal) => {
|
||||
const context = hostContextRef.current;
|
||||
if (!context) return Promise.resolve({ requestId: envelope.requestId, action: envelope.action, status: "error", error: { code: "host_unavailable", message: "plugin bridge host is unavailable" } });
|
||||
|
||||
@@ -71,8 +71,8 @@ describe("Game Client Bridge safe projection schema", () => {
|
||||
it("preserves sensitive-looking command and snapshot payloads", () => {
|
||||
expect(parseSafeGameClientBridgeCommand({
|
||||
...safeCommand,
|
||||
result: { ...safeCommand.result, payload: { dsn: "sqlite:///srv/scum/SCUM.db", endpoint: "tcp://127.0.0.1:9999", output: "/Users/operator/scum/config.yaml" } }
|
||||
})).toMatchObject({ result: { payload: { dsn: "sqlite:///srv/scum/SCUM.db", endpoint: "tcp://127.0.0.1:9999", output: "/Users/operator/scum/config.yaml" } } });
|
||||
result: { ...safeCommand.result, payload: { dsn: "sqlite:///srv/game/state.db", endpoint: "tcp://127.0.0.1:9999", output: "/Users/operator/game/config.yaml" } }
|
||||
})).toMatchObject({ result: { payload: { dsn: "sqlite:///srv/game/state.db", endpoint: "tcp://127.0.0.1:9999", output: "/Users/operator/game/config.yaml" } } });
|
||||
|
||||
const payload = { players: [{ playerId: "player-1", credential: "raw-password", note: "password=opaque" }] };
|
||||
expect(parseSafeGameClientBridgeSnapshotList({ ...safeSnapshotList, items: [{ ...safeSnapshotList.items[0], payload }] })).toMatchObject({ items: [{ payload }] });
|
||||
|
||||
@@ -757,7 +757,7 @@ to{transform:translate(-50%,-50%) rotate(calc(var(--construct-drift) + 360deg))}
|
||||
.console-stat-strip>div,.operations-pulse-strip>div{display:grid;gap:3px;min-width:0;padding:9px 10px;border:1px solid color-mix(in srgb,var(--line) 78%,transparent);border-radius:6px;background:color-mix(in srgb,var(--surface-solid) 78%,var(--accent-soft))}
|
||||
.console-stat-strip dt,.operations-pulse-strip dt{color:var(--ink-faint);font-size:11px}
|
||||
.console-stat-strip dd,.operations-pulse-strip dd{margin:0;color:var(--ink);font-size:18px;font-weight:850}
|
||||
.map-projection-board{position:relative;min-height:320px;border:1px solid color-mix(in srgb,var(--line) 76%,transparent);border-radius:14px;overflow:hidden;background:radial-gradient(circle at 50% 50%,color-mix(in srgb,var(--accent-soft) 42%,transparent),transparent 58%),linear-gradient(135deg,color-mix(in srgb,var(--surface-solid) 78%,#000),#05070d);background-size:cover;background-position:center}.map-grid-overlay{position:absolute;inset:0;z-index:1;pointer-events:none}.map-grid-line{position:absolute;background:color-mix(in srgb,var(--line) 62%,transparent)}.map-grid-line-v{top:0;bottom:0;width:1px}.map-grid-line-h{left:0;right:0;height:1px}.map-grid-label{position:absolute;transform:translate(-50%,-50%);padding:1px 5px;border:1px solid color-mix(in srgb,var(--line) 70%,transparent);border-radius:999px;background:color-mix(in srgb,var(--surface-solid) 72%,transparent);color:var(--ink);font:800 10px/1 var(--font-mono);text-shadow:0 1px 4px rgba(0,0,0,.55)}.map-grid-col-label{top:10px}.map-grid-row-label{left:12px}.map-projection-dot{position:absolute;z-index:3;width:9px;height:9px;padding:0;border:0;border-radius:999px;background:var(--accent);box-shadow:0 0 16px color-mix(in srgb,var(--accent) 80%,transparent);transform:translate(-50%,-50%);cursor:pointer}.map-projection-dot img{display:block;width:100%;height:100%;object-fit:contain;filter:drop-shadow(0 0 8px color-mix(in srgb,var(--accent) 76%,transparent))}.map-projection-dot.map-layer-vehicles{width:26px;height:26px;background:color-mix(in srgb,var(--surface-solid) 72%,transparent)}.map-projection-dot.map-layer-flags{background:var(--gold)}.map-projection-dot.map-layer-regions{background:var(--success)}.map-projection-dot-riding{outline:2px solid var(--gold);box-shadow:0 0 0 4px color-mix(in srgb,var(--gold) 24%,transparent),0 0 18px color-mix(in srgb,var(--gold) 80%,transparent)}.map-trajectory-dot{position:absolute;z-index:2;width:4px;height:4px;border-radius:999px;background:color-mix(in srgb,var(--accent) 82%,transparent);box-shadow:0 0 8px color-mix(in srgb,var(--accent) 66%,transparent);transform:translate(-50%,-50%);pointer-events:none}.map-trajectory-dot.map-layer-vehicles{width:5px;height:5px;background:color-mix(in srgb,var(--gold) 86%,transparent)}
|
||||
.map-surface-board{position:relative;min-height:320px;border:1px solid color-mix(in srgb,var(--line) 76%,transparent);border-radius:14px;overflow:hidden;background:radial-gradient(circle at 50% 50%,color-mix(in srgb,var(--accent-soft) 42%,transparent),transparent 58%),linear-gradient(135deg,color-mix(in srgb,var(--surface-solid) 78%,#000),#05070d);background-size:cover;background-position:center}.map-grid-overlay{position:absolute;inset:0;z-index:1;pointer-events:none}.map-grid-line{position:absolute;background:color-mix(in srgb,var(--line) 62%,transparent)}.map-grid-line-v{top:0;bottom:0;width:1px}.map-grid-line-h{left:0;right:0;height:1px}.map-grid-label{position:absolute;transform:translate(-50%,-50%);padding:1px 5px;border:1px solid color-mix(in srgb,var(--line) 70%,transparent);border-radius:999px;background:color-mix(in srgb,var(--surface-solid) 72%,transparent);color:var(--ink);font:800 10px/1 var(--font-mono);text-shadow:0 1px 4px rgba(0,0,0,.55)}.map-grid-col-label{top:10px}.map-grid-row-label{left:12px}.map-surface-dot{position:absolute;z-index:3;width:9px;height:9px;padding:0;border:0;border-radius:999px;background:var(--accent);box-shadow:0 0 16px color-mix(in srgb,var(--accent) 80%,transparent);transform:translate(-50%,-50%);cursor:pointer}.map-surface-dot img{display:block;width:100%;height:100%;object-fit:contain;filter:drop-shadow(0 0 8px color-mix(in srgb,var(--accent) 76%,transparent))}.map-surface-dot.map-layer-vehicles{width:26px;height:26px;background:color-mix(in srgb,var(--surface-solid) 72%,transparent)}.map-surface-dot.map-layer-flags{background:var(--gold)}.map-surface-dot.map-layer-regions{background:var(--success)}.map-surface-dot-riding{outline:2px solid var(--gold);box-shadow:0 0 0 4px color-mix(in srgb,var(--gold) 24%,transparent),0 0 18px color-mix(in srgb,var(--gold) 80%,transparent)}.map-trajectory-dot{position:absolute;z-index:2;width:4px;height:4px;border-radius:999px;background:color-mix(in srgb,var(--accent) 82%,transparent);box-shadow:0 0 8px color-mix(in srgb,var(--accent) 66%,transparent);transform:translate(-50%,-50%);pointer-events:none}.map-trajectory-dot.map-layer-vehicles{width:5px;height:5px;background:color-mix(in srgb,var(--gold) 86%,transparent)}
|
||||
.console-row-list,.operations-endpoint-list,.operations-job-list{display:grid;gap:6px;margin-top:10px}
|
||||
.console-row,.operations-endpoint-row,.operations-job-row{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:10px;min-width:0;padding:8px 10px;border:1px solid var(--line);border-radius:6px;background:var(--control-surface);color:var(--ink-soft);text-align:left}
|
||||
.console-row-button,.operations-job-row{width:100%;cursor:pointer}
|
||||
|
||||
@@ -117,7 +117,7 @@ describe("plugin bridge host utilities", () => {
|
||||
|
||||
it("allows mediated remote SQL execute payloads without direct connection material", () => {
|
||||
const context = createPluginBridgeHostContext({ plugin, routeKey: "remote", serverInstanceId: "server-1", themeTokens: { colorScheme: "dark", accentColor: "#22c55e" } });
|
||||
expect(validateBridgeExecutionRequest(context, { requestId: "sql-1", action: "remote.access.request", payload: { capability: "remote.run.db.sqlite.execute", declarationKey: "sqlite-db", targetKey: "scum-db", idempotencyKey: "sql-1", "input.sqlText": "UPDATE prisoner SET stamina = 855 WHERE id = 'steam-123';" } })).toBeNull();
|
||||
expect(validateBridgeExecutionRequest(context, { requestId: "sql-1", action: "remote.access.request", payload: { capability: "remote.run.db.sqlite.execute", declarationKey: "sqlite-db", targetKey: "game-db", idempotencyKey: "sql-1", "input.sqlText": "UPDATE players SET score = 855 WHERE id = 'player-123';" } })).toBeNull();
|
||||
});
|
||||
|
||||
it("passes plugin-owned bridge payload text through without frontend content scanning", () => {
|
||||
|
||||
Reference in New Issue
Block a user