Documentation
¶
Overview ¶
Package preferenceaccess is the memory Kind for the audit log of subject-named preference reads: every GET /memory/_preferences?user-ref= request, resolved or not. It exists because that route is a disclosure surface an agent controls the input to (the reference it asks the operator to resolve) and the platform decides the output of (which keys, if any, a class marked visibility: class) — the tamper-evident record is what lets a human answer "who did this agent look up, and what did it see" after the fact, the same evidentiary bar the rest of pkg/memory/kinds' append-only Kinds hold audit and security-relevant events to.
Index ¶
Constants ¶
const ( // OutcomeOK: the reference resolved and the class-visible snapshot was // returned. OutcomeOK = "ok" // OutcomeUnresolved: the reference did not resolve (unknown form, no // linked platform user, resolution unavailable on this cluster); the // request answered 422 and disclosed nothing beyond the schema. Reason // says why, in the resolver's own bounded words. OutcomeUnresolved = "unresolved" // OutcomeUnknownSubject: the reference resolved in FORM only (an email // canonicalizes to a subject unconditionally) and the platform has no // record of that subject, so the request answered 422 exactly like an // unresolved reference. ResolvedSubject IS populated — whom the attempt // was about is known and belongs in the record — which is what // distinguishes this token from OutcomeUnresolved in a forensic query. OutcomeUnknownSubject = "unknown-subject" // OutcomeResolverError: subjectresolve.Resolve itself faulted (a SpiceDB // read error, a session-annotation read error); the request answered // 502 and disclosed nothing. Reason carries the bounded fault text. OutcomeResolverError = "resolver-error" // OutcomeGlobalsError: the AgentSettings read faulted before resolution // was attempted; the request answered 500 and disclosed nothing. OutcomeGlobalsError = "globals-error" // OutcomeUserScopeError: the resolved subject's user scope could not be // derived; the request answered 500 and disclosed nothing. OutcomeUserScopeError = "user-scope-error" // OutcomeQueryError: the resolved subject's user_preference query // faulted; the request answered 5xx and disclosed nothing. OutcomeQueryError = "query-error" )
The canonical Outcome strings. They live here rather than being whatever a caller happens to spell — the same reasoning authzdecision's outcome constants record: a forensic query filtering on an outcome token must match every writer, and a writer inventing "resolver_error" where the reader greps "resolver-error" produces an audit trail whose faults are unfindable.
const IDPrefix = "prefacc-"
IDPrefix is the literal byte prefix every Entry of this Kind's ID starts with.
const KindName = "preference_access"
KindName is the registered name of this memory Kind.
Variables ¶
This section is empty.
Functions ¶
func Record ¶
Record appends one preference_access audit entry into scope — the SESSION scope the ?user-ref= request was made against, never the resolved subject's own user scope: this is a record of what the AGENT did, and belongs on the agent's own tamper-evident chain regardless of whether the reference resolved.
m is deliberately auditaccessor.Putter, not memory.Memory: the caller (httpsrv.WithPreferenceAudit) hands this a write-only signing facade, and that facade has no reason to ever gain Query access to this audit trail just to append to it. A memory.Memory value (e.g. a bare *memory.Local in a test) still satisfies this — its method set is a superset.
A unique ID per call (via memory.NewID), and CreatedAt stamped to now — c carries no timestamp field of its own (see Content's doc), so every call to Record produces a genuinely new entry, including two back-to-back reads of the exact same ref.
Types ¶
type Content ¶
type Content struct {
// Ref is the raw reference the caller supplied to ?user-ref= (e.g.
// "email:alice@example.com", "github_user:12345", "trigger-author").
Ref string `json:"ref"`
// ResolvedSubject is the canonical user id the reference resolved to;
// empty when resolution did not (or could not yet) produce one. It IS
// populated on the post-resolution fault outcomes (user-scope-error,
// query-error) AND on unknown-subject: the subject was known by then,
// and "whom the attempt was about" is the fact the trail exists to
// carry.
ResolvedSubject string `json:"resolvedSubject,omitempty"`
// Outcome is one of the canonical Outcome* tokens above.
Outcome string `json:"outcome"`
// Reason carries the human-readable detail: the resolver's Reason on
// OutcomeUnresolved, the bounded fault text on the error outcomes,
// empty on OutcomeOK.
Reason string `json:"reason,omitempty"`
// Keys lists the class-visible preference keys the read considered —
// the schema, filtered to visibility: class, BEFORE any user value was
// read. Self-only keys never appear here, resolved or not. Empty on the
// error outcomes: a faulted attempt disclosed nothing.
Keys []string `json:"keys,omitempty"`
}
Content is one subject-named preference read ATTEMPT — recorded on every exit of the ?user-ref= route once the session/class are validated, fault paths included, so a failed (and possibly client-retried) attempt is as visible in the trail as a successful disclosure.
type Kind ¶
type Kind struct{}
func (Kind) ContentSchema ¶
func (Kind) IndexedFields ¶
IndexedFields: "outcome" — the field a forensic sweep filters on ("show me every failed subject-named read attempt"). JSON tag, not the Go name, per the Kind interface's own contract.
func (Kind) NewScopeHooks ¶
func (Kind) NewScopeHooks(_ memory.Scope) memory.ScopeHooks
func (Kind) WriteAuthority ¶
func (Kind) WriteAuthority() memory.WriteAuthority
WriteAuthority: ComponentWritten — only the operator's own preferences GET handler authors this Kind, signed as "system:operator" through its own signing facade (see httpsrv.WithPreferenceAudit and the operator's newMemHandlerOpts). A session credential can never author its own audit record — the same reasoning user_preference's WriteAuthority documents.