Skip to content

Deployment Capability

DeploymentCapability is the contract base for a capability that acts on a deployable repository: enumerate refs and commits, compare them, cut tags and releases, and trigger CI workflow runs. Bind it with a Capability(kind='deployment', handler=...) in the plugin's manifest. Deployment is typically paired with an identity capability (set requires_identity=True) so deploy actions run as the human user rather than a shared service principal.

Surfaces: ui, api.

See Authoring Plugins for the manifest, capabilities, context, credential decryption, and error conventions shared by every plugin.

from imbi.common.plugins import (
    CompareResult,
    Commit,
    DeploymentCapability,
    DeploymentRun,
    PluginContext,
    Ref,
)


class GitHubDeployment(DeploymentCapability):
    async def list_refs(
        self,
        ctx: PluginContext,
        credentials: dict[str, str],
        kind: str = 'all',
        query: str | None = None,
    ) -> list[Ref]:
        ...

    async def list_commits(
        self,
        ctx: PluginContext,
        credentials: dict[str, str],
        ref: str,
        limit: int = 25,
    ) -> list[Commit]:
        ...

    async def resolve_committish(
        self,
        ctx: PluginContext,
        credentials: dict[str, str],
        committish: str,
    ) -> Commit:
        ...

    async def compare(
        self,
        ctx: PluginContext,
        credentials: dict[str, str],
        base: str,
        head: str,
    ) -> CompareResult:
        ...

    async def trigger_deployment(
        self,
        ctx: PluginContext,
        credentials: dict[str, str],
        ref_or_sha: str,
        inputs: dict[str, str] | None = None,
    ) -> DeploymentRun:
        ...

    async def get_deployment_status(
        self,
        ctx: PluginContext,
        credentials: dict[str, str],
        run_id: str,
    ) -> DeploymentRun:
        ...

Required methods

  • list_refs — enumerate branches / tags / the default ref, optionally filtered by kind and a query substring.
  • list_commits — list commits reachable from ref, newest first.
  • resolve_committish — hydrate a single branch / tag / SHA into a Commit.
  • compare — return a CompareResult for base..head (ahead / behind counts, commits, diffstat).
  • trigger_deployment — dispatch a CI workflow / pipeline run for ref_or_sha with optional inputs, returning a DeploymentRun.
  • get_deployment_status — poll a previously triggered run by run_id.

Optional methods

