diff --git a/docs/plugin-authoring.md b/docs/plugin-authoring.md index 0c15a46..a9965fa 100644 --- a/docs/plugin-authoring.md +++ b/docs/plugin-authoring.md @@ -99,6 +99,8 @@ the native Convax file picker; a Pet surface never receives a filesystem path. Convax accepts one current-format transparent 1536×1872 PNG or WebP atlas, stores a managed copy, and serves it through `convax-pet-asset:`. Legacy Goku folders, `pet.json`, remote assets, and arbitrary file reads are not supported. +Pet Plugins that do not offer custom collection management omit this optional +grant while retaining the three required activity and preference capabilities. The settings and overlay pages run with no Node, Electron, remote network, native path, or arbitrary IPC access. Their surface-scoped `convax.pet-host/1` ports expose diff --git a/docs/registry-spec.md b/docs/registry-spec.md index 3f0b91e..b9fa403 100644 --- a/docs/registry-spec.md +++ b/docs/registry-spec.md @@ -62,9 +62,10 @@ One Pet feature Plugin is a `convax.plugin/5` capability published through the normal Plugin Registry item. Its complete embedded manifest contains `contributes.pet` with package-relative `library`, `overlay`, and `settings` paths plus `protocol: "convax.pet-host/1"`. It requests exactly `pet.activity.read`, -`pet.activity.open`, `pet.preferences.write`, and `pet.custom.manage`, and has no -runtime or companion executable. The `convax.plugin-capability/1` compatibility -label remains the transport-neutral admission contract. +`pet.activity.open`, and `pet.preferences.write`, may additionally request the +exact `pet.custom.manage` grant, and has no runtime or companion executable. +Convax Pet 0.2.2 requests all four. The `convax.plugin-capability/1` +compatibility label remains the transport-neutral admission contract. Clients validate both static surface entries, the strict `convax.pet-library/1` document, and every referenced atlas before activation. Installation does not diff --git a/registry/config.json b/registry/config.json index 5093618..ee53e88 100644 --- a/registry/config.json +++ b/registry/config.json @@ -1,4 +1,4 @@ { - "sequence": 31, + "sequence": 32, "yanked": [] } diff --git a/schemas/convax-plugin-manifest-v5.schema.json b/schemas/convax-plugin-manifest-v5.schema.json index 337e5d7..82a3675 100644 --- a/schemas/convax-plugin-manifest-v5.schema.json +++ b/schemas/convax-plugin-manifest-v5.schema.json @@ -151,13 +151,20 @@ "required": ["capabilities"], "properties": { "capabilities": { - "minItems": 4, + "minItems": 3, "maxItems": 4, + "items": { + "enum": [ + "pet.activity.read", + "pet.activity.open", + "pet.preferences.write", + "pet.custom.manage" + ] + }, "allOf": [ { "contains": { "const": "pet.activity.read" } }, { "contains": { "const": "pet.activity.open" } }, - { "contains": { "const": "pet.preferences.write" } }, - { "contains": { "const": "pet.custom.manage" } } + { "contains": { "const": "pet.preferences.write" } } ] } }, diff --git a/tooling/lib.mjs b/tooling/lib.mjs index 19079d1..47cba09 100644 --- a/tooling/lib.mjs +++ b/tooling/lib.mjs @@ -800,15 +800,13 @@ function parsePluginManifestV5(value, label) { ? undefined : parsePetV5(value.contributes.pet, `${label} pet`) if (pet !== undefined) { - const requiredPetCapabilities = [ - "pet.activity.read", - "pet.activity.open", - "pet.preferences.write", - "pet.custom.manage", - ] - if (capabilities.length !== requiredPetCapabilities.length || - requiredPetCapabilities.some((capability) => !capabilities.includes(capability))) { - error(label, "pet capabilities must be exactly pet.activity.read, pet.activity.open, pet.preferences.write, and pet.custom.manage") + const requiredPetCapabilities = ["pet.activity.read", "pet.activity.open", "pet.preferences.write"] + const allowedPetCapabilities = new Set([...requiredPetCapabilities, "pet.custom.manage"]) + if (capabilities.length < requiredPetCapabilities.length || + capabilities.length > allowedPetCapabilities.size || + requiredPetCapabilities.some((capability) => !capabilities.includes(capability)) || + capabilities.some((capability) => !allowedPetCapabilities.has(capability))) { + error(label, "pet capabilities must be exactly pet.activity.read, pet.activity.open, and pet.preferences.write, with optional pet.custom.manage") } if (hasRuntime) error(label, "pet feature cannot declare an executable runtime") } diff --git a/tooling/plugin-v5.test.js b/tooling/plugin-v5.test.js index 6c5f8df..a5c7047 100644 --- a/tooling/plugin-v5.test.js +++ b/tooling/plugin-v5.test.js @@ -193,6 +193,12 @@ describe("convax.plugin/5 transport-neutral and pet contributions", () => { test("requires the exact pet capabilities and forbids executable runtimes", () => { expect(() => parsePluginManifest({ ...petManifest(), capabilities: [] })).toThrow("pet capabilities") + expect(() => + parsePluginManifest({ + ...petManifest(), + capabilities: [...petManifest().capabilities, "projects.read"], + }), + ).toThrow("pet capabilities") expect(() => parsePluginManifest({ ...petManifest(), @@ -202,6 +208,19 @@ describe("convax.plugin/5 transport-neutral and pet contributions", () => { ).toThrow("pet feature") }) + test("keeps historical Pet manifests valid when custom management is absent", () => { + const historical = petManifest({ + version: "0.2.1", + capabilities: petManifest().capabilities.filter((capability) => capability !== "pet.custom.manage"), + }) + + expect(parsePluginManifest(historical).capabilities).toEqual([ + "pet.activity.read", + "pet.activity.open", + "pet.preferences.write", + ]) + }) + test("validates every packaged pet library atlas", () => { const manifest = parsePluginManifest(petManifest()) const comet = {