/**
 * Device pairing (ADR 0020 Phase 0)
 *
 * Delegated device keys under an Ed25519 identity. Never copies identity.json
 * private key. OOB string payloads: start → join → approve → accept.
 */
import type { IdentityResolver } from './signing-middleware.js';
export declare const ROOT_DEVICE_ID = "root";
export declare const PAIR_TTL_SECONDS: number;
export declare const PAIR_PREFIX = "trellis:pair:v1:";
export declare const JOIN_PREFIX = "trellis:join:v1:";
export declare const AUTH_PREFIX = "trellis:auth:v1:";
export interface PairChallenge {
    v: 1;
    challengeId: string;
    did: string;
    identityEntityId: string;
    /** Root public key (base64 SPKI) so B can verify SignedDeviceAuthorization. */
    rootPublicKey: string;
    exp: number;
    nonce: string;
}
export interface JoinResponse {
    v: 1;
    challengeId: string;
    devicePublicKey: string;
    deviceLabel?: string;
    /** Slice C metadata — stamped by the joining device, honored by the registry. */
    kind?: DeviceKind;
    transport?: DeviceTransport;
    /** Signature over canonical challenge bytes. */
    signature: string;
}
/** Slice C — device identity metadata (docs/planning/device-registry-and-sprite-pairing.md). */
export type DeviceKind = 'desktop' | 'cli' | 'cloud-sprite';
export type DeviceTransport = 'ws' | 'http' | 'iroh';
export type DeviceSyncState = 'idle' | 'syncing' | 'behind' | 'diverged' | 'offline';
export interface DeviceAuthorization {
    v: 1;
    deviceId: string;
    identityEntityId: string;
    did: string;
    devicePublicKey: string;
    deviceLabel?: string;
    /** Slice C metadata — passed through from the joining device. */
    kind?: DeviceKind;
    transport?: DeviceTransport;
    issuedAt: string;
    expiresAt?: string;
    issuerDeviceId: string;
    challengeId: string;
}
export interface SignedDeviceAuthorization {
    authorization: DeviceAuthorization;
    signature: string;
}
export interface DeviceRecord {
    deviceId: string;
    devicePublicKey: string;
    deviceLabel?: string;
    authorizedAt: string;
    revokedAt?: string;
    issuerDeviceId: string;
    challengeId: string;
    /** Slice C — metadata + state (fed by pairing stamps, sync daemon, presence). */
    kind?: DeviceKind;
    transport?: DeviceTransport;
    lastSeenAt?: string;
    lastSyncOpHash?: string;
    syncState?: DeviceSyncState;
}
export interface DeviceRegistry {
    identityEntityId: string;
    did: string;
    rootPublicKey: string;
    devices: DeviceRecord[];
}
export interface LocalDeviceKey {
    deviceId: string;
    identityEntityId: string;
    did: string;
    publicKey: string;
    privateKey: string;
    deviceLabel?: string;
    createdAt: string;
    /** Slice C — this device's own metadata + state (self touchpoint). */
    kind?: DeviceKind;
    transport?: DeviceTransport;
    lastSeenAt?: string;
    lastSyncOpHash?: string;
    syncState?: DeviceSyncState;
}
export declare function encodePayload(prefix: string, obj: unknown): string;
export declare function decodePayload<T>(prefix: string, payload: string): T;
/** Crockford base32 short code from challengeId (first 8 chars of hash). */
export declare function challengeShortCode(challengeId: string): string;
export declare function deviceFingerprint(publicKeyBase64: string): string;
/**
 * Person-scoped device store (Slice B, docs/planning/device-registry-and-
 * sprite-pairing.md): a paired device follows the person, so every clone of
 * the identity recognizes it. `~/.trellis/devices` mirrors the person
 * identity dir (ADR 0032 §3). The legacy repo-scoped store
 * (`.trellis/devices`) is still read as a fallback and migrated up once.
 */