These default to returning 'unknown' or None, or raising NotImplementedError; implement only what the remote supports.

  • get_check_status — aggregate CI check-runs into a single CheckStatus (pass / fail / warn / unknown) for a ref. Powers the release-train green/red dot.
  • create_tag / create_release — mint an annotated tag and a release on the remote; required only for the Promote flow.
  • list_workflows — list CI workflow files so the UI can populate a workflow dropdown when an operator wires up the per-environment dispatch edge.
  • list_recent_deployments — return the most recent deployments per environment for the resync flow that backfills Release nodes and DEPLOYED_TO edges when webhook delivery has lapsed. Capabilities that set the supports_deployment_sync hint must implement this; each returned RemoteDeployment must carry a stable external_run_id so the host can dedupe. Status values use the host's canonical DeploymentEventStatus vocabulary (pending, in_progress, success, failed, rolled_back). To attribute the deployer to an Imbi user, populate creator (the remote login, for display) and creator_subject (the remote's stable identity subject — e.g. the numeric GitHub user id); the host resolves the latter through the Integration's identity capability. When the deployment targets a tagged release, populate release_notes with the release's notes body (e.g. a GitHub release's "What's Changed" markdown); the host persists it as the Release node's notes, distinct from the short description deploy note.
  • get_environment_state — report which deployment is active per environment, as the provider sees it. The currency counterpart to list_recent_deployments, which only answers "what happened recently": the newest attempt may have failed, still be in flight, or have been superseded, leaving an older deployment live. Each returned EnvironmentDeploymentState carries active, latest (the newest attempt of any status), and an active_resolution of found / none / unknown / error. unknown (a bounded scan stopped before exhausting the result set) and error (the provider call failed) both mean "retain the pointer", and neither may be reported as none — clearing a correct pointer is the failure mode this method exists to prevent. Optional even for capabilities that set supports_deployment_sync; hosts probe by catching NotImplementedError.
  • get_release_notes — return the remote release's notes body for a given tag. The tag-keyed counterpart to list_recent_deployments' release_notes field: it lets the host enrich a Release node's notes on paths that only know the tag — a webhook that created the release from a deployment event (which carries no body), or a resync whose deployment ref was a raw SHA. Best-effort: capabilities without a release concept, or that cannot resolve one for the tag, return None (the default) so the host never fails a write on a missing or unreadable release.

Hints

  • supports_deployment_sync — the capability implements list_recent_deployments for the resync flow.
  • cacheable — the host may cache reads from this capability.

When a deployment call creates, renames, or discovers the project's canonical repository URL, set ctx.link_writeback so the host self-heals the stored project link. See Plugin Context.

API reference

DeploymentCapability

Bases: CapabilityHandler

Act on a repo: enumerate refs/commits, compare them, create tags/releases, and trigger CI workflow runs.

create_deployment_artifact async

create_deployment_artifact(
    ctx: PluginContext,
    credentials: dict[str, str],
    ref: str,
    version: str,
    inputs: dict[str, str] | None = None,
) -> ArtifactRun

Start the build that produces the deployable artifact.

The step :meth:trigger_deployment currently assumes has already happened. ref is the committish to build from and version the release identity the build is expected to publish under -- the remote build is what cuts the tag and pushes any version-bump commits, so neither exists yet when this returns.

Optional and deliberately separate from :meth:trigger_deployment: the two are distinct stages with host handling between them, not two halves of one call. Returns as soon as the build is accepted; the host observes completion via :meth:get_artifact_run_status (or a webhook) and only then proceeds to deploy.

Capabilities whose remote has no build concept raise :class:NotImplementedError.

Source code in libraries/common/src/imbi/common/plugins/base.py
async def create_deployment_artifact(
    self,
    ctx: PluginContext,
    credentials: dict[str, str],
    ref: str,
    version: str,
    inputs: dict[str, str] | None = None,
) -> ArtifactRun:
    """Start the build that *produces* the deployable artifact.

    The step :meth:`trigger_deployment` currently assumes has already
    happened.  ``ref`` is the committish to build from and ``version``
    the release identity the build is expected to publish under -- the
    remote build is what cuts the tag and pushes any version-bump
    commits, so neither exists yet when this returns.

    Optional and deliberately separate from
    :meth:`trigger_deployment`: the two are distinct stages with host
    handling between them, not two halves of one call.  Returns as
    soon as the build is *accepted*; the host observes completion via
    :meth:`get_artifact_run_status` (or a webhook) and only then
    proceeds to deploy.

    Capabilities whose remote has no build concept raise
    :class:`NotImplementedError`.
    """
    del ctx, credentials, ref, version, inputs
    raise NotImplementedError

create_release async

create_release(
    ctx: PluginContext,
    credentials: dict[str, str],
    tag: str,
    name: str,
    body_markdown: str,
    prerelease: bool = False,
) -> ReleaseInfo

Create a release on the remote (e.g. a GitHub Release).

Optional — paired with :meth:create_tag. Capabilities without a release concept raise :class:NotImplementedError.

Source code in libraries/common/src/imbi/common/plugins/base.py
async def create_release(
    self,
    ctx: PluginContext,
    credentials: dict[str, str],
    tag: str,
    name: str,
    body_markdown: str,
    prerelease: bool = False,
) -> ReleaseInfo:
    """Create a release on the remote (e.g. a GitHub Release).

    Optional — paired with :meth:`create_tag`.  Capabilities without a
    release concept raise :class:`NotImplementedError`.
    """
    raise NotImplementedError

create_tag async

create_tag(
    ctx: PluginContext,
    credentials: dict[str, str],
    sha: str,
    tag: str,
    message: str,
) -> RefInfo

Create an annotated tag on the remote.

Optional — only required for the Promote flow. Capabilities that cannot mint tags raise :class:NotImplementedError; the host surfaces the error to the caller.

Source code in libraries/common/src/imbi/common/plugins/base.py
async def create_tag(
    self,
    ctx: PluginContext,
    credentials: dict[str, str],
    sha: str,
    tag: str,
    message: str,
) -> RefInfo:
    """Create an annotated tag on the remote.

    Optional — only required for the Promote flow.  Capabilities that
    cannot mint tags raise :class:`NotImplementedError`; the host
    surfaces the error to the caller.
    """
    raise NotImplementedError

diff_commit_notes async

diff_commit_notes(
    ctx: PluginContext,
    credentials: dict[str, str],
    namespace: str,
    before: str,
    after: str,
) -> dict[str, str | None]

Diff a notes ref between two of its own commits.

before and after are commits of the notes ref (what a push event to refs/notes/<namespace> reports), not annotated commits. Returns a map of annotated commit SHA (full, fan-out subtrees flattened) to the note's new body -- None for a note the push removed. An all-zero before means the ref was just created; implementations return every note in after.

Optional -- same contract as :meth:get_commit_note.

Source code in libraries/common/src/imbi/common/plugins/base.py
async def diff_commit_notes(
    self,
    ctx: PluginContext,
    credentials: dict[str, str],
    namespace: str,
    before: str,
    after: str,
) -> dict[str, str | None]:
    """Diff a notes ref between two of its own commits.

    ``before`` and ``after`` are commits *of the notes ref* (what a
    push event to ``refs/notes/<namespace>`` reports), not annotated
    commits.  Returns a map of annotated commit SHA (full, fan-out
    subtrees flattened) to the note's new body -- ``None`` for a
    note the push removed.  An all-zero ``before`` means the ref was
    just created; implementations return every note in ``after``.

    Optional -- same contract as :meth:`get_commit_note`.
    """
    del ctx, credentials, namespace, before, after
    raise NotImplementedError

get_artifact_run_status async

get_artifact_run_status(
    ctx: PluginContext,
    credentials: dict[str, str],
    run_id: str,
) -> ArtifactRun

Report a build run's status.

The :class:ArtifactRun counterpart to :meth:get_deployment_status; run_id is what :meth:create_deployment_artifact returned, resolved against the remote's build endpoint rather than its deployment one.

Optional -- paired with :meth:create_deployment_artifact.

Source code in libraries/common/src/imbi/common/plugins/base.py
async def get_artifact_run_status(
    self,
    ctx: PluginContext,
    credentials: dict[str, str],
    run_id: str,
) -> ArtifactRun:
    """Report a build run's status.

    The :class:`ArtifactRun` counterpart to
    :meth:`get_deployment_status`; ``run_id`` is what
    :meth:`create_deployment_artifact` returned, resolved against the
    remote's *build* endpoint rather than its deployment one.

    Optional -- paired with :meth:`create_deployment_artifact`.
    """
    del ctx, credentials, run_id
    raise NotImplementedError

get_check_status async

get_check_status(
    ctx: PluginContext,
    credentials: dict[str, str],
    committish: str,
) -> CheckStatus

Aggregate CI check-runs status for a ref / SHA / tag.

Optional — used by the release-train read path to surface a green/red dot per env's currently-deployed version. Capabilities without a CI concept return 'unknown' (the default).

Source code in libraries/common/src/imbi/common/plugins/base.py
async def get_check_status(
    self,
    ctx: PluginContext,
    credentials: dict[str, str],
    committish: str,
) -> CheckStatus:
    """Aggregate CI check-runs status for a ref / SHA / tag.

    Optional — used by the release-train read path to surface a
    green/red dot per env's currently-deployed version.  Capabilities
    without a CI concept return ``'unknown'`` (the default).
    """
    del ctx, credentials, committish
    return 'unknown'

get_commit_note async

get_commit_note(
    ctx: PluginContext,
    credentials: dict[str, str],
    namespace: str,
    committish: str,
) -> str | None

Return the git note on committish in namespace.

namespace names the notes ref without its refs/notes/ prefix (e.g. imbi-drift for refs/notes/imbi-drift). committish may be short; the notes tree is keyed by the full SHA, so implementations resolve it first. Returns the note's raw body, or None when the ref does not exist or carries no note for the commit -- both mean "no note", which is a real answer, not a failure.

Optional -- capabilities without a git-notes concept raise :class:NotImplementedError so hosts can tell "no note" apart from "cannot answer".

Source code in libraries/common/src/imbi/common/plugins/base.py
async def get_commit_note(
    self,
    ctx: PluginContext,
    credentials: dict[str, str],
    namespace: str,
    committish: str,
) -> str | None:
    """Return the git note on ``committish`` in ``namespace``.

    ``namespace`` names the notes ref without its ``refs/notes/``
    prefix (e.g. ``imbi-drift`` for ``refs/notes/imbi-drift``).
    ``committish`` may be short; the notes tree is keyed by the full
    SHA, so implementations resolve it first.  Returns the note's
    raw body, or ``None`` when the ref does not exist or carries no
    note for the commit -- both mean "no note", which is a real
    answer, not a failure.

    Optional -- capabilities without a git-notes concept raise
    :class:`NotImplementedError` so hosts can tell "no note" apart
    from "cannot answer".
    """
    del ctx, credentials, namespace, committish
    raise NotImplementedError

get_environment_state async

get_environment_state(
    ctx: PluginContext,
    credentials: dict[str, str],
    environments: list[str],
) -> list[EnvironmentDeploymentState]

Report which deployment is active per environment.

The currency counterpart to :meth:list_recent_deployments: that method answers "what happened recently", this one answers "what is serving the environment now", which an attempt list cannot -- the newest attempt may have failed, still be in flight, or have been superseded, leaving an older deployment as the live one.

Optional even for capabilities that advertise supports_deployment_sync: hosts probe by calling it and treating :class:NotImplementedError as "this provider cannot report currency", falling back to the attempt list alone. Returns one :class:EnvironmentDeploymentState per requested environment, never dropping one, because the host has to distinguish "not answered" from "nothing deployed".

An environment the remote does not recognise resolves unknown, NOT none. The host passes its own environment slugs through unmapped, so a slug the provider has never heard of is indistinguishable from one with no deployments -- and none authorizes the host to clear its current-release pointer. Only positive evidence earns it: deployments read, none of them serving.

Implementations walk the provider newest-first under a bounded cap. Reaching the cap without an answer is unknown; a read that failed -- the listing errored, or a row's status would not load -- is error. Both retain the host's pointer, so the distinction is not about safety but about diagnosis: reporting a provider outage as unknown files it as a scan-limit result and hides it from the operator, and only error reaches summary.errors. Neither may be reported as none. See :class:EnvironmentDeploymentState for the invariants each resolution carries.

Source code in libraries/common/src/imbi/common/plugins/base.py
async def get_environment_state(
    self,
    ctx: PluginContext,
    credentials: dict[str, str],
    environments: list[str],
) -> list[EnvironmentDeploymentState]:
    """Report which deployment is *active* per environment.

    The currency counterpart to :meth:`list_recent_deployments`: that
    method answers "what happened recently", this one answers "what is
    serving the environment now", which an attempt list cannot -- the
    newest attempt may have failed, still be in flight, or have been
    superseded, leaving an older deployment as the live one.

    Optional even for capabilities that advertise
    ``supports_deployment_sync``: hosts probe by calling it and
    treating :class:`NotImplementedError` as "this provider cannot
    report currency", falling back to the attempt list alone.  Returns
    one :class:`EnvironmentDeploymentState` per requested environment,
    never dropping one, because the host has to distinguish "not
    answered" from "nothing deployed".

    An environment the remote does not recognise resolves ``unknown``,
    NOT ``none``.  The host passes its own environment slugs through
    unmapped, so a slug the provider has never heard of is
    indistinguishable from one with no deployments -- and ``none``
    authorizes the host to clear its current-release pointer.  Only
    positive evidence earns it: deployments read, none of them
    serving.

    Implementations walk the provider newest-first under a bounded
    cap.  Reaching the cap without an answer is ``unknown``; a read
    that *failed* -- the listing errored, or a row's status would not
    load -- is ``error``.  Both retain the host's pointer, so the
    distinction is not about safety but about diagnosis: reporting a
    provider outage as ``unknown`` files it as a scan-limit result
    and hides it from the operator, and only ``error`` reaches
    ``summary.errors``.  Neither may be reported as ``none``.  See
    :class:`EnvironmentDeploymentState` for the invariants each
    resolution carries.
    """
    del ctx, credentials, environments
    raise NotImplementedError

get_release async

get_release(
    ctx: PluginContext,
    credentials: dict[str, str],
    tag: str,
) -> RemoteRelease | None

Return the remote release for tag, metadata included.

The richer counterpart to :meth:get_release_notes: same lookup, but it also carries who cut the release on the remote, its title, and its URL, so the host can attribute a release it only observed to a person instead of to the process that recorded it.

Optional. Capabilities without a release concept, or that cannot resolve one for tag, return None (the default); hosts fall back to :meth:get_release_notes for the body alone.

Source code in libraries/common/src/imbi/common/plugins/base.py
async def get_release(
    self,
    ctx: PluginContext,
    credentials: dict[str, str],
    tag: str,
) -> RemoteRelease | None:
    """Return the remote release for ``tag``, metadata included.

    The richer counterpart to :meth:`get_release_notes`: same lookup,
    but it also carries who cut the release on the remote, its title,
    and its URL, so the host can attribute a release it only observed
    to a person instead of to the process that recorded it.

    Optional. Capabilities without a release concept, or that cannot
    resolve one for ``tag``, return ``None`` (the default); hosts fall
    back to :meth:`get_release_notes` for the body alone.
    """
    del ctx, credentials, tag
    return None

get_release_notes async

get_release_notes(
    ctx: PluginContext,
    credentials: dict[str, str],
    tag: str,
) -> str | None

Return the remote release's notes body for tag.

Optional -- lets the host enrich a Release node's notes with the remote release body (e.g. a GitHub release's "What's Changed" markdown) on paths that only know the tag, such as a webhook that created the release from a deployment event (which carries no body) or a resync whose deployment ref was a raw SHA. This is the tag-keyed counterpart to the release_notes field :meth:list_recent_deployments populates from a deployment's ref.

