e0d0244fa9
15 root markdown files, ~4,900 lines against ~33,000 lines of Go, with at least three pairs contradicting each other. When five documents describe the architecture, the code becomes the only trustworthy one — which defeats the point of having them. That drift is why the resident-model question had four incompatible answers. SPEC.md, maven.md and ROADMAP.md are deduped into DESIGN.md rather than concatenated, with a "Superseded" section carrying eight retired decisions and what replaced each: classifier-owns-the-route (the cascade is still the live path, but as a stopgap, not a design to extend), faster-whisper/vosk/silero, the small-model phrasing claim, sqlcipher, the Kotlin/Spring sketches, obsidian->chroma, script deployment, and FloorEnrollment. Superseded material is kept and marked rather than deleted, so it cannot read as current. SESSION-05/06-07-2026.md and PLANS.md are removed outright — git history holds them, and both were verified tracked before deletion. Go doc comments citing the deleted files are repointed to the equivalent DESIGN.md sections. Several asserted designs that were already retired, so the claims are corrected and not just relinked: stt.go named faster-whisper as production (it is whisper.cpp), tts.go named silero (it is piper), intent.go still described the classifier as owning the route, and stale vosk/chroma vocabulary is replaced. ECOSYSTEM-SPEC.md references are deliberately untouched — that is a different document, and a naive grep for SPEC.md matches it. Root markdown drops from 4,880 to ~3,700 lines. The review's ~1,500 target is not reachable while keeping the files it also said to keep — those alone are 2,553 lines — so trimming further needs a separate decision on MAVEN_ECOSYSTEM_ARCHITECTURE.md and PROGRESS.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X5JApcrCRVGmqrxnhynSik
95 lines
4.5 KiB
Go
95 lines
4.5 KiB
Go
// 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
|
|
}
|
|
}
|