package authz import ( "encoding/json" "time" ) // Target binds an op to a specific box (and optionally a guest) — the anti-retarget // field. The §7 reference omitted the json tags; production needs them so the // signed canonical bytes decode correctly. type Target struct { HostID string `json:"host_id"` GuestID string `json:"guest_id"` } // OpBlob is the canonical signed object (phase4 §2). The signature covers the // EXACT bytes of this object's canonical JSON (keys sorted at every level, no // insignificant whitespace, no trailing newline, UTF-8) — produced by the // operator CLI / hub, verified here over the raw received bytes. type OpBlob struct { Op string `json:"op"` Target Target `json:"target"` Params json.RawMessage `json:"params"` Nonce string `json:"nonce"` IssuedAt time.Time `json:"issued_at"` ExpiresAt time.Time `json:"expires_at"` KeyID string `json:"key_id"` } // VerifiedOp is the authenticated, parsed op returned on success — everything the // reconcile layer (slice 4) needs to route and execute, not just the op string. type VerifiedOp struct { Op string // the operation, e.g. "guest_destroy" HostID string // target host (== this agent's host) // GuestID is non-empty for a guest-scoped op; the caller routes by it. "" = // host-scoped op. The verifier does NOT need to know all guest ids. GuestID string Params json.RawMessage Nonce string IssuedAt time.Time ExpiresAt time.Time // KeyID is the blob's self-declared key id — ADVISORY / audit only, never an // authz input. Authz is the key-material allow-list match (Signer below). KeyID string // Signer is the allow-listed key whose material matched the signature. Signer AllowedSigner // KeyIDMatchesSigner is false when the blob's advisory KeyID disagrees with // the matched signer's id (a benign audit signal, not a rejection). KeyIDMatchesSigner bool }