@happyvertical/smrt-profiles 0.51.39 → 0.53.1

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.
Files changed (46) hide show
  1. package/AGENTS.md +3 -1
  2. package/README.md +33 -1
  3. package/dist/chunks/{OidcIdentity-K8V4u0CR.js → OidcIdentity-Cb1bxreM.js} +3 -3
  4. package/dist/chunks/{OidcIdentity-K8V4u0CR.js.map → OidcIdentity-Cb1bxreM.js.map} +1 -1
  5. package/dist/chunks/{OidcIdentityCollection-gnrLoF08.js → OidcIdentityCollection-TV8VdK69.js} +2 -2
  6. package/dist/chunks/{OidcIdentityCollection-gnrLoF08.js.map → OidcIdentityCollection-TV8VdK69.js.map} +1 -1
  7. package/dist/chunks/{ProfileAssetCollection-CmbeSgDi.js → ProfileAssetCollection-B9hacUJa.js} +10 -4
  8. package/dist/chunks/ProfileAssetCollection-B9hacUJa.js.map +1 -0
  9. package/dist/chunks/{ProfileCollection-DTT4KNpQ.js → ProfileCollection-DuJT7Mum.js} +52 -14
  10. package/dist/chunks/ProfileCollection-DuJT7Mum.js.map +1 -0
  11. package/dist/chunks/ProfileLinkCollection-4uNURz32.js +228 -0
  12. package/dist/chunks/ProfileLinkCollection-4uNURz32.js.map +1 -0
  13. package/dist/chunks/{ProfileMetadataCollection-DxX_BJKV.js → ProfileMetadataCollection-DQhbfEfa.js} +2 -1
  14. package/dist/chunks/ProfileMetadataCollection-DQhbfEfa.js.map +1 -0
  15. package/dist/chunks/{ProfileMetafieldCollection-CMKhVi2I.js → ProfileMetafieldCollection-CoBuM_5G.js} +2 -1
  16. package/dist/chunks/ProfileMetafieldCollection-CoBuM_5G.js.map +1 -0
  17. package/dist/chunks/{oidcProvisioningPrimitives-0-3n3D6f.js → oidcProvisioningPrimitives-D9OYu5-F.js} +3 -3
  18. package/dist/chunks/{oidcProvisioningPrimitives-0-3n3D6f.js.map → oidcProvisioningPrimitives-D9OYu5-F.js.map} +1 -1
  19. package/dist/chunks/{resolveIdentity-BrJDCjYi.js → resolveIdentity-B-PK6RwU.js} +14 -14
  20. package/dist/chunks/{resolveIdentity-BrJDCjYi.js.map → resolveIdentity-B-PK6RwU.js.map} +1 -1
  21. package/dist/collections/ProfileLinkCollection.d.ts +40 -0
  22. package/dist/collections/ProfileLinkCollection.d.ts.map +1 -0
  23. package/dist/collections/index.d.ts +1 -0
  24. package/dist/collections/index.d.ts.map +1 -1
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +14 -13
  28. package/dist/index.js.map +1 -1
  29. package/dist/internal/oidc-provisioning.js +2 -2
  30. package/dist/manifest.json +589 -13
  31. package/dist/models/Profile.d.ts +26 -0
  32. package/dist/models/Profile.d.ts.map +1 -1
  33. package/dist/models/ProfileAsset.d.ts.map +1 -1
  34. package/dist/models/ProfileLink.d.ts +54 -0
  35. package/dist/models/ProfileLink.d.ts.map +1 -0
  36. package/dist/models/ProfileMetadata.d.ts.map +1 -1
  37. package/dist/models/ProfileMetafield.d.ts.map +1 -1
  38. package/dist/models/index.d.ts +1 -0
  39. package/dist/models/index.d.ts.map +1 -1
  40. package/dist/smrt-knowledge.json +537 -7
  41. package/dist/utils.js +4 -4
  42. package/package.json +12 -12
  43. package/dist/chunks/ProfileAssetCollection-CmbeSgDi.js.map +0 -1
  44. package/dist/chunks/ProfileCollection-DTT4KNpQ.js.map +0 -1
  45. package/dist/chunks/ProfileMetadataCollection-DxX_BJKV.js.map +0 -1
  46. package/dist/chunks/ProfileMetafieldCollection-CMKhVi2I.js.map +0 -1
@@ -3,13 +3,13 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-profiles",
6
- "packageVersion": "0.51.39",
6
+ "packageVersion": "0.53.1",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "fa61533fd5cc48ff6950434a8296761ce3e5378d760ebe68901e1ca5137ba672",
11
- "packageJson": "48bc684af80aa00634725390571ecb90b593b2483268635f5a242064ff0324ef",
12
- "agents": "a6646a088f8c421b5556f945946db6f83d3a23d77b34c40bb02b726af30b4bd2"
10
+ "manifest": "18ca01e2858e97fb618ab2f8473af87a739f47f0ed898c6495a330643201b526",
11
+ "packageJson": "04a36ebf5ca140525b4c38fcb01b8e82f276c5dc591d183bb06205cb46428faf",
12
+ "agents": "abb7a0e87fb7aeac9eda85e40d68e9198fc807aa8338004161dcb62923a1078c"
13
13
  },
14
14
  "exports": [
15
15
  ".",
@@ -2020,6 +2020,78 @@
2020
2020
  "tags": [],
2021
2021
  "risks": []
2022
2022
  },
