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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//! **NOT a stable public API.** Engine-tier binding primitive — published to
//! crates.io only because the SDK closure requires it on the registry; 3rd
//! parties never name this crate. They meet the value-sets it binds through an
//! SDK product facade or a wire contract, never here.
//!
//! # Schema-Constrained Value-Sets (the enum ↔ `CHECK` anchor)
//!
//! A *schema-constrained* enum is a domain value-set whose members are also
//! enumerated by a PostgreSQL `CHECK (col IN (…))` constraint. The enum and the
//! constraint are two reifications of one fact ("the legal values of this
//! column"); left unbound they drift independently — a migration widens the
//! `CHECK`, or a variant is added, and the other side silently goes stale.
//!
//! This crate is the single seam that binds them. The same drift class exists
//! on both sides of the monorepo (PAS `scaccounts`, PCS `scchat`) and neither
//! core may depend on the other, so the binding primitive is hoisted out of
//! both — a pure, dependency-free trait + macro (`std::BTreeSet` only), which
//! both cores (that ban IO/transport crates) can depend on.
//!
//! ## Why engine tier and not `crates/shared/`
//!
//! It sat in `crates/shared/` (`publish = false`) until `RFC_202607252223`
//! T-03, which is when the placement was first *tested* rather than assumed:
//! `ppoppo-identity` needs to enroll its own `EntityType`, and `engine →
//! shared` is forbidden by the crate lattice (`xtask::policy::rules::taxonomy`)
//! — so the enrollment was unreachable, and the vocabulary had to keep a
//! second PAS-local enum alive just to carry it.
//!
//! The fix was to notice that the folder was wrong, not the lattice. Engine
//! tier means *published substrate that no 3rd party names* — a dependency-free
//! trait + macro consumed by two service cores and one vocabulary crate is
//! exactly that. The move corrected a misfile that predates the tier; it did
//! not trade a principle for convenience.
//!
//! ## The anchor triple (per `STS_SSOT_GOVERNANCE`)
//!
//! - **Owner**: the domain enum in `accounts-core` / `accounts-api` /
//! `chat-core` (the closest reified form of the value-set decision).
//! - **Anchor**: domain-specific — these are tuned domain vocabularies with no
//! external standard, so *this crate doc-comment is the anchor of record*
//! (governance §4). (Formerly PAS
//! `ADR_202605242324_schema-constrained-value-sets.md`, folded into
//! `accounts-core` on its retirement, then hoisted here when PCS adopted the
//! same gate.)
//! - **Verification**: [`bindings`](SchemaConstrained::bindings) feeds each
//! service's `schema_check_drift.rs` DB test
//! (`accounts-api/tests/` for `scaccounts`, `chat-api/tests/` for `scchat`),
//! which reads each `CHECK` from the *materialized* schema
//! (`pg_get_constraintdef`) and asserts set-equality with `ALL`. The
//! compile-time half lives in the [`impl_schema_constrained!`] macro: it
//! emits an exhaustive `match`, so adding a variant without listing it fails
//! to build.
//!
//! ## Why a DB test and not a file parse
//!
//! The value-sets evolve through `ALTER … DROP/ADD CONSTRAINT` migrations (e.g.
//! `lifecycle_state` gained `tombstoned`; `oauth_audit_events.event_type`
//! gained `otp_issue`/`otp_verify`). The authoritative set is therefore the
//! *result of applying every migration*, which only the database knows —
//! parsing the baseline `.sql` would report phantom drift. Asking Postgres via
//! `pg_get_constraintdef` is the only correct anchor (and avoids the brittle
//! bespoke-parser ops-tax rejected in `STS_RATE_LIMITS_PPOPPO` §Anchor
//! "Rationale" option A).
//!
//! ## Two more rejected alternatives
//!
//! - **`#[sqlx::Type]` alone.** Binds the column *type* (text), not the
//! `CHECK`'s value-set — a typo in the enum still compiles and the set still
//! drifts. Complementary at the query boundary, not a substitute for the
//! verification.
//! - **Accept drift under human review.** Leaves security-adjacent value-sets
//! (audit taxonomy, lifecycle, step-up purpose) under governance §2's
//! "aspiration, not enforcement" gate. Rejected.
//!
//! ## Caveat — the value-equality half is integration-tier
//!
//! The compile-time exhaustiveness guard covers the Rust side on every build.
//! The `ALL`-vs-`CHECK` set-equality half needs a live database, and there is
//! no CI job running DB-backed tests — so it bites via each service's
//! `just test-integration` and the `/deploy-ppoppo` pre-flight, not on a plain
//! `cargo test`.
use BTreeSet;
/// One `(constraint, allowed-value-set)` pair, erased of the originating enum
/// type so the drift test can iterate heterogeneous value-sets.
/// A domain value-set whose members are mirrored by one or more SQL `CHECK`
/// constraints.
///
/// Implemented via [`impl_schema_constrained!`]; never hand-written, so the
/// compile-time exhaustiveness guard is always emitted alongside.
/// Implement [`SchemaConstrained`] for a unit-variant enum and emit a
/// compile-time exhaustiveness guard from the same variant list.
///
/// ```ignore
/// ppoppo_schema_constrained::impl_schema_constrained!(EntityType via as_str {
/// all: [Human, AiAgent, Enterprise, Programmable, Mask],
/// constraints: ["ck_ppnums_entity_type_enum"],
/// });
/// ```
///
/// `via $method` is the enum's inherent value accessor (`as_str` for most,
/// `as_db_str` / `as_wire` for others). Place the invocation next to the enum
/// so the constraint name lives *on the fact* (governance §6 carrier).
///
/// `non_stored` lists render-only variants that exist in the enum but are never
/// persisted (and so never appear in the `CHECK`) — e.g. `EntityType::Delegated`.
/// They are excluded from `ALL` yet still covered by the exhaustiveness guard,
/// so the asymmetry is declared, not hidden.
) => ;
}