@happyvertical/smrt-profiles 0.39.13 → 0.39.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +69 -5
- package/README.md +101 -50
- package/dist/chunks/{ApiKey-BrNyaaZN.js → ApiKey-Br9NGBnr.js} +3 -3
- package/dist/chunks/{ApiKey-BrNyaaZN.js.map → ApiKey-Br9NGBnr.js.map} +1 -1
- package/dist/chunks/{ApiKeyCollection-DNhDkS7O.js → ApiKeyCollection-CrTjdMz8.js} +3 -3
- package/dist/chunks/{ApiKeyCollection-DNhDkS7O.js.map → ApiKeyCollection-CrTjdMz8.js.map} +1 -1
- package/dist/chunks/{AuditLogCollection-CnIYU0aK.js → AuditLogCollection-BBIEI6BD.js} +2 -2
- package/dist/chunks/{AuditLogCollection-CnIYU0aK.js.map → AuditLogCollection-BBIEI6BD.js.map} +1 -1
- package/dist/chunks/{NostrIdentityCollection-CG6HGf6c.js → NostrIdentity-DF_a_WZk.js} +6 -125
- package/dist/chunks/NostrIdentity-DF_a_WZk.js.map +1 -0
- package/dist/chunks/NostrIdentityCollection-BRYUgt47.js +124 -0
- package/dist/chunks/NostrIdentityCollection-BRYUgt47.js.map +1 -0
- package/dist/chunks/OidcIdentity-ChkiT6hg.js +111 -0
- package/dist/chunks/OidcIdentity-ChkiT6hg.js.map +1 -0
- package/dist/chunks/OidcIdentityCollection-Ce4yv8p0.js +76 -0
- package/dist/chunks/OidcIdentityCollection-Ce4yv8p0.js.map +1 -0
- package/dist/chunks/{ProfileAssetCollection-DzUwbtTq.js → ProfileAssetCollection-CtlvXbGy.js} +3 -3
- package/dist/chunks/{ProfileAssetCollection-DzUwbtTq.js.map → ProfileAssetCollection-CtlvXbGy.js.map} +1 -1
- package/dist/chunks/ProfileCollection-Bix9yAiy.js +1161 -0
- package/dist/chunks/ProfileCollection-Bix9yAiy.js.map +1 -0
- package/dist/chunks/{ProfileMetadataCollection-CGcO4Iw_.js → ProfileMetadataCollection-loKVv_pj.js} +2 -2
- package/dist/chunks/{ProfileMetadataCollection-CGcO4Iw_.js.map → ProfileMetadataCollection-loKVv_pj.js.map} +1 -1
- package/dist/chunks/{ProfileMetafieldCollection-C8TX9HvN.js → ProfileMetafieldCollection-CACMY1hl.js} +2 -2
- package/dist/chunks/{ProfileMetafieldCollection-C8TX9HvN.js.map → ProfileMetafieldCollection-CACMY1hl.js.map} +1 -1
- package/dist/chunks/{ProfileRelationshipCollection-JnskbZTd.js → ProfileRelationshipCollection-CzQKfvMy.js} +5 -5
- package/dist/chunks/{ProfileRelationshipCollection-JnskbZTd.js.map → ProfileRelationshipCollection-CzQKfvMy.js.map} +1 -1
- package/dist/chunks/{ProfileRelationshipTermCollection-UopP7sja.js → ProfileRelationshipTermCollection-DzmU48P6.js} +2 -2
- package/dist/chunks/{ProfileRelationshipTermCollection-UopP7sja.js.map → ProfileRelationshipTermCollection-DzmU48P6.js.map} +1 -1
- package/dist/chunks/{ProfileRelationshipType-OMBn5cWR.js → ProfileRelationshipType-BjoaCt-u.js} +2 -2
- package/dist/chunks/{ProfileRelationshipType-OMBn5cWR.js.map → ProfileRelationshipType-BjoaCt-u.js.map} +1 -1
- package/dist/chunks/{ProfileRelationshipTypeCollection-CQu08kxF.js → ProfileRelationshipTypeCollection-Bh4xJ4-I.js} +3 -3
- package/dist/chunks/{ProfileRelationshipTypeCollection-CQu08kxF.js.map → ProfileRelationshipTypeCollection-Bh4xJ4-I.js.map} +1 -1
- package/dist/chunks/oidcProvisioningPrimitives-ZuVcDPt-.js +138 -0
- package/dist/chunks/oidcProvisioningPrimitives-ZuVcDPt-.js.map +1 -0
- package/dist/chunks/resolveIdentity-_h_2sGqJ.js +260 -0
- package/dist/chunks/resolveIdentity-_h_2sGqJ.js.map +1 -0
- package/dist/index.d.ts +127 -15
- package/dist/index.js +30 -273
- package/dist/index.js.map +1 -1
- package/dist/internal/oidc-provisioning.d.ts +17 -0
- package/dist/internal/oidc-provisioning.js +13 -0
- package/dist/internal/oidc-provisioning.js.map +1 -0
- package/dist/manifest.json +431 -46
- package/dist/oidc-provisioning.d.ts +83 -0
- package/dist/smrt-knowledge.json +105 -43
- package/dist/types.d.ts +26 -2
- package/dist/utils.d.ts +26 -2
- package/dist/utils.js +4 -4
- package/package.json +10 -6
- package/dist/chunks/NostrIdentityCollection-CG6HGf6c.js.map +0 -1
- package/dist/chunks/OidcIdentityCollection-DiLneB7J.js +0 -158
- package/dist/chunks/OidcIdentityCollection-DiLneB7J.js.map +0 -1
- package/dist/chunks/ProfileCollection-BOMOloTL.js +0 -641
- package/dist/chunks/ProfileCollection-BOMOloTL.js.map +0 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { getDatabase } from '@happyvertical/sql';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Coordinate one OIDC provisioning operation across adapters.
|
|
5
|
+
*
|
|
6
|
+
* Root adapters must expose `beginTransaction`. Already-bound adapters must
|
|
7
|
+
* support savepoints. An adapter that exposes only `transaction()` is
|
|
8
|
+
* intentionally ambiguous and fails closed instead of risking a nested
|
|
9
|
+
* transaction that could roll back its caller's work.
|
|
10
|
+
*/
|
|
11
|
+
export declare function coordinateOidcProvisioning<T>(options: OidcProvisioningCoordinatorOptions<T>): Promise<T>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Trusted integration surface for framework packages participating in the
|
|
15
|
+
* owner-aware OIDC provisioning transaction.
|
|
16
|
+
*
|
|
17
|
+
* Application code should use UserCollection.getOrCreateFromOidc() instead.
|
|
18
|
+
* The generic result avoids duplicating nominal SMRT model declarations across
|
|
19
|
+
* package entry bundles; trusted callers bind it to the root package model.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createOidcProvisioningPerson<TProfile>(db: DatabaseInterface, input: OidcProvisioningPersonInput): Promise<TProfile>;
|
|
22
|
+
|
|
23
|
+
declare type DatabaseInterface = Awaited<ReturnType<typeof getDatabase>>;
|
|
24
|
+
|
|
25
|
+
declare type DatabaseInterface_2 = Awaited<ReturnType<typeof getDatabase>>;
|
|
26
|
+
|
|
27
|
+
/** Trusted integration insert after the caller proves Profile ownership. */
|
|
28
|
+
export declare function insertOidcProvisioningIdentity<TIdentity>(db: DatabaseInterface, input: OidcProvisioningIdentityInput): Promise<TIdentity>;
|
|
29
|
+
|
|
30
|
+
/** Detect an adapter error caused by an aborted transaction. */
|
|
31
|
+
export declare function isOidcAbortedTransactionError(error: unknown): boolean;
|
|
32
|
+
|
|
33
|
+
/** Classify only the durable constraints used to converge OIDC retries. */
|
|
34
|
+
export declare function isOidcProvisioningRaceConflict(error: unknown, options?: OidcProvisioningRaceClassifierOptions): boolean;
|
|
35
|
+
|
|
36
|
+
/** Shared transaction and concurrency policy for OIDC provisioning. */
|
|
37
|
+
export declare interface OidcProvisioningCoordinatorOptions<T> {
|
|
38
|
+
/** Root or transaction-bound database used for every provisioning write. */
|
|
39
|
+
db: DatabaseInterface_2;
|
|
40
|
+
/**
|
|
41
|
+
* Stable keys used for local serialization. Supply both the exact
|
|
42
|
+
* issuer/subject key and normalized email when available.
|
|
43
|
+
*/
|
|
44
|
+
lockKeys: readonly string[];
|
|
45
|
+
/** Package-specific durable race-arbiter classifier. */
|
|
46
|
+
isRaceConflict: (error: unknown) => boolean;
|
|
47
|
+
/** Package-specific transaction failure mapping. */
|
|
48
|
+
createTransactionError: (message: string, cause?: unknown) => Error;
|
|
49
|
+
/** Package-specific terminal concurrency failure mapping. */
|
|
50
|
+
createConcurrencyError: (cause?: unknown) => Error;
|
|
51
|
+
/** Provision atomically using the supplied transaction-bound handle. */
|
|
52
|
+
provision: (tx: DatabaseInterface_2) => Promise<T>;
|
|
53
|
+
/**
|
|
54
|
+
* Rehydrate root-owned results after commit so models do not retain a
|
|
55
|
+
* released transaction client. Omit for non-model results.
|
|
56
|
+
*/
|
|
57
|
+
rebindRootResult?: (result: T, rootDb: DatabaseInterface_2) => Promise<T>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export declare interface OidcProvisioningIdentityInput {
|
|
61
|
+
email?: string;
|
|
62
|
+
issuer: string;
|
|
63
|
+
profileId: string;
|
|
64
|
+
provider: string;
|
|
65
|
+
subject: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export declare interface OidcProvisioningPersonInput {
|
|
69
|
+
email?: string;
|
|
70
|
+
name?: string;
|
|
71
|
+
preferredUsername?: string;
|
|
72
|
+
subject: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export declare interface OidcProvisioningRaceClassifierOptions {
|
|
76
|
+
/** Package-specific database constraint messages. */
|
|
77
|
+
messagePatterns?: readonly RegExp[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Wrap a known OIDC arbiter write so only its unique failure is retryable. */
|
|
81
|
+
export declare function saveOidcRaceArbiter<T>(operation: () => Promise<T>): Promise<T>;
|
|
82
|
+
|
|
83
|
+
export { }
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-07-
|
|
3
|
+
"generatedAt": "2026-07-14T05:50:21.948Z",
|
|
4
4
|
"packageName": "@happyvertical/smrt-profiles",
|
|
5
|
-
"packageVersion": "0.39.
|
|
5
|
+
"packageVersion": "0.39.15",
|
|
6
6
|
"sourceManifestPath": "dist/manifest.json",
|
|
7
7
|
"agentDocPath": "AGENTS.md",
|
|
8
8
|
"sourceHashes": {
|
|
9
|
-
"manifest": "
|
|
10
|
-
"packageJson": "
|
|
11
|
-
"agents": "
|
|
9
|
+
"manifest": "d3eedfb2b0a6cf1292a59c90b9f57a649de709433363226ae9de236b48d3372f",
|
|
10
|
+
"packageJson": "7e39df481904d091e911f65035f618f2c7b9dd7e4a8fc726e70f596c7b15b33e",
|
|
11
|
+
"agents": "97e18475c8dba05b1f8811457c02bbfe98fbca30988476605cffbf189a13290d"
|
|
12
12
|
},
|
|
13
13
|
"exports": [
|
|
14
14
|
".",
|
|
15
|
+
"./internal/oidc-provisioning",
|
|
15
16
|
"./manifest",
|
|
16
17
|
"./manifest.json",
|
|
17
18
|
"./utils"
|
|
@@ -184,6 +185,23 @@
|
|
|
184
185
|
"tags": [],
|
|
185
186
|
"risks": []
|
|
186
187
|
},
|
|
188
|
+
{
|
|
189
|
+
"name": "OidcProfileEmailReservationCollection",
|
|
190
|
+
"qualifiedName": "@happyvertical/smrt-profiles:OidcProfileEmailReservationCollection",
|
|
191
|
+
"collection": "oidcprofileemailreservations",
|
|
192
|
+
"tableName": "oidc_profile_email_reservations",
|
|
193
|
+
"packageName": "@happyvertical/smrt-profiles",
|
|
194
|
+
"extends": "SmrtCollection",
|
|
195
|
+
"fields": [],
|
|
196
|
+
"relationships": [],
|
|
197
|
+
"methods": [],
|
|
198
|
+
"surfaces": [],
|
|
199
|
+
"relationshipFeatures": [
|
|
200
|
+
"uuidColumns"
|
|
201
|
+
],
|
|
202
|
+
"tags": [],
|
|
203
|
+
"risks": []
|
|
204
|
+
},
|
|
187
205
|
{
|
|
188
206
|
"name": "ProfileAssetCollection",
|
|
189
207
|
"qualifiedName": "@happyvertical/smrt-profiles:ProfileAssetCollection",
|
|
@@ -229,10 +247,13 @@
|
|
|
229
247
|
"findByType",
|
|
230
248
|
"findGlobal",
|
|
231
249
|
"findRelated",
|
|
250
|
+
"findUniqueGlobalPersonByEmail",
|
|
232
251
|
"findWithGlobals",
|
|
233
252
|
"getAssets",
|
|
234
253
|
"getRelationshipNetwork",
|
|
235
|
-
"removeAsset"
|
|
254
|
+
"removeAsset",
|
|
255
|
+
"requireCanonicalGlobalPerson",
|
|
256
|
+
"reserveCanonicalIdentityEmail"
|
|
236
257
|
],
|
|
237
258
|
"surfaces": [],
|
|
238
259
|
"relationshipFeatures": [
|
|
@@ -358,7 +379,8 @@
|
|
|
358
379
|
"relationships": [],
|
|
359
380
|
"methods": [
|
|
360
381
|
"getBySlug",
|
|
361
|
-
"getOrCreateBySlug"
|
|
382
|
+
"getOrCreateBySlug",
|
|
383
|
+
"getOrCreateGlobalBySlug"
|
|
362
384
|
],
|
|
363
385
|
"surfaces": [],
|
|
364
386
|
"relationshipFeatures": [
|
|
@@ -953,6 +975,12 @@
|
|
|
953
975
|
"required": false,
|
|
954
976
|
"columnType": "TEXT"
|
|
955
977
|
},
|
|
978
|
+
{
|
|
979
|
+
"name": "identityKey",
|
|
980
|
+
"type": "text",
|
|
981
|
+
"required": false,
|
|
982
|
+
"columnType": "TEXT"
|
|
983
|
+
},
|
|
956
984
|
{
|
|
957
985
|
"name": "email",
|
|
958
986
|
"type": "text",
|
|
@@ -976,10 +1004,12 @@
|
|
|
976
1004
|
}
|
|
977
1005
|
],
|
|
978
1006
|
"methods": [
|
|
1007
|
+
"buildIdentityKey",
|
|
979
1008
|
"findBySubject",
|
|
980
1009
|
"findOrCreate",
|
|
981
1010
|
"getProfile",
|
|
982
|
-
"recordUsage"
|
|
1011
|
+
"recordUsage",
|
|
1012
|
+
"save"
|
|
983
1013
|
],
|
|
984
1014
|
"surfaces": [
|
|
985
1015
|
{
|
|
@@ -998,22 +1028,6 @@
|
|
|
998
1028
|
"path": "/oidcidentities/[id]",
|
|
999
1029
|
"method": "GET"
|
|
1000
1030
|
},
|
|
1001
|
-
{
|
|
1002
|
-
"kind": "api",
|
|
1003
|
-
"name": "oidcidentities.create",
|
|
1004
|
-
"operation": "create",
|
|
1005
|
-
"objectName": "@happyvertical/smrt-profiles:OidcIdentity",
|
|
1006
|
-
"path": "/oidcidentities",
|
|
1007
|
-
"method": "POST"
|
|
1008
|
-
},
|
|
1009
|
-
{
|
|
1010
|
-
"kind": "api",
|
|
1011
|
-
"name": "oidcidentities.update",
|
|
1012
|
-
"operation": "update",
|
|
1013
|
-
"objectName": "@happyvertical/smrt-profiles:OidcIdentity",
|
|
1014
|
-
"path": "/oidcidentities/[id]",
|
|
1015
|
-
"method": "PATCH"
|
|
1016
|
-
},
|
|
1017
1031
|
{
|
|
1018
1032
|
"kind": "cli",
|
|
1019
1033
|
"name": "oidcidentity_list",
|
|
@@ -1046,6 +1060,46 @@
|
|
|
1046
1060
|
"tags": [],
|
|
1047
1061
|
"risks": []
|
|
1048
1062
|
},
|
|
1063
|
+
{
|
|
1064
|
+
"name": "OidcProfileEmailReservation",
|
|
1065
|
+
"qualifiedName": "@happyvertical/smrt-profiles:OidcProfileEmailReservation",
|
|
1066
|
+
"collection": "oidcprofileemailreservations",
|
|
1067
|
+
"tableName": "oidc_profile_email_reservations",
|
|
1068
|
+
"packageName": "@happyvertical/smrt-profiles",
|
|
1069
|
+
"extends": "SmrtObject",
|
|
1070
|
+
"fields": [
|
|
1071
|
+
{
|
|
1072
|
+
"name": "profileId",
|
|
1073
|
+
"type": "foreignKey",
|
|
1074
|
+
"required": true,
|
|
1075
|
+
"related": "Profile",
|
|
1076
|
+
"columnType": "UUID"
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
"name": "emailKey",
|
|
1080
|
+
"type": "text",
|
|
1081
|
+
"required": true,
|
|
1082
|
+
"columnType": "TEXT"
|
|
1083
|
+
}
|
|
1084
|
+
],
|
|
1085
|
+
"relationships": [
|
|
1086
|
+
{
|
|
1087
|
+
"name": "profileId",
|
|
1088
|
+
"type": "foreignKey",
|
|
1089
|
+
"required": true,
|
|
1090
|
+
"related": "Profile",
|
|
1091
|
+
"columnType": "UUID"
|
|
1092
|
+
}
|
|
1093
|
+
],
|
|
1094
|
+
"methods": [],
|
|
1095
|
+
"surfaces": [],
|
|
1096
|
+
"relationshipFeatures": [
|
|
1097
|
+
"foreignKey",
|
|
1098
|
+
"uuidColumns"
|
|
1099
|
+
],
|
|
1100
|
+
"tags": [],
|
|
1101
|
+
"risks": []
|
|
1102
|
+
},
|
|
1049
1103
|
{
|
|
1050
1104
|
"name": "Profile",
|
|
1051
1105
|
"qualifiedName": "@happyvertical/smrt-profiles:Profile",
|
|
@@ -1085,6 +1139,12 @@
|
|
|
1085
1139
|
"required": false,
|
|
1086
1140
|
"columnType": "TEXT"
|
|
1087
1141
|
},
|
|
1142
|
+
{
|
|
1143
|
+
"name": "emailKey",
|
|
1144
|
+
"type": "text",
|
|
1145
|
+
"required": false,
|
|
1146
|
+
"columnType": "TEXT"
|
|
1147
|
+
},
|
|
1088
1148
|
{
|
|
1089
1149
|
"name": "name",
|
|
1090
1150
|
"type": "text",
|
|
@@ -2542,6 +2602,12 @@
|
|
|
2542
2602
|
"required": false,
|
|
2543
2603
|
"columnType": "TEXT"
|
|
2544
2604
|
},
|
|
2605
|
+
{
|
|
2606
|
+
"name": "emailKey",
|
|
2607
|
+
"type": "text",
|
|
2608
|
+
"required": false,
|
|
2609
|
+
"columnType": "TEXT"
|
|
2610
|
+
},
|
|
2545
2611
|
{
|
|
2546
2612
|
"name": "name",
|
|
2547
2613
|
"type": "text",
|
|
@@ -2719,6 +2785,12 @@
|
|
|
2719
2785
|
"required": false,
|
|
2720
2786
|
"columnType": "TEXT"
|
|
2721
2787
|
},
|
|
2788
|
+
{
|
|
2789
|
+
"name": "emailKey",
|
|
2790
|
+
"type": "text",
|
|
2791
|
+
"required": false,
|
|
2792
|
+
"columnType": "TEXT"
|
|
2793
|
+
},
|
|
2722
2794
|
{
|
|
2723
2795
|
"name": "name",
|
|
2724
2796
|
"type": "text",
|
|
@@ -2896,6 +2968,12 @@
|
|
|
2896
2968
|
"required": false,
|
|
2897
2969
|
"columnType": "TEXT"
|
|
2898
2970
|
},
|
|
2971
|
+
{
|
|
2972
|
+
"name": "emailKey",
|
|
2973
|
+
"type": "text",
|
|
2974
|
+
"required": false,
|
|
2975
|
+
"columnType": "TEXT"
|
|
2976
|
+
},
|
|
2899
2977
|
{
|
|
2900
2978
|
"name": "name",
|
|
2901
2979
|
"type": "text",
|
|
@@ -3254,22 +3332,6 @@
|
|
|
3254
3332
|
"path": "/oidcidentities/[id]",
|
|
3255
3333
|
"method": "GET"
|
|
3256
3334
|
},
|
|
3257
|
-
{
|
|
3258
|
-
"kind": "api",
|
|
3259
|
-
"name": "oidcidentities.create",
|
|
3260
|
-
"operation": "create",
|
|
3261
|
-
"objectName": "@happyvertical/smrt-profiles:OidcIdentity",
|
|
3262
|
-
"path": "/oidcidentities",
|
|
3263
|
-
"method": "POST"
|
|
3264
|
-
},
|
|
3265
|
-
{
|
|
3266
|
-
"kind": "api",
|
|
3267
|
-
"name": "oidcidentities.update",
|
|
3268
|
-
"operation": "update",
|
|
3269
|
-
"objectName": "@happyvertical/smrt-profiles:OidcIdentity",
|
|
3270
|
-
"path": "/oidcidentities/[id]",
|
|
3271
|
-
"method": "PATCH"
|
|
3272
|
-
},
|
|
3273
3335
|
{
|
|
3274
3336
|
"kind": "cli",
|
|
3275
3337
|
"name": "oidcidentity_list",
|
|
@@ -3855,12 +3917,12 @@
|
|
|
3855
3917
|
}
|
|
3856
3918
|
],
|
|
3857
3919
|
"relationshipsV2": {
|
|
3858
|
-
"foreignKeyFields":
|
|
3920
|
+
"foreignKeyFields": 19,
|
|
3859
3921
|
"crossPackageRefFields": 1,
|
|
3860
3922
|
"junctionCollections": 1,
|
|
3861
3923
|
"hierarchicalObjects": 0,
|
|
3862
3924
|
"polymorphicAssociations": 0,
|
|
3863
|
-
"uuidColumns":
|
|
3925
|
+
"uuidColumns": 61
|
|
3864
3926
|
},
|
|
3865
|
-
"agentDoc": "# @happyvertical/smrt-profiles\n\nCentral identity system with multi-auth, relationships, controlled metadata, and audit logging.\n\n## Models\n\n- **Profile** (STI base → Bot, Organization, Person): email (globally unique), `typeId` FK to ProfileType, plus a `metadata` `@oneToMany('ProfileMetadata')` relationship for controlled per-profile values.\n- **ProfileAsset**: dedicated owned-asset join in `profile_assets` with `relationship` and `sortOrder`.\n- **ProfileRelationship**: bidirectional — creating one auto-creates reciprocal inverse. `contextProfileId` for tertiary relationships. `ProfileRelationshipTerm` tracks start/end dates.\n- **ProfileMetafield**: controlled vocabulary with `validationSchema`. **ProfileMetadata**: per-profile values linked to metafields.\n- **AuditLog**: action, resourceType/Id, `source` (web/cli/ci/webhook/mcp), `onBehalfOfId` for CI pass-through identity. `allowSuperAdminBypass: true`.\n\n## Auth Methods\n\n| Model | Pattern |\n|-------|---------|\n| NostrIdentity | Encrypted keypair (AES-256-GCM). Requires `SERVER_MASTER_SECRET` env var for decryption. NIP-05 address generation. |\n| OidcIdentity | Multiple issuers (Keycloak/Google/GitHub). Lookup by `issuer + subject` pair. `
|
|
3927
|
+
"agentDoc": "# @happyvertical/smrt-profiles\n\nCentral identity system with multi-auth, relationships, controlled metadata, and audit logging.\n\n## Models\n\n- **Profile** (STI base → Bot, Organization, Person): email (globally unique exact value), readonly indexed `emailKey` derived with `normalizeIdentityEmail()` for adapter-independent identity lookup, `typeId` FK to ProfileType, plus a `metadata` `@oneToMany('ProfileMetadata')` relationship for controlled per-profile values.\n- **ProfileAsset**: dedicated owned-asset join in `profile_assets` with `relationship` and `sortOrder`.\n- **ProfileRelationship**: bidirectional — creating one auto-creates reciprocal inverse. `contextProfileId` for tertiary relationships. `ProfileRelationshipTerm` tracks start/end dates.\n- **ProfileMetafield**: controlled vocabulary with `validationSchema`. **ProfileMetadata**: per-profile values linked to metafields.\n- **AuditLog**: action, resourceType/Id, `source` (web/cli/ci/webhook/mcp), `onBehalfOfId` for CI pass-through identity. `allowSuperAdminBypass: true`.\n\n## Auth Methods\n\n| Model | Pattern |\n|-------|---------|\n| NostrIdentity | Encrypted keypair (AES-256-GCM). Requires `SERVER_MASTER_SECRET` env var for decryption. NIP-05 address generation. |\n| OidcIdentity | Multiple issuers (Keycloak/Google/GitHub). Lookup by `issuer + subject` pair. Transactional provisioning derives the readonly nullable unique `identityKey` and backfills legacy rows. |\n| ApiKey | SHA-256 hashed. **Plaintext returned once only** on `generate()`. `keyPrefix` for identification. Scope-based with expiry. |\n| MagicLinkToken | One-time token with expiry for passwordless auth. |\n\n## Identity Resolution\n\nAuth helpers in `src/auth/` build profiles from external identity claims:\n\n- `resolveIdentity()` — top-level dispatcher that returns/creates a Profile from Nostr signatures, OIDC claims, magic link tokens, or API keys.\n- `createProfileFromOidc(claims, provider, options)` — creates `Profile` + `OidcIdentity` for first-time OIDC sign-in using a transaction-capable root database in `options.db`.\n- `ProfileCollection.findUniqueGlobalPersonByEmail(email)` — supported\n verified-identity lookup; fails closed on tenant-scoped, non-Person, or\n duplicate case-insensitive matches.\n- `ProfileCollection.requireCanonicalGlobalPerson(profileId, email?)` —\n validates an application-selected Profile against the same canonical global\n Person invariant.\n- `ProfileCollection.reserveCanonicalIdentityEmail(profileId, email?)` —\n validates and synchronizes the private unique\n `oidc_profile_email_reservations.email_key` used as the database arbiter for\n concurrent external-identity provisioning. Omitting `email` uses the\n Profile's stored address, moving or removing an existing reservation as the\n canonical Profile changes.\n- `createProfileFromNostr(email, nostrData)` — creates `Profile` + `NostrIdentity` for Nostr-authenticated users.\n\n## Key Methods\n\n- `Profile.getAssets()` / `addAsset()` / `removeAsset()` and the matching `ProfileCollection` wrappers — canonical owned asset helpers backed by `profile_assets`.\n- `Profile.addMetadata(metafieldSlug, value)` / `Profile.getMetadata()` — validates against metafield schema. `ProfileCollection.batchGetMetadata()` / `batchUpdateMetadata()` for bulk reads/writes.\n- `Profile.getRelationships({ direction: 'from'|'to'|'all' })` — direction matters.\n- `Profile.getRelationshipsFrom()` / `getRelationshipsTo()` — R10-generated `@oneToMany` accessors. ProfileRelationship has two FKs back to Profile, so each `@oneToMany` annotates its inverse explicitly (`{ foreignKey: 'fromProfileId' }` / `'toProfileId'`). Return raw `ProfileRelationship[]`; use `getRelationships()` for slug/direction filtering.\n- AI: `generateBio()` (uses `smrtProfiles.profile.generateBio` prompt via `@happyvertical/smrt-prompts`), `matches(criteria)` (delegates to `is()`).\n\n## Prompt Registry\n\n`generateBio()` is registered with `@happyvertical/smrt-prompts` so tenants can override template/model/params at runtime:\n\n```typescript\nimport { smrtProfilesGenerateBioPrompt } from '@happyvertical/smrt-profiles';\n// key: 'smrtProfiles.profile.generateBio'\n```\n\n## Gotchas\n\n- **SERVER_MASTER_SECRET required** for Nostr private key decryption — centralized key management\n- **API key never returned again**: `ApiKey.generate()` returns plaintext once; only `keyPrefix` visible later\n- **OIDC unique per issuer+subject**: same subject from different issuers =\n different identities. Both claims are opaque and case-sensitive; preserve\n exact whitespace after using trim only to reject blank values.\n- **OIDC identity mutations are trusted-only**: generated REST, MCP, and CLI\n surfaces are read-only. Link or update identities through transactional\n provisioning APIs so callers cannot rebind issuer/subject authority to an\n arbitrary Profile. Deprecated `OidcIdentity.findOrCreate()` preserves only\n transaction-safe exact-link reuse and refuses to create new links;\n `OidcIdentityCollection.linkToProfile()` and `Profile.linkOidcIdentity()`\n delegate to that same non-creating compatibility path.\n- **Apply schema migrations for identity race keys**: run `smrt db:status`,\n `smrt db:migrate`, then `smrt db:status` before deploying. Transactional OIDC\n provisioning populates `OidcIdentity.identityKey` and\n `oidc_profile_email_reservations`; legacy rows reserve an address only after\n safe validation. Stop/upgrade old Profile writers, then run the public,\n transactional and idempotent `backfillProfileEmailKeys(db)` from one deploy\n process. Verified-email provisioning and public email-key lookup require the\n standard `_smrt_backfills` readiness marker and fail closed immediately when\n it is absent. This keeps table scans in the explicit deploy step; runtime\n reads use the indexed key and validate only returned candidates.\n- **Public OIDC provisioning requires transactions**:\n `createProfileFromOidc()` owns a root transaction or uses a savepoint on an\n already-bound handle. Root adapters must expose `beginTransaction`;\n transaction-only handles are ambiguous and fail closed. Pass the root\n database for adapters without nested savepoints, including DuckDB;\n provisioning fails before durable writes if neither path is safe. Trusted\n framework packages use the private\n `@happyvertical/smrt-profiles/internal/oidc-provisioning` subpath instead of\n duplicating adapter probing, locking, or retry policy, and supply both exact\n issuer/subject and normalized-email lock keys in deterministic order. The\n coordinator additionally serializes all SQLite/DuckDB provisioning\n transactions per database URL because those adapters cannot overlap\n unrelated root transactions safely and retries bounded PostgreSQL\n deadlock/serialization failures. New OIDC Profiles use per-profile,\n non-semantic slugs so duplicate display names never invoke natural-key upsert.\n Caller-owned transactions never execute `_smrt_backfills` DDL; paths that\n perform canonical email lookup or reservation require the table to already\n exist or the caller must retry with the root database. Exact issuer/subject\n reuse skips the email-key readiness-marker lookup, but root coordination still\n initializes the shared tracker table. Caller-owned exact reuse does not\n consult the tracker and therefore does not require that table.\n- **Profile-only OIDC linking fails closed on existing email matches**:\n `createProfileFromOidc()` preserves exact issuer/subject reuse, including\n legacy tenant-scoped and non-Person links, but profiles cannot prove whether a\n User owns a same-email Profile. New identities therefore never attach to an\n existing email match through this helper; User/session provisioning still\n rejects unsafe linked Profiles before creating authentication state. Use\n `UserCollection.getOrCreateFromOidc()` for owner-aware verified-email reuse and\n its supported transaction-bound resolver hook.\n- **Email storage and identity matching differ**: `Profile.email` has an\n exact-value DB constraint. Identity boundaries query readonly indexed\n `Profile.emailKey`, derived by the shared TypeScript\n `normalizeIdentityEmail()` helper rather than adapter-specific SQL casing or\n trimming, and deliberately fail closed on duplicate normalized keys.\n- **Optional tenancy** on Profile; AuditLog allows super-admin bypass\n"
|
|
3866
3928
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -270,6 +270,14 @@ declare class OidcIdentity extends SmrtObject {
|
|
|
270
270
|
* OIDC subject claim - unique identifier from the provider
|
|
271
271
|
*/
|
|
272
272
|
subject: string;
|
|
273
|
+
/**
|
|
274
|
+
* Stable issuer+subject key used as the database race arbiter.
|
|
275
|
+
*
|
|
276
|
+
* Nullable for legacy rows; every newly linked or reused identity backfills
|
|
277
|
+
* it. A separate unique constraint makes concurrent first login fail with a
|
|
278
|
+
* retryable conflict instead of creating two identities.
|
|
279
|
+
*/
|
|
280
|
+
identityKey: string | null;
|
|
273
281
|
/**
|
|
274
282
|
* Cached email from the IdP (for display/lookup)
|
|
275
283
|
*/
|
|
@@ -287,8 +295,17 @@ declare class OidcIdentity extends SmrtObject {
|
|
|
287
295
|
* Find identity by issuer and subject
|
|
288
296
|
*/
|
|
289
297
|
static findBySubject(issuer: string, subject: string, options?: SmrtObjectOptions): Promise<OidcIdentity | null>;
|
|
298
|
+
/** Build the collision-free natural key for one OIDC issuer subject. */
|
|
299
|
+
static buildIdentityKey(issuer: string, subject: string): string;
|
|
300
|
+
/** Keep the durable key derived from its natural-key source fields. */
|
|
301
|
+
save(): Promise<this>;
|
|
290
302
|
/**
|
|
291
|
-
*
|
|
303
|
+
* Reuse an existing exact identity for its unchanged Profile.
|
|
304
|
+
*
|
|
305
|
+
* @deprecated Authentication links must be created through transactional
|
|
306
|
+
* provisioning. This compatibility method only refreshes a unique mapping
|
|
307
|
+
* that already belongs to the supplied Profile, including legacy Profile
|
|
308
|
+
* types, and deliberately refuses to create or rebind authority.
|
|
292
309
|
*/
|
|
293
310
|
static findOrCreate(profile: Profile, oidcData: {
|
|
294
311
|
provider: string;
|
|
@@ -315,12 +332,16 @@ declare class Profile extends SmrtObject {
|
|
|
315
332
|
tenantId: string | null;
|
|
316
333
|
typeId?: string;
|
|
317
334
|
email?: string;
|
|
335
|
+
/** Adapter-independent identity lookup key derived from email on save. */
|
|
336
|
+
emailKey: string | null;
|
|
318
337
|
name: string;
|
|
319
338
|
description?: string;
|
|
320
339
|
metadata: ProfileMetadata[];
|
|
321
340
|
relationshipsFrom: ProfileRelationship[];
|
|
322
341
|
relationshipsTo: ProfileRelationship[];
|
|
323
342
|
constructor(options?: ProfileOptions);
|
|
343
|
+
/** Keep the durable identity key derived from the public email field. */
|
|
344
|
+
save(): Promise<this>;
|
|
324
345
|
/**
|
|
325
346
|
* Get the profile type slug for this profile
|
|
326
347
|
*
|
|
@@ -472,10 +493,13 @@ declare class Profile extends SmrtObject {
|
|
|
472
493
|
*/
|
|
473
494
|
getOidcIdentities(): Promise<OidcIdentity[]>;
|
|
474
495
|
/**
|
|
475
|
-
*
|
|
496
|
+
* Reuse an existing exact OIDC identity for this unchanged Profile.
|
|
476
497
|
*
|
|
477
498
|
* @param oidcData - OIDC provider data
|
|
478
499
|
* @returns The linked OIDC identity record
|
|
500
|
+
* @deprecated New authentication links require owner-aware transactional
|
|
501
|
+
* provisioning. This compatibility helper may refresh a legacy Profile
|
|
502
|
+
* type, but refuses to create or rebind authority.
|
|
479
503
|
*/
|
|
480
504
|
linkOidcIdentity(oidcData: {
|
|
481
505
|
provider: string;
|
package/dist/utils.d.ts
CHANGED
|
@@ -302,6 +302,14 @@ declare class OidcIdentity extends SmrtObject {
|
|
|
302
302
|
* OIDC subject claim - unique identifier from the provider
|
|
303
303
|
*/
|
|
304
304
|
subject: string;
|
|
305
|
+
/**
|
|
306
|
+
* Stable issuer+subject key used as the database race arbiter.
|
|
307
|
+
*
|
|
308
|
+
* Nullable for legacy rows; every newly linked or reused identity backfills
|
|
309
|
+
* it. A separate unique constraint makes concurrent first login fail with a
|
|
310
|
+
* retryable conflict instead of creating two identities.
|
|
311
|
+
*/
|
|
312
|
+
identityKey: string | null;
|
|
305
313
|
/**
|
|
306
314
|
* Cached email from the IdP (for display/lookup)
|
|
307
315
|
*/
|
|
@@ -319,8 +327,17 @@ declare class OidcIdentity extends SmrtObject {
|
|
|
319
327
|
* Find identity by issuer and subject
|
|
320
328
|
*/
|
|
321
329
|
static findBySubject(issuer: string, subject: string, options?: SmrtObjectOptions): Promise<OidcIdentity | null>;
|
|
330
|
+
/** Build the collision-free natural key for one OIDC issuer subject. */
|
|
331
|
+
static buildIdentityKey(issuer: string, subject: string): string;
|
|
332
|
+
/** Keep the durable key derived from its natural-key source fields. */
|
|
333
|
+
save(): Promise<this>;
|
|
322
334
|
/**
|
|
323
|
-
*
|
|
335
|
+
* Reuse an existing exact identity for its unchanged Profile.
|
|
336
|
+
*
|
|
337
|
+
* @deprecated Authentication links must be created through transactional
|
|
338
|
+
* provisioning. This compatibility method only refreshes a unique mapping
|
|
339
|
+
* that already belongs to the supplied Profile, including legacy Profile
|
|
340
|
+
* types, and deliberately refuses to create or rebind authority.
|
|
324
341
|
*/
|
|
325
342
|
static findOrCreate(profile: Profile, oidcData: {
|
|
326
343
|
provider: string;
|
|
@@ -347,12 +364,16 @@ declare class Profile extends SmrtObject {
|
|
|
347
364
|
tenantId: string | null;
|
|
348
365
|
typeId?: string;
|
|
349
366
|
email?: string;
|
|
367
|
+
/** Adapter-independent identity lookup key derived from email on save. */
|
|
368
|
+
emailKey: string | null;
|
|
350
369
|
name: string;
|
|
351
370
|
description?: string;
|
|
352
371
|
metadata: ProfileMetadata[];
|
|
353
372
|
relationshipsFrom: ProfileRelationship[];
|
|
354
373
|
relationshipsTo: ProfileRelationship[];
|
|
355
374
|
constructor(options?: ProfileOptions);
|
|
375
|
+
/** Keep the durable identity key derived from the public email field. */
|
|
376
|
+
save(): Promise<this>;
|
|
356
377
|
/**
|
|
357
378
|
* Get the profile type slug for this profile
|
|
358
379
|
*
|
|
@@ -504,10 +525,13 @@ declare class Profile extends SmrtObject {
|
|
|
504
525
|
*/
|
|
505
526
|
getOidcIdentities(): Promise<OidcIdentity[]>;
|
|
506
527
|
/**
|
|
507
|
-
*
|
|
528
|
+
* Reuse an existing exact OIDC identity for this unchanged Profile.
|
|
508
529
|
*
|
|
509
530
|
* @param oidcData - OIDC provider data
|
|
510
531
|
* @returns The linked OIDC identity record
|
|
532
|
+
* @deprecated New authentication links require owner-aware transactional
|
|
533
|
+
* provisioning. This compatibility helper may refresh a legacy Profile
|
|
534
|
+
* type, but refuses to create or rebind authority.
|
|
511
535
|
*/
|
|
512
536
|
linkOidcIdentity(oidcData: {
|
|
513
537
|
provider: string;
|
package/dist/utils.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
//#region src/utils.ts
|
|
2
2
|
async function getProfileMetadata(profileId, options = {}) {
|
|
3
|
-
const { ProfileMetadataCollection } = await import("./chunks/ProfileMetadataCollection-
|
|
3
|
+
const { ProfileMetadataCollection } = await import("./chunks/ProfileMetadataCollection-loKVv_pj.js").then((n) => n.n);
|
|
4
4
|
return await (await ProfileMetadataCollection.create(options)).getMetadataObject(profileId);
|
|
5
5
|
}
|
|
6
6
|
async function setProfileMetadata(profileId, metafieldSlug, value, options = {}) {
|
|
7
|
-
const { ProfileCollection } = await import("./chunks/ProfileCollection-
|
|
7
|
+
const { ProfileCollection } = await import("./chunks/ProfileCollection-Bix9yAiy.js").then((n) => n.r);
|
|
8
8
|
const profile = await (await ProfileCollection.create(options)).get({ id: profileId });
|
|
9
9
|
if (!profile) throw new Error(`Profile '${profileId}' not found`);
|
|
10
10
|
await profile.addMetadata(metafieldSlug, value);
|
|
11
11
|
}
|
|
12
12
|
async function findProfilesByMeta(metafieldSlug, value, options = {}) {
|
|
13
|
-
const { ProfileMetafieldCollection } = await import("./chunks/ProfileMetafieldCollection-
|
|
14
|
-
const { ProfileMetadataCollection } = await import("./chunks/ProfileMetadataCollection-
|
|
13
|
+
const { ProfileMetafieldCollection } = await import("./chunks/ProfileMetafieldCollection-CACMY1hl.js").then((n) => n.n);
|
|
14
|
+
const { ProfileMetadataCollection } = await import("./chunks/ProfileMetadataCollection-loKVv_pj.js").then((n) => n.n);
|
|
15
15
|
const metafield = await (await ProfileMetafieldCollection.create(options)).getBySlug(metafieldSlug);
|
|
16
16
|
if (!metafield) throw new Error(`Metafield '${metafieldSlug}' not found`);
|
|
17
17
|
return await (await ProfileMetadataCollection.create(options)).findProfilesByMetadata(metafield.id, value);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-profiles",
|
|
3
|
-
"version": "0.39.
|
|
3
|
+
"version": "0.39.15",
|
|
4
4
|
"description": "Profile management system with relationships, metadata, and reciprocal associations for SMRT framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"types": "./dist/utils.d.ts",
|
|
20
20
|
"import": "./dist/utils.js"
|
|
21
21
|
},
|
|
22
|
+
"./internal/oidc-provisioning": {
|
|
23
|
+
"types": "./dist/oidc-provisioning.d.ts",
|
|
24
|
+
"import": "./dist/internal/oidc-provisioning.js"
|
|
25
|
+
},
|
|
22
26
|
"./manifest": "./dist/manifest.json",
|
|
23
27
|
"./manifest.json": "./dist/manifest.json"
|
|
24
28
|
},
|
|
@@ -30,10 +34,10 @@
|
|
|
30
34
|
"@happyvertical/utils": "^0.78.1",
|
|
31
35
|
"@noble/curves": "^1.9.7",
|
|
32
36
|
"bech32": "^2.0.0",
|
|
33
|
-
"@happyvertical/smrt-assets": "0.39.
|
|
34
|
-
"@happyvertical/smrt-
|
|
35
|
-
"@happyvertical/smrt-
|
|
36
|
-
"@happyvertical/smrt-
|
|
37
|
+
"@happyvertical/smrt-assets": "0.39.15",
|
|
38
|
+
"@happyvertical/smrt-tenancy": "0.39.15",
|
|
39
|
+
"@happyvertical/smrt-core": "0.39.15",
|
|
40
|
+
"@happyvertical/smrt-prompts": "0.39.15"
|
|
37
41
|
},
|
|
38
42
|
"devDependencies": {
|
|
39
43
|
"@faker-js/faker": "^10.5.0",
|
|
@@ -42,7 +46,7 @@
|
|
|
42
46
|
"typescript": "^5.9.3",
|
|
43
47
|
"vite": "^8.1.3",
|
|
44
48
|
"vitest": "^4.1.9",
|
|
45
|
-
"@happyvertical/smrt-vitest": "0.39.
|
|
49
|
+
"@happyvertical/smrt-vitest": "0.39.15"
|
|
46
50
|
},
|
|
47
51
|
"keywords": [
|
|
48
52
|
"ai",
|