2023
+ {
2024
+ "name": "ProfileLinkCollection",
2025
+ "qualifiedName": "@happyvertical/smrt-profiles:ProfileLinkCollection",
2026
+ "collection": "profilelinks",
2027
+ "tableName": "profile_links",
2028
+ "packageName": "@happyvertical/smrt-profiles",
2029
+ "extends": "SmrtCollection",
2030
+ "fields": [],
2031
+ "relationships": [],
2032
+ "methods": [
2033
+ "listForProfile",
2034
+ "reorder",
2035
+ "replaceForProfile"
2036
+ ],
2037
+ "methodSignatures": [
2038
+ {
2039
+ "name": "listForProfile",
2040
+ "async": true,
2041
+ "params": [
2042
+ "profileId: string"
2043
+ ],
2044
+ "returns": "Promise<ProfileLink[]>"
2045
+ },
2046
+ {
2047
+ "name": "reorder",
2048
+ "async": true,
2049
+ "params": [
2050
+ "profileId: string",
2051
+ "linkIds: any"
2052
+ ],
2053
+ "returns": "Promise<ProfileLink[]>"
2054
+ },
2055
+ {
2056
+ "name": "replaceForProfile",
2057
+ "async": true,
2058
+ "params": [
2059
+ "profileId: string",
2060
+ "links: any"
2061
+ ],
2062
+ "returns": "Promise<ProfileLink[]>"
2063
+ }
2064
+ ],
2065
+ "surfaces": [],
2066
+ "withheldSurfaces": [
2067
+ {
2068
+ "kind": "api",
2069
+ "operation": "listForProfile",
2070
+ "code": "api-disabled",
2071
+ "reason": "api is disabled",
2072
+ "objectName": "@happyvertical/smrt-profiles:ProfileLinkCollection"
2073
+ },
2074
+ {
2075
+ "kind": "api",
2076
+ "operation": "reorder",
2077
+ "code": "api-disabled",
2078
+ "reason": "api is disabled",
2079
+ "objectName": "@happyvertical/smrt-profiles:ProfileLinkCollection"
2080
+ },
2081
+ {
2082
+ "kind": "api",
2083
+ "operation": "replaceForProfile",
2084
+ "code": "api-disabled",
2085
+ "reason": "api is disabled",
2086
+ "objectName": "@happyvertical/smrt-profiles:ProfileLinkCollection"
2087
+ }
2088
+ ],
2089
+ "relationshipFeatures": [
2090
+ "uuidColumns"
2091
+ ],
2092
+ "tags": [],
2093
+ "risks": []
2094
+ },
2023
2095
  {
2024
2096
  "name": "ProfileMetadataCollection",
2025
2097
  "qualifiedName": "@happyvertical/smrt-profiles:ProfileMetadataCollection",
@@ -4091,6 +4163,13 @@
4091
4163
  "required": false,
4092
4164
  "columnType": "TEXT"
4093
4165
  },
4166
+ {
4167
+ "name": "isPublic",
4168
+ "type": "boolean",
4169
+ "required": false,
4170
+ "columnType": "BOOLEAN",
4171
+ "default": false
4172
+ },
4094
4173
  {
4095
4174
  "name": "metadata",
4096
4175
  "type": "oneToMany",
@@ -4150,6 +4229,7 @@
4150
4229
  "getApiKeys",
4151
4230
  "getAssets",
4152
4231
  "getAuditLogs",
4232
+ "getLinks",
4153
4233
  "getMetadata",
4154
4234
  "getNostrIdentities",
4155
4235
  "getOidcIdentities",
@@ -4165,6 +4245,7 @@
4165
4245
  "removeRelationship",
4166
4246
  "save",
4167
4247
  "searchByEmail",
4248
+ "setLinks",
4168
4249
  "setTypeBySlug",
4169
4250
  "updateMetadata"
4170
4251
  ],
@@ -4266,6 +4347,11 @@
4266
4347
  ],
4267
4348
  "returns": "Promise<AuditLog[]>"
4268
4349
  },
4350
+ {
4351
+ "name": "getLinks",
4352
+ "async": true,
4353
+ "returns": "Promise<ProfileLink[]>"
4354
+ },
4269
4355
  {
4270
4356
  "name": "getMetadata",
4271
4357
  "async": true,
@@ -4374,6 +4460,14 @@
4374
4460
  ],
4375
4461
  "returns": "Promise<Profile | null>"
4376
4462
  },
4463
+ {
4464
+ "name": "setLinks",
4465
+ "async": true,
4466
+ "params": [
4467
+ "links: any"
4468
+ ],
4469
+ "returns": "Promise<ProfileLink[]>"
4470
+ },
4377
4471
  {
4378
4472
  "name": "setTypeBySlug",
4379
4473
  "async": true,
@@ -4510,6 +4604,18 @@
4510
4604
  "operation": "removeMetadata",
4511
4605
  "objectName": "@happyvertical/smrt-profiles:Profile"
4512
4606
  },
4607
+ {
4608
+ "kind": "cli",
4609
+ "name": "profile_getLinks",
4610
+ "operation": "getLinks",
4611
+ "objectName": "@happyvertical/smrt-profiles:Profile"
4612
+ },
4613
+ {
4614
+ "kind": "cli",
4615
+ "name": "profile_setLinks",
4616
+ "operation": "setLinks",
4617
+ "objectName": "@happyvertical/smrt-profiles:Profile"
4618
+ },
4513
4619
  {
4514
4620
  "kind": "cli",
4515
4621
  "name": "profile_getAssets",
@@ -4752,6 +4858,13 @@
4752
4858
  "reason": "not listed in api.include",
4753
4859
  "objectName": "@happyvertical/smrt-profiles:Profile"
4754
4860
  },
4861
+ {
4862
+ "kind": "api",
4863
+ "operation": "getLinks",
4864
+ "code": "not-included",
4865
+ "reason": "not listed in api.include",
4866
+ "objectName": "@happyvertical/smrt-profiles:Profile"
4867
+ },
4755
4868
  {
4756
4869
  "kind": "api",
4757
4870
  "operation": "getMetadata",
@@ -4857,6 +4970,13 @@
4857
4970
  "reason": "not listed in api.include",
4858
4971
  "objectName": "@happyvertical/smrt-profiles:Profile"
4859
4972
  },