Best-effort: capabilities without a release concept, or that cannot resolve one for tag, return None (the default) so the host never fails a write on a missing or unreadable release.

Source code in libraries/common/src/imbi/common/plugins/base.py
async def get_release_notes(
    self,
    ctx: PluginContext,
    credentials: dict[str, str],
    tag: str,
) -> str | None:
    """Return the remote release's notes body for ``tag``.

    Optional -- lets the host enrich a ``Release`` node's notes with
    the remote release body (e.g. a GitHub release's "What's Changed"
    markdown) on paths that only know the tag, such as a webhook that
    created the release from a deployment event (which carries no
    body) or a resync whose deployment ``ref`` was a raw SHA.  This is
    the tag-keyed counterpart to the ``release_notes`` field
    :meth:`list_recent_deployments` populates from a deployment's ref.

    Best-effort: capabilities without a release concept, or that
    cannot resolve one for ``tag``, return ``None`` (the default) so
    the host never fails a write on a missing or unreadable release.
    """
    del ctx, credentials, tag
    return None

list_commit_notes async

list_commit_notes(
    ctx: PluginContext,
    credentials: dict[str, str],
    namespace: str,
    skip_shas: Collection[str] = (),
) -> NotesListing

Return every note in namespace, keyed by annotated SHA.

