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
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
class CredentialStatus(models.TextChoices)Lifecycle state for per-user credential material.
OAuthClientQuerySet
class OAuthClientQuerySet(RebacQuerySet[Any])REBAC-scoped reads for OAuth client registration.
connectable
def connectable() -> OAuthClientQuerySetReturn enabled, client-configured OAuth clients for the connect picker.
console_oauth_clients
def console_oauth_clients() -> OAuthClientQuerySetReturn admin-visible OAuth clients (self-describing; no vendor join).
enabled_for_slug
def enabled_for_slug(slug: str, *, environment: str = "prod") -> Any | NoneReturn the preferred OAuth client for a slug when that row is enabled.
OAuthClientManager
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
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
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
class Meta()Django model options for OAuth clients.
__str__
def __str__() -> strReturn the configured OAuth client display name or slug environment.
configuration_state
@property
def configuration_state() -> strReturn this OAuth client's operator-facing configuration readiness.
default_scope_values
@property
def default_scope_values() -> list[str]Return the configured default OAuth scopes as strings.
scopes_catalogue_values
@property
def scopes_catalogue_values() -> list[str]Return the advertised OAuth scopes as strings.
authorize_param_values
@property
def authorize_param_values() -> dict[str, str]Return configured provider-specific authorize parameters.
token_param_values
@property
def token_param_values() -> dict[str, str]Return configured provider-specific token-exchange parameters.
resolve_connect_redirect
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
@property
def token_request_format_value() -> strReturn the configured token request body format.
external_id_from_claims
def external_id_from_claims(claims: Mapping[str, Any]) -> strReturn this provider account's stable external id from profile claims.
email_from_claims
def email_from_claims(claims: Mapping[str, Any]) -> strReturn this provider account's email from profile claims.
display_name_from_claims
def display_name_from_claims(claims: Mapping[str, Any], email: str) -> strReturn this provider account's display label from profile claims.
avatar_url_from_claims
def avatar_url_from_claims(claims: Mapping[str, Any]) -> strReturn this provider account's avatar URL from profile claims.
fill_endpoints_from_discovery
def fill_endpoints_from_discovery(discovery: Mapping[str, Any]) -> boolFill 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
def fill_extension_fields_from_discovery(discovery: Mapping[str, Any]) -> boolHook for composed extensions to project discovery onto their own fields.
discover_endpoints
def discover_endpoints() -> Mapping[str, Any]Fetch discovery and fill blank OAuth/extension endpoints on this row.
ExternalAccountQuerySet
class ExternalAccountQuerySet(RebacQuerySet[Any])REBAC-scoped reads for external accounts.
console_external_accounts
def console_external_accounts() -> ExternalAccountQuerySetReturn admin-visible external accounts with guarded FK joins.
ExternalAccountManager
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.
link
def link(oauth_client: Any,
external_id: str,
*,
owner: Any | None = None,
**identity: Any) -> AnyCreate or update one (oauth_client, external_id) external account.
grant_owner
def grant_owner(account: Any, owner: Any) -> NoneGrant owner direct ownership of an external account.
revoke_owner
def revoke_owner(account: Any, owner: Any) -> NoneRevoke owner direct ownership of an external account.
owner_for
def owner_for(account: Any) -> Any | NoneReturn the user granted owner on account, if one exists.
ExternalAccount
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
class Meta()Django model options for external accounts.
__str__
def __str__() -> strReturn a stable provider-qualified account label.
credential_status
@property
def credential_status() -> strReturn the current OAuth credential status, if this account has one.
provider_slug
@property
def provider_slug() -> strReturn the originating OAuth client's slug.
provider_environment
@property
def provider_environment() -> strReturn the originating OAuth client's environment.
provider_label
@property
def provider_label() -> strReturn the originating OAuth client's display label.
provider_icon
@property
def provider_icon() -> strReturn the originating OAuth client's branding icon.
display_name_from_claims
@staticmethod
def display_name_from_claims(claims: Mapping[str, Any], email: str) -> strReturn the best display label from verified identity claims.
CredentialQuerySet
class CredentialQuerySet(RebacQuerySet[Any])REBAC-scoped reads for credential health and connected accounts.
connected_for
def connected_for(user: Any) -> CredentialQuerySetReturn 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
def console_credentials() -> CredentialQuerySetReturn admin-visible credential health with guarded FK joins.
CredentialManager
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
def live_oauth_for_user(user: Any, oauth_client: Any) -> Any | NoneReturn this user's active, non-expired OAuth credential for one client.
upsert_for_user
def upsert_for_user(user: Any,
oauth_client: Any,
kind: str,
material: dict[str, Any],
*,
external_account: Any | None = None,
**fields: Any) -> AnyCreate or update one (user, oauth_client) OAuth credential (connect/login flow).
create_local_credential
def create_local_credential(user: Any, *, kind: str, name: str,
material: dict[str, Any], **fields: Any) -> AnyCreate 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
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
class Meta()Django model options for credentials.
handler
@property
def handler() -> AnyReturn the registered handler for this credential kind.
reveal
def reveal() -> dict[str, Any]Return decrypted material through the kind handler.
auth_headers
def auth_headers() -> dict[str, str]Return authorization headers through the kind handler.
secret_value
def secret_value() -> strReturn the primary secret value through the kind handler.
display_name
@model_property(only=["name", "oauth_client_id", "external_account_id"], )
def display_name() -> strReturn a human label for lists, headers, and relation pickers.
connected_display_name
@property
def connected_display_name() -> strReturn a public-safe label for the current user's connected account.
ensure_fresh
def ensure_fresh() -> NoneRenew 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
def refresh_now() -> NoneForce 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
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
class Meta()Django model options for integration vendors.
__str__
def __str__() -> strReturn the display label used by Django surfaces.
IntegrationStatus
class IntegrationStatus(models.TextChoices)Lifecycle state for one integration implementation.
from_value
@classmethod
def from_value(cls, value: object) -> IntegrationStatusReturn the member for one string or enum integration-status value.
IntegrationManager
class IntegrationManager(AngeeManager)Manager factories for invariants that span Integration and its impl row.
impl_class_for_key
def impl_class_for_key(key: str) -> type[IntegrationImpl]Return the implementation class registered for key on this model.
sync_kinds
def sync_kinds() -> intBackfill parent rows with the concrete integration kind they materialize.
Integration
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
class Meta()Django model options for integrations.
__str__
def __str__() -> strReturn a stable vendor-qualified integration label.
integration_kind_value
@classmethod
def integration_kind_value(cls) -> strReturn the grouping label this integration concrete model contributes.
save
def save(*args: Any, **kwargs: Any) -> NonePersist the parent grouping kind when a concrete child row saves.
display_label
@property
def display_label() -> strReturn 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
@property
def impl() -> IntegrationImplReturn this row's integration-level implementation.
attach_credential
def attach_credential(credential: Any) -> NoneAttach a live credential and activate this draft integration.
report_status
def report_status(status: IntegrationStatus | str, error: str = "") -> NoneRecord implementation status telemetry and persist this integration.
Bridge
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
class Meta()Django model options for abstract bridge inheritance.
mark_sync_started
def mark_sync_started(*, now: datetime) -> NonePersist the start timestamp for one scheduler sync attempt.
record_sync
def record_sync(result: int, *, now: datetime) -> NonePersist one successful scheduler sync result and healthy status report.
record_sync_error
def record_sync_error(error: Exception, *, now: datetime) -> NonePersist one failed scheduler sync result and error status report.
run_sync
def run_sync(*, now: datetime) -> intRun one sync attempt and persist its lifecycle telemetry.
sync
def sync() -> intSynchronize this bridge with its external system.
handle_webhook
def handle_webhook(payload: Any) -> NoneApply one verified inbound webhook payload to this bridge.
verify_webhook
def verify_webhook(request: Any) -> boolReturn whether an inbound webhook request is authentic for this bridge.
dispatch_inbound
def dispatch_inbound(request_or_payload: Any) -> boolVerify one inbound webhook and apply it to this bridge when authentic.
start_live
def start_live() -> NoneStart or renew this bridge's live vendor subscription.
stop_live
def stop_live() -> NoneStop this bridge's live vendor subscription.
RepoVisibility
class RepoVisibility(models.TextChoices)Visibility of a git remote on its host.
VcsBridge
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
class Meta()Django model options for the VCS bridge child model.
backend
@property
def backend() -> VCSBackendReturn this bridge's selected VCS backend.
repositories_by_org
def repositories_by_org() -> dict[str, list[Any]]Return every visible repository grouped and sorted by owning org.
discover
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
def sync() -> intRefresh 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
def handle_webhook(payload: Any) -> NoneRe-sync this bridge's inventory on an inbound push webhook.
verify_webhook
def verify_webhook(request: Any) -> boolReturn whether an inbound push webhook is authentic for this bridge.
search_repositories
def search_repositories(query: str) -> list[Any]Return host repositories whose name matches query (the add typeahead).
import_repository
def import_repository(name: str) -> AnyInventory one repository by its host name (a picked typeahead result).
discover_repositories
def discover_repositories(*, org: str = "") -> intInventory every repository the account exposes (bulk import; prunes vanished).
RepositoryManager
class RepositoryManager(RebacManager)Manager owning the upsert/reconcile of repository rows from a host listing.
reconcile
def reconcile(vcs_bridge: Any, descriptors: Iterable[Any]) -> intUpsert 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
def add(vcs_bridge: Any, descriptor: Any) -> AnyInventory one repository (no prune) — the typeahead "add this repo" path.
Repository
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
class Meta()Django model options for repository inventory.
__str__
def __str__() -> strReturn the repository's host path.
Source
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
class Meta()Django model options for source inventory.
__str__
def __str__() -> strReturn a kind-qualified source label.
kind_models
@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
@classmethod
def available_kinds(cls) -> tuple[str, ...]Return the source kinds any installed addon contributes an output model for.
target_for_kind
@classmethod
def target_for_kind(cls, kind: str) -> type[models.Model]Return the output model bound to one source kind or raise.
refresh
def refresh() -> intRe-enumerate over REST into the kind's output rows; return the row count.
materialize_spec
def materialize_spec() -> dict[str, str]Return the operator handoff coordinates to clone and check out this source.
TemplateManager
class TemplateManager(RebacManager)Manager owning the reconcile of template rows from a template source.
sync_from_source
def sync_from_source(source: Any) -> intWalk the source for copier.yml and upsert/prune Template rows.
Template
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
class Meta()Django model options for discovered templates.
__str__
def __str__() -> strReturn a kind-qualified template label.
WebhookSubscriptionManager
class WebhookSubscriptionManager(RebacManager)Manager for webhook subscriptions.
deliver_event
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
class WebhookSubscription(SqidMixin, AuditMixin, AngeeModel)Outbound webhook endpoint owned by one user.
Meta
class Meta()Django model options for webhook subscriptions.
matches
def matches(*, kind: str, impl_app: str, integration: Any | None) -> boolReturn whether this subscription should receive one event.
deliver
def deliver(body: bytes) -> strPOST one signed event body to this subscription's pinned target; raise on non-2xx.
deliver_recorded
def deliver_recorded(body: bytes) -> tuple[bool, str]Deliver one body, persist telemetry, and return (ok, status_or_error).
deliver_test
def deliver_test() -> tuple[bool, str]Send a test event, persist telemetry, and return an action result tuple.
rotate_secret
def rotate_secret() -> strGenerate 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
def record_delivery(status: str) -> NonePersist success telemetry for one delivery attempt (mirrors Bridge.record_sync).
record_delivery_failure
def record_delivery_failure(*, status: str, error: str) -> NonePersist 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.