4973
+ {
4974
+ "kind": "api",
4975
+ "operation": "setLinks",
4976
+ "code": "not-included",
4977
+ "reason": "not listed in api.include",
4978
+ "objectName": "@happyvertical/smrt-profiles:Profile"
4979
+ },
4860
4980
  {
4861
4981
  "kind": "api",
4862
4982
  "operation": "setTypeBySlug",
@@ -4958,6 +5078,125 @@
4958
5078
  "tags": [],
4959
5079
  "risks": []
4960
5080
  },
5081
+ {
5082
+ "name": "ProfileLink",
5083
+ "qualifiedName": "@happyvertical/smrt-profiles:ProfileLink",
5084
+ "collection": "profilelinks",
5085
+ "tableName": "profile_links",
5086
+ "packageName": "@happyvertical/smrt-profiles",
5087
+ "extends": "SmrtObject",
5088
+ "fields": [
5089
+ {
5090
+ "name": "tenantId",
5091
+ "type": "text",
5092
+ "required": false,
5093
+ "columnType": "UUID"
5094
+ },
5095
+ {
5096
+ "name": "profileId",
5097
+ "type": "foreignKey",
5098
+ "required": true,
5099
+ "related": "Profile",
5100
+ "columnType": "UUID"
5101
+ },
5102
+ {
5103
+ "name": "platform",
5104
+ "type": "text",
5105
+ "required": true,
5106
+ "columnType": "TEXT",
5107
+ "default": "website",
5108
+ "constraints": {
5109
+ "maxLength": 32
5110
+ }
5111
+ },
5112
+ {
5113
+ "name": "url",
5114
+ "type": "text",
5115
+ "required": true,
5116
+ "columnType": "TEXT",
5117
+ "constraints": {
5118
+ "maxLength": 2048
5119
+ }
5120
+ },
5121
+ {
5122
+ "name": "label",
5123
+ "type": "text",
5124
+ "required": false,
5125
+ "columnType": "TEXT",
5126
+ "constraints": {
5127
+ "maxLength": 200
5128
+ }
5129
+ },
5130
+ {
5131
+ "name": "sortOrder",
5132
+ "type": "integer",
5133
+ "required": true,
5134
+ "columnType": "INTEGER",
5135
+ "default": 0
5136
+ }
5137
+ ],
5138
+ "relationships": [
5139
+ {
5140
+ "name": "profileId",
5141
+ "type": "foreignKey",
5142
+ "required": true,
5143
+ "related": "Profile",
5144
+ "columnType": "UUID"
5145
+ }
5146
+ ],
5147
+ "methods": [
5148
+ "getSlug",
5149
+ "save"
5150
+ ],
5151
+ "methodSignatures": [
5152
+ {
5153
+ "name": "getSlug",
5154
+ "async": true,
5155
+ "returns": "Promise<string | null | undefined>"
5156
+ },
5157
+ {
5158
+ "name": "save",
5159
+ "async": true,
5160
+ "params": [
5161
+ "...args?: any"
5162
+ ],
5163
+ "returns": "Promise<this>"
5164
+ }
5165
+ ],
5166
+ "tenant": {
5167
+ "scoped": true,
5168
+ "mode": "optional",
5169
+ "field": "tenantId"
5170
+ },
5171
+ "conflictColumns": [
5172
+ "tenant_id",
5173
+ "slug",
5174
+ "context"
5175
+ ],
5176
+ "surfaces": [],
5177
+ "withheldSurfaces": [
5178
+ {
5179
+ "kind": "api",
5180
+ "operation": "getSlug",
5181
+ "code": "api-disabled",
5182
+ "reason": "api is disabled",
5183
+ "objectName": "@happyvertical/smrt-profiles:ProfileLink"
5184
+ },
5185
+ {
5186
+ "kind": "api",
5187
+ "operation": "save",
5188
+ "code": "api-disabled",
5189
+ "reason": "api is disabled",
5190
+ "objectName": "@happyvertical/smrt-profiles:ProfileLink"
5191
+ }
5192
+ ],
5193
+ "relationshipFeatures": [
5194
+ "foreignKey",
5195
+ "uuidColumns"
5196
+ ],
5197
+ "tags": [],
5198
+ "risks": []
5199
+ },
4961
5200
  {
4962
5201
  "name": "ProfileMetadata",
4963
5202
  "qualifiedName": "@happyvertical/smrt-profiles:ProfileMetadata",
@@ -6351,6 +6590,13 @@
6351
6590
  "required": false,
6352
6591
  "columnType": "TEXT"
6353
6592
  },
6593
+ {
6594
+ "name": "isPublic",
6595
+ "type": "boolean",
6596
+ "required": false,
6597
+ "columnType": "BOOLEAN",
6598
+ "default": false
6599
+ },
6354
6600
  {
6355
6601
  "name": "metadata",
6356
6602
  "type": "oneToMany",
@@ -6410,6 +6656,7 @@
6410
6656
  "getApiKeys",
6411
6657
  "getAssets",
6412
6658
  "getAuditLogs",
6659
+ "getLinks",
6413
6660
  "getMetadata",
6414
6661
  "getNostrIdentities",
6415
6662
  "getOidcIdentities",
@@ -6425,6 +6672,7 @@
6425
6672
  "removeRelationship",
6426
6673
  "save",
6427
6674
  "searchByEmail",
6675
+ "setLinks",
6428
6676
  "setTypeBySlug",
6429
6677
  "updateMetadata"
6430
6678
  ],