export declare function personDevicesDir(): string;
export declare function loadRegistry(trellisDir: string): DeviceRegistry | null;
export declare function saveRegistry(trellisDir: string, registry: DeviceRegistry): void;
export declare function loadLocalDevice(trellisDir: string): LocalDeviceKey | null;
export declare function saveLocalDevice(trellisDir: string, local: LocalDeviceKey): void;
export declare function listDevices(trellisDir: string): DeviceRecord[];
export declare function revokeDevice(trellisDir: string, deviceId: string): boolean;
/**
 * Register a device record without the QR handshake (Slice D — sprite
 * provisioning). The device key is minted out-of-band (never the root
 * identity key) and installed on the sprite; this records it in the
 * person-scoped registry so it appears in `pair list` and resolves through
 * the resolver like any paired device.
 */
export declare function registerDevice(trellisDir: string, record: Omit<DeviceRecord, 'authorizedAt' | 'issuerDeviceId' | 'challengeId' | 'deviceId'> & {
    deviceId: string;
}): DeviceRecord;
export declare function resolveDevicePublicKey(trellisDir: string, identityEntityId: string, deviceId: string): string | null;
export declare function resolvePublicKeys(trellisDir: string, identityEntityId: string): string[];
export declare function assertChallengeValid(challenge: PairChallenge): void;
export declare function pairStart(trellisDir: string, opts?: {
    ttlSeconds?: number;
}): {
    challenge: PairChallenge;
    payload: string;
    shortCode: string;
};
export declare function pairJoin(trellisDir: string, challengePayloadOrCode: string, opts?: {
    deviceLabel?: string;
    kind?: DeviceKind;
    transport?: DeviceTransport;
}): {
    join: JoinResponse;
    payload: string;
    local: LocalDeviceKey;
    challenge: PairChallenge;
};
export declare function pairApprove(trellisDir: string, joinPayload: string, opts?: {
    yes?: boolean;
}): {
    signed: SignedDeviceAuthorization;
    payload: string;
    fingerprint: string;
};
export declare function pairAccept(trellisDir: string, authPayload: string): {
    local: LocalDeviceKey;
    authorization: DeviceAuthorization;
};
/**
 * Signing material for this install: prefer paired device key, else root identity.
 */
export declare function getSigningMaterial(trellisDir: string): {
    privateKey: string;
    identityEntityId: string;
    signedWith: string;
} | null;
/**
 * Update this machine's device state (Slice C): stamps `lastSeenAt` plus
 * optional sync metadata on the local device key, and mirrors the same onto
 * this device's record in the local registry when present (e.g. the approving
 * machine's own paired devices). Returns false when this machine has no
 * local device key (not paired).
 */
export declare function markDeviceSeen(trellisDir: string, patch?: {
    syncState?: DeviceSyncState;
    lastSyncOpHash?: string;
}): boolean;
/**
 * Patch state on an arbitrary device record in the registry (Slice C) —
 * used by the sync daemon and future sprite heartbeats. No-op for unknown or
 * revoked devices.
 */
export declare function updateDeviceState(trellisDir: string, deviceId: string, patch: {
    syncState?: DeviceSyncState;
    lastSyncOpHash?: string;
    lastSeenAt?: string;
}): boolean;
/**
 * Build an `IdentityResolver` bound to a trellis directory (ADR 0022 Phase 3).
 *
 * The `IdentityResolver` interface takes only an `entityId` (it is injected
 * into the engine, which has no filesystem concept), whereas the registry
 * lookups in this module need the `trellisDir`. This adapter closes that gap:
 * it captures the dir and forwards to the registry/local-device resolution
 * that backs ADR 0020 device keys.
 *
 * Returns `null` when the directory has no identity at all, so callers can
 * keep the resolver opt-in (an identity-less repo gets no PKI enforcement).
 */
/**
 * Build an `IdentityResolver` bound to a trellis directory (ADR 0022 Phase 3,
 * ADR 0036). Resolves keys from the device registries (person scope first,
 * repo scope fallback) plus the local identity root — regardless of whether a
 * local identity exists: a machine with no identity still verifies remote ops
 * signed by identities it knows through pairing. An identity whose key is in
 * neither the registries nor the local store resolves to nothing (fail-closed).
 */
export declare function pairingResolver(trellisDir: string): IdentityResolver;
//# sourceMappingURL=pairing.d.ts.map