store

package
v1.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 29, 2026 License: AGPL-3.0 Imports: 24 Imported by: 0

Documentation

Overview

Package store owns Ken's embedded SQLite database: schema migrations plus the search/get queries. Writes serialize through a single-writer pool; reads use a separate pool. WAL lets readers run concurrently with the writer.

Index

Constants

View Source
const HandoffKey = "handoff"

HandoffKey is the reserved page the briefing reads first, transfer collides on, and every station is expected to keep. A handoff written only on the way out is never written, so maintaining it is a duty of the current session.

Variables

View Source
var (
	ErrSlugConflict = errors.New("an entry with that slug already exists")
	ErrNotFound     = errors.New("entry not found")
	ErrBadVersion   = errors.New("version not found or not in a promotable state")
	// ErrInvalid wraps user-facing validation failures (safe to surface to clients).
	ErrInvalid = errors.New("invalid input")
	// ErrForeignLang blocks promoting a version whose detected content language is
	// not one the curator declared they can read — the "can't promote what you
	// can't read" comprehension gate. It is enforced in the store (Promote /
	// Repromote), not just the UI, so no server-side path reaches the head with
	// unreadable content.
	ErrForeignLang = errors.New("proposal is not in a curation language")

	// OAuth authorization-server errors (see internal/store/oauth.go).
	ErrOAuthNoClient  = errors.New("oauth client not found")
	ErrOAuthBadCode   = errors.New("authorization code invalid, expired, or already used")
	ErrOAuthBadToken  = errors.New("oauth token invalid, expired, or revoked")
	ErrOAuthReuseKill = errors.New("refresh token reuse detected — grant revoked")
)

Sentinel errors surfaced to callers (MCP tools, web handlers).

View Source
var ErrLockerCapReached = errors.New("locker cap reached")

ErrLockerCapReached refuses rather than evicting (S12).

View Source
var ErrNoteRevConflict = errors.New("notebook page changed underneath this write")

ErrNoteRevConflict is returned when an `if_rev` precondition fails. Two sessions may staff one station (S4), so a blind write would silently destroy the other's page.

View Source
var ErrNotebookCapReached = errors.New("notebook cap reached")

ErrNotebookCapReached refuses rather than evicting (S12): silent eviction of a working note is data loss the session cannot see, a refusal is an error the model reacts to.

View Source
var ErrStationNameTaken = errors.New("station name already in use in this space")

ErrStationNameTaken is returned when a name collides within a space. Names are display-only and unique per space; routing is always by the opaque station_id, so a collision is a human-facing inconvenience rather than an addressing failure.

View Source
var ErrTaskCapReached = errors.New("open task cap reached")

ErrTaskCapReached is returned instead of evicting. S12: refuse, never evict — silent eviction of a working note is data loss the session cannot see, while a refusal is an error the model reads and reacts to.

Functions

func LangForeign

func LangForeign(contentLang string, curationLangs []string) bool

LangForeign reports whether a detected content language would be BLOCKED for promotion under the given curation languages — the inverse of the internal gate. The web layer uses it to flag out-of-language proposals on the review queue with the exact same rule the store enforces (no drift between the badge and the gate).

func VerifySnapshot

func VerifySnapshot(ctx context.Context, path string) (int, error)

VerifySnapshot opens a database file and runs the backup's mandatory checks: PRAGMA integrity_check, foreign-key integrity, the FTS5 internal integrity-check on both indexes, a functional MATCH canary, embedding vector-length parity, and returns the entry count (for the caller to reconcile against the source). Opened read-write so the FTS integrity-check can run; a VACUUM-INTO snapshot is in rollback-journal mode, so no -wal/-shm persists.

Types

type BrowseFilter

type BrowseFilter struct {
	Category  string // exact match; "" = any
	Kind      string // user|feedback|project|reference; "" = any
	Staleness string // fresh|aging|stale|refuted; "" = any
	Lifecycle string // draft|active|deprecated; "" = any non-archived
	Sort      string // updated (default) | title | used | created | kind
	Limit     int    // default 50, max 200
	Offset    int
}

BrowseFilter parameterizes ListEntries. Every field is optional; the zero value lists all non-archived entries, newest-updated first.

type BrowseRow

type BrowseRow struct {
	Slug           string
	Title          string
	Summary        string
	Kind           string
	Category       string
	Staleness      string
	Lifecycle      string
	CuratedRev     int
	UseCount       int
	HasProvisional bool
	UpdatedAt      string
}

