Skip to content

Describe each format in Rust and remove plugin.toml - #458

Merged
schmitch merged 2 commits into
mainfrom
claude/444-format-descriptor
Oct 24, 2026
Merged

schmitch merged 2 commits into
mainfrom
claude/444-format-descriptor

Conversation

@schmitch

Copy link
Copy Markdown
Member

Fixes #444

Part of #442. This PR is stacked on #456 (#443), which moves the format crates to crates/formats/. Merge that one first; GitHub then retargets this one to main.

Changes

The descriptor. Each format describes itself with a 'static value that FormatPlugin::descriptor() returns. The new types live in forge-plugin-api, next to RepositoryKind:

  • Descriptor: id, name, version, description, mount, kinds, storage, assets;
  • Storage;
  • Assets, with &'static str fields and Assets::DEFAULT in place of the serde default;
  • DEFAULT_MOUNT.

NuGet and raw each define pub const DESCRIPTOR next to their impl FormatPlugin, with version: env!("CARGO_PKG_VERSION"). A typo in a key or a kind now fails the build.

The host side.

  • Plugin::builtin(implementation) takes only the format.
  • Plugin::descriptor() replaces Plugin::manifest().
  • Plugin::id() returns descriptor().id without leaking a copy.
  • RepositoryKind::as_str() saves the format registry its own mapping.

Checks. The startup parser's checks are now forge_plugin::descriptor::check:

  • the id's spelling and RESERVED_IDS;
  • the mount and RESERVED_MOUNTS, together with the existing test that every web UI page is reserved;
  • the kinds, each once;
  • the asset names and content types.

check also refuses two formats with the same id. A new test in forge-server, plugins::tests::the_builtin_formats_describe_themselves_well, runs it over plugins::builtin(). It also shows that check refuses a malformed or reserved id, a second format with a built-in's id, and invalid asset names. The probe plugin in plugin_contract.rs checks its own descriptor the same way.

Removed:

  • plugin.toml from both crates;
  • plugin_toml!, PLUGIN_TOML, Manifest::parse and manifest.rs;
  • PluginError and SUPPORTED_APIS;
  • forge_plugin_api::API and the api key;
  • the plugin_toml_names_this_crate test;
  • the "keep it equal to version in plugin.toml" comments;
  • forge-plugin's toml dependency, plus its now unused serde and thiserror.

Docs. crates/formats/README.md and Layout and Writing a plugin in docs/format-plugins.md now show the descriptor. The status note no longer mentions a declared contract version.

The new doc example on Descriptor is no_run. test-parallel.sh records every test binary cargo would run, and that includes a doctest's temporary binary, which no longer exists when the script runs it. That's why the other doc examples in the repo don't run either.

No changelog fragment, since nothing changes for anyone running Forge.

Checks

  • cargo fmt --check and cargo clippy --workspace --all-targets -- -D warnings pass.
  • test-parallel.sh --workspace: 105 of 106 test binaries passed, including the existing admin tests of GET /admin/formats, unchanged. The 106th was the doctest's temporary binary described above. With the example no_run, test-parallel.sh over the plugin crates passes, the doctest included.
  • cargo tree -p forge-plugin shows no toml.
  • git grep -n 'plugin\.toml\|plugin_toml\|PLUGIN_TOML' finds nothing outside CHANGELOG.md and docs/changes/.

Generated by Claude Code

Each format now describes itself with a 'static Descriptor that its
FormatPlugin::descriptor returns: id, name, version (the crate's,
env!("CARGO_PKG_VERSION")), description, mount, repository kinds,
storage and asset names. Descriptor, Storage, Assets (with
Assets::DEFAULT) and DEFAULT_MOUNT live in forge-plugin-api next to
RepositoryKind. NuGet and raw define theirs (DESCRIPTOR) next to their
implementations, so a typo in a key or a kind fails to compile.

Plugin::builtin takes only the format, Plugin::descriptor replaces
Plugin::manifest, and Plugin::id returns the descriptor's id without
leaking a copy. What the startup parser checked (the id's spelling and
the reserved ids, the mount, the kinds, the asset names) is now
forge_plugin::descriptor::check, which a test over plugins::builtin()
runs, together with the check that no two formats share an id: a bad
descriptor fails cargo test instead of startup.

Gone: plugin.toml from both crates, plugin_toml! and PLUGIN_TOML,
Manifest::parse, PluginError, SUPPORTED_APIS, forge_plugin_api::API and
the api key, the plugin_toml_names_this_crate tests, the "keep it equal"
comments, and forge-plugin's toml dependency (and its unused serde and
thiserror). The crates' README and docs/format-plugins.md show the
descriptor instead of plugin.toml.

GET /admin/formats answers as before.

Fixes #444

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CxwRK8xYPsMDBiMkySxcHf
Base automatically changed from claude/443-crates-formats to main October 24, 2026 07:31
@schmitch
schmitch added this pull request to the merge queue Oct 24, 2026
Merged via the queue into main with commit 7908753 Oct 24, 2026
19 checks passed
@schmitch
schmitch deleted the claude/444-format-descriptor branch October 24, 2026 08:44
schmitch pushed a commit that referenced this pull request Oct 24, 2026
Resolve the conflicts with #458 (formats describe themselves in Rust,
plugin.toml is gone) and #460 (access levels):

- npm describes itself with a DESCRIPTOR like NuGet and raw; plugin.toml's
  upstream_token key becomes the Descriptor field `upstream_token`
  (UpstreamToken::Bearer for npm, None for NuGet and raw).
- The dispatcher's per-action authorization uses authz::Level.
- The package_tag / cli_login migration becomes 0047: main's access
  levels took 0046.
- The cli-login web page's path is a reserved mount.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BaGpNGgkQV54XvgL5D9ay2
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.

Describe each format in Rust and remove plugin.toml

2 participants