@@ -6526,6 +6774,11 @@
6526
6774
  ],
6527
6775
  "returns": "Promise<AuditLog[]>"
6528
6776
  },
6777
+ {
6778
+ "name": "getLinks",
6779
+ "async": true,
6780
+ "returns": "Promise<ProfileLink[]>"
6781
+ },
6529
6782
  {
6530
6783
  "name": "getMetadata",
6531
6784
  "async": true,
@@ -6634,6 +6887,14 @@
6634
6887
  ],
6635
6888
  "returns": "Promise<Profile | null>"
6636
6889
  },
6890
+ {
6891
+ "name": "setLinks",
6892
+ "async": true,
6893
+ "params": [
6894
+ "links: any"
6895
+ ],
6896
+ "returns": "Promise<ProfileLink[]>"
6897
+ },
6637
6898
  {
6638
6899
  "name": "setTypeBySlug",
6639
6900
  "async": true,
@@ -6741,6 +7002,14 @@
6741
7002
  "path": "/profiles/[id]/removeMetadata",
6742
7003
  "method": "POST"
6743
7004
  },
7005
+ {
7006
+ "kind": "api",
7007
+ "name": "profiles.getLinks",
7008
+ "operation": "getLinks",
7009
+ "objectName": "@happyvertical/smrt-profiles:Person",
7010
+ "path": "/profiles/[id]/getLinks",
7011
+ "method": "POST"
7012
+ },
6744
7013
  {
6745
7014
  "kind": "api",
6746
7015
  "name": "profiles.getAssets",
@@ -6951,6 +7220,18 @@
6951
7220
  "operation": "removeMetadata",
6952
7221
  "objectName": "@happyvertical/smrt-profiles:Person"
6953
7222
  },
7223
+ {
7224
+ "kind": "cli",
7225
+ "name": "person_getLinks",
7226
+ "operation": "getLinks",
7227
+ "objectName": "@happyvertical/smrt-profiles:Person"
7228
+ },
7229
+ {
7230
+ "kind": "cli",
7231
+ "name": "person_setLinks",
7232
+ "operation": "setLinks",
7233
+ "objectName": "@happyvertical/smrt-profiles:Person"
7234
+ },
6954
7235
  {
6955
7236
  "kind": "cli",
6956
7237
  "name": "person_getAssets",
@@ -7149,6 +7430,18 @@
7149
7430
  "operation": "removeMetadata",
7150
7431
  "objectName": "@happyvertical/smrt-profiles:Person"
7151
7432
  },
7433
+ {
7434
+ "kind": "mcp",
7435
+ "name": "person_getlinks",
7436
+ "operation": "getLinks",
7437
+ "objectName": "@happyvertical/smrt-profiles:Person"
7438
+ },
7439
+ {
7440
+ "kind": "mcp",
7441
+ "name": "person_setlinks",
7442
+ "operation": "setLinks",
7443
+ "objectName": "@happyvertical/smrt-profiles:Person"
7444
+ },
7152
7445
  {
7153
7446
  "kind": "mcp",
7154
7447
  "name": "person_getassets",
@@ -7317,6 +7610,13 @@
7317
7610
  "code": "lifecycle-method",
7318
7611
  "reason": "`save` is a framework lifecycle method, not a distinct operation",
7319
7612
  "objectName": "@happyvertical/smrt-profiles:Person"
7613
+ },
7614
+ {
7615
+ "kind": "api",
7616
+ "operation": "setLinks",
7617
+ "code": "not-wireable",
7618
+ "reason": "not routed: the declared type of `links` could not be resolved by the scanner",
7619
+ "objectName": "@happyvertical/smrt-profiles:Person"
7320
7620
  }
7321
7621
  ],
7322
7622
  "relationshipFeatures": [
@@ -7386,6 +7686,13 @@
7386
7686
  "required": false,
7387
7687
  "columnType": "TEXT"
7388
7688
  },
7689
+ {
7690
+ "name": "isPublic",
7691
+ "type": "boolean",
7692
+ "required": false,
7693
+ "columnType": "BOOLEAN",
7694
+ "default": false
7695
+ },
7389
7696
  {
7390
7697
  "name": "metadata",
7391
7698
  "type": "oneToMany",
@@ -7445,6 +7752,7 @@
7445
7752
  "getApiKeys",
7446
7753
  "getAssets",
7447
7754
  "getAuditLogs",
7755
+ "getLinks",
7448
7756
  "getMetadata",
7449
7757
  "getNostrIdentities",
7450
7758
  "getOidcIdentities",
@@ -7460,6 +7768,7 @@
7460
7768
  "removeRelationship",
7461
7769
  "save",
7462
7770
  "searchByEmail",
7771
+ "setLinks",
7463
7772
  "setTypeBySlug",
7464
7773
  "updateMetadata"
7465
7774
  ],
@@ -7561,6 +7870,11 @@
7561
7870
  ],
7562
7871
  "returns": "Promise<AuditLog[]>"
7563
7872
  },
7873
+ {
7874
+ "name": "getLinks",
7875
+ "async": true,
7876
+ "returns": "Promise<ProfileLink[]>"
7877
+ },
7564
7878
  {
7565
7879
  "name": "getMetadata",
7566
7880
  "async": true,
@@ -7669,6 +7983,14 @@
7669
7983
  ],
7670
7984
  "returns": "Promise<Profile | null>"
7671
7985
  },
7986
+ {
7987
+ "name": "setLinks",
7988
+ "async": true,
7989
+ "params": [
7990
+ "links: any"
7991
+ ],
7992
+ "returns": "Promise<ProfileLink[]>"
7993
+ },
7672
7994
  {
7673
7995
  "name": "setTypeBySlug",
7674
7996
  "async": true,
@@ -7776,6 +8098,14 @@
7776
8098
  "path": "/profiles/[id]/removeMetadata",
7777
8099
  "method": "POST"
7778
8100
  },
