fix(publish): read the catalog slug from the Agent Plugins document name - #33
Merged
Conversation
Every plugin publish has been failing since the manifests became Agent Plugins 1.0.0 documents: plugin.json.id must be a non-empty string ::error::failed to read plugin.json id Both manifest readers in this workflow still looked for the flat `id`, which the document shape does not carry — the catalog slug is now the document's top-level `name`. Six plugin repos (ep, git, local-indexer, meeting, ms-graph, work-assistant) tag-triggered a publish and every one stopped here, so no new version reached the catalog. The failure was fail-closed: nothing was published under a wrong identity. Reading `name` unconditionally would be worse than the bug. A pre-1.0.0 flat manifest ALSO carries a top-level `name`, but there it holds the human display name — meeting's flat manifest reads `name: "LVIS Meeting"`, `id: "meeting"`. Publishing under "LVIS Meeting" is exactly the silent cross-entry corruption the slug cross-check exists to prevent. So gate on `$schema` and refuse anything that is not a document rather than guessing between the two shapes; this mirrors the server-side `manifest_fields()` discrimination. Every caller of this reusable workflow is one of the seven plugin repos, all of which now ship documents whose `name` equals the caller's `slug` input (verified across all seven), so the gate has no legitimate flat caller to break. Verified by extracting both readers from this file and running them against real manifests in both directions: meeting v0.8.0 (document) yields "meeting" and exit 0; meeting v0.7.0 (flat) exits 2 and never emits "LVIS Meeting".
`$schema` inside the single-quoted `node -e '...'` body is the Agent Plugins document's JSON key, read literally by JS — shell expansion would corrupt it. Same targeted directive the artifact reader below already carries.
A block-top directive did not suppress it; shellcheck honours the disable when it precedes the command it applies to, which is how the artifact reader below already carries its own. Verified with actionlint locally: removing the directive reproduces the CI error verbatim, restoring it returns exit 0.
The isolation contract exercises PUBLISH_MANIFEST_READER against a fixture
manifest, and that fixture was a flat `{ id, version }` — so the reader's new
`$schema` gate refused it and the contract failed. The fixture was pinned to
the shape the migration retired.
Add the counter-example the gate exists for. A pre-1.0.0 flat manifest also
carries a top-level `name`, holding the human display name; if the reader ever
stops discriminating on `$schema`, that fixture publishes under the slug
"LVIS Meeting" instead of "meeting" — a different catalog entry than the
manifest declares. The new case asserts the refusal, and separately asserts
the display name never reaches stdout.
Controlled: with the `$schema` gate stripped from the reader the new case
fails with "pre-1.0.0 flat manifest must be rejected, not published";
with it restored the contract exits 0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every plugin publish has failed since the manifests became Agent Plugins 1.0.0 documents. All six tag-triggered runs on 2026-08-26 stopped at the same line:
Both manifest readers in this reusable workflow still looked for the flat
id. The document shape does not carry it — the catalog slug is now the document's top-levelname. No new plugin version has reached the catalog as a result. The failure was fail-closed, so nothing was published under a wrong identity.Why not just read
nameA pre-1.0.0 flat manifest also carries a top-level
name, but there it holds the human display name:id: "meeting"name: "meeting"name: "LVIS Meeting"extensions["xyz.lvisai"].displayName: "LVIS Meeting"An unconditional
nameread would publish flat manifests under the slugLVIS Meeting— precisely the silent cross-entry corruption this slug cross-check exists to prevent. So the readers gate on$schemaand refuse anything that is not a document, rather than guessing between the two shapes. This mirrors the server-sidemanifest_fields()discrimination.Blast radius
Every caller of this workflow is one of the seven plugin repos, all now shipping documents whose
nameequals the caller'ssluginput — verified across all seven, no mismatches. The gate has no legitimate flat caller to break.Verification
Both readers were extracted from this file and run against real manifests in both directions:
meeting, exit 0LVIS Meetingmeeting/0.8.0, exit 0