The whole-ref counterpart to :meth:diff_commit_notes, for a host backfilling notes it never saw a push for. Keys are full SHAs with fan-out subtrees flattened, exactly as :meth:diff_commit_notes returns them; a missing ref is an empty listing, not an error, because "no notes" is a real answer.

skip_shas names annotated commits the host already holds an answer for. Enumerating the ref is a call or two; reading the bodies is one per note, so a host repairing a gap would pay for the whole history to learn about the handful of notes it is missing. Implementations must still enumerate the ref -- the skip applies to reading bodies, and skipped notes are absent from the map rather than present with a None body, which means "removed".

complete says whether every note the ref holds is either in the map or was skipped on request. It has to be reported rather than inferred: a host cannot tell a ref with three notes from a ref with four whose fourth could not be read, and treating the second as the whole truth would record a partial backfill as a finished one.

Optional -- same contract as :meth:get_commit_note.

Source code in libraries/common/src/imbi/common/plugins/base.py
async def list_commit_notes(
    self,
    ctx: PluginContext,
    credentials: dict[str, str],
    namespace: str,
    skip_shas: collections.abc.Collection[str] = (),
) -> NotesListing:
    """Return every note in ``namespace``, keyed by annotated SHA.

    The whole-ref counterpart to :meth:`diff_commit_notes`, for a
    host backfilling notes it never saw a push for.  Keys are full
    SHAs with fan-out subtrees flattened, exactly as
    :meth:`diff_commit_notes` returns them; a missing ref is an empty
    listing, not an error, because "no notes" is a real answer.

    ``skip_shas`` names annotated commits the host already holds an
    answer for.  Enumerating the ref is a call or two; reading the
    bodies is one *per note*, so a host repairing a gap would pay for
    the whole history to learn about the handful of notes it is
    missing.  Implementations must still enumerate the ref -- the
    skip applies to reading bodies, and skipped notes are absent from
    the map rather than present with a ``None`` body, which means
    "removed".

    ``complete`` says whether every note the ref holds is either in
    the map or was skipped on request.  It has to be reported rather
    than inferred: a host cannot tell a ref with three notes from a
    ref with four whose fourth could not be read, and treating the
    second as the whole truth would record a partial backfill as a
    finished one.

    Optional -- same contract as :meth:`get_commit_note`.
    """
    del ctx, credentials, namespace, skip_shas
    raise NotImplementedError