8101
+ {
8102
+ "kind": "api",
8103
+ "name": "profiles.getLinks",
8104
+ "operation": "getLinks",
8105
+ "objectName": "@happyvertical/smrt-profiles:Organization",
8106
+ "path": "/profiles/[id]/getLinks",
8107
+ "method": "POST"
8108
+ },
7779
8109
  {
7780
8110
  "kind": "api",
7781
8111
  "name": "profiles.getAssets",
@@ -7986,6 +8316,18 @@
7986
8316
  "operation": "removeMetadata",
7987
8317
  "objectName": "@happyvertical/smrt-profiles:Organization"
7988
8318
  },
8319
+ {
8320
+ "kind": "cli",
8321
+ "name": "organization_getLinks",
8322
+ "operation": "getLinks",
8323
+ "objectName": "@happyvertical/smrt-profiles:Organization"
8324
+ },
8325
+ {
8326
+ "kind": "cli",
8327
+ "name": "organization_setLinks",
8328
+ "operation": "setLinks",
8329
+ "objectName": "@happyvertical/smrt-profiles:Organization"
8330
+ },
7989
8331
  {
7990
8332
  "kind": "cli",
7991
8333
  "name": "organization_getAssets",
@@ -8184,6 +8526,18 @@
8184
8526
  "operation": "removeMetadata",
8185
8527
  "objectName": "@happyvertical/smrt-profiles:Organization"
8186
8528
  },
8529
+ {
8530
+ "kind": "mcp",
8531
+ "name": "organization_getlinks",
8532
+ "operation": "getLinks",
8533
+ "objectName": "@happyvertical/smrt-profiles:Organization"
8534
+ },
8535
+ {
8536
+ "kind": "mcp",
8537
+ "name": "organization_setlinks",
8538
+ "operation": "setLinks",
8539
+ "objectName": "@happyvertical/smrt-profiles:Organization"
8540
+ },
8187
8541
  {
8188
8542
  "kind": "mcp",
8189
8543
  "name": "organization_getassets",
@@ -8352,6 +8706,13 @@
8352
8706
  "code": "lifecycle-method",
8353
8707
  "reason": "`save` is a framework lifecycle method, not a distinct operation",
8354
8708
  "objectName": "@happyvertical/smrt-profiles:Organization"
8709
+ },
8710
+ {
8711
+ "kind": "api",
8712
+ "operation": "setLinks",
8713
+ "code": "not-wireable",
8714
+ "reason": "not routed: the declared type of `links` could not be resolved by the scanner",
8715
+ "objectName": "@happyvertical/smrt-profiles:Organization"
8355
8716
  }
8356
8717
  ],
8357
8718
  "relationshipFeatures": [
@@ -8421,6 +8782,13 @@
8421
8782
  "required": false,
8422
8783
  "columnType": "TEXT"
8423
8784
  },
8785
+ {
8786
+ "name": "isPublic",
8787
+ "type": "boolean",
8788
+ "required": false,
8789
+ "columnType": "BOOLEAN",
8790
+ "default": false
8791
+ },
8424
8792
  {
8425
8793
  "name": "metadata",
8426
8794
  "type": "oneToMany",
@@ -8480,6 +8848,7 @@
8480
8848
  "getApiKeys",
8481
8849
  "getAssets",
8482
8850
  "getAuditLogs",
8851
+ "getLinks",
8483
8852
  "getMetadata",
8484
8853
  "getNostrIdentities",
8485
8854
  "getOidcIdentities",
@@ -8495,6 +8864,7 @@
8495
8864
  "removeRelationship",
8496
8865
  "save",
8497
8866
  "searchByEmail",
8867
+ "setLinks",
8498
8868
  "setTypeBySlug",
8499
8869
  "updateMetadata"
8500
8870
  ],
@@ -8596,6 +8966,11 @@
8596
8966
  ],
8597
8967
  "returns": "Promise<AuditLog[]>"
8598
8968
  },
8969
+ {
8970
+ "name": "getLinks",
8971
+ "async": true,
8972
+ "returns": "Promise<ProfileLink[]>"
8973
+ },
8599
8974
  {
8600
8975
  "name": "getMetadata",
8601
8976
  "async": true,
@@ -8704,6 +9079,14 @@
8704
9079
  ],
8705
9080
  "returns": "Promise<Profile | null>"
8706
9081
  },
9082
+ {
9083
+ "name": "setLinks",
9084
+ "async": true,
9085
+ "params": [
9086
+ "links: any"
9087
+ ],
9088
+ "returns": "Promise<ProfileLink[]>"
9089
+ },
8707
9090
  {
8708
9091
  "name": "setTypeBySlug",
8709
9092
  "async": true,
@@ -8811,6 +9194,14 @@
8811
9194
  "path": "/profiles/[id]/removeMetadata",
8812
9195
  "method": "POST"
8813
9196
  },
9197
+ {
9198
+ "kind": "api",
9199
+ "name": "profiles.getLinks",
9200
+ "operation": "getLinks",
9201
+ "objectName": "@happyvertical/smrt-profiles:Bot",
9202
+ "path": "/profiles/[id]/getLinks",
9203
+ "method": "POST"
9204
+ },
8814
9205
  {
8815
9206
  "kind": "api",
8816
9207
  "name": "profiles.getAssets",
@@ -9021,6 +9412,18 @@
9021
9412
  "operation": "removeMetadata",
9022
9413
  "objectName": "@happyvertical/smrt-profiles:Bot"
9023
9414
  },
