Files
dify/api/models/human_input_v2.py

1969 lines
84 KiB
Python

"""Persistence models for Human Input v2 contacts, delivery, and approval runtime.
The models intentionally use logical references instead of database foreign keys.
Every relationship therefore requires an explicit eager-loading strategy, and
authorization code must scope queries by the owning directory or tenant. Column
comments name the referenced ``table.column`` for every logical foreign key.
Human Input v2 forms and every form-scoped child use a dedicated table namespace;
they never reference the legacy ``human_input_forms`` aggregate. Runtime forms
bind to the shared workflow pause infrastructure through ``workflow_pause_id``.
Contacts represent the conceptual Organization boundary through ``tenant_id``:
EE Organization rows use a null value, while workspace-owned rows reference
``tenants.id``. Their immutable ``identity_source`` selects the lifecycle policy;
workspace-local Contact type remains a query projection. IM child rows use their
integration as the concrete persistence boundary.
"""
from __future__ import annotations
from datetime import datetime
from enum import StrEnum
from typing import Annotated, Literal
import sqlalchemy as sa
from pydantic import BaseModel, ConfigDict, Field, JsonValue, RootModel, TypeAdapter
from sqlalchemy import orm
from sqlalchemy.orm import Mapped, mapped_column, relationship
from core.human_input_v2.approval.recipient_resolution import RecipientSourceKind as _RecipientSourceKind
from core.human_input_v2.entities import (
EmailProviderType as _EmailProviderType,
)
from core.human_input_v2.entities import (
HumanInputApproverGrantSubjectType as _HumanInputApproverGrantSubjectType,
)
from core.human_input_v2.entities import (
HumanInputAuthorizationProofType as _HumanInputAuthorizationProofType,
)
from core.human_input_v2.entities import (
HumanInputDeliveryAttemptStatus as _HumanInputDeliveryAttemptStatus,
)
from core.human_input_v2.entities import (
HumanInputDeliveryChannel as _HumanInputDeliveryChannel,
)
from core.human_input_v2.entities import (
HumanInputOTPChallengeStatus as _HumanInputOTPChallengeStatus,
)
from core.human_input_v2.entities import (
HumanInputSubmissionActorType as _HumanInputSubmissionActorType,
)
from core.human_input_v2.entities import (
HumanInputV2FormKind as _HumanInputV2FormKind,
)
from core.human_input_v2.entities import (
HumanInputV2FormStatus as _HumanInputV2FormStatus,
)
from core.human_input_v2.entities import (
IMBindingScope as _IMBindingScope,
)
from core.human_input_v2.entities import (
IMIntegrationStatus as _IMIntegrationStatus,
)
from core.human_input_v2.entities import (
IMProvider as _IMProvider,
)
from core.human_input_v2.entities import (
IMSyncRemovalReason as _IMSyncRemovalReason,
)
from core.human_input_v2.entities import (
IMSyncResultType as _IMSyncResultType,
)
from core.human_input_v2.entities import (
IMSyncRunStatus as _IMSyncRunStatus,
)
from core.workflow.nodes.human_input.entities import FormInputConfig, UserActionConfig
from .base import DefaultFieldsDCMixin, TypeBase
from .types import EnumText, FrozenPydanticModelColumn, LongText, StringUUID
class _ImmutableJSONModel(BaseModel):
"""Strict immutable base for structured JSON persistence values."""
model_config = ConfigDict(extra="forbid", frozen=True, strict=True, validate_default=True)
class _ImmutableJSONObject(RootModel[dict[str, JsonValue]]):
"""Strict immutable base for intentionally opaque JSON object payloads."""
model_config = ConfigDict(frozen=True, strict=True, validate_default=True)
class _FeishuLarkIMIntegrationEncryptedCredentialsBase(_ImmutableJSONModel):
"""Encrypted credential fields shared by Feishu and Lark integrations."""
app_id: str = Field(description="Provider application identifier.")
encrypted_app_secret: str = Field(description="Encrypted provider application secret.")
encrypted_verification_token: str | None = Field(
default=None,
description="Encrypted callback verification token, when configured.",
)
encrypted_encrypt_key: str | None = Field(
default=None,
description="Encrypted callback encryption key, when configured.",
)
class FeishuIMIntegrationEncryptedCredentials(_FeishuLarkIMIntegrationEncryptedCredentialsBase):
"""Encrypted credentials persisted for a Feishu integration."""
provider: Literal[_IMProvider.FEISHU] = Field(
default=_IMProvider.FEISHU,
description="Discriminator for Feishu encrypted credentials.",
)
class LarkIMIntegrationEncryptedCredentials(_FeishuLarkIMIntegrationEncryptedCredentialsBase):
"""Encrypted credentials persisted for a Lark integration."""
provider: Literal[_IMProvider.LARK] = Field(
default=_IMProvider.LARK,
description="Discriminator for Lark encrypted credentials.",
)
class SlackIMIntegrationEncryptedCredentials(_ImmutableJSONModel):
"""Encrypted credentials persisted for a Slack integration."""
provider: Literal[_IMProvider.SLACK] = Field(
default=_IMProvider.SLACK,
description="Discriminator for Slack encrypted credentials.",
)
client_id: str = Field(description="Slack OAuth client identifier.")
encrypted_client_secret: str = Field(description="Encrypted Slack OAuth client secret.")
encrypted_signing_secret: str = Field(description="Encrypted Slack callback signing secret.")
encrypted_bot_token: str = Field(description="Encrypted Slack bot token.")
class DingTalkIMIntegrationEncryptedCredentials(_ImmutableJSONModel):
"""Encrypted credentials persisted for a DingTalk integration."""
provider: Literal[_IMProvider.DING_TALK] = Field(
default=_IMProvider.DING_TALK,
description="Discriminator for DingTalk encrypted credentials.",
)
client_id: str = Field(description="DingTalk application client identifier.")
encrypted_client_secret: str = Field(description="Encrypted DingTalk application client secret.")
class MSTeamsIMIntegrationEncryptedCredentials(_ImmutableJSONModel):
"""Encrypted credentials persisted for a Microsoft Teams integration."""
provider: Literal[_IMProvider.MS_TEAMS] = Field(
default=_IMProvider.MS_TEAMS,
description="Discriminator for Microsoft Teams encrypted credentials.",
)
tenant_id: str = Field(description="Microsoft Entra tenant identifier.")
client_id: str = Field(description="Microsoft Teams application client identifier.")
encrypted_client_secret: str = Field(description="Encrypted Microsoft Teams application client secret.")
class WeComIMIntegrationEncryptedCredentials(_ImmutableJSONModel):
"""Encrypted credentials persisted for a WeCom integration."""
provider: Literal[_IMProvider.WE_COM] = Field(
default=_IMProvider.WE_COM,
description="Discriminator for WeCom encrypted credentials.",
)
corp_id: str = Field(description="WeCom corporation identifier.")
agent_id: str = Field(description="WeCom agent identifier.")
encrypted_secret: str = Field(description="Encrypted WeCom application secret.")
class ResendEmailProviderEncryptedCredentials(_ImmutableJSONModel):
"""Encrypted credentials persisted for a Resend email provider."""
provider: Literal[_EmailProviderType.RESEND] = Field(
default=_EmailProviderType.RESEND,
description="Discriminator for Resend encrypted credentials.",
)
encrypted_api_key: str = Field(description="Encrypted Resend API key.")
class IMIdentityRawPayload(_ImmutableJSONObject):
"""Opaque provider identity payload retained only for diagnostics."""
class IMSyncDirectoryEntryPayload(_ImmutableJSONObject):
"""Opaque provider directory entry captured by one synchronization run."""
class IMSyncContactSnapshot(_ImmutableJSONModel):
"""Immutable contact values needed to display historical sync results."""
contact_id: str = Field(description="Contact identifier captured by the sync result.")
name: str = Field(description="Contact display name captured by the sync result.")
email: str | None = Field(default=None, description="Contact email captured by the sync result.")
avatar_file_id: str | None = Field(default=None, description="Avatar file identifier captured by the result.")
class IMSyncIdentitySnapshot(_ImmutableJSONModel):
"""Immutable last-known IM identity values retained after removal."""
identity_id: str = Field(description="IM identity identifier captured by the sync result.")
provider: _IMProvider = Field(
strict=False,
description="Provider that owned the captured IM identity.",
)
provider_user_id: str = Field(description="Provider user identifier captured by the sync result.")
display_name: str | None = Field(default=None, description="Provider display name captured by the result.")
email: str | None = Field(default=None, description="Provider email captured by the result.")
class FormApproverGrantMatchedSource(_ImmutableJSONModel):
"""One ordered recipient source retained by a historical grant."""
kind: _RecipientSourceKind = Field(strict=False, description="Stable recipient source discriminator.")
position: int = Field(ge=0, description="Original recipient configuration position.")
reference: str | None = Field(default=None, description="Saved source reference when one existed.")
class FormApproverGrantMatchedSources(_ImmutableJSONModel):
"""Immutable recipient sources merged into one form approver grant."""
sources: tuple[FormApproverGrantMatchedSource, ...] = Field(
default_factory=tuple,
strict=False,
description="Ordered recipient source snapshots merged into this approver grant.",
)
class FormApproverGrantSubjectSnapshot(_ImmutableJSONModel):
"""Minimal subject values retained after the current identity changes or is deleted."""
display_name: str | None = Field(default=None, description="Subject display name resolved when the grant was made.")
email: str | None = Field(default=None, description="Subject email resolved when the grant was made.")
class AccountSessionAuthorizationProof(_ImmutableJSONModel):
"""Successful account-session verification without retaining reusable session credentials."""
type: Literal[_HumanInputAuthorizationProofType.ACCOUNT_SESSION] = Field(
default=_HumanInputAuthorizationProofType.ACCOUNT_SESSION,
description="Discriminator for account-session authorization evidence.",
)
account_id: str = Field(description="Current Dify Account authenticated by the verified session.")
class EmailOTPAuthorizationProof(_ImmutableJSONModel):
"""Successful Email verification retained without plaintext or hash material."""
type: Literal[_HumanInputAuthorizationProofType.EMAIL_OTP] = Field(
default=_HumanInputAuthorizationProofType.EMAIL_OTP,
description="Discriminator for email OTP authorization evidence.",
)
otp_challenge_id: str = Field(description="Historical Human Input OTP challenge identifier.")
workspace_id: str = Field(description="Workspace owner captured by the verified challenge form reference.")
form_id: str = Field(description="Human Input v2 form identifier scoped by the verified challenge.")
approver_grant_id: str = Field(description="Approver grant identifier scoped by the verified challenge.")
subject_type: _HumanInputApproverGrantSubjectType = Field(
strict=False,
description="Contact or standalone Email subject verified by the challenge.",
)
contact_id: str | None = Field(
default=None,
description="Contact incarnation captured for a contact-backed proof.",
)
verified_email: str = Field(description="Normalized Email address verified by the challenge.")
verified_at: datetime = Field(description="Timestamp at which the challenge verified the Email address.")
class IMIdentityAuthorizationProof(_ImmutableJSONModel):
"""Resolved IM identity and binding evidence that survives deletion of current IM rows."""
type: Literal[_HumanInputAuthorizationProofType.IM_IDENTITY] = Field(
default=_HumanInputAuthorizationProofType.IM_IDENTITY,
description="Discriminator for IM identity authorization evidence.",
)
integration_id: str = Field(description="Historical Human Input IM integration identifier.")
im_identity_id: str = Field(description="Historical Human Input IM identity identifier.")
im_binding_id: str | None = Field(
default=None,
description="Current effective binding identifier, or null for Email fallback.",
)
provider: _IMProvider = Field(strict=False, description="IM provider that authenticated the external identity.")
provider_tenant_id: str = Field(description="Provider organization or workspace identifier.")
provider_user_id: str = Field(description="Provider user identifier verified by the IM interaction.")
display_name: str | None = Field(default=None, description="Provider display name captured at authorization time.")
email: str | None = Field(default=None, description="Provider email captured at authorization time.")
class TrustedEndUserAuthorizationProof(_ImmutableJSONModel):
"""Trusted app-token context used to authenticate one request-scoped end user."""
type: Literal[_HumanInputAuthorizationProofType.TRUSTED_END_USER] = Field(
default=_HumanInputAuthorizationProofType.TRUSTED_END_USER,
description="Discriminator for trusted end-user authorization evidence.",
)
app_id: str = Field(description="Application whose trusted context authenticated the end user.")
end_user_id: str = Field(description="Current EndUser authenticated by the trusted app context.")
type FormAuthorizationProof = Annotated[
AccountSessionAuthorizationProof
| EmailOTPAuthorizationProof
| IMIdentityAuthorizationProof
| TrustedEndUserAuthorizationProof,
Field(discriminator="type"),
]
_FORM_AUTHORIZATION_PROOF_ADAPTER: TypeAdapter[FormAuthorizationProof] = TypeAdapter(FormAuthorizationProof)
class FormDeliveryProviderResponse(_ImmutableJSONObject):
"""Opaque provider delivery response retained only for diagnostics."""
class HumanInputV2FormDefinition(_ImmutableJSONModel):
"""Frozen runtime definition used for rendering and submission validation."""
form_content: str = Field(description="Human-readable form template captured when the form was created.")
inputs: tuple[FormInputConfig, ...] = Field(
default_factory=tuple,
strict=False,
description="Resolved input definitions accepted by this form.",
)
user_actions: tuple[UserActionConfig, ...] = Field(
default_factory=tuple,
strict=False,
description="Resolved actions accepted by this form.",
)
default_values: dict[str, JsonValue] = Field(
default_factory=dict,
description="Serialized default input values resolved when the form was created.",
)
node_title: str | None = Field(default=None, description="Node title captured for display and audit context.")
display_in_ui: bool | None = Field(
default=None,
description="Whether runtime surfaces should expose the form in their UI.",
)
class FormInputSnapshot(_ImmutableJSONObject):
"""Unvalidated raw values from the request ``inputs`` object only."""
class FormCanonicalValues(_ImmutableJSONObject):
"""Validated runtime values persisted using current Segment serialization."""
class FormAuditEventPayload(_ImmutableJSONObject):
"""Event-specific immutable audit context not used for primary queries."""
type IMIntegrationEncryptedCredentials = Annotated[
FeishuIMIntegrationEncryptedCredentials
| LarkIMIntegrationEncryptedCredentials
| SlackIMIntegrationEncryptedCredentials
| DingTalkIMIntegrationEncryptedCredentials
| MSTeamsIMIntegrationEncryptedCredentials
| WeComIMIntegrationEncryptedCredentials,
Field(discriminator="provider"),
]
_IM_INTEGRATION_ENCRYPTED_CREDENTIALS_ADAPTER: TypeAdapter[IMIntegrationEncryptedCredentials] = TypeAdapter(
IMIntegrationEncryptedCredentials
)
class HumanInputContactIdentitySource(StrEnum):
"""Immutable persistence discriminator for Contact lifecycle ownership.
This model-layer enum is intentionally separate from ``HumanInputContactType``.
The identity source is persisted once and determines who owns creation and
deletion of the Contact: an EE Organization Account, a CE/SaaS workspace
membership, or a workspace-managed External Contact. It does not vary by
workspace.
``HumanInputContactType`` is an external, workspace-relative projection. One
``ORGANIZATION_ACCOUNT`` Contact can resolve to ``WORKSPACE`` in one
workspace, ``PLATFORM`` in another, and be absent elsewhere. Promote and
Demote therefore change membership and the Platform allow-list, never this
enum. This enum must not be serialized as the external Contact type.
"""
ORGANIZATION_ACCOUNT = "organization_account"
WORKSPACE_MEMBER = "workspace_member"
EXTERNAL = "external"
class HumanInputContact(DefaultFieldsDCMixin, TypeBase):
"""Canonical contact identity shared by directory, delivery, and authorization.
``identity_source`` and the owner columns define the immutable lifecycle
source of the identity. Workspace membership and Platform availability are
external facts and never mutate this source. Because both supported
databases allow duplicate nulls in unique constraints, EE Organization
contact uniqueness and Organization/External Email claims must also be
protected by a contact write transaction that locks the deployment's stable
``DifySetup`` row before conflict checks.
"""
__tablename__ = "human_input_contacts"
__table_args__ = (
sa.UniqueConstraint(
"tenant_id",
"account_id",
name="human_input_contacts_tenant_account_uq",
),
sa.UniqueConstraint(
"tenant_id",
"normalized_email",
name="human_input_contacts_tenant_email_uq",
),
sa.CheckConstraint(
"(identity_source = 'organization_account' AND tenant_id IS NULL AND account_id IS NOT NULL) OR "
"(identity_source = 'workspace_member' AND tenant_id IS NOT NULL AND account_id IS NOT NULL) OR "
"(identity_source = 'external' AND tenant_id IS NOT NULL AND account_id IS NULL)",
name="identity_owner",
),
sa.CheckConstraint(
"identity_source <> 'external' OR (email IS NOT NULL AND normalized_email IS NOT NULL)",
name="external_email",
),
sa.CheckConstraint(
"(email IS NULL AND normalized_email IS NULL) OR (email IS NOT NULL AND normalized_email IS NOT NULL)",
name="email_normalization_pair",
),
sa.Index("human_input_contacts_tenant_normalized_name_idx", "tenant_id", "normalized_name"),
{
"comment": (
"Canonical Human Input contact identities. EE Organization Account contacts have tenant_id IS NULL; "
"workspace-owned contacts have tenant_id = tenants.id; CE and SaaS must not create contacts with "
"tenant_id IS NULL."
)
},
)
name: Mapped[str] = mapped_column(sa.String(255), nullable=False, comment="Display name shown in contact surfaces.")
normalized_name: Mapped[str] = mapped_column(
sa.String(255), nullable=False, comment="Lower-cased search value maintained by the application."
)
identity_source: Mapped[HumanInputContactIdentitySource] = mapped_column(
EnumText(HumanInputContactIdentitySource),
nullable=False,
comment="Immutable identity source that determines the Contact lifecycle owner.",
)
tenant_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment=(
"Ownership boundary: null only for EE Organization contacts; otherwise the owning tenants.id for "
"workspace-owned contacts. CE and SaaS must never persist a null value."
),
)
account_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to accounts.id for an account-backed contact.",
)
email: Mapped[str | None] = mapped_column(
sa.String(320), nullable=True, default=None, comment="Current deliverable email address, when available."
)
normalized_email: Mapped[str | None] = mapped_column(
sa.String(320), nullable=True, default=None, comment="Full lower-cased email used for equality matching."
)
avatar_file_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to upload_files.id for an external contact avatar.",
)
platform_workspace_entries: Mapped[list[HumanInputPlatformContactWorkspaceEntry]] = relationship(
lambda: HumanInputPlatformContactWorkspaceEntry,
primaryjoin=lambda: HumanInputContact.id == orm.foreign(HumanInputPlatformContactWorkspaceEntry.contact_id),
back_populates="contact",
viewonly=True,
lazy="raise",
init=False,
)
im_bindings: Mapped[list[HumanInputIMBinding]] = relationship(
lambda: HumanInputIMBinding,
primaryjoin=lambda: HumanInputContact.id == orm.foreign(HumanInputIMBinding.contact_id),
back_populates="contact",
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputPlatformContactWorkspaceEntry(DefaultFieldsDCMixin, TypeBase):
"""EE-only workspace allow-list entry for an Organization Account contact.
The entry does not own the Contact and does not duplicate workspace
membership or the externally resolved Contact type. Its existence means that
an EE Organization Account contact without current membership is explicitly
available in one workspace. The Contact Directory service must ensure the
referenced Contact has ``ORGANIZATION_ACCOUNT`` as its identity source and
serialize Promote/Demote with the corresponding membership mutation.
"""
__tablename__ = "human_input_platform_contact_workspace_entries"
__table_args__ = (
sa.UniqueConstraint(
"tenant_id",
"contact_id",
name="hipcwe_tenant_contact_uq",
),
sa.Index("hipcwe_tenant_created_at_id_idx", "tenant_id", "created_at", "id"),
sa.Index("hipcwe_contact_id_idx", "contact_id"),
{
"comment": (
"EE-only workspace allow-list for Organization Account contacts. Workspace membership and External "
"contact ownership must not create rows in this table."
)
},
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False, comment="Logical foreign key to tenants.id.")
contact_id: Mapped[str] = mapped_column(
StringUUID, nullable=False, comment="Logical foreign key to human_input_contacts.id."
)
added_by_account_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Logical foreign key to accounts.id for the administrator who added this directory entry.",
)
contact: Mapped[HumanInputContact] = relationship(
lambda: HumanInputContact,
primaryjoin=lambda: orm.foreign(HumanInputPlatformContactWorkspaceEntry.contact_id) == HumanInputContact.id,
back_populates="platform_workspace_entries",
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputIMIntegration(DefaultFieldsDCMixin, TypeBase):
"""Single organization-level IM control-plane configuration.
Credential values must be encrypted before persistence. CE/SaaS rows are
tenant-scoped. EE uses a null ``tenant_id`` because the deployment is the
conceptual Organization boundary; creation must lock the stable ``DifySetup``
owner before checking for an existing null-owned row. Configuration writes use
``config_version`` for explicit compare-and-swap; connectivity diagnostics
do not advance that revision. Asynchronous work must capture the revision
that produced it and reject stale current-state writes.
"""
__tablename__ = "human_input_im_integrations"
__table_args__ = (
sa.UniqueConstraint("tenant_id", name="human_input_im_integrations_tenant_uq"),
sa.CheckConstraint("config_version > 0", name="config_version_positive"),
{"comment": "Organization-level Human Input IM integration configuration."},
)
provider: Mapped[_IMProvider] = mapped_column(
EnumText(_IMProvider), nullable=False, comment="Configured IM provider discriminator."
)
encrypted_credentials: Mapped[IMIntegrationEncryptedCredentials] = mapped_column(
FrozenPydanticModelColumn(
_IM_INTEGRATION_ENCRYPTED_CREDENTIALS_ADAPTER,
model_types=(
FeishuIMIntegrationEncryptedCredentials,
LarkIMIntegrationEncryptedCredentials,
SlackIMIntegrationEncryptedCredentials,
DingTalkIMIntegrationEncryptedCredentials,
MSTeamsIMIntegrationEncryptedCredentials,
WeComIMIntegrationEncryptedCredentials,
),
),
nullable=False,
comment=(
"Provider-specific encrypted credential Pydantic model stored as JSON; "
"its provider discriminator must match the provider column."
),
)
tenant_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to tenants.id in CE/SaaS; null for the EE deployment-wide integration.",
)
provider_tenant_id: Mapped[str] = mapped_column(
sa.String(255),
nullable=False,
kw_only=True,
comment=(
"Provider-side Organization or workspace identity. Credential rotation preserves current identities and "
"bindings only when the provider adapter confirms this value is unchanged."
),
)
status: Mapped[_IMIntegrationStatus] = mapped_column(
EnumText(_IMIntegrationStatus),
nullable=False,
default=_IMIntegrationStatus.CONFIGURED,
comment="Last persisted provider connectivity result.",
)
config_version: Mapped[int] = mapped_column(
sa.Integer,
nullable=False,
default=1,
comment="Monotonic integration configuration revision used for compare-and-swap and stale-work rejection.",
)
configured_by_account_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to accounts.id for the latest configuration write.",
)
callback_url: Mapped[str | None] = mapped_column(
sa.String(1024), nullable=True, default=None, comment="Provider callback URL, when callback delivery is used."
)
safe_status_reason: Mapped[str | None] = mapped_column(
LongText, nullable=True, default=None, comment="Operator-safe connection or permission diagnostic."
)
last_checked_at: Mapped[datetime | None] = mapped_column(
sa.DateTime, nullable=True, default=None, comment="Timestamp of the latest connection validation."
)
identities: Mapped[list[HumanInputIMIdentity]] = relationship(
lambda: HumanInputIMIdentity,
primaryjoin=lambda: HumanInputIMIntegration.id == orm.foreign(HumanInputIMIdentity.integration_id),
back_populates="integration",
viewonly=True,
lazy="raise",
init=False,
)
sync_runs: Mapped[list[HumanInputIMSyncRun]] = relationship(
lambda: HumanInputIMSyncRun,
primaryjoin=lambda: HumanInputIMIntegration.id == orm.foreign(HumanInputIMSyncRun.integration_id),
back_populates="integration",
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputEmailProvider(DefaultFieldsDCMixin, TypeBase):
"""Single workspace-level email provider used for Human Input delivery."""
__tablename__ = "human_input_email_providers"
__table_args__ = (
sa.UniqueConstraint("tenant_id", name="human_input_email_providers_tenant_uq"),
{"comment": "Workspace-level Human Input email delivery configuration."},
)
provider: Mapped[_EmailProviderType] = mapped_column(
EnumText(_EmailProviderType), nullable=False, comment="Configured email provider discriminator."
)
sender_email: Mapped[str] = mapped_column(
sa.String(320), nullable=False, comment="Configured sender email address."
)
encrypted_credentials: Mapped[ResendEmailProviderEncryptedCredentials] = mapped_column(
FrozenPydanticModelColumn(ResendEmailProviderEncryptedCredentials),
nullable=False,
comment="Encrypted Resend credential Pydantic model stored as JSON.",
)
tenant_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Logical foreign key to tenants.id.",
)
sender_name: Mapped[str] = mapped_column(
sa.String(255), nullable=False, default="", comment="Optional sender display name."
)
configured_by_account_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to accounts.id for the latest configuration write.",
)
class HumanInputIMIdentity(DefaultFieldsDCMixin, TypeBase):
"""Durable provider identity discovered by manual directory synchronization.
Searchable and match-critical fields are stored in columns. ``raw_payload``
retains the provider response for diagnostics without making it query state.
An identity absent from the current provider directory is deleted after its
last-known snapshot is written to the synchronization result.
"""
__tablename__ = "human_input_im_identities"
__table_args__ = (
sa.UniqueConstraint(
"integration_id",
"provider",
"provider_user_id",
name="human_input_im_identities_integration_provider_user_uq",
),
sa.CheckConstraint(
"(email IS NULL AND normalized_email IS NULL) OR (email IS NOT NULL AND normalized_email IS NOT NULL)",
name="email_normalization_pair",
),
sa.Index("hiimi_integration_provider_email_idx", "integration_id", "provider", "normalized_email"),
sa.Index("hiimi_integration_provider_name_idx", "integration_id", "provider", "normalized_name"),
sa.Index("hiimi_integration_last_seen_run_idx", "integration_id", "last_seen_sync_run_id"),
{"comment": "Synchronized IM directory identities available for contact binding."},
)
integration_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Logical foreign key to human_input_im_integrations.id.",
)
provider: Mapped[_IMProvider] = mapped_column(
EnumText(_IMProvider), nullable=False, comment="Provider that owns the external identity."
)
provider_user_id: Mapped[str] = mapped_column(
sa.String(255),
nullable=False,
comment="External provider user identifier used for first-pass matching; not a logical foreign key.",
)
display_name: Mapped[str | None] = mapped_column(
sa.String(255), nullable=True, default=None, comment="Latest provider display name."
)
normalized_name: Mapped[str | None] = mapped_column(
sa.String(255), nullable=True, default=None, comment="Lower-cased provider display name for prefix search."
)
email: Mapped[str | None] = mapped_column(
sa.String(320), nullable=True, default=None, comment="Latest provider email, when available."
)
normalized_email: Mapped[str | None] = mapped_column(
sa.String(320),
nullable=True,
default=None,
comment="Full lower-cased provider email used for fallback matching.",
)
raw_payload: Mapped[IMIdentityRawPayload] = mapped_column(
FrozenPydanticModelColumn(IMIdentityRawPayload),
nullable=False,
default_factory=lambda: IMIdentityRawPayload({}),
comment="Latest provider payload Pydantic model retained as non-query diagnostic data.",
)
last_seen_sync_run_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to human_input_im_sync_runs.id for the run that last observed this identity.",
)
last_seen_at: Mapped[datetime | None] = mapped_column(
sa.DateTime, nullable=True, default=None, comment="Timestamp when the identity was last observed."
)
integration: Mapped[HumanInputIMIntegration] = relationship(
lambda: HumanInputIMIntegration,
primaryjoin=lambda: orm.foreign(HumanInputIMIdentity.integration_id) == HumanInputIMIntegration.id,
back_populates="identities",
viewonly=True,
lazy="raise",
init=False,
)
bindings: Mapped[list[HumanInputIMBinding]] = relationship(
lambda: HumanInputIMBinding,
primaryjoin=lambda: HumanInputIMIdentity.id == orm.foreign(HumanInputIMBinding.im_identity_id),
back_populates="identity",
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputIMBinding(DefaultFieldsDCMixin, TypeBase):
"""Current association between a contact and a synchronized IM identity.
``scope_id`` is always non-null: it references the owning IM integration for
an organization binding and the target tenant for a workspace override. This
avoids relying on dialect-specific uniqueness semantics for nullable columns.
"""
__tablename__ = "human_input_im_bindings"
__table_args__ = (
sa.UniqueConstraint(
"scope",
"scope_id",
"contact_id",
"provider",
name="human_input_im_bindings_scope_contact_provider_uq",
),
sa.UniqueConstraint("scope", "scope_id", "im_identity_id", name="human_input_im_bindings_scope_identity_uq"),
sa.CheckConstraint(
"scope <> 'organization' OR scope_id = integration_id",
name="organization_scope_owner",
),
sa.Index(
"hiimb_integration_contact_provider_scope_idx",
"integration_id",
"contact_id",
"provider",
"scope",
"scope_id",
),
sa.Index("hiimb_identity_scope_idx", "im_identity_id", "scope", "scope_id"),
{"comment": "Current organization binding or workspace override for a contact IM identity."},
)
integration_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment=(
"Logical foreign key to human_input_im_integrations.id; "
"must match the integration that owns im_identity_id."
),
)
scope: Mapped[_IMBindingScope] = mapped_column(
EnumText(_IMBindingScope), nullable=False, comment="Organization binding or workspace override."
)
scope_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment=(
"Polymorphic logical foreign key selected by scope: human_input_im_integrations.id for ORGANIZATION; "
"tenants.id for WORKSPACE."
),
)
contact_id: Mapped[str] = mapped_column(
StringUUID, nullable=False, comment="Logical foreign key to human_input_contacts.id."
)
im_identity_id: Mapped[str] = mapped_column(
StringUUID, nullable=False, comment="Logical foreign key to human_input_im_identities.id."
)
provider: Mapped[_IMProvider] = mapped_column(
EnumText(_IMProvider), nullable=False, comment="Denormalized provider used by effective-binding queries."
)
bound_by_account_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to accounts.id for the administrator who created the override.",
)
contact: Mapped[HumanInputContact] = relationship(
lambda: HumanInputContact,
primaryjoin=lambda: orm.foreign(HumanInputIMBinding.contact_id) == HumanInputContact.id,
back_populates="im_bindings",
viewonly=True,
lazy="raise",
init=False,
)
identity: Mapped[HumanInputIMIdentity] = relationship(
lambda: HumanInputIMIdentity,
primaryjoin=lambda: orm.foreign(HumanInputIMBinding.im_identity_id) == HumanInputIMIdentity.id,
back_populates="bindings",
viewonly=True,
lazy="raise",
init=False,
)
integration: Mapped[HumanInputIMIntegration] = relationship(
lambda: HumanInputIMIntegration,
primaryjoin=lambda: orm.foreign(HumanInputIMBinding.integration_id) == HumanInputIMIntegration.id,
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputIMSyncRun(DefaultFieldsDCMixin, TypeBase):
"""One manually triggered organization IM directory synchronization.
Preventing parallel queued or running rows is a service transaction invariant:
lock the owning integration row before creating a run. A portable partial
unique index is deliberately not used. A worker may apply current identities
and bindings only while ``integration_config_version`` still matches the
owning integration.
"""
__tablename__ = "human_input_im_sync_runs"
__table_args__ = (
sa.CheckConstraint("integration_config_version > 0", name="captured_version_positive"),
sa.CheckConstraint(
"added_count >= 0 AND not_matched_count >= 0 AND failed_count >= 0 AND removed_count >= 0 "
"AND skipped_count >= 0",
name="result_counts_nonnegative",
),
sa.Index("hiimsr_integration_created_idx", "integration_id", "created_at", "id"),
sa.Index("hiimsr_integration_status_created_idx", "integration_id", "status", "created_at"),
{"comment": "Manual IM directory synchronization lifecycle and aggregate counts."},
)
integration_id: Mapped[str] = mapped_column(
StringUUID, nullable=False, comment="Logical foreign key to human_input_im_integrations.id."
)
integration_config_version: Mapped[int] = mapped_column(
sa.Integer,
nullable=False,
comment="Integration configuration revision captured when this synchronization was created.",
)
provider: Mapped[_IMProvider] = mapped_column(
EnumText(_IMProvider), nullable=False, comment="Provider snapshot used by this synchronization."
)
status: Mapped[_IMSyncRunStatus] = mapped_column(
EnumText(_IMSyncRunStatus), nullable=False, comment="Current synchronization lifecycle state."
)
added_count: Mapped[int] = mapped_column(
sa.Integer, nullable=False, default=0, comment="Number of entries newly matched and bound."
)
not_matched_count: Mapped[int] = mapped_column(
sa.Integer, nullable=False, default=0, comment="Number of entries requiring later manual handling."
)
failed_count: Mapped[int] = mapped_column(
sa.Integer, nullable=False, default=0, comment="Number of entries that failed reconciliation."
)
removed_count: Mapped[int] = mapped_column(
sa.Integer,
nullable=False,
default=0,
comment="Number of removed binding facts, including one unbound-identity fact when applicable.",
)
skipped_count: Mapped[int] = mapped_column(
sa.Integer, nullable=False, default=0, comment="Number of entries intentionally skipped."
)
started_by_account_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to accounts.id for the administrator who started this run.",
)
started_at: Mapped[datetime | None] = mapped_column(
sa.DateTime, nullable=True, default=None, comment="Timestamp when a worker started processing the run."
)
finished_at: Mapped[datetime | None] = mapped_column(
sa.DateTime, nullable=True, default=None, comment="Terminal completion timestamp."
)
error_code: Mapped[str | None] = mapped_column(
sa.String(100), nullable=True, default=None, comment="Machine-readable terminal failure code."
)
error_message: Mapped[str | None] = mapped_column(
LongText, nullable=True, default=None, comment="Operator-safe terminal failure summary."
)
integration: Mapped[HumanInputIMIntegration] = relationship(
lambda: HumanInputIMIntegration,
primaryjoin=lambda: orm.foreign(HumanInputIMSyncRun.integration_id) == HumanInputIMIntegration.id,
back_populates="sync_runs",
viewonly=True,
lazy="raise",
init=False,
)
results: Mapped[list[HumanInputIMSyncResult]] = relationship(
lambda: HumanInputIMSyncResult,
primaryjoin=lambda: HumanInputIMSyncRun.id == orm.foreign(HumanInputIMSyncResult.sync_run_id),
back_populates="sync_run",
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputIMSyncResult(DefaultFieldsDCMixin, TypeBase):
"""Immutable reconciliation result for one entry, removed binding, or diagnostic.
Provider identifiers and normalized email remain queryable columns. JSON is
limited to the raw provider input and immutable display snapshots.
"""
__tablename__ = "human_input_im_sync_results"
__table_args__ = (
sa.Index("hiimsres_run_type_created_idx", "sync_run_id", "result_type", "created_at", "id"),
sa.Index("hiimsres_integration_contact_created_idx", "integration_id", "contact_id", "created_at"),
sa.Index("hiimsres_integration_identity_created_idx", "integration_id", "im_identity_id", "created_at"),
{"comment": "Append-only per-entry, removed-binding, and diagnostic IM synchronization outcomes."},
)
integration_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment=(
"Denormalized logical foreign key to human_input_im_integrations.id; "
"must match the integration referenced by sync_run_id."
),
)
sync_run_id: Mapped[str] = mapped_column(
StringUUID, nullable=False, comment="Logical foreign key to human_input_im_sync_runs.id."
)
result_type: Mapped[_IMSyncResultType] = mapped_column(
EnumText(_IMSyncResultType), nullable=False, comment="Stable result bucket used by pagination."
)
provider_user_id: Mapped[str | None] = mapped_column(
sa.String(255),
nullable=True,
default=None,
comment="External provider user identifier observed for this result; not a logical foreign key.",
)
display_name: Mapped[str | None] = mapped_column(
sa.String(255), nullable=True, default=None, comment="Provider display name observed for this result."
)
email: Mapped[str | None] = mapped_column(
sa.String(320), nullable=True, default=None, comment="Provider email observed for this result."
)
normalized_email: Mapped[str | None] = mapped_column(
sa.String(320), nullable=True, default=None, comment="Lower-cased provider email used during matching."
)
contact_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to human_input_contacts.id, when a contact was matched.",
)
im_identity_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment=(
"Historical logical foreign key to human_input_im_identities.id; the target row may be deleted after sync."
),
)
im_binding_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment=(
"Historical logical foreign key to human_input_im_bindings.id; the target row may be deleted or replaced."
),
)
removal_reason: Mapped[_IMSyncRemovalReason | None] = mapped_column(
EnumText(_IMSyncRemovalReason),
nullable=True,
default=None,
comment="Stable removal reason for removed results only.",
)
reason_code: Mapped[str | None] = mapped_column(
sa.String(100), nullable=True, default=None, comment="Machine-readable failure, skip, or mismatch reason."
)
reason_message: Mapped[str | None] = mapped_column(
LongText, nullable=True, default=None, comment="Operator-safe detail for diagnostics."
)
directory_entry_payload: Mapped[IMSyncDirectoryEntryPayload | None] = mapped_column(
FrozenPydanticModelColumn(IMSyncDirectoryEntryPayload),
nullable=True,
default=None,
comment="Immutable provider entry payload Pydantic model observed during this run.",
)
contact_snapshot: Mapped[IMSyncContactSnapshot | None] = mapped_column(
FrozenPydanticModelColumn(IMSyncContactSnapshot),
nullable=True,
default=None,
comment="Immutable contact snapshot Pydantic model used for historical display.",
)
identity_snapshot: Mapped[IMSyncIdentitySnapshot | None] = mapped_column(
FrozenPydanticModelColumn(IMSyncIdentitySnapshot),
nullable=True,
default=None,
comment="Immutable last-known IM identity Pydantic model for removed results.",
)
sync_run: Mapped[HumanInputIMSyncRun] = relationship(
lambda: HumanInputIMSyncRun,
primaryjoin=lambda: orm.foreign(HumanInputIMSyncResult.sync_run_id) == HumanInputIMSyncRun.id,
back_populates="results",
viewonly=True,
lazy="raise",
init=False,
)
contact: Mapped[HumanInputContact | None] = relationship(
lambda: HumanInputContact,
primaryjoin=lambda: orm.foreign(HumanInputIMSyncResult.contact_id) == HumanInputContact.id,
viewonly=True,
lazy="raise",
init=False,
)
identity: Mapped[HumanInputIMIdentity | None] = relationship(
lambda: HumanInputIMIdentity,
primaryjoin=lambda: orm.foreign(HumanInputIMSyncResult.im_identity_id) == HumanInputIMIdentity.id,
viewonly=True,
lazy="raise",
init=False,
)
binding: Mapped[HumanInputIMBinding | None] = relationship(
lambda: HumanInputIMBinding,
primaryjoin=lambda: orm.foreign(HumanInputIMSyncResult.im_binding_id) == HumanInputIMBinding.id,
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputV2Form(DefaultFieldsDCMixin, TypeBase):
"""Independent Human Input v2 form root.
Runtime forms bind one shared workflow pause to the exact owning
``workflow_node_executions`` row. ``node_execution_id`` intentionally names
the business role while referencing that table's primary key ``id`` rather
than its separate runtime ``node_execution_id`` column. ``rendered_content``
is retained for current runtime compatibility; ``form_definition`` remains
the frozen source used for validation and future rerendering.
"""
__tablename__ = "human_input_v2_forms"
__table_args__ = (
sa.UniqueConstraint("workflow_pause_id", name="hiv2_forms_workflow_pause_uq"),
sa.UniqueConstraint("node_execution_id", name="hiv2_forms_node_execution_uq"),
sa.CheckConstraint(
"form_kind <> 'runtime' OR (workflow_pause_id IS NOT NULL AND node_execution_id IS NOT NULL)",
name="runtime_owner",
),
sa.Index("hiv2_forms_tenant_status_node_timeout_idx", "tenant_id", "status", "node_timeout_at"),
sa.Index("hiv2_forms_tenant_status_global_expiry_idx", "tenant_id", "status", "global_expires_at"),
{"comment": "Independent Human Input v2 form roots bound only to shared workflow pause infrastructure."},
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False, comment="Logical foreign key to tenants.id.")
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False, comment="Logical foreign key to apps.id.")
form_definition: Mapped[HumanInputV2FormDefinition] = mapped_column(
FrozenPydanticModelColumn(HumanInputV2FormDefinition),
nullable=False,
comment="Frozen Human Input v2 form definition used for display and submission validation.",
)
rendered_content: Mapped[str] = mapped_column(
LongText,
nullable=False,
comment="Rendered content retained for current runtime compatibility; form_definition remains authoritative.",
)
node_timeout_at: Mapped[datetime] = mapped_column(
sa.DateTime,
nullable=False,
comment="Frozen node-level timeout timestamp used to resume through the timeout branch.",
)
global_expires_at: Mapped[datetime] = mapped_column(
sa.DateTime,
nullable=False,
comment="Frozen global expiration timestamp after which the form cannot be submitted.",
)
form_kind: Mapped[_HumanInputV2FormKind] = mapped_column(
EnumText(_HumanInputV2FormKind),
nullable=False,
default=_HumanInputV2FormKind.RUNTIME,
comment="Human Input v2 form ownership kind.",
)
status: Mapped[_HumanInputV2FormStatus] = mapped_column(
EnumText(_HumanInputV2FormStatus),
nullable=False,
default=_HumanInputV2FormStatus.WAITING,
comment="Current Human Input v2 form lifecycle state.",
)
workflow_pause_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to workflow_pauses.id for the owning shared workflow pause.",
)
node_execution_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment=(
"Logical foreign key to workflow_node_executions.id for the owning node-execution row. "
"This intentionally does not reference workflow_node_executions.node_execution_id."
),
)
grants: Mapped[list[HumanInputV2FormApproverGrant]] = relationship(
lambda: HumanInputV2FormApproverGrant,
primaryjoin=lambda: HumanInputV2Form.id == orm.foreign(HumanInputV2FormApproverGrant.form_id),
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputV2FormApproverGrant(DefaultFieldsDCMixin, TypeBase):
"""Form-scoped approval authority granted to one canonical business subject.
Contact-backed grants are revalidated through the current Contact lifecycle.
End-user and email-address grants cover subjects that intentionally do not
belong to the Contact Directory. ``subject_snapshot`` is display-only and
never substitutes for current-state authorization checks.
"""
__tablename__ = "human_input_v2_form_approver_grants"
__table_args__ = (
sa.UniqueConstraint("form_id", "subject_key", name="hiv2_form_grants_form_subject_uq"),
sa.CheckConstraint(
"(subject_type = 'contact' AND contact_id IS NOT NULL AND end_user_id IS NULL "
"AND normalized_email IS NULL) OR "
"(subject_type = 'end_user' AND contact_id IS NULL AND end_user_id IS NOT NULL "
"AND normalized_email IS NULL) OR "
"(subject_type = 'email_address' AND contact_id IS NULL AND end_user_id IS NULL "
"AND normalized_email IS NOT NULL)",
name="subject_identity",
),
sa.Index("hiv2_form_grants_form_contact_idx", "form_id", "contact_id"),
sa.Index("hiv2_form_grants_form_end_user_idx", "form_id", "end_user_id"),
sa.Index("hiv2_form_grants_form_email_idx", "form_id", "normalized_email"),
{"comment": "Frozen Human Input v2 form approval grants resolved from runtime recipients."},
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False, comment="Logical foreign key to tenants.id.")
form_id: Mapped[str] = mapped_column(
StringUUID, nullable=False, comment="Logical foreign key to human_input_v2_forms.id."
)
subject_type: Mapped[_HumanInputApproverGrantSubjectType] = mapped_column(
EnumText(_HumanInputApproverGrantSubjectType),
nullable=False,
comment="Discriminator for the business subject receiving approval authority.",
)
subject_key: Mapped[str] = mapped_column(
sa.String(255),
nullable=False,
comment=(
"Portable deduplication key: contact:<id>, end_user:<id>, or email_address:<sha256(normalized_email)>."
),
)
matched_sources: Mapped[FormApproverGrantMatchedSources] = mapped_column(
FrozenPydanticModelColumn(FormApproverGrantMatchedSources),
nullable=False,
default_factory=FormApproverGrantMatchedSources,
comment="Immutable recipient sources merged into this approver grant.",
)
subject_snapshot: Mapped[FormApproverGrantSubjectSnapshot] = mapped_column(
FrozenPydanticModelColumn(FormApproverGrantSubjectSnapshot),
nullable=False,
default_factory=FormApproverGrantSubjectSnapshot,
comment="Minimal display-only subject values captured when the grant was created.",
)
contact_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to human_input_contacts.id for a contact-backed grant.",
)
end_user_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to end_users.id for an app-scoped end-user grant.",
)
normalized_email: Mapped[str | None] = mapped_column(
sa.String(320),
nullable=True,
default=None,
comment="Lower-cased mailbox identity for a one-time email-address grant.",
)
form: Mapped[HumanInputV2Form] = relationship(
lambda: HumanInputV2Form,
primaryjoin=lambda: orm.foreign(HumanInputV2FormApproverGrant.form_id) == HumanInputV2Form.id,
viewonly=True,
lazy="raise",
init=False,
)
contact: Mapped[HumanInputContact | None] = relationship(
lambda: HumanInputContact,
primaryjoin=lambda: orm.foreign(HumanInputV2FormApproverGrant.contact_id) == HumanInputContact.id,
viewonly=True,
lazy="raise",
init=False,
)
endpoints: Mapped[list[HumanInputV2FormDeliveryEndpoint]] = relationship(
lambda: HumanInputV2FormDeliveryEndpoint,
primaryjoin=lambda: (
HumanInputV2FormApproverGrant.id == orm.foreign(HumanInputV2FormDeliveryEndpoint.approver_grant_id)
),
back_populates="approver_grant",
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputV2FormDeliveryEndpoint(DefaultFieldsDCMixin, TypeBase):
"""Immutable delivery or interaction endpoint belonging to one approver grant.
``address_hash`` supports portable uniqueness without indexing long recipient
addresses. Its canonical input must include the channel namespace and, for
IM endpoints, ``integration_id`` so equal provider user IDs from different
integrations remain distinct. IM endpoints retain the integration that owns
their provider identity so later retries cannot resolve through a replacement
integration. The task-creation application service owns the channel shape:
Email endpoints set only ``email_address``; IM endpoints set
``integration_id``, ``provider``, ``provider_user_id``, and
``im_identity_id``. Web and Console endpoints set none of those fields. The
database intentionally does not duplicate this discriminated-union rule;
the repository mapper rejects missing or cross-channel persisted fields on
load. Opaque public form tokens are persisted only as hashes.
"""
__tablename__ = "human_input_v2_form_delivery_endpoints"
__table_args__ = (
sa.UniqueConstraint(
"form_id",
"approver_grant_id",
"channel",
"address_hash",
name="hiv2_form_endpoints_grant_channel_address_uq",
),
sa.UniqueConstraint("access_token_hash", name="hiv2_form_endpoints_token_uq"),
sa.Index("hiv2_form_endpoints_identity_form_idx", "im_identity_id", "form_id"),
{"comment": "Immutable notification and interaction endpoints for Human Input v2 approver grants."},
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False, comment="Logical foreign key to tenants.id.")
form_id: Mapped[str] = mapped_column(
StringUUID, nullable=False, comment="Logical foreign key to human_input_v2_forms.id."
)
approver_grant_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Logical foreign key to human_input_v2_form_approver_grants.id.",
)
channel: Mapped[_HumanInputDeliveryChannel] = mapped_column(
EnumText(_HumanInputDeliveryChannel), nullable=False, comment="Delivery or interaction channel."
)
address_hash: Mapped[str] = mapped_column(
sa.String(64), nullable=False, comment="SHA-256 of the canonical channel address used for uniqueness."
)
email_address: Mapped[str | None] = mapped_column(
sa.String(320),
nullable=True,
default=None,
comment="Frozen recipient email address for Email endpoints; null otherwise.",
)
integration_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to human_input_im_integrations.id for IM endpoints; null otherwise.",
)
provider: Mapped[_IMProvider | None] = mapped_column(
EnumText(_IMProvider), nullable=True, default=None, comment="IM provider for IM endpoints only."
)
provider_user_id: Mapped[str | None] = mapped_column(
sa.String(255),
nullable=True,
default=None,
comment="Frozen provider-side user identifier for IM endpoints; null otherwise.",
)
provider_tenant_id: Mapped[str | None] = mapped_column(
sa.String(255),
nullable=True,
default=None,
comment="Frozen provider-side tenant identity for IM endpoints; null otherwise.",
)
im_identity_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to human_input_im_identities.id.",
)
im_binding_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Historical logical foreign key to human_input_im_bindings.id for IM endpoints.",
)
access_token_hash: Mapped[str | None] = mapped_column(
sa.String(64), nullable=True, default=None, comment="SHA-256 hash of an opaque form access token."
)
form: Mapped[HumanInputV2Form] = relationship(
lambda: HumanInputV2Form,
primaryjoin=lambda: orm.foreign(HumanInputV2FormDeliveryEndpoint.form_id) == HumanInputV2Form.id,
viewonly=True,
lazy="raise",
init=False,
)
approver_grant: Mapped[HumanInputV2FormApproverGrant] = relationship(
lambda: HumanInputV2FormApproverGrant,
primaryjoin=lambda: (
orm.foreign(HumanInputV2FormDeliveryEndpoint.approver_grant_id) == HumanInputV2FormApproverGrant.id
),
back_populates="endpoints",
viewonly=True,
lazy="raise",
init=False,
)
attempts: Mapped[list[HumanInputV2FormDeliveryAttempt]] = relationship(
lambda: HumanInputV2FormDeliveryAttempt,
primaryjoin=lambda: (
HumanInputV2FormDeliveryEndpoint.id == orm.foreign(HumanInputV2FormDeliveryAttempt.endpoint_id)
),
back_populates="endpoint",
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputV2FormDeliveryAttempt(DefaultFieldsDCMixin, TypeBase):
"""One delivery attempt whose failure never mutates the form status directly."""
__tablename__ = "human_input_v2_form_delivery_attempts"
__table_args__ = (
sa.UniqueConstraint("endpoint_id", "attempt_number", name="hiv2_form_attempts_endpoint_number_uq"),
sa.Index("hiv2_form_attempts_form_status_created_idx", "form_id", "status", "created_at", "id"),
sa.Index("hiv2_form_attempts_status_scheduled_idx", "status", "scheduled_at", "id"),
{"comment": "Append-oriented delivery attempts for Human Input v2 form endpoints."},
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False, comment="Logical foreign key to tenants.id.")
form_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Denormalized logical foreign key to human_input_v2_forms.id.",
)
endpoint_id: Mapped[str] = mapped_column(
StringUUID, nullable=False, comment="Logical foreign key to human_input_v2_form_delivery_endpoints.id."
)
attempt_number: Mapped[int] = mapped_column(
sa.Integer, nullable=False, comment="One-based retry sequence within an endpoint."
)
status: Mapped[_HumanInputDeliveryAttemptStatus] = mapped_column(
EnumText(_HumanInputDeliveryAttemptStatus), nullable=False, comment="Current delivery lifecycle state."
)
scheduled_at: Mapped[datetime] = mapped_column(
sa.DateTime, nullable=False, comment="Timestamp at which the attempt becomes eligible for processing."
)
started_at: Mapped[datetime | None] = mapped_column(
sa.DateTime, nullable=True, default=None, comment="Timestamp when provider delivery started."
)
finished_at: Mapped[datetime | None] = mapped_column(
sa.DateTime, nullable=True, default=None, comment="Terminal delivery timestamp."
)
provider_message_id: Mapped[str | None] = mapped_column(
sa.String(255),
nullable=True,
default=None,
comment="External provider message identifier; not a logical foreign key to a local table.",
)
failure_code: Mapped[str | None] = mapped_column(
sa.String(100), nullable=True, default=None, comment="Machine-readable terminal delivery failure code."
)
failure_reason: Mapped[str | None] = mapped_column(
LongText, nullable=True, default=None, comment="Operator-safe terminal delivery failure detail."
)
provider_response: Mapped[FormDeliveryProviderResponse | None] = mapped_column(
FrozenPydanticModelColumn(FormDeliveryProviderResponse),
nullable=True,
default=None,
comment="Immutable provider response Pydantic model retained for diagnostics.",
)
form: Mapped[HumanInputV2Form] = relationship(
lambda: HumanInputV2Form,
primaryjoin=lambda: orm.foreign(HumanInputV2FormDeliveryAttempt.form_id) == HumanInputV2Form.id,
viewonly=True,
lazy="raise",
init=False,
)
endpoint: Mapped[HumanInputV2FormDeliveryEndpoint] = relationship(
lambda: HumanInputV2FormDeliveryEndpoint,
primaryjoin=lambda: (
orm.foreign(HumanInputV2FormDeliveryAttempt.endpoint_id) == HumanInputV2FormDeliveryEndpoint.id
),
back_populates="attempts",
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputV2FormOTPChallenge(DefaultFieldsDCMixin, TypeBase):
"""Hashed Email proof session scoped to one form and approver grant.
Resend locks the stable grant row, invalidates the previous current session,
inserts its replacement, and calls a transaction-scoped audit port. The
Submission Runtime persistence layer owns the concrete shared audit record.
``contact_id`` captures the Contact incarnation so deleting and recreating
the same Email cannot make historical proof current. Plaintext code and
challenge tokens never enter this record. Domain reconstruction requires
``resend_after`` and ``expires_at`` to retain their issuance-time durations.
"""
__tablename__ = "human_input_v2_form_otp_challenges"
__table_args__ = (
sa.UniqueConstraint("challenge_token_hash", name="hiv2_form_otp_challenges_token_uq"),
sa.CheckConstraint(
"(subject_type = 'contact' AND contact_id IS NOT NULL) OR "
"(subject_type = 'email_address' AND contact_id IS NULL)",
name=sa.schema.conv("hiv2_form_otp_challenges_subject_identity_ck"),
),
sa.CheckConstraint(
"send_count >= 1 AND send_count <= 5",
name=sa.schema.conv("hiv2_form_otp_challenges_send_count_ck"),
),
sa.CheckConstraint(
"attempt_count >= 0 AND attempt_count <= 5",
name=sa.schema.conv("hiv2_form_otp_challenges_attempt_count_ck"),
),
sa.CheckConstraint(
"(status = 'verified' AND verified_at IS NOT NULL AND invalidated_at IS NULL) OR "
"(status = 'invalidated' AND verified_at IS NULL AND invalidated_at IS NOT NULL) OR "
"(status IN ('pending', 'expired') AND verified_at IS NULL AND invalidated_at IS NULL)",
name=sa.schema.conv("hiv2_form_otp_challenges_terminal_timestamps_ck"),
),
sa.Index(
"hiv2_form_otp_scope_created_idx",
"tenant_id",
"form_id",
"approver_grant_id",
"created_at",
"id",
),
{"comment": "Hashed OTP proof sessions for email-based Human Input v2 approval."},
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False, comment="Logical foreign key to tenants.id.")
form_id: Mapped[str] = mapped_column(
StringUUID, nullable=False, comment="Logical foreign key to human_input_v2_forms.id."
)
approver_grant_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Logical foreign key to human_input_v2_form_approver_grants.id.",
)
subject_type: Mapped[_HumanInputApproverGrantSubjectType] = mapped_column(
EnumText(_HumanInputApproverGrantSubjectType),
nullable=False,
comment="Contact or standalone Email identity verified by this proof session.",
)
challenge_token_hash: Mapped[str] = mapped_column(
sa.String(64), nullable=False, comment="SHA-256 hash of the ephemeral challenge token."
)
code_hash: Mapped[str] = mapped_column(
sa.String(255), nullable=False, comment="Slow password hash of the one-time verification code."
)
code_hash_algorithm: Mapped[str] = mapped_column(
sa.String(50), nullable=False, comment="Verifier algorithm discriminator for code_hash."
)
email_hash: Mapped[str] = mapped_column(
sa.String(64), nullable=False, comment="SHA-256 of the normalized destination email."
)
email: Mapped[str] = mapped_column(
sa.String(320), nullable=False, comment="Destination email used by this OTP challenge."
)
status: Mapped[_HumanInputOTPChallengeStatus] = mapped_column(
EnumText(_HumanInputOTPChallengeStatus), nullable=False, comment="Current proof-session usability."
)
expires_at: Mapped[datetime] = mapped_column(sa.DateTime, nullable=False, comment="Challenge expiration timestamp.")
resend_after: Mapped[datetime] = mapped_column(
sa.DateTime, nullable=False, comment="Earliest timestamp at which a replacement may be issued."
)
contact_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to human_input_contacts.id for the captured Contact incarnation.",
)
send_count: Mapped[int] = mapped_column(
sa.Integer, nullable=False, default=1, comment="Number of OTP emails issued for this form approver grant."
)
attempt_count: Mapped[int] = mapped_column(
sa.Integer, nullable=False, default=0, comment="Number of failed or completed verification attempts."
)
verified_at: Mapped[datetime | None] = mapped_column(
sa.DateTime, nullable=True, default=None, comment="Timestamp of successful OTP verification."
)
invalidated_at: Mapped[datetime | None] = mapped_column(
sa.DateTime, nullable=True, default=None, comment="Timestamp when resend or identity change invalidated it."
)
form: Mapped[HumanInputV2Form] = relationship(
lambda: HumanInputV2Form,
primaryjoin=lambda: orm.foreign(HumanInputV2FormOTPChallenge.form_id) == HumanInputV2Form.id,
viewonly=True,
lazy="raise",
init=False,
)
approver_grant: Mapped[HumanInputV2FormApproverGrant] = relationship(
lambda: HumanInputV2FormApproverGrant,
primaryjoin=lambda: (
orm.foreign(HumanInputV2FormOTPChallenge.approver_grant_id) == HumanInputV2FormApproverGrant.id
),
viewonly=True,
lazy="raise",
init=False,
)
contact: Mapped[HumanInputContact | None] = relationship(
lambda: HumanInputContact,
primaryjoin=lambda: orm.foreign(HumanInputV2FormOTPChallenge.contact_id) == HumanInputContact.id,
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputV2FormSubmission(DefaultFieldsDCMixin, TypeBase):
"""Immutable winning submission for a Human Input form.
The unique ``form_id`` constraint is the database-level first-success-wins
guard. Insert this row and transition ``HumanInputV2Form.status`` in one transaction.
The referenced audit event must be ``submission_authorized`` and must describe
the same form, approver grant, and optional endpoint; the application service
creates both records in that transaction.
``input_snapshot`` contains only the unvalidated request ``inputs`` object;
it never stores the whole request, action, OTP, token, session, or proof data.
``canonical_values`` contains the validated execution-time source of truth,
currently persisted using Segment serialization.
"""
__tablename__ = "human_input_v2_form_submissions"
__table_args__ = (
sa.UniqueConstraint("form_id", name="hiv2_form_submissions_form_uq"),
sa.UniqueConstraint(
"authorization_audit_event_id",
name="hiv2_submission_authorization_audit_event_uq",
),
sa.CheckConstraint(
"(actor_type = 'account' AND actor_account_id IS NOT NULL AND actor_end_user_id IS NULL "
"AND actor_normalized_email IS NULL) OR "
"(actor_type = 'end_user' AND actor_account_id IS NULL AND actor_end_user_id IS NOT NULL "
"AND actor_normalized_email IS NULL) OR "
"(actor_type = 'email_address' AND actor_account_id IS NULL AND actor_end_user_id IS NULL "
"AND actor_normalized_email IS NOT NULL)",
name=sa.schema.conv("hiv2_form_submissions_actor_identity_ck"),
),
sa.Index("hiv2_form_submissions_tenant_submitted_idx", "tenant_id", "submitted_at", "id"),
{"comment": "Immutable first successful Human Input v2 submission and its business actor."},
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False, comment="Logical foreign key to tenants.id.")
form_id: Mapped[str] = mapped_column(
StringUUID, nullable=False, comment="Logical foreign key to human_input_v2_forms.id."
)
approver_grant_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Logical foreign key to human_input_v2_form_approver_grants.id for the exercised grant.",
)
actor_type: Mapped[_HumanInputSubmissionActorType] = mapped_column(
EnumText(_HumanInputSubmissionActorType),
nullable=False,
comment="Discriminator for the business identity that completed the submission.",
)
authorization_audit_event_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Logical foreign key to the submission_authorized human_input_v2_form_audit_events.id.",
)
selected_action_id: Mapped[str] = mapped_column(
sa.String(200),
nullable=False,
comment="Action identifier from the frozen form configuration; not a logical foreign key to a table.",
)
input_snapshot: Mapped[FormInputSnapshot] = mapped_column(
FrozenPydanticModelColumn(FormInputSnapshot),
nullable=False,
comment="Unvalidated raw form values from request.inputs only; this is not the complete HTTP request.",
)
canonical_values: Mapped[FormCanonicalValues] = mapped_column(
FrozenPydanticModelColumn(FormCanonicalValues),
nullable=False,
comment="Validated canonical runtime values persisted using Segment serialization; execution source of truth.",
)
submitted_at: Mapped[datetime] = mapped_column(
sa.DateTime, nullable=False, comment="Timestamp when the first successful submission committed."
)
actor_account_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to accounts.id for an account actor.",
)
actor_end_user_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to end_users.id for an end-user actor.",
)
actor_normalized_email: Mapped[str | None] = mapped_column(
sa.String(320),
nullable=True,
default=None,
comment="Lower-cased mailbox identity for an email-address actor.",
)
endpoint_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to human_input_v2_form_delivery_endpoints.id, when submitted through an endpoint.",
)
form: Mapped[HumanInputV2Form] = relationship(
lambda: HumanInputV2Form,
primaryjoin=lambda: orm.foreign(HumanInputV2FormSubmission.form_id) == HumanInputV2Form.id,
viewonly=True,
lazy="raise",
init=False,
)
approver_grant: Mapped[HumanInputV2FormApproverGrant] = relationship(
lambda: HumanInputV2FormApproverGrant,
primaryjoin=lambda: (
orm.foreign(HumanInputV2FormSubmission.approver_grant_id) == HumanInputV2FormApproverGrant.id
),
viewonly=True,
lazy="raise",
init=False,
)
endpoint: Mapped[HumanInputV2FormDeliveryEndpoint | None] = relationship(
lambda: HumanInputV2FormDeliveryEndpoint,
primaryjoin=lambda: orm.foreign(HumanInputV2FormSubmission.endpoint_id) == HumanInputV2FormDeliveryEndpoint.id,
viewonly=True,
lazy="raise",
init=False,
)
authorization_audit_event: Mapped[HumanInputV2FormAuditEvent] = relationship(
lambda: HumanInputV2FormAuditEvent,
primaryjoin=lambda: (
orm.foreign(HumanInputV2FormSubmission.authorization_audit_event_id) == HumanInputV2FormAuditEvent.id
),
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputV2FormAuditEvent(DefaultFieldsDCMixin, TypeBase):
"""Append-only audit fact for resolution, access, delivery, or submission.
A successful submission references its ``submission_authorized`` event, whose
``authorization_proof`` retains verified evidence without reusable secrets.
Its business actor remains exclusively on ``HumanInputV2FormSubmission``;
rejected attempts remain audit-only facts because they produce no Submission.
"""
__tablename__ = "human_input_v2_form_audit_events"
__table_args__ = (
sa.Index("hiv2_form_audit_form_occurred_idx", "form_id", "occurred_at", "id"),
sa.Index("hiv2_form_audit_tenant_occurred_idx", "tenant_id", "occurred_at", "id"),
sa.CheckConstraint(
"event_type <> 'submission_authorized' OR "
"(approver_grant_id IS NOT NULL AND authorization_proof IS NOT NULL)",
name=sa.schema.conv("hiv2_form_audit_authorized_proof_ck"),
),
sa.CheckConstraint(
"event_type <> 'submission_rejected' OR reason_code IS NOT NULL",
name=sa.schema.conv("hiv2_form_audit_rejection_reason_ck"),
),
{"comment": "Append-only Human Input v2 audit facts for security and operational queries."},
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False, comment="Logical foreign key to tenants.id.")
form_id: Mapped[str] = mapped_column(
StringUUID, nullable=False, comment="Logical foreign key to human_input_v2_forms.id."
)
event_type: Mapped[str] = mapped_column(
sa.String(64), nullable=False, comment="Stable event name such as access_checked or submission_rejected."
)
occurred_at: Mapped[datetime] = mapped_column(
sa.DateTime, nullable=False, comment="Business timestamp at which the audited fact occurred."
)
approver_grant_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to human_input_v2_form_approver_grants.id, when a grant was resolved.",
)
endpoint_id: Mapped[str | None] = mapped_column(
StringUUID,
nullable=True,
default=None,
comment="Logical foreign key to human_input_v2_form_delivery_endpoints.id, when an endpoint was involved.",
)
channel: Mapped[_HumanInputDeliveryChannel | None] = mapped_column(
EnumText(_HumanInputDeliveryChannel),
nullable=True,
default=None,
comment="Channel from which the event originated.",
)
reason_code: Mapped[str | None] = mapped_column(
sa.String(100), nullable=True, default=None, comment="Machine-readable authorization or delivery reason."
)
reason_message: Mapped[str | None] = mapped_column(
LongText, nullable=True, default=None, comment="Operator-safe diagnostic detail."
)
authorization_proof: Mapped[FormAuthorizationProof | None] = mapped_column(
FrozenPydanticModelColumn(
_FORM_AUTHORIZATION_PROOF_ADAPTER,
model_types=(
AccountSessionAuthorizationProof,
EmailOTPAuthorizationProof,
IMIdentityAuthorizationProof,
TrustedEndUserAuthorizationProof,
),
),
nullable=True,
default=None,
comment="Verified authorization evidence; raw OTP codes, tokens, and signatures are never persisted.",
)
event_payload: Mapped[FormAuditEventPayload | None] = mapped_column(
FrozenPydanticModelColumn(FormAuditEventPayload),
nullable=True,
default=None,
comment="Immutable event-specific Pydantic model not used as a primary query predicate.",
)
form: Mapped[HumanInputV2Form] = relationship(
lambda: HumanInputV2Form,
primaryjoin=lambda: orm.foreign(HumanInputV2FormAuditEvent.form_id) == HumanInputV2Form.id,
viewonly=True,
lazy="raise",
init=False,
)
approver_grant: Mapped[HumanInputV2FormApproverGrant | None] = relationship(
lambda: HumanInputV2FormApproverGrant,
primaryjoin=lambda: (
orm.foreign(HumanInputV2FormAuditEvent.approver_grant_id) == HumanInputV2FormApproverGrant.id
),
viewonly=True,
lazy="raise",
init=False,
)
endpoint: Mapped[HumanInputV2FormDeliveryEndpoint | None] = relationship(
lambda: HumanInputV2FormDeliveryEndpoint,
primaryjoin=lambda: orm.foreign(HumanInputV2FormAuditEvent.endpoint_id) == HumanInputV2FormDeliveryEndpoint.id,
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputV2FormUploadToken(DefaultFieldsDCMixin, TypeBase):
"""Hashed upload capability scoped to one Human Input v2 delivery endpoint.
The opaque token is returned only at creation time. Persistence retains its
SHA-256 hash so an upload credential cannot be recovered from the database.
Endpoint scoping keeps public upload access within the same form surface that
issued the capability.
"""
__tablename__ = "human_input_v2_form_upload_tokens"
__table_args__ = (
sa.UniqueConstraint("upload_token_hash", name="hiv2_form_upload_tokens_hash_uq"),
sa.Index("hiv2_form_upload_tokens_form_endpoint_idx", "form_id", "endpoint_id"),
{"comment": "Hashed endpoint-scoped upload capabilities for Human Input v2 forms."},
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False, comment="Logical foreign key to tenants.id.")
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False, comment="Logical foreign key to apps.id.")
form_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Logical foreign key to human_input_v2_forms.id.",
)
endpoint_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Logical foreign key to human_input_v2_form_delivery_endpoints.id.",
)
upload_token_hash: Mapped[str] = mapped_column(
sa.String(64),
nullable=False,
comment="SHA-256 hash of the opaque upload token; plaintext tokens are never persisted.",
)
form: Mapped[HumanInputV2Form] = relationship(
lambda: HumanInputV2Form,
primaryjoin=lambda: orm.foreign(HumanInputV2FormUploadToken.form_id) == HumanInputV2Form.id,
viewonly=True,
lazy="raise",
init=False,
)
endpoint: Mapped[HumanInputV2FormDeliveryEndpoint] = relationship(
lambda: HumanInputV2FormDeliveryEndpoint,
primaryjoin=lambda: orm.foreign(HumanInputV2FormUploadToken.endpoint_id) == HumanInputV2FormDeliveryEndpoint.id,
viewonly=True,
lazy="raise",
init=False,
)
class HumanInputV2FormUploadFile(DefaultFieldsDCMixin, TypeBase):
"""Durable association between one endpoint-scoped upload capability and file."""
__tablename__ = "human_input_v2_form_upload_files"
__table_args__ = (
sa.UniqueConstraint("upload_file_id", name="hiv2_form_upload_files_file_uq"),
sa.Index("hiv2_form_upload_files_form_endpoint_idx", "form_id", "endpoint_id"),
sa.Index("hiv2_form_upload_files_token_idx", "upload_token_id"),
{"comment": "Durable Human Input v2 form, endpoint, upload-token, and file associations."},
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False, comment="Logical foreign key to tenants.id.")
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False, comment="Logical foreign key to apps.id.")
form_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Logical foreign key to human_input_v2_forms.id.",
)
endpoint_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Logical foreign key to human_input_v2_form_delivery_endpoints.id.",
)
upload_file_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Logical foreign key to upload_files.id.",
)
upload_token_id: Mapped[str] = mapped_column(
StringUUID,
nullable=False,
comment="Logical foreign key to human_input_v2_form_upload_tokens.id.",
)
form: Mapped[HumanInputV2Form] = relationship(
lambda: HumanInputV2Form,
primaryjoin=lambda: orm.foreign(HumanInputV2FormUploadFile.form_id) == HumanInputV2Form.id,
viewonly=True,
lazy="raise",
init=False,
)
upload_token: Mapped[HumanInputV2FormUploadToken] = relationship(
lambda: HumanInputV2FormUploadToken,
primaryjoin=lambda: orm.foreign(HumanInputV2FormUploadFile.upload_token_id) == HumanInputV2FormUploadToken.id,
viewonly=True,
lazy="raise",
init=False,
)
endpoint: Mapped[HumanInputV2FormDeliveryEndpoint] = relationship(
lambda: HumanInputV2FormDeliveryEndpoint,
primaryjoin=lambda: orm.foreign(HumanInputV2FormUploadFile.endpoint_id) == HumanInputV2FormDeliveryEndpoint.id,
viewonly=True,
lazy="raise",
init=False,
)
__all__ = [
"AccountSessionAuthorizationProof",
"DingTalkIMIntegrationEncryptedCredentials",
"EmailOTPAuthorizationProof",
"FeishuIMIntegrationEncryptedCredentials",
"FormApproverGrantMatchedSource",
"FormApproverGrantMatchedSources",
"FormApproverGrantSubjectSnapshot",
"FormAuditEventPayload",
"FormAuthorizationProof",
"FormCanonicalValues",
"FormDeliveryProviderResponse",
"FormInputSnapshot",
"HumanInputContact",
"HumanInputContactIdentitySource",
"HumanInputEmailProvider",
"HumanInputIMBinding",
"HumanInputIMIdentity",
"HumanInputIMIntegration",
"HumanInputIMSyncResult",
"HumanInputIMSyncRun",
"HumanInputPlatformContactWorkspaceEntry",
"HumanInputV2Form",
"HumanInputV2FormApproverGrant",
"HumanInputV2FormAuditEvent",
"HumanInputV2FormDefinition",
"HumanInputV2FormDeliveryAttempt",
"HumanInputV2FormDeliveryEndpoint",
"HumanInputV2FormOTPChallenge",
"HumanInputV2FormSubmission",
"HumanInputV2FormUploadFile",
"HumanInputV2FormUploadToken",
"IMIdentityAuthorizationProof",
"IMIdentityRawPayload",
"IMIntegrationEncryptedCredentials",
"IMSyncContactSnapshot",
"IMSyncDirectoryEntryPayload",
"IMSyncIdentitySnapshot",
"LarkIMIntegrationEncryptedCredentials",
"MSTeamsIMIntegrationEncryptedCredentials",
"ResendEmailProviderEncryptedCredentials",
"SlackIMIntegrationEncryptedCredentials",
"TrustedEndUserAuthorizationProof",
"WeComIMIntegrationEncryptedCredentials",
]