Identity, Devices & Pairing

Onboarding-first Ed25519 identities, person-scoped device keys, QR pairing, and sprite devices.

Overview

Trellis builds identity directly into the data structure, not a third-party auth server. Every author is a cryptographic identity, every op can carry an Ed25519 signature, and identities are socially witnessed: you are who your peers know you to be (network-scoped handles), not who a directory says you are.

The identity model is an "Apple ID" for your graph: you create one identity per person, once, at first setup — and every machine, repo, and sprite you use afterward binds to it. Your identity is a keypair you generated; nothing is hosted or hostage.

Onboarding: first-run identity

Identity is created at first-run onboarding, long before pairing. When you trellis init on a fresh machine (no person identity, no profile), Trellis asks:

Are you new to Trellis, or do you already have an identity?
  • New — create my identity
  • Existing — pair with another device to sync my graph
  • New → creates your person identity at ~/.trellis/identity.json (person scope, authoritative) and your profile. Repos minted after that are signed from op #1.
  • Existing → walks you through adopting your identity from an already-onboarded device via the pairing flow (below). Your machine receives a device key — your root key never leaves the approving device.
  • Non-interactive runs (CI, provisioning) default to the new-user path: --identity new|existing|skip.
trellis init                    # interactive onboarding
trellis init --identity new     # deterministic: create person identity
trellis init --identity skip    # stay anonymous (dev-only, warns)

Identity scopes

ScopePathRole
Person~/.trellis/identity.jsonAuthoritative identity — created at onboarding
Repo.trellis/identity.jsonLegacy per-repo key — fallback only, never required

Resolution is person-first (person ?? repo). Creating an identity generates an Ed25519 key pair with a did:key: identifier:

trellis identity init --name "Ada"          # person scope (default)
trellis identity init --name "Ada" --local  # legacy repo scope
trellis identity                            # show your identity
trellis identity export                     # machine-readable JSON
trellis identity import                     # adopt an exported identity

Device pairing (QR / OOB)

Other machines join your identity by pairing a device — they receive a delegated device key scoped under your identity. The root private key is never copied; identity.json never leaves the approving device.

# On your existing device (holds the identity):
trellis pair start                    # challenge + QR payload

# On the new device:
trellis pair join <payload>           # creates a device key (never identity.json)
trellis pair accept <auth-payload>    # finish after approval

# Back on the existing device:
trellis pair approve <join-payload> --yes   # verify the fingerprint first

The full handshake: start → join → approve → accept. Payloads are OOB strings (trellis:pair:v1:…) or terminal QR.

Managing devices

Devices are stored in a person-scoped registry (~/.trellis/devices), so a paired device is recognized by every clone of your identity — pair once, and all your repos see it. Legacy repo-scoped registries are read and migrated up automatically.

trellis pair list          # kind, transport, sync state, last seen
trellis pair show <id>     # full record incl. last-synced op
trellis pair revoke <id>   # local revocation
trellis pair revoke <id> --push <ws-url>   # + notify peers (device-revoked)
trellis pair join <payload> --kind desktop --transport ws

Device records carry metadata: kind (desktop / cli / cloud-sprite), transport (ws / http / iroh), lastSeenAt, lastSyncOpHash, and syncState (idle / syncing / behind / diverged / offline). The sync daemon stamps idle/offline + the last-synced op hash after every push/pull cycle.

Revocation is fail-closed: a revoked key never resolves through the identity resolver, and revocation propagates to connected peers via the device-revoked sync message — clones and sprites update their registries, and signatures from the revoked key stop verifying.

Sprites as devices

A sprite you provision is a first-class paired device:

trellis vm create prod-room
# ✓ Sprite created and Trellis DB deployed
# ✓ Sprite paired as device: dev_sprite_prod-room

vm create mints a cloud-sprite device key (a fresh keypair — your root key never leaves your machine), registers it in your person-scoped registry, and installs it on the sprite. The sprite signs as your identity with its device key and syncs in realtime through the standard room path.

Op signing

Ops are signed device-first: a paired machine signs with its device key (signedWith: <deviceId>); a machine with no device key falls back to the identity root (signedWith: root). Signatures are inside the hashed payload, so a signed op still verifies its integrity hash, and recipients verify signatures against keys resolved from the device registries plus the local peer graph (~/.trellis/peers.json — network-scoped handles, ADR 0036).

# Signing material resolution order (getSigningMaterial):
#   1. local device key (paired)     → signedWith: <deviceId>
#   2. person identity root          → signedWith: root
#   3. repo identity (legacy)        → signedWith: root

Governance Policies

Governance policies are enforced by KernelMiddleware: they intercept ops before they are applied and can reject or transform them.

Built-in Policy Types

PolicyEffect
requireSigningReject ops without a valid Ed25519 signature
branchProtectionPrevent direct pushes to protected branches
identityWhitelistOnly allow ops from a list of known identities
opRateLimitThrottle ops per identity per time window

Policy Configuration

{
  "governance": {
    "policies": [
      { "type": "branchProtection", "branch": "main", "requireReview": true },
      { "type": "requireSigning", "branches": ["main", "release/*"] },
      {
        "type": "identityWhitelist",
        "branch": "release/*",
        "identities": ["did:key:z6MkAlice", "did:key:z6MkBob"]
      }
    ]
  }
}

The Governance Pillar

Trellis' Governance Subgraph is one of the Five Pillars. Rather than bolting on auth after the fact, governance is expressed in the same EAV primitives as everything else:

  • Identity entities public keys, DIDs, display names
  • PolicyRule entities typed rules with scope (repo, branch, operation kind)
  • AuditTrail links every policy decision is linked to the op that triggered it

References

  • ADR 0020 — QR device pairing (delegated device keys)
  • ADR 0032 — person identity authority; identity-addressed clones
  • ADR 0036 — peer-aware identity resolution (IdentityResolver contract)
  • Peer system spec — network-scoped handles (identities are socially witnessed)