list_recent_deployments async

list_recent_deployments(
    ctx: PluginContext,
    credentials: dict[str, str],
    environments: list[str],
    limit: int = 1,
) -> list[RemoteDeployment]

Return the most recent limit deployments per environment.

Optional -- powers the deployment resync flow that backfills Release nodes and DEPLOYED_TO edges when webhook delivery has lapsed. Capabilities that advertise the supports_deployment_sync hint MUST implement this; others raise :class:NotImplementedError (the host treats that as "skip resync").

environments is the remote-facing list of environment names the host wants populated, in the project's preferred order. Capabilities should ignore environments their remote does not know about (rather than raising) so a partial resync still succeeds. Returned events MUST carry a stable external_run_id so the host can dedupe. When a deployment targets a tagged release, populate release_notes with the release's notes body (distinct from the short description deploy note) so the host can persist it as the Release node's notes.

Source code in libraries/common/src/imbi/common/plugins/base.py
async def list_recent_deployments(
    self,
    ctx: PluginContext,
    credentials: dict[str, str],
    environments: list[str],
    limit: int = 1,
) -> list[RemoteDeployment]:
    """Return the most recent ``limit`` deployments per environment.

    Optional -- powers the deployment resync flow that backfills
    ``Release`` nodes and ``DEPLOYED_TO`` edges when webhook delivery
    has lapsed.  Capabilities that advertise the
    ``supports_deployment_sync`` hint MUST implement this; others
    raise :class:`NotImplementedError` (the host treats that as
    "skip resync").

    ``environments`` is the remote-facing list of environment names
    the host wants populated, in the project's preferred order.
    Capabilities should ignore environments their remote does not know
    about (rather than raising) so a partial resync still succeeds.
    Returned events MUST carry a stable ``external_run_id`` so the
    host can dedupe.  When a deployment targets a tagged release,
    populate ``release_notes`` with the release's notes body (distinct
    from the short ``description`` deploy note) so the host can persist
    it as the ``Release`` node's notes.
    """
    del ctx, credentials, environments, limit
    raise NotImplementedError

