ADR-0134: PluginManifest.party defaults to untrusted¶
Status: Accepted (2026-10-05); decisions 3, 4 and 6 amended 2026-10-06 (iris-harness #125)
Amends: ADR-0133 (the default value only; the field, its value set and its independence
from trust stand)
Relates to: iris-harness issue #97 and PRs #123 (the default) and #125 (the notice, the
explicit examples and scaffolds), runtime/plugin_host/manifest.py,
runtime/plugin_host/loader.py, sdk/stable_tier.yaml,
docs/architecture/plugin-contract.md
Context¶
ADR-0133 added party (first-party | trusted-third-party | untrusted) with a
default of first-party, chosen so no bundled manifest needed a change. The default is
the wrong way round. A third-party plugin that omits the field reads as first-party, so
the day something enforces party (refuse or warn on an in-process untrusted plugin),
the plugins that most need the check pass it without declaring anything. A safety field
whose default is the safe-looking value fails open.
Nothing reads party to make a decision today. This ADR changes what an undeclared
manifest says; it changes no behavior.
Decision¶
partydefaults tountrusted. The allowed values and the field's place in the manifest (and instable_tier.yaml) are unchanged.- A plugin this project ships declares
party: first-partyexplicitly: the sevenplugins_builtin/*and the threeiris_personal/plugins/*manifests. - Examples and the
iris plugin newscaffolds are written for outside authors, so they do not claimfirst-party. They declareparty: untrustedexplicitly, with a short comment, so copying one teaches the field and a fresh scaffold mounts without the notice in decision 6. (Originally they inherited the default silently; amended by the owner on 2026-10-06 and implemented in #125.) partyis surfaced wherevertrustis: the plugin inventory (GET /plugins,/plugins/{name}),--dump-config(text and JSON),iris pluginsandiris plugins show, the registry snapshot and the web Plugins screens. A plugin with no manifest shows no party.- Nothing enforces
partyyet. No mount, load or governance decision reads it. Enforcement is later work and gets its own ADR. - The loader says so when it falls back to the default: one WARNING per mount when a
manifest does not declare
party, naming the plugin and the fix. That includes the manifest synthesised for an entry-point plugin that ships none, the usual third-party case. A plugin supplied from code (testing.plugin(),add_in_process) is the caller's own and gets no notice, and discovery-only--dump-confignever mounts. The notice is not enforcement: the plugin still mounts. (Added 2026-10-06, #125.)
Alternatives considered¶
- Require the field (no default). Rejected. It breaks every existing third-party
manifest at load time for a field nothing enforces yet, and the
entry_pointpath deliberately loads a plugin that ships no manifest at all ("name + entry point is the minimum contract"), so there would be no manifest to require it in. A required field also makes authors guess a value to get past the error, which is worse than the fail-closed default saying the honest thing. A default ofuntrustedgives the same safety property without a breaking change. - Keep
first-partyand rely on enforcement to be strict later. Rejected: that is the fail-open this ADR removes.
Consequences¶
- A manifest, or a synthesized one (an entry-point plugin with no manifest, an
in-process plugin registered by name), now reports
untrusted. Display only, plus the mount-time warning in decision 6 for a manifest that omits the field. - Anyone copying a bundled manifest must keep its
party: first-partyline on purpose; copying an example or scaffold carries an explicitparty: untrusteduntil they or their operator say otherwise. - The warning is per mount, so a plugin that omits
partylogs it on every start. Declaring the field is the fix; there is no way to silence it otherwise. - Reversible: one default, one line per bundled manifest.