9415
+ {
9416
+ "kind": "cli",
9417
+ "name": "bot_getLinks",
9418
+ "operation": "getLinks",
9419
+ "objectName": "@happyvertical/smrt-profiles:Bot"
9420
+ },
9421
+ {
9422
+ "kind": "cli",
9423
+ "name": "bot_setLinks",
9424
+ "operation": "setLinks",
9425
+ "objectName": "@happyvertical/smrt-profiles:Bot"
9426
+ },
9024
9427
  {
9025
9428
  "kind": "cli",
9026
9429
  "name": "bot_getAssets",
@@ -9219,6 +9622,18 @@
9219
9622
  "operation": "removeMetadata",
9220
9623
  "objectName": "@happyvertical/smrt-profiles:Bot"
9221
9624
  },
9625
+ {
9626
+ "kind": "mcp",
9627
+ "name": "bot_getlinks",
9628
+ "operation": "getLinks",
9629
+ "objectName": "@happyvertical/smrt-profiles:Bot"
9630
+ },
9631
+ {
9632
+ "kind": "mcp",
9633
+ "name": "bot_setlinks",
9634
+ "operation": "setLinks",
9635
+ "objectName": "@happyvertical/smrt-profiles:Bot"
9636
+ },
9222
9637
  {
9223
9638
  "kind": "mcp",
9224
9639
  "name": "bot_getassets",
@@ -9387,6 +9802,13 @@
9387
9802
  "code": "lifecycle-method",
9388
9803
  "reason": "`save` is a framework lifecycle method, not a distinct operation",
9389
9804
  "objectName": "@happyvertical/smrt-profiles:Bot"
9805
+ },
9806
+ {
9807
+ "kind": "api",
9808
+ "operation": "setLinks",
9809
+ "code": "not-wireable",
9810
+ "reason": "not routed: the declared type of `links` could not be resolved by the scanner",
9811
+ "objectName": "@happyvertical/smrt-profiles:Bot"
9390
9812
  }
9391
9813
  ],
9392
9814
  "relationshipFeatures": [
@@ -11317,6 +11739,18 @@
11317
11739
  "operation": "removeMetadata",
11318
11740
  "objectName": "@happyvertical/smrt-profiles:Profile"
11319
11741
  },
11742
+ {
11743
+ "kind": "cli",
11744
+ "name": "profile_getLinks",
11745
+ "operation": "getLinks",
11746
+ "objectName": "@happyvertical/smrt-profiles:Profile"
11747
+ },
11748
+ {
11749
+ "kind": "cli",
11750
+ "name": "profile_setLinks",
11751
+ "operation": "setLinks",
11752
+ "objectName": "@happyvertical/smrt-profiles:Profile"
11753
+ },
11320
11754
  {
11321
11755
  "kind": "cli",
11322
11756
  "name": "profile_getAssets",
@@ -12135,6 +12569,14 @@
12135
12569
  "path": "/profiles/[id]/removeMetadata",
12136
12570
  "method": "POST"
12137
12571
  },
12572
+ {
12573
+ "kind": "api",
12574
+ "name": "profiles.getLinks",
12575
+ "operation": "getLinks",
12576
+ "objectName": "@happyvertical/smrt-profiles:Person",
12577
+ "path": "/profiles/[id]/getLinks",
12578
+ "method": "POST"
12579
+ },
12138
12580
  {
12139
12581
  "kind": "api",
12140
12582
  "name": "profiles.getAssets",
@@ -12345,6 +12787,18 @@
12345
12787
  "operation": "removeMetadata",
12346
12788
  "objectName": "@happyvertical/smrt-profiles:Person"
12347
12789
  },
12790
+ {
12791
+ "kind": "cli",
12792
+ "name": "person_getLinks",
12793
+ "operation": "getLinks",
12794
+ "objectName": "@happyvertical/smrt-profiles:Person"
12795
+ },
12796
+ {
12797
+ "kind": "cli",
12798
+ "name": "person_setLinks",
12799
+ "operation": "setLinks",
12800
+ "objectName": "@happyvertical/smrt-profiles:Person"
12801
+ },
12348
12802
  {
12349
12803
  "kind": "cli",
12350
12804
  "name": "person_getAssets",
@@ -12543,6 +12997,18 @@
12543
12997
  "operation": "removeMetadata",
12544
12998
  "objectName": "@happyvertical/smrt-profiles:Person"
12545
12999
  },
13000
+ {
13001
+ "kind": "mcp",
13002
+ "name": "person_getlinks",
13003
+ "operation": "getLinks",
13004
+ "objectName": "@happyvertical/smrt-profiles:Person"
13005
+ },
13006
+ {
13007
+ "kind": "mcp",
13008
+ "name": "person_setlinks",
13009
+ "operation": "setLinks",
13010
+ "objectName": "@happyvertical/smrt-profiles:Person"
13011
+ },
12546
13012
  {
12547
13013
  "kind": "mcp",
12548
13014
  "name": "person_getassets",
@@ -12763,6 +13229,14 @@
12763
13229
  "path": "/profiles/[id]/removeMetadata",
12764
13230
  "method": "POST"
12765
13231
  },
13232
+ {
13233
+ "kind": "api",
13234
+ "name": "profiles.getLinks",
13235
+ "operation": "getLinks",
13236
+ "objectName": "@happyvertical/smrt-profiles:Organization",
13237
+ "path": "/profiles/[id]/getLinks",
13238
+ "method": "POST"
13239
+ },
12766
13240
  {
12767
13241
  "kind": "api",
12768
13242
  "name": "profiles.getAssets",
@@ -12973,6 +13447,18 @@
12973
13447
  "operation": "removeMetadata",
12974
13448
  "objectName": "@happyvertical/smrt-profiles:Organization"
12975
13449
  },
