feat(scum): add live data asset manifest contracts
This commit is contained in:
@@ -221,6 +221,46 @@ describe("plugin manifest validation", () => {
|
||||
expect(errors.some((error) => error.includes("enabled gates require immutable asset digests"))).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts safe SCUM live-data asset declarations without enabling unproven capabilities", () => {
|
||||
const digest = (char: string) => `sha256:${char.repeat(64)}`;
|
||||
const fingerprint = `sha256:${"f".repeat(64)}`;
|
||||
const assetPaths = [
|
||||
"assets/scum-live/login-parser.json",
|
||||
"assets/scum-live/queries/players-read.json",
|
||||
"assets/scum-live/rcon/gift-grant.json",
|
||||
"assets/scum-live/mutations/profile-xml-patch.json",
|
||||
"assets/scum-live/map/island.png",
|
||||
"assets/scum-live/map/transform.json",
|
||||
"assets/scum-live/gifts/catalog.json"
|
||||
];
|
||||
const errors = validateTemporaryScumCompanionManifest((manifest, fixtureDir) => {
|
||||
manifest.assetFiles = [...manifest.assetFiles, ...assetPaths.map((assetPath) => ({ path: assetPath, mode: 384 }))];
|
||||
for (const assetPath of assetPaths) writeFixtureJSON(fixtureDir, assetPath, { packaged: true });
|
||||
manifest.scumLiveData = {
|
||||
...manifest.scumLiveData,
|
||||
logParsers: [{ key: "login-parser", adapterVersion: "scum-live-data-v1", assetPath: assetPaths[0], digest: digest("a"), parserVersion: "login-v1", sourceKey: "scum-login-events", eventType: "scum.login", eventSchemaRef: "schemas/scum-live/login-event.schema.json", maxLineBytes: 4096, cursorPolicy: "source-generation-sequence", privacy: { stripNetworkIdentifiers: true, logicalEventIdentity: "native-or-sanitized-fields" } }],
|
||||
sqliteQueries: [{ key: "players-read", adapterVersion: "scum-live-data-v1", assetPath: assetPaths[1], digest: digest("b"), capability: "players.read", requiredSchemaFingerprint: fingerprint, transportKey: "scum-database", targetKey: "scum-database", parameterSchemaRef: "schemas/scum-live/players-read.parameters.schema.json", resultSchemaRef: "schemas/scum-live/players-read.result.schema.json", maxRows: 100, timeoutMs: 5000, maxResultBytes: 65536 }],
|
||||
syncCadences: [{ capability: "players.read", intervalSeconds: 300, jitterPercent: 20, timeoutMs: 5000, maxConcurrentPerServer: 1 }],
|
||||
typedRconTemplates: [{ key: "gift-grant", adapterVersion: "scum-live-data-v1", assetPath: assetPaths[2], digest: digest("c"), capability: "gift-command.write", requiredSchemaFingerprint: fingerprint, transportKey: "scum-management", targetKey: "scum-management", permission: "server.game-client.command", payloadSchemaRef: "schemas/scum-live/gift-grant.payload.schema.json", resultSchemaRef: "schemas/scum-live/gift-grant.result.schema.json", confirmationSchemaRef: "schemas/scum-live/gift-grant.confirmation.schema.json", timeoutMs: 5000, maxPayloadBytes: 4096 }],
|
||||
guardedMutations: [{ key: "profile-xml-patch", adapterVersion: "scum-live-data-v1", assetPath: assetPaths[3], digest: digest("d"), capability: "profile-xml.write", requiredSchemaFingerprint: fingerprint, transportKey: "scum-database", targetKey: "scum-database", permission: "server.game-client.maintenance", payloadSchemaRef: "schemas/scum-live/profile-xml-patch.payload.schema.json", resultSchemaRef: "schemas/scum-live/profile-xml-patch.result.schema.json", confirmationSchemaRef: "schemas/scum-live/profile-xml-patch.confirmation.schema.json", timeoutMs: 10000, maxPayloadBytes: 8192, maxRowsAffected: 1, safety: { requiresExpectedChecksum: true, requiresBackupEvidence: true, requiresOfflineOrMaintenance: true, requiresReadAfterWrite: true } }],
|
||||
mapAssets: [{ key: "island-map", adapterVersion: "scum-live-data-v1", assetPath: assetPaths[4], digest: digest("e"), requiredSchemaFingerprint: fingerprint, metadataSchemaRef: "schemas/scum-live/map-metadata.schema.json", transformAssetPath: assetPaths[5], transformDigest: digest("1"), worldBounds: { minX: -100000, minY: -100000, maxX: 100000, maxY: 100000 }, image: { width: 4096, height: 4096 } }],
|
||||
giftCatalogs: [{ key: "starter-gifts", adapterVersion: "scum-live-data-v1", assetPath: assetPaths[6], digest: digest("2"), catalogVersion: "catalog-v1", itemSchemaRef: "schemas/scum-live/gift-item.schema.json", transportTemplateKeys: ["gift-grant"] }]
|
||||
};
|
||||
});
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it("rejects unsafe SCUM live-data asset declarations", () => {
|
||||
const errors = validateTemporaryScumCompanionManifest((manifest) => {
|
||||
manifest.scumLiveData.sqliteQueries = [{ key: "players-read", adapterVersion: "scum-live-data-v1", assetPath: "/srv/scum/SCUM.db", digest: "sha256:bad", capability: "players.read", requiredSchemaFingerprint: "sha256:bad", transportKey: "scum-database", targetKey: "scum-database", parameterSchemaRef: "schemas/scum-live/players-read.parameters.schema.json", resultSchemaRef: "schemas/scum-live/players-read.result.schema.json", maxRows: 1000, timeoutMs: 70000, maxResultBytes: 2097152, rawSql: "SELECT * FROM prisoner" }];
|
||||
manifest.scumLiveData.giftCatalogs = [{ key: "starter-gifts", adapterVersion: "scum-live-data-v1", assetPath: "assets/scum-live/gifts/catalog.json", digest: `sha256:${"2".repeat(64)}`, catalogVersion: "catalog-v1", itemSchemaRef: "schemas/scum-live/gift-item.schema.json", transportTemplateKeys: ["missing-template"] }];
|
||||
});
|
||||
expect(errors.some((error) => error.includes("assetPath") && error.includes("contained package-relative path"))).toBe(true);
|
||||
expect(errors.some((error) => error.includes("digest") && error.includes("sha256"))).toBe(true);
|
||||
expect(errors.some((error) => error.includes("must NOT have additional properties") || error.includes("additional properties"))).toBe(true);
|
||||
expect(errors.some((error) => error.includes("transportTemplateKeys") && error.includes("missing-template"))).toBe(true);
|
||||
});
|
||||
|
||||
it("declares SCUM install/update and start lifecycle through plugin assets", () => {
|
||||
const pluginDir = path.join(pluginsRoot, "examples/scum-server-plugin");
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(pluginDir, "manifest.json"), "utf8")) as any;
|
||||
|
||||
Reference in New Issue
Block a user