Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/plugins/nexus-service/convax-package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"id": "nexus-service",
"name": "Nexus · OpenRouter",
"description": "Connects Convax to the Convax Workspace in Nexus for OpenRouter chat and live image-model generation through short-lived Data Tokens.",
"version": "0.3.10",
"version": "0.3.11",
"license": "MIT",
"compatibility": {
"pluginSchema": "convax.plugin/7",
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/nexus-service/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@microvoid/convax-plugin-nexus-service",
"version": "0.3.10",
"version": "0.3.11",
"private": true,
"type": "module",
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/nexus-service/package/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"id": "nexus-service",
"name": "Nexus · OpenRouter",
"description": "Connects Convax to the Convax Workspace in Nexus for OpenRouter chat and live image-model generation through short-lived Data Tokens.",
"version": "0.3.10",
"version": "0.3.11",
"contributes": {
"generation": {
"models": [
Expand Down
37 changes: 37 additions & 0 deletions tooling/marketplace-output.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,43 @@ describe("published Marketplace output closure", () => {
.rejects.toThrow("release-plan tags differ from Registry v2")
})

test("verifies only selected package Releases while retaining the complete Registry", async () => {
const fixture = await writeFixture()
const selected = fixture.releasePlan[0]
const metadata = fixture.releasePlan.at(-1)
await fs.writeFile(
path.join(fixture.catalogDirectory, "release-plan.json"),
`${JSON.stringify({
schema: "convax.release-plan/1",
releases: [selected, metadata],
})}\n`,
)
const selectedVersions = [{
id: "fixture-plugin",
itemKey: sha256(Buffer.from("plugin\0fixture-plugin", "utf8")),
kind: "plugin",
previousVersion: "0.9.0",
releaseTag: "plugin-fixture-plugin-v1.0.0",
version: "1.0.0",
}]

await expect(verifyMarketplaceOutput(
fixture.catalogDirectory,
{ selectedVersions },
)).resolves.toEqual({
packages: 3,
releaseAssets: 4,
releaseTags: 2,
v1Packages: 2,
})

selectedVersions[0].releaseTag = "plugin-fixture-plugin-v9.9.9"
await expect(verifyMarketplaceOutput(
fixture.catalogDirectory,
{ selectedVersions },
)).rejects.toThrow("selected version change plugin/fixture-plugin differs from Registry v2")
})

test("rejects a descriptor or Showcase that changes the Official publication identity", async () => {
const descriptorFixture = await writeFixture()
const descriptor = JSON.parse(await fs.readFile(
Expand Down
27 changes: 27 additions & 0 deletions tooling/product-lock-output.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,33 @@ describe("Convax product lock input closure", () => {
})
})

test("verifies an existing preinstall outside a selected package release-plan", async () => {
const fixture = await writeFixture()
const planPath = path.join(fixture.root, "catalog", "release-plan.json")
const plan = JSON.parse(await fs.readFile(planPath, "utf8"))
plan.releases = plan.releases.filter((release) =>
release.tag.startsWith("registry-v2-"))
await fs.writeFile(planPath, `${JSON.stringify(plan)}\n`)
const lockPath = path.join(fixture.root, "product-lock-input.json")

await expect(verifyProductLockInput(lockPath)).resolves.toEqual({
builtinReservations: 1,
preinstalledPackages: 1,
verifiedArtifacts: 7,
})

const pluginPath = path.join(
fixture.root,
"catalog",
"releases",
"plugin-ffmpeg-tools-v0.3.1",
"convax-plugin-ffmpeg-tools-0.3.1.zip",
)
await fs.writeFile(pluginPath, "PLUGIN")
await expect(verifyProductLockInput(lockPath))
.rejects.toThrow("preinstalled Plugin artifact bytes differ from Registry v2")
})

test("rejects a source swap or widened preinstall policy", async () => {
const fixture = await writeFixture()
const lockPath = path.join(fixture.root, "product-lock-input.json")
Expand Down
64 changes: 61 additions & 3 deletions tooling/verify-marketplace-output.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,52 @@ function expectedReleaseTag(entry) {
return `${entry.kind}-${entry.id}-v${entry.version}`
}

function selectedPackageTags(registryPackages, selectedVersions) {
const packageTags = new Set(registryPackages.map(expectedReleaseTag))
if (selectedVersions === undefined) return packageTags
if (!Array.isArray(selectedVersions)) {
throw new Error("selected version changes must be an array")
}

const registryByIdentity = new Map(
registryPackages.map((entry) => [`${entry.kind}\0${entry.id}`, entry]),
)
const selectedIdentities = new Set()
const selectedTags = new Set()
for (const entry of selectedVersions) {
if (
!entry ||
typeof entry !== "object" ||
Array.isArray(entry) ||
typeof entry.kind !== "string" ||
typeof entry.id !== "string" ||
typeof entry.version !== "string" ||
typeof entry.itemKey !== "string" ||
typeof entry.releaseTag !== "string" ||
(entry.previousVersion !== undefined && typeof entry.previousVersion !== "string")
) {
throw new Error("selected version change is incomplete")
}
const identity = `${entry.kind}\0${entry.id}`
const registryEntry = registryByIdentity.get(identity)
const expectedItemKey = sha256(Buffer.from(identity, "utf8"))
if (
!registryEntry ||
registryEntry.version !== entry.version ||
entry.itemKey !== expectedItemKey ||
entry.releaseTag !== expectedReleaseTag(registryEntry)
) {
throw new Error(`selected version change ${entry.kind}/${entry.id} differs from Registry v2`)
}
if (selectedIdentities.has(identity) || selectedTags.has(entry.releaseTag)) {
throw new Error(`selected version change duplicates ${entry.kind}/${entry.id}`)
}
selectedIdentities.add(identity)
selectedTags.add(entry.releaseTag)
}
return selectedTags
}

function releaseReference(value, label) {
if (
!value ||
Expand Down Expand Up @@ -230,7 +276,10 @@ async function readExactCatalogFile(
return fs.readFile(real)
}

export async function verifyMarketplaceOutput(catalogDirectory) {
export async function verifyMarketplaceOutput(
catalogDirectory,
{ selectedVersions } = {},
) {
const [descriptor, registryV2, registryV1, showcaseV2, releasePlan] = await Promise.all([
readJson(path.join(catalogDirectory, "marketplace.json"), "Marketplace descriptor"),
readJson(path.join(catalogDirectory, "registry-v2.json"), "Registry v2"),
Expand Down Expand Up @@ -271,8 +320,9 @@ export async function verifyMarketplaceOutput(catalogDirectory) {
)

const packageTags = new Set(registryV2.packages.map(expectedReleaseTag))
const publishedPackageTags = selectedPackageTags(registryV2.packages, selectedVersions)
const metadataTag = `registry-v2-${registryV2.revision}`
const admittedPlanTags = new Set([...packageTags, metadataTag])
const admittedPlanTags = new Set([...publishedPackageTags, metadataTag])
const assetsByUrl = new Map()
const actualTags = new Set()
const catalogRealPath = await fs.realpath(catalogDirectory)
Expand Down Expand Up @@ -361,6 +411,8 @@ export async function verifyMarketplaceOutput(catalogDirectory) {

for (const reference of collectReleaseReferences(registryV2)) {
await readExactLocalRelease(catalogDirectory, catalogRealPath, reference.url, reference)
const { tag } = parseReleaseUrl(reference.url, "Registry")
if (!publishedPackageTags.has(tag)) continue
const asset = assetsByUrl.get(reference.url)
if (!asset) {
throw new Error(`${reference.url} is absent from the release-plan`)
Expand Down Expand Up @@ -391,7 +443,13 @@ export async function verifyMarketplaceOutput(catalogDirectory) {
async function main(argv) {
if (argv.length > 1) throw new Error("Usage: verify-marketplace-output [catalog-directory]")
const directory = path.resolve(argv[0] ?? "dist/catalog")
const result = await verifyMarketplaceOutput(directory)
const selectedVersions = process.env.CONVAX_MARKETPLACE_CHANGED
? await readJson(
path.resolve(process.env.CONVAX_MARKETPLACE_CHANGED),
"selected version changes",
)
: undefined
const result = await verifyMarketplaceOutput(directory, { selectedVersions })
console.log(
`Verified ${result.packages} packages, ${result.v1Packages} v1 identities, ` +
`${result.releaseTags} immutable Releases, and ${result.releaseAssets} exact assets.`,
Expand Down
35 changes: 24 additions & 11 deletions tooling/verify-product-lock-input.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ async function verifyLockedArtifact({
releaseAssets,
label,
expected,
allowExistingRelease = false,
}) {
exactKeys(lock, ["path", "url"], label)
const relativePath = validateRelativePath(lock.path, `${label}.path`)
Expand All @@ -216,15 +217,19 @@ async function verifyLockedArtifact({
throw new Error(`${label} path and URL identify different immutable bytes`)
}
const releaseAsset = releaseAssets.get(lock.url)
if (!releaseAsset) throw new Error(`${label} is absent from the ${area} release-plan`)
if (releaseAsset.path !== `releases/${tag}/${name}`) {
throw new Error(`${label} release-plan path differs from its URL`)
}
if (expected && (
releaseAsset.size !== expected.size ||
releaseAsset.sha256 !== expected.sha256
)) {
throw new Error(`${label} differs from Registry v2`)
if (!releaseAsset && (!allowExistingRelease || !expected)) {
throw new Error(`${label} is absent from the ${area} release-plan`)
}
if (releaseAsset) {
if (releaseAsset.path !== `releases/${tag}/${name}`) {
throw new Error(`${label} release-plan path differs from its URL`)
}
if (expected && (
releaseAsset.size !== expected.size ||
releaseAsset.sha256 !== expected.sha256
)) {
throw new Error(`${label} differs from Registry v2`)
}
}
const maximumSize = area === "builtin"
? builtinLimit
Expand All @@ -238,8 +243,13 @@ async function verifyLockedArtifact({
label,
maximumSize,
)
if (bytes.length !== releaseAsset.size || sha256(bytes) !== releaseAsset.sha256) {
throw new Error(`${label} bytes differ from the immutable release-plan`)
const admitted = releaseAsset ?? expected
if (bytes.length !== admitted.size || sha256(bytes) !== admitted.sha256) {
throw new Error(
releaseAsset
? `${label} bytes differ from the immutable release-plan`
: `${label} bytes differ from Registry v2`,
)
}
return bytes
}
Expand Down Expand Up @@ -461,6 +471,7 @@ export async function verifyProductLockInput(inputFile) {
releaseAssets: catalogAssets,
label: "preinstalled Plugin artifact",
expected: registryArtifact(plugin, "ffmpeg-tools"),
allowExistingRelease: true,
})

const declaredOwnedSkills = plugin.manifest?.contributes?.skills
Expand All @@ -482,6 +493,7 @@ export async function verifyProductLockInput(inputFile) {
releaseAssets: catalogAssets,
label: "preinstalled owned Skill artifact",
expected: registryArtifact(ownedSkill, "ffmpeg-canvas"),
allowExistingRelease: true,
})

const lockedCompanion = preinstalled.companions[0]
Expand All @@ -503,6 +515,7 @@ export async function verifyProductLockInput(inputFile) {
releaseAssets: catalogAssets,
label: "preinstalled companion artifact",
expected: companionTarget.artifact,
allowExistingRelease: true,
})

return {
Expand Down