13450
+ {
13451
+ "kind": "cli",
13452
+ "name": "organization_getLinks",
13453
+ "operation": "getLinks",
13454
+ "objectName": "@happyvertical/smrt-profiles:Organization"
13455
+ },
13456
+ {
13457
+ "kind": "cli",
13458
+ "name": "organization_setLinks",
13459
+ "operation": "setLinks",
13460
+ "objectName": "@happyvertical/smrt-profiles:Organization"
13461
+ },
12976
13462
  {
12977
13463
  "kind": "cli",
12978
13464
  "name": "organization_getAssets",
@@ -13171,6 +13657,18 @@
13171
13657
  "operation": "removeMetadata",
13172
13658
  "objectName": "@happyvertical/smrt-profiles:Organization"
13173
13659
  },
13660
+ {
13661
+ "kind": "mcp",
13662
+ "name": "organization_getlinks",
13663
+ "operation": "getLinks",
13664
+ "objectName": "@happyvertical/smrt-profiles:Organization"
13665
+ },
13666
+ {
13667
+ "kind": "mcp",
13668
+ "name": "organization_setlinks",
13669
+ "operation": "setLinks",
13670
+ "objectName": "@happyvertical/smrt-profiles:Organization"
13671
+ },
13174
13672
  {
13175
13673
  "kind": "mcp",
13176
13674
  "name": "organization_getassets",
@@ -13391,6 +13889,14 @@
13391
13889
  "path": "/profiles/[id]/removeMetadata",
13392
13890
  "method": "POST"
13393
13891
  },
13892
+ {
13893
+ "kind": "api",
13894
+ "name": "profiles.getLinks",
13895
+ "operation": "getLinks",
13896
+ "objectName": "@happyvertical/smrt-profiles:Bot",
13897
+ "path": "/profiles/[id]/getLinks",
13898
+ "method": "POST"
13899
+ },
13394
13900
  {
13395
13901
  "kind": "api",
13396
13902
  "name": "profiles.getAssets",
@@ -13601,6 +14107,18 @@
13601
14107
  "operation": "removeMetadata",
13602
14108
  "objectName": "@happyvertical/smrt-profiles:Bot"
13603
14109
  },
14110
+ {
14111
+ "kind": "cli",
14112
+ "name": "bot_getLinks",
14113
+ "operation": "getLinks",
14114
+ "objectName": "@happyvertical/smrt-profiles:Bot"
14115
+ },
14116
+ {
14117
+ "kind": "cli",
14118
+ "name": "bot_setLinks",
14119
+ "operation": "setLinks",
14120
+ "objectName": "@happyvertical/smrt-profiles:Bot"
14121
+ },
13604
14122
  {
13605
14123
  "kind": "cli",
13606
14124
  "name": "bot_getAssets",
@@ -13799,6 +14317,18 @@
13799
14317
  "operation": "removeMetadata",
13800
14318
  "objectName": "@happyvertical/smrt-profiles:Bot"
13801
14319
  },
14320
+ {
14321
+ "kind": "mcp",
14322
+ "name": "bot_getlinks",
14323
+ "operation": "getLinks",
14324
+ "objectName": "@happyvertical/smrt-profiles:Bot"
14325
+ },
14326
+ {
14327
+ "kind": "mcp",
14328
+ "name": "bot_setlinks",
14329
+ "operation": "setLinks",
14330
+ "objectName": "@happyvertical/smrt-profiles:Bot"
14331
+ },
13802
14332
  {
13803
14333
  "kind": "mcp",
13804
14334
  "name": "bot_getassets",
@@ -13938,12 +14468,12 @@
13938
14468
  }
13939
14469
  ],
13940
14470
  "relationshipsV2": {
13941
- "foreignKeyFields": 19,
14471
+ "foreignKeyFields": 20,
13942
14472
  "crossPackageRefFields": 1,
13943
14473
  "junctionCollections": 1,
13944
14474
  "hierarchicalObjects": 0,
13945
14475
  "polymorphicAssociations": 0,
13946
- "uuidColumns": 85
14476
+ "uuidColumns": 91
13947
14477
  },
