Skip to content

angee.integrate.models

Source models for Angee's integration runtime primitives.

This addon owns the integration layer end to end. The connection substrate — the OAuthClient registration, the user's ExternalAccount at a provider, and the per-user Credential material — authenticates everything above it. On top of that sit the third-party Vendor catalogue, the first-class Integration an integration runs over, concrete child integration kinds such as VcsBridge, addon-owned children such as agents.InferenceProvider, the host-agnostic VCS inventory (VcsBridge + Repository/Source/ Template), and outbound WebhookSubscription.

This addon is pure OAuth: it connects out to external systems and never authenticates a session. OIDC login fields and ID-token verification live one level up in iam_integrate_oidc, which extends this OAuth base and composes the iam user. Host-specific VCS backends live in their own addons (integrate_github) and are named per VcsBridge.backend_class row; this addon never imports them.

AccountStatus

python
class AccountStatus(models.TextChoices)

Connection lifecycle for a linked external account.

Pure connection health — does the account's credential still work. The integration implementation health lives on Integration, not here.

CredentialStatus

python
class CredentialStatus(models.TextChoices)

Lifecycle state for per-user credential material.

OAuthClientQuerySet

python
class OAuthClientQuerySet(RebacQuerySet[Any])

REBAC-scoped reads for OAuth client registration.

connectable

python
def connectable() -> OAuthClientQuerySet

Return enabled, client-configured OAuth clients for the connect picker.

console_oauth_clients

python
def console_oauth_clients() -> OAuthClientQuerySet

Return admin-visible OAuth clients (self-describing; no vendor join).

enabled_for_slug

python
def enabled_for_slug(slug: str, *, environment: str = "prod") -> Any | None

Return the preferred OAuth client for a slug when that row is enabled.

OAuthClientManager

python
class OAuthClientManager(RebacManager.from_queryset(OAuthClientQuerySet))

Manager for settings-sourced OAuth client registration.

OAuth only: settings entries carry the OAuth base fields. The OIDC refinement (issuer/JWKS/discovery + login policy) is owned by the iam_integrate_oidc addon and seeded there, never from here.

sync_from_settings

python
def sync_from_settings(
    entries: Iterable[Mapping[str, Any]] | Mapping[str, Mapping[str, Any]]
    | None = None
) -> tuple[Any, ...]

Create or update OAuth clients declared in settings.ANGEE_INTEGRATE_OAUTH_CLIENTS.

The host owns reading environment variables. Integrate reads only Django settings and keeps secrets out of resource files.

OAuthClient

python
class OAuthClient(SqidMixin, ImplDefaultsMixin, AuditMixin, AngeeModel)

OAuth2 client registration for connecting an external account.

The base of the connection substrate: enough to run the authorization-code and refresh flows and act against a provider's API (Gemini, Grok, Anthropic). It carries no identity or login policy itself — a provider that also authenticates a login gains direct fields from the iam_integrate_oidc extension when that addon is installed.

Self-describing: slug is its own connect-client key and icon/ display_name its own button branding. The third-party catalogue is integrate.Vendor; that slug is a deliberately independent namespace, not a foreign key into this one.

provider_type

Provider preset key whose defaults seed this OAuth client.

userinfo_endpoint

Access-token-protected profile endpoint used to label a connected account (read through the claim mappings below). Plain OAuth, not OIDC: a connect-only provider populates its ExternalAccount from here without any ID token.

Meta

python
class Meta()

Django model options for OAuth clients.

__str__

python
def __str__() -> str

Return the configured OAuth client display name or slug environment.

configuration_state

python
@property
def configuration_state() -> str

Return this OAuth client's operator-facing configuration readiness.

default_scope_values

python
@property
def default_scope_values() -> list[str]

Return the configured default OAuth scopes as strings.

scopes_catalogue_values

python
@property
def scopes_catalogue_values() -> list[str]

Return the advertised OAuth scopes as strings.

authorize_param_values

python
@property
def authorize_param_values() -> dict[str, str]

Return configured provider-specific authorize parameters.

