Skip to content

feat(stella-plugin): the plugin manifest — participation declared, never inferred (#3245 slice A) - #3311

Draft
macanderson wants to merge 1 commit into
mainfrom
feat/3245-slice-a-plugin-manifest
Draft

feat(stella-plugin): the plugin manifest — participation declared, never inferred (#3245 slice A)#3311
macanderson wants to merge 1 commit into
mainfrom
feat/3245-slice-a-plugin-manifest

Conversation

@macanderson

@macanderson macanderson commented Aug 14, 2026

Copy link
Copy Markdown
Owner

What

Slice A of #3245 (plugins as turn-loop participants): a new leaf crate, stella-plugin, that parses and validates the epic's new manifest blocks — [loop] with the participation ladder (none < observer < steering < arbiter), [requirements], [oracle], [subloop], and [roles] — as pure functions over borrowed text. No I/O, no workspace-crate dependencies (the stella-diag/stella-tty leaf shape): the engine never learns plugins exist, and the host that binds these grants to the engine's gates (the Stop gate PR #3302 is generalizing, the hook runner, the sub-agent primitive) arrives with slices B–E.

Rules enforced, each with a typed ManifestError variant (invariant 5) and a rejection test:

  • Undeclared [loop] = grade none; unknown keys/hook names/grades are load errors (deny_unknown_fields everywhere — the Epic: Stella Apps — a vendor-neutral extension platform (TOML manifest · OAuth lifecycle · host API · marketplace) #1400 rule).
  • No hooks below steering; Stop only at arbiter; an arbiter must declare Stop (an undeclared hook is never invoked, so an arbiter without it is a contradiction).
  • max_holds and [requirements] are arbiter-only; an arbiter requires non-empty [requirements] (every hold must cite a named requirement).
  • [oracle] is arbiter-only (conservative: widening later is compatible, narrowing is not), argv non-empty, timeout ≥ 1; flip/tamper are closed vocabularies ("required", "artifact-identity") so an unknown value is a load error, never a silently weaker contract.
  • [subloop] requires ≥ steering, stages non-empty/unique; [roles] requires [subloop], tiers are non-empty open-vocabulary intents (never a credential or URL — routing stays the user's).

LoopGrant::permits_hook is the authoritative filter behind "an undeclared hook is never invoked": it gates on grade and declared list, so even a hand-built grant cannot leak a dispatch.

Witness

The crate is new, so the whole test surface is the witness: all 26 tests fail on main (the crate does not exist there) and pass here. Slice A's acceptance from #3245 §6 verbatim, in tests/manifest_grades.rs: fixture manifests at all four grades round-trip (through TOML and serde_json — invariant 4), and a hook not named in the manifest is not permitted even if the process registers for it.

Decisions a reviewer should weigh

Dependencies

No new workspace dependencies: serde, toml, thiserror were already in [workspace.dependencies]; serde_json is dev-only for the round-trip tests.

Gate

cargo test -p stella-plugin (26 green), cargo clippy -p stella-plugin --all-targets -- -D warnings, RUSTDOCFLAGS="-D warnings" cargo doc -p stella-plugin, make guards-fast incl. god-files (README carries the no-god-files section), typed-errors, module-reachability, file-size (judged against base), doc-links, invariants — all green locally. AGENTS.md's crate count, god-file prose, and workspace table updated in the same PR.

Refs #3245
Refs #3310

Summary by Sourcery

Add a new leaf crate for parsing and validating plugin manifests that declare participation in the turn loop, and wire it into the workspace metadata.

New Features:

  • Introduce the stella-plugin crate to parse and validate plugin manifests, including participation grades, hook grants, requirements, oracle configuration, subloop stages, and role routing intents, as pure functions over borrowed text.
  • Expose a typed PluginManifest API with a single TOML-based constructor and an authoritative LoopGrant::permits_hook check for whether specific hook events are allowed.

Enhancements:

  • Document the responsibilities and boundaries of the new stella-plugin crate, including invariants, layout, and dependency constraints, in a dedicated README.
  • Update workspace documentation and metadata to include the new crate and keep god-file and crate-count invariants accurate.

Tests:

  • Add manifest fixture files for all participation grades and tests that validate parsing, cross-field rules, round-tripping through TOML and JSON, and enforcement of undeclared-hook and arbiter-only constraints.

…ver inferred (#3245 slice A)

A new leaf crate (no workspace-crate dependencies, the stella-diag shape)
that parses and validates #3245's new manifest blocks as one consent
document: the [loop] participation ladder (none < observer < steering <
arbiter, monotone), hook grants (Stop only at arbiter, none below
steering), max_holds and [requirements] as arbiter-only powers, the
host-run [oracle] contract (flip = "required", tamper =
"artifact-identity"), and [subloop]/[roles] as declared stages with
routing intents. Unknown keys, hook names, and grades are load errors
(deny_unknown_fields everywhere — the #1400 rule).

LoopGrant::permits_hook is the authoritative filter behind the epic's
"an undeclared hook is never invoked": it gates on both the grade and
the declared list, so even a hand-built grant cannot leak a dispatch.

Slice A's acceptance, as tests: fixture manifests at all four grades
round-trip through TOML and serde_json, and a hook not named in the
manifest is not permitted even if the process registers for it. Every
cross-field rule has a typed ManifestError variant (invariant 5) and a
rejection test.

HookEvent mirrors stella-core::hooks::HookEvent by name because the
dependency is forbidden in both directions; #3310 tracks unifying the
two in a shared home.

Exemplars followed: stella-tty/stella-diag for the leaf-crate shape,
stella-protocol for serde-first round-trip discipline.

Refs #3245
Refs #3310
@vercel

vercel Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
stella-cli-docs Ignored Ignored Aug 14, 2026 8:50pm

@sourcery-ai

sourcery-ai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Introduces a new leaf crate stella-plugin that parses and validates plugin manifests (loop participation grades, hook grants, requirements, oracle, subloop, and roles) as pure functions over borrowed text, with a typed error model and test fixtures, and wires the crate into the workspace docs and Cargo configuration.

Sequence diagram for host loading a manifest and gating hook dispatch

sequenceDiagram
    actor Host
    participant PluginManifest
    participant LoopGrant

    Host->>PluginManifest: from_toml_str(text)
    PluginManifest->>PluginManifest: validate()
    PluginManifest-->>Host: Result<PluginManifest, ManifestError>

    alt manifest_ok
        Host->>LoopGrant: permits_hook(hook_event)
        LoopGrant->>LoopGrant: includes(Participation::Steering)
        LoopGrant->>LoopGrant: hooks.contains(hook_event)
        LoopGrant-->>Host: bool
        alt permits_hook == true
            Host->>Host: dispatch hook_event to plugin
        else permits_hook == false
            Host->>Host: skip dispatch (undeclared or under‑graded)
        end
    else manifest_error
        Host->>Host: reject plugin load using ManifestError
    end
Loading

File-Level Changes

Change Details Files
Add stella-plugin crate to parse and validate plugin manifests, including participation grades, hooks, requirements, oracle, subloop and roles, with a single constructor and typed validation errors.
  • Define core manifest data model types such as PluginManifest, LoopGrant, Participation ladder, HookEvent mirror, Oracle/OracleCommand, Subloop, and Role using serde for TOML/JSON (de)serialization with deny_unknown_fields.
  • Implement PluginManifest::from_toml_str as the canonical constructor that parses TOML, enforces unknown-key/unknown-variant errors, and runs cross-field validation logic.
  • Encode validation rules in PluginManifest::validate to enforce grade/feature relationships (hooks require steering, Stop requires arbiter and arbiters must declare it, max_holds and requirements/oracle/subloop/roles constraints, non-empty names/tiers/descriptions, uniqueness constraints).
  • Expose LoopGrant::permits_hook as the authoritative gating function that requires at least steering grade and explicit hook declaration to authorize dispatch, guarding even hand-constructed grants.
  • Provide public re-exports in lib.rs so consumers can depend on stella-plugin’s types without touching module internals.
crates/stella-plugin/src/manifest.rs
crates/stella-plugin/src/lib.rs
crates/stella-plugin/Cargo.toml
Introduce a typed ManifestError enum that unifies parse and validation failures and documents each manifest rule as a dedicated variant.
  • Create ManifestError with thiserror, including a Parse variant wrapping toml::de::Error and specific rule-level variants (e.g., HooksRequireSteering, StopHookRequiresArbiter, ArbiterRequiresRequirements, OracleRequiresArbiter, SubloopRequiresSteering, EmptyName).
  • Ensure each variant carries enough context (e.g., offending hook, participation grade, requirement name, role name) for user-facing diagnostics.
  • Use ManifestError throughout parsing/validation entry points so callers get a single error type regardless of whether failure came from TOML shape or cross-field rules.
crates/stella-plugin/src/error.rs
crates/stella-plugin/src/manifest.rs
Add tests and fixtures to assert manifest behavior across all participation grades and round-tripping through TOML and JSON, plus enforcement of the undeclared-hook rule.
  • Add grade fixture TOML files for none, observer, steering, and arbiter manifests capturing canonical examples of each grade’s allowed fields and combinations.
  • Write tests that parse each fixture and assert expected participation grade, TOML round-trip stability, and JSON round-trip stability.
  • Add behavior tests for LoopGrant::permits_hook across grades, ensuring undeclared hooks are never permitted and that below-steering grades cannot gain dispatch even via hand-constructed grants.
  • Add focused tests for all validation rules (unknown keys/grades/hooks, duplicate hooks/stages, empty descriptions/tiers/names, arbiter-only constraints, oracle argv/timeout bounds, roles requiring subloop).
crates/stella-plugin/tests/manifest_grades.rs
crates/stella-plugin/tests/fixtures/none.toml
crates/stella-plugin/tests/fixtures/observer.toml
crates/stella-plugin/tests/fixtures/steering.toml
crates/stella-plugin/tests/fixtures/arbiter.toml
Document the new crate and wire it into workspace metadata and docs as a leaf with no workspace-crate dependencies.
  • Create stella-plugin README describing its purpose, invariants, boundaries, god-file policy, layout, and consumers, including the mirroring of HookEvent with stella-core::hooks::HookEvent.
  • Add stella-plugin to the workspace members list in the root Cargo.toml and configure serde/toml/thiserror dependencies plus serde_json as a dev-dependency.
  • Update AGENTS.md to increase crate and non-god-file counts and add a row describing stella-plugin’s remit as a leaf manifest-parsing crate.
crates/stella-plugin/README.md
Cargo.toml
AGENTS.md

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

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