Skip to content

Schemas

The Pydantic models that define install.yaml (xcore_agent.schema.install) and manifest.json (xcore_agent.schema.manifest).

install.yaml — install plan & steps

xcore_agent.schema.install

Schema and validation for install.yaml — the deployment plan shipped inside a .xdeploy artifact.

Every step's action is restricted to a fixed, closed enum of verbs the agent knows how to execute safely. There is intentionally no generic "run a shell command" action: a malicious or tampered artifact must not be able to turn xcore-agent into an arbitrary remote-execution primitive.

Step = Annotated[Union[PrepareStep, DownloadStep, ExtractStep, ProvisionStep, InstallPluginStep, InstallExtensionStep, ConfigurePluginStep, WriteEnvStep, NotifyStep, StartStep, StopStep, RestartStep, HealthcheckStep, RollbackStep], Field(discriminator='action')] module-attribute

PrepareStep

Bases: _StepBase

Source code in xcore_agent/schema/install.py
class PrepareStep(_StepBase):
    action: Literal["prepare"] = "prepare"
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
action: Literal['prepare'] = 'prepare' class-attribute instance-attribute

DownloadStep

Bases: _StepBase

Source code in xcore_agent/schema/install.py
class DownloadStep(_StepBase):
    action: Literal["download"] = "download"
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
action: Literal['download'] = 'download' class-attribute instance-attribute

ExtractStep

Bases: _StepBase

Source code in xcore_agent/schema/install.py
class ExtractStep(_StepBase):
    action: Literal["extract"] = "extract"
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
action: Literal['extract'] = 'extract' class-attribute instance-attribute

ProvisionStep

Bases: _PluginStepBase

Source code in xcore_agent/schema/install.py
class ProvisionStep(_PluginStepBase):
    action: Literal["provision"] = "provision"
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
plugin: str instance-attribute
action: Literal['provision'] = 'provision' class-attribute instance-attribute

InstallPluginStep

Bases: _PluginStepBase

Source code in xcore_agent/schema/install.py
class InstallPluginStep(_PluginStepBase):
    action: Literal["install_plugin"] = "install_plugin"
    # Where to fetch this plugin's code from — marketplace slug (preferred)
    # or git (fallback), same `PluginSource` the packer would otherwise read
    # off the plugin's own plugin.yaml (see packer.builder._read_plugin_
    # source) or `.xcore-registry.json` (_read_registry_source). Declaring
    # it here instead keeps plugin.yaml itself untouched — a project that
    # wants its deployment-time origins centralized in one reviewable file
    # (this one) rather than scattered across every plugin's own manifest.
    # Checked first when the packer resolves a plugin's source at build
    # time (see write_manifest); plugin.yaml's own `source:` and the
    # registry are still consulted, in that order, if this step has none.
    source: PluginSource | None = None
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
plugin: str instance-attribute
action: Literal['install_plugin'] = 'install_plugin' class-attribute instance-attribute
source: PluginSource | None = None class-attribute instance-attribute

InstallExtensionStep

Bases: _ExtensionStepBase

Source code in xcore_agent/schema/install.py
class InstallExtensionStep(_ExtensionStepBase):
    action: Literal["install_extension"] = "install_extension"
    # Mirrors InstallPluginStep.source, for extensions — see its docstring.
    source: PluginSource | None = None
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
extension: str instance-attribute
action: Literal['install_extension'] = 'install_extension' class-attribute instance-attribute
source: PluginSource | None = None class-attribute instance-attribute

NotifyStep

Bases: _StepBase

Tells the agent's notify() a named event happened at this point in the plan — never a URL/webhook/recipient itself (same reasoning as ProvisionStep.plugin: the artifact only supplies an opaque label, the real destination is host-side operator config, see agent.notifiers). A missing or failing notifier never fails the deployment — notifying is a side channel, not part of what makes an install succeed or fail.