token_param_values

python
@property
def token_param_values() -> dict[str, str]

Return configured provider-specific token-exchange parameters.

resolve_connect_redirect

python
def resolve_connect_redirect(proposed_redirect_uri: str) -> tuple[str, str]

Return the (redirect_uri, mode) this client uses to connect from a browser.

mode is "auto" (the provider redirects back to the returned redirect) or "manual" (the user copies the code the provider displays and pastes it back). A client with no manual_redirect_uri always redirects back to the browser-proposed redirect. A fixed public client (manual_redirect_uri set) has an allow-list we cannot extend: on localhost it can round-trip only to the loopback path its allow-list registers, so the proposed redirect's path is replaced with loopback_redirect_path (origin preserved); off-localhost — or with no loopback path declared — its allow-list rejects the redirect and the cross-origin callback would drop the session, so it falls back to manual paste.

token_request_format_value

python
@property
def token_request_format_value() -> str

Return the configured token request body format.

external_id_from_claims

python
def external_id_from_claims(claims: Mapping[str, Any]) -> str

Return this provider account's stable external id from profile claims.

email_from_claims

python
def email_from_claims(claims: Mapping[str, Any]) -> str

Return this provider account's email from profile claims.

display_name_from_claims

python
def display_name_from_claims(claims: Mapping[str, Any], email: str) -> str

Return this provider account's display label from profile claims.

avatar_url_from_claims

python
def avatar_url_from_claims(claims: Mapping[str, Any]) -> str

Return this provider account's avatar URL from profile claims.

fill_endpoints_from_discovery

python
def fill_endpoints_from_discovery(discovery: Mapping[str, Any]) -> bool

Fill this client's blank endpoints from a discovery document; return whether changed.

The client owns projecting a discovery document onto its own endpoint fields, so a caller (the OIDC protocol/discovery action) never reaches in to set them by name.

fill_extension_fields_from_discovery

python
def fill_extension_fields_from_discovery(discovery: Mapping[str, Any]) -> bool

Hook for composed extensions to project discovery onto their own fields.

discover_endpoints

python
def discover_endpoints() -> Mapping[str, Any]

Fetch discovery and fill blank OAuth/extension endpoints on this row.

ExternalAccountQuerySet

python
class ExternalAccountQuerySet(RebacQuerySet[Any])

REBAC-scoped reads for external accounts.

console_external_accounts

python
def console_external_accounts() -> ExternalAccountQuerySet

Return admin-visible external accounts with guarded FK joins.

ExternalAccountManager

python
class ExternalAccountManager(
        RebacManager.from_queryset(ExternalAccountQuerySet))

Manager for idempotent external account linking.

Actor-less framework writes run under system_context; update paths do not maintain updated_by.

python
def link(oauth_client: Any,
         external_id: str,
         *,
         owner: Any | None = None,
         **identity: Any) -> Any

Create or update one (oauth_client, external_id) external account.

grant_owner

python
def grant_owner(account: Any, owner: Any) -> None

Grant owner direct ownership of an external account.

revoke_owner

python
def revoke_owner(account: Any, owner: Any) -> None

Revoke owner direct ownership of an external account.

owner_for

python
def owner_for(account: Any) -> Any | None

Return the user granted owner on account, if one exists.

ExternalAccount

python
class ExternalAccount(SqidMixin, AuditMixin, AngeeModel)

A user's identity at a provider, shared by principals through REBAC grants.

Connection identity only: which client minted it (oauth_client), which external subject (external_id), and the credential that authenticates as it. The integration that runs over a connection lives in integrate.Integration, which owns implementation health.

Meta

python
class Meta()

Django model options for external accounts.

__str__

python
def __str__() -> str

Return a stable provider-qualified account label.

credential_status

python
@property
def credential_status() -> str

Return the current OAuth credential status, if this account has one.

provider_slug

python
@property
def provider_slug() -> str

Return the originating OAuth client's slug.

provider_environment

python
@property
def provider_environment() -> str

Return the originating OAuth client's environment.

provider_label