list_workflows async

list_workflows(
    ctx: PluginContext, credentials: dict[str, str]
) -> list[WorkflowFile]

List CI workflow files defined in the project's remote repo.

Optional — used by the UI to populate a workflow dropdown when an operator configures assignment env_payloads. Capabilities without a workflow concept raise :class:NotImplementedError.

Source code in libraries/common/src/imbi/common/plugins/base.py
async def list_workflows(
    self,
    ctx: PluginContext,
    credentials: dict[str, str],
) -> list[WorkflowFile]:
    """List CI workflow files defined in the project's remote repo.

    Optional — used by the UI to populate a workflow dropdown when an
    operator configures assignment ``env_payloads``.  Capabilities
    without a workflow concept raise :class:`NotImplementedError`.
    """
    del ctx, credentials
    raise NotImplementedError

Ref

Bases: BaseModel

A branch, tag, or default-ref pointer on a deployable repo.

Commit

Bases: BaseModel

A commit on a deployable repo, hydrated for UI display.

CompareResult

Bases: BaseModel

Result of comparing two commit-ish refs (base..head).

RefInfo

Bases: BaseModel

Metadata returned after creating a ref (e.g. an annotated tag).

ReleaseInfo

Bases: BaseModel

Metadata returned after creating a release on the remote.

