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
| Scope | Path | Role |
|---|---|---|
| Person | ~/.trellis/identity.json | Authoritative identity — created at onboarding |
| Repo | .trellis/identity.json | Legacy 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
| Policy | Effect |
|---|---|
requireSigning | Reject ops without a valid Ed25519 signature |
branchProtection | Prevent direct pushes to protected branches |
identityWhitelist | Only allow ops from a list of known identities |
opRateLimit | Throttle 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 (
IdentityResolvercontract) - Peer system spec — network-scoped handles (identities are socially witnessed)