// Package auth is maven's authority layer — the 4-layer cascade and the // "surface caps authority" invariant. // // Spec contract (from DESIGN.md § Auth): // // a cascade, not a pick-one — each layer answers a different question: // // | layer | question | mechanism | surface | // | 0 | on the network at all? | wireguard | floor | // | 1 | enrolled box? | mTLS client cert | pc client, authed page | // | 2 | you, this session? | passkey / webauthn | pc client, authed page | // | 3 | you, right now, for this act? | passkey user-verification | step-up acts | // // wg is necessary-not-sufficient: an unlocked laptop inside the tunnel is // "authed" at layer 0 only — that gap is why the upper layers exist. // // the invariant: auth tier is a property of the SURFACE; the surface caps // maximum authority. you can't step up past what the channel structurally // carries. voice STOPS at L0 (a room mic is reachable by anyone present → // speaker verification is attribution, not auth). telegram inbound = weak // tier (read + soft acts, never destructive, never registration). only // pc_client / authed_page carry passkey user-verification (L3) at all. // // "compromised X can't forge Y" is the through-line — applied at smaller // and smaller scope (network → box → process). the auth layer applies it at // the process radius: a module calling CoreAPI is bound to a Surface; the // Surface caps what methods/authority that call can carry. package auth // Layer — one rung of the auth cascade. type Layer int8 const ( // Layer0 — wireguard: on the network at all. Floor for everything. Layer0 Layer = 0 // Layer1 — mTLS client cert: enrolled box. Optional per spec ("if dropping // one, drop mTLS, never the passkey"); not wired by the floor enrollment. Layer1 Layer = 1 // Layer2 — passkey/webauthn session: you, this session. Layer2 Layer = 2 // Layer3 — passkey user-verification gesture for a SINGLE act: you, right // now, for this. registration-enable, destructive acts, core cold-start // unlock. the highest-authority op. Layer3 Layer = 3 ) // Surface — where a call ENTERS maven from. A property of the channel that // structurally caps the maximum authority that channel can carry. The wire // doesn't carry a layer — it carries a Caller; the Enrollment maps the caller // to a Surface; MaxLayer caps it. So voice can never reach EnableTool — not // because auth "failed" but because the channel can't carry the proof. type Surface string const ( // SurfaceVoice — a room mic / wake-word path. Speaker verification is // attribution, not auth: anyone present (gf, the TV) can speak. STOPS at // L0. never destructive, never registration. SurfaceVoice Surface = "voice" // SurfaceTelegram — telegram inbound. weak tier: telegram's own auth, // outside our control. read + soft acts, never destructive, never // registration. carries up to L2 (a chat-id allowlist is the best we get). SurfaceTelegram Surface = "telegram" // SurfacePCClient — the desktop gui, mTLS'd and passkey'd. carries L3 // (passkey user-verification is available on-device). SurfacePCClient Surface = "pc_client" // SurfaceAuthedPage — the web authed page over wg. carries L3 (passkey // user-verification via the browser / platform authenticator). SurfaceAuthedPage Surface = "authed_page" // SurfaceCoreProcess — a module running in core's own address space (the // daemon-embedded router/delivery today). There is no boundary to cross; // this is the 0600-floor equivalent: trusted same-process. Layer3-capped // since the user already unlocked the daemon (cold-start IS L3 per spec). SurfaceCoreProcess Surface = "core_process" // SurfaceUnknown — enrollment didn't recognize the caller. fail closed. SurfaceUnknown Surface = "unknown" ) // MaxLayer — the surface-caps-authority table. PURE. The invariant: you // cannot step up past what your channel structurally carries. voice → L0; // telegram → L2 (chat allowlist); pc_client / authed_page / core_process → // L3. An unrecognized surface caps at -1 (fail closed) — a caller with no // enrolled identity is not "unauthed at L0", it's refused outright. func MaxLayer(s Surface) Layer { switch s { case SurfaceVoice: return Layer0 case SurfaceTelegram: return Layer2 case SurfacePCClient, SurfaceAuthedPage, SurfaceCoreProcess: return Layer3 default: return -1 } }