WorkflowFile

Bases: BaseModel

A CI workflow file discoverable in the project's remote repo.

Returned by :meth:DeploymentCapability.list_workflows so the UI can populate a workflow dropdown when an operator wires up the per-environment dispatch edge. id is the remote's stable identifier (e.g. the GitHub workflow id); path is the repo- relative file path; name is the human label from the workflow file's name: field.

DeploymentRun

Bases: BaseModel

A workflow / pipeline run triggered by trigger_deployment.

RemoteDeployment

Bases: BaseModel

A deployment observed on the remote for resync.

Returned by :meth:DeploymentCapability.list_recent_deployments so the host can backfill Release nodes and DEPLOYED_TO edges when webhook delivery has lapsed or when bringing a project online for the first time. environment is the remote's environment name as reported (callers map it to the project's local environment slug). sha is the commit the deployment was created against; ref is the human-facing label the deploy was triggered with (branch / tag / SHA prefix) and may be None for older deploys. external_run_id MUST be a stable identifier (e.g. the GitHub deployment id) so the host can dedupe re-runs of resync without appending duplicate events.

EnvironmentDeploymentState

Bases: BaseModel

What the remote says is deployed to one environment now.

Returned by :meth:DeploymentCapability.get_environment_state so the host can reconcile its "current release" pointer against the provider instead of inferring currency from the newest deployment attempt it happens to have observed.

active_resolution is what makes the answer actionable, and its four outcomes are deliberately distinct:

  • found -- active is not None: the provider reports this deployment as the one serving the environment.
  • none -- the provider's result set was exhausted and no deployment qualifies; active is None. The host may clear its pointer.
  • unknown -- the bounded scan stopped before exhausting the result set, so an older active deployment may exist unseen; active is None but the host MUST retain its pointer.
  • error -- the provider call failed. Also "retain the pointer", but it needs a different operational response than unknown: "we scanned the cap" and "the remote returned 503" are not the same problem.

latest -- the newest attempt of any status -- may be populated for every outcome, error included when the failure happened after the listing was read. It is never a substitute for active: an in-flight or failed attempt is activity, not currency.