@happyvertical/smrt-profiles 0.55.6 → 0.55.8
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/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +18 -1
- package/dist/smrt-knowledge.json +21 -4
- package/package.json +11 -11
package/dist/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": "1.0.0",
|
|
3
3
|
"timestamp": 0,
|
|
4
4
|
"packageName": "@happyvertical/smrt-profiles",
|
|
5
|
-
"packageVersion": "0.55.
|
|
5
|
+
"packageVersion": "0.55.8",
|
|
6
6
|
"objects": {
|
|
7
7
|
"@happyvertical/smrt-profiles:ApiKeyCollection": {
|
|
8
8
|
"name": "apikeycollection",
|
|
@@ -1374,6 +1374,7 @@
|
|
|
1374
1374
|
"className": "OidcProfileEmailReservationCollection",
|
|
1375
1375
|
"qualifiedName": "@happyvertical/smrt-profiles:OidcProfileEmailReservationCollection",
|
|
1376
1376
|
"collection": "oidcprofileemailreservations",
|
|
1377
|
+
"description": "Internal collection for canonical OIDC email reservations.",
|
|
1377
1378
|
"filePath": "/home/runner/work/smrt/smrt/packages/profiles/src/collections/OidcProfileEmailReservationCollection.ts",
|
|
1378
1379
|
"packageName": "@happyvertical/smrt-profiles",
|
|
1379
1380
|
"fields": {},
|
|
@@ -4907,6 +4908,7 @@
|
|
|
4907
4908
|
"className": "OidcProfileEmailReservation",
|
|
4908
4909
|
"qualifiedName": "@happyvertical/smrt-profiles:OidcProfileEmailReservation",
|
|
4909
4910
|
"collection": "oidcprofileemailreservations",
|
|
4911
|
+
"description": "Private database arbiter for canonical OIDC email provisioning races.",
|
|
4910
4912
|
"filePath": "/home/runner/work/smrt/smrt/packages/profiles/src/models/OidcProfileEmailReservation.ts",
|
|
4911
4913
|
"packageName": "@happyvertical/smrt-profiles",
|
|
4912
4914
|
"fields": {
|
|
@@ -7561,6 +7563,7 @@
|
|
|
7561
7563
|
"className": "Person",
|
|
7562
7564
|
"qualifiedName": "@happyvertical/smrt-profiles:Person",
|
|
7563
7565
|
"collection": "profiles",
|
|
7566
|
+
"description": "Person profile type",
|
|
7564
7567
|
"filePath": "/home/runner/work/smrt/smrt/packages/profiles/src/models/ProfileTypes.ts",
|
|
7565
7568
|
"packageName": "@happyvertical/smrt-profiles",
|
|
7566
7569
|
"fields": {
|
|
@@ -8245,6 +8248,7 @@
|
|
|
8245
8248
|
"className": "Organization",
|
|
8246
8249
|
"qualifiedName": "@happyvertical/smrt-profiles:Organization",
|
|
8247
8250
|
"collection": "profiles",
|
|
8251
|
+
"description": "Organization profile type",
|
|
8248
8252
|
"filePath": "/home/runner/work/smrt/smrt/packages/profiles/src/models/ProfileTypes.ts",
|
|
8249
8253
|
"packageName": "@happyvertical/smrt-profiles",
|
|
8250
8254
|
"fields": {
|
|
@@ -8929,6 +8933,7 @@
|
|
|
8929
8933
|
"className": "Bot",
|
|
8930
8934
|
"qualifiedName": "@happyvertical/smrt-profiles:Bot",
|
|
8931
8935
|
"collection": "profiles",
|
|
8936
|
+
"description": "Bot profile type",
|
|
8932
8937
|
"filePath": "/home/runner/work/smrt/smrt/packages/profiles/src/models/ProfileTypes.ts",
|
|
8933
8938
|
"packageName": "@happyvertical/smrt-profiles",
|
|
8934
8939
|
"fields": {
|
|
@@ -9616,5 +9621,17 @@
|
|
|
9616
9621
|
"@happyvertical/smrt-prompts",
|
|
9617
9622
|
"@happyvertical/smrt-tenancy"
|
|
9618
9623
|
],
|
|
9624
|
+
"browser": {
|
|
9625
|
+
"status": "server-only",
|
|
9626
|
+
"issues": [
|
|
9627
|
+
"#3617",
|
|
9628
|
+
"#3624",
|
|
9629
|
+
"#3626"
|
|
9630
|
+
],
|
|
9631
|
+
"reason": "node:crypto in root; core migrations (node:os)",
|
|
9632
|
+
"via": [
|
|
9633
|
+
"@happyvertical/smrt-tenancy"
|
|
9634
|
+
]
|
|
9635
|
+
},
|
|
9619
9636
|
"stampsConstructors": true
|
|
9620
9637
|
}
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-profiles",
|
|
6
|
-
"packageVersion": "0.55.
|
|
6
|
+
"packageVersion": "0.55.8",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
10
|
+
"manifest": "c5a73c9f8b997a3edd7701fa43aa09f812360f261a1471ab335fe7eb77b2f6c9",
|
|
11
|
+
"packageJson": "1446900a80221927cf1fa6baad352072f4388c1fbd7e27f352cccb6883340945",
|
|
12
12
|
"agents": "6f73ea6628f32d4799d6c0f9da4ab98a2ae7c5e7e43e2554b8d874233789e170"
|
|
13
13
|
},
|
|
14
14
|
"exports": [
|
|
@@ -1275,6 +1275,7 @@
|
|
|
1275
1275
|
"name": "OidcProfileEmailReservationCollection",
|
|
1276
1276
|
"qualifiedName": "@happyvertical/smrt-profiles:OidcProfileEmailReservationCollection",
|
|
1277
1277
|
"collection": "oidcprofileemailreservations",
|
|
1278
|
+
"description": "Internal collection for canonical OIDC email reservations.",
|
|
1278
1279
|
"tableName": "oidc_profile_email_reservations",
|
|
1279
1280
|
"packageName": "@happyvertical/smrt-profiles",
|
|
1280
1281
|
"extends": "SmrtCollection",
|
|
@@ -3980,6 +3981,7 @@
|
|
|
3980
3981
|
"name": "OidcProfileEmailReservation",
|
|
3981
3982
|
"qualifiedName": "@happyvertical/smrt-profiles:OidcProfileEmailReservation",
|
|
3982
3983
|
"collection": "oidcprofileemailreservations",
|
|
3984
|
+
"description": "Private database arbiter for canonical OIDC email provisioning races.",
|
|
3983
3985
|
"tableName": "oidc_profile_email_reservations",
|
|
3984
3986
|
"packageName": "@happyvertical/smrt-profiles",
|
|
3985
3987
|
"extends": "SmrtObject",
|
|
@@ -6454,6 +6456,7 @@
|
|
|
6454
6456
|
"qualifiedName": "@happyvertical/smrt-profiles:Person",
|
|
6455
6457
|
"displayLabelField": "name",
|
|
6456
6458
|
"collection": "profiles",
|
|
6459
|
+
"description": "Person profile type",
|
|
6457
6460
|
"tableName": "profiles",
|
|
6458
6461
|
"packageName": "@happyvertical/smrt-profiles",
|
|
6459
6462
|
"extends": "Profile",
|
|
@@ -7551,6 +7554,7 @@
|
|
|
7551
7554
|
"qualifiedName": "@happyvertical/smrt-profiles:Organization",
|
|
7552
7555
|
"displayLabelField": "name",
|
|
7553
7556
|
"collection": "profiles",
|
|
7557
|
+
"description": "Organization profile type",
|
|
7554
7558
|
"tableName": "profiles",
|
|
7555
7559
|
"packageName": "@happyvertical/smrt-profiles",
|
|
7556
7560
|
"extends": "Profile",
|
|
@@ -8648,6 +8652,7 @@
|
|
|
8648
8652
|
"qualifiedName": "@happyvertical/smrt-profiles:Bot",
|
|
8649
8653
|
"displayLabelField": "name",
|
|
8650
8654
|
"collection": "profiles",
|
|
8655
|
+
"description": "Bot profile type",
|
|
8651
8656
|
"tableName": "profiles",
|
|
8652
8657
|
"packageName": "@happyvertical/smrt-profiles",
|
|
8653
8658
|
"extends": "Profile",
|
|
@@ -14230,5 +14235,17 @@
|
|
|
14230
14235
|
"polymorphicAssociations": 0,
|
|
14231
14236
|
"uuidColumns": 91
|
|
14232
14237
|
},
|
|
14233
|
-
"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**: sensitive audit persistence with actor, action, reason, before/after `changes`, indexed `occurredAt`, resourceType/Id, source, and delegated identity. No generated read routes. `createAuditWriter`, consumer-authorized `readAuditTrail`, and bounded `pruneAuditTrail` live in `src/audit-trail.ts`; see [audit contract](../../docs/content/audit-trail.md).\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"
|
|
14238
|
+
"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**: sensitive audit persistence with actor, action, reason, before/after `changes`, indexed `occurredAt`, resourceType/Id, source, and delegated identity. No generated read routes. `createAuditWriter`, consumer-authorized `readAuditTrail`, and bounded `pruneAuditTrail` live in `src/audit-trail.ts`; see [audit contract](../../docs/content/audit-trail.md).\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",
|
|
14239
|
+
"browser": {
|
|
14240
|
+
"status": "server-only",
|
|
14241
|
+
"issues": [
|
|
14242
|
+
"#3617",
|
|
14243
|
+
"#3624",
|
|
14244
|
+
"#3626"
|
|
14245
|
+
],
|
|
14246
|
+
"reason": "node:crypto in root; core migrations (node:os)",
|
|
14247
|
+
"via": [
|
|
14248
|
+
"@happyvertical/smrt-tenancy"
|
|
14249
|
+
]
|
|
14250
|
+
}
|
|
14234
14251
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-profiles",
|
|
3
|
-
"version": "0.55.
|
|
3
|
+
"version": "0.55.8",
|
|
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",
|
|
@@ -27,21 +27,21 @@
|
|
|
27
27
|
"./manifest.json": "./dist/manifest.json"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@happyvertical/ai": "^0.
|
|
31
|
-
"@happyvertical/files": "^0.
|
|
32
|
-
"@happyvertical/logger": "^0.
|
|
33
|
-
"@happyvertical/smrt-assets": "0.55.
|
|
34
|
-
"@happyvertical/smrt-core": "0.55.
|
|
35
|
-
"@happyvertical/smrt-prompts": "0.55.
|
|
36
|
-
"@happyvertical/smrt-tenancy": "0.55.
|
|
37
|
-
"@happyvertical/sql": "^0.
|
|
38
|
-
"@happyvertical/utils": "^0.
|
|
30
|
+
"@happyvertical/ai": "^0.103.1",
|
|
31
|
+
"@happyvertical/files": "^0.103.1",
|
|
32
|
+
"@happyvertical/logger": "^0.103.1",
|
|
33
|
+
"@happyvertical/smrt-assets": "0.55.8",
|
|
34
|
+
"@happyvertical/smrt-core": "0.55.8",
|
|
35
|
+
"@happyvertical/smrt-prompts": "0.55.8",
|
|
36
|
+
"@happyvertical/smrt-tenancy": "0.55.8",
|
|
37
|
+
"@happyvertical/sql": "^0.103.1",
|
|
38
|
+
"@happyvertical/utils": "^0.103.1",
|
|
39
39
|
"@noble/curves": "^2.4.0",
|
|
40
40
|
"bech32": "^2.0.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@faker-js/faker": "^10.6.0",
|
|
44
|
-
"@happyvertical/smrt-vitest": "0.55.
|
|
44
|
+
"@happyvertical/smrt-vitest": "0.55.8",
|
|
45
45
|
"@types/node": "24.13.6",
|
|
46
46
|
"fast-glob": "3.3.3",
|
|
47
47
|
"typescript": "6.0.3",
|