1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# =============================================================================
# Domain: Integrations
# Entity: IntegrationAccount
# Description: One OAuth account connection to an external provider (gmail /
# outlook mail, Google / Microsoft calendar). The account row is the FLOW's own
# bookkeeping — binding, lifecycle, and the honest expiry MIRROR. It carries NO
# secret material: token bundles live only in the fenced credential store,
# reached through the OAuthCredentialStore port (never a Cargo edge).
#
# The lifecycle is one hand_set enum (no boolean impostors): pending → active |
# revoked; active → expired | revoked; expired and revoked are TERMINAL. A
# re-authorization replaces a terminal row (delete + fresh insert) rather than
# transitioning out of a terminal state — the same replacement-not-mutation
# discipline the credential store's rotate lineage uses. ADR-0015 note: the
# expires_at NOT-NULL-when-active rule is service-enforced on every write path
# here, but raw SQL can null it — expiry truth lives in the store; this mirror
# is advisory for the scheduler and API surface.
# =============================================================================
models:
- name: IntegrationAccount
collection: integration_accounts
description: "One OAuth account connection to an external provider (mail or calendar); flow state + honest-expiry mirror, never secret material"
generators:
disabled:
- handler
- usecase
- cqrs
- projection
- bulk-operations
- seeder
- integration-test
- openapi
fields:
id:
type: uuid
attributes:
description: "Unique account id"
provider:
type: OAuthProvider
attributes:
description: "Which OAuth provider this account connects to (adapter data — endpoints, scopes, PKCE — comes from the provider registry)"
account_ref:
type: string
attributes:
description: "The provider-side identity this account claims: the mailbox address for mail providers, the user subject (or address) for calendars. Verified against the provider's identity read before the account goes active"
# hand_set lifecycle (ADR-0016 pattern 2): pending → active | revoked;
# active → expired | revoked; expired/revoked terminal. `expired` is set
# by the refresh path when the provider answers invalid_grant (the user
# must reconnect); `revoked` by disconnect. Re-authorization replaces the
# row instead of un-terminal-ing it.
status:
type: IntegrationAccountStatus
attributes:
description: "Connection lifecycle (pending = authorization started, not yet verified; only status metadata crosses HTTP)"
scopes:
type: string
attributes:
description: "The scopes granted for this connection (space-delimited, as the provider echoes them)"
# Transient PKCE material — NOT a stored credential: single-use, lives
# only while status=pending, at most the state TTL, cleared the moment
# the code exchange consumes it. Never selected into any HTTP response
# and never sent anywhere except the one token-endpoint exchange.
pkce_verifier:
type: string?
attributes:
description: "PKCE S256 code_verifier for the in-flight authorization (pending-only, single-use, cleared on completion)"
# Honest-expiry MIRROR of the stored credential's expires_at (set from
# the provider's real expires_in — never NULL while status=active on the
# service path). Advisory: the credential store is the expiry authority.
expires_at:
type: datetime?
description: "Mirror of the stored credential's honest expiry; drives refresh-before-expiry scheduling"
last_refreshed_at:
type: datetime?
description: "When the token bundle was last obtained or rotated (observability)"
metadata:
type: Metadata
attributes:
description: "Audit metadata"
indexes:
# No tenancy indexes (ADR-0029): the module is tenant-agnostic. The per-unit
# one-connection unique — one live account per (provider, account_ref) per
# org unit — is installed by the composing service's tenancy decorator
# (org_unit_id-leading), not declared here.
enums:
- name: OAuthProvider
description: "The OAuth providers the one generation serves (they differ only in adapter data)"
variants:
- name: gmail
description: "Google mail (SMTP/IMAP via OAuth)"
default: true
- name: outlook
description: "Microsoft mail (SMTP/IMAP via OAuth)"
- name: google_calendar
description: "Google Calendar"
- name: microsoft_calendar
description: "Microsoft Outlook Calendar (Graph)"
- name: IntegrationAccountStatus
description: "Lifecycle of an OAuth account connection"
variants:
- name: pending
description: "Authorization started (state minted); not yet identity-verified"
default: true
- name: active
description: "Code exchanged, identity verified, credential stored; expires_at mirrored"
- name: expired
description: "Provider rejected the refresh grant (invalid_grant) — terminal; the user must reconnect (a fresh authorization replaces the row)"
- name: revoked
description: "Disconnected by an operator — terminal; credential revoked in the store"