Skip to content

Generated file

This page is generated from hadron-server/prisma/schema.prisma. To refresh it, run npm run docs:entities from the root of this repo.

Hadron Data Model

Generated by prisma-markdown

default

erDiagram
"users" {
  String id PK
  String handle UK "nullable"
  Int github_id UK "nullable"
  String github_username UK "nullable"
  IdentityProvider identity_provider "nullable"
  String external_id "nullable"
  String external_app_id FK "nullable"
  DateTime linked_at "nullable"
  String name "nullable"
  String email UK "nullable"
  String avatar_url "nullable"
  Role roles
  Int max_referrals "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"organizations" {
  String id PK
  String name
  String urn UK
  Boolean is_visible "nullable"
  Int github_installation_id UK "nullable"
  String github_app_id "nullable"
  String github_app_private_key_encrypted "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"org_members" {
  String id PK
  String organization_id FK
  String user_id FK
  Role role
  Boolean can_invite
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"memories" {
  String id PK
  String organization_id FK
  String app_id FK "nullable"
  String user_id FK "nullable"
  String user_memory_of_agent_id FK "nullable"
  String urn UK
  String name
  String short_description "nullable"
  String description "nullable"
  String tags
  String license "nullable"
  String category_0 "nullable"
  String category_1 "nullable"
  String category_2 "nullable"
  String icon_url "nullable"
  String hero_url "nullable"
  String home_url "nullable"
  String source "nullable"
  String source_token_encrypted "nullable"
  DateTime source_token_expires_at "nullable"
  String read_branch "nullable"
  String write_branch "nullable"
  MemoryVisibility visibility "nullable"
  Boolean is_encrypted
  MemoryClass class
  DateTime anonymous_expires_at "nullable"
  Boolean requires_license
  DateTime last_synced_at "nullable"
  SyncStatus sync_status
  String sync_error "nullable"
  Int pending_edge_count
  Boolean accepts_uploads
  Boolean vector_index_enabled
  EmbeddingSource embedding_source
  Int chunk_tokens "nullable"
  Int chunk_overlap "nullable"
  Boolean force_fixed_size
  DateTime vector_index_encrypted_ack_at "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
  DateTime urn_normalized_at "nullable"
  String urn_migration_failed_reason "nullable"
}
"assets" {
  String id PK
  String memory_id FK "nullable"
  String filename
  String mime_type
  Int size_bytes
  String storage_key UK
  AssetScanStatus scan_status
  String description "nullable"
  DateTime uploaded_at
  String uploaded_by
  DateTime deleted_at "nullable"
}
"nodes" {
  String id PK
  String memory_id FK
  String node_type
  String name
  String alias "nullable"
  String loc
  Boolean is_link
  String description "nullable"
  String abstract "nullable"
  String content "nullable"
  String content_hash "nullable"
  String abstract_origin_hash "nullable"
  DateTime embedding_pending_at "nullable"
  DateTime embedding_failed_at "nullable"
  Int embedding_attempts
  String embedding_error "nullable"
  Int tokens "nullable"
  String tags
  Json properties "nullable"
  Json data "nullable"
  Int seq "nullable"
  String owner_repo "nullable"
  Boolean is_runnable "nullable"
  String llm_model "nullable"
  String ai_agent "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"node_embeddings" {
  String id PK
  String node_id FK
  String memory_id FK
  EmbeddingKind kind
  String provider
  String model
  Int dim
  Int chunk_index "nullable"
  Int char_start "nullable"
  Int char_end "nullable"
  String chunk_text "nullable"
  DateTime created_at
}
"edges" {
  String id PK
  String source_id FK
  String target_id FK
  String label
  Json condition "nullable"
  Int priority
  Json data "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"pending_edges" {
  String id PK
  String source_id FK
  String target_id
  String label "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"sessions" {
  String id PK
  SessionType type
  String app_id FK "nullable"
  String user_id FK "nullable"
  String agent_id FK "nullable"
  String memory_id FK "nullable"
  DateTime expires_at "nullable"
  String repo "nullable"
  String branch "nullable"
  Int pr_number "nullable"
  String customer_id "nullable"
  String language "nullable"
  String plan "nullable"
  String llm_model "nullable"
  Int input_tokens "nullable"
  Int output_tokens "nullable"
  Int turn_count "nullable"
  Int error_count "nullable"
  String parent_session_id FK "nullable"
  String prev_session_id FK "nullable"
  String summary "nullable"
  Float outcome "nullable"
  String outcome_ref "nullable"
  Json outcome_meta "nullable"
  DateTime started_at
  DateTime ended_at "nullable"
  DateTime auto_expired_at "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"usage_events" {
  String id PK
  String type
  String node_loc "nullable"
  String node_id FK "nullable"
  String session_id FK "nullable"
  String app_id FK "nullable"
  Json action_args "nullable"
  String model "nullable"
  Int tokens_in "nullable"
  Int tokens_out "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"agents" {
  String id PK
  String organization_id FK
  String urn UK
  String name
  String description "nullable"
  String system_prompt "nullable"
  String system_memory_id "nullable"
  AgentVisibility visibility
  AgentType type
  String surfaces
  String ai_provider "nullable"
  String ai_model "nullable"
  String ai_api_key_encrypted "nullable"
  String ai_api_key_preview "nullable"
  String published_revision_loc "nullable"
  String editor_lock_user_id "nullable"
  DateTime editor_lock_expires_at "nullable"
  Json properties "nullable"
  Json memory_provisioning
  Json installation_policy
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
  DateTime urn_normalized_at "nullable"
  String urn_migration_failed_reason "nullable"
}
"agent_memory_items" {
  String id PK
  String agent_id FK
  String memory_id FK
  String role
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"memory_licenses" {
  String id PK
  String memory_id FK
  String license_type
  Int seats
  DateTime valid_from
  DateTime valid_until
  String license_keys
  String terms
  Boolean activated
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"memory_subscriptions" {
  String id PK
  String memory_id FK
  String organization_id FK
  String license_id FK "nullable"
  Role role
  Boolean activated
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"hadron_server" {
  String id PK
  String organization_id "nullable"
  String url
  LogLevel log_level
  String version "nullable"
  DateTime last_heartbeat_at "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"server_log" {
  String id PK
  String server_id FK
  LogLevel level
  String memory_id "nullable"
  String message
  Json detail "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
}
"memory_log" {
  String id PK
  String memory_id FK
  LogLevel level
  MemoryLogEventType event_type
  String message
  Json detail "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
}
"org_member_invitations" {
  String id PK
  String member_user_id FK
  String recipient_user_id FK
  Role role
  DateTime expires_at "nullable"
  DateTime accepted_at "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"user_invitations" {
  String id PK
  String sender_user_id FK "nullable"
  String organization_id FK "nullable"
  String slug UK
  Role user_role "nullable"
  Role member_role
  String new_user_id "nullable"
  String name "nullable"
  String email "nullable"
  String github_username "nullable"
  String phone_number "nullable"
  Int max_activations "nullable"
  DateTime expires_at "nullable"
  DateTime accepted_at "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"user_invitation_activations" {
  String id PK
  String invitation_id FK
  String user_id FK
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"email_verification_tokens" {
  String id PK
  String email
  String token UK
  DateTime expires_at
  DateTime used_at "nullable"
  DateTime created_at
}
"node_versions" {
  String id PK
  String node_id FK
  String loc
  String name
  String alias "nullable"
  String description "nullable"
  String abstract "nullable"
  String abstract_origin_hash "nullable"
  String content "nullable"
  String tags
  String edited_by "nullable"
  String created_by "nullable"
  DateTime created_at
}
"pending_setups" {
  String id PK
  String user_id FK,UK
  String app_id FK
  String raw_key_encrypted
  Boolean consumed
  String created_by "nullable"
  DateTime created_at
}
"exchange_connections" {
  String id PK
  String organization_id FK
  String user_id FK
  String mailbox_email
  String display_name "nullable"
  String refresh_token_encrypted
  Boolean sync_enabled
  SyncStatus sync_status
  DateTime last_sync_at "nullable"
  String last_error "nullable"
  String webhook_subscription_id "nullable"
  DateTime webhook_expires_at "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"waiting_list" {
  String id PK
  String email
  String requested_features "nullable"
  DateTime created_at
}
"apps" {
  String id PK
  String name
  String urn
  String organization_id FK
  CreateUserPermission create_user_permission
  IdentifyUserMethod identify_user_method
  Int session_timeout_seconds
  Int anonymous_ttl_days
  AppType app_type
  AppMembershipRole role
  String description "nullable"
  String system_prompt "nullable"
  String agent_tools
  String ai_provider "nullable"
  String ai_model "nullable"
  String ai_api_key_encrypted "nullable"
  DateTime expires_at "nullable"
  Boolean training_mode
  String surfaces
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
  DateTime uninstalled_at "nullable"
  DateTime urn_normalized_at "nullable"
  String urn_migration_failed_reason "nullable"
}
"app_keys" {
  String id PK
  String app_id FK
  String key_hash UK
  String key_preview
  String label "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime last_used_at "nullable"
  DateTime revoked_at "nullable"
}
"app_members" {
  String app_id FK
  String user_id FK
  String role
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
}
"agent_org_grants" {
  String org_id FK
  String agent_id FK
  DateTime activated_at "nullable"
  DateTime expires_at "nullable"
  DateTime revoked_at "nullable"
  String revoked_by "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
}
"agent_imports" {
  String parent_agent_id FK
  String imported_agent_id FK
  Int position
  Boolean required
  String agent_org_grant_org_id FK
  String agent_org_grant_agent_id
  DateTime created_at
  String created_by "nullable"
}
"agent_subscriptions" {
  String user_id FK
  String agent_id FK
  DateTime activated_at "nullable"
  DateTime expires_at "nullable"
  DateTime revoked_at "nullable"
  String revoked_by "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
}
"app_log" {
  String id PK
  String app_id FK
  LogLevel level
  String memory_id "nullable"
  String session_id "nullable"
  String message
  Json detail "nullable"
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
}
"app_agents" {
  String app_id FK
  String agent_id FK
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
}
"memory_shares" {
  String memory_id FK
  String grantee_id FK
  String grantor_id FK
  MemoryShareRole role
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
}
"memory_members" {
  String memory_id FK
  String user_id FK
  MemoryMemberRole role
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at "nullable"
  String updated_by "nullable"
}
"user_api_keys" {
  String id PK
  String user_id FK
  String key_hash UK
  String key_preview
  String label "nullable"
  DateTime created_at
  String created_by "nullable"
  String issued_via "nullable"
  DateTime last_used_at "nullable"
  DateTime revoked_at "nullable"
}
"oauth_clients" {
  String client_id PK
  String client_name
  String redirect_uris
  DateTime created_at
  String created_by "nullable"
  DateTime updated_at
  String updated_by "nullable"
  DateTime deleted_at "nullable"
  String deleted_by "nullable"
}
"auth_codes" {
  String id PK
  String code_hash UK
  String client_id FK
  String user_id FK
  String redirect_uri
  String code_challenge
  String resource
  DateTime expires_at
  DateTime redeemed_at "nullable"
  DateTime created_at
  String created_by "nullable"
}
"users" }o--o| "apps" : externalApp
"org_members" }o--|| "organizations" : organization
"org_members" }o--|| "users" : user
"memories" }o--|| "organizations" : organization
"memories" }o--o| "agents" : userMemoryOfAgent
"memories" }o--o| "users" : user
"memories" }o--o| "apps" : app
"assets" }o--o| "memories" : memory
"nodes" }o--|| "memories" : memory
"node_embeddings" }o--|| "nodes" : node
"node_embeddings" }o--|| "memories" : memory
"edges" }o--|| "nodes" : source
"edges" }o--|| "nodes" : target
"pending_edges" }o--|| "nodes" : source
"sessions" }o--o| "sessions" : parent
"sessions" }o--o| "sessions" : prev
"sessions" }o--o| "apps" : app
"sessions" }o--o| "users" : user
"sessions" }o--o| "agents" : agent
"sessions" }o--o| "memories" : memory
"usage_events" }o--o| "nodes" : node
"usage_events" }o--o| "sessions" : session
"usage_events" }o--o| "apps" : app
"agents" }o--|| "organizations" : organization
"agent_memory_items" }o--|| "agents" : agent
"agent_memory_items" }o--|| "memories" : memory
"memory_licenses" }o--|| "memories" : memory
"memory_subscriptions" }o--|| "memories" : memory
"memory_subscriptions" }o--|| "organizations" : organization
"memory_subscriptions" }o--o| "memory_licenses" : license
"server_log" }o--|| "hadron_server" : server
"memory_log" }o--|| "memories" : memory
"org_member_invitations" }o--|| "org_members" : sender
"org_member_invitations" }o--|| "users" : recipient
"user_invitations" }o--o| "users" : sender
"user_invitations" }o--o| "organizations" : organization
"user_invitation_activations" }o--|| "user_invitations" : invitation
"user_invitation_activations" }o--|| "users" : user
"node_versions" }o--|| "nodes" : node
"pending_setups" |o--|| "users" : user
"pending_setups" }o--|| "apps" : app
"exchange_connections" }o--|| "organizations" : organization
"exchange_connections" }o--|| "users" : user
"apps" }o--|| "organizations" : organization
"app_keys" }o--|| "apps" : app
"app_members" }o--|| "apps" : app
"app_members" }o--|| "users" : user
"agent_org_grants" }o--|| "organizations" : organization
"agent_org_grants" }o--|| "agents" : agent
"agent_imports" }o--|| "agents" : parentAgent
"agent_imports" }o--|| "agents" : importedAgent
"agent_imports" }o--|| "agent_org_grants" : agentOrgGrant
"agent_subscriptions" }o--|| "users" : user
"agent_subscriptions" }o--|| "agents" : agent
"app_log" }o--|| "apps" : app
"app_agents" }o--|| "apps" : app
"app_agents" }o--|| "agents" : agent
"memory_shares" }o--|| "memories" : memory
"memory_shares" }o--|| "users" : grantee
"memory_shares" }o--|| "users" : grantor
"memory_members" }o--|| "memories" : memory
"memory_members" }o--|| "users" : user
"user_api_keys" }o--|| "users" : user
"auth_codes" }o--|| "users" : user
"auth_codes" }o--|| "oauth_clients" : client