Source code in xcore_agent/schema/install.py
class NotifyStep(_StepBase):
    """Tells the agent's `notify()` a named event happened at this point in
    the plan — never a URL/webhook/recipient itself (same reasoning as
    `ProvisionStep.plugin`: the artifact only supplies an opaque label, the
    real destination is host-side operator config, see `agent.notifiers`).
    A missing or failing notifier never fails the deployment — notifying is
    a side channel, not part of what makes an install succeed or fail."""

    action: Literal["notify"] = "notify"
    event: str
    # Optional human-readable text for the notifier to use as-is, e.g.
    # "auth deployed successfully" — not a template, no placeholder
    # substitution happens on it.
    message: str | None = None

    @field_validator("event")
    @classmethod
    def _valid_event(cls, v: str) -> str:
        if not _ID_RE.match(v):
            raise ValueError(f"invalid notify event {v!r}: must match {_ID_RE.pattern}")
        return v
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
action: Literal['notify'] = 'notify' class-attribute instance-attribute
event: str instance-attribute
message: str | None = None class-attribute instance-attribute

ConfigurePluginStep

Bases: _PluginStepBase

Source code in xcore_agent/schema/install.py
class ConfigurePluginStep(_PluginStepBase):
    action: Literal["configure_plugin"] = "configure_plugin"
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
plugin: str instance-attribute
action: Literal['configure_plugin'] = 'configure_plugin' class-attribute instance-attribute

WriteEnvStep

Bases: _PluginStepBase

Source code in xcore_agent/schema/install.py
class WriteEnvStep(_PluginStepBase):
    action: Literal["write_env"] = "write_env"
    from_: str = Field(..., alias="from")

    @field_validator("from_")
    @classmethod
    def _relative_path_only(cls, v: str) -> str:
        if v.startswith("/") or v.startswith("~") or ".." in v.split("/"):
            raise ValueError(f"'from' must be a relative path inside the artifact, got {v!r}")
        return v
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
plugin: str instance-attribute
action: Literal['write_env'] = 'write_env' class-attribute instance-attribute
from_: str = Field(..., alias='from') class-attribute instance-attribute

StartStep

Bases: _StepBase

Source code in xcore_agent/schema/install.py
class StartStep(_StepBase):
    action: Literal["start"] = "start"
    plugin: str | None = None
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
action: Literal['start'] = 'start' class-attribute instance-attribute
plugin: str | None = None class-attribute instance-attribute

StopStep

Bases: _StepBase

Source code in xcore_agent/schema/install.py
class StopStep(_StepBase):
    action: Literal["stop"] = "stop"
    plugin: str | None = None
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
action: Literal['stop'] = 'stop' class-attribute instance-attribute
plugin: str | None = None class-attribute instance-attribute

RestartStep

Bases: _StepBase

Source code in xcore_agent/schema/install.py
class RestartStep(_StepBase):
    action: Literal["restart"] = "restart"
    plugin: str | None = None
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
action: Literal['restart'] = 'restart' class-attribute instance-attribute
plugin: str | None = None class-attribute instance-attribute

HealthcheckStep

Bases: _StepBase

Source code in xcore_agent/schema/install.py
class HealthcheckStep(_StepBase):
    action: Literal["healthcheck"] = "healthcheck"
    plugin: str | None = None
    timeout_seconds: int = Field(default=30, gt=0, le=600, alias="timeout")
    retries: int = Field(default=3, ge=0, le=20)

    @field_validator("timeout_seconds", mode="before")
    @classmethod
    def _parse_duration(cls, v: object) -> object:
        if isinstance(v, str):
            return _parse_duration_string(v)
        return v
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
action: Literal['healthcheck'] = 'healthcheck' class-attribute instance-attribute
plugin: str | None = None class-attribute instance-attribute
timeout_seconds: int = Field(default=30, gt=0, le=600, alias='timeout') class-attribute instance-attribute
retries: int = Field(default=3, ge=0, le=20) class-attribute instance-attribute

