Skip to content

fix(publish): read the catalog slug from the Agent Plugins document name - #33

Merged
ken-jo merged 4 commits into
mainfrom
fix/publish-reads-agent-plugins-name
Aug 26, 2026
Merged

fix(publish): read the catalog slug from the Agent Plugins document name#33
ken-jo merged 4 commits into
mainfrom
fix/publish-reads-agent-plugins-name

Conversation

@ken-jo

@ken-jo ken-jo commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

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:

plugin.json.id must be a non-empty string
::error::failed to read plugin.json id

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-level name. 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 name

A pre-1.0.0 flat manifest also carries a top-level name, but there it holds the human display name:

meeting v0.7.0 (flat) meeting v0.8.0 (document)
id: "meeting" name: "meeting"
name: "LVIS Meeting" extensions["xyz.lvisai"].displayName: "LVIS Meeting"

An unconditional name read would publish flat manifests under the slug LVIS Meeting — precisely the silent cross-entry corruption this slug cross-check exists to prevent. So the readers 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.

Blast radius

Every caller of this workflow is one of the seven plugin repos, all now shipping documents whose name equals the caller's slug input — 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:

reader input result
slug cross-check meeting v0.8.0 (document) meeting, exit 0
slug cross-check meeting v0.7.0 (flat) refused, exit 2, never emits LVIS Meeting
hardened reader meeting v0.8.0 (document) meeting / 0.8.0, exit 0
hardened reader meeting v0.7.0 (flat) refused, exit 2

ken-jo added 4 commits August 26, 2026 15:39
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.
@ken-jo
ken-jo merged commit 51a5679 into main Aug 26, 2026
1 check passed
@ken-jo
ken-jo deleted the fix/publish-reads-agent-plugins-name branch August 26, 2026 06:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant