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(AngeeQuerySet[Any])REBAC-scoped reads for OAuth client registration.
connectable
def connectable() -> OAuthClientQuerySetReturn enabled, client-configured OAuth clients for the connect picker.
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(AngeeManager.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, Any]Return provider token parameters while preserving JSON scalar types.
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(AngeeQuerySet[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(
AngeeManager.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(AngeeQuerySet[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(AngeeManager.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.
check_disconnect
def check_disconnect(credential: Any) -> NoneRun installed credential-disconnect guards for an explicit disconnect.
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.
prepare_local_credential
def prepare_local_credential(user: Any, *, kind: str, name: str,
material: dict[str, Any], **fields: Any) -> AnyReturn a validated, unsaved provider-less credential for external preflight.
credential_disconnect_guards
@cache
def credential_disconnect_guards() -> tuple[Any, ...]Return configured credential-disconnect guard callables.
check_credential_disconnect_guards
def check_credential_disconnect_guards(
app_configs: list[object] | None = None,
**kwargs: object) -> list[checks.CheckMessage]Validate configured credential-disconnect guard callables.
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.
encode_material
@staticmethod
def encode_material(material: Mapping[str, Any]) -> strEncode credential material into its deterministic encrypted-field payload.
update_material
def update_material(**changes: Any) -> NoneMerge encrypted material changes under a row lock; None deletes.
Credential material may be edited concurrently by operator and worker processes holding separately-loaded instances. Re-read the row under the same authoritative lock canon as :meth:Bridge.merge_subscription_state so one key's consume-once edit cannot clobber another writer's material. Kind handlers validate required keys at credential creation; transient extra keys remain permitted here.
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.
VendorManager
class VendorManager(AngeeManager)Manager for the resource-seeded third-party catalogue.
seeded
def seeded(slug: str) -> AnyReturn the seeded vendor named by slug or raise with load guidance.
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.
IntegrationLifecycle
class IntegrationLifecycle(models.TextChoices)Durable connection lifecycle shared by every integration.
from_value
@classmethod
def from_value(cls, value: object) -> IntegrationLifecycleReturn the member for one string or enum integration-lifecycle value.
IntegrationRuntimeStatus
class IntegrationRuntimeStatus(models.TextChoices)Observed runtime health for one integration implementation.
from_value
@classmethod
def from_value(cls, value: object) -> IntegrationRuntimeStatusReturn the member for one string or enum runtime-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.
disconnected_for
def disconnected_for(user: Any, *, vendor: Any, impl_class: str) -> AnyReturn the user's parent row for one disconnected integration.
connect_from_credential
def connect_from_credential(user: Any,
*,
vendor: Any,
credential: Any,
impl_class: str = "none") -> AnyAttach credential to the user's parent row and connect it.
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.
Lifecycle
Expose the lifecycle vocabulary off the row for callers that cannot import this module.
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.
lifecycle
Declared connection intent for this integration.
Connected means configured and enabled, paused retains configuration while intentionally stopped, and disconnected releases the connection. Runtime health lives separately on runtime_status.
runtime_status
Observed health for the integration's last runtime interaction.
OK and ERROR describe the last credential/sync/use outcome; they do not move the lifecycle journey.
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 (lifecycle).
impl
@property
def impl() -> IntegrationImplReturn this row's integration-level implementation.
connect
@transition(
lifecycle,
source=[IntegrationLifecycle.DISCONNECTED, IntegrationLifecycle.PAUSED],
target=IntegrationLifecycle.CONNECTED,
on_success=save_state,
)
def connect(*, credential: Any = _UNSET, account: Any = _UNSET) -> NoneMark this integration connected, optionally attaching connection rows.
pause
@transition(
lifecycle,
source=IntegrationLifecycle.CONNECTED,
target=IntegrationLifecycle.PAUSED,
on_success=save_state,
)
def pause() -> NonePause this connected integration without changing its runtime health.
disconnect
@transition(
lifecycle,
source=[IntegrationLifecycle.CONNECTED, IntegrationLifecycle.PAUSED],
target=IntegrationLifecycle.DISCONNECTED,
on_success=save_state,
)
def disconnect() -> NoneDisconnect this integration without changing its runtime health.
set_lifecycle
def set_lifecycle(lifecycle: IntegrationLifecycle | str) -> NoneMove to one declared lifecycle state using this model's transition methods.
attach_credential
def attach_credential(credential: Any) -> NoneAttach a live credential and connect this disconnected integration.
report_status
def report_status(status: IntegrationRuntimeStatus | str,
error: str = "") -> NoneRecord implementation status telemetry and persist this integration.
status is a runtime status, resolved by member name (ERROR — the shape GraphQL serializes) or stored value (error); anything else fails at the vocabulary that could not read it. Only the runtime axis is written — lifecycle is the operator's to declare, and a status report is not the operator. last_used_status keeps what was reported verbatim.
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.
SyncStage
class SyncStage(models.TextChoices)Generic lifecycle stage for bridge sync jobs.
LiveState
class LiveState(models.TextChoices)Desired live-ingest state persisted in subscription_state["desired"].
The base owns this vocabulary: bridge start/stop methods write it, and a live implementation's session loop and reconciler read it. Stopping is cooperative - a running session notices STOPPED and exits.
live_impl_field
Name the ImplClassField that selects this bridge's runtime implementation.
config
Bridge-scoped settings interpreted by the selected backend.
next_sync_at
Next scheduler poll. NULL means unscheduled: a bridge enters the poll loop when its first (eager) sync records a result; the scheduler claims a due row by pushing this one interval out for the duration of the run.
Meta
class Meta()Django model options for abstract bridge inheritance.
live_impl
@property
def live_impl() -> IntegrationImplReturn this bridge's selected runtime implementation.
is_syncing
@property
def is_syncing() -> boolReturn whether a worker currently holds this bridge's live sync lock.
effective_sync_stage
@property
def effective_sync_stage() -> strReturn the sync stage reconciled against the live lock, not the record.
The persisted sync_stage is a progress report, not the source of truth for "is a run alive" — only the advisory lock is. A crashed worker leaves syncing behind forever; this projection reports such a row as FAILED (the run was interrupted) instead of trusting the stale column. queued is exempt: a queued task legitimately holds no lock until a worker picks it up. When the lock backend is process-local (the SQLite floor), the web process cannot see a worker's lock at all — reconciling there would misreport every healthy run, so the column is trusted as-is.
sync_lock_key
def sync_lock_key() -> LockKeyReturn the advisory task lock key for this bridge sync.
mark_sync_started
def mark_sync_started(*, now: datetime) -> NonePersist the start timestamp for one scheduler sync attempt.
claim_sync
def claim_sync(*, now: datetime) -> NonePush the next poll one interval out as an in-flight claim.
The scheduler claims a due row under a row lock before running it, so an overlapping scan — a backfill outliving the tick cadence, or a second worker — re-reads the row, sees a future next_sync_at, and skips instead of double-syncing one source. record_sync / record_sync_error recompute the real next poll when the run ends.
mark_sync_queued
def mark_sync_queued(*, now: datetime) -> NonePersist that a worker task has been queued for this bridge.
reset_sync_queue
def reset_sync_queue(*, now: datetime) -> NoneMake a failed queue dispatch due again for the next scheduler pass.
sync_queue_token_matches
def sync_queue_token_matches(timestamp: datetime) -> boolReturn whether a queued task payload still matches this bridge row.
release_sync_queue
def release_sync_queue(*, now: datetime) -> boolRelease a queue claim this run declined; return whether it was still ours.
A declined run is terminal for its queue token — the work is not happening. Leaving the row QUEUED makes the stale-queue recovery in angee.integrate.scheduler re-queue it every _QUEUED_RECOVERY_SECONDS forever, because that recovery exists to repair a lost enqueue and cannot tell one from a declined run. So the task that declines the work is the one that clears the claim.
Guarded by the queue token and re-read under a row lock: whoever holds the sync lock may already have moved this row on (a concurrent :meth:mark_sync_started), and the decliner must never overwrite the holder's stage. Mirrors :meth:merge_subscription_state.
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.
merge_subscription_state
def merge_subscription_state(**values: Any) -> dict[str, Any]Merge sub-keys into subscription_state under a row lock; return it.
This method merges only — a key can never be removed; consumed markers settle to a sentinel value defined by their owning flow. subscription_state has disjoint owners — the operator writes desired (start/stop), a live session writes its own pairing facts — that may write concurrently from separately-loaded instances. A full read-modify-write from a stale instance would clobber the other owner's key, so this re-reads the row locked, merges only the given keys, and saves. Mirrors :meth:Channel._persist_cursor_slice.
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(AngeeManager)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; the integration registry owns the deterministic app scan and contract check.
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(AngeeManager)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(AngeeManager)Manager for webhook subscriptions.
enqueue_event
def enqueue_event(*,
kind: EventKind,
payload: Any,
impl_app: str = "",
integration: Any | None = None) -> NoneQueue one event fan-out after the current transaction commits.
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 = None,
integration_pk: Any | None = 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.