python
@property
def provider_label() -> str

Return the originating OAuth client's display label.

provider_icon

python
@property
def provider_icon() -> str

Return the originating OAuth client's branding icon.

display_name_from_claims

python
@staticmethod
def display_name_from_claims(claims: Mapping[str, Any], email: str) -> str

Return the best display label from verified identity claims.

CredentialQuerySet

python
class CredentialQuerySet(RebacQuerySet[Any])

REBAC-scoped reads for credential health and connected accounts.

connected_for

python
def connected_for(user: Any) -> CredentialQuerySet

Return user's external-account-backed credentials.

Each row is a credential whose external_account (and that account's credential) is preloaded under the ambient actor's scope. Owns the "what counts as a connected account" predicate for the self-service connected-accounts surface.

console_credentials

python
def console_credentials() -> CredentialQuerySet

Return admin-visible credential health with guarded FK joins.

CredentialManager

python
class CredentialManager(RebacManager.from_queryset(CredentialQuerySet))

Manager for idempotent per-user credential writes.

Actor-less framework writes run under system_context; update paths do not maintain updated_by.

live_oauth_for_user

python
def live_oauth_for_user(user: Any, oauth_client: Any) -> Any | None

Return this user's active, non-expired OAuth credential for one client.

upsert_for_user

python
def upsert_for_user(user: Any,
                    oauth_client: Any,
                    kind: str,
                    material: dict[str, Any],
                    *,
                    external_account: Any | None = None,
                    **fields: Any) -> Any

Create or update one (user, oauth_client) OAuth credential (connect/login flow).

create_local_credential

python
def create_local_credential(user: Any, *, kind: str, name: str,
                            material: dict[str, Any], **fields: Any) -> Any

Create or update one provider-less (static/ssh) credential, keyed by (user, name).

The provider-less counterpart to :meth:upsert_for_user: oauth_client is NULL (the kind/provider invariant forbids one), and name is the credential's identity. OAuth credentials are minted by the connect/login flow only.

Credential

python
class Credential(SqidMixin, AuditMixin, AngeeModel)

Per-user credential material for acting against a vendor OAuth client.

oauth_client

The provider this credential authenticates to — required for oauth (its identity is the provider account), optional for local kinds (static_token/ssh_key), which may be created without one.

name

Human credential label; provider-backed rows are named on create, local rows use it as their per-user identity.

Meta

python
class Meta()

Django model options for credentials.

handler

python
@property
def handler() -> Any

Return the registered handler for this credential kind.

reveal

python
def reveal() -> dict[str, Any]

Return decrypted material through the kind handler.

auth_headers

python
def auth_headers() -> dict[str, str]

Return authorization headers through the kind handler.

secret_value

python
def secret_value() -> str

Return the primary secret value through the kind handler.

display_name

python
@model_property(only=["name", "oauth_client_id", "external_account_id"], )
def display_name() -> str

Return a human label for lists, headers, and relation pickers.

connected_display_name

python
@property
def connected_display_name() -> str

Return a public-safe label for the current user's connected account.

ensure_fresh

python
def ensure_fresh() -> None

Renew this credential's token in place when it is near expiry and can refresh.

A best-effort freshening hook for a server-side consumer about to use the secret (e.g. syncing it into a provisioned agent): an OAuth credential whose access token is near expiry is renewed through its provider refresh grant; every other case — a non-expiring local token, a still-valid token, or a credential with no refresh grant — is a no-op. The refresh is serialized and re-checked under a row lock (:meth:_refresh_locked), so racing consumers issue at most one network refresh. A provider rejecting the refresh is recorded (last_refresh_status="failed") and logged, not raised, so it never blocks the consumer (the stale token then fails downstream as it would have anyway); unexpected errors propagate.

refresh_now

python
def refresh_now() -> None

Force a provider refresh now for an interactive caller, raising on failure.

The explicit counterpart to :meth:ensure_fresh: a console refresh action renews the token regardless of how much life it has left and surfaces the outcome instead of swallowing it. Requires a refresh-capable provider and a stored refresh token — an expired access token still refreshes, since the grant uses the refresh token; otherwise raises ValueError telling the caller to reconnect. A provider rejecting the grant records last_refresh_status as "failed" and re-raises (OAuthFlowError) so the caller can report it. Serialized under the same row lock as :meth:ensure_fresh.

Vendor

python
class Vendor(SqidMixin, AuditMixin, AngeeModel)

Admin-managed third-party catalogue (GitHub, Google, Slack, …).

The single source of truth for "what is this third party" — branding and reference metadata only. New integration addons add their own row via an install-tier resource seed (adopt: slug). The connect-side OAuthClient carries its own slug; that is a deliberately independent namespace, not a foreign key into this catalogue.

Meta

python
class Meta()

Django model options for integration vendors.

__str__

python
def __str__() -> str

Return the display label used by Django surfaces.

IntegrationStatus

python
class IntegrationStatus(models.TextChoices)

Lifecycle state for one integration implementation.

from_value

python
@classmethod
def from_value(cls, value: object) -> IntegrationStatus

Return the member for one string or enum integration-status value.

IntegrationManager

python
class IntegrationManager(AngeeManager)

Manager factories for invariants that span Integration and its impl row.

impl_class_for_key

python
def impl_class_for_key(key: str) -> type[IntegrationImpl]

Return the implementation class registered for key on this model.

sync_kinds

python
def sync_kinds() -> int

Backfill parent rows with the concrete integration kind they materialize.

Integration

python
class Integration(SqidMixin, ImplDefaultsMixin, AuditMixin, AngeeModel)

A product/workspace integration to a vendor account.

The first-class "what we're connected to and what runs over it": it draws a credential (and optionally an account) from the connection substrate to authenticate, points at a catalogue vendor, and stores the implementation key that owns integration-level behavior. Domain-specific state and config live on concrete child models.

integration_kind_label

Human kind label for parent-level integration grouping.

kind

Human integration type/kind label, denormalized for server-side grouping.

impl_class

Registry key for the implementation this integration runs.

Meta

python
class Meta()

Django model options for integrations.

__str__

python
def __str__() -> str

Return a stable vendor-qualified integration label.

integration_kind_value

python
@classmethod
def integration_kind_value(cls) -> str

Return the grouping label this integration concrete model contributes.

save

python
def save(*args: Any, **kwargs: Any) -> None

Persist the parent grouping kind when a concrete child row saves.

display_label

python
@property
def display_label() -> str

Return the operator label, or a vendor-derived one when none was given.

Headers, lists, and relation pickers read this so a named integration shows its name and an unnamed one still reads as Vendor (status).

impl

python
@property
def impl() -> IntegrationImpl

Return this row's integration-level implementation.

attach_credential

python
def attach_credential(credential: Any) -> None

Attach a live credential and activate this draft integration.

report_status

python
def report_status(status: IntegrationStatus | str, error: str = "") -> None

Record implementation status telemetry and persist this integration.

Bridge

python
class Bridge(AngeeModel)

Abstract base for child models that synchronize or subscribe to vendor data.

Pure bridge state and behavior. A materialized bridge extends integrate.Integration so common identity, credential, status, and audit fields stay on the integration parent row while bridge-specific settings stay on the child.

config

Bridge-scoped settings used by the selected VCS backend.

Meta

python
class Meta()

Django model options for abstract bridge inheritance.

mark_sync_started

python
def mark_sync_started(*, now: datetime) -> None

Persist the start timestamp for one scheduler sync attempt.

record_sync

python
def record_sync(result: int, *, now: datetime) -> None

Persist one successful scheduler sync result and healthy status report.

record_sync_error

python
def record_sync_error(error: Exception, *, now: datetime) -> None

Persist one failed scheduler sync result and error status report.

run_sync

python
def run_sync(*, now: datetime) -> int

Run one sync attempt and persist its lifecycle telemetry.

sync

python
def sync() -> int

Synchronize this bridge with its external system.

handle_webhook

python
def handle_webhook(payload: Any) -> None

Apply one verified inbound webhook payload to this bridge.

verify_webhook

python
def verify_webhook(request: Any) -> bool

Return whether an inbound webhook request is authentic for this bridge.

dispatch_inbound

python
def dispatch_inbound(request_or_payload: Any) -> bool

Verify one inbound webhook and apply it to this bridge when authentic.

start_live

python
def start_live() -> None

Start or renew this bridge's live vendor subscription.

stop_live

python
def stop_live() -> None

Stop this bridge's live vendor subscription.

RepoVisibility

python
class RepoVisibility(models.TextChoices)

Visibility of a git remote on its host.

VcsBridge

python
class VcsBridge(Bridge)

The VCS sync child model over Integration.

A :class:Bridge: the scheduler refreshes its repositories' sources over the host REST API and an inbound push webhook triggers the same refresh. The host-specific wire format is the integration child row's non-model :class:~angee.integrate.vcs.backend.VCSBackend implementation — so github/gitlab/bitbucket share this one table, differing only in behavior. Django keeps the inventory only; the operator performs every git operation, consuming :meth:Source.materialize_spec.

backend_class

Registry key for the VCS backend bound to this bridge.

webhook_secret

Shared secret for verifying inbound push webhooks (per account, not per repo).

Meta

python
class Meta()

Django model options for the VCS bridge child model.

backend

python
@property
def backend() -> VCSBackend

Return this bridge's selected VCS backend.

repositories_by_org

python
def repositories_by_org() -> dict[str, list[Any]]

Return every visible repository grouped and sorted by owning org.

discover

python
def discover(source: Any, *, marker: str,
             parse: Callable[[bytes], dict[str, Any]]) -> list[dict[str, Any]]

Return one descriptor per directory under source bearing marker.

The single enumeration walk shared by every source kind: list the source's subtree, read each marker blob, parse it, record the bearing directory, and return the descriptors in deterministic order. A source kind's output manager supplies only its marker filename and parse function.

sync

python
def sync() -> int

Refresh every inventoried repository's sources over REST (Bridge contract).

Repository discovery (creating rows from the account) is the explicit discoverRepositories action; the scheduled/webhook sync refreshes the content of already-inventoried repositories.

handle_webhook

python
def handle_webhook(payload: Any) -> None

Re-sync this bridge's inventory on an inbound push webhook.

verify_webhook

python
def verify_webhook(request: Any) -> bool

Return whether an inbound push webhook is authentic for this bridge.

search_repositories

python
def search_repositories(query: str) -> list[Any]

Return host repositories whose name matches query (the add typeahead).

import_repository

python
def import_repository(name: str) -> Any

Inventory one repository by its host name (a picked typeahead result).

discover_repositories

python
def discover_repositories(*, org: str = "") -> int

Inventory every repository the account exposes (bulk import; prunes vanished).

RepositoryManager

python
class RepositoryManager(RebacManager)

Manager owning the upsert/reconcile of repository rows from a host listing.

reconcile

python
def reconcile(vcs_bridge: Any, descriptors: Iterable[Any]) -> int

Upsert one repository row per descriptor and prune rows that vanished.

Bulk import for discoverRepositories: prunes against the full listing, so the caller must pass every repository (see GitHubBackend.ls_repos pagination), never a partial page.

add

python
def add(vcs_bridge: Any, descriptor: Any) -> Any

Inventory one repository (no prune) — the typeahead "add this repo" path.

Repository

python
class Repository(SqidMixin, AuditMixin, AngeeModel)

Inventory of one git remote, reached through its VcsBridge.

A plain noun: Django records the remote; the operator clones it. org groups the account's repositories in the browse list.

name

The repository's owner/repo path on its remote host.

remote

The HTTPS remote URL the operator clones.

Meta

python
class Meta()

Django model options for repository inventory.

__str__

python
def __str__() -> str

Return the repository's host path.

Source

python
class Source(SqidMixin, AuditMixin, AngeeModel)

A pointer into a Repository at a ref and path, with a kind.

One noun for every source kind. kind binds the source to an output model (Template/Skill) whose manager reconciles its rows; :meth:refresh dispatches there. The operator materializes a source from :meth:materialize_spec.

kind

The source kind (e.g. template, skill); resolves to an output model.

ref

Branch, tag, or commit oid; blank resolves to the repository's default branch.

path

Pathspec of the subtree this source points at within the repository.

Meta

python
class Meta()

Django model options for source inventory.

__str__

python
def __str__() -> str

Return a kind-qualified source label.

kind_models

python
@classmethod
def kind_models(cls) -> tuple[type[models.Model], ...]

Return the output models that declare a source_kind (e.g. Template).

Source owns "what a kind resolves to": an output model binds itself to a kind with a source_kind class attribute, discovered through the app registry so a new addon adds a kind without integrate changing.

available_kinds

python
@classmethod
def available_kinds(cls) -> tuple[str, ...]

Return the source kinds any installed addon contributes an output model for.

target_for_kind

python
@classmethod
def target_for_kind(cls, kind: str) -> type[models.Model]

Return the output model bound to one source kind or raise.

refresh

python
def refresh() -> int

Re-enumerate over REST into the kind's output rows; return the row count.

materialize_spec

python
def materialize_spec() -> dict[str, str]

Return the operator handoff coordinates to clone and check out this source.

TemplateManager

python
class TemplateManager(RebacManager)

Manager owning the reconcile of template rows from a template source.

sync_from_source

python
def sync_from_source(source: Any) -> int

Walk the source for copier.yml and upsert/prune Template rows.

Template

python
class Template(SqidMixin, AuditMixin, AngeeModel)

One Copier template discovered under a Source (source_kind="template").

The operator renders these; the kind here is the template kind from the manifest's _angee.kind (stack/workspace/service).

source_kind

Binds the template source kind to this output model (see registry).

kind

The template kind from _angee.kind (stack/workspace/service).

Meta

python
class Meta()

Django model options for discovered templates.

__str__

python
def __str__() -> str

Return a kind-qualified template label.

WebhookSubscriptionManager

python
class WebhookSubscriptionManager(RebacManager)

Manager for webhook subscriptions.

deliver_event

python
def deliver_event(*,
                  kind: EventKind,
                  payload: Any,
                  impl_app: str = "",
                  integration: Any | None = None) -> dict[str, int]

Deliver one integration event to every matching enabled subscription.

Actor-less framework fan-out: it reads subscriptions across all owners, so it runs under system_context. Each subscription matches and delivers itself; this method only owns the row-set loop and the success/error tally.

WebhookSubscription

python
class WebhookSubscription(SqidMixin, AuditMixin, AngeeModel)

Outbound webhook endpoint owned by one user.

Meta

python
class Meta()

Django model options for webhook subscriptions.

matches

python
def matches(*, kind: str, impl_app: str, integration: Any | None) -> bool

Return whether this subscription should receive one event.

deliver

python
def deliver(body: bytes) -> str

POST one signed event body to this subscription's pinned target; raise on non-2xx.

deliver_recorded

python
def deliver_recorded(body: bytes) -> tuple[bool, str]

Deliver one body, persist telemetry, and return (ok, status_or_error).

deliver_test

python
def deliver_test() -> tuple[bool, str]

Send a test event, persist telemetry, and return an action result tuple.

rotate_secret

python
def rotate_secret() -> str

Generate a new signing secret, persist it, and return the plaintext once.

The subscription owns its signing material: a console action calls this to roll the secret. The plaintext is returned only here (for one-time display); reads never expose it.

record_delivery

python
def record_delivery(status: str) -> None

Persist success telemetry for one delivery attempt (mirrors Bridge.record_sync).

record_delivery_failure

python
def record_delivery_failure(*, status: str, error: str) -> None

Persist failure telemetry for one delivery attempt (mirrors Bridge.record_sync_error).

Takes the already-classified status/error: the delivery layer owns turning a delivery exception into those strings.

Released under the AGPL-3.0 License.