Remove pre-1.0 bridge governance scaffolding
This commit is contained in:
@@ -675,7 +675,7 @@ export function validateGameClientBridgeCatalog(manifest: unknown): string[] {
|
||||
if (typeof manifest !== "object" || manifest === null) {
|
||||
return [];
|
||||
}
|
||||
type BridgeCommand = { type?: string; approvalLevel?: string; payloadSchemaRef?: string; resultSchemaRef?: string };
|
||||
type BridgeCommand = { type?: string; payloadSchemaRef?: string; resultSchemaRef?: string };
|
||||
type BridgeQueryTemplate = {
|
||||
key?: string;
|
||||
permission?: string;
|
||||
@@ -704,25 +704,7 @@ export function validateGameClientBridgeCatalog(manifest: unknown): string[] {
|
||||
activityTarget?: BridgeLogProjectionTarget;
|
||||
};
|
||||
};
|
||||
type BridgeOperationSafety = { requiresApproval?: boolean; requiresOfflinePlayer?: boolean; requiresMaintenanceWindow?: boolean; requiresBeforeValue?: boolean; requiresConfirmation?: boolean; backupRequired?: boolean };
|
||||
type BridgeOperationMutation = { fieldKey?: string; tableKey?: string; identityKey?: string; valueKey?: string; confirmationQueryKey?: string; allowedValueType?: string; minValue?: number; maxValue?: number };
|
||||
type BridgeOperationTemplate = {
|
||||
key?: string;
|
||||
permission?: string;
|
||||
approvalLevel?: string;
|
||||
kind?: string;
|
||||
transportKey?: string;
|
||||
targetKey?: string;
|
||||
payloadSchemaRef?: string;
|
||||
resultSchemaRef?: string;
|
||||
confirmationSchemaRef?: string;
|
||||
timeoutSeconds?: number;
|
||||
maxPayloadBytes?: number;
|
||||
maxRowsAffected?: number;
|
||||
mutation?: BridgeOperationMutation;
|
||||
safety?: BridgeOperationSafety;
|
||||
};
|
||||
type BridgePage = { pageKey?: string; commandTypes?: string[]; snapshotTypes?: string[]; queryTemplateKeys?: string[]; operationKeys?: string[] };
|
||||
type BridgePage = { pageKey?: string; commandTypes?: string[]; snapshotTypes?: string[]; queryTemplateKeys?: string[] };
|
||||
type BridgeCompanion = {
|
||||
profileKey?: string;
|
||||
configTemplateKey?: string;
|
||||
@@ -745,7 +727,7 @@ export function validateGameClientBridgeCatalog(manifest: unknown): string[] {
|
||||
remoteAccess?: { runCapabilities?: string[]; databaseEngines?: string[] };
|
||||
pages?: PluginPage[];
|
||||
runtimeProfiles?: { transportProfiles?: RuntimeTransportProfile[]; clientManagers?: RuntimeClientManager[] };
|
||||
gameClientBridge?: { commands?: BridgeCommand[]; snapshots?: Array<{ type?: string }>; queryTemplates?: BridgeQueryTemplate[]; logProjections?: BridgeLogProjection[]; operationTemplates?: BridgeOperationTemplate[]; pages?: BridgePage[]; companion?: BridgeCompanion };
|
||||
gameClientBridge?: { commands?: BridgeCommand[]; snapshots?: Array<{ type?: string }>; queryTemplates?: BridgeQueryTemplate[]; logProjections?: BridgeLogProjection[]; pages?: BridgePage[]; companion?: BridgeCompanion };
|
||||
};
|
||||
const bridge = declaration.gameClientBridge;
|
||||
if (!bridge) {
|
||||
@@ -756,7 +738,6 @@ export function validateGameClientBridgeCatalog(manifest: unknown): string[] {
|
||||
const snapshots = new Set((bridge.snapshots ?? []).map((snapshot) => snapshot.type ?? ""));
|
||||
const queryTemplates = new Map<string, BridgeQueryTemplate>();
|
||||
const logProjections = new Set<string>();
|
||||
const operationTemplates = new Map<string, BridgeOperationTemplate>();
|
||||
const declaredPermissions = new Set(declaration.permissions ?? []);
|
||||
const declaredCapabilities = new Set(declaration.capabilities ?? []);
|
||||
const remoteCapabilities = new Set(declaration.remoteAccess?.runCapabilities ?? []);
|
||||
@@ -931,92 +912,6 @@ export function validateGameClientBridgeCatalog(manifest: unknown): string[] {
|
||||
if (!Number.isInteger(presence.activeWindowSeconds) || (presence.activeWindowSeconds ?? 0) < 1 || (presence.activeWindowSeconds ?? 0) > 31536000) errors.push(`${location}.presence.activeWindowSeconds: must be between 1 and 31536000`);
|
||||
if (presence.activityTarget) errors.push(...validateProjectionTarget(`${location}.presence.activityTarget`, presence.activityTarget, captures));
|
||||
}
|
||||
for (const [index, operationTemplate] of (bridge.operationTemplates ?? []).entries()) {
|
||||
const location = `manifest.gameClientBridge.operationTemplates[${index}]`;
|
||||
const key = operationTemplate.key ?? "";
|
||||
const unsafeReason = unsafeGameClientBridgeCommandTypeReason(key);
|
||||
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/.test(key) || unsafeReason) {
|
||||
errors.push(`${location}.key: ${unsafeReason ?? "operation template key is unsafe"}`);
|
||||
}
|
||||
if (operationTemplates.has(key)) {
|
||||
errors.push(`${location}.key: duplicate operation template ${key}`);
|
||||
}
|
||||
operationTemplates.set(key, operationTemplate);
|
||||
if (!operationTemplate.permission || !declaredPermissions.has(operationTemplate.permission)) {
|
||||
errors.push(`${location}.permission: permission must be declared by the plugin manifest`);
|
||||
}
|
||||
if (!new Set(["none", "operator", "platform-admin"]).has(operationTemplate.approvalLevel ?? "")) {
|
||||
errors.push(`${location}.approvalLevel: must be none, operator, or platform-admin`);
|
||||
}
|
||||
if (!new Set(["rcon", "sqlite-mutation"]).has(operationTemplate.kind ?? "")) {
|
||||
errors.push(`${location}.kind: must be rcon or sqlite-mutation`);
|
||||
}
|
||||
for (const [field, ref] of [["payloadSchemaRef", operationTemplate.payloadSchemaRef], ["resultSchemaRef", operationTemplate.resultSchemaRef], ["confirmationSchemaRef", operationTemplate.confirmationSchemaRef]] as const) {
|
||||
if ((field === "payloadSchemaRef" && !ref) || (ref && !isSafeRelativeJsonRef(ref))) {
|
||||
errors.push(`${location}.${field}: raw host paths and unsafe schema references are not allowed`);
|
||||
}
|
||||
}
|
||||
if (!Number.isInteger(operationTemplate.timeoutSeconds) || (operationTemplate.timeoutSeconds ?? 0) < 1 || (operationTemplate.timeoutSeconds ?? 0) > 3600) {
|
||||
errors.push(`${location}.timeoutSeconds: must be an integer between 1 and 3600`);
|
||||
}
|
||||
if (!Number.isInteger(operationTemplate.maxPayloadBytes) || (operationTemplate.maxPayloadBytes ?? 0) < 1 || (operationTemplate.maxPayloadBytes ?? 0) > 65536) {
|
||||
errors.push(`${location}.maxPayloadBytes: must be an integer between 1 and 65536`);
|
||||
}
|
||||
const transport = transportProfiles.find((profile) => profile.key === operationTemplate.transportKey);
|
||||
if (!transport) {
|
||||
errors.push(`${location}.transportKey: undeclared transport profile ${operationTemplate.transportKey ?? ""}`);
|
||||
continue;
|
||||
}
|
||||
if (!operationTemplate.targetKey || transport.targetKey !== operationTemplate.targetKey) {
|
||||
errors.push(`${location}.targetKey: must match the declared runtime transport target`);
|
||||
}
|
||||
if (operationTemplate.kind === "rcon") {
|
||||
if (transport.kind !== "rcon" || !transport.capabilities?.includes("remote.run.rcon.command")) {
|
||||
errors.push(`${location}.transportKey: rcon operations require remote.run.rcon.command transport`);
|
||||
}
|
||||
if (operationTemplate.maxRowsAffected !== undefined) {
|
||||
errors.push(`${location}.maxRowsAffected: only sqlite-mutation operations may declare affected row bounds`);
|
||||
}
|
||||
if (operationTemplate.mutation !== undefined) {
|
||||
errors.push(`${location}.mutation: only sqlite-mutation operations may declare mutation metadata`);
|
||||
}
|
||||
}
|
||||
if (operationTemplate.kind === "sqlite-mutation") {
|
||||
if (transport.kind !== "sqlite" || !transport.capabilities?.includes("remote.run.protected.sql")) {
|
||||
errors.push(`${location}.transportKey: sqlite-mutation operations require sqlite remote.run.protected.sql transport`);
|
||||
}
|
||||
if (operationTemplate.approvalLevel !== "platform-admin") {
|
||||
errors.push(`${location}.approvalLevel: sqlite-mutation operations require platform-admin approval`);
|
||||
}
|
||||
if (!Number.isInteger(operationTemplate.maxRowsAffected) || (operationTemplate.maxRowsAffected ?? 0) < 1 || (operationTemplate.maxRowsAffected ?? 0) > 10) {
|
||||
errors.push(`${location}.maxRowsAffected: must be an integer between 1 and 10`);
|
||||
}
|
||||
const safety = operationTemplate.safety;
|
||||
if (!safety?.requiresBeforeValue || !safety.requiresConfirmation || (!safety.requiresOfflinePlayer && !safety.requiresMaintenanceWindow)) {
|
||||
errors.push(`${location}.safety: sqlite-mutation operations require before value, confirmation, and offline or maintenance protection`);
|
||||
}
|
||||
const mutation = operationTemplate.mutation;
|
||||
if (!mutation) {
|
||||
errors.push(`${location}.mutation: sqlite-mutation operations require field/table/identity metadata`);
|
||||
} else {
|
||||
for (const field of ["fieldKey", "tableKey", "identityKey", "valueKey", "confirmationQueryKey"] as const) {
|
||||
const value = mutation[field] ?? "";
|
||||
if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,159}$/.test(value) || unsafeGameClientBridgePayloadKey(value)) {
|
||||
errors.push(`${location}.mutation.${field}: must be a safe logical key`);
|
||||
}
|
||||
}
|
||||
if (!new Set(["integer", "number", "string", "boolean"]).has(mutation.allowedValueType ?? "")) {
|
||||
errors.push(`${location}.mutation.allowedValueType: must be integer, number, string, or boolean`);
|
||||
}
|
||||
if (mutation.minValue !== undefined && mutation.maxValue !== undefined && mutation.minValue > mutation.maxValue) {
|
||||
errors.push(`${location}.mutation: minValue must not exceed maxValue`);
|
||||
}
|
||||
if (mutation.confirmationQueryKey && !queryTemplates.has(mutation.confirmationQueryKey)) {
|
||||
errors.push(`${location}.mutation.confirmationQueryKey: must reference a declared query template`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [index, page] of (bridge.pages ?? []).entries()) {
|
||||
for (const commandType of page.commandTypes ?? []) {
|
||||
if (!commands.has(commandType)) {
|
||||
@@ -1042,17 +937,6 @@ export function validateGameClientBridgeCatalog(manifest: unknown): string[] {
|
||||
errors.push(`manifest.gameClientBridge.pages[${index}].queryTemplateKeys: page must declare remote.access.request`);
|
||||
}
|
||||
}
|
||||
for (const operationKey of page.operationKeys ?? []) {
|
||||
const operationTemplate = operationTemplates.get(operationKey);
|
||||
if (!operationTemplate) {
|
||||
errors.push(`manifest.gameClientBridge.pages[${index}].operationKeys: undeclared operation template ${operationKey}`);
|
||||
continue;
|
||||
}
|
||||
const pluginPage = declaration.pages?.find((candidate) => candidate.key === page.pageKey);
|
||||
if (!pluginPage?.permissions?.includes(operationTemplate.permission ?? "")) {
|
||||
errors.push(`manifest.gameClientBridge.pages[${index}].operationKeys: page must declare operation template permission ${operationTemplate.permission ?? ""}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
@@ -1183,8 +1067,7 @@ function referencedGameClientBridgeSchemas(manifest: unknown): GameClientBridgeS
|
||||
type BridgeCommand = { payloadSchemaRef?: string; resultSchemaRef?: string };
|
||||
type BridgeSnapshot = { schemaRef?: string };
|
||||
type BridgeQueryTemplate = { parameterSchemaRef?: string; resultSchemaRef?: string };
|
||||
type BridgeOperationTemplate = { payloadSchemaRef?: string; resultSchemaRef?: string; confirmationSchemaRef?: string };
|
||||
const bridge = (manifest as { gameClientBridge?: { commands?: BridgeCommand[]; snapshots?: BridgeSnapshot[]; queryTemplates?: BridgeQueryTemplate[]; operationTemplates?: BridgeOperationTemplate[] } }).gameClientBridge;
|
||||
const bridge = (manifest as { gameClientBridge?: { commands?: BridgeCommand[]; snapshots?: BridgeSnapshot[]; queryTemplates?: BridgeQueryTemplate[] } }).gameClientBridge;
|
||||
if (!bridge) {
|
||||
return [];
|
||||
}
|
||||
@@ -1210,17 +1093,6 @@ function referencedGameClientBridgeSchemas(manifest: unknown): GameClientBridgeS
|
||||
refs.push({ location: `manifest.gameClientBridge.queryTemplates[${index}].resultSchemaRef`, ref: queryTemplate.resultSchemaRef });
|
||||
}
|
||||
}
|
||||
for (const [index, operationTemplate] of (bridge.operationTemplates ?? []).entries()) {
|
||||
if (operationTemplate.payloadSchemaRef) {
|
||||
refs.push({ location: `manifest.gameClientBridge.operationTemplates[${index}].payloadSchemaRef`, ref: operationTemplate.payloadSchemaRef });
|
||||
}
|
||||
if (operationTemplate.resultSchemaRef) {
|
||||
refs.push({ location: `manifest.gameClientBridge.operationTemplates[${index}].resultSchemaRef`, ref: operationTemplate.resultSchemaRef });
|
||||
}
|
||||
if (operationTemplate.confirmationSchemaRef) {
|
||||
refs.push({ location: `manifest.gameClientBridge.operationTemplates[${index}].confirmationSchemaRef`, ref: operationTemplate.confirmationSchemaRef });
|
||||
}
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user