RollbackStep

Bases: _StepBase

Source code in xcore_agent/schema/install.py
class RollbackStep(_StepBase):
    action: Literal["rollback"] = "rollback"
    to: str | None = None
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
depends_on: list[str] = Field(default_factory=list) class-attribute instance-attribute
snapshot: bool = False class-attribute instance-attribute
action: Literal['rollback'] = 'rollback' class-attribute instance-attribute
to: str | None = None class-attribute instance-attribute

InstallPlan

Bases: BaseModel

Parsed, validated install.yaml.

Source code in xcore_agent/schema/install.py
class InstallPlan(BaseModel):
    """Parsed, validated `install.yaml`."""

    model_config = {"extra": "forbid"}

    format_version: Literal["1"]
    project_id: str
    version: str
    steps: list[Step] = Field(..., min_length=1)

    @model_validator(mode="after")
    def _validate_graph(self) -> "InstallPlan":
        seen: set[str] = set()
        for step in self.steps:
            if step.id in seen:
                raise ValueError(f"duplicate step id: {step.id!r}")
            seen.add(step.id)

        for step in self.steps:
            for dep in step.depends_on:
                if dep not in seen:
                    raise ValueError(f"step {step.id!r} depends_on unknown step {dep!r}")
                if dep == step.id:
                    raise ValueError(f"step {step.id!r} cannot depend on itself")

        # Raises on cycles; result is reused by execution_order() at call time
        # rather than cached here, since it's cheap and keeps the model simple.
        _topological_order([s.id for s in self.steps], {s.id: s.depends_on for s in self.steps})
        return self

    def execution_order(self) -> list[str]:
        """Return step ids in an order that respects every `depends_on` edge."""
        return _topological_order(
            [s.id for s in self.steps], {s.id: s.depends_on for s in self.steps}
        )

    def step(self, step_id: str) -> "Step":
        for s in self.steps:
            if s.id == step_id:
                return s
        raise KeyError(step_id)
model_config = {'extra': 'forbid'} class-attribute instance-attribute
format_version: Literal['1'] instance-attribute
project_id: str instance-attribute
version: str instance-attribute
steps: list[Step] = Field(..., min_length=1) class-attribute instance-attribute
execution_order() -> list[str]

Return step ids in an order that respects every depends_on edge.

Source code in xcore_agent/schema/install.py
def execution_order(self) -> list[str]:
    """Return step ids in an order that respects every `depends_on` edge."""
    return _topological_order(
        [s.id for s in self.steps], {s.id: s.depends_on for s in self.steps}
    )
step(step_id: str) -> Step
Source code in xcore_agent/schema/install.py
def step(self, step_id: str) -> "Step":
    for s in self.steps:
        if s.id == step_id:
            return s
    raise KeyError(step_id)

manifest.json — project & plugin manifest

xcore_agent.schema.manifest

Schema for manifest.json — the plaintext description of a .xdeploy artifact's contents, hashed and referenced by the outer signature so the agent can verify what it received matches what was built, independently of the encryption layer.

EnvironmentSpec

Bases: BaseModel

A plugin's declared .env contract — which variables the host operator must fill in before the plugin can start. write_env (agent/install_driver.py) checks required against the actual env file after seeding it from the template.

Source code in xcore_agent/schema/manifest.py
class EnvironmentSpec(BaseModel):
    """A plugin's declared `.env` contract — which variables the host
    operator must fill in before the plugin can start. `write_env`
    (agent/install_driver.py) checks `required` against the actual env
    file after seeding it from the template."""

    model_config = {"extra": "forbid"}

    required: list[str] = Field(default_factory=list)
    optional: list[str] = Field(default_factory=list)
model_config = {'extra': 'forbid'} class-attribute instance-attribute
required: list[str] = Field(default_factory=list) class-attribute instance-attribute
optional: list[str] = Field(default_factory=list) class-attribute instance-attribute

