@happyvertical/smrt-profiles
People, organizations, identities, metadata, and reciprocal or directional relationships.
What it gives you
People, organizations, identities, metadata, and reciprocal or directional relationships.
- 01Profiles remain separate from authentication records
- 02Reciprocal and directional relationship types
- 03Time-bounded relationship terms and contextual third parties
- 04Organization-to-organization links on the same Profile rows
- 05Ordered web links and a public-figure flag on the profile
Attach identities without exposing credentials
Profiles can carry OIDC and Nostr identities, one-time magic-link records, and API keys. Nostr keypairs are encrypted; API-key material is SHA-256 hashed and shown only at creation. resolveIdentity and the createProfileFromOidc and createProfileFromNostr helpers keep canonical-person and email reservation checks in one place.
Keep extensible metadata and audit evidence controlled
ProfileMetafield supplies the validation schema for ProfileMetadata, whose collection supports add, get, batchGet, and batchUpdate. ProfileAsset is the owned-asset join. AuditLog is a sensitive model with no generated read routes. It records actor, action, reason, before and after changes, resource, source, and delegated identity. createAuditWriter, the consumer-authorized readAuditTrail, and the bounded pruneAuditTrail are its access paths; its super-admin bypass is an explicit policy choice.
Links and public figures
ProfileLink stores ordered web links with a platform, a validated http or https url, an optional label, and a sort order. Write them through replaceForProfile and reorder on the collection, or setLinks and getLinks on the profile; each is one transaction. Profile.isPublic records that a person is a public figure and nothing more; consumers decide visibility.
Relate two organizations
Organization is a Profile subtype, so an organization relates to another the way a person does. Create a supplier type with getOrCreateBySlug, then call addRelationship on the mill with the shop and the supplier slug. A directional type writes that one row. A reciprocal type with a handler also writes the inverse, and the shipped handlers are friend, spouse, partner, colleague, and sibling. Read the link from either side with getRelationships, date it with addTerm, and name a third profile as its context.
Profile relationships →A relationship is not a tenant link and not a permission
ProfileRelationship carries one tenantId, and the Tenant record in smrt-users has no profile field. A tenant-to-tenant partnership is something an application builds on top, for example one Organization profile per tenant. Permission resolution never reads a relationship, so relating two profiles changes nothing about access.
Decide where data belongs →Package UI
No UI layer to carry
This package stays focused on models, runtime behavior, or tooling. Build its interface with smrt-ui, or use a related domain package that exposes a /svelte entry point.
Intentionally headless
Package previews
No playground module yet
This package does not currently export a ./playground module. Its component names and import path remain available in the Components tab.
Intentionally not registered
Generated interface
REST
The exact route set comes from each model's @smrt({ api }) policy
and the application manifest—not from a second controller definition.
GET /api/profiles
GET /api/profiles/:id
POST /api/profiles
PATCH /api/profiles/:idWhat stays consistent
- Tenant scope and operation permissions run before model access.
- OpenAPI and generated clients use the same manifest fields.
- Packages can expose read-only or custom action sets.
Generated interface
MCP
AI tools are generated from the methods you explicitly include. Their inputs are validated against the same model metadata used by REST and CLI.
profiles_list({ where, limit, orderBy })
profiles_get({ id })
profiles_create({ data })
profiles_update({ id, data })Agent-safe by construction
- Tool availability can be narrower than the HTTP API.
- Principal execution intersects RBAC, agent ceilings, and persona tools.
- Descriptions and schemas are emitted from source metadata.
Browser-native agent interface
WebMCP
The generated web definitions carry the same tool names, descriptions, and JSON Schema
inputs as MCP. smrt-web registers them with document.modelContext.
import { webMcpToolDefinitions } from '@happyvertical/smrt-virt-web';
import { registerWebMcpTools } from '@happyvertical/smrt-web';
// Omitting an exposure policy registers read-only tools only.
const dispose = registerWebMcpTools(webMcpToolDefinitions);The page keeps its security boundary
- Tools execute through generated REST fetchers as the signed-in page user.
- Authentication, tenant gates, permissions, and field policy stay server-side.
- Feature detection makes registration a safe no-op outside supported browsers.
Generated interface
CLI
The app CLI turns manifest resources into predictable operator commands while keeping package-specific actions close to their models.
smrt profiles list --limit 25 --order-by created_at:desc
smrt profiles get <id>
smrt profiles create --from-file ./record.jsonMade for automation
- Machine-readable output and file-based input.
- The same include/exclude policy as other surfaces.
- Migration, introspection, dispatch, and knowledge commands stay in smrt-cli.