13948
- "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 (optional; identity uniqueness is arbitrated by `oidc_profile_email_reservations.email_key`, not a DB constraint — legacy duplicates are tolerated and fail closed, #2359), 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## Agent (bot) profiles (#2995)\n\nAn automated agent is an author, and every authoring seam in the framework\n(`ChatMessage.senderProfileId`, `ChatParticipant.profileId`, a persona's\n`actsAsProfileId`) is a `crossPackageRef` to `Profile` — a native uuid column on\nPostgreSQL/DuckDB. A slug-style agent id is therefore not a representable author\n(`22P02`). `src/agent-profile.ts` is the owning-package API that turns one into\na real Profile so no consumer mints bot profiles itself and no uuid column is\nweakened to text:\n\n- `resolveAgentProfile(profiles, { agentId, tenantId, name? })` /\n `resolveAgentProfileId(...)` — resolve, creating on first use, the `bot`\n Profile an agent authors as. Idempotent and tenant-bound: identity is keyed on\n `(tenantId, slug = agentId, context = AGENT_PROFILE_CONTEXT)`, matching the\n model's `(tenant_id, slug, context, _meta_type)` unique key, so an agent id\n resolves to a distinct profile per tenant and never collides with a\n person/organization sharing the slug. Classification is the `bot` ProfileType\n (`AGENT_PROFILE_TYPE_SLUG`), created on demand. Parallel first use converges\n on the natural key rather than on whoever's INSERT won (each attempt re-reads\n before and after writing), which matters because NULLs are distinct in the\n `(tenant_id, slug, context)` unique index, so an untenanted agent is not\n arbitrated by the database (smrt#2360). Pinned against real PostgreSQL by\n `src/__tests__/agent-profile-postgres.test.ts`\n (`pnpm --filter @happyvertical/smrt-profiles test:postgres`).\n\nFirst consumer: `@happyvertical/smrt-chat`'s `ChatService`.\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 every root-handle statement it owns per\n database URL on SQLite/DuckDB — shared `_smrt_backfills` initialization, the\n provisioning transaction, and the post-commit rebind — because those adapters\n multiplex one native connection and cannot overlap unrelated root\n transactions safely. **Never overlap two statements on one such handle,\n transaction-bound or not** — no `Promise.all` over reads, not even\n primary-key rebinds — because DuckDB fails the losing prepared statement or\n aborts the process outright. It retries\n bounded PostgreSQL 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 the typed canonical scenario contract is\n `src/testing/oidcProvisioningDecisionMatrix.ts`, executed by both Profiles\n and Users tests; keep public docs pointed at it instead of adding a second\n behavioral table.\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 hooks. A pre-provisioned owned Person remains\n fail-closed unless the application explicitly uses `authorizeProfileOwner`\n to select both the canonical Profile and its existing approved sole owner;\n the users package revalidates those IDs, emails, and identity authority in\n the provisioning transaction.\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- **Manifest objects must remain root-importable**: the non-API\n `OidcProfileEmailReservation` model and collection are public root exports\n because generated consumer registration imports every advertised manifest\n symbol from `@happyvertical/smrt-profiles`. Release-pack validation imports\n the actual generated register file from the packed tarball.\n- **Optional tenancy** on Profile; AuditLog allows super-admin bypass\n"
14478
+ "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 (optional; identity uniqueness is arbitrated by `oidc_profile_email_reservations.email_key`, not a DB constraint — legacy duplicates are tolerated and fail closed, #2359), 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- **ProfileLink**: ordered web links in `profile_links` (`platform` from `PROFILE_LINK_PLATFORMS`, validated http(s) `url`, optional `label`, `sortOrder`). `profileId` FK is `onDelete: 'CASCADE'`. Slug is the id (never url/label, so a new link cannot adopt another row through the natural key). Write through `ProfileLinkCollection.replaceForProfile()` / `reorder()` (one transaction each; resolve the profile in the current tenant context and stamp its tenant) or `Profile.setLinks()` / `getLinks()`.\n- **Profile.isPublic**: optional public-figure flag (boolean, default false). Records the fact only; consumers decide visibility.\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. `addMetadata()` stamps new rows with the profile's `tenantId` (never rely on auto-population, which super-admin/system context skips, #3235) and resolves the metafield as profile-tenant first, then global, never another tenant's.\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## Agent (bot) profiles (#2995)\n\nAn automated agent is an author, and every authoring seam in the framework\n(`ChatMessage.senderProfileId`, `ChatParticipant.profileId`, a persona's\n`actsAsProfileId`) is a `crossPackageRef` to `Profile` — a native uuid column on\nPostgreSQL/DuckDB. A slug-style agent id is therefore not a representable author\n(`22P02`). `src/agent-profile.ts` is the owning-package API that turns one into\na real Profile so no consumer mints bot profiles itself and no uuid column is\nweakened to text:\n\n- `resolveAgentProfile(profiles, { agentId, tenantId, name? })` /\n `resolveAgentProfileId(...)` — resolve, creating on first use, the `bot`\n Profile an agent authors as. Idempotent and tenant-bound: identity is keyed on\n `(tenantId, slug = agentId, context = AGENT_PROFILE_CONTEXT)`, matching the\n model's `(tenant_id, slug, context, _meta_type)` unique key, so an agent id\n resolves to a distinct profile per tenant and never collides with a\n person/organization sharing the slug. Classification is the `bot` ProfileType\n (`AGENT_PROFILE_TYPE_SLUG`), created on demand. Parallel first use converges\n on the natural key rather than on whoever's INSERT won (each attempt re-reads\n before and after writing), which matters because NULLs are distinct in the\n `(tenant_id, slug, context)` unique index, so an untenanted agent is not\n arbitrated by the database (smrt#2360). Pinned against real PostgreSQL by\n `src/__tests__/agent-profile-postgres.test.ts`\n (`pnpm --filter @happyvertical/smrt-profiles test:postgres`).\n\nFirst consumer: `@happyvertical/smrt-chat`'s `ChatService`.\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 every root-handle statement it owns per\n database URL on SQLite/DuckDB — shared `_smrt_backfills` initialization, the\n provisioning transaction, and the post-commit rebind — because those adapters\n multiplex one native connection and cannot overlap unrelated root\n transactions safely. **Never overlap two statements on one such handle,\n transaction-bound or not** — no `Promise.all` over reads, not even\n primary-key rebinds — because DuckDB fails the losing prepared statement or\n aborts the process outright. It retries\n bounded PostgreSQL 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 the typed canonical scenario contract is\n `src/testing/oidcProvisioningDecisionMatrix.ts`, executed by both Profiles\n and Users tests; keep public docs pointed at it instead of adding a second\n behavioral table.\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 hooks. A pre-provisioned owned Person remains\n fail-closed unless the application explicitly uses `authorizeProfileOwner`\n to select both the canonical Profile and its existing approved sole owner;\n the users package revalidates those IDs, emails, and identity authority in\n the provisioning transaction.\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- **Manifest objects must remain root-importable**: the non-API\n `OidcProfileEmailReservation` model and collection are public root exports\n because generated consumer registration imports every advertised manifest\n symbol from `@happyvertical/smrt-profiles`. Release-pack validation imports\n the actual generated register file from the packed tarball.\n- **Optional tenancy** on Profile; AuditLog allows super-admin bypass\n"
13949
14479
  }