PluginSource

Bases: BaseModel

Where to fetch a plugin's code from instead of (or in addition to) what's embedded in the .xdeploy artifact — typically handed out by a marketplace/registry as a resolvable link.

Exactly one origin, not both:

  • Marketplace (preferred)marketplace_slug (+ optional marketplace_version/marketplace_kind). Resolved at deploy time via the real xcore-team/marketplace GET /{slug}/install endpoint (agent.marketplace_client.MarketplaceClient), whose response is HMAC-SHA256-signed — see plugin_resolver.PluginResolver._resolve_ marketplace. This is xcli's default when it writes .xcore- registry.json for a plugin installed from the marketplace (see xcli's shared.record_install): the marketplace is the authoritative origin for anything published there, not an alternative to git.
  • Git (fallback)url + ref, for a plugin never published to the marketplace (an operator's own private fork, something still under development). ref should be a commit SHA whenever integrity matters: it's the only form that's content-addressed, so pinning to one lets PluginRef.sha256 (computed over the resolved tree) actually mean something. A branch or tag is mutable — the code behind it can change without sha256 in the manifest ever being updated, silently defeating the tamper check.

A marketplace-sourced plugin gets an equivalent integrity guarantee for free from the HMAC signature itself (verified against the publisher's signing_secret on every fetch), independently of whether PluginRef. sha256 is also pinned.

Source code in xcore_agent/schema/manifest.py
class PluginSource(BaseModel):
    """Where to fetch a plugin's code from instead of (or in addition to)
    what's embedded in the `.xdeploy` artifact — typically handed out by a
    marketplace/registry as a resolvable link.

    Exactly one origin, not both:

    - **Marketplace (preferred)** — `marketplace_slug` (+ optional
      `marketplace_version`/`marketplace_kind`). Resolved at deploy time via
      the real xcore-team/marketplace `GET /{slug}/install` endpoint
      (`agent.marketplace_client.MarketplaceClient`), whose response is
      HMAC-SHA256-signed — see `plugin_resolver.PluginResolver._resolve_
      marketplace`. This is `xcli`'s default when it writes `.xcore-
      registry.json` for a plugin installed *from* the marketplace (see
      `xcli`'s `shared.record_install`): the marketplace is the
      authoritative origin for anything published there, not an
      alternative to git.
    - **Git (fallback)** — `url` + `ref`, for a plugin never published to
      the marketplace (an operator's own private fork, something still
      under development). `ref` should be a commit SHA whenever integrity
      matters: it's the only form that's content-addressed, so pinning to
      one lets `PluginRef.sha256` (computed over the resolved tree) actually
      mean something. A branch or tag is mutable — the code behind it can
      change without `sha256` in the manifest ever being updated, silently
      defeating the tamper check.

    A marketplace-sourced plugin gets an equivalent integrity guarantee for
    free from the HMAC signature itself (verified against the publisher's
    `signing_secret` on every fetch), independently of whether `PluginRef.
    sha256` is also pinned.
    """

    model_config = {"extra": "forbid"}

    marketplace_slug: str | None = None
    marketplace_version: str = "latest"
    marketplace_kind: Literal["plugin", "service"] = "plugin"

    url: str | None = None
    ref: str | None = None
    subdirectory: str | None = None

    @model_validator(mode="after")
    def _exactly_one_origin(self) -> "PluginSource":
        has_marketplace = self.marketplace_slug is not None
        has_git = self.url is not None
        if has_marketplace == has_git:  # both set, or neither
            raise ValueError(
                "PluginSource needs exactly one origin: either 'marketplace_slug' "
                "(preferred — resolved from the marketplace) or 'url' (+'ref', git "
                "fallback for a plugin not published there), not both or neither"
            )
        if has_git and self.ref is None:
            raise ValueError("git source ('url') requires 'ref'")
        return self
model_config = {'extra': 'forbid'} class-attribute instance-attribute
marketplace_slug: str | None = None class-attribute instance-attribute
marketplace_version: str = 'latest' class-attribute instance-attribute
marketplace_kind: Literal['plugin', 'service'] = 'plugin' class-attribute instance-attribute
url: str | None = None class-attribute instance-attribute
ref: str | None = None class-attribute instance-attribute
subdirectory: str | None = None class-attribute instance-attribute

PluginRef

Bases: BaseModel

Source code in xcore_agent/schema/manifest.py
class PluginRef(BaseModel):
    model_config = {"extra": "forbid"}

    id: str
    version: str
    # Required for an embedded plugin (hash of its files inside the
    # artifact). Optional for a `source`-based plugin: the packer doesn't
    # fetch external repositories at build time, so it has nothing to hash
    # unless the caller pins one out of band. When present, the agent still
    # verifies it against the resolved tree after fetching — see
    # agent/pipeline.py's plugin resolution stage.
    sha256: str | None = None
    environment: EnvironmentSpec | None = None
    source: PluginSource | None = None

    @field_validator("id")
    @classmethod
    def _valid_id(cls, v: str) -> str:
        if not _PLUGIN_ID_RE.match(v):
            raise ValueError(f"invalid plugin id {v!r}")
        return v

    @field_validator("version")
    @classmethod
    def _valid_version(cls, v: str) -> str:
        if not _SEMVER_RE.match(v):
            raise ValueError(f"invalid semantic version {v!r}")
        return v

    @field_validator("sha256")
    @classmethod
    def _valid_sha256(cls, v: str | None) -> str | None:
        return v if v is None else _validate_sha256(v)

    @model_validator(mode="after")
    def _require_hash_for_embedded_plugins(self) -> "PluginRef":
        if self.source is None and self.sha256 is None:
            raise ValueError(
                f"plugin {self.id!r} has no 'source' — it's embedded in the artifact, "
                "so 'sha256' is required"
            )
        return self
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
version: str instance-attribute
sha256: str | None = None class-attribute instance-attribute
environment: EnvironmentSpec | None = None class-attribute instance-attribute
source: PluginSource | None = None class-attribute instance-attribute

ExtensionRef

Bases: BaseModel

A shared, non-plugin service bundled into the artifact (e.g. extensions/xmailler) — embedded by default, OR resolved from git at deploy time via source (see extensions/<id>/extension.yamlPluginSource reused verbatim; the field name stays source for symmetry with PluginRef.source, there's nothing plugin-specific about it). Same rule as PluginRef: sha256 is required unless source is set — nothing to hash for a repo the packer never fetches at build time.

Source code in xcore_agent/schema/manifest.py
class ExtensionRef(BaseModel):
    """A shared, non-plugin service bundled into the artifact (e.g.
    `extensions/xmailler`) — embedded by default, OR resolved from git at
    deploy time via `source` (see `extensions/<id>/extension.yaml` —
    `PluginSource` reused verbatim; the field name stays `source` for
    symmetry with `PluginRef.source`, there's nothing plugin-specific about
    it). Same rule as `PluginRef`: `sha256` is required unless `source` is
    set — nothing to hash for a repo the packer never fetches at build time."""

    model_config = {"extra": "forbid"}

    id: str
    sha256: str | None = None
    source: PluginSource | None = None

    @field_validator("id")
    @classmethod
    def _valid_id(cls, v: str) -> str:
        if not _PLUGIN_ID_RE.match(v):
            raise ValueError(f"invalid extension id {v!r}")
        return v

    @field_validator("sha256")
    @classmethod
    def _valid_sha256(cls, v: str | None) -> str | None:
        return v if v is None else _validate_sha256(v)

    @model_validator(mode="after")
    def _require_hash_for_embedded_extensions(self) -> "ExtensionRef":
        if self.source is None and self.sha256 is None:
            raise ValueError(
                f"extension {self.id!r} has no 'source' — it's embedded in the artifact, "
                "so 'sha256' is required"
            )
        return self
model_config = {'extra': 'forbid'} class-attribute instance-attribute
id: str instance-attribute
sha256: str | None = None class-attribute instance-attribute
source: PluginSource | None = None class-attribute instance-attribute

ProjectManifest

Bases: BaseModel

Describes one built version of a project's .xdeploy artifact.

Source code in xcore_agent/schema/manifest.py
class ProjectManifest(BaseModel):
    """Describes one built version of a project's `.xdeploy` artifact."""

    model_config = {"extra": "forbid"}

    format_version: str = Field(..., pattern=r"^\d+$")
    project_id: str
    project_name: str
    version: str
    built_at: datetime
    plugins: list[PluginRef] = Field(..., min_length=1)
    # Optional and separate from `plugins`: a project with no extensions/
    # directory at all is the common case, not an error (unlike plugins,
    # where an empty list is rejected in write_manifest — see builder.py).
    extensions: list[ExtensionRef] = Field(default_factory=list)
    # Which top-level directory `plugins` were embedded under inside this
    # artifact — read at build time from the source project's own
    # `integration.yaml` (`plugins.directory`, e.g. `./app`), so a project
    # that doesn't use the `plugins/` convention still round-trips through
    # build -> deploy correctly. Defaults to "plugins" so an artifact built
    # before this field existed (or a project that never overrides the
    # default) still parses the same as always — see packer/builder.py and
    # agent/pipeline.py/install_driver.py for where it's read back.
    plugins_dirname: str = "plugins"
    content_sha256: str

    @field_validator("plugins_dirname")
    @classmethod
    def _valid_plugins_dirname(cls, v: str) -> str:
        if not _PLUGINS_DIRNAME_RE.match(v):
            raise ValueError(f"invalid plugins_dirname {v!r}")
        return v

    @field_validator("project_id")
    @classmethod
    def _valid_project_id(cls, v: str) -> str:
        if not _PROJECT_ID_RE.match(v):
            raise ValueError(f"invalid project id {v!r}")
        return v

    @field_validator("version")
    @classmethod
    def _valid_version(cls, v: str) -> str:
        if not _SEMVER_RE.match(v):
            raise ValueError(f"invalid semantic version {v!r}")
        return v

    @field_validator("content_sha256")
    @classmethod
    def _valid_content_sha256(cls, v: str) -> str:
        return _validate_sha256(v)

    def plugin(self, plugin_id: str) -> PluginRef:
        for p in self.plugins:
            if p.id == plugin_id:
                return p
        raise KeyError(plugin_id)

    def extension(self, extension_id: str) -> ExtensionRef:
        for e in self.extensions:
            if e.id == extension_id:
                return e
        raise KeyError(extension_id)
model_config = {'extra': 'forbid'} class-attribute instance-attribute
format_version: str = Field(..., pattern='^\\d+$') class-attribute instance-attribute
project_id: str instance-attribute
project_name: str instance-attribute
version: str instance-attribute
built_at: datetime instance-attribute
plugins: list[PluginRef] = Field(..., min_length=1) class-attribute instance-attribute
extensions: list[ExtensionRef] = Field(default_factory=list) class-attribute instance-attribute
plugins_dirname: str = 'plugins' class-attribute instance-attribute
content_sha256: str instance-attribute
plugin(plugin_id: str) -> PluginRef
Source code in xcore_agent/schema/manifest.py
def plugin(self, plugin_id: str) -> PluginRef:
    for p in self.plugins:
        if p.id == plugin_id:
            return p
    raise KeyError(plugin_id)
extension(extension_id: str) -> ExtensionRef
Source code in xcore_agent/schema/manifest.py
def extension(self, extension_id: str) -> ExtensionRef:
    for e in self.extensions:
        if e.id == extension_id:
            return e
    raise KeyError(extension_id)