Remove pre-1.0 audit and protected request scaffolding
This commit is contained in:
@@ -19,7 +19,6 @@ const safeCommand = {
|
||||
approvalState: "not_required",
|
||||
requesterId: "user-1",
|
||||
result: { status: "succeeded", summary: "player found", payload: { found: true }, completedAt: now },
|
||||
auditReferences: ["audit-1"],
|
||||
expiresAt: now,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
||||
@@ -92,8 +92,6 @@ export function parseSafeGameClientBridgeCommand(value: unknown): GameClientBrid
|
||||
copyOptionalString(record, result, "requesterId");
|
||||
copyOptionalString(record, result, "resultSummary");
|
||||
copyOptionalString(record, result, "completedAt");
|
||||
const auditReferences = optionalStringArray(record.auditReferences, "auditReferences");
|
||||
if (auditReferences) result.auditReferences = auditReferences;
|
||||
if (record.result !== undefined) result.result = parseResult(record.result);
|
||||
if (record.cancellation !== undefined) result.cancellation = parseCancellation(record.cancellation);
|
||||
return result;
|
||||
@@ -115,8 +113,6 @@ export function parseSafeGameClientBridgeCancellation(value: unknown): GameClien
|
||||
cancellation: parseCancellation(record.cancellation),
|
||||
updatedAt: string(record.updatedAt, "updatedAt")
|
||||
};
|
||||
const auditReferences = optionalStringArray(record.auditReferences, "auditReferences");
|
||||
if (auditReferences) result.auditReferences = auditReferences;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -193,8 +189,6 @@ function parseSnapshot(value: unknown): GameClientBridgeSnapshotResponse {
|
||||
expiresAt: string(record.expiresAt, "snapshot.expiresAt")
|
||||
};
|
||||
if (retention.maxRecords !== undefined) result.retention.maxRecords = nonNegativeInteger(retention.maxRecords, "snapshot.retention.maxRecords");
|
||||
const auditReferences = optionalStringArray(record.auditReferences, "snapshot.auditReferences");
|
||||
if (auditReferences) result.auditReferences = auditReferences;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("safe job projection schema", () => {
|
||||
const parsed = parseSafeJobResponse({
|
||||
...retryingJob,
|
||||
state: "succeeded",
|
||||
executionResult: { kind: "file.write", version: 2, checksum: "sha256:" + "a".repeat(64), sizeBytes: 18, auditSummary: "atomic compare-and-swap file write" }
|
||||
executionResult: { kind: "file.write", version: 2, checksum: "sha256:" + "a".repeat(64), sizeBytes: 18, summary: "atomic compare-and-swap file write" }
|
||||
});
|
||||
expect(parsed.executionResult).toMatchObject({ kind: "file.write", version: 2, sizeBytes: 18 });
|
||||
expect(parsed.executionResult).not.toHaveProperty("content");
|
||||
|
||||
@@ -57,7 +57,7 @@ function optionalExecutionResult(value: Record<string, unknown>, key: string): J
|
||||
version: optionalNumber(field, "version"),
|
||||
checksum: optionalString(field, "checksum"),
|
||||
sizeBytes: optionalNumber(field, "sizeBytes"),
|
||||
auditSummary: optionalString(field, "auditSummary")
|
||||
summary: optionalString(field, "summary")
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { scumAnnouncementCommand, scumManagementRCONCommandRequest } from "./scumManagementRcon";
|
||||
import { scumSourceRCONChatRequest, scumSourceRCONCommandRequest } from "./scumManagementRcon";
|
||||
|
||||
describe("SCUM management RCON bridge schema", () => {
|
||||
it("builds a protected bridge request without connection material", () => {
|
||||
describe("SCUM management Source RCON schema", () => {
|
||||
it("builds a direct RCON job request without connection material", () => {
|
||||
const stamp = Date.UTC(2026, 7, 3, 8, 0, 0);
|
||||
|
||||
expect(scumManagementRCONCommandRequest("server/unsafe", " ListPlayers ", stamp)).toEqual({
|
||||
profileKey: "scum-client-manager",
|
||||
commandType: "management.rcon.request",
|
||||
payload: { requestText: "#ListPlayers" },
|
||||
idempotencyKey: `web:scum-rcon:server-unsafe:${stamp}`,
|
||||
priority: 20,
|
||||
expiresAt: "2026-08-03T08:02:00.000Z"
|
||||
expect(scumSourceRCONCommandRequest("server/unsafe", " ListPlayers ", stamp)).toEqual({
|
||||
kind: "command",
|
||||
command: "#ListPlayers",
|
||||
idempotencyKey: `web:scum-rcon:server-unsafe:${stamp}`
|
||||
});
|
||||
});
|
||||
|
||||
it("formats announcements and rejects framed command text", () => {
|
||||
expect(scumAnnouncementCommand("Restart in ten minutes")).toBe("#Announce Restart in ten minutes");
|
||||
expect(scumManagementRCONCommandRequest("server-1", "#SetTime 12").payload).toEqual({ requestText: "#SetTime 12" });
|
||||
expect(() => scumManagementRCONCommandRequest("server-1", "ListPlayers\nSetTime 12")).toThrow("管理指令必须是受限的单行文本");
|
||||
it("formats direct commands and rejects framed text", () => {
|
||||
expect(scumSourceRCONChatRequest("server-1", "Restart in ten minutes", 42)).toEqual({ kind: "chat", chatType: 4, message: "Restart in ten minutes", idempotencyKey: "web:scum-rcon-chat:server-1:42" });
|
||||
expect(scumSourceRCONCommandRequest("server-1", "#SetTime 12").command).toBe("#SetTime 12");
|
||||
expect(() => scumSourceRCONCommandRequest("server-1", "ListPlayers\nSetTime 12")).toThrow("管理指令必须是单行文本");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
import type { GameClientBridgeQueueRequest } from "../api/types";
|
||||
|
||||
export const scumManagementRCONProfileKey = "scum-client-manager";
|
||||
export const scumManagementRCONCommandType = "management.rcon.request";
|
||||
import type { SourceRCONCommandRequest } from "../api/types";
|
||||
|
||||
const maxManagementCommandBytes = 8192;
|
||||
const managementCommandTtlMs = 120_000;
|
||||
|
||||
export function scumManagementRCONCommandRequest(serverInstanceId: string, command: string, sequence = Date.now()): GameClientBridgeQueueRequest {
|
||||
export function scumSourceRCONCommandRequest(serverInstanceId: string, command: string, sequence = Date.now()): SourceRCONCommandRequest {
|
||||
const stamp = Math.max(0, Math.floor(sequence));
|
||||
return {
|
||||
profileKey: scumManagementRCONProfileKey,
|
||||
commandType: scumManagementRCONCommandType,
|
||||
payload: { requestText: normalizeSCUMManagementCommand(command, "管理指令") },
|
||||
kind: "command",
|
||||
command: normalizeSCUMManagementCommand(command, "管理指令"),
|
||||
idempotencyKey: `web:scum-rcon:${safeBridgeIdentifierPart(serverInstanceId)}:${stamp}`,
|
||||
priority: 20,
|
||||
expiresAt: new Date(stamp + managementCommandTtlMs).toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
export function scumAnnouncementCommand(message: string): string {
|
||||
return `#Announce ${validateSCUMManagementRCONText(message, "公告内容")}`;
|
||||
export function scumSourceRCONChatRequest(serverInstanceId: string, message: string, sequence = Date.now()): SourceRCONCommandRequest {
|
||||
const stamp = Math.max(0, Math.floor(sequence));
|
||||
return {
|
||||
kind: "chat",
|
||||
chatType: 4,
|
||||
message: validateSCUMManagementRCONText(message, "聊天内容"),
|
||||
idempotencyKey: `web:scum-rcon-chat:${safeBridgeIdentifierPart(serverInstanceId)}:${stamp}`
|
||||
};
|
||||
}
|
||||
|
||||
export function validateSCUMManagementRCONText(value: string, label: string): string {
|
||||
const normalized = value.trim();
|
||||
if (!normalized || new TextEncoder().encode(normalized).byteLength > maxManagementCommandBytes || /[\u0000\r\n]/.test(normalized)) {
|
||||
throw new Error(`${label}必须是受限的单行文本。`);
|
||||
throw new Error(`${label}必须是单行文本。`);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user