Align SCUM adapter release gates
This commit is contained in:
@@ -294,14 +294,31 @@ describe("plugin manifest validation", () => {
|
||||
expect(fs.existsSync(path.join(pluginDir, "schemas/bridge/queries/SCUM_DB_CONTRACT.md"))).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps SCUM database-backed live-data gates disabled until current-service evidence exists", () => {
|
||||
it("matches SCUM live-data gates to the current-service adapter matrix", () => {
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(pluginsRoot, "examples/scum-server-plugin/manifest.json"), "utf8")) as GamePluginManifest & { scumLiveData: SCUMLiveDataManifestDeclaration; remoteAccess: { runCapabilities: string[] } };
|
||||
expect(manifest.capabilities).toContain("remote.run.db.sqlite.probe");
|
||||
expect(manifest.remoteAccess.runCapabilities).toContain("remote.run.db.sqlite.probe");
|
||||
expect(manifest.scumLiveData.probe).toMatchObject({ capability: "remote.run.db.sqlite.probe", targetKey: "scum-database" });
|
||||
expect(manifest.runtimeProfiles?.dataTargets?.find((target) => target.key === "scum-database")).toMatchObject({ kind: "sqlite.snapshot", transportKey: "scum-database", sourceRootKey: "server-root", sourcePath: "SCUM/Saved/SaveFiles/SCUM.db", workspaceKey: "databases/scum-database", refreshPolicy: "on-demand-snapshot" });
|
||||
expect(manifest.scumLiveData.capabilityGates.map((gate) => gate.capability)).toEqual(expect.arrayContaining(["players.read", "squads.read", "vehicles.read", "flags.read", "positions.read", "profile-xml.write", "economy-command.write", "gift-command.write"]));
|
||||
expect(manifest.scumLiveData.capabilityGates.every((gate) => gate.gate === "disabled" && gate.evidenceStatus === "missing")).toBe(true);
|
||||
const gates = new Map(manifest.scumLiveData.capabilityGates.map((gate) => [gate.capability, gate]));
|
||||
expect(gates.get("schema-probe")).toMatchObject({ gate: "enabled", adapterVersion: "scum-live-data-v1", requiredSchemaFingerprint: currentSCUMSchemaFingerprint, evidenceStatus: "compatible" });
|
||||
|
||||
const queryDigestsByCapability = new Map<string, string[]>();
|
||||
for (const query of manifest.scumLiveData.sqliteQueries ?? []) {
|
||||
const digests = queryDigestsByCapability.get(query.capability) ?? [];
|
||||
digests.push(query.digest);
|
||||
queryDigestsByCapability.set(query.capability, digests);
|
||||
}
|
||||
const mapAsset = manifest.scumLiveData.mapAssets?.[0];
|
||||
if (mapAsset) queryDigestsByCapability.set("positions.read", [...(queryDigestsByCapability.get("positions.read") ?? []), mapAsset.digest, mapAsset.transformDigest]);
|
||||
|
||||
for (const [capability, digests] of queryDigestsByCapability) {
|
||||
const gate = gates.get(capability as any);
|
||||
expect(gate).toMatchObject({ gate: "disabled", adapterVersion: "scum-live-data-v1", requiredSchemaFingerprint: currentSCUMSchemaFingerprint, evidenceStatus: "missing" });
|
||||
for (const digest of digests) expect(gate?.requiredAssetDigests).toContain(digest);
|
||||
}
|
||||
expect([...gates.keys()]).toEqual(expect.arrayContaining(["players.read", "player-details.read", "squads.read", "squad-members.read", "vehicles.read", "flags.read", "positions.read", "profile-xml.write", "economy-command.write", "gift-command.write"]));
|
||||
for (const capability of ["profile-xml.write", "economy-command.write", "gift-command.write"] as const) expect(gates.get(capability)).toMatchObject({ gate: "disabled", adapterVersion: "scum-live-data-v1", evidenceStatus: "missing" });
|
||||
expect(JSON.stringify(manifest.scumLiveData).toLowerCase()).not.toMatch(/select\s+.+from|sqlite:\/\/|mysql:\/\/|password|credential|socket|hostpath/);
|
||||
});
|
||||
|
||||
@@ -483,7 +500,7 @@ describe("plugin manifest validation", () => {
|
||||
|
||||
it("rejects enabling SCUM live-data gates without compatible evidence and immutable digests", () => {
|
||||
const errors = validateTemporaryScumCompanionManifest((manifest) => {
|
||||
manifest.scumLiveData.capabilityGates[1] = { ...manifest.scumLiveData.capabilityGates[1], gate: "enabled", evidenceStatus: "missing" };
|
||||
manifest.scumLiveData.capabilityGates[1] = { capability: "players.read", gate: "enabled", adapterVersion: "scum-live-data-v1", evidenceStatus: "missing", safeReason: "waiting for current service evidence" };
|
||||
});
|
||||
expect(errors.some((error) => error.includes("enabled gates require compatible evidence"))).toBe(true);
|
||||
expect(errors.some((error) => error.includes("enabled gates require a schema fingerprint"))).toBe(true);
|
||||
@@ -503,21 +520,33 @@ describe("plugin manifest validation", () => {
|
||||
];
|
||||
const errors = validateTemporaryScumCompanionManifest((manifest, fixtureDir) => {
|
||||
manifest.assetFiles = [...manifest.assetFiles, ...assetPaths.map((assetPath) => ({ path: assetPath, mode: 384 }))];
|
||||
for (const assetPath of [assetPaths[0], assetPaths[1], assetPaths[2], assetPaths[3], assetPaths[6]]) writeFixtureJSON(fixtureDir, assetPath, { packaged: true });
|
||||
for (const assetPath of [assetPaths[0], assetPaths[1], assetPaths[2], assetPaths[3], assetPaths[6]]) writeFixtureJSON(fixtureDir, assetPath, { packaged: true, assetPath });
|
||||
const mapBounds = { minX: -100000, minY: -100000, maxX: 100000, maxY: 100000 };
|
||||
const mapImage = { width: 4096, height: 4096 };
|
||||
writeFixtureJSON(fixtureDir, assetPaths[4], { key: "island-map", mapVersion: "island-map-v1", adapterVersion: "scum-live-data-v1", requiredSchemaFingerprint: fingerprint, sourceEvidence: "fixture", authorization: { redistribution: "first-party-generated-coordinate-metadata", baseMapArtwork: "not-packaged", renderingAvailability: "unavailable-until-authorized-base-map" }, worldBounds: mapBounds, image: mapImage, layers: [{ key: "players", capability: "positions.read", subjectType: "player", label: "Players", sourceQueryKey: "scum-positions-read" }], transformAssetPath: assetPaths[5] });
|
||||
writeFixtureJSON(fixtureDir, assetPaths[5], { key: "island-transform", transformVersion: "island-transform-v1", mapAssetKey: "island-map", adapterVersion: "scum-live-data-v1", requiredSchemaFingerprint: fingerprint, worldBounds: mapBounds, image: mapImage, validation: { rejectNonFinite: true, rejectOutOfBounds: true, acceptBoundaryPoints: true }, fixtures: [{ name: "origin", world: { x: 0, y: 0 }, pixel: { x: 2047.5, y: 2047.5 } }] });
|
||||
const digest = (assetPath: string) => sha256FixtureDigest(fixtureDir, assetPath);
|
||||
const giftGrantDigest = digest(assetPaths[2]);
|
||||
const mutationDigest = digest(assetPaths[3]);
|
||||
const mapDigest = digest(assetPaths[4]);
|
||||
const transformDigest = digest(assetPaths[5]);
|
||||
const catalogDigest = digest(assetPaths[6]);
|
||||
manifest.scumLiveData = {
|
||||
...manifest.scumLiveData,
|
||||
capabilityGates: [
|
||||
{ capability: "schema-probe", gate: "disabled", adapterVersion: "scum-live-data-v1", evidenceStatus: "missing", safeReason: "waiting for current service evidence" },
|
||||
{ capability: "players.read", gate: "disabled", adapterVersion: "scum-live-data-v1", requiredSchemaFingerprint: fingerprint, requiredAssetDigests: [digest(assetPaths[1])], evidenceStatus: "missing", safeReason: "waiting for query execution evidence" },
|
||||
{ capability: "positions.read", gate: "disabled", adapterVersion: "scum-live-data-v1", requiredSchemaFingerprint: fingerprint, requiredAssetDigests: [mapDigest, transformDigest], evidenceStatus: "missing", safeReason: "waiting for map execution evidence" },
|
||||
{ capability: "gift-command.write", gate: "disabled", adapterVersion: "scum-live-data-v1", requiredSchemaFingerprint: fingerprint, requiredAssetDigests: [giftGrantDigest, catalogDigest], evidenceStatus: "missing", safeReason: "waiting for gift command evidence" },
|
||||
{ capability: "profile-xml.write", gate: "disabled", adapterVersion: "scum-live-data-v1", requiredSchemaFingerprint: fingerprint, requiredAssetDigests: [mutationDigest], evidenceStatus: "missing", safeReason: "waiting for mutation safety evidence" }
|
||||
],
|
||||
logParsers: [{ key: "login-parser", adapterVersion: "scum-live-data-v1", assetPath: assetPaths[0], digest: digest(assetPaths[0]), 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(assetPaths[1]), 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(assetPaths[2]), 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(assetPaths[3]), 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(assetPaths[4]), requiredSchemaFingerprint: fingerprint, metadataSchemaRef: "schemas/scum-live/map-metadata.schema.json", transformAssetPath: assetPaths[5], transformDigest: digest(assetPaths[5]), 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(assetPaths[6]), catalogVersion: "catalog-v1", itemSchemaRef: "schemas/scum-live/gift-item.schema.json", transportTemplateKeys: ["gift-grant"] }]
|
||||
typedRconTemplates: [{ key: "gift-grant", adapterVersion: "scum-live-data-v1", assetPath: assetPaths[2], digest: giftGrantDigest, 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: mutationDigest, 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: mapDigest, requiredSchemaFingerprint: fingerprint, metadataSchemaRef: "schemas/scum-live/map-metadata.schema.json", transformAssetPath: assetPaths[5], transformDigest, 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: catalogDigest, catalogVersion: "catalog-v1", itemSchemaRef: "schemas/scum-live/gift-item.schema.json", transportTemplateKeys: ["gift-grant"] }]
|
||||
};
|
||||
});
|
||||
expect(errors).toEqual([]);
|
||||
|
||||
Reference in New Issue
Block a user