users

A person who uses Hadron, authenticated via GitHub OAuth (or email in future). N:N to Organization via OrgMember. Every user has a personal organization (auto-created on signup, isVisible=false).

Invitation chain: UserInvitationActivation links each User back to their UserInvitation, whose senderUserId points to the inviter, recursively up to the root (senderUserId IS NULL = system seed from Baragaun).

Referral quota (maxReferrals): the maximum number of new platform users this user is allowed to onboard. Unaccepted invitations don't count; the count is on accepted activations only. Quota is tracked transitively up the chain — if A invites B and B invites C, both A's and B's quotas decrement when C joins. When sending an invite, UserInvitation.maxActivations must not exceed the minimum free quota across the inviter's referral chain. There is a known race condition: if a referral starts onboarding and the "create account" request would push the chain over quota, the account is still created (don't block a real human on a counter).

Properties as follows:

  • id:
  • handle

    Unique username, e.g. "holger"; displayed as @holger in UI and node frontmatter author field. Defaults to githubUsername on signup.

  • github_id: GitHub numeric account id.
  • github_username: GitHub login.
  • identity_provider:
  • external_id: App-scoped external user id. Composite-unique with externalAppId.
  • external_app_id

    The App that minted this externalId. Forms a (App, externalId) idempotency key for chatbot user provisioning.

  • linked_at

    When this app-scoped external identity was linked to the platform User (i.e. when the chatbot user "graduated" to a real account).

  • name:
  • email: Used for invite matching and email-based auth.
  • avatar_url:
  • roles: Global platform roles (not org-scoped). Default [READER].
  • max_referrals

    Maximum number of new platform users this user can onboard. See the model-level doc for the referral-quota semantics.

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

organizations

A company, team, or group that owns memories and has members. Must have at least one OrgMember with role OWNER (can have many). Every user has a personal organization (auto-created on signup, isVisible: false) — see hadron-concept/hadron-specs/General/Personal Organizations.md (sister repo).

Properties as follows:

  • id:
  • name:
  • urn

    The organization's domain name, e.g. "baragaun.com", "micromentor.org". Serves as the namespace for all resources owned by this org (URN prefix).

  • is_visible

    Whether this org appears in public listings. false for auto-created personal orgs.

  • github_installation_id

    GitHub App installation id. Used to mint ephemeral tokens (1-hour expiry) for memory git-sync against repos within this installation's scope.

  • github_app_id:
  • github_app_private_key_encrypted

    AES-256-GCM-encrypted GitHub App private key. Plaintext never stored or shown after initial entry. Encryption key: HADRON_ENCRYPTION_KEY env var.

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

org_members

Associates a User with an Organization. To remove a User from an Organization, delete the OrgMember row. Must NOT delete the OrgMember if it is the only one with role OWNER (enforced at the resolver, not the schema).

Properties as follows:

  • id:
  • organization_id:
  • user_id:
  • role:
  • can_invite

    Whether this member can invite other existing platform users to the organization. Inviting a new platform user (UserInvitation) is governed separately by User.maxReferrals.

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

memories

A Hadron knowledge graph, owned by an Organization. The four-way class field (system / app / knowledge / personal) governs addressing and access — see the MemoryClass enum doc.

Git-backed memories — the root node.yaml in the repo is the memory manifest, declaring the memory's urn and marketplace metadata. On import the server reads this manifest and creates or updates the Memory row. The org portion of the URN must match an existing Organization on the server.

Git authentication — the server first tries the owning Organization's githubInstallationId to mint ephemeral tokens (1-hour expiry). If the repo is outside the GitHub App's scope (different GitHub org, personal repo, GitLab, etc.), the server falls back to sourceTokenEncrypted. The GitHub App's private key (used to sign JWT requests for installation tokens) lives in the server's environment/secrets manager — not in the database. For the SaaS server, Baragaun manages this key.

Token encryptionsourceTokenEncrypted uses AES-256-GCM with a symmetric key from the server's environment (HADRON_ENCRYPTION_KEY). Plaintext never stored or shown after initial entry. To replace an expired/revoked token, the user pastes a new one (overwrites the old encrypted value). Key rotation: re-encrypt all stored tokens with the new key in a single migration.

Properties as follows:

  • id:
  • organization_id:
  • app_id

    The App this memory belongs to. Required when class is app; optional for personal/private (set when app-scoped, NULL when free-standing); NULL for system/knowledge/group. Enforced by chk_memory_class_app_id.

  • user_id

    Owning user for personal/private-class memories. Bidirectional invariant (CHECK chk_personal_visibility_user): class IN ('personal','private')userId IS NOT NULL.

  • user_memory_of_agent_id

    Legacy 005 Agent-pivot for personal memories. Retained for backward-compat queries (008 R-3); not consulted by post-008 access-control, which uses appId directly. A future cleanup spec may collapse it.

  • urn

    Globally unique identifier, e.g. "baragaun.com:secureid". Derived from org.urn + ":" + slug.

  • name: Human-friendly display name, e.g. "SecureID Service".
  • short_description: One-line summary for marketplace listings and search results.
  • description: Full description (markdown); shown when expanding a listing.
  • tags: Classification tags for search and filtering.
  • license: License identifier (e.g. "MIT", "CC-BY-4.0", "proprietary").
  • category_0: Primary category (e.g. "Developer Tools").
  • category_1: Secondary category (e.g. "Backend").
  • category_2: Tertiary category (e.g. "Node.js").
  • icon_url: Square icon/logo URL for listings.
  • hero_url: Banner image URL for the detail page.
  • home_url: External homepage or documentation URL.
  • source: Git clone URL; null = DB-only mode (no git backing).
  • source_token_encrypted

    AES-256-GCM-encrypted access token for repos not covered by the org's GitHub App installation (e.g. personal repos, GitLab, Bitbucket). Plaintext never stored or displayed after initial entry.

  • source_token_expires_at

    Expiry of the stored token; server warns via MemoryLogEntry before expiration.

  • read_branch: Branch to clone/pull from (default: repo default branch).
  • write_branch: Branch for mutations (default: "hadron-updates").
  • visibility

    035-visibility-enum-cleanup: nullable, no default. Set only for knowledge (PUBLIC/ORGANIZATION) and group (GROUP); NULL for system/app/personal/private (enforced by chk_memory_visibility_class).

  • is_encrypted

    Whether the memory's contents (including asset bytes) are encrypted at rest using AES-256-GCM under HADRON_ENCRYPTION_KEY.

  • class

    Per-class governance — see MemoryClass enum doc for the four classes and their addressing rules.

  • anonymous_expires_at

    When an anonymous (pre-account-creation) memory expires and is reaped by the janitor. NULL for non-anonymous memories.

  • requires_license: If true, any subscription must have a valid MemoryLicense.
  • last_synced_at: Last successful git sync.
  • sync_status:
  • sync_error: Error message from the last failed sync.
  • pending_edge_count

    Number of unresolved PendingEdge records (denormalized for dashboard display).

  • accepts_uploads

    Whether this memory accepts asset uploads. system-class memories typically opt out.

  • vector_index_enabled

    Opt-in: when true, this memory's nodes are vector-indexed. DB default is false; the class-derived default is applied at createMemory — a knowledge-class, non-encrypted memory is created with true, all other classes and any encrypted memory stay false (FR-001). Flippable per memory.

  • embedding_source

    What the vector index is built from. Memory property, NOT a query parameter (FR-002). Only meaningful when vectorIndexEnabled.

  • chunk_tokens

    Per-memory chunking dials (FR-003). NULL ⇒ platform default (512 / 64). Consulted only when embeddingSource includes chunks.

  • chunk_overlap:
  • force_fixed_size: Force fixed-size chunking, bypassing structure-aware (FR-015).
  • vector_index_encrypted_ack_at

    Timestamp the owner acknowledged the embedding-inversion disclosure for an ENCRYPTED memory (FR-026). NULL ⇒ not acknowledged. Gates encrypted-memory indexing via chk_memory_vector_encrypted_ack.

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:
  • urn_normalized_at

    Spec 021 FR-032 online URN-shape migration gate (transient). Null = pre-canonical row; set = canonical-form row. Dropped post-migration. TODO(spec-021-cleanup): drop once the FR-032 migration completes.

  • urn_migration_failed_reason

    Spec 021 sentinel for data-defect rows during the FR-032 migration. TODO(spec-021-cleanup): drop alongside urnNormalizedAt.

assets

A user-uploaded file (document, image) bound to a Memory. Bytes live in object storage (Cloudflare R2 in v1; MinIO for local dev); this row carries metadata, the storage pointer, and virus-scan state. Spec: hadron-concept/spec-kits/specs/004-asset-upload/spec.md (sister repo).

Why a separate table (not a nodeType): normalization (asset and node schemas diverge), scale (raw-material assets shouldn't bloat the knowledge-node table), and FK-shape (containment is foreign-key-shaped, not graph-shaped). See hadron-concept/design-discussions/hadron-platform/2026-04-28-asset-upload-design/010-summary.md (sister repo) §"Why this summary supersedes 007-summary.md".

URN form: <memory.urn>:assets:<asset.id> resolves to this row.

Cross-references from nodes are reserved for a future spec — Node.attachedAssetId? (one-asset case) or a node_assets join (many-to-many). Not in v1.

Properties as follows:

  • id:
  • memory_id

    The Memory this asset belongs to; NULL while the asset is staged (uploaded but not yet attached). Janitor sweeps staged assets past the 24h TTL.

  • filename

    Original filename as supplied by the user. Never appears in storageKey.

  • mime_type: RFC 6838 type/subtype.
  • size_bytes: Verified at complete-upload time against HeadObject.contentLength.
  • storage_key

    Object-store key shape: {orgUrn}/{memoryUrn-segment}/{assetId}.{ext}. Encrypted at rest when the memory is encrypted; plaintext otherwise.

  • scan_status: Virus scan state. Downloads gated on CLEAN.
  • description: Optional; agent- or user-provided.
  • uploaded_at:
  • uploaded_by: fk to User.id — who uploaded.
  • deleted_at

    Soft-delete marker. Janitor sweeps R2 object + hard-deletes after 24h.

nodes

A single node in a Memory. loc is the colon-delimited path within the memory (e.g. auth:tokens), memory-relative. The full URN is computed memory.urn + ":" + loc (e.g. baragaun.com:secureid:auth:tokens) and not stored. id is optional in YAML; the server assigns it on import if missing.

Node types define the node's role in the memory: - info (default) — living knowledge: specs, guides, facts. Shown in tree, normal search priority. - abstract — condensed knowledge: summaries, TL;DRs, paper abstracts. Highest search priority. May be the only representation if the source is archived. - reference — external source: papers, URLs, legislation, books. Carries metadata (DOI, ISBN, URL) in data. - record — immutable history: chat messages, session logs. Hidden in tree by default, low search priority. - system — agent configuration: conversation designs, stages, prompt templates, partials. Hidden in tree, excluded from search. (The earlier asset node-type was promoted to its own Asset table in spec 004-asset-upload.)

Column type is String (not enum) because the running schema pre-dates the formalization; the intended-values list above is the contract — tightening to a Prisma enum is reserved for a future cleanup spec.

Chat-root data.scope (spec 017, 2026-05-12) — chat-root nodes (loc chats:<id>, depth 2, nodeType record) carry a scope field in data recording the chat's visibility tier (private | shared). Set at chat creation and immutable thereafter (FR-002). Denormalized from Memory.class for chat-list query performance; MUST stay consistent with the holding memory's class per the invariant validator in src/api/graphql/schema/chat-scope.ts.

Properties as follows:

  • id

    UUIDv7 from node.yaml when supplied by the importer (stable across moves); falls back to cuid() for inserts that omit id.

  • memory_id:
  • node_type: See the node-types list in the model doc. Default info.
  • name: Human-friendly display name of this node.
  • alias: Alternative name.
  • loc: Colon-delimited path within the memory (e.g. "auth:tokens").
  • is_link:
  • description:
  • abstract

    Paragraph-length summary of this node. Cap is 2000 characters, enforced at the API boundary (not as a DB CHECK — caps are validation, not invariants). Surfacing rules across MCP tools (find/read/list/validate), the Source: abstract-fallback marker on h-read-node content-scope fallback, and the RAG-substrate motivation are documented in spec 031 (FR-005 + edge cases). That is the load-bearing reference; this comment is the cold-read pointer for someone scanning the schema.

  • content: The topic content (topic.md full text).
  • content_hash

    SHA256(yaml+md)[0:8] for change detection. Set by external producers (YAML frontmatter via importer, Git sync). UNCHANGED by spec 032 — that spec adds abstractOriginHash below as a separate column with its own domain (SHA256(content)[0:8]); the two are never compared against each other.

  • abstract_origin_hash

    Spec 032 — fingerprint of content value at the time abstract was authored. SHA-256 of plaintext content truncated to 8 hex chars (same width as contentHash, different domain). At read time, compared against computeContentHash(node.content) (via src/lib/contentHash.ts) to detect staleness; when the two values differ AND abstractOriginHash is non-null, the abstract may not reflect current content. Surfaced by h-read-node's Source: abstract-stale marker, h-validate's [stale-abstract] warning, and the upcoming RAG layer. System-managed: never settable via GraphQL NodeInput.

  • embedding_pending_at

    Set when this node needs (re-)embedding; cleared on success. The single work signal the embedding worker drains (FR-006/FR-007). Operational state only — intentionally NOT versioned on NodeVersion.

  • embedding_failed_at

    Set when an embed attempt failed (record, not a work signal). Transient failures keep pending set for retry; permanent failures clear pending (FR-009).

  • embedding_attempts: Attempt counter for backoff / give-up. Non-null; reset to 0 on success/revoke.
  • embedding_error: Last embed error (diagnosability; surfaced by h-validate).
  • tokens: Number of input tokens.
  • tags: Classification tags.
  • properties: Typed key-value structured data; queryable via the API.
  • data

    Structured JSON on the node; used for template-variable resolution, user profiles, extracted facts, reference metadata. Returned by h-get-data, set by h-set-data.

  • seq

    Sort order among sibling nodes (lower values come first); used by h-read-next-node and portal drag-and-drop reordering.

  • owner_repo: GitHub repo slug (e.g. "micromentor/mmdata").
  • is_runnable: If true, this node can be executed as a task.
  • llm_model

    LLM model that created or last modified this node (e.g. "claude-opus-4-6", "gpt-4o").

  • ai_agent

    AI agent tool that created or last modified this node (e.g. "claude-code", "cursor", "hadron-portal").

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

node_embeddings

033-rag-vector-retrieval: a stored embedding vector for a node's abstract or one of its content chunks. Vectors are computed on plaintext at write time (FR-027) and stored plaintext (pgvector). The embedding column is Unsupported("vector(768)") — Prisma has no native vector type, so it is NEVER read/written through the generated client; all access is raw SQL ($queryRaw/$executeRaw). The HNSW index + abstract partial-unique index live in the migration + post-push.sql (not expressible in Prisma schema).

Properties as follows:

  • id:
  • node_id:
  • memory_id

    Denormalized for fast per-memory similarity filtering (search filters by memory before ORDER BY distance).

  • kind:
  • provider: Embedding provider — "local" | "openai" | "voyage" … v1 is always "local".
  • model

    Stored model id (FR-014) — e.g. "nomic-embed-text-v1.5". Forward-compat for a deliberate re-embed when the platform model changes.

  • dim: Vector dimension (sanity / forward-compat guard).
  • chunk_index:
  • char_start:
  • char_end:
  • chunk_text:
  • created_at:

edges

Directed edge between two Nodes. The label describes the relationship — e.g. "imports", "depends_on", "related-to", "cites", "abstract-of". An edge whose target is not yet synced lives in PendingEdge until the target arrives.

Properties as follows:

  • id:
  • source_id:
  • target_id:
  • label:
  • condition

    JSONLogic gating expression evaluated by the shared edge-condition evaluator at edge-resolution time. NULL means the edge always fires. Validated against the v1 operator subset and the five variable scopes (memory.*, chat.*, agent.*, message.data.*, now() / today()). Spec: hadron-concept/spec-kits/specs/001-edge-condition-spec/spec.md (sister repo). User docs: https://docs.hadronmemory.com/reference/edge-conditions/.

  • priority

    Resolution-order hook for edges sharing a source node; lower fires first, ties broken by insertion order. Reserved space for the future agent-exceptions feature to slot handler edges at the top.

  • data

    Free-form structured payload on the edge (e.g. transition metadata for conversation-design edges).

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

pending_edges

An edge whose target Node has not been synced yet; stored without a foreign key to the target. When the target Node is synced, the PendingEdge is converted to a regular Edge.

Properties as follows:

  • id:
  • source_id:
  • target_id

    Intended target node id — NOT a foreign key; resolved at promotion time.

  • label:
  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

sessions

Tracks a unit of work by an App: an AI/agent conversation, a chatbot interaction, an automation run. Sessions are the top-level tracking concept; UsageEvent rows are recorded within a session.

Properties as follows:

  • id:
  • type:
  • app_id:
  • user_id

    The User this session is attributed to (typically the end-user in a chatbot/automation session; the developer in a DEVELOPER session).

  • agent_id

    The Agent driving the session (NULL for free-form developer sessions not tied to a specific Agent).

  • memory_id

    The active write-memory for this session, when there's an unambiguous one. Resolves the multi-writable-memory ambiguity.

  • expires_at: Hard expiry; the janitor reaps sessions past this point.
  • repo: Git repo slug.
  • branch: Git branch name at session start.
  • pr_number: GitHub PR number.
  • customer_id: Tenant/customer identifier (free-form, supplied by the App).
  • language: Spoken ("en", "es") or programming language.
  • plan: The execution plan, when one was prepared.
  • llm_model: LLM model used (e.g. "claude-opus-4-6").
  • input_tokens: Total prompt tokens across the session.
  • output_tokens: Total completion tokens across the session.
  • turn_count: Number of human-AI exchanges.
  • error_count: Failed tool calls / action runs.
  • parent_session_id: Sub-agent vertical relation (this session was spawned by a parent).
  • prev_session_id

    Chain horizontal relation (linked journey — this session continues from a previous one).

  • summary: AI-composed summary at session end.
  • outcome: 0.0-1.0, reported externally.
  • outcome_ref: GUID or URL of authoritative outcome record.
  • outcome_meta: Domain-specific outcome detail.
  • started_at:
  • ended_at:
  • auto_expired_at

    Set if the session was auto-expired due to inactivity (vs. explicitly ended).

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

usage_events

Record of a single operation within a Session (read, write, action run, edge traversal, session summary).

Column type is String (not enum) — intended values: read | write | action-run | edge-traversal | session-summary. Tightening to a Prisma enum is reserved for a future cleanup spec.

Properties as follows:

  • id:
  • type:
  • node_loc: Stored directly so the event survives node deletion.
  • node_id: Set null on node deletion.
  • session_id: Set null on session deletion.
  • app_id: The App that authored this event.
  • action_args: Key-value args when type = 'action-run'.
  • model: LLM model used for this specific event.
  • tokens_in: Input tokens consumed.
  • tokens_out: Output tokens generated.
  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

agents

The Agent definition — what a Builder creates and what the marketplace sells. One Agent row holds the canonical conversation design (system memory), the default AI config, the declared memory-provisioning policy, and the declared installation policy. Spec: 008-agent-installation (2026-05-02).

An App is the deployment of an Agent in some org. The Agent is what is shared across installations; the App is what authenticates, holds members, and accumulates per-deployment data. (PWA analogy: you build an Agent, and when someone installs it, the running instance is called an App.)

Examples: "Mealplan" (the Agent your family meal planner is built from), "Juno" (the mentee chat agent shared across MicroMentor orgs).

Properties as follows:

  • id:
  • organization_id:
  • urn

    Globally unique identifier (e.g. "micromentor.org:juno"); derived from org.urn + ":" + slug.

  • name: Display name of this agent.
  • description:
  • system_prompt: System prompt sent to the LLM at session start.
  • system_memory_id

    Memory containing conversation designs (stages, prompts, partials). Universally read-only from any App context post-008 (DEVELOP-from-App is dropped per R-10); edits happen in the Agent ownership surface.

  • visibility

    PUBLIC | ORGANIZATION | PERSONAL (035-visibility-enum-cleanup gave agents their own enum). PERSONAL is the creator-only draft state.

  • type

    Coarse behavior hint — see AgentType enum doc. Largely legacy post-008.

  • surfaces

    Surfaces this Agent is designed for (free-form per 008 R-9; v1 known values: 'chat', 'slack', 'mcp', 'web'). Distinct from the per-App surfaces — this is the design-time declaration.

  • ai_provider

    AI provider for the Agent's default AI config (e.g. "anthropic", "openai"). An App can override at deploy time via App.aiProvider.

  • ai_model:
  • ai_api_key_encrypted: AES-256-GCM-encrypted API key for the Agent's default AI config.
  • ai_api_key_preview: Last 4 characters of the raw API key for masked display.
  • published_revision_loc

    Loc of the currently-published revision of the Agent's system memory. Lets the Builder ship draft revisions without affecting running Apps.

  • editor_lock_user_id

    Optimistic editor-lock owner (one Builder at a time can hold the edit lock on the system memory).

  • editor_lock_expires_at: When the editor lock expires (auto-released on stale).
  • properties

    Agent working state (per-user data like family budget, preferences); automatically available to the LLM as context.

  • memory_provisioning

    Declares how this Agent expects per-App memory to be provisioned. Shape: { appMemory: 'shared' | 'user' | 'none' }. personalMemory provisioning is implicit (lazy-create on first user-attributed write). 008-agent-installation FR-008.

  • installation_policy

    Declares membership constraints for any App that installs this Agent. Shape: { maxMembers: number | 'unlimited', memberRoles: string[] }. maxMembers enforced at AppMember invite-acceptance time. 008-agent-installation FR-009.

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:
  • urn_normalized_at

    Spec 021 FR-032 online URN-shape migration gate (transient). Null = pre-canonical row; set = canonical-form row. Dropped post-migration. TODO(spec-021-cleanup): drop once the FR-032 migration completes.

  • urn_migration_failed_reason

    Spec 021 sentinel for data-defect rows during the FR-032 migration. TODO(spec-021-cleanup): drop alongside urnNormalizedAt.

agent_memory_items

Associates a Memory with an Agent, with a per-memory access level. The Agent controls which memories are read-only vs read-write per-attached-memory. After 005-agent-subscription, AgentMemoryItem.role is the sole per-knowledge-memory write cap (the AppAgent-level cap was first relaxed in 005, then the AppAgent join itself was dropped in 008-agent-installation).

Properties as follows:

  • id:
  • agent_id:
  • memory_id:
  • role

    "read" or "read-write". Default "read". Controls whether the Agent can write to this memory.

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

memory_licenses

Commercial usage grant of a Memory; defines capacity and terms. Consumed via MemorySubscription.licenseId.

Properties as follows:

  • id:
  • memory_id:
  • license_type

    Column type is String (not enum) — intended values: user | app | organization.

  • seats: Number of consumers allowed to use this license.
  • valid_from:
  • valid_until:
  • license_keys: One or more license keys.
  • terms: Usage terms & conditions.
  • activated

    If true, the memory will be merged into the blended graph for the consumer.

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

memory_subscriptions

An Organization subscribes to another Organization's Memory. Governed by a MemoryLicense when the memory requires one.

Access evaluation order (cross-org chain — extended in 005-agent-subscription with the App↔Agent and User↔Agent gates; 008-agent-installation added AgentOrgGrant; 023-app-shape replaced the direct App.agentId with the AppAgent join (an App can install N Agents)): 1. App ↔ Agent — the App has the Agent installed (an AppAgent row links them) AND Agent.visibility allows the App's org. Cross-org installs (App.organizationId != Agent.organizationId) additionally require an active AgentOrgGrant(App.organizationId, agentId). Same-org installs auto-pass. 2. Agent ↔ Memory (knowledge / app / system) — current implementation (src/mcp/access-control.ts#foldAgentIntoAccessMap): AgentMemoryItem(agent, memory) exists OR memory is the Agent's systemMemoryId. When cross-org for knowledge: MemorySubscription(orgB, memory) AND any required MemoryLicense valid (not expired, seats available). Known gap (tracked in issue #115): this gate does NOT auto-grant access to app-class memories where memory.appId = App.id AND memory.class = 'app'. Today an explicit AgentMemoryItem row is required for app-class memories to be reachable through the MCP/GraphQL gates. The spec called for auto-grant; the implementation currently does not. 3. User ↔ Agent (personal-class memory only)AgentSubscription(user, agentId) exists AND active per the predicate, AND Memory.appId = App.id (the per-App isolation that User Story 2 hangs on — a session through App A cannot read the personal memory at (B.id, user.id) for some other App B of the same Agent). 4. Effective role — most restrictive of AgentMemoryItem.role and MemorySubscription.role. (The legacy AppAgent.role is dropped post-008 — system memory is universally read-only from any App.) personal/private-class memories enforce strict ownership additionally: even an ADMIN/OWNER who would otherwise satisfy gate 2 cannot read a personal/private memory they do not own (FR-016).

Worked example (the four-org Juno scenario, 008 vocabulary): an App in OrgA deploys Juno (Agent in OrgB with visibility=ORGANIZATION since Alice is a Micromentor org-member; or PUBLIC for marketplace shape) which references a knowledge Memory in OrgC. The cross-org App↔Agent gate is satisfied by an active AgentOrgGrant(OrgA, juno). End-user Alice (personal org personal-alice) chats through that App; her records land in a personal Memory at (App.id, alice.id) (Memory.userId = alice, Memory.appId = App.id, class = personal, visibility = null). The cross-org write into Alice's personal memory is granted by AgentSubscription(alice, juno) — without it, Juno has zero access. If Alice installs Juno into a second App (different Org), her records there go to a separate (B.id, alice.id) row — fully isolated from the OrgA installation. Each gate above must clear in order; failure denies at the right layer with a typed error.

Properties as follows:

  • id:
  • memory_id:
  • organization_id: The subscribing organization (not the owner).
  • license_id:
  • role

    Column type is the full Role enum, but the intended grant set is CONTRIBUTOR | READER (reader = read-only, contributor = read-write). Tightening reserved for a future cleanup spec.

  • activated

    If true, the memory will be merged into the blended graph for the subscriber.

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

hadron_server

Represents a running Hadron server instance (SaaS or self-hosted). Each organization connects to one server; a server serves one or many organizations.

Properties as follows:

  • id:
  • organization_id

    The organization that owns/operates this server; NULL for the Baragaun SaaS server.

  • url: Base URL of this server (e.g. "https://server.hadrongraph.io").
  • log_level: Minimum level for ServerLogEntry records.
  • version: Deployed software version.
  • last_heartbeat_at: Last health-check timestamp.
  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

server_log

Log entry for the Hadron server, surfaced in hadron-portal for troubleshooting. Examples: memory sync failures, GitHub App errors, system-level events.

Properties as follows:

  • id:
  • server_id:
  • level:
  • memory_id: Set if the log relates to a specific memory.
  • message: Human-readable log message.
  • detail: Structured context (stack trace, request/response, etc.).
  • created_at:
  • created_by:
  • updated_at:
  • updated_by:

memory_log

Log entry for a specific memory, surfaced in hadron-portal for troubleshooting. Examples: sync failures, license expirations, pending edge resolution, branch conflicts.

Properties as follows:

  • id:
  • memory_id:
  • level:
  • event_type: Categorizes the log for filtering.
  • message: Human-readable log message.
  • detail: Structured context (sync diff, expired license details, etc.).
  • created_at:
  • created_by:
  • updated_at:
  • updated_by:

org_member_invitations

An invitation sent to an existing platform User to join an Organization. The role cannot be higher than that of the sender. (Contrast with UserInvitation, which is for inviting new platform users.)

Properties as follows:

  • id:
  • member_user_id: fk to OrgMember.id — the sender of the invitation.
  • recipient_user_id: fk to User.id — the recipient (an existing platform user).
  • role

    Column type is the full Role enum, but the intended grant set is ADMIN | CONTRIBUTOR | READER (you can't invite someone as OWNER — promotion happens via a separate transfer flow).

  • expires_at: When this invitation expires.
  • accepted_at: When the recipient accepted the invitation.
  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

user_invitations

Invitation sent out to one or more persons that do not yet have a user account. If maxActivations = 1 this invitation is single-use. If email or githubUsername is given, the new user is validated against this — which essentially locks the identity of the new person and sets maxActivations to 1.

Quota is tracked on User.maxReferrals, not on the invitation. When a new user is created from this invitation, their maxReferrals is set by the onboarding process (e.g. from the sender's remaining quota or a system default).

Race conditions: if a referral arrives at the landing page after activations exceed maxActivations, the page shows "The invitation has expired. Please contact the sender." If onboarding has started and the create-account request would push the count above the cap, the account is still created (don't block a real human on a counter).

Role bounds: userRole cannot be higher than the sender's; memberRole cannot be higher than the sender's OrgMember role in the target org.

Properties as follows:

  • id:
  • sender_user_id

    fk to User.id — the sender; NULL for system/seed invitations from Baragaun.

  • organization_id: If the new user should be added to an organization on signup.
  • slug: Random string to get a unique invite URL.
  • user_role

    Column type is the full Role enum, but the intended set is ADMIN | null (most invites grant no global role).

  • member_role

    Column type is the full Role enum, but the intended grant set is ADMIN | CONTRIBUTOR | READER.

  • new_user_id

    fk to User.id — set when the invitation is accepted and a User is created.

  • name: Recipient's name.
  • email:
  • github_username:
  • phone_number:
  • max_activations

    Maximum number of times this invitation can be accepted by a new recipient; current count is determined from UserInvitationActivation rows.

  • expires_at:
  • accepted_at

    When this invitation was accepted and the new User was created. (For multi-use invites this is set on the first acceptance.)

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

user_invitation_activations

One row per User created in response to a UserInvitation. Role bounds match UserInvitation's: userRole and memberRole cannot exceed the sender's at activation time.

Properties as follows:

  • id:
  • invitation_id: The invitation that was accepted.
  • user_id: The newly created User.
  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

email_verification_tokens

Temporary token for email verification during signup.

Properties as follows:

  • id:
  • email:
  • token:
  • expires_at:
  • used_at:
  • created_at:

node_versions

A versioned snapshot of a Node's content. Written every time a Node is updated, so the full history is recoverable.

Properties as follows:

  • id:
  • node_id:
  • loc:
  • name:
  • alias:
  • description:
  • abstract

    Paragraph summary at the time of the snapshot. Spec 031. Added in the US1 implementation when the live Node.abstract column landed — version history would otherwise silently drop abstract edits.

  • abstract_origin_hash

    Spec 032 — snapshot of Node.abstractOriginHash at the time of version capture. Restored verbatim by restoreNodeVersion; legacy versions (pre-spec) have NULL here, which short-circuits the staleness check on the live row after restore (no false positives).

  • content:
  • tags:
  • edited_by

    Free-form author label (typically a User.id, but may be an agent identifier for AI edits).

  • created_by:
  • created_at:

pending_setups

Temporary record holding an encrypted API key for a newly provisioned workspace, consumed during first-time setup. The raw key is the hdr_app_<…> value, shown once via /setup/info.

Properties as follows:

  • id:
  • user_id:
  • app_id: The workspace's App; ON DELETE CASCADE.
  • raw_key_encrypted: AES-256-GCM-encrypted raw API key.
  • consumed:
  • created_by:
  • created_at:

exchange_connections

A linked Microsoft Exchange / Outlook mailbox for an Organization-scoped user. Used by the email-ingestion pipeline to pull message bodies into memory nodes. refreshTokenEncrypted is AES-256-GCM-encrypted under HADRON_ENCRYPTION_KEY.

Properties as follows:

  • id:
  • organization_id:
  • user_id:
  • mailbox_email

    The mailbox email address; (organizationId, mailboxEmail) is unique.

  • display_name:
  • refresh_token_encrypted: AES-256-GCM-encrypted MS Graph refresh token.
  • sync_enabled

    Soft toggle — operator can pause sync without removing the connection.

  • sync_status:
  • last_sync_at:
  • last_error: Error message from the last failed sync.
  • webhook_subscription_id: MS Graph change-notification subscription id.
  • webhook_expires_at

    Subscription expiry — MS Graph webhooks are short-lived; the renewer cron job watches this column.

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

waiting_list

Pre-launch waitlist signup capture from the marketing site. Holds an email plus an optional free-form "what features would you most want?" answer. Not connected to User (signups happen before the user has an account).

Properties as follows:

  • id:
  • email:
  • requested_features:
  • created_at:

apps

The runtime deployment of an Agent in some Organization — what someone installs when they pick an Agent off the marketplace (or install one from their own org). Holds credentials, surfaces, optional AI override, members, and per-deployment memory pivots. Per Constitution Technology Invariant #6, this is also the unified caller-identity entity that authenticates against the Hadron platform.

Per (org, Agent) install: typically one App per pair. Multi-install of the same Agent across orgs is supported and explicitly isolates app- and personal-class memory at (app_id, …) keys.

Auth credentials live on AppKey rows (one or many per App, independently revocable). The wire-token prefix hdr_app_ and the appKey developer-facing identifier are deliberately preserved by 008 — there is no rename.

008-agent-installation drops the AppAgent join: an App now references its Agent directly via agentId. The legacy OPERATE/DEVELOP role distinction is removed (system memory is universally read-only from any App context — DEVELOP-from-App is gone per R-10).

Cascade behavior: hard App deletion cascades to AppKey, AppLogEntry, AppMember, and the App's app/personal-class Memory rows. The orphan-retention behavior in 008 FR-015 (personal memory survives an AppMember removal) is independent — it's a row delete in app_members, not in apps.

Properties as follows:

  • id:
  • name: Display name.
  • urn

    Globally unique identifier; derived from org.urn + ":" + slug.

    Spec 021 (FR-027) — uniqueness is now PARTIAL. The DB-level constraint is apps_urn_active_uniq WHERE uninstalled_at IS NULL (declared in the 021 migration). The same URN can legitimately appear on multiple rows: one active (uninstalledAt IS NULL) plus N uninstalled history rows. Prisma's field-level @unique declaration was REMOVED because it would generate a non-partial global unique index that fights with the migration.

    Callers MUST use findFirst({ where: { urn, uninstalledAt: null } }) for active-row resolution per FR-028 resolution precedence; the findUnique callers in resolvers.ts were migrated to this pattern in PR #124.

  • organization_id:
  • create_user_permission:
  • identify_user_method:
  • session_timeout_seconds:
  • anonymous_ttl_days:
  • app_type

    Deprecated — superseded by surfaces. Scheduled for removal in a follow-up cleanup spec (per 008 R-4); kept in place to avoid spurious portal churn.

  • role

    Default member role applied when the Agent's installation_policy.memberRoles doesn't apply.

  • description:
  • system_prompt

    Deprecated — superseded by Agent.systemMemoryId + Agent.systemPrompt. Scheduled for removal in a follow-up cleanup spec (per 008 R-4).

  • agent_tools: Tools this App's agent can access; default [].
  • ai_provider

    Optional AI override on the App; if NULL, sessions resolve from the Agent's default AI config.

  • ai_model:
  • ai_api_key_encrypted:
  • expires_at:
  • training_mode

    Migrated from the dropped app_agents.training_mode. Sessions through this App are flagged as training data for the Agent's owner to review. 008-agent-installation FR-022.

  • surfaces

    Free-form per 008 R-9; v1 known values: 'chat', 'slack', 'mcp', 'web'. Surface enforcement (which features are gated on which surfaces) is handled by the portal/app code; chat-tab gating in hadron-portal#187 reads surfaces.includes('chat'). 008-agent-installation FR-014.

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:
  • uninstalled_at

    Soft-uninstall timestamp per spec 021 FR-025. Null = active install; set = uninstalled (read-only, write-rejected with "app uninstalled" error including cascade to child agents/memories/nodes per FR-026). Reinstall after soft-uninstall is allowed via the partial unique index apps_urn_active_uniq WHERE uninstalled_at IS NULL (declared in the 021 migration; supersedes Prisma's @unique on urn at the DB level).

  • urn_normalized_at

    Spec 021 FR-032 online URN-shape migration gate (transient). Null = pre-canonical row (read through the legacy parser); set = canonical- form row (read through the new parser). Dropped once 100% of rows are migrated. TODO(spec-021-cleanup): drop this column once the FR-032 migration completes — tracked under the spec-021 cleanup follow-up. The apps_urn_migration_pending_idx partial index goes with it.

  • urn_migration_failed_reason

    Spec 021 sentinel for data-defect rows during the FR-032 migration. Non-null = the row's urn failed normalization; the migration job skips it on subsequent passes (avoids infinite retry). Transient; dropped with urnNormalizedAt. TODO(spec-021-cleanup): drop alongside urnNormalizedAt.

app_keys

An API key for an App, independently revocable; multiple keys per App. The raw key carries the canonical hdr_app_ prefix and is shown once at creation; only the SHA-256 hash is stored. (SHA-256, not bcrypt — bcrypt's per-hash salt would prevent the constant-time keyHash lookup the auth resolver does. The raw key is a 256-bit random value, so dictionary attacks against the hash are not a concern.)

Auth path: the resolver at src/middleware/auth.ts accepts hdr_app_-prefixed bearer tokens, looks up the AppKey by SHA-256 hash, and resolves the principal to the parent App. Memory access is then gated by src/mcp/access-control.ts, which reads App.agentId directly (the legacy AppAgent join was dropped in 008-agent-installation) and folds the Agent's systemMemoryId + memoryItems into the access map.

Client config field: hadron-client reads the AppKey value from the appKey field of .hadron/config.json. The env var name HADRON_CLIENT_KEY describes location (the client process), not the entity.

Properties as follows:

  • id:
  • app_id:
  • key_hash: SHA-256 hash of the raw hdr_app_<…> key.
  • key_preview: Last 4 characters of the raw key for masked display.
  • label: Human-friendly name for this key.
  • created_at:
  • created_by:
  • last_used_at:
  • revoked_at:

app_members

The User↔App membership join with a per-member role. Spec: 008-agent-installation FR-004.

Per-User lifecycle is reserved for a future spec — there is no revokedAt / expiresAt column. Removing a member is a row delete; the user's personal-class Memory at (app_id, user.id) is retained by default as an orphan (FR-015) and re-attaches automatically if the user later rejoins the same App.

No deletedAt — soft-delete is meaningless for a pure join.

Properties as follows:

  • app_id:
  • user_id:
  • role

    Free-form string validated at write-time against the parent Agent's installation_policy.memberRoles (per 008 R-7). Examples: 'owner', 'guide', 'cook'. installation_policy.maxMembers is enforced at invite-acceptance time (per FR-016).

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:

agent_org_grants

The Org↔Agent license — authorizes an Org to (a) create Apps that deploy this Agent, and (b) bundle this Agent in AgentImport edges of Agents the Org owns. Spec: 008-agent-installation FR-006 / R-2.

Sibling to AgentSubscription (User↔Agent, from 005). Both share the lifecycle field shape (activatedAt, expiresAt, revokedAt, revokedBy) and the same active-status predicate. The two-table split (rather than a polymorphic subjectType + subjectId) preserves typed FK integrity and per-table indexing clarity.

For same-org installs (an Org deploying its own Agent), the grant is auto-provisioned by ensureAgentOrgGrant on first contact. Cross-org installs require an explicit grant; the v1 surface auto-provisions on first contact (the marketplace UX for explicit subscribe lands in a future spec).

No deletedAt — soft-delete is redundant with the revokedAt lifecycle.

Active-status predicate (shared with AgentSubscription via the isLifecycleActive helper):

activatedAt IS NOT NULL AND revokedAt IS NULL AND (expiresAt IS NULL OR expiresAt > now())

Properties as follows:

  • org_id:
  • agent_id:
  • activated_at: v1 default: now() on creation since billing is out of scope.
  • expires_at:
  • revoked_at

    Set by an ADMIN/OWNER of the Agent's owning org via revokeAgentOrgGrant.

  • revoked_by: fk to User.id — audit trail for who revoked.
  • created_at:
  • created_by:
  • updated_at:
  • updated_by:

agent_imports

A directed dependency edge between two Agents — "Agent A imports Agent B as a dep." Spec: 008-agent-installation FR-005 / User Story 3.

Installing the parent (a row in apps with agentId = parent) cascade-installs required deps (creates additional Apps in the same org, one per dep). Optional deps install only if the caller passes installOptional.

v1 enforces 1-level imports only — the dep itself must NOT be a parent of any AgentImport. Cycles are trivially blocked at write time (parentAgentId != importedAgentId).

Properties as follows:

  • parent_agent_id

    The Agent that does the importing (the "Suite"). ON DELETE CASCADE.

  • imported_agent_id

    The dep Agent. ON DELETE RESTRICT — deleting it would invalidate every Suite that depends on it; operators must remove the import edge first.

  • position

    Sparse-int ordering (per 008 R-8). Builders set values like 100, 200, 300 to allow easy reordering without renumbering.

  • required: Hard dep vs optional. Default true.
  • agent_org_grant_org_id

    Composite fk to AgentOrgGrant(orgId, agentId) (ON DELETE RESTRICT) — the Builder grant authorizing this import. Spec FR-005 named this column agent_subscription_id; renamed per 008 R-2 since the universal-grant role for org-level grants lives on the new AgentOrgGrant sibling, not AgentSubscription.

  • agent_org_grant_agent_id:
  • created_at:
  • created_by:

agent_subscriptions

The User-side license: "this user is authorized to use this agent." Auto-provisioned on first user-attributed contact (per controller/operations/ensureAgentSubscription.ts); revocable by an ADMIN/OWNER of the Agent's owning org. Authorizes the cross-org write from the Agent into the user's personal-class Memory.

Sibling to AgentOrgGrant (Org↔Agent, added in 008-agent-installation). Both share the lifecycle field shape and the same isLifecycleActive predicate; AgentSubscription continues to express User↔Agent grants while AgentOrgGrant covers Org↔Agent (App-creation + import-bundling) grants.

Spec: 005-agent-subscription (2026-04-30). Composite primary key on (userId, agentId).

No deletedAt — soft-delete is redundant with revokedAt.

Active-status predicate (used by the access-resolver):

activatedAt IS NOT NULL AND revokedAt IS NULL AND (expiresAt IS NULL OR expiresAt > now())

Revocation behavior (per FR-028 / FR-029): when revoked, the user's personal Memory of the Agent is retained by default — the user owns it, may want to keep their notes after a class ends. The Agent loses write access (per FR-019); the user retains read access via direct authenticated queries. The Memory.userMemoryOfAgentId pivot is preserved so the memory remains identifiable as "originally from Agent X." Single exception: if the personal Memory has zero nodes at revocation time, the platform may hard-delete it alongside the AgentSubscription (avoids orphan accumulation).

Properties as follows:

  • user_id:
  • agent_id:
  • activated_at

    v1 default: now() on creation since billing is out of scope; future billing introduces a deferred-activation path (activatedAt = null until payment confirms).

  • expires_at: Time-bounded subscriptions; NULL for non-expiring.
  • revoked_at

    Set by an ADMIN/OWNER of the Agent's owning org via revokeAgentSubscription.

  • revoked_by: fk to User.id — audit trail.
  • created_at:
  • created_by:
  • updated_at:
  • updated_by:

app_log

Log entry for a specific App, surfaced in hadron-portal for troubleshooting.

Properties as follows:

  • id:
  • app_id:
  • level:
  • memory_id:
  • session_id:
  • message: Human-readable log message.
  • detail: Structured context.
  • created_at:
  • created_by:
  • updated_at:
  • updated_by:

app_agents

N:M join between App and Agent, reintroduced by spec 023.

No role column — system memory is read-only to every App regardless (the pre-008 OPERATE/DEVELOP distinction stays deleted per spec 023 FR-003). No trainingMode column — training mode is a per-App setting and lives on App.trainingMode (spec 023 FR-001; deviates from the 2026-05-12-user-installing-app summary §3 to avoid per-Agent conflicts on an App-wide setting).

Transition note: App.agentId was dropped and consumers retrofitted in Phase 2b (hadron-server#129, 2026-05-17). Install flows go through the installAgentIntoApp mutation (spec 023 US1, hadron-server#130) which writes an AppAgent row.

Cascade behavior: hard App or Agent deletion cascades to the AppAgent row. Uninstalling an Agent from an App (deleting the row) does NOT cascade-delete the (app_id, agent_id, *) memories (FR-005; orphan retention preserved). Duplicate inserts for the same (app_id, agent_id) are rejected by the composite PK.

Properties as follows:

  • app_id:
  • agent_id:
  • created_at:
  • created_by:
  • updated_at:
  • updated_by:

memory_shares

Asymmetric cross-user grant for personal-class memory (spec 023 US3 / FR-017–FR-022). Composite PK (memoryId, granteeId) — one row per (Memory, grantee).

Scope: applies ONLY to personal-class Memories (FR-018). Application-layer guard in createMemoryShare.ts enforces this.

Grantor semantics (FR-019): grantorId is the principal (Memory.userId), recorded explicitly so the row is self-describing without a JOIN to memories at read time. The API actor (which may be an App backend acting on the principal's behalf) is recorded in createdBy.

No time-window (FR-021): isolation between grantees over time is modeled by creating a new personal Memory per pairing — not by time-slicing one Memory.

Properties as follows:

  • memory_id:
  • grantee_id:
  • grantor_id:
  • role:
  • created_at:
  • created_by:
  • updated_at:
  • updated_by:

memory_members

Symmetric team membership for group-class memory (spec 023 US4 / FR-026–FR-031). Composite PK (memoryId, userId) — one row per (Memory, member).

Scope: applies ONLY to group-class Memories (FR-027). Application-layer guard in addMemoryMember.ts enforces this.

Role capabilities: reader — read. writer — read + write nodes within the Memory. owner — read + write + manage member list, delete/rename the Memory, change Memory configuration (FR-036).

Last-owner protection (FR-038): the platform rejects removal or demotion of the sole remaining owner. The MemoryMember row can still be deleted; the operation-layer guard enforces the invariant (removeMemoryMember.ts, updateMemoryMemberRole.ts). FR-031: removing the last non-owner does NOT delete the Memory.

Properties as follows:

  • memory_id:
  • user_id:
  • role:
  • created_at:
  • created_by:
  • updated_at:
  • updated_by:

user_api_keys

User-owned API key — the credential primitive for user-scoped MCP access (spec 025-oauth-for-mcp; D-2026-05-13-001, D-2026-05-17-001). Mirrors AppKey but resolves to a User instead of an App. The raw key is hdr_user_<…>; we store SHA-256 (not bcrypt) for the same reason AppKey does — direct hash lookup needs a deterministic hash, and the 256-bit raw key (32 bytes / 64 hex chars) already makes dictionary attacks irrelevant.

Tokens issued by the OAuth /token endpoint share this shape and storage path so a single resolveUserApiKey middleware validates both portal-minted and OAuth-issued tokens (FR-015). The path the key arrived through is recorded in issuedVia (not createdBy); see the column docstrings.

Audit fields omitted (matches AppKey precedent): no updatedAt / updatedBy / deletedAt / deletedBy. UserApiKey rows are append-once with a single terminal state transition (revoked); revokedAt carries the lifecycle info, and there is no edit operation. Soft-delete is meaningless for a revocable credential.

Properties as follows:

  • id:
  • user_id:
  • key_hash: SHA-256 hash of the raw hdr_user_<…> key.
  • key_preview: Last 4 characters of the raw key for masked display.
  • label: Human-friendly name for this key.
  • created_at:
  • created_by

    Actor that minted this key. Schema-wide convention: User.id of the actor. For self-service v1 this equals userId; the column exists for future admin-issued paths (where actor != owner).

  • issued_via

    Issuance-path discriminator: 'portal' for createUserApiKey GraphQL mutation; 'oauth:<client_id>' for /oauth/token-issued tokens. Distinct from createdBy so the audit-actor convention is preserved.

  • last_used_at:
  • revoked_at:

oauth_clients

OAuth 2.1 client registry (spec 025-oauth-for-mcp; D-2026-05-17-002). Rows are populated by POST /oauth/register (RFC 7591 DCR) or seeded explicitly. Public clients only (no clientSecret); PKCE S256 is the auth proof. Per Q-025-001, only the 'test' row will be seeded by prisma/seed.ts (spec 025 task T009 — not yet in this PR); 'claude-desktop' and other real clients self-register via DCR.

PK note: clientId is the PK (no separate cuid id) because clientId is the natural key — it's the public, immutable identifier the client sends on every /authorize / /token request and is referenced by AuthCode.clientId as the FK target. Precedent for a non-cuid PK: Session.id is caller-generated. Diverges from PR 137's reference (which carried a separate cuid id) — see spec-kits/specs/025-oauth-for-mcp/data-model.md §OAuthClient.

Carries the full standard audit-field set per the schema-top convention because edit / delete affordances are anticipated (admin "rename a client", "rotate redirect URIs", etc.); v1 has no such surface, but the columns are cheap to add now and would otherwise need a backfill later.

Properties as follows:

  • client_id

    The public client identifier. DCR-registered: dcr_<32 hex> (16 bytes / 128 bits). Seeded fixtures use stable identifiers like 'test'. Primary key.

  • client_name:
  • redirect_uris

    Allowed redirect URIs. /authorize must reject any redirect_uri not present here (OAuth 2.1 §3.1.2).

  • created_at:
  • created_by:
  • updated_at:
  • updated_by:
  • deleted_at:
  • deleted_by:

auth_codes

Short-lived OAuth authorization code (spec 025-oauth-for-mcp; RFC 6749 §4.1). Issued by POST /oauth/authorize on decision=approve, redeemed once at POST /oauth/token. Stores PKCE S256 challenge + RFC 8707 resource indicators for verification at redemption. Single-use via race-safe updateMany-with-conditional redeemedAt: null pattern (FR-013). PKCE / resource mismatch at /token also marks redeemedAt to prevent probing.

Code storage: the raw code value is hashed (SHA-256) before storage, matching AppKey.keyHash and UserApiKey.keyHash — auth codes are credentials in their 10-minute redemption window and should not be plaintext in the DB. /oauth/authorize returns the raw code to the client; /oauth/token hashes the supplied code and looks up by hash. Diverges from PR 137's reference (which stored plaintext) — see spec-kits/specs/025-oauth-for-mcp/contracts/oauth-endpoints.md §POST /oauth/token.

Audit fields omitted (matches AppKey precedent): no updatedAt / updatedBy / deletedAt / deletedBy. AuthCode rows are append-once with two terminal state transitions (redeemed / expired); redeemedAt and expiresAt carry the lifecycle info. Soft-delete is meaningless for a single-use, expiring credential.

Properties as follows:

  • id:
  • code_hash

    SHA-256 hash of the raw code value handed to the client. Looked up by hash at /token to avoid plaintext credentials in the DB.

  • client_id:
  • user_id:
  • redirect_uri:
  • code_challenge: PKCE S256 challenge value (base64url, no padding).
  • resource

    RFC 8707 resource indicator(s) captured at /authorize. Multi-valued per RFC 8707 §2; v1 MCP usage populates a single element (the MCP endpoint URL). Application enforces ≥1 element.

  • expires_at:
  • redeemed_at:
  • created_at:
  • created_by

    Actor that initiated the /authorize request that minted this code. Resolves to the signed-in User; equals userId in self-service v1.