BrowseRow is one entry as shown in the browse listing. Every field is read straight from the denormalized entry row (the curated head's title/summary/ category are kept in sync on promotion), so browsing never joins entry_version.

type CodeData

type CodeData struct {
	GrantID             int64
	ClientID            string
	RedirectURI         string
	CodeChallenge       string
	CodeChallengeMethod string
	Scope               string
	Resource            string
}

CodeData is the authorization-code record needed to validate a token exchange.

type Content

type Content struct {
	Title, Summary, Problem, Solution, Rationale, Caveats string
	Code                                                  []model.CodeSnippet
	Tags, Triggers, AppliesTo                             []string
	VerifiedAgainst                                       []model.VerifiedRef
}

Content is the writable content of an entry version.

type DiffResult

type DiffResult struct {
	Slug           string
	RevA, RevB     int
	StateA, StateB string
	Fields         []FieldDiff
}

DiffResult is the field-by-field diff of two revisions of an entry.

type EmbedTarget

type EmbedTarget struct {
	VersionID int64
	Text      string // title + summary + problem + solution
}

EmbedTarget is a version that needs an embedding computed.

type FieldDiff

type FieldDiff struct {
	Field   string
	Changed bool
	A, B    string
}

FieldDiff is one field's before/after in a version diff.

type HumanCred

type HumanCred struct {
	ActorID int64
	Name    string
	PwHash  string
}

HumanCred carries a human actor's login credential.

type HumanUser

type HumanUser struct {
	ActorID   int64
	Name      string
	CreatedAt string
}

HumanUser is a row for `ken user list`.

type ImportInput

type ImportInput struct {
	Slug       string
	Kind       string
	Content    Content
	ChangeNote string
	Links      []LinkInput
}

ImportInput migrates a flat-memory file into Ken as a curated rev-1 entry.

type LinkInput

type LinkInput struct{ ToSlug, LinkType string }

LinkInput is a [[wikilink]] to create from the new entry.

type NewAuthCode

type NewAuthCode struct {
	ClientID            string
	ConnectorActorID    int64
	HumanActorID        int64
	RedirectURI         string
	CodeChallenge       string
	CodeChallengeMethod string
	Scope               string
	Resource            string
}

NewAuthCode is the input to CreateOAuthGrantAndCode: everything captured at the consent step. ConnectorActorID is the 'ai' actor that will author MCP writes; HumanActorID is the curator who approved.

type OAuthClient

type OAuthClient struct {
	ClientID     string
	Name         string
	RedirectURIs []string
}

OAuthClient is a registered public client (PKCE; no secret).

type OAuthGrantRow

type OAuthGrantRow struct {
	ID           int64
	ClientName   string
	ApprovedBy   string
	Scope        string
	CreatedAt    string
	ActiveTokens int
}

OAuthGrantRow is one live connector grant, for the curator UI.

type OAuthPrincipal

type OAuthPrincipal struct {
	ActorID  int64
	GrantID  int64
	Scope    string
	Resource string
}

OAuthPrincipal is the resolved identity behind a valid OAuth access token.

type Patch

type Patch struct {
	Title, Summary, Problem, Solution, Rationale, Caveats *string
	Code                                                  *[]model.CodeSnippet
	Tags, Triggers, AppliesTo                             *[]string
	VerifiedAgainst                                       *[]model.VerifiedRef
}

Patch carries the fields to change in an enhancement; nil fields inherit from the based-on version (so an enhancement need only send what it changes).

type PromoteInput

type PromoteInput struct {
	Slug      string
	VersionID int64
	ActorID   int64
	ActorKind string
	Note      string
	// CurationLangs, when non-empty, enforces the comprehension gate: a version
	// whose detected content language is not one of these is refused with
	// ErrForeignLang. Empty ⇒ no language restriction (feature off). The web
	// handler fills it from the live settings snapshot.
	CurationLangs []string
}

PromoteInput promotes a proposed version to the curated head (the curation gate).

type ProposalRow

type ProposalRow struct {
	Slug             string
	Title            string
	Kind             string
	NProposals       int
	LatestRev        int
	LatestVersionID  int64
	LatestConfidence float64
	LatestChangeNote string
	LatestLang       string // detected content language of the latest proposal ("" ⇒ undetected/legacy)
	// LatestViaComm marks the latest proposal as possibly SECOND-HAND: the token
	// that authored it had recently received an inter-session message
	// (docs/COMM.md §7). False means "no signal", never "known first-hand" — it is
	// a prompt to ask for a citation, not a verdict.
	LatestViaComm bool
}

ProposalRow summarizes an entry that has pending proposals (the review queue).

type ProposeInput

type ProposeInput struct {
	Slug          string
	BasedOnRev    int // 0 => base on the current curated head
	ChangeNote    string
	Confidence    float64
	AuthorActorID int64
	AuthorKind    string
	SessionID     string
	Patch         Patch
	// ViaComm — see SaveInput.ViaComm.
	ViaComm bool
}

ProposeInput appends an enhancement to an existing entry.

type ProposeResult

type ProposeResult struct {
	Slug      string
	VersionID int64
	RevNo     int
	State     string
	Warning   string
}

ProposeResult reports the appended version and any rebase warning.

type RecentEntry

type RecentEntry struct {
	Slug, Title, Summary, Kind, LastEvent, LastAt string
}

RecentEntry is one row of the recent-activity briefing.

type RefreshResult

type RefreshResult struct {
	GrantID  int64
	Scope    string
	Resource string
}

RefreshResult reports the grant a rotated refresh belongs to.

type ReviewData

type ReviewData struct {
	Slug          string
	EntryTitle    string
	ProposalVID   int64
	ProposalRev   int
	ProposalState string
	ChangeNote    string
	// ViaComm marks this proposal as possibly second-hand (docs/COMM.md §7). It
	// belongs here, not only on the review-queue listing, because THIS is the view
	// that carries the Promote button — a hearsay warning that never reaches the
	// moment of promotion is not a mitigation.
	ViaComm    bool
	Proposal   Content
	HasCurated bool
	CuratedRev int
	Curated    Content
}

ReviewData is the material for a proposal diff view: the proposal content alongside the current curated head content.

type SaveInput

type SaveInput struct {
	Slug          string // optional; derived from the title if empty
	Kind          string
	Category      string
	Content       Content
	Confidence    float64
	AuthorActorID int64 // 0 => NULL (e.g. the dev token)
	AuthorKind    string
	SessionID     string
	Links         []LinkInput
	// ViaComm marks the version as possibly second-hand: the authoring token had
	// recently RECEIVED an inter-session message (docs/COMM.md §7). It is a prompt
	// for the curator's judgement, not a verdict — false means "no signal", never
	// "known first-hand".
	ViaComm bool
}

SaveInput creates a new draft entry with its first (proposed) version.

type SaveResult

type SaveResult struct {
	Slug      string
	EntryID   int64
	VersionID int64
	RevNo     int
	Lifecycle string
	State     string
}

SaveResult reports the created entry.

type SearchOpts

type SearchOpts struct {
	Kind       string
	Category   string
	Scope      string // curated (default) | proposals | history | all
	K          int
	Offset     int
	QueryVec   []float32 // optional; when set, adds a semantic (vector) arm
	EmbedModel string    // model id of QueryVec; only vectors from this model are compared
}

SearchOpts filters a kb_search query.

type Session

type Session struct {
	ID        string
	ActorID   int64
	ActorName string
	CSRF      string
	ExpiresAt string
}

Session is a human web-login session (server-side).

type Station added in v1.4.2

type Station struct {
	StationID          string
	SpaceID            int64
	Name               string // human-typed; never agent-supplied
	Purpose            string
	SelfDescribedAbout string   // a CLAIM (S8) — the field name carries that
	SelfDescribedTags  []string // ditto
	Published          bool
	State              string // active | archived
	CreatedAt          string
	AdvertisedAt       string
	LastActivityAt     string
}

Station is a durable working identity.

type StationKey added in v1.4.2

type StationKey struct {
	TokenID, StationID, Label, CreatedAt, LastUsedAt, RetiredAt, RevokedAt string
}

StationKey is a row for the console's key list.

type StationLockerEntry added in v1.4.2

type StationLockerEntry struct {
	Name        string
	SizeBytes   int
	SHA256      string
	ContentType string
	UpdatedAt   string
	Bytes       []byte // only populated by GetStationLockerBlob
}

StationLockerEntry is one stored file. Bytes are omitted from listings.

type StationLockerLimits added in v1.4.2

type StationLockerLimits struct {
	MaxBlobBytes  int // 256 KiB
	MaxTotalBytes int // 2 MiB per station
}

StationLockerLimits are §9's numbers.

func DefaultStationLockerLimits added in v1.4.2

func DefaultStationLockerLimits() StationLockerLimits

DefaultStationLockerLimits are §9's numbers.

type StationNote added in v1.4.2

type StationNote struct {
	Key            string
	Title          string
	Tags           []string
	Body           string
	Rev            int
	Bytes          int
	UpdatedAt      string
	UpdatedByToken string
	HearsayAtWrite bool
}

StationNote is one page. Provenance is ken.db facts only — never an endpoint id, which is guaranteed to dangle once the COMM sweep runs and does not exist with COMM off (S7).

type StationNoteLimits added in v1.4.2

type StationNoteLimits struct {
	MaxPageBytes     int // 64 KiB — larger than this is a document, not a note
	MaxRevisionBytes int // 256 KiB per page of history: an undo buffer, not an archive
	MaxNotebookBytes int // 4 MiB of HEAD revisions; history is bounded separately
}

StationNoteLimits are §9's numbers. Each is a BACKUP decision: every byte lands in the live database plus fourteen nightlies plus Litestream, so a cap is really cap × ~15.

func DefaultStationNoteLimits added in v1.4.2

func DefaultStationNoteLimits() StationNoteLimits

DefaultStationNoteLimits are §9's numbers.

type StationPrincipal added in v1.4.2

type StationPrincipal struct {
	TokenID   string
	ActorID   int64
	StationID string // empty = a station-less key: station_request and nothing else
	Scopes    []string
}

StationPrincipal is what a verified station key resolves to.

type StationRequestRow added in v1.4.2

type StationRequestRow struct {
	RequestID, Kind, NameHint, Purpose, Reason, CreatedAt string
	PromptedByPeerTraffic                                 bool
}

StationRequestRow is a pending ask awaiting a human decision.

type StationTask added in v1.4.2

type StationTask struct {
	TaskID           string
	StationID        string
	StationName      string // filled by the cross-station view
	Text             string
	Detail           string
	Context          string
	BlockedOn        string // self | human | peer
	BlockedOnStation string
	RemindAfter      string
	State            string // open | done | dropped
	Resolution       string
	ResolutionLink   string
	CreatedAt        string
	HearsayAtWrite   bool
	LastBriefedAt    string
	BriefedCount     int
	DeferredUntil    string
	DeferCount       int
	LastDeferReason  string
	ClosedAt         string
}

StationTask is one row. `blocked_on` is the field that earns its place: it turns the end-of-session guess ("two things waiting on you") into a query.

type StationTaskLimits added in v1.4.2

type StationTaskLimits struct {
	MaxOpen        int // refuse a new task past this (§9: 500)
	MaxTextBytes   int // one line, by construction (§9: 512)
	MaxDetailBytes int // detail + context (§9: 4 KiB)
	ListLimit      int // default AND hard ceiling (§11.5: 50)
	// BriefStampThrottleSec approximates "at most once per staffing session" without a
	// session table: a task the briefing displayed within this window is shown again but
	// NOT re-stamped, so `station_me` called repeatedly cannot advance the aging clock.
	BriefStampThrottleSec int
}

StationTaskLimits bounds the list. Values are settings in the shipped product; the defaults here match §9, whose reason is a BACKUP argument: every byte lands in the live database plus fourteen nightlies plus Litestream.

func DefaultStationTaskLimits added in v1.4.2

func DefaultStationTaskLimits() StationTaskLimits

DefaultStationTaskLimits are §9's numbers.

type Store

type Store struct {
	W *sql.DB // single-writer pool (MaxOpenConns == 1) — serializes all writes
	R *sql.DB // reader pool
	// contains filtered or unexported fields
}

Store holds the writer and reader connection pools over one SQLite file.

func Open

func Open(path string) (*Store, error)

Open opens (creating if needed) the SQLite database at path.

func (*Store) AddStationTask added in v1.4.2

func (s *Store) AddStationTask(ctx context.Context, lim StationTaskLimits, t StationTask, tokenID string, actorID int64, hearsay bool) (*StationTask, []StationTask, error)

AddStationTask records a commitment. `blockedOn` is REQUIRED: it is a three-value enum costing one token, and making it optional would put an unstated default into the human's only cross-station view (§11.3).

It returns the new task plus NEAR-MATCHES from the open set, in the same result and at zero extra call cost — a model that just re-created something is told immediately rather than discovering it three weeks later (§11.5).

func (*Store) ArchiveStation added in v1.4.2

func (s *Store) ArchiveStation(ctx context.Context, stationID string, archived bool) error

ArchiveStation is reversible (S3/§10): assets are kept, links go dormant rather than revoked so unarchiving restores them, and the NAME is held — releasing it is a separate act that makes the archive irreversible, because a released name can be taken by a new station.

func (*Store) AuthenticateStationKey added in v1.4.2

func (s *Store) AuthenticateStationKey(ctx context.Context, presented string) (*StationPrincipal, error)

AuthenticateStationKey verifies a `kens_<id>_<secret>` credential.

Retired and revoked keys are both refused here, and indistinguishably from an unknown one — extending COMM's unprobeability house rule (§5). The one place a caller learns WHY it was cut off is after its endpoint secret has already verified (S6), which informs a proven holder and tells a prober nothing.

func (*Store) BriefStationTasks added in v1.4.2

func (s *Store) BriefStationTasks(ctx context.Context, lim StationTaskLimits, stationID string) (*TaskBriefing, error)

BriefStationTasks builds the briefing AND performs the only stamping in the system.

The head has FIXED SLOTS — up to 2 due, 2 human-blocked, 3 aging — because classes 1 and 2 are monotonic (a passed date never un-passes; the human-blocked pile is by definition the one not being cleared), so a pure rank order lets them hold the head forever and the aging clause never runs. Silence, the cheapest human response, must not be able to pin an item at rank 1 and freeze everything beneath it (§11.5).

Only the rows actually returned are stamped, and only if they were not stamped inside the throttle window — so `station_me` called repeatedly cannot advance the clock (§11.4).

func (*Store) Close

func (s *Store) Close() error

Close closes both connection pools.

func (*Store) CloseStationTasks added in v1.4.2

func (s *Store) CloseStationTasks(ctx context.Context, stationID string, taskIDs []string, resolution, link string, actorID int64) (int, error)

CloseStationTasks is the cheapest verb, and takes several ids because closing a batch after a release is the common case — five calls is five chances not to bother (§11.6).

func (*Store) CountActiveOAuthGrants

func (s *Store) CountActiveOAuthGrants(ctx context.Context) (int, error)

CountActiveOAuthGrants counts live connector grants (for the dashboard stat).

func (*Store) CountActiveTokens

func (s *Store) CountActiveTokens(ctx context.Context) (int, error)

CountActiveTokens returns the number of non-revoked agent API tokens.

func (*Store) CountEntries

func (s *Store) CountEntries(ctx context.Context) (int, error)

CountEntries returns the number of knowledge-base entries (curated or draft).

func (*Store) CountHumanUsers

func (s *Store) CountHumanUsers(ctx context.Context) (int, error)

CountHumanUsers returns the number of human (login) actors; 0 drives the first-run setup wizard.

func (*Store) CountProposals added in v1.2.0

func (s *Store) CountProposals(ctx context.Context) (int, error)

CountProposals returns how many entries have at least one proposed version — the same population ListProposals returns, counted cheaply. It backs the Proposals page's live auto-refresh: a curator who keeps that page open should see a new proposal appear without a manual reload, and polling a bare count is far lighter than re-running the full listing query on a timer.

func (*Store) CountVersions

func (s *Store) CountVersions(ctx context.Context) (int, error)

CountVersions returns the number of entry versions (the append-only history, which only ever grows).

func (*Store) CreateFirstAdmin

func (s *Store) CreateFirstAdmin(ctx context.Context, name, pwHash string) (bool, error)

CreateFirstAdmin atomically creates the initial human admin ONLY if no human user exists yet (the first-run wizard). Returns created=false (no error) if one already exists — a single INSERT...WHERE NOT EXISTS that closes the check-then- insert TOCTOU a separate SELECT+INSERT would leave open (two concurrent /setup posts can't both create an admin).

func (*Store) CreateHumanUser

func (s *Store) CreateHumanUser(ctx context.Context, name, pwHash string) (int64, error)

CreateHumanUser creates a human actor with an Argon2id password hash.

func (*Store) CreateOAuthGrantAndCode

func (s *Store) CreateOAuthGrantAndCode(ctx context.Context, in NewAuthCode, codeTTL time.Duration) (string, error)

CreateOAuthGrantAndCode records the human's approval as a durable grant and a single-use authorization code (hashed), returning the plaintext code once.

func (*Store) CreateSession

func (s *Store) CreateSession(ctx context.Context, actorID int64, ttl time.Duration) (*Session, error)

CreateSession creates a session for actorID with the given TTL and a fresh CSRF token (rotated per login).

func (*Store) CreateStation added in v1.4.2

func (s *Store) CreateStation(ctx context.Context, spaceID int64, name, purpose string, actorID int64) (*Station, error)

CreateStation creates a station with a HUMAN-supplied name. There is deliberately no agent-reachable path to this function: an agent files a station request and a human approves it, typing the name at that moment (S3).

func (*Store) CreateStationRequest added in v1.4.2

func (s *Store) CreateStationRequest(ctx context.Context, spaceID int64, tokenID, fromStation, nameHint, purpose string) (string, error)

CreateStationRequest files an agent's ask for a human decision (S3/S9). The reason and purpose are shown ONLY to the human: nothing here is delivered to a target station before approval, because a request that reached its target would be a one-shot unauthorized message channel.

func (*Store) CrossStationHumanTasks added in v1.4.2

func (s *Store) CrossStationHumanTasks(ctx context.Context, spaceID int64, blockedOn string, limit int) ([]StationTask, error)

CrossStationHumanTasks answers the HUMAN's question — "what is everyone waiting on me for?" — which per-station lists do not (§11.8). Ordered by the same §11.5 contract applied across stations, never by recent station activity: ordering the whole-pile view by recency would sink the old items on the one surface built to stop that.

func (*Store) DeferStationTask added in v1.4.2

func (s *Store) DeferStationTask(ctx context.Context, stationID, taskID, until, reason string) error

DeferStationTask is deliberately the wordiest verb: a date AND a reason, and it leaves a counted trace. Deferring is legitimate; deferring silently and repeatedly is the failure mode (§11.6).

func (*Store) DeleteExpiredSessions

func (s *Store) DeleteExpiredSessions(ctx context.Context) (int64, error)

DeleteExpiredSessions purges sessions past their expiry; returns the count.

func (*Store) DeleteSession

func (s *Store) DeleteSession(ctx context.Context, id string) error

DeleteSession removes a session (logout). Takes the RAW cookie value.

func (*Store) DeleteStationLockerBlob added in v1.4.2

func (s *Store) DeleteStationLockerBlob(ctx context.Context, stationID, name string) error

DeleteStationLockerBlob removes one file.

func (*Store) DistinctCategories

func (s *Store) DistinctCategories(ctx context.Context) ([]string, error)

DistinctCategories returns the non-empty categories present on non-archived entries, alphabetically — the option list for the browse category filter.

func (*Store) DropStationTasks added in v1.4.2

func (s *Store) DropStationTasks(ctx context.Context, stationID string, taskIDs []string, reason string, humanDecided bool, actorID int64) (int, error)

DropStationTasks abandons tasks. It REFUSES a `blocked_on: human` task unless the caller carries the human's own decision — without that guard the nag would aim the model's one destructive verb squarely at the pile the feature exists to preserve (§11.6).

func (*Store) EmbeddingStats

func (s *Store) EmbeddingStats(ctx context.Context) (embedded, total int, err error)

EmbeddingStats reports embedded vs total version counts.

func (*Store) ExchangeOAuthCode

func (s *Store) ExchangeOAuthCode(ctx context.Context, code string, grantID int64, accessTTL, refreshTTL time.Duration) (access, refresh string, err error)

ExchangeOAuthCode atomically consumes the code (single-use) and issues a new access + refresh token pair under its grant, returning both plaintexts once. The DELETE row-count is the double-spend guard: if the code was already used (or expired) between Peek and here, RowsAffected is 0 and this returns ErrOAuthBadCode without issuing anything.

func (*Store) FindOrCreateActor

func (s *Store) FindOrCreateActor(ctx context.Context, kind, name string) (int64, error)

FindOrCreateActor returns the id of an actor with the given kind + display name, creating it if absent.

func (*Store) FirstHumanActor added in v1.4.2

func (s *Store) FirstHumanActor(ctx context.Context) (int64, error)

FirstHumanActor returns the earliest human actor, so CLI commands have a sane default.

func (*Store) FlagStale

func (s *Store) FlagStale(ctx context.Context, slug, reason string, actorID int64, actorKind string) (string, error)

FlagStale marks an entry stale (still authoritative, ranks lower) and records the concern. Raising a concern is safe/additive; asserting freshness is not.

func (*Store) Get

func (s *Store) Get(ctx context.Context, slugs []string, detailed bool) (entries []model.Entry, missing []string, err error)

Get returns full entries for the given slugs (curated head, or the provisional version for an uncurated draft). Unknown slugs are returned in missing. Each found entry bumps use_count. detailed adds provenance.

func (*Store) GetEntry

func (s *Store) GetEntry(ctx context.Context, slug string) (*model.Entry, error)

GetEntry returns one entry without bumping use_count (for the human web UI).

func (*Store) GetSettings

func (s *Store) GetSettings(ctx context.Context) (map[string]string, error)

GetSettings returns all operator-set setting overrides (key -> value). An empty map means "all defaults".

func (*Store) GetStationLockerBlob added in v1.4.2

func (s *Store) GetStationLockerBlob(ctx context.Context, stationID, name string) (*StationLockerEntry, error)

GetStationLockerBlob returns one file's bytes.

func (*Store) HandoffStaleness added in v1.4.2

func (s *Store) HandoffStaleness(ctx context.Context, stationID string) (writtenAt string, activitiesSince int, err error)

HandoffStaleness reports how stale the handoff page is, measured in STATION ACTIVITY rather than the wall clock (§4): an idle station is never stale, a busy one goes stale fast. Activity is counted from ken.db facts only — tasks touched and pages edited since the handoff was last written — never messages, which live in the expendable file and may be absent entirely.

func (*Store) History

func (s *Store) History(ctx context.Context, slug string) ([]VersionRow, error)

History returns all versions of an entry, newest rev first.

func (*Store) HumanByName

func (s *Store) HumanByName(ctx context.Context, name string) (*HumanCred, error)

HumanByName returns the login credential for a human actor (web login).

func (*Store) ImportEntry

func (s *Store) ImportEntry(ctx context.Context, in ImportInput) (created bool, err error)

ImportEntry inserts an imported entry directly as curated (lifecycle 'active', one 'curated' version rev 1, author_kind 'import') — imported memories are already curated knowledge, so they bypass the proposal queue. Idempotent: returns created=false without changes if the slug already exists.

func (*Store) IssueStationKey added in v1.4.2

func (s *Store) IssueStationKey(ctx context.Context, actorID int64, stationID, label string, scopes []string) (string, error)

IssueStationKey mints a `kens_`-prefixed key. stationID may be empty: such a key can call exactly one tool, station_request, which is how a session with no station asks for one (S3).

actorID must be the SAME actor as that machine's comm token, because the hearsay window is keyed on the actor — a different actor silently defeats prompted_by_peer_traffic, and a marker that fails open without saying so is worse than no marker (S5). The caller enforces that; this function records what it is told.

func (*Store) IssueToken

func (s *Store) IssueToken(ctx context.Context, actorID int64, scopes []string, label string) (string, error)

IssueToken creates an API token for actorID and returns the full token string (ken_<id>_<secret>) exactly once; only SHA-256(secret) is persisted.

func (*Store) ListEntries

func (s *Store) ListEntries(ctx context.Context, f BrowseFilter) ([]BrowseRow, bool, error)

ListEntries returns a filtered, sorted, paginated page of entries plus a has-more flag (it over-fetches one row so an exact-limit final page is not reported as "more" — the same technique as SearchPage). Archived entries are always excluded; a blank Lifecycle filter still shows draft/active/deprecated.

func (*Store) ListHumanUsers

func (s *Store) ListHumanUsers(ctx context.Context) ([]HumanUser, error)

ListHumanUsers lists human (login) actors.

func (*Store) ListOAuthGrants

func (s *Store) ListOAuthGrants(ctx context.Context) ([]OAuthGrantRow, error)

ListOAuthGrants returns live (non-revoked) grants, newest first.

func (*Store) ListProposals

func (s *Store) ListProposals(ctx context.Context) ([]ProposalRow, error)

ListProposals returns entries with at least one proposed version, newest first.

func (*Store) ListStationKeys added in v1.4.2

func (s *Store) ListStationKeys(ctx context.Context, stationID string) ([]StationKey, error)

ListStationKeys lists a station's keys for the console, including retired and revoked ones — a key nobody uses should be visible before it is a problem (§8).

func (*Store) ListStationLocker added in v1.4.2

func (s *Store) ListStationLocker(ctx context.Context, stationID string) ([]StationLockerEntry, error)

ListStationLocker returns metadata only — names, sizes and digests. A caller that wants bytes asks for one file.

func (*Store) ListStationNotes added in v1.4.2

func (s *Store) ListStationNotes(ctx context.Context, stationID string) ([]StationNote, error)

ListStationNotes returns page metadata and SIZES but never bodies — the AI pays to read, so the list exists to let it choose what is worth a second call.

func (*Store) ListStationTasks added in v1.4.2

func (s *Store) ListStationTasks(ctx context.Context, lim StationTaskLimits, stationID, state, blockedOn string, limit int) ([]StationTask, int, error)

ListStationTasks is a PURE QUERY and stamps nothing (§11.4). A model checking its own list three times must not silently demote items nobody was told about.

func (*Store) ListStations added in v1.4.2

func (s *Store) ListStations(ctx context.Context, spaceID int64) ([]Station, error)

ListStations returns every station in a space, newest activity first. Console-facing: what an AGENT may see is narrower (published stations plus its own links, §5).

func (*Store) ListTokens

func (s *Store) ListTokens(ctx context.Context) ([]TokenRow, error)

ListTokens lists all API tokens, newest first.

func (*Store) Migrate

func (s *Store) Migrate() error

Migrate applies embedded migrations in lexical order, skipping versions already recorded in schema_migration. It is idempotent. All migrations are plain SQL (no loadable extensions), so all apply unconditionally; the embeddings table is created empty and only populated when a provider is configured.

func (*Store) OAuthClientByID

func (s *Store) OAuthClientByID(ctx context.Context, clientID string) (*OAuthClient, error)

OAuthClientByID returns the registered client or ErrOAuthNoClient.

func (*Store) PeekOAuthCode

func (s *Store) PeekOAuthCode(ctx context.Context, code string) (*CodeData, error)

PeekOAuthCode reads (without consuming) a non-expired authorization code so the token endpoint can verify client_id, redirect_uri, and PKCE before committing. ErrOAuthBadCode if missing or expired. Consumption happens in ExchangeOAuthCode.

func (*Store) PendingStationRequests added in v1.4.2

func (s *Store) PendingStationRequests(ctx context.Context, spaceID int64) ([]StationRequestRow, error)

PendingStationRequests lists what is waiting on the human.

func (*Store) Promote

func (s *Store) Promote(ctx context.Context, in PromoteInput) error

Promote is the only operation that moves the curated head. In one IMMEDIATE transaction, guarded by the proposal's state='proposed' check (see below), it supersedes the old head, marks the proposal curated, advances the head, resets staleness, and refreshes the denormalized ranking/browse surface. A duplicate or stale promote returns ErrBadVersion from that state check — reconcile, don't clobber. (lock_version is still bumped for auditing but is no longer a guard.)

func (*Store) PromoteStationNote added in v1.4.2

func (s *Store) PromoteStationNote(ctx context.Context, stationID, key string) (string, error)

PromoteStationNote opens a PENDING PROMOTION for the human to convert. It writes no curated row, calls no kb_* tool, and requires no knowledge-base scope (S10).

func (*Store) ProposalReview

func (s *Store) ProposalReview(ctx context.Context, versionID int64) (*ReviewData, error)

ProposalReview loads a version and its entry's curated head for a diff view.

func (*Store) ProposeEnhancement

func (s *Store) ProposeEnhancement(ctx context.Context, in ProposeInput) (ProposeResult, error)

ProposeEnhancement appends an immutable 'proposed' version. It never moves the curated head — knowledge is persisted the instant it is proposed.

func (*Store) ProvisionalReview

func (s *Store) ProvisionalReview(ctx context.Context, slug string) (*ReviewData, error)

ProvisionalReview returns the review material for an entry's pending proposal (its provisional version), or (nil, nil) when the entry has none.

func (*Store) PurgeExpiredOAuth

func (s *Store) PurgeExpiredOAuth(ctx context.Context) error

PurgeExpiredOAuth deletes spent authorization codes and long-expired tokens. Best-effort housekeeping; safe to call periodically.

func (*Store) PutStationLockerBlob added in v1.4.2

func (s *Store) PutStationLockerBlob(ctx context.Context, lim StationLockerLimits, stationID, name string,
	body []byte, contentType, tokenID string, actorID int64) (*StationLockerEntry, error)

PutStationLockerBlob stores or replaces a file.

func (*Store) ReadStationNote added in v1.4.2

func (s *Store) ReadStationNote(ctx context.Context, stationID, key string) (*StationNote, error)

ReadStationNote fetches one page, body included.

func (*Store) RecentContext

func (s *Store) RecentContext(ctx context.Context, sinceDays, limit int, kind string) ([]RecentEntry, error)

RecentContext returns entries with curation activity in the last sinceDays, newest first — a compact "what the KB learned recently" briefing.

func (*Store) RecordOutcome

func (s *Store) RecordOutcome(ctx context.Context, slug, outcome string, actorID int64, actorKind, sessionID, note string) (staleness string, err error)

RecordOutcome records an agent's outcome report for an entry. 'was-wrong' also flags the entry stale (for human review). Returns the entry's staleness after.

func (*Store) RegisterOAuthClient

func (s *Store) RegisterOAuthClient(ctx context.Context, name string, redirectURIs []string) (string, error)

RegisterOAuthClient mints a new public client_id for the given name + redirect-URI allowlist and returns it. The caller validates the redirect URIs (https or loopback) before calling.

func (*Store) Reject

func (s *Store) Reject(ctx context.Context, slug string, versionID, actorID int64, actorKind, note string) error

Reject marks a proposed version rejected (retained + searchable as a dead-end).

func (*Store) RenameStation added in v1.4.2

func (s *Store) RenameStation(ctx context.Context, stationID, name string) error

RenameStation / SetStationPublished / ArchiveStation are HUMAN-only operations, reachable from the console and the CLI and from no tool.

func (*Store) ReopenStationTasks added in v1.4.2

func (s *Store) ReopenStationTasks(ctx context.Context, stationID string, taskIDs []string, reason string) (int, error)

ReopenStationTasks exists because a decision to drop is sometimes wrong, and because a terminal state nobody can leave makes the record a dead end (§11.3).

func (*Store) Repromote

func (s *Store) Repromote(ctx context.Context, in PromoteInput) error

Repromote sets an EXISTING historical version (superseded/rejected/withdrawn — anything but the current head or a still-'proposed' version) back as the curated head. It is the human recovery path when promotions were applied in the wrong order and regressed the head; unlike Promote it does not require state='proposed' (proposed versions go through the normal Promote review). One IMMEDIATE tx.

func (*Store) RetireStationKey added in v1.4.2

func (s *Store) RetireStationKey(ctx context.Context, tokenID string) error

RetireStationKey stops the key binding NEW endpoints and leaves live ones alone — the graceful "I moved machines" path. Revocation is the other verb and it SEVERS; see RevokeToken plus the endpoint-severing pass in the comm layer (S6).

func (*Store) RevokeOAuthGrant

func (s *Store) RevokeOAuthGrant(ctx context.Context, id int64) error

RevokeOAuthGrant revokes a grant and all of its outstanding tokens in one tx. Idempotent: revoking an already-revoked grant is a no-op success.

func (*Store) RevokeToken

func (s *Store) RevokeToken(ctx context.Context, tokenID string) error

RevokeToken soft-revokes a token by id.

func (*Store) RotateOAuthRefresh

func (s *Store) RotateOAuthRefresh(ctx context.Context, refresh string, accessTTL, refreshTTL time.Duration) (access, newRefresh string, rr *RefreshResult, err error)

RotateOAuthRefresh validates a refresh token and, on success, revokes it and issues a fresh access + refresh pair (rotation). If a refresh token that has ALREADY been rotated (revoked) is presented, that is a theft signal: the whole grant is revoked and ErrOAuthReuseKill is returned. ErrOAuthBadToken for a missing/expired token or a revoked grant.

func (*Store) Save

func (s *Store) Save(ctx context.Context, in SaveInput) (SaveResult, error)

Save creates a new draft entry (lifecycle 'draft', one 'proposed' version).

func (*Store) Search

func (s *Store) Search(ctx context.Context, query string, opt SearchOpts) ([]model.SearchResult, error)

Search runs the hybrid keyword+vector search and returns the clamped page.

func (*Store) SearchPage

func (s *Store) SearchPage(ctx context.Context, query string, opt SearchOpts) ([]model.SearchResult, bool, error)

SearchPage is Search plus an accurate has-more flag. It over-fetches one row (K+1) so an exact-K final page isn't reported as "more" (a false positive).

func (*Store) SeedDemo

func (s *Store) SeedDemo(ctx context.Context) (string, error)

SeedDemo inserts (idempotently) one curated demo entry so the skeleton has something to return from kb_search / kb_get. Dev/smoke use only.

func (*Store) SessionByID

func (s *Store) SessionByID(ctx context.Context, id string) (*Session, error)

SessionByID returns a live (unexpired) session for the RAW cookie value, or ErrNotFound. The lookup is by hash (see sessionKey); the returned Session carries the raw id the caller passed in, so callers are unaffected by the storage change.

func (*Store) SetDetector

func (s *Store) SetDetector(d lang.Detector)

SetDetector overrides the content-language detector (used by tests to force a deterministic language without exercising whatlanggo).

func (*Store) SetSettings

func (s *Store) SetSettings(ctx context.Context, upsert map[string]string, remove []string, updater string) error

SetSettings applies setting changes in one transaction: it upserts each key/value in upsert (an empty value is a legitimate override, stored verbatim) and deletes each key in remove (reverting it to the default). Deletion is an explicit list, never inferred from an empty value.

func (*Store) SetStationPublished added in v1.4.2

func (s *Store) SetStationPublished(ctx context.Context, stationID string, published bool) error

func (*Store) SetStationSelfDescription added in v1.4.2

func (s *Store) SetStationSelfDescription(ctx context.Context, stationID, about string, tags []string) error

SetStationSelfDescription is the ONE station field an agent may write. It is stored in columns whose names say the value is a claim, so a reader that flattens the result still sees it marked (S8).

func (*Store) Snapshot

func (s *Store) Snapshot(ctx context.Context, dest string) error

Snapshot writes a consistent copy of the database to dest via VACUUM INTO — safe on a live WAL database (no torn file). dest must not already exist.

The result is chmod'd 0600 HERE, not left to the caller. A snapshot is a byte-complete copy of the knowledge base — every entry, the full curation history, curator accounts and token records — so its mode is part of writing it correctly, not a courtesy the caller may forget. SQLite creates the file at the process umask (0644 under the usual 0022), and callers that only *document* a safe umask leave every hand-run `ken backup snapshot --out …` world-readable: the shell wrappers cannot protect an operator following the runbook by hand.

The umask is also narrowed for the duration of the write (see the CLI), so the file is 0600 from creation rather than only once VACUUM INTO returns; this chmod is the backstop that holds no matter which caller invoked us.

func (*Store) StationByID added in v1.4.2

func (s *Store) StationByID(ctx context.Context, stationID string) (*Station, error)

StationByID resolves the opaque routing id — the only identifier anything outside this package should hold.

func (*Store) StationByName added in v1.4.2

func (s *Store) StationByName(ctx context.Context, spaceID int64, name string) (*Station, error)

StationByName resolves a display name within a space. For CONSOLE and CLI use only: a name is not an address, and no agent-facing path may route by it (S3).

func (*Store) StationTaskByID added in v1.4.2

func (s *Store) StationTaskByID(ctx context.Context, taskID string) (*StationTask, error)

StationTaskByID fetches one task.

func (*Store) TouchStationActivity added in v1.4.2

func (s *Store) TouchStationActivity(ctx context.Context, stationID string) error

TouchStationActivity stamps last_activity_at from ken.db facts only — a task touched or a page edited. Deliberately NOT messages: those live in the expendable file, and the console's cross-station ordering must not depend on a database that may be absent (S7, §11.8).

func (*Store) TouchToken

func (s *Store) TouchToken(ctx context.Context, tokenID string)

TouchToken updates last_used_at, throttled to at most ~once per minute per token, so the read path doesn't amplify into a write on every request.

func (*Store) UpsertEmbedding

func (s *Store) UpsertEmbedding(ctx context.Context, versionID int64, modelID string, vec []float32) error

UpsertEmbedding stores (or replaces) a version's embedding for a given model. The (version_id, model_id) primary key lets multiple models coexist per version; OR REPLACE upserts the row for this exact (version, model) pair only.

func (*Store) ValidateOAuthAccessToken

func (s *Store) ValidateOAuthAccessToken(ctx context.Context, token string) (*OAuthPrincipal, error)

ValidateOAuthAccessToken resolves an opaque access token to its principal, or ErrOAuthBadToken if it is unknown, expired, revoked, or its grant was revoked. Read-only (reader pool) so it never contends for the single writer.

func (*Store) VersionDiff

func (s *Store) VersionDiff(ctx context.Context, slug string, revA, revB int) (*DiffResult, error)

VersionDiff diffs two revisions of an entry, field by field.

func (*Store) VersionsNeedingEmbedding

func (s *Store) VersionsNeedingEmbedding(ctx context.Context, modelID string, limit int) ([]EmbedTarget, error)

VersionsNeedingEmbedding returns versions lacking an embedding for modelID (limit<=0 means all). Text is what the query vector is compared against.

func (*Store) WriteStationNote added in v1.4.2

func (s *Store) WriteStationNote(ctx context.Context, lim StationNoteLimits, stationID, key, title, body string,
	tags []string, mode string, ifRev int, tokenID string, actorID int64, hearsay bool) (*StationNote, error)

WriteStationNote appends to or replaces a page, creating a revision.

ifRev > 0 is an optimistic-concurrency precondition: the write is refused if the page moved underneath it, naming the current revision. Two sessions may staff one station, and without a precondition the second writer silently destroys the first's page.

type TaskBriefing added in v1.4.2

type TaskBriefing struct {
	Head            []StationTask // the fixed-slot head, already stamped
	OpenTotal       int
	BlockedOnHuman  int
	Overdue         int
	AgingCount      int // not briefed in the last N sessions
	StuckCount      int // briefed repeatedly, unchanged and never deferred
	RepeatedlyDefer int
	Remainder       int
}

TaskBriefing is what a session is handed when it staffs a station: named rows, not only counts. A task the human never hears named is a task that decayed.

type TokenRow

type TokenRow struct {
	TokenID, ActorName, Kind, Scopes, Label, CreatedAt, LastUsedAt, RevokedAt string
}

TokenRow is a row for `ken token list`.

type VersionRow

type VersionRow struct {
	VersionID  int64
	RevNo      int
	State      string
	ChangeNote string
	AuthorKind string
	Confidence float64
	CreatedAt  string
}

VersionRow is a row in an entry's version history.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL