Remove pre-1.0 bridge governance scaffolding
This commit is contained in:
@@ -5,11 +5,18 @@
|
||||
"description": "Development game management plugin for local registry and lifecycle testing.",
|
||||
"version": "0.1.0",
|
||||
"kind": "game-plugin",
|
||||
"tags": ["example", "development"],
|
||||
"tags": [
|
||||
"example",
|
||||
"development"
|
||||
],
|
||||
"server": {
|
||||
"type": "example",
|
||||
"displayName": "Example Server",
|
||||
"supportedOS": ["windows", "linux", "darwin"],
|
||||
"supportedOS": [
|
||||
"windows",
|
||||
"linux",
|
||||
"darwin"
|
||||
],
|
||||
"createFormSchema": "schemas/create-form.schema.json"
|
||||
},
|
||||
"capabilities": [
|
||||
@@ -57,18 +64,46 @@
|
||||
"status": "actions/status.json"
|
||||
},
|
||||
"assetFiles": [
|
||||
{ "path": "actions/install.json", "mode": 384 },
|
||||
{ "path": "actions/start.json", "mode": 384 },
|
||||
{ "path": "actions/stop.json", "mode": 384 },
|
||||
{ "path": "actions/restart.json", "mode": 384 },
|
||||
{ "path": "actions/status.json", "mode": 384 },
|
||||
{ "path": "bin/install-server", "mode": 448 },
|
||||
{ "path": "bin/game-server", "mode": 448 }
|
||||
{
|
||||
"path": "actions/install.json",
|
||||
"mode": 384
|
||||
},
|
||||
{
|
||||
"path": "actions/start.json",
|
||||
"mode": 384
|
||||
},
|
||||
{
|
||||
"path": "actions/stop.json",
|
||||
"mode": 384
|
||||
},
|
||||
{
|
||||
"path": "actions/restart.json",
|
||||
"mode": 384
|
||||
},
|
||||
{
|
||||
"path": "actions/status.json",
|
||||
"mode": 384
|
||||
},
|
||||
{
|
||||
"path": "bin/install-server",
|
||||
"mode": 448
|
||||
},
|
||||
{
|
||||
"path": "bin/game-server",
|
||||
"mode": 448
|
||||
}
|
||||
],
|
||||
"productionLifecycle": {
|
||||
"operations": ["install", "enable", "disable", "upgrade", "rollback", "retire", "dependency-check"],
|
||||
"dependencyPolicy": "optional",
|
||||
"approvalRequired": ["disable", "rollback", "retire"]
|
||||
"operations": [
|
||||
"install",
|
||||
"enable",
|
||||
"disable",
|
||||
"upgrade",
|
||||
"rollback",
|
||||
"retire",
|
||||
"dependency-check"
|
||||
],
|
||||
"dependencyPolicy": "optional"
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
@@ -78,8 +113,12 @@
|
||||
"bundleKey": "dev-game-plugin",
|
||||
"bundleVersion": "1.0.0",
|
||||
"bundleIntegritySha256": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"permissions": ["server.read"],
|
||||
"bridgeActions": ["server.instances.read"]
|
||||
"permissions": [
|
||||
"server.read"
|
||||
],
|
||||
"bridgeActions": [
|
||||
"server.instances.read"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "config",
|
||||
@@ -88,8 +127,15 @@
|
||||
"bundleKey": "dev-game-plugin",
|
||||
"bundleVersion": "1.0.0",
|
||||
"bundleIntegritySha256": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"permissions": ["server.files.read", "server.files.write", "ai.invoke"],
|
||||
"bridgeActions": ["files.request", "ai.invoke"]
|
||||
"permissions": [
|
||||
"server.files.read",
|
||||
"server.files.write",
|
||||
"ai.invoke"
|
||||
],
|
||||
"bridgeActions": [
|
||||
"files.request",
|
||||
"ai.invoke"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "logs",
|
||||
@@ -98,12 +144,21 @@
|
||||
"bundleKey": "dev-game-plugin",
|
||||
"bundleVersion": "1.0.0",
|
||||
"bundleIntegritySha256": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"permissions": ["server.logs.read", "ai.invoke"],
|
||||
"bridgeActions": ["logs.query", "ai.invoke"]
|
||||
"permissions": [
|
||||
"server.logs.read",
|
||||
"ai.invoke"
|
||||
],
|
||||
"bridgeActions": [
|
||||
"logs.query",
|
||||
"ai.invoke"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ai": {
|
||||
"purposes": ["config.suggest", "logs.diagnose"],
|
||||
"purposes": [
|
||||
"config.suggest",
|
||||
"logs.diagnose"
|
||||
],
|
||||
"mediation": "platform",
|
||||
"configWritePolicy": "review-required"
|
||||
}
|
||||
|
||||
@@ -21,9 +21,29 @@
|
||||
],
|
||||
"createFormSchema": "schemas/create-form.schema.json",
|
||||
"createFields": [
|
||||
{ "key": "serverName", "label": "服务器名称", "type": "text", "required": true, "configKey": "motd" },
|
||||
{ "key": "gamePort", "label": "游戏端口", "type": "port", "required": true, "defaultValue": "25565", "configKey": "serverPort" },
|
||||
{ "key": "rconPort", "label": "RCON 端口", "type": "port", "required": true, "defaultValue": "25575", "configKey": "rconPort" }
|
||||
{
|
||||
"key": "serverName",
|
||||
"label": "服务器名称",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"configKey": "motd"
|
||||
},
|
||||
{
|
||||
"key": "gamePort",
|
||||
"label": "游戏端口",
|
||||
"type": "port",
|
||||
"required": true,
|
||||
"defaultValue": "25565",
|
||||
"configKey": "serverPort"
|
||||
},
|
||||
{
|
||||
"key": "rconPort",
|
||||
"label": "RCON 端口",
|
||||
"type": "port",
|
||||
"required": true,
|
||||
"defaultValue": "25575",
|
||||
"configKey": "rconPort"
|
||||
}
|
||||
]
|
||||
},
|
||||
"capabilities": [
|
||||
@@ -99,18 +119,46 @@
|
||||
"status": "actions/status.json"
|
||||
},
|
||||
"assetFiles": [
|
||||
{ "path": "actions/install.json", "mode": 384 },
|
||||
{ "path": "actions/start.json", "mode": 384 },
|
||||
{ "path": "actions/stop.json", "mode": 384 },
|
||||
{ "path": "actions/restart.json", "mode": 384 },
|
||||
{ "path": "actions/status.json", "mode": 384 },
|
||||
{ "path": "bin/install-server", "mode": 448 },
|
||||
{ "path": "bin/game-server", "mode": 448 }
|
||||
{
|
||||
"path": "actions/install.json",
|
||||
"mode": 384
|
||||
},
|
||||
{
|
||||
"path": "actions/start.json",
|
||||
"mode": 384
|
||||
},
|
||||
{
|
||||
"path": "actions/stop.json",
|
||||
"mode": 384
|
||||
},
|
||||
{
|
||||
"path": "actions/restart.json",
|
||||
"mode": 384
|
||||
},
|
||||
{
|
||||
"path": "actions/status.json",
|
||||
"mode": 384
|
||||
},
|
||||
{
|
||||
"path": "bin/install-server",
|
||||
"mode": 448
|
||||
},
|
||||
{
|
||||
"path": "bin/game-server",
|
||||
"mode": 448
|
||||
}
|
||||
],
|
||||
"productionLifecycle": {
|
||||
"operations": ["install", "enable", "disable", "upgrade", "rollback", "retire", "dependency-check"],
|
||||
"dependencyPolicy": "required",
|
||||
"approvalRequired": ["disable", "rollback", "retire"]
|
||||
"operations": [
|
||||
"install",
|
||||
"enable",
|
||||
"disable",
|
||||
"upgrade",
|
||||
"rollback",
|
||||
"retire",
|
||||
"dependency-check"
|
||||
],
|
||||
"dependencyPolicy": "required"
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
@@ -153,20 +201,92 @@
|
||||
"fileWorkspace": {
|
||||
"defaultDirectoryKey": "minecraft-config",
|
||||
"directories": [
|
||||
{ "key": "minecraft-config", "label": "服务器配置", "scope": "config" },
|
||||
{ "key": "minecraft-logs", "label": "日志文件", "scope": "logs" }
|
||||
{
|
||||
"key": "minecraft-config",
|
||||
"label": "服务器配置",
|
||||
"scope": "config"
|
||||
},
|
||||
{
|
||||
"key": "minecraft-logs",
|
||||
"label": "日志文件",
|
||||
"scope": "logs"
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
{ "key": "server-properties", "directoryKey": "minecraft-config", "label": "server.properties", "kind": "config", "editable": true },
|
||||
{ "key": "ops-json", "directoryKey": "minecraft-config", "label": "ops.json", "kind": "config", "editable": true },
|
||||
{ "key": "whitelist-json", "directoryKey": "minecraft-config", "label": "whitelist.json", "kind": "config", "editable": true },
|
||||
{ "key": "minecraft-latest-log", "directoryKey": "minecraft-logs", "label": "latest.log", "kind": "log", "streamKey": "minecraft.latest" }
|
||||
{
|
||||
"key": "server-properties",
|
||||
"directoryKey": "minecraft-config",
|
||||
"label": "server.properties",
|
||||
"kind": "config",
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
"key": "ops-json",
|
||||
"directoryKey": "minecraft-config",
|
||||
"label": "ops.json",
|
||||
"kind": "config",
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
"key": "whitelist-json",
|
||||
"directoryKey": "minecraft-config",
|
||||
"label": "whitelist.json",
|
||||
"kind": "config",
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
"key": "minecraft-latest-log",
|
||||
"directoryKey": "minecraft-logs",
|
||||
"label": "latest.log",
|
||||
"kind": "log",
|
||||
"streamKey": "minecraft.latest"
|
||||
}
|
||||
],
|
||||
"configFields": [
|
||||
{ "key": "motd", "fileKey": "server-properties", "configKey": "motd", "label": "服务器名称", "description": "显示在 Minecraft 服务器列表中的 MOTD。", "control": "text", "defaultValue": "Minecraft Server", "restartImpact": "restart-required" },
|
||||
{ "key": "server-port", "fileKey": "server-properties", "configKey": "server-port", "label": "游戏端口", "description": "Minecraft Java 客户端连接端口。", "control": "port", "minimum": 1, "maximum": 65535, "defaultValue": "25565", "restartImpact": "restart-required" },
|
||||
{ "key": "max-players", "fileKey": "server-properties", "configKey": "max-players", "label": "最大玩家数", "description": "允许同时进入服务器的玩家上限。", "control": "number", "minimum": 1, "maximum": 200, "defaultValue": "20", "restartImpact": "restart-required" },
|
||||
{ "key": "online-mode", "fileKey": "server-properties", "configKey": "online-mode", "label": "正版验证", "description": "是否启用 Mojang 在线身份验证。", "control": "boolean", "defaultValue": "true", "restartImpact": "restart-required" }
|
||||
{
|
||||
"key": "motd",
|
||||
"fileKey": "server-properties",
|
||||
"configKey": "motd",
|
||||
"label": "服务器名称",
|
||||
"description": "显示在 Minecraft 服务器列表中的 MOTD。",
|
||||
"control": "text",
|
||||
"defaultValue": "Minecraft Server",
|
||||
"restartImpact": "restart-required"
|
||||
},
|
||||
{
|
||||
"key": "server-port",
|
||||
"fileKey": "server-properties",
|
||||
"configKey": "server-port",
|
||||
"label": "游戏端口",
|
||||
"description": "Minecraft Java 客户端连接端口。",
|
||||
"control": "port",
|
||||
"minimum": 1,
|
||||
"maximum": 65535,
|
||||
"defaultValue": "25565",
|
||||
"restartImpact": "restart-required"
|
||||
},
|
||||
{
|
||||
"key": "max-players",
|
||||
"fileKey": "server-properties",
|
||||
"configKey": "max-players",
|
||||
"label": "最大玩家数",
|
||||
"description": "允许同时进入服务器的玩家上限。",
|
||||
"control": "number",
|
||||
"minimum": 1,
|
||||
"maximum": 200,
|
||||
"defaultValue": "20",
|
||||
"restartImpact": "restart-required"
|
||||
},
|
||||
{
|
||||
"key": "online-mode",
|
||||
"fileKey": "server-properties",
|
||||
"configKey": "online-mode",
|
||||
"label": "正版验证",
|
||||
"description": "是否启用 Mojang 在线身份验证。",
|
||||
"control": "boolean",
|
||||
"defaultValue": "true",
|
||||
"restartImpact": "restart-required"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ai": {
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
"remote.run.process.start",
|
||||
"remote.run.process.stop",
|
||||
"remote.run.logs.transfer",
|
||||
"remote.run.protected.sql",
|
||||
"remote.run.rcon.command",
|
||||
"remote.run.program.command",
|
||||
"client-manager.deploy",
|
||||
@@ -99,7 +98,6 @@
|
||||
"remote.run.process.start",
|
||||
"remote.run.process.stop",
|
||||
"remote.run.logs.transfer",
|
||||
"remote.run.protected.sql",
|
||||
"remote.run.rcon.command",
|
||||
"remote.run.program.command"
|
||||
],
|
||||
@@ -127,7 +125,6 @@
|
||||
"type": "companion.diagnostics",
|
||||
"title": "Collect companion diagnostics",
|
||||
"permission": "server.game-client.read",
|
||||
"approvalLevel": "none",
|
||||
"payloadSchemaRef": "schemas/bridge/diagnostics.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/diagnostics.result.schema.json",
|
||||
"timeoutSeconds": 30,
|
||||
@@ -137,7 +134,6 @@
|
||||
"type": "player.lookup",
|
||||
"title": "Look up SCUM player",
|
||||
"permission": "server.game-client.read",
|
||||
"approvalLevel": "none",
|
||||
"payloadSchemaRef": "schemas/bridge/player-lookup.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/player-lookup.result.schema.json",
|
||||
"timeoutSeconds": 30,
|
||||
@@ -147,7 +143,6 @@
|
||||
"type": "reward.deliver",
|
||||
"title": "Deliver SCUM reward",
|
||||
"permission": "server.game-client.command",
|
||||
"approvalLevel": "none",
|
||||
"payloadSchemaRef": "schemas/bridge/reward-deliver.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/reward-deliver.result.schema.json",
|
||||
"timeoutSeconds": 60,
|
||||
@@ -157,7 +152,6 @@
|
||||
"type": "player.notify",
|
||||
"title": "Notify SCUM player about approved gift",
|
||||
"permission": "server.game-client.command",
|
||||
"approvalLevel": "none",
|
||||
"payloadSchemaRef": "schemas/bridge/player-notify.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/player-notify.result.schema.json",
|
||||
"timeoutSeconds": 60,
|
||||
@@ -167,7 +161,6 @@
|
||||
"type": "vehicle.spawn",
|
||||
"title": "Spawn catalogued SCUM vehicle",
|
||||
"permission": "server.game-client.command",
|
||||
"approvalLevel": "none",
|
||||
"payloadSchemaRef": "schemas/bridge/vehicle-spawn.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/vehicle-spawn.result.schema.json",
|
||||
"timeoutSeconds": 60,
|
||||
@@ -177,7 +170,6 @@
|
||||
"type": "event.start",
|
||||
"title": "Start SCUM event",
|
||||
"permission": "server.game-client.command",
|
||||
"approvalLevel": "none",
|
||||
"payloadSchemaRef": "schemas/bridge/event-start.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/event-start.result.schema.json",
|
||||
"timeoutSeconds": 60,
|
||||
@@ -187,7 +179,6 @@
|
||||
"type": "restart.prepare",
|
||||
"title": "Prepare SCUM restart",
|
||||
"permission": "server.game-client.maintenance",
|
||||
"approvalLevel": "none",
|
||||
"payloadSchemaRef": "schemas/bridge/restart-prepare.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/restart-prepare.result.schema.json",
|
||||
"timeoutSeconds": 120,
|
||||
@@ -197,7 +188,6 @@
|
||||
"type": "maintenance.prepare",
|
||||
"title": "Prepare SCUM maintenance",
|
||||
"permission": "server.game-client.maintenance",
|
||||
"approvalLevel": "none",
|
||||
"payloadSchemaRef": "schemas/bridge/maintenance-prepare.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/maintenance-prepare.result.schema.json",
|
||||
"timeoutSeconds": 120,
|
||||
@@ -207,7 +197,6 @@
|
||||
"type": "game-state.patch",
|
||||
"title": "Patch SCUM player state",
|
||||
"permission": "server.game-client.maintenance",
|
||||
"approvalLevel": "none",
|
||||
"payloadSchemaRef": "schemas/bridge/game-state-patch.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/game-state-patch.result.schema.json",
|
||||
"timeoutSeconds": 120,
|
||||
@@ -286,9 +275,26 @@
|
||||
"pollIntervalSeconds": 3,
|
||||
"rowTarget": {
|
||||
"collection": "scum_users",
|
||||
"upsertKeys": ["steamId"],
|
||||
"upsertKeys": [
|
||||
"steamId"
|
||||
],
|
||||
"writeMode": "merge",
|
||||
"columnMappings": { "userProfileId": "userProfileId", "steamId": "steamId", "gamePlayerId": "gamePlayerId", "displayName": "displayName", "squadId": "squadId", "squadName": "squadName", "famePoints": "famePoints", "normalBalance": "normalBalance", "goldBalance": "goldBalance", "x": "x", "y": "y", "z": "z", "lastLoginTime": "lastLoginTime", "lastSaveTime": "lastSaveTime" }
|
||||
"columnMappings": {
|
||||
"userProfileId": "userProfileId",
|
||||
"steamId": "steamId",
|
||||
"gamePlayerId": "gamePlayerId",
|
||||
"displayName": "displayName",
|
||||
"squadId": "squadId",
|
||||
"squadName": "squadName",
|
||||
"famePoints": "famePoints",
|
||||
"normalBalance": "normalBalance",
|
||||
"goldBalance": "goldBalance",
|
||||
"x": "x",
|
||||
"y": "y",
|
||||
"z": "z",
|
||||
"lastLoginTime": "lastLoginTime",
|
||||
"lastSaveTime": "lastSaveTime"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
@@ -306,9 +312,22 @@
|
||||
"pollIntervalSeconds": 1800,
|
||||
"rowTarget": {
|
||||
"collection": "scum_squads",
|
||||
"upsertKeys": ["squadId"],
|
||||
"upsertKeys": [
|
||||
"squadId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": { "squadId": "squadId", "name": "name", "leaderProfileId": "leaderProfileId", "leaderPlayerId": "leaderPlayerId", "memberCount": "memberCount", "score": "score", "memberLimit": "memberLimit", "message": "message", "info": "info", "lastMemberLoginTime": "lastMemberLoginTime" }
|
||||
"columnMappings": {
|
||||
"squadId": "squadId",
|
||||
"name": "name",
|
||||
"leaderProfileId": "leaderProfileId",
|
||||
"leaderPlayerId": "leaderPlayerId",
|
||||
"memberCount": "memberCount",
|
||||
"score": "score",
|
||||
"memberLimit": "memberLimit",
|
||||
"message": "message",
|
||||
"info": "info",
|
||||
"lastMemberLoginTime": "lastMemberLoginTime"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
@@ -326,9 +345,20 @@
|
||||
"pollIntervalSeconds": 1800,
|
||||
"rowTarget": {
|
||||
"collection": "scum_squad_members",
|
||||
"upsertKeys": ["squadId", "steamId"],
|
||||
"upsertKeys": [
|
||||
"squadId",
|
||||
"steamId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": { "squadId": "squadId", "userProfileId": "userProfileId", "gamePlayerId": "gamePlayerId", "steamId": "steamId", "displayName": "displayName", "rank": "rank", "isLeader": "isLeader" }
|
||||
"columnMappings": {
|
||||
"squadId": "squadId",
|
||||
"userProfileId": "userProfileId",
|
||||
"gamePlayerId": "gamePlayerId",
|
||||
"steamId": "steamId",
|
||||
"displayName": "displayName",
|
||||
"rank": "rank",
|
||||
"isLeader": "isLeader"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
@@ -346,9 +376,21 @@
|
||||
"pollIntervalSeconds": 3,
|
||||
"rowTarget": {
|
||||
"collection": "scum_vehicles",
|
||||
"upsertKeys": ["vehicleId"],
|
||||
"upsertKeys": [
|
||||
"vehicleId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": { "vehicleId": "vehicleId", "entityId": "entityId", "className": "className", "label": "label", "x": "x", "y": "y", "z": "z", "lastAccessTime": "lastAccessTime", "isFunctional": "isFunctional" }
|
||||
"columnMappings": {
|
||||
"vehicleId": "vehicleId",
|
||||
"entityId": "entityId",
|
||||
"className": "className",
|
||||
"label": "label",
|
||||
"x": "x",
|
||||
"y": "y",
|
||||
"z": "z",
|
||||
"lastAccessTime": "lastAccessTime",
|
||||
"isFunctional": "isFunctional"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
@@ -366,9 +408,25 @@
|
||||
"pollIntervalSeconds": 1800,
|
||||
"rowTarget": {
|
||||
"collection": "scum_flags",
|
||||
"upsertKeys": ["flagId"],
|
||||
"upsertKeys": [
|
||||
"flagId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": { "flagId": "flagId", "entityId": "entityId", "baseId": "baseId", "ownerProfileId": "ownerProfileId", "ownerPlayerId": "ownerPlayerId", "ownerSquadId": "ownerSquadId", "ownerSquadName": "ownerSquadName", "overtakerProfileId": "overtakerProfileId", "overtakeEndTime": "overtakeEndTime", "ownershipConfidence": "ownershipConfidence", "x": "x", "y": "y", "z": "z" }
|
||||
"columnMappings": {
|
||||
"flagId": "flagId",
|
||||
"entityId": "entityId",
|
||||
"baseId": "baseId",
|
||||
"ownerProfileId": "ownerProfileId",
|
||||
"ownerPlayerId": "ownerPlayerId",
|
||||
"ownerSquadId": "ownerSquadId",
|
||||
"ownerSquadName": "ownerSquadName",
|
||||
"overtakerProfileId": "overtakerProfileId",
|
||||
"overtakeEndTime": "overtakeEndTime",
|
||||
"ownershipConfidence": "ownershipConfidence",
|
||||
"x": "x",
|
||||
"y": "y",
|
||||
"z": "z"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
@@ -386,9 +444,24 @@
|
||||
"pollIntervalSeconds": 3,
|
||||
"rowTarget": {
|
||||
"collection": "scum_map_points",
|
||||
"upsertKeys": ["subjectType", "subjectId"],
|
||||
"upsertKeys": [
|
||||
"subjectType",
|
||||
"subjectId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": { "subjectType": "subjectType", "subjectId": "subjectId", "userProfileId": "userProfileId", "gamePlayerId": "gamePlayerId", "vehicleId": "vehicleId", "entityId": "entityId", "baseId": "baseId", "x": "x", "y": "y", "z": "z", "observedAt": "observedAt" }
|
||||
"columnMappings": {
|
||||
"subjectType": "subjectType",
|
||||
"subjectId": "subjectId",
|
||||
"userProfileId": "userProfileId",
|
||||
"gamePlayerId": "gamePlayerId",
|
||||
"vehicleId": "vehicleId",
|
||||
"entityId": "entityId",
|
||||
"baseId": "baseId",
|
||||
"x": "x",
|
||||
"y": "y",
|
||||
"z": "z",
|
||||
"observedAt": "observedAt"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
@@ -406,9 +479,22 @@
|
||||
"pollIntervalSeconds": 1800,
|
||||
"rowTarget": {
|
||||
"collection": "scum_tasks",
|
||||
"upsertKeys": ["taskRecordId"],
|
||||
"upsertKeys": [
|
||||
"taskRecordId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": { "taskRecordId": "taskRecordId", "taskKind": "taskKind", "userProfileId": "userProfileId", "mapId": "mapId", "trackingDataSetId": "trackingDataSetId", "dataAssetPath": "dataAssetPath", "sequenceIndex": "sequenceIndex", "isTracked": "isTracked", "state": "state", "completionDeadline": "completionDeadline" }
|
||||
"columnMappings": {
|
||||
"taskRecordId": "taskRecordId",
|
||||
"taskKind": "taskKind",
|
||||
"userProfileId": "userProfileId",
|
||||
"mapId": "mapId",
|
||||
"trackingDataSetId": "trackingDataSetId",
|
||||
"dataAssetPath": "dataAssetPath",
|
||||
"sequenceIndex": "sequenceIndex",
|
||||
"isTracked": "isTracked",
|
||||
"state": "state",
|
||||
"completionDeadline": "completionDeadline"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
@@ -426,9 +512,25 @@
|
||||
"pollIntervalSeconds": 1800,
|
||||
"rowTarget": {
|
||||
"collection": "scum_native_event_rounds",
|
||||
"upsertKeys": ["eventRecordId"],
|
||||
"upsertKeys": [
|
||||
"eventRecordId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": { "eventRecordId": "eventRecordId", "eventId": "eventId", "roundId": "roundId", "userProfileId": "userProfileId", "startTime": "startTime", "endTime": "endTime", "state": "state", "score": "score", "enemyKills": "enemyKills", "teamKills": "teamKills", "deaths": "deaths", "assists": "assists", "headshots": "headshots" }
|
||||
"columnMappings": {
|
||||
"eventRecordId": "eventRecordId",
|
||||
"eventId": "eventId",
|
||||
"roundId": "roundId",
|
||||
"userProfileId": "userProfileId",
|
||||
"startTime": "startTime",
|
||||
"endTime": "endTime",
|
||||
"state": "state",
|
||||
"score": "score",
|
||||
"enemyKills": "enemyKills",
|
||||
"teamKills": "teamKills",
|
||||
"deaths": "deaths",
|
||||
"assists": "assists",
|
||||
"headshots": "headshots"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
@@ -446,9 +548,17 @@
|
||||
"pollIntervalSeconds": 1800,
|
||||
"rowTarget": {
|
||||
"collection": "scum_timed_gift_events",
|
||||
"upsertKeys": ["timedGiftId"],
|
||||
"upsertKeys": [
|
||||
"timedGiftId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": { "timedGiftId": "timedGiftId", "userProfileId": "userProfileId", "mapId": "mapId", "spawnTime": "spawnTime", "spawnAt": "spawnAt" }
|
||||
"columnMappings": {
|
||||
"timedGiftId": "timedGiftId",
|
||||
"userProfileId": "userProfileId",
|
||||
"mapId": "mapId",
|
||||
"spawnTime": "spawnTime",
|
||||
"spawnAt": "spawnAt"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
@@ -457,18 +567,35 @@
|
||||
"logProjections": [
|
||||
{
|
||||
"key": "scum.battleye.login",
|
||||
"streamKeys": ["scum.console.stdout"],
|
||||
"streamKeys": [
|
||||
"scum.console.stdout"
|
||||
],
|
||||
"steps": [
|
||||
{ "pattern": "Player \"(?P<displayName>[^\"]+)\" reported as player (?P<slot>\\d+)" },
|
||||
{ "pattern": "Player (?P<slot>\\d+) SteamID \\(assumed\\): (?P<steamId>\\d+)" }
|
||||
{
|
||||
"pattern": "Player \"(?P<displayName>[^\"]+)\" reported as player (?P<slot>\\d+)"
|
||||
},
|
||||
{
|
||||
"pattern": "Player (?P<slot>\\d+) SteamID \\(assumed\\): (?P<steamId>\\d+)"
|
||||
}
|
||||
],
|
||||
"correlationFields": [
|
||||
"slot"
|
||||
],
|
||||
"correlationFields": ["slot"],
|
||||
"maxInterveningLines": 8,
|
||||
"target": {
|
||||
"collection": "scum_users",
|
||||
"upsertKeys": ["steamId"],
|
||||
"captureMappings": { "steamId": "steamId", "displayName": "displayName", "slot": "slot" },
|
||||
"fixedValues": { "online": "true", "source": "process.stdout" },
|
||||
"upsertKeys": [
|
||||
"steamId"
|
||||
],
|
||||
"captureMappings": {
|
||||
"steamId": "steamId",
|
||||
"displayName": "displayName",
|
||||
"slot": "slot"
|
||||
},
|
||||
"fixedValues": {
|
||||
"online": "true",
|
||||
"source": "process.stdout"
|
||||
},
|
||||
"observedAtField": "lastLoginObservedAt"
|
||||
},
|
||||
"presence": {
|
||||
@@ -476,9 +603,18 @@
|
||||
"activeWindowSeconds": 600,
|
||||
"activityTarget": {
|
||||
"collection": "scum_activity_events",
|
||||
"upsertKeys": ["steamId", "observedAt"],
|
||||
"captureMappings": { "steamId": "steamId", "displayName": "displayName" },
|
||||
"fixedValues": { "eventType": "login", "source": "process.stdout" },
|
||||
"upsertKeys": [
|
||||
"steamId",
|
||||
"observedAt"
|
||||
],
|
||||
"captureMappings": {
|
||||
"steamId": "steamId",
|
||||
"displayName": "displayName"
|
||||
},
|
||||
"fixedValues": {
|
||||
"eventType": "login",
|
||||
"source": "process.stdout"
|
||||
},
|
||||
"observedAtField": "observedAt"
|
||||
}
|
||||
}
|
||||
@@ -488,109 +624,16 @@
|
||||
{
|
||||
"key": "scum-db-v57",
|
||||
"databaseUserVersion": 57,
|
||||
"logParserRefs": ["data-packs/scum-db-v57/log-parsers.json"],
|
||||
"configMapRefs": ["data-packs/scum-db-v57/config-maps.json"],
|
||||
"dataRefs": ["data-packs/scum-db-v57/gift-items.json", "data-packs/scum-db-v57/map-geometry.json"]
|
||||
}
|
||||
],
|
||||
"operationTemplates": [
|
||||
{
|
||||
"key": "player.fame.set",
|
||||
"title": "Set SCUM player fame through RCON",
|
||||
"permission": "server.game-client.command",
|
||||
"approvalLevel": "none",
|
||||
"kind": "rcon",
|
||||
"transportKey": "scum-management",
|
||||
"targetKey": "scum-management",
|
||||
"payloadSchemaRef": "schemas/bridge/player-fame-set.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/player-rcon-set.result.schema.json",
|
||||
"timeoutSeconds": 60,
|
||||
"maxPayloadBytes": 2048
|
||||
},
|
||||
{
|
||||
"key": "player.currency.normal.set",
|
||||
"title": "Set SCUM normal currency through RCON",
|
||||
"permission": "server.game-client.command",
|
||||
"approvalLevel": "none",
|
||||
"kind": "rcon",
|
||||
"transportKey": "scum-management",
|
||||
"targetKey": "scum-management",
|
||||
"payloadSchemaRef": "schemas/bridge/player-currency-set.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/player-rcon-set.result.schema.json",
|
||||
"timeoutSeconds": 60,
|
||||
"maxPayloadBytes": 2048
|
||||
},
|
||||
{
|
||||
"key": "player.currency.gold.set",
|
||||
"title": "Set SCUM gold currency through RCON",
|
||||
"permission": "server.game-client.command",
|
||||
"approvalLevel": "none",
|
||||
"kind": "rcon",
|
||||
"transportKey": "scum-management",
|
||||
"targetKey": "scum-management",
|
||||
"payloadSchemaRef": "schemas/bridge/player-currency-set.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/player-rcon-set.result.schema.json",
|
||||
"timeoutSeconds": 60,
|
||||
"maxPayloadBytes": 2048
|
||||
},
|
||||
{
|
||||
"key": "player.notify",
|
||||
"title": "Notify SCUM player through RCON chat",
|
||||
"permission": "server.game-client.command",
|
||||
"approvalLevel": "none",
|
||||
"kind": "rcon",
|
||||
"transportKey": "scum-management",
|
||||
"targetKey": "scum-management",
|
||||
"payloadSchemaRef": "schemas/bridge/player-notify.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/player-notify.result.schema.json",
|
||||
"timeoutSeconds": 60,
|
||||
"maxPayloadBytes": 2048
|
||||
},
|
||||
{
|
||||
"key": "reward.deliver",
|
||||
"title": "Deliver SCUM reward through typed command workflow",
|
||||
"permission": "server.game-client.command",
|
||||
"approvalLevel": "none",
|
||||
"kind": "rcon",
|
||||
"transportKey": "scum-management",
|
||||
"targetKey": "scum-management",
|
||||
"payloadSchemaRef": "schemas/bridge/reward-deliver.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/reward-deliver.result.schema.json",
|
||||
"timeoutSeconds": 60,
|
||||
"maxPayloadBytes": 4096
|
||||
},
|
||||
{
|
||||
"key": "player.attribute.855.set",
|
||||
"title": "Set SCUM DB-only player attribute 855",
|
||||
"permission": "server.game-client.maintenance",
|
||||
"approvalLevel": "platform-admin",
|
||||
"kind": "sqlite-mutation",
|
||||
"transportKey": "scum-database",
|
||||
"targetKey": "scum-database",
|
||||
"payloadSchemaRef": "schemas/bridge/player-attribute-855-set.payload.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/player-attribute-855-set.result.schema.json",
|
||||
"confirmationSchemaRef": "schemas/bridge/player-attribute-855-set.confirmation.schema.json",
|
||||
"timeoutSeconds": 120,
|
||||
"maxPayloadBytes": 4096,
|
||||
"maxRowsAffected": 1,
|
||||
"mutation": {
|
||||
"fieldKey": "855",
|
||||
"tableKey": "prisoner",
|
||||
"identityKey": "user_profile_id",
|
||||
"valueKey": "value",
|
||||
"confirmationQueryKey": "scum.player.profile",
|
||||
"allowedValueType": "integer",
|
||||
"minValue": 0,
|
||||
"maxValue": 100000
|
||||
},
|
||||
"safety": {
|
||||
"requiresApproval": true,
|
||||
"requiresOfflinePlayer": true,
|
||||
"requiresMaintenanceWindow": true,
|
||||
"requiresBeforeValue": true,
|
||||
"requiresConfirmation": true,
|
||||
"backupRequired": true
|
||||
}
|
||||
"logParserRefs": [
|
||||
"data-packs/scum-db-v57/log-parsers.json"
|
||||
],
|
||||
"configMapRefs": [
|
||||
"data-packs/scum-db-v57/config-maps.json"
|
||||
],
|
||||
"dataRefs": [
|
||||
"data-packs/scum-db-v57/gift-items.json",
|
||||
"data-packs/scum-db-v57/map-geometry.json"
|
||||
]
|
||||
}
|
||||
],
|
||||
"commandRetentionSeconds": 604800,
|
||||
@@ -652,13 +695,6 @@
|
||||
"scum.player.profile",
|
||||
"scum.positions"
|
||||
],
|
||||
"operationKeys": [
|
||||
"player.fame.set",
|
||||
"player.currency.normal.set",
|
||||
"player.currency.gold.set",
|
||||
"player.notify",
|
||||
"player.attribute.855.set"
|
||||
],
|
||||
"featureKeys": [
|
||||
"player.intelligence",
|
||||
"state.patch"
|
||||
@@ -707,10 +743,6 @@
|
||||
"commandTypes": [
|
||||
"reward.deliver"
|
||||
],
|
||||
"operationKeys": [
|
||||
"reward.deliver",
|
||||
"player.notify"
|
||||
],
|
||||
"featureKeys": [
|
||||
"reward.delivery"
|
||||
]
|
||||
@@ -873,12 +905,7 @@
|
||||
"retire",
|
||||
"dependency-check"
|
||||
],
|
||||
"dependencyPolicy": "required",
|
||||
"approvalRequired": [
|
||||
"disable",
|
||||
"rollback",
|
||||
"retire"
|
||||
]
|
||||
"dependencyPolicy": "required"
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
@@ -944,8 +971,8 @@
|
||||
"trajectory.collect"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "gifts",
|
||||
{
|
||||
"key": "gifts",
|
||||
"title": "礼包管理",
|
||||
"path": "/gifts",
|
||||
"bundleKey": "scum-server-plugin",
|
||||
@@ -957,16 +984,16 @@
|
||||
"server.game-client.read",
|
||||
"server.game-client.command"
|
||||
],
|
||||
"bridgeActions": [
|
||||
"server.instances.read",
|
||||
"remote.access.request"
|
||||
],
|
||||
"bridgeActions": [
|
||||
"server.instances.read",
|
||||
"remote.access.request"
|
||||
],
|
||||
"featureKeys": [
|
||||
"reward.delivery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "workflows",
|
||||
{
|
||||
"key": "workflows",
|
||||
"title": "活动管理",
|
||||
"path": "/activity",
|
||||
"bundleKey": "scum-server-plugin",
|
||||
@@ -978,10 +1005,10 @@
|
||||
"server.game-client.read",
|
||||
"server.game-client.command"
|
||||
],
|
||||
"bridgeActions": [
|
||||
"server.instances.read",
|
||||
"remote.access.request"
|
||||
],
|
||||
"bridgeActions": [
|
||||
"server.instances.read",
|
||||
"remote.access.request"
|
||||
],
|
||||
"featureKeys": [
|
||||
"player.intelligence"
|
||||
]
|
||||
@@ -1359,8 +1386,7 @@
|
||||
"kind": "sqlite",
|
||||
"targetKey": "scum-database",
|
||||
"capabilities": [
|
||||
"remote.run.db.sqlite.query",
|
||||
"remote.run.protected.sql"
|
||||
"remote.run.db.sqlite.query"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
},
|
||||
"productionLifecycle": {
|
||||
"type": "object",
|
||||
"required": ["operations", "dependencyPolicy", "approvalRequired"],
|
||||
"required": ["operations", "dependencyPolicy"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"operations": {
|
||||
@@ -173,12 +173,7 @@
|
||||
"uniqueItems": true,
|
||||
"minItems": 1
|
||||
},
|
||||
"dependencyPolicy": { "enum": ["required", "optional"] },
|
||||
"approvalRequired": {
|
||||
"type": "array",
|
||||
"items": { "enum": ["disable", "rollback", "retire"] },
|
||||
"uniqueItems": true
|
||||
}
|
||||
"dependencyPolicy": { "enum": ["required", "optional"] }
|
||||
}
|
||||
},
|
||||
"pages": {
|
||||
@@ -269,11 +264,6 @@
|
||||
"items": { "$ref": "#/$defs/gameClientBridgeDataPack" },
|
||||
"maxItems": 64
|
||||
},
|
||||
"operationTemplates": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/gameClientBridgeOperationTemplate" },
|
||||
"maxItems": 128
|
||||
},
|
||||
"commandRetentionSeconds": { "type": "integer", "minimum": 1, "maximum": 31536000 },
|
||||
"maxCommands": { "type": "integer", "minimum": 1, "maximum": 100000 },
|
||||
"pages": {
|
||||
@@ -307,13 +297,12 @@
|
||||
},
|
||||
"gameClientBridgeCommand": {
|
||||
"type": "object",
|
||||
"required": ["type", "title", "permission", "approvalLevel", "payloadSchemaRef", "timeoutSeconds", "maxPayloadBytes"],
|
||||
"required": ["type", "title", "permission", "payloadSchemaRef", "timeoutSeconds", "maxPayloadBytes"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$" },
|
||||
"title": { "type": "string", "minLength": 1, "maxLength": 80 },
|
||||
"permission": { "$ref": "#/$defs/pluginPermission" },
|
||||
"approvalLevel": { "enum": ["none", "operator", "platform-admin"] },
|
||||
"payloadSchemaRef": { "$ref": "#/$defs/relativeJsonRef" },
|
||||
"resultSchemaRef": { "$ref": "#/$defs/relativeJsonRef" },
|
||||
"timeoutSeconds": { "type": "integer", "minimum": 1, "maximum": 3600 },
|
||||
@@ -419,55 +408,6 @@
|
||||
"dataRefs": { "type": "array", "items": { "$ref": "#/$defs/relativeJsonRef" }, "uniqueItems": true }
|
||||
}
|
||||
},
|
||||
"gameClientBridgeOperationSafety": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"requiresApproval": { "type": "boolean" },
|
||||
"requiresOfflinePlayer": { "type": "boolean" },
|
||||
"requiresMaintenanceWindow": { "type": "boolean" },
|
||||
"requiresBeforeValue": { "type": "boolean" },
|
||||
"requiresConfirmation": { "type": "boolean" },
|
||||
"backupRequired": { "type": "boolean" }
|
||||
}
|
||||
},
|
||||
"gameClientBridgeOperationMutation": {
|
||||
"type": "object",
|
||||
"required": ["fieldKey", "tableKey", "identityKey", "valueKey", "confirmationQueryKey", "allowedValueType"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"fieldKey": { "$ref": "#/$defs/logicalKey" },
|
||||
"tableKey": { "$ref": "#/$defs/logicalKey" },
|
||||
"identityKey": { "$ref": "#/$defs/logicalKey" },
|
||||
"valueKey": { "$ref": "#/$defs/logicalKey" },
|
||||
"confirmationQueryKey": { "$ref": "#/$defs/logicalKey" },
|
||||
"allowedValueType": { "enum": ["integer", "number", "string", "boolean"] },
|
||||
"minValue": { "type": "number" },
|
||||
"maxValue": { "type": "number" }
|
||||
}
|
||||
},
|
||||
"gameClientBridgeOperationTemplate": {
|
||||
"type": "object",
|
||||
"required": ["key", "title", "permission", "approvalLevel", "kind", "transportKey", "targetKey", "payloadSchemaRef", "timeoutSeconds", "maxPayloadBytes"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$" },
|
||||
"title": { "type": "string", "minLength": 1, "maxLength": 80 },
|
||||
"permission": { "$ref": "#/$defs/pluginPermission" },
|
||||
"approvalLevel": { "enum": ["none", "operator", "platform-admin"] },
|
||||
"kind": { "enum": ["rcon", "sqlite-mutation"] },
|
||||
"transportKey": { "$ref": "#/$defs/logicalKey" },
|
||||
"targetKey": { "$ref": "#/$defs/logicalKey" },
|
||||
"payloadSchemaRef": { "$ref": "#/$defs/relativeJsonRef" },
|
||||
"resultSchemaRef": { "$ref": "#/$defs/relativeJsonRef" },
|
||||
"confirmationSchemaRef": { "$ref": "#/$defs/relativeJsonRef" },
|
||||
"timeoutSeconds": { "type": "integer", "minimum": 1, "maximum": 3600 },
|
||||
"maxPayloadBytes": { "type": "integer", "minimum": 1, "maximum": 65536 },
|
||||
"maxRowsAffected": { "type": "integer", "minimum": 1, "maximum": 10 },
|
||||
"mutation": { "$ref": "#/$defs/gameClientBridgeOperationMutation" },
|
||||
"safety": { "$ref": "#/$defs/gameClientBridgeOperationSafety" }
|
||||
}
|
||||
},
|
||||
"gameClientBridgePageContract": {
|
||||
"type": "object",
|
||||
"required": ["pageKey"],
|
||||
@@ -477,7 +417,6 @@
|
||||
"commandTypes": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
||||
"snapshotTypes": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
||||
"queryTemplateKeys": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
||||
"operationKeys": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
||||
"featureKeys": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }
|
||||
}
|
||||
},
|
||||
@@ -525,7 +464,6 @@
|
||||
"remote.run.db.sqlite.query",
|
||||
"remote.run.logs.transfer",
|
||||
"remote.run.rcon.command",
|
||||
"remote.run.protected.sql",
|
||||
"remote.run.program.command",
|
||||
"client-manager.deploy",
|
||||
"client-manager.control",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-55
@@ -46,7 +46,6 @@ export type RunCapability =
|
||||
| "remote.run.db.sqlite.query"
|
||||
| "remote.run.logs.transfer"
|
||||
| "remote.run.rcon.command"
|
||||
| "remote.run.protected.sql"
|
||||
| "remote.run.program.command"
|
||||
| "client-manager.deploy"
|
||||
| "client-manager.control"
|
||||
@@ -150,7 +149,6 @@ export type PluginProductionLifecyclePayload = Record<string, string> & {
|
||||
operation: ProductionPluginLifecycleOperation;
|
||||
targetVersion: string;
|
||||
idempotencyKey: string;
|
||||
confirmed: "true" | "false";
|
||||
};
|
||||
|
||||
export type PluginRemoteAccessPayload = Record<string, string> & {
|
||||
@@ -218,15 +216,12 @@ export interface GamePluginRemoteAccess {
|
||||
logTransfer?: boolean;
|
||||
}
|
||||
|
||||
export type GameClientBridgeApprovalLevel = "none" | "operator" | "platform-admin";
|
||||
export type GameClientBridgeApprovalState = "not_required" | "pending" | "approved" | "rejected";
|
||||
export type GameClientBridgeCommandState = "pending" | "claimed" | "succeeded" | "failed" | "unknown" | "cancelled" | "expired";
|
||||
|
||||
export interface GameClientBridgeCommandDeclaration {
|
||||
type: string;
|
||||
title: string;
|
||||
permission: PluginPermission;
|
||||
approvalLevel: GameClientBridgeApprovalLevel;
|
||||
payloadSchemaRef: string;
|
||||
resultSchemaRef?: string;
|
||||
timeoutSeconds: number;
|
||||
@@ -299,52 +294,11 @@ export interface GameClientBridgeDataPackDeclaration {
|
||||
configMapRefs: string[];
|
||||
}
|
||||
|
||||
export type GameClientBridgeOperationKind = "rcon" | "sqlite-mutation";
|
||||
|
||||
export interface GameClientBridgeOperationSafety {
|
||||
requiresApproval?: boolean;
|
||||
requiresOfflinePlayer?: boolean;
|
||||
requiresMaintenanceWindow?: boolean;
|
||||
requiresBeforeValue?: boolean;
|
||||
requiresConfirmation?: boolean;
|
||||
backupRequired?: boolean;
|
||||
}
|
||||
|
||||
export interface GameClientBridgeOperationMutationDeclaration {
|
||||
fieldKey: string;
|
||||
tableKey: string;
|
||||
identityKey: string;
|
||||
valueKey: string;
|
||||
confirmationQueryKey: string;
|
||||
allowedValueType: "integer" | "number" | "string" | "boolean";
|
||||
minValue?: number;
|
||||
maxValue?: number;
|
||||
}
|
||||
|
||||
export interface GameClientBridgeOperationTemplateDeclaration {
|
||||
key: string;
|
||||
title: string;
|
||||
permission: PluginPermission;
|
||||
approvalLevel: Exclude<GameClientBridgeApprovalLevel, "none">;
|
||||
kind: GameClientBridgeOperationKind;
|
||||
transportKey: string;
|
||||
targetKey: string;
|
||||
payloadSchemaRef: string;
|
||||
resultSchemaRef?: string;
|
||||
confirmationSchemaRef?: string;
|
||||
timeoutSeconds: number;
|
||||
maxPayloadBytes: number;
|
||||
maxRowsAffected?: number;
|
||||
mutation?: GameClientBridgeOperationMutationDeclaration;
|
||||
safety?: GameClientBridgeOperationSafety;
|
||||
}
|
||||
|
||||
export interface GameClientBridgePageContract {
|
||||
pageKey: string;
|
||||
commandTypes?: string[];
|
||||
snapshotTypes?: string[];
|
||||
queryTemplateKeys?: string[];
|
||||
operationKeys?: string[];
|
||||
featureKeys?: string[];
|
||||
}
|
||||
|
||||
@@ -372,7 +326,6 @@ export interface GameClientBridgeManifest {
|
||||
queryTemplates?: GameClientBridgeQueryTemplateDeclaration[];
|
||||
logProjections?: GameClientBridgeLogProjectionDeclaration[];
|
||||
dataPacks?: GameClientBridgeDataPackDeclaration[];
|
||||
operationTemplates?: GameClientBridgeOperationTemplateDeclaration[];
|
||||
commandRetentionSeconds: number;
|
||||
maxCommands: number;
|
||||
pages?: GameClientBridgePageContract[];
|
||||
@@ -418,7 +371,6 @@ export interface GameClientBridgeCommand {
|
||||
commandType: string;
|
||||
priority: number;
|
||||
state: GameClientBridgeCommandState;
|
||||
approvalState: GameClientBridgeApprovalState;
|
||||
result?: GameClientBridgeCommandResult;
|
||||
expiresAt: string;
|
||||
createdAt: string;
|
||||
@@ -760,7 +712,6 @@ export interface GamePluginManifest {
|
||||
productionLifecycle: {
|
||||
operations: ProductionPluginLifecycleOperation[];
|
||||
dependencyPolicy: "required" | "optional";
|
||||
approvalRequired: Array<"disable" | "rollback" | "retire">;
|
||||
};
|
||||
pages?: GamePluginPage[];
|
||||
ai?: {
|
||||
@@ -870,14 +821,10 @@ export function createProductionPluginLifecycleRequest(input: {
|
||||
operation: ProductionPluginLifecycleOperation;
|
||||
targetVersion?: string;
|
||||
idempotencyKey: string;
|
||||
confirmed?: boolean;
|
||||
}): PluginBridgeExecutionRequest<PluginProductionLifecyclePayload> {
|
||||
if (!input.context.serverInstanceId) {
|
||||
throw new Error("serverInstanceId is required for plugin lifecycle requests");
|
||||
}
|
||||
if (["disable", "rollback", "retire"].includes(input.operation) && !input.confirmed) {
|
||||
throw new Error("disruptive plugin lifecycle requests require confirmation");
|
||||
}
|
||||
return createBridgeExecutionRequest({
|
||||
requestId: input.requestId,
|
||||
context: input.context,
|
||||
@@ -885,8 +832,7 @@ export function createProductionPluginLifecycleRequest(input: {
|
||||
payload: {
|
||||
operation: input.operation,
|
||||
targetVersion: input.targetVersion ?? "",
|
||||
idempotencyKey: input.idempotencyKey,
|
||||
confirmed: input.confirmed ? "true" : "false"
|
||||
idempotencyKey: input.idempotencyKey
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
parseAIInvocationResponse,
|
||||
type GameClientBridgeQueryTemplateDeclaration,
|
||||
type GameClientBridgeLogProjectionDeclaration,
|
||||
type GameClientBridgeOperationTemplateDeclaration,
|
||||
type GameClientBridgeCompanionDeclaration,
|
||||
type GamePluginManifest,
|
||||
type RuntimeLogEventDeclaration,
|
||||
@@ -55,7 +54,6 @@ type MutableBridgeManifest = {
|
||||
commands: Array<Record<string, unknown>>;
|
||||
snapshots: Array<Record<string, unknown>>;
|
||||
queryTemplates?: Array<Record<string, unknown>>;
|
||||
operationTemplates?: Array<Record<string, unknown>>;
|
||||
commandRetentionSeconds: number;
|
||||
maxCommands: number;
|
||||
pages: Array<Record<string, unknown>>;
|
||||
@@ -94,14 +92,13 @@ function validateTemporaryBridgeManifest(mutate?: (manifest: MutableBridgeManife
|
||||
fs.cpSync(path.join(pluginsRoot, "examples/dev-game-plugin"), fixtureDir, { recursive: true });
|
||||
const manifestPath = path.join(fixtureDir, "manifest.json");
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")) as MutableBridgeManifest;
|
||||
manifest.capabilities = [...manifest.capabilities, "remote.run.db.sqlite.query", "remote.run.rcon.command", "remote.run.protected.sql"];
|
||||
manifest.capabilities = [...manifest.capabilities, "remote.run.db.sqlite.query", "remote.run.rcon.command"];
|
||||
manifest.permissions = [...manifest.permissions, "server.game-client.command", "server.game-client.read", "server.game-client.maintenance"];
|
||||
manifest.remoteAccess = { methods: ["run"], runCapabilities: ["remote.run.db.sqlite.query", "remote.run.rcon.command", "remote.run.protected.sql"], databaseEngines: ["sqlite"] };
|
||||
manifest.remoteAccess = { methods: ["run"], runCapabilities: ["remote.run.db.sqlite.query", "remote.run.rcon.command"], databaseEngines: ["sqlite"] };
|
||||
manifest.runtimeProfiles = {
|
||||
transportProfiles: [
|
||||
{ key: "sqlite-db", kind: "sqlite", targetKey: "db/sqlite", capabilities: ["remote.run.db.sqlite.query"] },
|
||||
{ key: "scum-rcon", kind: "rcon", targetKey: "scum-rcon", capabilities: ["remote.run.rcon.command"] },
|
||||
{ key: "scum-mutation-db", kind: "sqlite", targetKey: "scum-mutation-db", capabilities: ["remote.run.protected.sql"] }
|
||||
{ key: "scum-rcon", kind: "rcon", targetKey: "scum-rcon", capabilities: ["remote.run.rcon.command"] }
|
||||
]
|
||||
};
|
||||
const overviewPage = manifest.pages?.find((page) => page.key === "overview");
|
||||
@@ -110,24 +107,18 @@ function validateTemporaryBridgeManifest(mutate?: (manifest: MutableBridgeManife
|
||||
overviewPage.bridgeActions = [...(overviewPage.bridgeActions ?? []), "remote.access.request"];
|
||||
}
|
||||
manifest.gameClientBridge = {
|
||||
commands: [{ type: "diagnostic.ping", title: "Diagnostic ping", permission: "server.game-client.command", approvalLevel: "none", payloadSchemaRef: "schemas/bridge/diagnostic-ping.schema.json", resultSchemaRef: "schemas/bridge/diagnostic-ping-result.schema.json", timeoutSeconds: 60, maxPayloadBytes: 4096 }],
|
||||
commands: [{ type: "diagnostic.ping", title: "Diagnostic ping", permission: "server.game-client.command", payloadSchemaRef: "schemas/bridge/diagnostic-ping.schema.json", resultSchemaRef: "schemas/bridge/diagnostic-ping-result.schema.json", timeoutSeconds: 60, maxPayloadBytes: 4096 }],
|
||||
snapshots: [{ type: "players", schemaVersion: "1", schemaRef: "schemas/bridge/players.schema.json", keepForSeconds: 3600, maxRecords: 100 }],
|
||||
queryTemplates: [{ key: "player.by-id", title: "Find player by ID", permission: "server.game-client.read", engine: "sqlite", transportKey: "sqlite-db", targetKey: "db/sqlite", parameterSchemaRef: "schemas/bridge/player-by-id.parameters.schema.json", resultSchemaRef: "schemas/bridge/player-by-id.result.schema.json", maxRows: 1, timeoutSeconds: 10 }],
|
||||
operationTemplates: [
|
||||
{ key: "player.fame.set", title: "Set player fame", permission: "server.game-client.command", approvalLevel: "none", kind: "rcon", transportKey: "scum-rcon", targetKey: "scum-rcon", payloadSchemaRef: "schemas/bridge/player-fame-set.payload.schema.json", resultSchemaRef: "schemas/bridge/player-fame-set.result.schema.json", timeoutSeconds: 60, maxPayloadBytes: 2048 },
|
||||
{ key: "player.attribute.855.set", title: "Set player attribute 855", permission: "server.game-client.maintenance", approvalLevel: "platform-admin", kind: "sqlite-mutation", transportKey: "scum-mutation-db", targetKey: "scum-mutation-db", payloadSchemaRef: "schemas/bridge/player-attribute-855-set.payload.schema.json", resultSchemaRef: "schemas/bridge/player-attribute-855-set.result.schema.json", confirmationSchemaRef: "schemas/bridge/player-attribute-855-set.confirmation.schema.json", timeoutSeconds: 120, maxPayloadBytes: 4096, maxRowsAffected: 1, mutation: { fieldKey: "855", tableKey: "prisoner", identityKey: "user_profile_id", valueKey: "value", confirmationQueryKey: "player.by-id", allowedValueType: "integer", minValue: 0, maxValue: 100000 }, safety: { requiresApproval: true, requiresOfflinePlayer: true, requiresBeforeValue: true, requiresConfirmation: true, backupRequired: true } }
|
||||
],
|
||||
commandRetentionSeconds: 86400,
|
||||
maxCommands: 1000,
|
||||
pages: [{ pageKey: "overview", commandTypes: ["diagnostic.ping"], snapshotTypes: ["players"], queryTemplateKeys: ["player.by-id"], operationKeys: ["player.fame.set", "player.attribute.855.set"] }]
|
||||
pages: [{ pageKey: "overview", commandTypes: ["diagnostic.ping"], snapshotTypes: ["players"], queryTemplateKeys: ["player.by-id"] }]
|
||||
};
|
||||
writeFixtureJSON(fixtureDir, "schemas/bridge/diagnostic-ping.schema.json", bridgeObjectSchema({ message: { type: "string", minLength: 1, maxLength: 200 } }, ["message"]));
|
||||
writeFixtureJSON(fixtureDir, "schemas/bridge/diagnostic-ping-result.schema.json", bridgeObjectSchema({ accepted: { type: "boolean" } }, ["accepted"]));
|
||||
writeFixtureJSON(fixtureDir, "schemas/bridge/players.schema.json", bridgeObjectSchema({ players: { type: "array", maxItems: 100, items: bridgeObjectSchema({ id: { type: "string", minLength: 1, maxLength: 80 } }, ["id"]) } }, ["players"]));
|
||||
writeFixtureJSON(fixtureDir, "schemas/bridge/player-by-id.parameters.schema.json", bridgeObjectSchema({ playerId: { type: "string", minLength: 1, maxLength: 96 } }, ["playerId"]));
|
||||
writeFixtureJSON(fixtureDir, "schemas/bridge/player-by-id.result.schema.json", bridgeObjectSchema({ players: { type: "array", maxItems: 1, items: bridgeObjectSchema({ playerId: { type: "string", minLength: 1, maxLength: 96 } }, ["playerId"]) } }, ["players"]));
|
||||
writeFixtureJSON(fixtureDir, "schemas/bridge/player-fame-set.payload.schema.json", bridgeObjectSchema({ playerId: { type: "string", minLength: 1, maxLength: 96 }, fame: { type: "integer", minimum: 0, maximum: 2147483647 } }, ["playerId", "fame"]));
|
||||
writeFixtureJSON(fixtureDir, "schemas/bridge/player-fame-set.result.schema.json", bridgeObjectSchema({ outcome: { enum: ["queued", "succeeded", "failed", "unknown"] } }, ["outcome"]));
|
||||
writeFixtureJSON(fixtureDir, "schemas/bridge/player-fame-set.confirmation.schema.json", bridgeObjectSchema({ playerId: { type: "string" }, fame: { type: "integer" } }, ["playerId", "fame"]));
|
||||
writeFixtureJSON(fixtureDir, "schemas/bridge/player-attribute-855-set.payload.schema.json", bridgeObjectSchema({ playerId: { type: "string", minLength: 1, maxLength: 96 }, before: { type: "number" }, after: { type: "number" }, safetyWindow: { type: "string", minLength: 1, maxLength: 96 } }, ["playerId", "before", "after", "safetyWindow"]));
|
||||
writeFixtureJSON(fixtureDir, "schemas/bridge/player-attribute-855-set.result.schema.json", bridgeObjectSchema({ outcome: { enum: ["succeeded", "failed", "unknown"] }, rowsAffected: { type: "integer", minimum: 0, maximum: 1 } }, ["outcome", "rowsAffected"]));
|
||||
@@ -192,12 +183,10 @@ describe("plugin manifest validation", () => {
|
||||
|
||||
it("removes raw SQL command surfaces", () => {
|
||||
const pluginDir = path.join(pluginsRoot, "examples/scum-server-plugin");
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(pluginDir, "manifest.json"), "utf8")) as { gameClientBridge: { commands: Array<{ type: string; payloadSchemaRef: string }>; queryTemplates: Array<{ key: string }>; operationTemplates: Array<{ key: string; kind: string }> } };
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(pluginDir, "manifest.json"), "utf8")) as { gameClientBridge: { commands: Array<{ type: string; payloadSchemaRef: string }>; queryTemplates: Array<{ key: string }> } };
|
||||
expect(manifest.gameClientBridge.commands.some((command) => command.type === "diagnostic.ping")).toBe(false);
|
||||
expect(manifest.gameClientBridge.commands.map((command) => command.type)).not.toEqual(expect.arrayContaining(["config.read", "config.patch", "database.request", "management.rcon.request", "management.program.request"]));
|
||||
expect(manifest.gameClientBridge.queryTemplates.map((query) => query.key)).toEqual(expect.arrayContaining(["scum.player.profile", "scum.squads", "scum.squad-members", "scum.vehicles", "scum.flags", "scum.positions"]));
|
||||
expect(manifest.gameClientBridge.operationTemplates.map((operation) => operation.key)).toEqual(expect.arrayContaining(["player.fame.set", "player.currency.normal.set", "player.currency.gold.set", "player.notify", "reward.deliver", "player.attribute.855.set"]));
|
||||
expect(manifest.gameClientBridge.operationTemplates.find((operation) => operation.key === "player.attribute.855.set")?.kind).toBe("sqlite-mutation");
|
||||
expect(fs.existsSync(path.join(pluginDir, "schemas/bridge/queries/SCUM_DB_CONTRACT.md"))).toBe(true);
|
||||
});
|
||||
|
||||
@@ -286,7 +275,7 @@ describe("plugin manifest validation", () => {
|
||||
expect(local?.capabilities).toContain("remote.run.rcon.command");
|
||||
expect(local?.transportKeys).toContain("scum-management");
|
||||
expect(manifest.runtimeProfiles?.transportProfiles).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ key: "scum-database", kind: "sqlite", capabilities: expect.arrayContaining(["remote.run.db.sqlite.query", "remote.run.protected.sql"]) }),
|
||||
expect.objectContaining({ key: "scum-database", kind: "sqlite", capabilities: expect.arrayContaining(["remote.run.db.sqlite.query"]) }),
|
||||
expect.objectContaining({ key: "scum-management", kind: "rcon", capabilities: ["remote.run.rcon.command"] }),
|
||||
expect.objectContaining({ key: "scum-program", kind: "program", capabilities: ["remote.run.program.command"] })
|
||||
]));
|
||||
@@ -461,14 +450,13 @@ describe("plugin manifest validation", () => {
|
||||
commands: Array<{
|
||||
type: string;
|
||||
permission: string;
|
||||
approvalLevel: string;
|
||||
payloadSchemaRef: string;
|
||||
resultSchemaRef?: string;
|
||||
timeoutSeconds: number;
|
||||
maxPayloadBytes: number;
|
||||
}>;
|
||||
snapshots: Array<{ type: string; schemaVersion: string; schemaRef: string }>;
|
||||
pages: Array<{ pageKey: string; commandTypes?: string[]; snapshotTypes?: string[]; queryTemplateKeys?: string[]; operationKeys?: string[] }>;
|
||||
pages: Array<{ pageKey: string; commandTypes?: string[]; snapshotTypes?: string[]; queryTemplateKeys?: string[] }>;
|
||||
};
|
||||
pages: Array<{ key: string; permissions?: string[] }>;
|
||||
fileWorkspace?: {
|
||||
@@ -511,13 +499,6 @@ describe("plugin manifest validation", () => {
|
||||
expect(manifest.gameClientBridge.snapshots.map((snapshot) => snapshot.type)).toEqual(expect.arrayContaining(["companion.health", "online.sessions", "players", "squads", "vehicles", "flags"]));
|
||||
expect(manifest.gameClientBridge.pages.map((page) => page.pageKey)).toEqual(expect.arrayContaining(["players", "squads", "live-map", "gifts", "workflows"]));
|
||||
expect(manifest.gameClientBridge.pages.map((page) => page.pageKey)).not.toContain("files-config");
|
||||
expect(manifest.gameClientBridge.pages.find((page) => page.pageKey === "players")?.operationKeys).toEqual(expect.arrayContaining([
|
||||
"player.fame.set",
|
||||
"player.currency.normal.set",
|
||||
"player.currency.gold.set",
|
||||
"player.notify",
|
||||
"player.attribute.855.set"
|
||||
]));
|
||||
expect(manifest.gameClientBridge.pages.find((page) => page.pageKey === "workflows")?.queryTemplateKeys).toEqual(expect.arrayContaining(["scum.player.profile", "scum.squads", "scum.vehicles", "scum.flags", "scum.positions"]));
|
||||
expect(manifest.pages.map((page) => page.key)).toEqual(expect.arrayContaining(["players", "squads", "live-map", "gifts", "workflows"]));
|
||||
expect(manifest.pages.map((page) => page.key)).not.toContain("files-config");
|
||||
@@ -534,7 +515,6 @@ describe("plugin manifest validation", () => {
|
||||
commands: Array<{
|
||||
type: string;
|
||||
permission: string;
|
||||
approvalLevel: string;
|
||||
payloadSchemaRef: string;
|
||||
resultSchemaRef?: string;
|
||||
timeoutSeconds: number;
|
||||
@@ -543,15 +523,15 @@ describe("plugin manifest validation", () => {
|
||||
};
|
||||
};
|
||||
const expected = {
|
||||
"companion.diagnostics": { permission: "server.game-client.read", approvalLevel: "none" },
|
||||
"player.lookup": { permission: "server.game-client.read", approvalLevel: "none" },
|
||||
"reward.deliver": { permission: "server.game-client.command", approvalLevel: "none" },
|
||||
"player.notify": { permission: "server.game-client.command", approvalLevel: "none" },
|
||||
"vehicle.spawn": { permission: "server.game-client.command", approvalLevel: "none" },
|
||||
"event.start": { permission: "server.game-client.command", approvalLevel: "none" },
|
||||
"restart.prepare": { permission: "server.game-client.maintenance", approvalLevel: "none" },
|
||||
"maintenance.prepare": { permission: "server.game-client.maintenance", approvalLevel: "none" },
|
||||
"game-state.patch": { permission: "server.game-client.maintenance", approvalLevel: "none" }
|
||||
"companion.diagnostics": { permission: "server.game-client.read" },
|
||||
"player.lookup": { permission: "server.game-client.read" },
|
||||
"reward.deliver": { permission: "server.game-client.command" },
|
||||
"player.notify": { permission: "server.game-client.command" },
|
||||
"vehicle.spawn": { permission: "server.game-client.command" },
|
||||
"event.start": { permission: "server.game-client.command" },
|
||||
"restart.prepare": { permission: "server.game-client.maintenance" },
|
||||
"maintenance.prepare": { permission: "server.game-client.maintenance" },
|
||||
"game-state.patch": { permission: "server.game-client.maintenance" }
|
||||
} as const;
|
||||
|
||||
expect(manifest.gameClientBridge.commands.map((command) => command.type)).toEqual(expect.arrayContaining(Object.keys(expected)));
|
||||
@@ -561,7 +541,6 @@ describe("plugin manifest validation", () => {
|
||||
continue;
|
||||
}
|
||||
expect(command.permission).toBe(policy.permission);
|
||||
expect(command.approvalLevel).toBe(policy.approvalLevel);
|
||||
expect(command.timeoutSeconds).toBeGreaterThan(0);
|
||||
expect(command.timeoutSeconds).toBeLessThanOrEqual(3600);
|
||||
expect(command.maxPayloadBytes).toBeGreaterThan(0);
|
||||
@@ -742,37 +721,6 @@ describe("plugin manifest validation", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("declares typed SCUM RCON operations without arbitrary command inputs", () => {
|
||||
const pluginDir = path.join(pluginsRoot, "examples/scum-server-plugin");
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(pluginDir, "manifest.json"), "utf8")) as {
|
||||
gameClientBridge: {
|
||||
operationTemplates: Array<{ key: string; kind: string; permission: string; approvalLevel: string; payloadSchemaRef: string; resultSchemaRef?: string; confirmationSchemaRef?: string; safety?: Record<string, boolean> }>;
|
||||
pages: Array<{ pageKey: string; operationKeys?: string[] }>;
|
||||
};
|
||||
pages: Array<{ key: string; permissions?: string[] }>;
|
||||
};
|
||||
const expectedKeys = ["player.fame.set", "player.currency.normal.set", "player.currency.gold.set", "player.notify", "reward.deliver"];
|
||||
const operationsByKey = new Map(manifest.gameClientBridge.operationTemplates.map((operation) => [operation.key, operation]));
|
||||
expect([...operationsByKey.keys()]).toEqual(expect.arrayContaining(expectedKeys));
|
||||
for (const key of expectedKeys) {
|
||||
const operation = operationsByKey.get(key)!;
|
||||
expect(operation.kind).toBe("rcon");
|
||||
expect(operation.permission).toBe("server.game-client.command");
|
||||
expect(operation.approvalLevel).toBe("none");
|
||||
const payload = JSON.parse(fs.readFileSync(path.join(pluginDir, operation.payloadSchemaRef), "utf8"));
|
||||
const result = JSON.parse(fs.readFileSync(path.join(pluginDir, operation.resultSchemaRef!), "utf8"));
|
||||
expect(payload).toMatchObject({ type: "object", additionalProperties: false });
|
||||
expect(result).toMatchObject({ type: "object", additionalProperties: false });
|
||||
expect(JSON.stringify(payload).toLowerCase()).not.toMatch(/rcon|commandtext|requesttext|sql|dsn|hostpath/);
|
||||
}
|
||||
const playersPage = manifest.gameClientBridge.pages.find((page) => page.pageKey === "players");
|
||||
const giftsPage = manifest.gameClientBridge.pages.find((page) => page.pageKey === "gifts");
|
||||
expect(playersPage?.operationKeys).toEqual(expect.arrayContaining(["player.fame.set", "player.currency.normal.set", "player.currency.gold.set", "player.notify"]));
|
||||
expect(giftsPage?.operationKeys).toEqual(expect.arrayContaining(["reward.deliver", "player.notify"]));
|
||||
expect(manifest.pages.find((page) => page.key === "players")?.permissions).toContain("server.game-client.command");
|
||||
expect(manifest.pages.find((page) => page.key === "gifts")?.permissions).toContain("server.game-client.command");
|
||||
});
|
||||
|
||||
it("packages SCUM v57 config, UTF-16LE logs, and gift metadata inside the plugin", () => {
|
||||
const pluginDir = path.join(pluginsRoot, "examples/scum-server-plugin");
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(pluginDir, "manifest.json"), "utf8")) as {
|
||||
@@ -912,7 +860,7 @@ describe("plugin manifest validation", () => {
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(pluginsRoot, "examples/dev-game-plugin/manifest.json"), "utf8"));
|
||||
manifest.permissions = [...manifest.permissions, "server.game-client.command", "server.game-client.read"];
|
||||
manifest.gameClientBridge = {
|
||||
commands: [{ type: "diagnostic.ping", title: "Diagnostic ping", permission: "server.game-client.command", approvalLevel: "operator", payloadSchemaRef: "schemas/bridge/diagnostic-ping.schema.json", resultSchemaRef: "schemas/bridge/diagnostic-ping-result.schema.json", timeoutSeconds: 60, maxPayloadBytes: 4096 }],
|
||||
commands: [{ type: "diagnostic.ping", title: "Diagnostic ping", permission: "server.game-client.command", payloadSchemaRef: "schemas/bridge/diagnostic-ping.schema.json", resultSchemaRef: "schemas/bridge/diagnostic-ping-result.schema.json", timeoutSeconds: 60, maxPayloadBytes: 4096 }],
|
||||
snapshots: [{ type: "players", schemaVersion: "1", schemaRef: "schemas/bridge/players.schema.json", keepForSeconds: 3600, maxRecords: 100 }],
|
||||
logProjections: [{ key: "player.login", streamKeys: ["process.stdout"], steps: [{ pattern: "Player \\\"(?<name>[^\\\"]+)\\\" reported as player (?<slot>\\\\d+)" }, { pattern: "Player (?<slot>\\\\d+) SteamID: (?<steamId>\\\\d+)" }], correlationFields: ["slot"], maxInterveningLines: 16, target: { collection: "users", upsertKeys: ["steamId"], captureMappings: { steamId: "steamId", name: "name" }, observedAtField: "lastLoginAt" }, presence: { timestampField: "lastLoginAt", activeWindowSeconds: 600 } }],
|
||||
commandRetentionSeconds: 86400,
|
||||
@@ -921,7 +869,7 @@ describe("plugin manifest validation", () => {
|
||||
};
|
||||
const validate = new Ajv2020({ allErrors: true }).compile(schema);
|
||||
expect(validate(manifest), JSON.stringify(validate.errors)).toBe(true);
|
||||
manifest.gameClientBridge.commands[0].approvalLevel = "automatic";
|
||||
manifest.gameClientBridge.commands[0].unexpectedControlGate = "automatic";
|
||||
expect(validate(manifest)).toBe(false);
|
||||
});
|
||||
|
||||
@@ -946,7 +894,7 @@ describe("plugin manifest validation", () => {
|
||||
permissions: ["server.game-client.command"],
|
||||
runtimeProfiles: { clientManagers: [{ key: "scum-client", health: { requiredCapabilities: ["game-client.bridge"] } }] },
|
||||
gameClientBridge: {
|
||||
commands: [{ type: "diagnostic.ping", approvalLevel: "none", payloadSchemaRef: "schemas/bridge/diagnostic-ping.schema.json" }],
|
||||
commands: [{ type: "diagnostic.ping", payloadSchemaRef: "schemas/bridge/diagnostic-ping.schema.json" }],
|
||||
snapshots: [],
|
||||
logProjections: [projection]
|
||||
}
|
||||
@@ -1047,35 +995,6 @@ describe("plugin manifest validation", () => {
|
||||
expect(actionErrors.some((error) => error.includes("page must declare remote.access.request"))).toBe(true);
|
||||
});
|
||||
|
||||
it("validates typed operation templates and page operation bindings", () => {
|
||||
expect(validateTemporaryBridgeManifest()).toEqual([]);
|
||||
|
||||
const unsafeKeyErrors = validateTemporaryBridgeManifest((manifest) => {
|
||||
manifest.gameClientBridge.operationTemplates![0].key = "raw.sql.execute";
|
||||
});
|
||||
expect(unsafeKeyErrors.some((error) => error.includes("operationTemplates") && error.includes("arbitrary SQL"))).toBe(true);
|
||||
|
||||
const approvalErrors = validateTemporaryBridgeManifest((manifest) => {
|
||||
manifest.gameClientBridge.operationTemplates![0].approvalLevel = "automatic";
|
||||
});
|
||||
expect(approvalErrors.some((error) => error.includes("approvalLevel") && error.includes("none, operator, or platform-admin"))).toBe(true);
|
||||
|
||||
const rconTransportErrors = validateTemporaryBridgeManifest((manifest) => {
|
||||
Object.assign(manifest.gameClientBridge.operationTemplates![0], { transportKey: "sqlite-db", targetKey: "db/sqlite" });
|
||||
});
|
||||
expect(rconTransportErrors.some((error) => error.includes("rcon operations require"))).toBe(true);
|
||||
|
||||
const mutationSafetyErrors = validateTemporaryBridgeManifest((manifest) => {
|
||||
manifest.gameClientBridge.operationTemplates![1].safety = { requiresConfirmation: true };
|
||||
});
|
||||
expect(mutationSafetyErrors.some((error) => error.includes("sqlite-mutation operations require before value"))).toBe(true);
|
||||
|
||||
const pageErrors = validateTemporaryBridgeManifest((manifest) => {
|
||||
manifest.gameClientBridge.pages[0].operationKeys = ["missing.operation"];
|
||||
});
|
||||
expect(pageErrors.some((error) => error.includes("undeclared operation template missing.operation"))).toBe(true);
|
||||
});
|
||||
|
||||
it.each(["sqlText", "dsn", "hostPath", "shellCommand", "socketAddress", "accessToken", "credential"])("rejects unsafe query parameter schema field %s", (fieldName) => {
|
||||
const errors = validateTemporaryBridgeManifest((_manifest, fixtureDir) => {
|
||||
writeFixtureJSON(fixtureDir, "schemas/bridge/player-by-id.parameters.schema.json", bridgeObjectSchema({ [fieldName]: { type: "string", minLength: 1, maxLength: 120 } }, [fieldName]));
|
||||
@@ -1097,13 +1016,6 @@ describe("plugin manifest validation", () => {
|
||||
expect(errors.some((error) => error.includes("resultSchemaRef") && error.includes("missing bridge schema file"))).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects missing bridge command approval metadata end to end", () => {
|
||||
const errors = validateTemporaryBridgeManifest((manifest) => {
|
||||
delete manifest.gameClientBridge.commands[0].approvalLevel;
|
||||
});
|
||||
expect(errors.some((error) => error.includes("approvalLevel") && (error.includes("required") || error.includes("approval metadata")))).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects unsafe executor capabilities end to end", () => {
|
||||
const errors = validateTemporaryBridgeManifest((manifest) => {
|
||||
manifest.capabilities = [...manifest.capabilities, "shell.exec"];
|
||||
@@ -1223,28 +1135,6 @@ describe("plugin SDK", () => {
|
||||
expect(declaration).toMatchObject({ key: "scum.player.login", correlationFields: ["slot"] });
|
||||
});
|
||||
|
||||
it("types plugin operation template declarations", () => {
|
||||
const declaration: GameClientBridgeOperationTemplateDeclaration = {
|
||||
key: "player.attribute.855.set",
|
||||
title: "Set player attribute 855",
|
||||
permission: "server.game-client.maintenance",
|
||||
approvalLevel: "platform-admin",
|
||||
kind: "sqlite-mutation",
|
||||
transportKey: "scum-mutation-db",
|
||||
targetKey: "scum-mutation-db",
|
||||
payloadSchemaRef: "schemas/bridge/operations/player-attribute-855-set.payload.schema.json",
|
||||
resultSchemaRef: "schemas/bridge/operations/player-attribute-855-set.result.schema.json",
|
||||
confirmationSchemaRef: "schemas/bridge/operations/player-attribute-855-set.confirmation.schema.json",
|
||||
timeoutSeconds: 120,
|
||||
maxPayloadBytes: 4096,
|
||||
maxRowsAffected: 1,
|
||||
mutation: { fieldKey: "855", tableKey: "prisoner", identityKey: "user_profile_id", valueKey: "value", confirmationQueryKey: "player.lookup", allowedValueType: "integer", minValue: 0, maxValue: 100000 },
|
||||
safety: { requiresApproval: true, requiresOfflinePlayer: true, requiresBeforeValue: true, requiresConfirmation: true, backupRequired: true }
|
||||
};
|
||||
expect(declaration).toMatchObject({ kind: "sqlite-mutation", approvalLevel: "platform-admin", maxRowsAffected: 1 });
|
||||
expect(JSON.stringify(declaration).toLowerCase()).not.toMatch(/sqltext|dsn|hostpath|socket|credential|password/);
|
||||
});
|
||||
|
||||
it("builds safe game-client bridge requests without component transport material", () => {
|
||||
const request = createGameClientBridgeQueueRequest({
|
||||
profileKey: "scum-client",
|
||||
@@ -1555,7 +1445,7 @@ describe("plugin SDK", () => {
|
||||
server: { type: "runtime", displayName: "Runtime Fixture", createFormSchema: "schemas/create-form.schema.json" },
|
||||
capabilities: ["process.start", "process.stop", "logs.read"],
|
||||
permissions: ["server.read", "server.lifecycle", "server.logs.read"],
|
||||
productionLifecycle: { operations: ["install", "enable", "disable", "upgrade", "rollback", "retire", "dependency-check"], dependencyPolicy: "required", approvalRequired: ["disable", "rollback", "retire"] },
|
||||
productionLifecycle: { operations: ["install", "enable", "disable", "upgrade", "rollback", "retire", "dependency-check"], dependencyPolicy: "required" },
|
||||
runtimeProfiles: {
|
||||
discovery: [{ key: "java", kind: "command.version", targetKey: "java", required: true }],
|
||||
dependencyProbes: [{ key: "java-21", kind: "java.version", targetKey: "java", minimumVersion: "21" }],
|
||||
@@ -1635,10 +1525,9 @@ describe("plugin SDK", () => {
|
||||
serverInstanceId: "server-1",
|
||||
permissions: ["server.lifecycle"]
|
||||
};
|
||||
const request = createProductionPluginLifecycleRequest({ requestId: "plugin-upgrade-1", context, operation: "upgrade", targetVersion: "1.2.0", idempotencyKey: "plugin-upgrade-v1" });
|
||||
expect(request).toMatchObject({ action: "plugin-lifecycle.request", payload: { operation: "upgrade", targetVersion: "1.2.0", confirmed: "false" } });
|
||||
expect(JSON.stringify(request)).not.toMatch(/apiKey|providerBaseUrl|runSocket|runEndpoint|hostPath|credential/i);
|
||||
expect(() => createProductionPluginLifecycleRequest({ requestId: "plugin-retire-1", context, operation: "retire", idempotencyKey: "plugin-retire-v1" })).toThrow(/confirmation/);
|
||||
});
|
||||
const request = createProductionPluginLifecycleRequest({ requestId: "plugin-upgrade-1", context, operation: "upgrade", targetVersion: "1.2.0", idempotencyKey: "plugin-upgrade-v1" });
|
||||
expect(request).toMatchObject({ action: "plugin-lifecycle.request", payload: { operation: "upgrade", targetVersion: "1.2.0", idempotencyKey: "plugin-upgrade-v1" } });
|
||||
expect(JSON.stringify(request)).not.toMatch(/apiKey|providerBaseUrl|runSocket|runEndpoint|hostPath|credential/i);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user