Complete platform management workflows

This commit is contained in:
npc0-hue
2026-07-14 16:39:37 +08:00
parent 7e05d0a4e7
commit 4f33f761a3
106 changed files with 11313 additions and 460 deletions
+26 -2
View File
@@ -10,6 +10,8 @@ A game management plugin defines how the platform creates and manages one type o
- Create-server form schema.
- Lifecycle action definitions.
- Required run capabilities.
- Optional remote access methods and remote run capabilities.
- Optional runtime profiles for discovery, lifecycle modes, dependency probes, install plans, log sources, transports, and client-manager builds.
- Optional plugin pages hosted by platform_web.
- AI/file/log permissions declared for platform authorization.
@@ -25,7 +27,29 @@ Implementation should use dedicated directories for:
Plugins must use the platform bridge and must not connect directly to run, log storage, artifact storage internals, or AI provider endpoints.
Manifest validation is the plugin-side installability gate. The shared manifest schema defines identity, version, server type/display metadata, create form schema reference, lifecycle action references, required run capabilities, scoped permissions, optional pages, tags, and AI purposes. `scripts/validate-manifest.ts` also scans manifest and create-form content for unsafe raw host path, raw credential, direct run, and raw AI/provider key requests.
Manifest validation is the plugin-side installability gate. The shared manifest schema defines identity, version, server type/display metadata, create form schema reference, lifecycle action references, required run capabilities, scoped permissions, remote access declarations, runtime profiles, optional pages, tags, and AI purposes. `scripts/validate-manifest.ts` also scans manifest and create-form content for unsafe raw host path, raw credential, direct run, and raw AI/provider key requests.
Remote access declarations describe whether a plugin can use `ftp`, `rsync`, or `run`, and which `remote.*` run capabilities are enabled for that game. Runtime profiles describe how run discovers servers, checks dependencies, tails live logs, backfills historical logs, resolves transports, and builds optional client managers. Plugin pages use platform-mediated bridge actions; they never receive FTP passwords, rsync endpoints, database DSNs, RCON credentials, run/client keys, run sockets, or host paths.
Runtime profiles are declarative contracts, not executable scripts. A profile can declare:
- discovery probes for logical targets such as Java, Steam app, service, file, or toolchain checks.
- lifecycle modes such as `local-process`, `hosted-ftp-rcon`, `ftp-only`, or `custom-client`.
- dependency probes and typed install plans for supported OS targets.
- log sources for stdout/stderr, file tailing, FTP polling, SQL cursors, or plugin-specific client-manager logs.
- transport profiles for declared file, FTP/rsync, SQL, RCON, and run-mediated operations.
- client-manager build profiles for games such as SCUM that need a separate companion executable.
Client-manager profiles declare repository URL, revision policy, supported target OS/architecture pairs, build system hints, config template keys, dependency hints, and produced artifact paths. Platform performs target validation, creates a build record, injects a distinct server/component key into the generated package config, redacts build logs, and publishes a downloadable artifact. The run key and client-manager key are separate singleton keys in platform storage; resetting either key revokes packages from older generations and requires regenerating that component.
Plugin pages may request these operations only through bridge helpers:
- `createRunDistributionRequest`: generate/download/reset/update run packages.
- `createDependencyActionRequest`: check or install declared dependency probes/plans.
- `createLogBackfillRequest`: request historical log cursors for declared sources.
- `createClientManagerRequest`: generate/download/reset declared client-manager packages.
Bridge envelopes carry operation names, profile keys, target platforms, artifact IDs, checkpoint refs, and idempotency keys only. The plugin SDK and manifest validation reject raw run keys, client-manager keys, FTP passwords, rsync endpoints, SQL DSNs, RCON passwords, direct run sockets, host paths, and arbitrary shell snippets.
Validated manifests are registered through the platform registry API rather than by plugin code importing platform internals. Platform stores registry metadata only and repeats safety validation before a plugin becomes installable.
@@ -46,4 +70,4 @@ npm run test
npm run validate:manifest
```
Current plugin behavior includes SDK bridge contracts, manifest schema validation, the `examples/dev-game-plugin` fixture, platform registry metadata registration, marketplace projections, hosted plugin-page bridge execution, and platform-mediated lifecycle job dispatch. Marketplace package acquisition, remote plugin hosting policies, and external package distribution remain future OpenSpec work.
Current plugin behavior includes SDK bridge contracts, manifest schema validation, the `examples/dev-game-plugin`, `examples/scum-server-plugin`, and `examples/minecraft-server-plugin` fixtures, platform registry metadata registration, marketplace projections, hosted plugin-page bridge execution, platform-mediated lifecycle job dispatch, declared remote access envelopes, runtime profile declarations, run distribution envelopes, typed dependency/log backfill requests, and SCUM-style client-manager build declarations. Marketplace package acquisition, private source credentials, public build-worker sandboxing, real FTP/rsync/database/RCON adapters beyond bounded envelopes, remote plugin hosting policies, and external package distribution remain future OpenSpec work.
@@ -0,0 +1,8 @@
{
"command": ["true"],
"env": {
"GAME_ID": "minecraft",
"SERVER_TEMPLATE": "minecraft-java"
},
"timeoutMs": 30000
}
@@ -0,0 +1,8 @@
{
"command": ["true"],
"env": {
"GAME_ID": "minecraft",
"SERVER_ACTION": "restart"
},
"timeoutMs": 30000
}
@@ -0,0 +1,8 @@
{
"command": ["true"],
"env": {
"GAME_ID": "minecraft",
"SERVER_ACTION": "start"
},
"timeoutMs": 30000
}
@@ -0,0 +1,8 @@
{
"command": ["true"],
"env": {
"GAME_ID": "minecraft",
"SERVER_ACTION": "status"
},
"timeoutMs": 30000
}
@@ -0,0 +1,8 @@
{
"command": ["true"],
"env": {
"GAME_ID": "minecraft",
"SERVER_ACTION": "stop"
},
"timeoutMs": 30000
}
@@ -0,0 +1,259 @@
{
"$schema": "../../manifests/game-plugin.manifest.schema.json",
"id": "game.minecraft",
"name": "Minecraft Server",
"description": "First-party Minecraft server management plugin for run-mediated files, logs, and RCON.",
"version": "0.1.0",
"kind": "game-plugin",
"tags": [
"minecraft",
"sandbox",
"rcon",
"run"
],
"server": {
"type": "minecraft",
"displayName": "Minecraft Java Server",
"supportedOS": [
"windows",
"linux",
"darwin"
],
"createFormSchema": "schemas/create-form.schema.json"
},
"capabilities": [
"process.install",
"process.start",
"process.stop",
"process.restart",
"process.status",
"files.list",
"files.read",
"files.write",
"files.patch",
"logs.read",
"remote.run.files.read",
"remote.run.files.write",
"remote.run.process.start",
"remote.run.process.stop",
"remote.run.logs.transfer",
"remote.run.rcon.command",
"artifacts.read",
"artifacts.write",
"ai.invoke"
],
"remoteAccess": {
"methods": [
"run"
],
"runCapabilities": [
"remote.run.files.read",
"remote.run.files.write",
"remote.run.process.start",
"remote.run.process.stop",
"remote.run.logs.transfer",
"remote.run.rcon.command"
],
"rcon": true,
"logTransfer": true
},
"bridge": {
"actions": [
"server.instances.read",
"jobs.dispatch",
"logs.query",
"artifacts.open",
"files.request",
"remote.access.request",
"ai.invoke",
"run.distribution.request",
"dependencies.request",
"logs.backfill.request"
]
},
"permissions": [
"server.create",
"server.read",
"server.lifecycle",
"server.files.read",
"server.files.write",
"server.logs.read",
"server.artifacts.read",
"server.artifacts.write",
"server.remote.access",
"ai.invoke",
"server.run.distribution",
"server.dependencies.manage"
],
"actions": {
"install": "actions/install.json",
"start": "actions/start.json",
"stop": "actions/stop.json",
"restart": "actions/restart.json",
"status": "actions/status.json"
},
"pages": [
{
"key": "overview",
"title": "MC 概览",
"path": "/overview",
"permissions": [
"server.read",
"server.lifecycle"
],
"bridgeActions": [
"server.instances.read",
"jobs.dispatch"
]
},
{
"key": "remote",
"title": "MC 远程",
"path": "/remote",
"permissions": [
"server.remote.access",
"server.files.read",
"server.files.write",
"server.logs.read"
],
"bridgeActions": [
"remote.access.request",
"files.request",
"logs.query"
]
},
{
"key": "rcon",
"title": "MC RCON",
"path": "/rcon",
"permissions": [
"server.remote.access"
],
"bridgeActions": [
"remote.access.request"
]
}
],
"ai": {
"purposes": [
"config.suggest",
"logs.diagnose"
]
},
"runtimeProfiles": {
"discovery": [
{
"key": "java-runtime",
"kind": "command.version",
"targetKey": "java",
"required": true,
"expected": "21"
},
{
"key": "server-jar",
"kind": "file.exists",
"targetKey": "server.jar",
"required": true
}
],
"lifecycleProfiles": [
{
"key": "run-local",
"mode": "local-process",
"capabilities": [
"process.install",
"process.start",
"process.stop",
"process.restart",
"process.status",
"remote.run.process.start",
"remote.run.process.stop"
],
"actionRefs": {
"install": "actions/install.json",
"start": "actions/start.json",
"stop": "actions/stop.json",
"restart": "actions/restart.json",
"status": "actions/status.json"
},
"transportKeys": [
"server-files",
"rcon"
],
"platforms": [
"windows",
"linux",
"darwin"
]
}
],
"dependencyProbes": [
{
"key": "java-21",
"kind": "java.version",
"targetKey": "java",
"required": true,
"minimumVersion": "21",
"platforms": [
"windows",
"linux",
"darwin"
]
}
],
"installPlans": [
{
"key": "install-java-linux",
"title": "Install Java runtime",
"platforms": [
"linux"
],
"steps": [
{
"type": "package",
"targetKey": "java",
"packageManager": "apt",
"packageName": "openjdk-21-jre"
}
]
}
],
"logSources": [
{
"key": "console",
"kind": "process.stdout",
"streamKey": "console",
"cursorKind": "sequence",
"retentionDays": 30
},
{
"key": "latest-log",
"kind": "file.tail",
"targetKey": "logs/latest",
"streamKey": "latest-log",
"cursorKind": "fingerprint",
"retentionDays": 30
}
],
"transportProfiles": [
{
"key": "server-files",
"kind": "file",
"targetKey": "server-root",
"capabilities": [
"remote.run.files.read",
"remote.run.files.write",
"remote.run.logs.transfer"
]
},
{
"key": "rcon",
"kind": "rcon",
"targetKey": "rcon",
"capabilities": [
"remote.run.rcon.command"
]
}
]
}
}
@@ -0,0 +1,24 @@
{
"fields": [
{
"key": "serverName",
"label": "服务器名称",
"type": "text",
"required": true
},
{
"key": "gamePort",
"label": "游戏端口",
"type": "port",
"required": true,
"default": 25565
},
{
"key": "rconPort",
"label": "RCON 端口",
"type": "port",
"required": true,
"default": 25575
}
]
}
+346 -11
View File
@@ -5,11 +5,19 @@
"description": "First-party local SCUM game server management plugin for platform-mediated lifecycle proof.",
"version": "0.1.0",
"kind": "game-plugin",
"tags": ["scum", "survival", "dedicated-server", "local-proof"],
"tags": [
"scum",
"survival",
"dedicated-server",
"local-proof"
],
"server": {
"type": "scum",
"displayName": "SCUM Dedicated Server",
"supportedOS": ["windows", "linux"],
"supportedOS": [
"windows",
"linux"
],
"createFormSchema": "schemas/create-form.schema.json"
},
"capabilities": [
@@ -22,10 +30,45 @@
"files.read",
"files.patch",
"logs.read",
"remote.ftp.read",
"remote.ftp.write",
"remote.rsync.read",
"remote.rsync.write",
"remote.run.files.read",
"remote.run.files.write",
"remote.run.process.start",
"remote.run.process.stop",
"remote.run.db.mysql.query",
"remote.run.db.sqlite.query",
"remote.run.logs.transfer",
"remote.run.rcon.command",
"artifacts.read",
"artifacts.write",
"ai.invoke"
],
"remoteAccess": {
"methods": [
"ftp",
"rsync",
"run"
],
"runCapabilities": [
"remote.run.files.read",
"remote.run.files.write",
"remote.run.process.start",
"remote.run.process.stop",
"remote.run.db.mysql.query",
"remote.run.db.sqlite.query",
"remote.run.logs.transfer",
"remote.run.rcon.command"
],
"databaseEngines": [
"mysql",
"sqlite"
],
"rcon": true,
"logTransfer": true
},
"bridge": {
"actions": [
"server.instances.read",
@@ -33,7 +76,12 @@
"logs.query",
"artifacts.open",
"files.request",
"ai.invoke"
"remote.access.request",
"ai.invoke",
"run.distribution.request",
"dependencies.request",
"logs.backfill.request",
"client-manager.request"
]
},
"permissions": [
@@ -45,7 +93,11 @@
"server.logs.read",
"server.artifacts.read",
"server.artifacts.write",
"ai.invoke"
"server.remote.access",
"ai.invoke",
"server.run.distribution",
"server.dependencies.manage",
"server.client-manager.manage"
],
"actions": {
"install": "actions/install.json",
@@ -59,25 +111,308 @@
"key": "overview",
"title": "SCUM 概览",
"path": "/overview",
"permissions": ["server.read", "server.lifecycle"],
"bridgeActions": ["server.instances.read", "jobs.dispatch"]
"permissions": [
"server.read",
"server.lifecycle"
],
"bridgeActions": [
"server.instances.read",
"jobs.dispatch"
]
},
{
"key": "config",
"title": "SCUM 配置",
"path": "/config",
"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",
"title": "SCUM 日志",
"path": "/logs",
"permissions": ["server.logs.read", "server.artifacts.read", "ai.invoke"],
"bridgeActions": ["logs.query", "artifacts.open", "ai.invoke"]
"permissions": [
"server.logs.read",
"server.artifacts.read",
"server.remote.access",
"ai.invoke"
],
"bridgeActions": [
"logs.query",
"artifacts.open",
"remote.access.request",
"ai.invoke"
]
},
{
"key": "remote",
"title": "SCUM 远程",
"path": "/remote",
"permissions": [
"server.remote.access",
"server.files.read",
"server.files.write",
"server.lifecycle"
],
"bridgeActions": [
"remote.access.request",
"files.request",
"jobs.dispatch"
]
}
],
"ai": {
"purposes": ["config.suggest", "logs.diagnose"]
"purposes": [
"config.suggest",
"logs.diagnose"
]
},
"runtimeProfiles": {
"discovery": [
{
"key": "steamcmd",
"kind": "command.version",
"targetKey": "steamcmd",
"required": true
},
{
"key": "scum-install",
"kind": "file.exists",
"targetKey": "server/install-root",
"required": true,
"platforms": [
"windows"
]
}
],
"lifecycleProfiles": [
{
"key": "run-local",
"mode": "local-process",
"capabilities": [
"process.install",
"process.start",
"process.stop",
"process.restart",
"process.status",
"remote.run.process.start",
"remote.run.process.stop"
],
"actionRefs": {
"install": "actions/install.json",
"start": "actions/start.json",
"stop": "actions/stop.json",
"restart": "actions/restart.json",
"status": "actions/status.json"
},
"transportKeys": [
"server-files",
"sqlite-db",
"mysql-db",
"rcon"
],
"platforms": [
"windows"
]
},
{
"key": "hosted-ftp",
"mode": "hosted-ftp-rcon",
"capabilities": [
"remote.ftp.read",
"remote.ftp.write",
"remote.run.logs.transfer",
"remote.run.rcon.command"
],
"transportKeys": [
"ftp",
"rcon"
],
"platforms": [
"windows",
"linux"
]
},
{
"key": "scum-client",
"mode": "custom-client",
"capabilities": [
"remote.run.rcon.command",
"remote.run.logs.transfer"
],
"transportKeys": [
"client-rcon"
],
"clientManagerRef": "scum-client-manager",
"platforms": [
"windows"
]
}
],
"dependencyProbes": [
{
"key": "steamcmd",
"kind": "command.version",
"targetKey": "steamcmd",
"required": true,
"platforms": [
"windows",
"linux"
]
},
{
"key": "sqlite",
"kind": "package.installed",
"targetKey": "sqlite-driver",
"required": false,
"platforms": [
"windows",
"linux"
]
}
],
"installPlans": [
{
"key": "install-steamcmd-linux",
"title": "Install SteamCMD",
"platforms": [
"linux"
],
"steps": [
{
"type": "package",
"targetKey": "steamcmd",
"packageManager": "apt",
"packageName": "steamcmd"
}
]
}
],
"logSources": [
{
"key": "chat-log",
"kind": "ftp.poll",
"targetKey": "logs/chat",
"streamKey": "chat",
"cursorKind": "ftp-listing",
"retentionDays": 90
},
{
"key": "server-log",
"kind": "file.tail",
"targetKey": "logs/server",
"streamKey": "server",
"cursorKind": "fingerprint",
"retentionDays": 90
},
{
"key": "client-manager",
"kind": "client-manager",
"targetKey": "scum-client-manager",
"streamKey": "client-manager",
"cursorKind": "sequence",
"retentionDays": 30
}
],
"transportProfiles": [
{
"key": "server-files",
"kind": "file",
"targetKey": "server-root",
"capabilities": [
"remote.run.files.read",
"remote.run.files.write"
]
},
{
"key": "ftp",
"kind": "ftp",
"targetKey": "ftp-root",
"capabilities": [
"remote.ftp.read",
"remote.ftp.write"
]
},
{
"key": "rsync",
"kind": "rsync",
"targetKey": "rsync-root",
"capabilities": [
"remote.rsync.read",
"remote.rsync.write"
]
},
{
"key": "sqlite-db",
"kind": "sqlite",
"targetKey": "db/sqlite",
"capabilities": [
"remote.run.db.sqlite.query"
]
},
{
"key": "mysql-db",
"kind": "mysql",
"targetKey": "db/mysql",
"capabilities": [
"remote.run.db.mysql.query"
]
},
{
"key": "rcon",
"kind": "rcon",
"targetKey": "rcon",
"capabilities": [
"remote.run.rcon.command"
]
},
{
"key": "client-rcon",
"kind": "rcon",
"targetKey": "client/rcon",
"capabilities": [
"remote.run.rcon.command"
]
}
],
"clientManagers": [
{
"key": "scum-client-manager",
"displayName": "SCUM Client Manager",
"repository": {
"url": "https://github.com/F88888/scum_client.git",
"revisionPolicy": "branch",
"branch": "main"
},
"supportedTargets": [
{
"os": "windows",
"arch": "amd64"
}
],
"build": {
"system": "go",
"workspaceRef": "scum_client",
"entryRef": "main.go"
},
"configTemplates": [
{
"key": "client-config",
"templateRef": "configs/client.template.json",
"outputRef": "config.json"
}
],
"outputArtifacts": [
"scum_client.exe"
]
}
]
}
}
@@ -52,6 +52,72 @@
"items": { "$ref": "#/$defs/pluginPermission" },
"uniqueItems": true
},
"remoteAccess": {
"type": "object",
"required": ["methods"],
"additionalProperties": false,
"properties": {
"methods": {
"type": "array",
"items": { "$ref": "#/$defs/remoteAccessMethod" },
"uniqueItems": true,
"minItems": 1
},
"runCapabilities": {
"type": "array",
"items": { "$ref": "#/$defs/runCapability" },
"uniqueItems": true
},
"databaseEngines": {
"type": "array",
"items": { "$ref": "#/$defs/remoteDatabaseEngine" },
"uniqueItems": true
},
"rcon": { "type": "boolean" },
"logTransfer": { "type": "boolean" }
}
},
"runtimeProfiles": {
"type": "object",
"additionalProperties": false,
"properties": {
"discovery": {
"type": "array",
"items": { "$ref": "#/$defs/runtimeDiscoveryProbe" },
"uniqueItems": true
},
"lifecycleProfiles": {
"type": "array",
"items": { "$ref": "#/$defs/runtimeLifecycleProfile" },
"uniqueItems": true
},
"dependencyProbes": {
"type": "array",
"items": { "$ref": "#/$defs/runtimeDependencyProbe" },
"uniqueItems": true
},
"installPlans": {
"type": "array",
"items": { "$ref": "#/$defs/runtimeInstallPlan" },
"uniqueItems": true
},
"logSources": {
"type": "array",
"items": { "$ref": "#/$defs/runtimeLogSource" },
"uniqueItems": true
},
"transportProfiles": {
"type": "array",
"items": { "$ref": "#/$defs/runtimeTransportProfile" },
"uniqueItems": true
},
"clientManagers": {
"type": "array",
"items": { "$ref": "#/$defs/runtimeClientManagerProfile" },
"uniqueItems": true
}
}
},
"actions": {
"type": "object",
"required": ["install", "start", "stop"],
@@ -104,6 +170,18 @@
"files.write",
"files.patch",
"logs.read",
"remote.ftp.read",
"remote.ftp.write",
"remote.rsync.read",
"remote.rsync.write",
"remote.run.files.read",
"remote.run.files.write",
"remote.run.process.start",
"remote.run.process.stop",
"remote.run.db.mysql.query",
"remote.run.db.sqlite.query",
"remote.run.logs.transfer",
"remote.run.rcon.command",
"artifacts.read",
"artifacts.write",
"ai.invoke"
@@ -119,6 +197,10 @@
"server.logs.read",
"server.artifacts.read",
"server.artifacts.write",
"server.remote.access",
"server.run.distribution",
"server.dependencies.manage",
"server.client-manager.manage",
"ai.invoke"
]
},
@@ -129,9 +211,191 @@
"logs.query",
"artifacts.open",
"files.request",
"remote.access.request",
"run.distribution.request",
"dependencies.request",
"logs.backfill.request",
"client-manager.request",
"ai.invoke"
]
},
"remoteAccessMethod": {
"enum": ["ftp", "rsync", "run"]
},
"remoteDatabaseEngine": {
"enum": ["mysql", "sqlite"]
},
"logicalKey": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9._/-]*$",
"maxLength": 120
},
"runtimePlatform": {
"enum": ["windows", "linux", "darwin"]
},
"runtimeArch": {
"enum": ["amd64", "arm64"]
},
"runtimeTarget": {
"type": "object",
"required": ["os", "arch"],
"additionalProperties": false,
"properties": {
"os": { "$ref": "#/$defs/runtimePlatform" },
"arch": { "$ref": "#/$defs/runtimeArch" }
}
},
"runtimeDiscoveryProbe": {
"type": "object",
"required": ["key", "kind", "targetKey"],
"additionalProperties": false,
"properties": {
"key": { "$ref": "#/$defs/logicalKey" },
"kind": { "enum": ["file.exists", "command.version", "service.status", "port.open", "steam.app", "docker.container"] },
"targetKey": { "$ref": "#/$defs/logicalKey" },
"required": { "type": "boolean" },
"expected": { "type": "string", "maxLength": 120 },
"platforms": { "type": "array", "items": { "$ref": "#/$defs/runtimePlatform" }, "uniqueItems": true }
}
},
"runtimeLifecycleProfile": {
"type": "object",
"required": ["key", "mode", "capabilities"],
"additionalProperties": false,
"properties": {
"key": { "$ref": "#/$defs/logicalKey" },
"mode": { "enum": ["local-process", "hosted-ftp-rcon", "ftp-only", "custom-client"] },
"capabilities": { "type": "array", "items": { "$ref": "#/$defs/runCapability" }, "uniqueItems": true, "minItems": 1 },
"actionRefs": {
"type": "object",
"additionalProperties": false,
"properties": {
"install": { "$ref": "#/$defs/relativeJsonRef" },
"start": { "$ref": "#/$defs/relativeJsonRef" },
"stop": { "$ref": "#/$defs/relativeJsonRef" },
"restart": { "$ref": "#/$defs/relativeJsonRef" },
"status": { "$ref": "#/$defs/relativeJsonRef" }
}
},
"transportKeys": { "type": "array", "items": { "$ref": "#/$defs/logicalKey" }, "uniqueItems": true },
"clientManagerRef": { "$ref": "#/$defs/logicalKey" },
"platforms": { "type": "array", "items": { "$ref": "#/$defs/runtimePlatform" }, "uniqueItems": true }
}
},
"runtimeDependencyProbe": {
"type": "object",
"required": ["key", "kind", "targetKey"],
"additionalProperties": false,
"properties": {
"key": { "$ref": "#/$defs/logicalKey" },
"kind": { "enum": ["command.version", "service.exists", "port.available", "steam.app", "java.version", "docker.available", "package.installed", "file.exists"] },
"targetKey": { "$ref": "#/$defs/logicalKey" },
"required": { "type": "boolean" },
"minimumVersion": { "type": "string", "maxLength": 80 },
"platforms": { "type": "array", "items": { "$ref": "#/$defs/runtimePlatform" }, "uniqueItems": true }
}
},
"runtimeInstallStep": {
"type": "object",
"required": ["type", "targetKey"],
"additionalProperties": false,
"properties": {
"type": { "enum": ["package", "verified-download", "steamcmd-app", "manual"] },
"targetKey": { "$ref": "#/$defs/logicalKey" },
"packageManager": { "enum": ["winget", "choco", "scoop", "apt", "yum", "dnf", "pacman", "zypper", "brew", "steamcmd", "manual"] },
"packageName": { "type": "string", "pattern": "^[a-zA-Z0-9_.:+@/-]+$", "maxLength": 120 },
"version": { "type": "string", "maxLength": 80 },
"downloadRef": { "type": "string", "pattern": "^https://[a-zA-Z0-9._~:/?#\\[\\]@!$&'()*+,;=%-]+$", "maxLength": 240 },
"checksum": { "type": "string", "pattern": "^sha256:[a-fA-F0-9]{64}$" }
}
},
"runtimeInstallPlan": {
"type": "object",
"required": ["key", "title", "steps"],
"additionalProperties": false,
"properties": {
"key": { "$ref": "#/$defs/logicalKey" },
"title": { "type": "string", "minLength": 1, "maxLength": 80 },
"platforms": { "type": "array", "items": { "$ref": "#/$defs/runtimePlatform" }, "uniqueItems": true },
"steps": { "type": "array", "items": { "$ref": "#/$defs/runtimeInstallStep" }, "minItems": 1 }
}
},
"runtimeLogSource": {
"type": "object",
"required": ["key", "kind", "streamKey"],
"additionalProperties": false,
"properties": {
"key": { "$ref": "#/$defs/logicalKey" },
"kind": { "enum": ["process.stdout", "process.stderr", "file.tail", "ftp.poll", "sql.query", "client-manager"] },
"targetKey": { "$ref": "#/$defs/logicalKey" },
"streamKey": { "$ref": "#/$defs/logicalKey" },
"cursorKind": { "enum": ["sequence", "offset", "fingerprint", "ftp-listing", "sql-cursor"] },
"retentionDays": { "type": "integer", "minimum": 1, "maximum": 365 }
}
},
"runtimeTransportProfile": {
"type": "object",
"required": ["key", "kind", "capabilities"],
"additionalProperties": false,
"properties": {
"key": { "$ref": "#/$defs/logicalKey" },
"kind": { "enum": ["file", "ftp", "rsync", "mysql", "sqlite", "rcon"] },
"targetKey": { "$ref": "#/$defs/logicalKey" },
"capabilities": { "type": "array", "items": { "$ref": "#/$defs/runCapability" }, "uniqueItems": true, "minItems": 1 }
}
},
"relativePathRef": {
"type": "string",
"pattern": "^(?!/)(?![A-Za-z]:)(?!.*://)(?!.*\\.\\.)[a-zA-Z0-9_./-]+$",
"maxLength": 160
},
"runtimeClientManagerProfile": {
"type": "object",
"required": ["key", "repository", "supportedTargets", "build", "outputArtifacts"],
"additionalProperties": false,
"properties": {
"key": { "$ref": "#/$defs/logicalKey" },
"displayName": { "type": "string", "minLength": 1, "maxLength": 80 },
"repository": {
"type": "object",
"required": ["url", "revisionPolicy"],
"additionalProperties": false,
"properties": {
"url": { "type": "string", "pattern": "^https://[a-zA-Z0-9._~:/?#\\[\\]@!$&'()*+,;=%-]+\\.git$", "maxLength": 240 },
"branch": { "type": "string", "pattern": "^[a-zA-Z0-9._/-]+$", "maxLength": 120 },
"tag": { "type": "string", "pattern": "^[a-zA-Z0-9._/-]+$", "maxLength": 120 },
"revision": { "type": "string", "pattern": "^[a-fA-F0-9]{7,64}$" },
"revisionPolicy": { "enum": ["pinned", "branch", "tag"] }
}
},
"supportedTargets": { "type": "array", "items": { "$ref": "#/$defs/runtimeTarget" }, "minItems": 1, "uniqueItems": true },
"build": {
"type": "object",
"required": ["system"],
"additionalProperties": false,
"properties": {
"system": { "enum": ["go", "npm", "cargo", "make"] },
"workspaceRef": { "$ref": "#/$defs/relativePathRef" },
"entryRef": { "$ref": "#/$defs/relativePathRef" }
}
},
"configTemplates": {
"type": "array",
"items": {
"type": "object",
"required": ["key", "templateRef", "outputRef"],
"additionalProperties": false,
"properties": {
"key": { "$ref": "#/$defs/logicalKey" },
"templateRef": { "$ref": "#/$defs/relativePathRef" },
"outputRef": { "$ref": "#/$defs/relativePathRef" }
}
},
"uniqueItems": true
},
"outputArtifacts": { "type": "array", "items": { "$ref": "#/$defs/relativePathRef" }, "minItems": 1, "uniqueItems": true }
}
},
"aiPurpose": {
"enum": [
"config.read",
+1 -1
View File
@@ -6,7 +6,7 @@
"scripts": {
"typecheck": "tsc -p tsconfig.json --noEmit",
"test": "vitest run",
"validate:manifest": "tsx scripts/validate-manifest.ts examples/dev-game-plugin/manifest.json && tsx scripts/validate-manifest.ts examples/scum-server-plugin/manifest.json"
"validate:manifest": "tsx scripts/validate-manifest.ts examples/dev-game-plugin/manifest.json && tsx scripts/validate-manifest.ts examples/scum-server-plugin/manifest.json && tsx scripts/validate-manifest.ts examples/minecraft-server-plugin/manifest.json"
},
"dependencies": {
"ajv": "8.18.0"
+22 -2
View File
@@ -24,6 +24,9 @@ function unsafeFieldReason(fieldName: string): string | undefined {
if (compact.includes("runcredential") || compact.includes("runsocket") || compact.includes("directrun")) {
return "direct run access field is not allowed";
}
if (compact.includes("password") || compact.includes("dsn") || compact.includes("rawkey") || compact.includes("secretkey") || compact.includes("credential")) {
return "raw credential field is not allowed";
}
if (compact.includes("hostpath") || compact.includes("rawpath")) {
return "raw host path field is not allowed";
}
@@ -47,7 +50,13 @@ function unsafeStringReasons(value: string): string[] {
lowered.includes("raw api key") ||
lowered.includes("raw credential") ||
lowered.includes("provider key") ||
lowered.includes("ai key")
lowered.includes("ai key") ||
lowered.includes("password=") ||
lowered.includes("rcon password") ||
lowered.includes("ftp password") ||
lowered.startsWith("mysql://") ||
lowered.startsWith("sqlite://") ||
lowered.includes("database dsn")
) {
reasons.push("raw credential or AI/provider key content is not allowed");
}
@@ -56,7 +65,9 @@ function unsafeStringReasons(value: string): string[] {
lowered.includes("run socket") ||
lowered.includes("run credential") ||
lowered.includes("run token") ||
lowered.includes("direct socket")
lowered.includes("direct socket") ||
lowered.startsWith("tcp://") ||
lowered.startsWith("unix://")
) {
reasons.push("direct run access request is not allowed");
}
@@ -70,6 +81,15 @@ function unsafeStringReasons(value: string): string[] {
) {
reasons.push("raw host path access is not allowed");
}
if (
lowered.includes("shell script") ||
lowered.includes("bash -c") ||
lowered.includes("powershell -") ||
lowered.includes("cmd.exe") ||
lowered.includes("curl |")
) {
reasons.push("arbitrary shell content is not allowed");
}
return reasons;
}
+10
View File
@@ -9,6 +9,11 @@ Plugins use the platform bridge for every privileged action.
- `logs.query`: query historical logs by server, stream, time range, cursor, or analysis window.
- `artifacts.open`: request platform-mediated artifact download references.
- `files.request`: request scoped file list/read/patch/replace operations through platform jobs.
- `remote.access.request`: request plugin-declared FTP, rsync, or run-mediated remote operations through platform jobs.
- `run.distribution.request`: request platform-mediated run package generation, download, key reset, or self-update orchestration.
- `dependencies.request`: request typed dependency checks or approved install plans declared by the plugin runtime profile.
- `logs.backfill.request`: request historical log backfill for a declared log source.
- `client-manager.request`: request generation, download, or key reset for a plugin-declared companion client manager.
- `ai.invoke`: request platform-mediated AI assistance.
- `theme.tokens`: read safe platform theme tokens.
@@ -22,6 +27,10 @@ AI requests use `createAIInvocationRequest` with an explicit purpose, prompt, sc
Artifact open requests use `createArtifactOpenRequest` with an artifact ID that belongs to the current server/job scope. Use `parseArtifactReference` to consume the bridge result. Parsed references contain platform-owned download URLs, filename, content type, size, checksum, expiry, range support, and chunk size; they do not contain bytes or raw storage adapter locations.
Remote access requests use `createRemoteAccessRequest` with a plugin-declared `remote.*` capability, logical target key, optional scoped `input://` or `artifact://` ref, and idempotency key. The SDK never accepts FTP passwords, rsync endpoints, database DSNs, RCON passwords, run sockets, or raw host paths in these envelopes.
Run distribution, dependency, log backfill, and client-manager requests use `createRunDistributionRequest`, `createDependencyActionRequest`, `createLogBackfillRequest`, and `createClientManagerRequest`. These helpers carry operation names, logical profile keys, target OS/architecture, artifact IDs, cursors, and idempotency keys only; raw run keys and client-manager keys are written only into generated packages by platform services.
## Forbidden Data
The bridge must not expose:
@@ -33,3 +42,4 @@ The bridge must not expose:
- storage backend endpoints.
- unrestricted artifact storage credentials.
- direct storage URLs or presigned backend URLs.
- FTP, rsync, database, or RCON credentials.
+293
View File
@@ -7,6 +7,10 @@ export type PluginPermission =
| "server.logs.read"
| "server.artifacts.read"
| "server.artifacts.write"
| "server.remote.access"
| "server.run.distribution"
| "server.dependencies.manage"
| "server.client-manager.manage"
| "ai.invoke";
export type RunCapability =
@@ -20,6 +24,18 @@ export type RunCapability =
| "files.write"
| "files.patch"
| "logs.read"
| "remote.ftp.read"
| "remote.ftp.write"
| "remote.rsync.read"
| "remote.rsync.write"
| "remote.run.files.read"
| "remote.run.files.write"
| "remote.run.process.start"
| "remote.run.process.stop"
| "remote.run.db.mysql.query"
| "remote.run.db.sqlite.query"
| "remote.run.logs.transfer"
| "remote.run.rcon.command"
| "artifacts.read"
| "artifacts.write"
| "ai.invoke";
@@ -32,6 +48,11 @@ export type PluginBridgeAction =
| "logs.query"
| "artifacts.open"
| "files.request"
| "remote.access.request"
| "run.distribution.request"
| "dependencies.request"
| "logs.backfill.request"
| "client-manager.request"
| "ai.invoke";
export type PluginBridgeRequestPayload = Record<string, unknown>;
@@ -105,6 +126,150 @@ export type PluginLifecycleDispatchPayload = Record<string, string> & {
idempotencyKey: string;
};
export type PluginRemoteAccessPayload = Record<string, string> & {
capability: Extract<RunCapability, `remote.${string}`>;
targetKey?: string;
inputRef?: string;
idempotencyKey: string;
};
export type PluginRunDistributionPayload = Record<string, string> & {
operation: "generate" | "download" | "reset-key" | "update";
targetOS?: RuntimePlatform;
targetArch?: RuntimeArch;
artifactId?: string;
idempotencyKey: string;
};
export type PluginDependencyActionPayload = Record<string, string> & {
operation: "check" | "install";
probeKey?: string;
planKey?: string;
idempotencyKey: string;
};
export type PluginLogBackfillPayload = Record<string, string> & {
sourceKey: string;
cursor?: string;
limit?: string;
idempotencyKey: string;
};
export type PluginClientManagerPayload = Record<string, string> & {
operation: "generate" | "download" | "reset-key";
profileKey: string;
targetOS?: RuntimePlatform;
targetArch?: RuntimeArch;
artifactId?: string;
idempotencyKey: string;
};
export type RemoteAccessMethod = "ftp" | "rsync" | "run";
export type RemoteDatabaseEngine = "mysql" | "sqlite";
export interface GamePluginRemoteAccess {
methods: RemoteAccessMethod[];
runCapabilities?: Array<Extract<RunCapability, `remote.${string}`>>;
databaseEngines?: RemoteDatabaseEngine[];
rcon?: boolean;
logTransfer?: boolean;
}
export type RuntimePlatform = "windows" | "linux" | "darwin";
export type RuntimeArch = "amd64" | "arm64";
export type RuntimeTarget = { os: RuntimePlatform; arch: RuntimeArch };
export interface RuntimeDiscoveryProbe {
key: string;
kind: "file.exists" | "command.version" | "service.status" | "port.open" | "steam.app" | "docker.container";
targetKey: string;
required?: boolean;
expected?: string;
platforms?: RuntimePlatform[];
}
export interface RuntimeLifecycleProfile {
key: string;
mode: "local-process" | "hosted-ftp-rcon" | "ftp-only" | "custom-client";
capabilities: RunCapability[];
actionRefs?: Partial<Record<PluginLifecycleAction, string>>;
transportKeys?: string[];
clientManagerRef?: string;
platforms?: RuntimePlatform[];
}
export interface RuntimeDependencyProbe {
key: string;
kind: "command.version" | "service.exists" | "port.available" | "steam.app" | "java.version" | "docker.available" | "package.installed" | "file.exists";
targetKey: string;
required?: boolean;
minimumVersion?: string;
platforms?: RuntimePlatform[];
}
export interface RuntimeInstallStep {
type: "package" | "verified-download" | "steamcmd-app" | "manual";
targetKey: string;
packageManager?: "winget" | "choco" | "scoop" | "apt" | "yum" | "dnf" | "pacman" | "zypper" | "brew" | "steamcmd" | "manual";
packageName?: string;
version?: string;
downloadRef?: string;
checksum?: `sha256:${string}`;
}
export interface RuntimeInstallPlan {
key: string;
title: string;
platforms?: RuntimePlatform[];
steps: RuntimeInstallStep[];
}
export interface RuntimeLogSource {
key: string;
kind: "process.stdout" | "process.stderr" | "file.tail" | "ftp.poll" | "sql.query" | "client-manager";
targetKey?: string;
streamKey: string;
cursorKind?: "sequence" | "offset" | "fingerprint" | "ftp-listing" | "sql-cursor";
retentionDays?: number;
}
export interface RuntimeTransportProfile {
key: string;
kind: "file" | "ftp" | "rsync" | "mysql" | "sqlite" | "rcon";
targetKey?: string;
capabilities: RunCapability[];
}
export interface RuntimeClientManagerProfile {
key: string;
displayName?: string;
repository: {
url: string;
revisionPolicy: "pinned" | "branch" | "tag";
branch?: string;
tag?: string;
revision?: string;
};
supportedTargets: RuntimeTarget[];
build: {
system: "go" | "npm" | "cargo" | "make";
workspaceRef?: string;
entryRef?: string;
};
configTemplates?: Array<{ key: string; templateRef: string; outputRef: string }>;
outputArtifacts: string[];
}
export interface GamePluginRuntimeProfiles {
discovery?: RuntimeDiscoveryProbe[];
lifecycleProfiles?: RuntimeLifecycleProfile[];
dependencyProbes?: RuntimeDependencyProbe[];
installPlans?: RuntimeInstallPlan[];
logSources?: RuntimeLogSource[];
transportProfiles?: RuntimeTransportProfile[];
clientManagers?: RuntimeClientManagerProfile[];
}
export interface PluginArtifactReference {
artifactId: string;
filename: string;
@@ -140,6 +305,11 @@ export const pluginBridgeActionPolicies: Record<PluginBridgeAction, PluginBridge
"logs.query": { permissions: ["server.logs.read"] },
"artifacts.open": { permissions: ["server.artifacts.read"] },
"files.request": { permissions: ["server.files.read"] },
"remote.access.request": { permissions: ["server.remote.access"] },
"run.distribution.request": { permissions: ["server.run.distribution"] },
"dependencies.request": { permissions: ["server.dependencies.manage"] },
"logs.backfill.request": { permissions: ["server.logs.read"] },
"client-manager.request": { permissions: ["server.client-manager.manage"] },
"ai.invoke": { permissions: ["ai.invoke"], aiPurposeRequired: true }
};
@@ -179,6 +349,8 @@ export interface GamePluginManifest {
bridge?: GamePluginBridge;
capabilities: RunCapability[];
permissions: PluginPermission[];
remoteAccess?: GamePluginRemoteAccess;
runtimeProfiles?: GamePluginRuntimeProfiles;
actions?: GamePluginActions;
pages?: GamePluginPage[];
ai?: {
@@ -280,6 +452,127 @@ export function createLifecycleDispatchRequest(input: {
});
}
export function createRemoteAccessRequest(input: {
requestId: string;
context: PluginBridgeContext;
capability: PluginRemoteAccessPayload["capability"];
targetKey?: string;
inputRef?: string;
idempotencyKey: string;
}): PluginBridgeExecutionRequest<PluginRemoteAccessPayload> {
return createBridgeExecutionRequest({
requestId: input.requestId,
context: input.context,
action: "remote.access.request",
payload: {
capability: input.capability,
targetKey: input.targetKey ?? "",
inputRef: input.inputRef ?? "",
idempotencyKey: input.idempotencyKey
}
});
}
export function createRunDistributionRequest(input: {
requestId: string;
context: PluginBridgeContext;
operation: PluginRunDistributionPayload["operation"];
targetOS?: RuntimePlatform;
targetArch?: RuntimeArch;
artifactId?: string;
idempotencyKey: string;
}): PluginBridgeExecutionRequest<PluginRunDistributionPayload> {
const payload: PluginRunDistributionPayload = {
operation: input.operation,
artifactId: input.artifactId ?? "",
idempotencyKey: input.idempotencyKey
};
if (input.targetOS) {
payload.targetOS = input.targetOS;
}
if (input.targetArch) {
payload.targetArch = input.targetArch;
}
return createBridgeExecutionRequest({
requestId: input.requestId,
context: input.context,
action: "run.distribution.request",
payload
});
}
export function createDependencyActionRequest(input: {
requestId: string;
context: PluginBridgeContext;
operation: PluginDependencyActionPayload["operation"];
probeKey?: string;
planKey?: string;
idempotencyKey: string;
}): PluginBridgeExecutionRequest<PluginDependencyActionPayload> {
return createBridgeExecutionRequest({
requestId: input.requestId,
context: input.context,
action: "dependencies.request",
payload: {
operation: input.operation,
probeKey: input.probeKey ?? "",
planKey: input.planKey ?? "",
idempotencyKey: input.idempotencyKey
}
});
}
export function createLogBackfillRequest(input: {
requestId: string;
context: PluginBridgeContext;
sourceKey: string;
cursor?: string;
limit?: number;
idempotencyKey: string;
}): PluginBridgeExecutionRequest<PluginLogBackfillPayload> {
return createBridgeExecutionRequest({
requestId: input.requestId,
context: input.context,
action: "logs.backfill.request",
payload: {
sourceKey: input.sourceKey,
cursor: input.cursor ?? "",
limit: typeof input.limit === "number" ? String(input.limit) : "",
idempotencyKey: input.idempotencyKey
}
});
}
export function createClientManagerRequest(input: {
requestId: string;
context: PluginBridgeContext;
operation: PluginClientManagerPayload["operation"];
profileKey: string;
targetOS?: RuntimePlatform;
targetArch?: RuntimeArch;
artifactId?: string;
idempotencyKey: string;
}): PluginBridgeExecutionRequest<PluginClientManagerPayload> {
const payload: PluginClientManagerPayload = {
operation: input.operation,
profileKey: input.profileKey,
artifactId: input.artifactId ?? "",
idempotencyKey: input.idempotencyKey
};
if (input.targetOS) {
payload.targetOS = input.targetOS;
}
if (input.targetArch) {
payload.targetArch = input.targetArch;
}
return createBridgeExecutionRequest({
requestId: input.requestId,
context: input.context,
action: "client-manager.request",
payload
});
}
export function parseArtifactReference(result: Record<string, string> | undefined): PluginArtifactReference | undefined {
if (!result) {
return undefined;
@@ -0,0 +1,38 @@
{
"$schema": "../../manifests/game-plugin.manifest.schema.json",
"id": "game.unsafe-runtime",
"name": "Unsafe Runtime Fixture",
"description": "Fixture with unsafe runtime profile values.",
"version": "0.1.0",
"kind": "game-plugin",
"server": {
"type": "unsafe-runtime",
"displayName": "Unsafe Runtime Fixture",
"createFormSchema": "create-form.valid.json"
},
"capabilities": ["process.start", "process.stop", "logs.read"],
"permissions": ["server.read", "server.lifecycle", "server.logs.read"],
"runtimeProfiles": {
"discovery": [
{"key": "leaky", "kind": "command.version", "targetKey": "java", "expected": "password=super-secret"}
],
"installPlans": [
{"key": "unsafe-install", "title": "bash -c installer", "steps": [{"type": "manual", "targetKey": "manual"}]}
],
"clientManagers": [
{
"key": "unsafe-client",
"repository": {"url": "https://github.com/F88888/scum_client.git", "revisionPolicy": "branch", "branch": "main"},
"supportedTargets": [{"os": "windows", "arch": "amd64"}],
"build": {"system": "go", "workspaceRef": "scum_client", "entryRef": "main.go"},
"configTemplates": [{"key": "bad", "templateRef": "/Users/tasia/client.json", "outputRef": "config.json"}],
"outputArtifacts": ["scum_client.exe"]
}
]
},
"actions": {
"install": "actions/install.json",
"start": "actions/start.json",
"stop": "actions/stop.json"
}
}
+105
View File
@@ -5,13 +5,19 @@ import {
canRequestBridgeAction,
createAIInvocationRequest,
createArtifactOpenRequest,
createClientManagerRequest,
createBridgeExecutionRequest,
createLifecycleDispatchRequest,
createBridgeRequest,
createDependencyActionRequest,
createLogBackfillRequest,
createRemoteAccessRequest,
createRunDistributionRequest,
hasPluginPermission,
parseArtifactReference,
parseBridgeExecutionResponse,
parseAIInvocationResponse,
type GamePluginManifest,
type PluginBridgeContext
} from "../sdk/index.js";
import { validateManifestFile } from "../scripts/validate-manifest.js";
@@ -25,6 +31,10 @@ describe("plugin manifest validation", () => {
expect(validateManifestFile("examples/scum-server-plugin/manifest.json")).toEqual([]);
});
it("accepts the Minecraft server plugin manifest", () => {
expect(validateManifestFile("examples/minecraft-server-plugin/manifest.json")).toEqual([]);
});
it("rejects a manifest with an invalid create form schema", () => {
const errors = validateManifestFile("tests/fixtures/invalid-create-form-manifest.json");
@@ -37,6 +47,14 @@ describe("plugin manifest validation", () => {
expect(errors.some((error) => error.includes("direct run access"))).toBe(true);
expect(errors.some((error) => error.includes("raw credential or AI/provider key"))).toBe(true);
});
it("rejects unsafe runtime profile values", () => {
const errors = validateManifestFile("tests/fixtures/unsafe-runtime-profile-manifest.json");
expect(errors.some((error) => error.includes("raw credential or AI/provider key"))).toBe(true);
expect(errors.some((error) => error.includes("raw host path"))).toBe(true);
expect(errors.some((error) => error.includes("arbitrary shell"))).toBe(true);
});
});
describe("plugin SDK", () => {
@@ -266,4 +284,91 @@ describe("plugin SDK", () => {
expect(JSON.stringify(request)).not.toContain("Bearer ");
expect(JSON.stringify(request)).not.toContain("sk-");
});
it("builds remote access request envelopes without direct transport secrets", () => {
const context: PluginBridgeContext = {
pluginId: "game.minecraft",
routeKey: "rcon",
serverInstanceId: "server-1",
permissions: ["server.remote.access"]
};
expect(canRequestBridgeAction(context, "remote.access.request")).toBe(true);
const request = createRemoteAccessRequest({
requestId: "remote-rcon-1",
context,
capability: "remote.run.rcon.command",
targetKey: "rcon/command",
inputRef: "input://server-1/rcon/command/1",
idempotencyKey: "idem-remote-rcon"
});
expect(request).toEqual({
requestId: "remote-rcon-1",
pluginId: "game.minecraft",
routeKey: "rcon",
serverInstanceId: "server-1",
action: "remote.access.request",
aiPurpose: undefined,
payload: {
capability: "remote.run.rcon.command",
targetKey: "rcon/command",
inputRef: "input://server-1/rcon/command/1",
idempotencyKey: "idem-remote-rcon"
}
});
expect(JSON.stringify(request)).not.toContain("tcp://");
expect(JSON.stringify(request)).not.toContain("password=");
expect(JSON.stringify(request)).not.toContain("/Users/");
});
it("types runtime profile declarations without raw credentials", () => {
const manifest: GamePluginManifest = {
id: "game.runtime",
name: "Runtime Fixture",
version: "0.1.0",
kind: "game-plugin",
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"],
runtimeProfiles: {
discovery: [{ key: "java", kind: "command.version", targetKey: "java", required: true }],
dependencyProbes: [{ key: "java-21", kind: "java.version", targetKey: "java", minimumVersion: "21" }],
logSources: [{ key: "console", kind: "process.stdout", streamKey: "console", cursorKind: "sequence" }],
transportProfiles: [{ key: "files", kind: "file", capabilities: ["files.read"] }]
}
};
expect(manifest.runtimeProfiles?.discovery?.[0].targetKey).toBe("java");
expect(JSON.stringify(manifest)).not.toContain("password=");
});
it("builds run distribution, dependency, log backfill, and client-manager envelopes", () => {
const context: PluginBridgeContext = {
pluginId: "game.scum",
routeKey: "remote",
serverInstanceId: "server-1",
permissions: ["server.run.distribution", "server.dependencies.manage", "server.logs.read", "server.client-manager.manage"]
};
expect(canRequestBridgeAction(context, "run.distribution.request")).toBe(true);
expect(createRunDistributionRequest({ requestId: "run-gen-1", context, operation: "generate", targetOS: "windows", targetArch: "amd64", idempotencyKey: "idem-run" })).toMatchObject({
action: "run.distribution.request",
payload: { operation: "generate", targetOS: "windows", targetArch: "amd64", idempotencyKey: "idem-run" }
});
expect(createDependencyActionRequest({ requestId: "dep-1", context, operation: "check", probeKey: "steamcmd", idempotencyKey: "idem-dep" })).toMatchObject({
action: "dependencies.request",
payload: { operation: "check", probeKey: "steamcmd" }
});
expect(createLogBackfillRequest({ requestId: "logs-1", context, sourceKey: "chat-log", limit: 500, idempotencyKey: "idem-logs" })).toMatchObject({
action: "logs.backfill.request",
payload: { sourceKey: "chat-log", limit: "500" }
});
expect(createClientManagerRequest({ requestId: "client-1", context, operation: "generate", profileKey: "scum-client-manager", targetOS: "windows", targetArch: "amd64", idempotencyKey: "idem-client" })).toMatchObject({
action: "client-manager.request",
payload: { operation: "generate", profileKey: "scum-client-manager" }
});
expect(JSON.stringify(createClientManagerRequest({ requestId: "client-2", context, operation: "reset-key", profileKey: "scum-client-manager", idempotencyKey: "idem-reset" }))).not.toContain("secret");
});
});