store

package
v3.3.2 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2026 License: MIT Imports: 36 Imported by: 0

Documentation

Overview

Package store implements the persistent memory engine for Engram.

It uses SQLite with FTS5 full-text search to store and retrieve observations from AI coding sessions. This is the core of Engram — everything else (HTTP server, MCP server, CLI, plugins) talks to this.

Index

Constants

View Source
const (
	RelationPending       = "pending"
	RelationRelated       = "related"
	RelationCompatible    = "compatible"
	RelationScoped        = "scoped"
	RelationConflictsWith = "conflicts_with"
	RelationSupersedes    = "supersedes"
	RelationNotConflict   = "not_conflict"
)

Valid relation type values. Type compatibility is NOT enforced in Phase 1; the agent does that judgment.

View Source
const (
	JudgmentStatusPending  = "pending"
	JudgmentStatusJudged   = "judged"
	JudgmentStatusOrphaned = "orphaned"
	JudgmentStatusIgnored  = "ignored"
)

Valid judgment_status values.

View Source
const (
	SessionOwnershipShared       = "shared"
	SessionOwnershipProjectOwned = "project_owned"
)
View Source
const (
	ObservationStateActive      = "active"
	ObservationStateNeedsReview = "needs_review"
)
View Source
const (
	DefaultSyncTargetKey = "cloud"
	LocalChunkTargetKey  = "local"

	// SyncInboxTargetKey is the reserved sync target of the cloud inbox. It owns
	// its own sync_state row and never represents an enrollable project.
	SyncInboxTargetKey = "cloud:inbox"

	// ReservedInboxProjectName is the project name the cloud inbox owns. Enrolling
	// it is rejected because its cloud:<project> target key would collide with
	// SyncInboxTargetKey.
	ReservedInboxProjectName = "inbox"

	SyncLifecycleIdle     = "idle"
	SyncLifecyclePending  = "pending"
	SyncLifecycleRunning  = "running"
	SyncLifecycleHealthy  = "healthy"
	SyncLifecycleDegraded = "degraded"

	// SyncLifecycleInbox is the fixed lifecycle of the reserved cloud inbox
	// target. Lifecycle setters and refresh helpers must never transition it.
	SyncLifecycleInbox = "inbox"

	SyncEntitySession     = "session"
	SyncEntityObservation = "observation"
	SyncEntityPrompt      = "prompt"
	SyncEntityRelation    = "relation"

	SyncOpUpsert = "upsert"
	SyncOpDelete = "delete"

	SyncSourceLocal  = "local"
	SyncSourceRemote = "remote"

	SyncSessionIdentityInvalidReasonCode     = "sync_session_identity_invalid"
	SyncObservationIdentityInvalidReasonCode = "sync_observation_identity_invalid"
	SyncPromptIdentityInvalidReasonCode      = "sync_prompt_identity_invalid"
	SyncParentSessionMissingReasonCode       = "pulled_parent_session_missing"
)
View Source
const (
	SyncMutationDispositionPending     = "pending"
	SyncMutationDispositionQuarantined = "quarantined"
	SyncMutationDispositionSuperseded  = "superseded"

	SyncMutationSupersededReasonLocalEntityDeleted = "local_entity_deleted"
)
View Source
const (
	UpgradeStagePlanned           = "planned"
	UpgradeStageDoctorReady       = "doctor_ready"
	UpgradeStageDoctorBlocked     = "doctor_blocked"
	UpgradeStageRepairApplied     = "repair_applied"
	UpgradeStageBootstrapEnrolled = "bootstrap_enrolled"
	UpgradeStageBootstrapPushed   = "bootstrap_pushed"
	UpgradeStageBootstrapVerified = "bootstrap_verified"
	UpgradeStageRolledBack        = "rolled_back"

	UpgradeRepairClassNone       = "none"
	UpgradeRepairClassReady      = "ready"
	UpgradeRepairClassRepairable = "repairable"
	UpgradeRepairClassBlocked    = "blocked"
	UpgradeRepairClassPolicy     = "policy"
)
View Source
const (
	UpgradeReasonRepairableLegacyMutationPayload = "upgrade_repairable_legacy_mutation_payload"
	UpgradeReasonBlockedLegacyMutationManual     = "upgrade_blocked_legacy_mutation_manual"
)
View Source
const (
	// RescueBlockedOwnedByOtherProject means the row itself already belongs to a
	// project other than the target.
	RescueBlockedOwnedByOtherProject = "owned_by_other_project"
	// RescueBlockedSessionOwnedByOtherProject means the row is unowned but its
	// parent session belongs to another project, so moving it would split it.
	RescueBlockedSessionOwnedByOtherProject = "session_owned_by_other_project"
	// RescueBlockedDependentRecordOwnedByOtherProject means an unowned session
	// was left in place because it parents a record owned by another project.
	RescueBlockedDependentRecordOwnedByOtherProject = "dependent_record_owned_by_other_project"
	// RescueBlockedMissing means the requested row does not exist.
	RescueBlockedMissing = "missing"
)

Reasons a record or session was left behind by a rescue.

View Source
const DefaultObservationVersionPageSize = 50

DefaultObservationVersionPageSize is the bound applied to a single history page when the caller does not choose a limit; MCP include_history renders exactly this many most recent versions per call.

View Source
const DefaultScanLimit = 100

DefaultScanLimit bounds each conflict scan page and its FTS candidate queries.

View Source
const MaxObservationVersionPageSize = 100

MaxObservationVersionPageSize caps a caller-supplied history page so one bounded read stays resource-safe no matter how many snapshots are stored.

View Source
const RescueOwnershipCommand = "engram projects rescue-ownership"

RescueOwnershipCommand is the repair an operator can always run, whatever the server authorization configuration is: it reaches the local store directly and never goes through the HTTP endpoint. Every ownership error names it, so the failure carries its own remedy instead of pointing at a changelog.

Variables

View Source
var (
	ErrSessionNotFound             = errors.New("session not found")
	ErrSessionIDRequired           = errors.New("session id is required")
	ErrSessionAlreadyEnded         = errors.New("session has already ended")
	ErrSessionHasObservations      = errors.New("session still has observations")
	ErrSessionDeleteBlocked        = errors.New("session deletion is blocked while cloud sync enrollment is active")
	ErrObservationNotFound         = errors.New("observation not found")
	ErrPromptNotFound              = errors.New("prompt not found")
	ErrPromptInboxDeleted          = errors.New("prompt inbox identity was deleted")
	ErrProjectNotFound             = errors.New("project not found")
	ErrProjectRequired             = errors.New("project identity is required")
	ErrInvalidSessionOwnershipMode = errors.New("invalid session ownership mode")
	ErrSessionOwnershipMismatch    = errors.New("session ownership does not match write project")
	ErrProjectRescueInvalidRequest = errors.New("project rescue request is invalid")
	// ErrProjectOwnershipAmbiguous is returned when an unowned session cannot
	// adopt a write's project because it already parents records owned by a
	// different one. Guessing there would split a record from its session.
	ErrProjectOwnershipAmbiguous                = errors.New("session project ownership is ambiguous")
	ErrExpectedProjectRequired                  = errors.New("expected_project must be a valid non-empty project name")
	ErrObservationProjectMismatch               = errors.New("expected_project does not match observation owner")
	ErrObservationProjectImmutable              = errors.New("observation project cannot be reassigned")
	ErrObservationTitleRequired                 = errors.New("observation title is required")
	ErrObservationContentRequired               = errors.New("observation content is required")
	ErrObservationOperationConflict             = errors.New("observation operation id conflicts with a different payload")
	ErrObservationOperationExpired              = errors.New("observation operation id is outside the replay horizon")
	ErrObservationFindReplacePairRequired       = errors.New("find and replace must be provided together")
	ErrObservationFindReplaceContentConflict    = errors.New("find and replace cannot be combined with content")
	ErrObservationFindReplaceInputTooLarge      = errors.New("find and replace inputs exceed maximum observation length")
	ErrObservationFindReplaceResultTooLarge     = errors.New("replacement result exceeds maximum observation length")
	ErrObservationFindReplaceLegacyContentLarge = errors.New("cannot partially replace oversized legacy observation")
	ErrPromptContentRequired                    = errors.New("prompt content is required")
)

Sentinel errors returned by Store operations so callers can use errors.Is.

View Source
var (
	// ErrRelationFKMissing is returned by applyRelationUpsertTx when one or
	// both observations referenced by the relation payload do not exist locally
	// yet. The caller must write the mutation to sync_apply_deferred and ACK
	// the sequence so the cursor does not stall.
	ErrRelationFKMissing = errors.New("relation FK precondition not met: referenced observation missing")

	// ErrRelationProjectMismatch reports existing endpoints outside the relation's
	// effective project. It shares the missing-endpoint retry/rearm lifecycle.
	ErrRelationProjectMismatch = errors.New("relation FK precondition not met: referenced observation effective project mismatch")

	// ErrCrossProjectRelation is returned by JudgeRelation when the source and
	// target observations belong to different projects. The write is rejected
	// entirely; no memory_relations row is created and no sync mutation is
	// enqueued.
	ErrCrossProjectRelation = errors.New("relation rejected: source and target observations are in different projects")

	// ErrApplyDead is returned when a deferred relation payload cannot be
	// decoded or fails a hard validation. The row is written to
	// sync_apply_deferred with apply_status='dead' and is never retried
	// automatically; Phase 3 adds a republish CLI.
	ErrApplyDead = errors.New("relation apply permanently failed: payload invalid or undecodable")

	// ErrPulledSessionIdentityInvalid identifies an invalid identity after successful decoding and legacy fallback.
	ErrPulledSessionIdentityInvalid = errors.New("pulled session identity is invalid")
	// ErrPulledObservationIdentityInvalid identifies a pulled observation whose payload and mutation identities disagree.
	ErrPulledObservationIdentityInvalid = errors.New("pulled observation identity is invalid")
	// ErrPulledPromptIdentityInvalid identifies a pulled prompt delete with an unusable keyed identity.
	ErrPulledPromptIdentityInvalid = errors.New("pulled prompt identity is invalid")
	// ErrPulledSessionDirectoryInvalid identifies a pulled or imported session that
	// has no concrete directory and therefore cannot be admitted as cloud state.
	ErrPulledSessionDirectoryInvalid = errors.New("pulled session directory is invalid")
)

Sentinel errors for relation sync apply path (Phase 2).

View Source
var ErrDatabaseGenerationChanged = errors.New("Engram database generation changed; restart Engram")

ErrDatabaseGenerationChanged means Engram's SQLite files were replaced while this process was running. Restart Engram before accessing the store again.

View Source
var ErrPromptSourceConfirmation = errors.New("prompt source confirmation rejected")
View Source
var ErrRuntimeSessionScopeConflict = errors.New("runtime session scope conflict")

ErrRuntimeSessionScopeConflict refuses runtime-root reuse outside its binding.

View Source
var ErrSemanticPromptBuilderRequired = errors.New("semantic scan requires a non-nil BuildPrompt function")

ErrSemanticPromptBuilderRequired is returned by ScanProject when ScanOptions.Semantic is true but ScanOptions.BuildPrompt is nil.

View Source
var ErrSemanticRunnerRequired = errors.New("semantic scan requires a non-nil Runner")

ErrSemanticRunnerRequired is returned by ScanProject when ScanOptions.Semantic is true but ScanOptions.Runner is nil.

View Source
var ErrSessionIsolationConflict = errors.New("isolated session registration requires an empty directory; existing runtime-bound sessions cannot be reused")

ErrSessionIsolationConflict refuses reuse of a runtime-bound or shared identity as a satellite.

Functions

func ClassifyTool

func ClassifyTool(toolName string) string

ClassifyTool returns the observation type for a given tool name.

func EnsureInstanceID

func EnsureInstanceID(dataDir string) (string, error)

func ExtractLearnings

func ExtractLearnings(text string) []string

ExtractLearnings parses structured learning items from text. It looks for sections like "## Key Learnings:" or "## Aprendizajes Clave:" and extracts numbered (1. text) or bullet (- text) items. Returns learnings from the LAST matching section (most recent output).

func NormalizeProject

func NormalizeProject(project string) (normalized string, warning string)

NormalizeProject applies canonical project name normalization: Unicode NFC + lowercase + trim whitespace + collapse consecutive hyphens/underscores. Returns the normalized name and a warning message if the name was changed (empty string if no change was needed). Exported so MCP and CLI handlers can surface the warning to users.

func Now

func Now() string

Now returns the current time formatted for SQLite with persisted generation precision.

func SuggestTopicKey

func SuggestTopicKey(typ, title, content string) string

SuggestTopicKey generates a stable topic key suggestion from type/title/content. It infers a topic family (e.g. architecture/*, bug/*) and then appends a normalized segment from title/content for stable cross-session keys.

func ValidateCloudSessionDirectory added in v3.2.0

func ValidateCloudSessionDirectory(raw []byte) error

ValidateCloudSessionDirectory admits omitted or blank string directories for project-owned sessions, including isolated registrations. Shared and legacy sessions still require a concrete directory. Null and non-string values never stand in for a blank directory.

func ValidateExpectedProject

func ValidateExpectedProject(expected string) (string, error)

ValidateExpectedProject validates an explicit caller-owned assertion, never a default.

func ValidateObservationTitle

func ValidateObservationTitle(title string) error

ValidateObservationTitle is the one definition of "an observation has a usable title", shared by every write path so the CLI, MCP and HTTP entry points can reject a titleless write before they create a session or open a transaction. It mirrors what ValidateSyncMutationPayload requires of an observation upsert: cloud sync rejects a payload whose title is empty, and because the mutation queue is an ordered log, one rejected row blocks every later mutation for the same project.

Types

type AddObservationParams

type AddObservationParams struct {
	SessionID   string        `json:"session_id"`
	Type        string        `json:"type"`
	Title       string        `json:"title"`
	Content     string        `json:"content"`
	ToolName    string        `json:"tool_name,omitempty"`
	Project     string        `json:"project,omitempty"`
	Scope       string        `json:"scope,omitempty"`
	TopicKey    string        `json:"topic_key,omitempty"`
	OperationID string        `json:"operation_id,omitempty"`
	Timings     *WriteTimings `json:"-"`
}

type AddObservationResult added in v3.3.0

type AddObservationResult struct {
	ID       int64
	Replayed bool
}

AddObservationResult reports whether an observation save created a new logical result or returned an exact replay from the operation ledger.

type AddPromptParams

type AddPromptParams struct {
	SessionID     string `json:"session_id"`
	Content       string `json:"content"`
	Project       string `json:"project,omitempty"`
	SourceInboxID string `json:"source_inbox_id,omitempty"`
}

type BackupRelation

type BackupRelation struct {
	SyncID                     string   `json:"sync_id"`
	SourceID                   string   `json:"source_id"`
	TargetID                   string   `json:"target_id"`
	Relation                   string   `json:"relation"`
	Reason                     *string  `json:"reason,omitempty"`
	Evidence                   *string  `json:"evidence,omitempty"`
	Confidence                 *float64 `json:"confidence,omitempty"`
	JudgmentStatus             string   `json:"judgment_status"`
	MarkedByActor              *string  `json:"marked_by_actor,omitempty"`
	MarkedByKind               *string  `json:"marked_by_kind,omitempty"`
	MarkedByModel              *string  `json:"marked_by_model,omitempty"`
	SessionID                  *string  `json:"session_id,omitempty"`
	SupersededAt               *string  `json:"superseded_at,omitempty"`
	SupersededByRelationSyncID *string  `json:"superseded_by_relation_sync_id,omitempty"`
	CreatedAt                  string   `json:"created_at"`
	UpdatedAt                  string   `json:"updated_at"`
}

BackupRelation is the lossless direct-backup representation of a memory_relations row. Relation IDs are local SQLite keys, so supersession is represented by the referenced relation's portable sync ID instead.

type Candidate

type Candidate struct {
	// ID is the integer primary key of the candidate observation.
	ID int64
	// SyncID is the TEXT sync_id of the candidate observation.
	SyncID string
	// Title is the candidate's title.
	Title string
	// Type is the candidate's observation type.
	Type string
	// TopicKey is the candidate's topic_key (may be nil).
	TopicKey *string
	// Score is the FTS5 BM25 rank; lower values are better matches.
	Score float64
	// JudgmentID is the sync_id of the pending memory_relations row created
	// for this (source, candidate) pair.
	JudgmentID string
}

Candidate represents a potential conflict candidate surfaced by FindCandidates.

type CandidateOptions

type CandidateOptions struct {
	// Project filters candidates to the same project as the saved observation.
	Project string
	// Scope filters candidates to the same scope as the saved observation.
	Scope string
	// Type is reserved for Phase 2 type-compatibility filtering; NOT enforced Phase 1.
	Type string
	// Limit caps the number of candidates returned. Default 3 when nil or <=0.
	Limit int
	// BM25MaxRank is the largest acceptable raw FTS5 BM25 rank. Smaller ranks
	// are better matches, so candidates whose rank is greater are excluded. nil
	// uses 0.0, which retains ordinary negative FTS5 ranks.
	BM25MaxRank *float64
	// BM25Floor preserves the deprecated legacy minimum-rank behavior. Candidates
	// below this value are excluded. It cannot be combined with BM25MaxRank.
	BM25Floor *float64
	// Query optionally overrides the saved observation title as the candidate
	// query source. Empty uses the saved title.
	Query string
	// RequireSaveRelevance opts into title/topic relevance filtering before the
	// candidate limit and pending relation inserts. MCP saves enable it; broad
	// recall scans and other callers retain their existing behavior by default.
	RequireSaveRelevance bool
	// SkipInsert controls whether FindCandidates inserts pending relation rows.
	// When true, candidates are returned but NO rows are written to memory_relations.
	// Default false preserves the existing behavior (rows are inserted).
	SkipInsert bool
	// Timings optionally receives per-call diagnostic durations. Use a distinct
	// pointer for each concurrent call. Nil performs no timing instrumentation.
	Timings *CandidateTimings
}

CandidateOptions controls the FindCandidates query.

type CandidateTimings added in v3.2.0

type CandidateTimings struct {
	Lookup  time.Duration
	Inserts time.Duration
	// RowsRead counts result rows consumed by Go, not SQLite FTS index work.
	RowsRead int
}

CandidateTimings separates candidate lookup (including row consumption and closure) from pending-relation insertion. Durations include database waits; they are not CPU times or separate SQLite lock-wait measurements.

type CloudSyncSummary

type CloudSyncSummary struct {
	LastSuccessAt    string
	PendingMutations int64
	LastError        string
	ReasonCode       string
}

CloudSyncSummary is the aggregate local cloud state shown when no project is selected.

type CloudUpgradeLegacyMutationFinding

type CloudUpgradeLegacyMutationFinding struct {
	Seq        int64  `json:"seq"`
	Entity     string `json:"entity"`
	Op         string `json:"op"`
	ReasonCode string `json:"reason_code"`
	Message    string `json:"message"`
	Repairable bool   `json:"repairable"`
	RepairHint string `json:"repair_hint,omitempty"`
	EntityKey  string `json:"entity_key,omitempty"`
	TargetKey  string `json:"target_key,omitempty"`
	Project    string `json:"project,omitempty"`
}

type CloudUpgradeLegacyMutationReport

type CloudUpgradeLegacyMutationReport struct {
	Project         string                              `json:"project"`
	RepairableCount int                                 `json:"repairable_count"`
	BlockedCount    int                                 `json:"blocked_count"`
	Findings        []CloudUpgradeLegacyMutationFinding `json:"findings,omitempty"`
}

type CloudUpgradeRepairReport

type CloudUpgradeRepairReport struct {
	Class         string `json:"class"`
	ReasonCode    string `json:"reason_code"`
	Message       string `json:"message"`
	PlannedAction string `json:"planned_action,omitempty"`
	Applied       bool   `json:"applied"`
}

type CloudUpgradeSnapshot

type CloudUpgradeSnapshot struct {
	Captured        bool `json:"captured"`
	ProjectEnrolled bool `json:"project_enrolled"`
}

type CloudUpgradeState

type CloudUpgradeState struct {
	Project          string               `json:"project"`
	Stage            string               `json:"stage"`
	RepairClass      string               `json:"repair_class"`
	Snapshot         CloudUpgradeSnapshot `json:"snapshot"`
	LastErrorCode    string               `json:"last_error_code,omitempty"`
	LastErrorMessage string               `json:"last_error_message,omitempty"`
	FindingsJSON     string               `json:"findings_json,omitempty"`
	AppliedActions   string               `json:"applied_actions,omitempty"`
	UpdatedAt        string               `json:"updated_at"`
}

type Config

type Config struct {
	DataDir              string
	MaxObservationLength int
	MaxContextResults    int
	MaxSearchResults     int
	DedupeWindow         time.Duration
}

func DefaultConfig

func DefaultConfig() (Config, error)

func FallbackConfig

func FallbackConfig(dataDir string) Config

FallbackConfig returns a Config with the given DataDir and default values. Use this when DefaultConfig fails and you have resolved the home directory through alternative means.

type ContextOptions

type ContextOptions struct {
	// MaxBytes caps the complete rendered context in bytes. Zero preserves the
	// unbounded legacy output; a positive value returns at most that many bytes.
	MaxBytes int

	// Observations caps the "### Recent Observations" section (unpinned).
	Observations int

	// Prompts caps the "### Recent User Prompts" section.
	Prompts int

	// Sessions caps the "### Recent Sessions" section.
	Sessions int

	// Pinned caps the "### Pinned" section.
	Pinned int

	// Compact drops the inline content preview from observation-shaped
	// bullets — both "### Pinned" and "### Recent Observations" render
	// `- [type] **title**` instead of `- [type] **title**: <300 chars of
	// body>`. Sessions and prompts bullets are unaffected.
	Compact bool
}

ContextOptions tunes FormatContextWithOptions, capping how many rows each section of the "## Memory from Previous Sessions" block renders.

Every field follows the same convention:

  • 0 uses that section's legacy default.
  • >0 caps the section at that many rows.
  • <0 omits the section entirely, including its "### ..." header.

The legacy defaults are Sessions 5, Prompts 10, Observations s.cfg.MaxContextResults, and Pinned unlimited (no SQL LIMIT) — exactly what FormatContext has always produced, so a zero-value ContextOptions{} reproduces FormatContext's output byte-for-byte. See issue #163 (bounded-size injection).

type DeferredRow

type DeferredRow struct {
	SyncID          string         `json:"sync_id"`
	Entity          string         `json:"entity"`
	TargetKey       string         `json:"target_key"`
	Project         string         `json:"project"`
	ScopeClass      string         `json:"scope_class"`
	RemoteSeq       int64          `json:"remote_seq,omitempty"`
	EntityKey       string         `json:"entity_key,omitempty"`
	Op              string         `json:"op,omitempty"`
	ReasonCode      string         `json:"reason_code,omitempty"`
	Payload         map[string]any `json:"payload,omitempty"`
	PayloadRaw      string         `json:"payload_raw"`
	PayloadValid    bool           `json:"payload_valid"`
	ApplyStatus     string         `json:"apply_status"`
	RetryCount      int            `json:"retry_count"`
	LastError       *string        `json:"last_error,omitempty"`
	LastAttemptedAt *string        `json:"last_attempted_at,omitempty"`
	FirstSeenAt     string         `json:"first_seen_at"`
}

DeferredRow represents a row in sync_apply_deferred with the payload decoded.

type DeleteProjectResult

type DeleteProjectResult struct {
	Project             string `json:"project"`
	ObservationsDeleted int64  `json:"observations_deleted"`
	PromptsDeleted      int64  `json:"prompts_deleted"`
	SessionsDeleted     int64  `json:"sessions_deleted"`
	HardDelete          bool   `json:"hard_delete"`
}

DeleteProjectResult summarises a cascade project deletion.

type DiagnosticSessionEvidence

type DiagnosticSessionEvidence struct {
	ID            string `json:"id"`
	Project       string `json:"project"`
	OwnershipMode string `json:"ownership_mode"`
	Directory     string `json:"directory"`
	Name          string `json:"name"`
}

DiagnosticSessionEvidence is the read-only session projection used by operational diagnostics. It intentionally avoids observation/prompt payloads.

type EnrolledProject

type EnrolledProject struct {
	Project    string `json:"project"`
	EnrolledAt string `json:"enrolled_at"`
}

EnrolledProject represents a project enrolled for cloud sync.

type ExplicitProjectMergePreview

type ExplicitProjectMergePreview struct {
	Canonical           string
	Source              string
	ObservationsUpdated int64
	SessionsUpdated     int64
	PromptsUpdated      int64
	SyncIdentityChanges bool
}

ExplicitProjectMergePreview is a point-in-time estimate of source rows the merge updates. Backfill mutations are intentionally not included: apply may enqueue additional mutations and must revalidate against its own transaction.

type ExportData

type ExportData struct {
	PromptTombstones []PromptTombstone `json:"prompt_tombstones,omitempty"`
	Version          string            `json:"version"`
	ExportedAt       string            `json:"exported_at"`
	Sessions         []Session         `json:"sessions"`
	Observations     []Observation     `json:"observations"`
	Prompts          []Prompt          `json:"prompts"`
	Relations        []BackupRelation  `json:"relations,omitempty"`
}

func (ExportData) MarshalJSON

func (d ExportData) MarshalJSON() ([]byte, error)

MarshalJSON projects observations through the backup-only form so direct exports include pinned state without changing Observation's sync JSON tag.

func (*ExportData) UnmarshalJSON

func (d *ExportData) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts both the current backup projection and legacy 0.1.0 exports, where pinned and relations are absent and therefore retain defaults.

Directory admission is part of the decode: an absent directory key is treated as missing (blank directory, accepted), a present JSON string is preserved exactly (blank and whitespace included), and JSON null or any non-string token fails the whole unmarshal with an error naming the offending session.

type ForeignSyncTargetCleanupAction

type ForeignSyncTargetCleanupAction struct {
	TargetKey           string `json:"target_key"`
	RetargetedMutations int64  `json:"retargeted_mutations"`
	RetainedMutations   int64  `json:"retained_mutations"`
	StateRemoved        bool   `json:"state_removed"`
}

ForeignSyncTargetCleanupAction records one foreign target cleanup classification.

type ForeignSyncTargetCleanupReport

type ForeignSyncTargetCleanupReport struct {
	Applied bool                             `json:"applied"`
	Actions []ForeignSyncTargetCleanupAction `json:"actions"`
}

ForeignSyncTargetCleanupReport is the result of planning or applying closed sync-target cleanup. Planning returns the same actions without mutating data.

type ImportResult

type ImportResult struct {
	SessionsImported         int `json:"sessions_imported"`
	ObservationsImported     int `json:"observations_imported"`
	ObservationsUpdated      int `json:"observations_updated"`
	ObservationsSkippedStale int `json:"observations_skipped_stale"`
	PromptsImported          int `json:"prompts_imported"`
}

type InvalidSessionIdentityEvidence

type InvalidSessionIdentityEvidence struct {
	Project             string `json:"project"`
	SessionID           string `json:"session_id"`
	ObservationCount    int64  `json:"observation_count"`
	PromptCount         int64  `json:"prompt_count"`
	InvalidJournalCount int64  `json:"invalid_journal_count"`
}

InvalidSessionIdentityEvidence describes a corrupt source session and the dependent local data that cannot be repaired without a canonical ID.

type JudgeBySemanticParams

type JudgeBySemanticParams struct {
	// SourceID is the TEXT sync_id of the source observation (required).
	SourceID string
	// TargetID is the TEXT sync_id of the target observation (required).
	TargetID string
	// Relation is the verdict verb (required); must be in validRelationVerbs.
	// not_conflict is persisted like every other valid semantic verdict.
	Relation string
	// Confidence is the LLM's self-reported confidence score [0.0, 1.0].
	Confidence float64
	// Reasoning is the LLM's short explanation.
	Reasoning string
	// Model is the LLM model identifier. Stored as marked_by_model.
	Model string
}

JudgeBySemanticParams holds the inputs for JudgeBySemantic.

type JudgeRelationParams

type JudgeRelationParams struct {
	// JudgmentID is the sync_id of the relation row to update (required).
	JudgmentID string
	// Relation is the verdict verb (required); must be one of validRelationVerbs.
	Relation string
	// Reason is an optional free-text explanation.
	Reason *string
	// Evidence is optional free-form JSON or text evidence.
	Evidence *string
	// Confidence is optional 0..1 confidence score.
	Confidence *float64
	// MarkedByActor is the actor identifier (e.g. "agent:claude-sonnet-4-6" or "user").
	MarkedByActor string
	// MarkedByKind is the actor kind ("agent", "human", "system").
	MarkedByKind string
	// MarkedByModel is the model ID (may be empty for human actors).
	MarkedByModel string
	// SessionID is the session in which the judgment was made (optional).
	SessionID string
}

JudgeRelationParams holds the inputs for JudgeRelation.

type LegacyEmptyPromptDiscardBlocker

type LegacyEmptyPromptDiscardBlocker struct {
	Code   string
	Detail string
}

LegacyEmptyPromptDiscardBlocker is a fail-closed planning or application error. Code is suitable for CLI reporting; no blocker implies remote absence.

func (*LegacyEmptyPromptDiscardBlocker) Error

type LegacyEmptyPromptDiscardPlan

type LegacyEmptyPromptDiscardPlan struct {
	Project       string
	SelectedSeq   int64
	PromptID      int64
	SyncID        string
	SessionID     string
	SourceInboxID string
	Sequences     []int64
	// contains filtered or unexported fields
}

LegacyEmptyPromptDiscardPlan previews the exact canonical/FTS deletion, tombstone, superseded sequences and single new delete. Its private snapshot must survive unchanged from planning to apply. Plans are bound to the Store; callers cannot reconstruct a valid plan by deserializing public fields.

type LegacyEmptyPromptDiscardResult

type LegacyEmptyPromptDiscardResult struct {
	Status     string
	Sequences  []int64
	DeleteSeq  int64
	BackupPath string
}

LegacyEmptyPromptDiscardResult reports local effects, never remote delivery. BackupPath may remain populated on error: backups/reservations are not removed if creation fails or later transactional revalidation rejects the plan.

type ListDeferredOptions

type ListDeferredOptions struct {
	// Status filters by apply_status. Empty means no status filter.
	Status string
	// Limit caps the number of rows returned. 0 or negative means no limit.
	Limit int
	// Offset is the pagination offset.
	Offset int
}

ListDeferredOptions controls ListDeferred queries.

type ListRelationsOptions

type ListRelationsOptions struct {
	// Project filters by the project of the source OR target observation (via JOIN).
	// Empty means no project filter (return all).
	Project string
	// Status filters by judgment_status. Empty means no status filter.
	Status string
	// SinceTime filters to rows created_at >= SinceTime. Zero value means no filter.
	SinceTime time.Time
	// Limit caps the number of rows returned. 0 or negative means no limit.
	Limit int
	// Offset is the pagination offset.
	Offset int
	// ExcludeNotConflict omits persisted not_conflict verdicts from conflict-facing views.
	ExcludeNotConflict bool
}

ListRelationsOptions controls ListRelations and CountRelations queries.

type MergeResult

type MergeResult struct {
	Canonical           string   `json:"canonical"`
	SourcesMerged       []string `json:"sources_merged"`
	ObservationsUpdated int64    `json:"observations_updated"`
	SessionsUpdated     int64    `json:"sessions_updated"`
	PromptsUpdated      int64    `json:"prompts_updated"`
}

MergeResult summarizes the result of merging multiple project name variants into a single canonical project name.

type MigrateResult

type MigrateResult struct {
	Migrated            bool  `json:"migrated"`
	ObservationsUpdated int64 `json:"observations_updated"`
	SessionsUpdated     int64 `json:"sessions_updated"`
	PromptsUpdated      int64 `json:"prompts_updated"`
}

type NetworkFilesystemError

type NetworkFilesystemError struct {
	DataDir    string
	Filesystem string
}

NetworkFilesystemError rejects persistent SQLite WAL storage on a known remote filesystem. Callers must propagate it unchanged so CLI, server, and MCP users receive the same actionable diagnosis.

func (*NetworkFilesystemError) Error

func (e *NetworkFilesystemError) Error() string

type Observation

type Observation struct {
	ID             int64   `json:"id"`
	SyncID         string  `json:"sync_id"`
	SessionID      string  `json:"session_id"`
	Type           string  `json:"type"`
	Title          string  `json:"title"`
	Content        string  `json:"content"`
	ToolName       *string `json:"tool_name,omitempty"`
	Project        *string `json:"project,omitempty"`
	Scope          string  `json:"scope"`
	TopicKey       *string `json:"topic_key,omitempty"`
	RevisionCount  int     `json:"revision_count"`
	DuplicateCount int     `json:"duplicate_count"`
	LastSeenAt     *string `json:"last_seen_at,omitempty"`
	ReviewAfter    *string `json:"review_after,omitempty"`
	Pinned         bool    `json:"-"`
	CreatedAt      string  `json:"created_at"`
	UpdatedAt      string  `json:"updated_at"`
	DeletedAt      *string `json:"deleted_at,omitempty"`
}

func (Observation) State

func (o Observation) State() string

State returns the virtual lifecycle state derived from review_after.

type ObservationRelations

type ObservationRelations struct {
	// AsSource holds relations where this observation is source_id.
	AsSource []Relation
	// AsTarget holds relations where this observation is target_id.
	AsTarget []Relation
}

ObservationRelations groups relations for a single observation, split by role.

type ObservationRequiredFieldsEvidence

type ObservationRequiredFieldsEvidence struct {
	ID            int64    `json:"id"`
	SyncID        string   `json:"sync_id"`
	Project       string   `json:"project"`
	MissingFields []string `json:"missing_fields"`
}

ObservationRequiredFieldsEvidence identifies a corrupt source observation without exposing its content in diagnostic output.

type ObservationSnippet

type ObservationSnippet struct {
	ID      int64
	SyncID  string
	Title   string
	Type    string
	Content string
}

ObservationSnippet carries the fields needed by BuildPrompt to construct an LLM comparison prompt without importing internal/llm from this package.

type ObservationSourceTitleRepairAction

type ObservationSourceTitleRepairAction struct {
	ID      int64  `json:"id"`
	SyncID  string `json:"sync_id"`
	Project string `json:"project"`
	Title   string `json:"title"`
}

ObservationSourceTitleRepairAction records a title derived from a corrupt source observation's first non-empty line.

type ObservationSourceTitleRepairReport

type ObservationSourceTitleRepairReport struct {
	Project    string                               `json:"project,omitempty"`
	Applied    bool                                 `json:"applied"`
	Actions    []ObservationSourceTitleRepairAction `json:"actions"`
	BackupPath string                               `json:"backup_path,omitempty"`
}

ObservationSourceTitleRepairReport is the local recovery result for source observation title repairs. A backup is created only when an apply changes rows.

type ObservationVersion added in v3.1.0

type ObservationVersion struct {
	ID            int64  `json:"id"`
	ObservationID int64  `json:"observation_id"`
	Title         string `json:"title"`
	Content       string `json:"content"`
	Version       int    `json:"version"`
	CreatedAt     string `json:"created_at"`
}

ObservationVersion is an immutable snapshot of an observation's title and content captured before a topic_key upsert or a content-changing mem_update overwrote it. Versions are numbered per observation starting at 1: the older the state, the lower the number.

type ObservationVersionPage added in v3.1.0

type ObservationVersionPage struct {
	Versions []ObservationVersion
	Total    int
	HasMore  bool
	// NextCursor is the oldest version in Versions when HasMore is true: pass
	// it as the cursor argument to fetch the next (older) page.
	NextCursor int64
}

ObservationVersionPage is one bounded page of an observation's captured version history, oldest first (ascending version numbers), plus the truthful total count of stored versions and a continuation cursor when older versions remain beyond the page.

type OrphanedObservationSessionEvidence

type OrphanedObservationSessionEvidence struct {
	Project          string `json:"project"`
	SessionID        string `json:"session_id"`
	ObservationCount int64  `json:"observation_count"`
	FirstObservedAt  string `json:"first_observed_at"`
}

OrphanedObservationSessionEvidence identifies observations whose stored session reference has no matching local session. It is grouped so diagnostics can report the affected reference without exposing observation payloads.

type OrphanedPendingRelationEvidence

type OrphanedPendingRelationEvidence struct {
	ID       int64  `json:"id"`
	SyncID   string `json:"sync_id"`
	SourceID string `json:"source_id"`
	TargetID string `json:"target_id"`
}

OrphanedPendingRelationEvidence identifies one legacy pending relation whose source and target observations are both absent from the active observation set. Such a row can never show a title in `engram conflicts show` and no verdict can ever be recorded against it.

type OrphanedPendingRelationEvidenceBounded

type OrphanedPendingRelationEvidenceBounded struct {
	CandidateCount     int64
	OneEndpointMissing int64
	LivePending        int64
	Sample             []OrphanedPendingRelationEvidence
}

OrphanedPendingRelationEvidenceBounded is the bounded diagnostic read for the doctor check: the total candidate count plus only the first `limit` evidence rows, so a large legacy backlog cannot force the check to materialize every candidate. CandidateCount always spans all candidates, while Sample is the id-ordered head of the same list.

type OrphanedPendingRelationEvidenceReport

type OrphanedPendingRelationEvidenceReport struct {
	Candidates         []OrphanedPendingRelationEvidence `json:"candidates"`
	OneEndpointMissing int64                             `json:"one_endpoint_missing"`
	LivePending        int64                             `json:"live_pending"`
}

OrphanedPendingRelationEvidenceReport is the read-only doctor evidence for unreviewable pending relations. Candidates and counts deliberately span all projects: a relation whose endpoints are both absent belongs to no project, so a project-scoped query could never return the rows it exists to surface.

type OrphanedPendingRelationReclassificationResult

type OrphanedPendingRelationReclassificationResult struct {
	Reclassified       int64  `json:"reclassified"`
	CandidatesAtApply  int64  `json:"candidates_at_apply"`
	OneEndpointMissing int64  `json:"one_endpoint_missing"`
	LivePending        int64  `json:"live_pending"`
	BackupPath         string `json:"backup_path,omitempty"`
}

OrphanedPendingRelationReclassificationResult reports one audited apply of the orphaned-pending-relations repair. Reclassified counts the rows actually moved into the `orphaned` disposition; CandidatesAtApply is the predicate re-derived inside the apply transaction, so a row judged between evidence and apply is never reclassified behind a reviewer's verdict.

type OrphanedSessionPlaceholder

type OrphanedSessionPlaceholder struct {
	SessionID        string `json:"session_id"`
	Project          string `json:"project"`
	ObservationCount int64  `json:"observation_count"`
	StartedAt        string `json:"started_at"`
}

OrphanedSessionPlaceholder is the minimal local-only session record used to restore an observation foreign-key reference without fabricating sync state.

type PassiveCaptureParams

type PassiveCaptureParams struct {
	SessionID string        `json:"session_id"`
	Content   string        `json:"content"`
	Project   string        `json:"project,omitempty"`
	Source    string        `json:"source,omitempty"` // e.g. "subagent-stop", "session-end"
	Timings   *WriteTimings `json:"-"`
}

PassiveCaptureParams holds the input for passive memory capture.

type PassiveCaptureResult

type PassiveCaptureResult struct {
	Extracted  int `json:"extracted"`  // Total learnings found in text
	Saved      int `json:"saved"`      // New observations created
	Duplicates int `json:"duplicates"` // Skipped because already existed
}

PassiveCaptureResult holds the output of passive memory capture.

type PendingSyncMutationProjectCount

type PendingSyncMutationProjectCount struct {
	Project string `json:"project"`
	Count   int64  `json:"count"`
}

type ProjectNameCount

type ProjectNameCount struct {
	Name  string `json:"name"`
	Count int    `json:"count"`
}

ProjectNameCount holds a project name and how many observations it has.

type ProjectRescueBlocked

type ProjectRescueBlocked struct {
	// Kind is "session", "observation", or "prompt".
	Kind string `json:"kind"`
	// ID is the session id or the decimal record id.
	ID string `json:"id"`
	// Reason is one of the RescueBlocked* constants.
	Reason string `json:"reason"`
	// OwnedBy is the conflicting project, when one is known.
	OwnedBy string `json:"owned_by,omitempty"`
}

ProjectRescueBlocked names one row the rescue deliberately did not move, and why. It is what lets an operator tell "everything moved" apart from "some things were left behind" without guessing from counters.

type ProjectRescueParams

type ProjectRescueParams struct {
	TargetProject  string
	ObservationIDs []int64
	SessionIDs     []string
	PromptIDs      []int64
}

ProjectRescueParams identifies historical rows whose missing project ownership or blank same-project ownership mode was explicitly confirmed by an operator.

type ProjectRescueResult

type ProjectRescueResult struct {
	RescuedObservations int64 `json:"rescued_observations"`
	RescuedSessions     int64 `json:"rescued_sessions"`
	RescuedPrompts      int64 `json:"rescued_prompts"`
	ConflictingRecords  int64 `json:"conflicting_records"`
	SkippedRecords      int64 `json:"skipped_records"`
	Journaled           bool  `json:"journaled"`
	// Complete is true only when every requested row now belongs to the target
	// project and nothing was left behind.
	Complete bool `json:"complete"`
	// Blocked lists exactly what was left behind, and why.
	Blocked []ProjectRescueBlocked `json:"blocked"`
}

ProjectRescueResult reports local ownership recovery. Journaled means a canonical pending local mutation exists after the call, whether newly inserted or already pending; it does not imply a cloud acknowledgement.

func (ProjectRescueResult) Rescued

func (r ProjectRescueResult) Rescued() int64

type ProjectStats

type ProjectStats struct {
	Name             string   `json:"name"`
	ObservationCount int      `json:"observation_count"`
	SessionCount     int      `json:"session_count"`
	PromptCount      int      `json:"prompt_count"`
	Directories      []string `json:"directories"` // unique directories from sessions
}

ProjectStats holds aggregate statistics for a single project.

type Prompt

type Prompt struct {
	SourceInboxID string `json:"source_inbox_id,omitempty"`
	ID            int64  `json:"id"`
	SyncID        string `json:"sync_id"`
	SessionID     string `json:"session_id"`
	Content       string `json:"content"`
	Project       string `json:"project,omitempty"`
	CreatedAt     string `json:"created_at"`
}

type PromptSourcePreview

type PromptSourcePreview struct {
	SessionID     string
	SourceInboxID string
	Project       string
	SyncID        string
	Kind          string // live or deleted
}

PromptSourcePreview describes observed prompt data, not ownership or authority.

type PromptTombstone

type PromptTombstone struct {
	SyncID        string  `json:"sync_id"`
	SessionID     string  `json:"session_id"`
	Project       *string `json:"project,omitempty"`
	SourceInboxID string  `json:"source_inbox_id,omitempty"`
	DeletedAt     string  `json:"deleted_at"`
}

ExportData is the full serializable direct-backup dump of the engram database.

type PruneResult

type PruneResult struct {
	Project         string `json:"project"`
	SessionsDeleted int64  `json:"sessions_deleted"`
	PromptsDeleted  int64  `json:"prompts_deleted"`
}

PruneResult holds the outcome of pruning a single project.

type QuarantinedPulledSessionEvidence

type QuarantinedPulledSessionEvidence struct {
	SyncID      string `json:"sync_id"`
	TargetKey   string `json:"target_key"`
	Project     string `json:"project"`
	EntityKey   string `json:"entity_key"`
	Op          string `json:"op"`
	RemoteSeq   int64  `json:"remote_seq"`
	ReasonCode  string `json:"reason_code"`
	FirstSeenAt string `json:"first_seen_at"`
}

QuarantinedPulledSessionEvidence describes a pulled session mutation that was skipped because its identity is blank or inconsistent. The pull cursor advances past such a mutation instead of halting, so this row is the only record that remote data was dropped.

type Relation

type Relation struct {
	ID             int64    `json:"id"`
	SyncID         string   `json:"sync_id"`
	SourceID       string   `json:"source_id"`
	TargetID       string   `json:"target_id"`
	Relation       string   `json:"relation"`
	Reason         *string  `json:"reason,omitempty"`
	Evidence       *string  `json:"evidence,omitempty"`
	Confidence     *float64 `json:"confidence,omitempty"`
	JudgmentStatus string   `json:"judgment_status"`
	MarkedByActor  *string  `json:"marked_by_actor,omitempty"`
	MarkedByKind   *string  `json:"marked_by_kind,omitempty"`
	MarkedByModel  *string  `json:"marked_by_model,omitempty"`
	SessionID      *string  `json:"session_id,omitempty"`
	CreatedAt      string   `json:"created_at"`
	UpdatedAt      string   `json:"updated_at"`

	// Annotation fields — populated by GetRelationsForObservations via LEFT JOIN.
	// Excluded from JSON output (used only for in-process annotation building).
	// REQ-005, REQ-012 | Design §7, §8.
	SourceIntID   int64  `json:"-"` // integer primary key of source observation
	SourceTitle   string `json:"-"` // title of source observation; empty if missing/deleted
	SourceMissing bool   `json:"-"` // true if source is soft-deleted or not found
	TargetIntID   int64  `json:"-"` // integer primary key of target observation
	TargetTitle   string `json:"-"` // title of target observation; empty if missing/deleted
	TargetMissing bool   `json:"-"` // true if target is soft-deleted or not found
}

Relation represents a row in memory_relations.

type RelationListItem

type RelationListItem struct {
	ID             int64  `json:"id"`
	SyncID         string `json:"sync_id"`
	Relation       string `json:"relation"`
	JudgmentStatus string `json:"judgment_status"`
	SourceID       string `json:"source_id"`
	SourceTitle    string `json:"source_title"`
	TargetID       string `json:"target_id"`
	TargetTitle    string `json:"target_title"`
	CreatedAt      string `json:"created_at"`
	UpdatedAt      string `json:"updated_at"`
}

RelationListItem represents a single row in a ListRelations result, enriched with observation titles via JOIN (no full Relation struct).

type RelationStats

type RelationStats struct {
	Project          string         `json:"project"`
	ByRelation       map[string]int `json:"by_relation"`
	ByJudgmentStatus map[string]int `json:"by_judgment_status"`
	DeferredCount    int            `json:"deferred"`
	DeadCount        int            `json:"dead"`
}

RelationStats holds aggregate counts of relations for a project.

type ReplayDeferredResult

type ReplayDeferredResult struct {
	Retried   int
	Succeeded int
	Failed    int
	Dead      int
}

ReplayDeferredResult holds counts returned by ReplayDeferred.

type ReviewClearEvent added in v3.3.0

type ReviewClearEvent struct {
	ID     int64
	Key    string
	SyncID string
}

ReviewClearEvent is local-only intent; it is never part of cloud payloads.

type SQLiteLockSnapshot

type SQLiteLockSnapshot struct {
	JournalMode        string `json:"journal_mode"`
	BusyTimeoutMS      int    `json:"busy_timeout_ms"`
	CheckpointBusy     int    `json:"checkpoint_busy"`
	CheckpointLog      int    `json:"checkpoint_log"`
	CheckpointedFrames int    `json:"checkpointed_frames"`
}

SQLiteLockSnapshot captures conservative SQLite lock/contention indicators. wal_checkpoint(PASSIVE) is an observational probe for this diagnostic surface; callers must not interpret it as a repair action.

type SaveRelationParams

type SaveRelationParams struct {
	// SyncID is the unique identifier for this relation row (format: rel-<16hex>).
	SyncID string
	// SourceID is the TEXT sync_id of the source observation.
	SourceID string
	// TargetID is the TEXT sync_id of the target observation.
	TargetID string
}

SaveRelationParams holds the inputs for SaveRelation.

type ScanOptions

type ScanOptions struct {
	// Project is required — scopes the observation walk.
	Project string
	// Since filters observations to created_at >= Since. Zero value means no filter.
	Since time.Time
	// Limit caps this scan page. Zero uses DefaultScanLimit.
	Limit int
	// Cursor resumes after this observation ID. Zero starts from the first row.
	Cursor int64
	// Apply controls whether new relation rows are inserted.
	// When false (dry-run, default), candidates are reported but not written.
	Apply bool
	// MaxInsert caps the number of new relation rows inserted in a single Apply run.
	// Default 100 when 0 or negative.
	MaxInsert int

	// Semantic controls whether the worker pool LLM-judge step runs.
	// When false (default), ScanProject behaves exactly as Phase 3.
	Semantic bool
	// Concurrency is the worker pool size for semantic calls. Default 5 if 0.
	Concurrency int
	// TimeoutPerCall is the per-pair context timeout for runner.Compare.
	// Default 60s if zero.
	TimeoutPerCall time.Duration
	// MaxSemantic caps the number of LLM calls in a single semantic scan. Default 100 if 0.
	MaxSemantic int
	// Runner is the SemanticRunner used for LLM comparison. Required when Semantic=true.
	Runner SemanticRunner
	// BuildPrompt constructs the LLM prompt for a given (a, b) pair.
	// Required when Semantic=true.
	BuildPrompt func(a, b ObservationSnippet) string
}

ScanOptions controls a ScanProject call.

type ScanResult

type ScanResult struct {
	Project           string `json:"project"`
	Inspected         int    `json:"inspected"`
	RankedQueries     int    `json:"ranked_queries"`
	CandidatesFound   int    `json:"candidates_found"`
	NextCursor        *int64 `json:"next_cursor,omitempty"`
	AlreadyRelated    int    `json:"already_related"`
	RelationsInserted int    `json:"inserted"`
	// Capped means work remains but this result has no continuation cursor. Re-run
	// the same incoming observation cursor with a higher applicable cap.
	Capped bool `json:"capped"`
	DryRun bool `json:"dry_run"`

	// Semantic counters — populated only when ScanOptions.Semantic is true.
	// Zero-value is safe for existing JSON consumers.
	SemanticJudged  int `json:"semantic_judged"`
	SemanticSkipped int `json:"semantic_skipped"`
	SemanticErrors  int `json:"semantic_errors"`
}

ScanResult holds the output of a ScanProject call.

type SearchOptions

type SearchOptions struct {
	Type      string `json:"type,omitempty"`
	Project   string `json:"project,omitempty"`
	Scope     string `json:"scope,omitempty"`
	Limit     int    `json:"limit,omitempty"`
	MatchMode string `json:"match_mode,omitempty"` // "all" (default) | "any"
}

type SearchPreviewResult

type SearchPreviewResult struct {
	ID          int64   `json:"id"`
	SyncID      string  `json:"sync_id"`
	Type        string  `json:"type"`
	Title       string  `json:"title"`
	Preview     string  `json:"preview"`
	Truncated   bool    `json:"truncated"`
	Project     *string `json:"project,omitempty"`
	TopicKey    *string `json:"topic_key,omitempty"`
	Scope       string  `json:"scope"`
	ReviewAfter *string `json:"review_after,omitempty"`
	Pinned      bool    `json:"-"`
	CreatedAt   string  `json:"created_at"`
	Rank        float64 `json:"rank"`
}

SearchPreviewResult is the bounded result shape used by preview-only callers. Content is deliberately excluded so those callers do not hydrate full bodies.

func (SearchPreviewResult) State

func (r SearchPreviewResult) State() string

State returns the virtual lifecycle state derived from review_after.

type SearchResult

type SearchResult struct {
	Observation
	Rank float64 `json:"rank"`
}

type SemanticRunner

type SemanticRunner interface {
	Compare(ctx context.Context, prompt string) (SemanticVerdict, error)
}

SemanticRunner is a duck-typed interface satisfied by *llm.ClaudeRunner and *llm.OpenCodeRunner without requiring this package to import internal/llm. Any value whose Compare method matches this signature satisfies the interface.

type SemanticVerdict

type SemanticVerdict struct {
	// Relation is one of: conflicts_with, supersedes, scoped, related, compatible, not_conflict.
	Relation string
	// Confidence is a 0.0–1.0 score from the runner.
	Confidence float64
	// Reasoning is a short human-readable explanation (≤200 chars).
	Reasoning string
	// Model is the model identifier reported by the CLI (e.g. "claude-haiku-4-5").
	Model string
	// DurationMS is wall-clock time for the CLI invocation in milliseconds.
	DurationMS int64
}

SemanticVerdict is the result of a semantic comparison between two observations. It mirrors llm.Verdict but lives in this package to avoid a store→llm import cycle.

type Session

type Session struct {
	ID                    string  `json:"id"`
	Project               string  `json:"project"`
	OwnershipMode         string  `json:"ownership_mode,omitempty"`
	Directory             string  `json:"directory"`
	StartedAt             string  `json:"started_at"`
	EndedAt               *string `json:"ended_at,omitempty"`
	Summary               *string `json:"summary,omitempty"`
	RuntimeLeaseExpiresAt *string `json:"-"`
}

type SessionIdentityRepairPlan

type SessionIdentityRepairPlan struct {
	SourceID         string `json:"source_id"`
	ReplacementID    string `json:"replacement_id"`
	Project          string `json:"project"`
	Observations     int64  `json:"observations"`
	Prompts          int64  `json:"prompts"`
	RetiredMutations int64  `json:"retired_mutations"`
	Enrolled         bool   `json:"enrolled"`
	// contains filtered or unexported fields
}

SessionIdentityRepairPlan records the read-only impact of replacing one blank identity. Apply rechecks the complete evidence under the SQLite writer lock.

type SessionIdentityRepairResult

type SessionIdentityRepairResult struct {
	SessionIdentityRepairPlan
	BackupPath         string `json:"backup_path"`
	PublishedMutations int64  `json:"published_mutations"`
}

type SessionProjectConflictError

type SessionProjectConflictError struct {
	SessionID        string
	OwnerProject     string
	RequestedProject string
}

SessionProjectConflictError identifies a strict registration that would reuse a persisted session under another project while preserving errors.Is behavior.

func (*SessionProjectConflictError) Error

func (*SessionProjectConflictError) Unwrap

func (e *SessionProjectConflictError) Unwrap() error

type SessionProjectReclassification

type SessionProjectReclassification struct {
	SessionID   string
	FromProject string
	ToProject   string
}

type SessionProjectReclassificationCounts

type SessionProjectReclassificationCounts struct {
	Sessions     int64
	Observations int64
	Prompts      int64
}

type SessionProjectReclassificationResult

type SessionProjectReclassificationResult struct {
	Counts     SessionProjectReclassificationCounts
	BackupPath string
}

type SessionSummary

type SessionSummary struct {
	ID               string  `json:"id"`
	Project          string  `json:"project"`
	StartedAt        string  `json:"started_at"`
	EndedAt          *string `json:"ended_at,omitempty"`
	Summary          *string `json:"summary,omitempty"`
	ObservationCount int     `json:"observation_count"`
}

type Stats

type Stats struct {
	TotalSessions     int      `json:"total_sessions"`
	TotalObservations int      `json:"total_observations"`
	TotalPrompts      int      `json:"total_prompts"`
	Projects          []string `json:"projects"`
}

type Store

type Store struct {
	// contains filtered or unexported fields
}

func New

func New(cfg Config) (*Store, error)

func (*Store) AckSyncMutationSeqs

func (s *Store) AckSyncMutationSeqs(targetKey string, seqs []int64) error

AckSyncMutationSeqs acknowledges specific mutation sequence numbers without requiring them to be contiguous.

func (*Store) AckSyncMutations

func (s *Store) AckSyncMutations(targetKey string, lastAckedSeq int64) error

func (*Store) AcquireSyncLease

func (s *Store) AcquireSyncLease(targetKey, owner string, ttl time.Duration, now time.Time) (bool, error)

func (*Store) ActiveRuntimeSessions

func (s *Store) ActiveRuntimeSessions(project string, directories ...string) ([]string, error)

MostRecentActiveSession resolves the active (un-ended) session for a project from the persisted sessions table. It returns the session ID and ok=true when such a session exists, or ok=false when none does.

This is the cross-process resolution that fixes issue #386: the SessionStart hook registers a UUID session via the HTTP server (POST /sessions) in one process, while mem_save runs in the separate MCP (stdio) process. The two share only the SQLite store, so the active session must be read from disk — never from in-memory state.

Candidate rules:

  • Scope to the (normalized) project.
  • Scope to the current runtime directory.
  • Require ended_at IS NULL — ended sessions are never returned.
  • A nonblank runtime lease is authoritative: return it only while it is valid and unexpired. Expired or malformed leases are excluded.
  • A live lease suppresses unleased legacy candidates in its directory only. Where no live lease exists, preserve the legacy effective-activity window: the latest observation, falling back to started_at, must be recent.
  • Exclude the manual-save fallback sessions (id LIKE 'manual-save%'); those are created by the fallback path itself and must not be resolved as "the active session", which would make resolution circular.

ActiveRuntimeSessions returns active, non-manual sessions for a project and runtime directories. The directories narrow candidates; callers must not treat them as session identity. Selection is read-only: it never ends or repairs historical rows.

func (*Store) AddObservation

func (s *Store) AddObservation(p AddObservationParams) (int64, error)

AddObservation preserves the legacy ID-only API for callers that do not need to distinguish a new save from an exact operation replay.

func (*Store) AddObservationWithResult added in v3.3.0

func (s *Store) AddObservationWithResult(p AddObservationParams) (AddObservationResult, error)

AddObservationWithResult persists an observation or returns an exact replay outcome from the operation ledger in the same transaction.

func (*Store) AddPrompt

func (s *Store) AddPrompt(p AddPromptParams) (int64, error)

func (*Store) AddPromptIfMissing

func (s *Store) AddPromptIfMissing(p AddPromptParams) (int64, bool, error)

func (*Store) AddPromptWithResult

func (s *Store) AddPromptWithResult(p AddPromptParams) (int64, bool, error)

AddPromptWithResult reports whether a prompt was inserted rather than replayed.

func (*Store) AllObservations

func (s *Store) AllObservations(project, scope string, limit int) ([]Observation, error)

AllObservations returns recent observations ordered by most recent first (for TUI browsing).

func (*Store) AllSessions

func (s *Store) AllSessions(project string, limit int) ([]SessionSummary, error)

AllSessions returns recent sessions ordered by most recent first (for TUI browsing). A database upgraded from the schema where sessions.project was nullable still carries rows that identify no project, so the column is read through ifnull(): an unscoped listing reads every session row and must not die on one of them.

func (*Store) ApplyLegacyEmptyPromptDiscard

func (s *Store) ApplyLegacyEmptyPromptDiscard(plan LegacyEmptyPromptDiscardPlan, backupPath string) (LegacyEmptyPromptDiscardResult, error)

ApplyLegacyEmptyPromptDiscard backs up first, then independently rederives eligibility inside the single mutation transaction. A changed/repeated plan fails with stale_plan and queues no duplicate. A preflight stale plan creates no backup; a change after backup may leave that backup on disk. There is no fencing of concurrent/in-flight exporters and no remote delivery guarantee.

func (*Store) ApplyPulledChunk

func (s *Store) ApplyPulledChunk(targetKey, chunkID string, mutations []SyncMutation) error

ApplyPulledChunk atomically applies all mutations contained in a pulled chunk under the local import domain and records the chunk as synced in the same transaction. This guarantees retry safety: a failed chunk import leaves no partial semantic mutations.

It shares ApplyPulledMutation's skip-plus-evidence rule for invalid session and observation identities, plus deferred handling for observation and prompt upserts whose parent session is absent, so either condition cannot block the chunk forever. A payload that does not even decode stays fail-closed and rolls back the whole chunk, because an undecodable payload is a transport-level fault rather than known-corrupt historical data.

func (*Store) ApplyPulledChunkForDomain

func (s *Store) ApplyPulledChunkForDomain(targetKey, chunkID string, mutations []SyncMutation, cloud bool) error

ApplyPulledChunkForDomain is ApplyPulledChunk with an explicit import domain. The domain must be carried explicitly by the caller; it is never inferred from the target key, because a cloud chunk's tracking key and its admission rule answer different questions. cloud=true runs the strict cloud-inbound directory admission (validatePulledSessionDirectory), so a shared session upsert with a blank or missing directory fails the whole chunk atomically — no session persisted, chunk not recorded. Only per-mutation Cloud pull admits a blank or omitted directory (validatePulledSessionDirectoryCloudMutation). cloud=false keeps the local partial-session domain: blank directories are accepted and the skip-plus-evidence quarantine ladder behaves exactly as before.

func (*Store) ApplyPulledMutation

func (s *Store) ApplyPulledMutation(targetKey string, mutation SyncMutation) error

ApplyPulledMutation applies one remote mutation and advances the pull cursor.

Session and observation identity semantics are skip-plus-evidence, not fail-closed. A blank or inconsistent session identity, or an inconsistent observation identity, in a pulled mutation is quarantined through deadLetterPulledIdentityTx and the cursor still advances past it. Failing closed here would be a permanent retry loop: immutable remote data cannot become valid through local retries, so halting would pin the cursor forever and block every later mutation behind it. Quarantining keeps the dropped data visible in `engram conflicts deferred`.

Every other apply failure keeps its existing fail-closed behavior.

func (*Store) ApplyPulledMutationPreservingSyncState

func (s *Store) ApplyPulledMutationPreservingSyncState(targetKey string, mutation SyncMutation) error

ApplyPulledMutationPreservingSyncState advances the pull cursor without changing the current lifecycle, reason, or failure state. It is used when inbound replication succeeds while outbound sync remains policy-blocked.

func (*Store) ApplySessionIdentityRepair

func (s *Store) ApplySessionIdentityRepair(plan SessionIdentityRepairPlan) (SessionIdentityRepairResult, error)

ApplySessionIdentityRepair leaves its pre-apply backup on disk even if the transaction fails. No corrected mutation is published before commit.

func (*Store) ApplySessionProjectReclassification

func (s *Store) ApplySessionProjectReclassification(actions []SessionProjectReclassification) (SessionProjectReclassificationResult, error)

func (*Store) BackupSQLite

func (s *Store) BackupSQLite() (string, error)

func (*Store) CanRollbackCloudUpgrade

func (s *Store) CanRollbackCloudUpgrade(project string) (bool, error)

func (*Store) CapturePromptForSave added in v3.2.0

func (s *Store) CapturePromptForSave(enabled *bool, prompt AddPromptParams) error

CapturePromptForSave applies the shared per-save prompt policy. An omitted option enables capture; false or missing prompt content is a no-op. The caller supplies the current prompt for the observation's session/project; persisted history is not a substitute for current runtime context. Call only after a successful observation save, and treat capture errors as nonfatal. This stores an independent prompt row, not an observation-ID link.

func (*Store) CleanupForeignSyncTargets

func (s *Store) CleanupForeignSyncTargets(apply bool) (ForeignSyncTargetCleanupReport, error)

CleanupForeignSyncTargets retargets pending foreign rows to cloud and removes their state only when no terminal journal rows remain.

func (*Store) ClearCloudUpgradeState

func (s *Store) ClearCloudUpgradeState(project string) error

func (*Store) Close

func (s *Store) Close() error

func (*Store) CloudSyncSummary

func (s *Store) CloudSyncSummary() (CloudSyncSummary, error)

CloudSyncSummary returns status across project-scoped cloud targets only. It deliberately excludes the legacy global cloud target because explicit cloud sync records state under cloud:<project>, and the reserved cloud inbox target because the inbox is not a project.

func (*Store) ConfirmPromptSourceAttestation

func (s *Store) ConfirmPromptSourceAttestation(remoteTarget string, preview PromptSourcePreview, assertedOwner string, remoteAttestationID int64) error

ConfirmPromptSourceAttestation records an explicit owner assertion only after the caller reports remote attestation success. The observed tuple is not ownership proof. A tuple recheck cannot detect change-away-and-back without a revision; autosync must revalidate cloud authorization before using it. Newer IDs are safe only within the same remote target while it retains the same Postgres identity sequence. Callers must supply an endpoint identity independent of local sync TargetKey and recheck cloud state before use.

func (*Store) ContentTruncation

func (s *Store) ContentTruncation(content string) TruncationMetadata

ContentTruncation returns the byte-based truncation metadata used by storage writes.

func (*Store) CountDeferredAndDead

func (s *Store) CountDeferredAndDead() (deferred, dead int, err error)

CountDeferredAndDead returns global administrative totals, including legacy unscoped rows that normal scoped imports intentionally leave untouched.

func (*Store) CountDeferredAndDeadForScope

func (s *Store) CountDeferredAndDeadForScope(targetKey, project string) (deferred, dead int, err error)

CountDeferredAndDeadForScope returns queue totals for an optional target and project. Scoped totals exclude legacy-unscoped rows; empty filters return the global administrative totals.

func (*Store) CountObservationsForProject

func (s *Store) CountObservationsForProject(name string) (int, error)

CountObservationsForProject returns the number of non-deleted observations for the given project name. Used by handleSave for the similar-project warning instead of the heavier ListProjectsWithStats.

func (*Store) CountPendingNonEnrolledSyncMutations

func (s *Store) CountPendingNonEnrolledSyncMutations(targetKey string) ([]PendingSyncMutationProjectCount, error)

func (*Store) CountRelationSyncMutations

func (s *Store) CountRelationSyncMutations() (int, error)

CountRelationSyncMutations returns the number of sync_mutations rows whose entity is NOT 'session', 'observation', or 'prompt'. Used by integration tests to verify the enrollment gate: an UNENROLLED project must never enqueue relation sync mutations (the enqueue in JudgeBySemantic/JudgeRelation is guarded by an enrollment check). The test that calls this uses an unenrolled store, so the count must remain zero.

Note: relation sync mutations ARE valid for enrolled projects (#313/#379/#383 enabled cloud relation sync; #496 extends it with backfill). This function is not a blanket "relations are local-only" check — it is an enrollment-gate regression guard scoped to the unenrolled test context that uses it.

func (*Store) CountRelations

func (s *Store) CountRelations(opts ListRelationsOptions) (int, error)

CountRelations returns the total number of relation rows matching opts. Uses the same WHERE conditions as ListRelations.

func (*Store) CreateSession

func (s *Store) CreateSession(id, project, directory string) error

func (*Store) CreateSessionWithOwnershipMode

func (s *Store) CreateSessionWithOwnershipMode(id, project, directory, mode string) error

CreateSessionWithOwnershipMode creates a session with an explicit persisted ownership policy. CreateSession remains the compatibility wrapper for shared runtime sessions.

func (*Store) DB

func (s *Store) DB() *sql.DB

DB returns the underlying *sql.DB. Intended for test helpers and integration tests that need to inject raw rows (e.g. legacy data with non-normalized project names) without going through the Store's public API.

func (*Store) DataDir

func (s *Store) DataDir() string

DataDir returns the directory containing the store database and cloud.json.

func (*Store) DeleteObservation

func (s *Store) DeleteObservation(id int64, hardDelete bool) error

func (*Store) DeleteObservationForProject

func (s *Store) DeleteObservationForProject(id int64, expected string, hardDelete bool) error

DeleteObservationForProject asserts ownership before soft or hard deletion in the same transaction.

func (*Store) DeleteProject

func (s *Store) DeleteProject(project string, hardDelete bool) (*DeleteProjectResult, error)

DeleteProject removes all data associated with a project in a single transaction.

When hardDelete is true: observation rows are permanently removed, prompts are hard-deleted, and sessions are hard-deleted. memory_relations that reference any removed observation are marked orphaned (audit history).

When hardDelete is false: observations are soft-deleted (deleted_at set), and prompts are hard-deleted. Sessions are NOT removed in this path because observations.session_id is a NOT NULL FK to sessions — removing sessions while soft-deleted observation rows still reference them would violate the FK constraint. The session rows remain and can be cleaned up with engram delete session <id> once the observations are purged.

Returns ErrProjectNotFound when no sessions or observations exist for the given project name.

func (*Store) DeletePrompt

func (s *Store) DeletePrompt(id int64) error

DeletePrompt hard-deletes a single prompt by ID and records a sync tombstone. It returns ErrPromptNotFound if no prompt with that ID exists.

func (*Store) DeleteSession

func (s *Store) DeleteSession(id string) error

DeleteSession hard-deletes a session and its prompts. It returns ErrSessionHasObservations if the session has any observations (including soft-deleted ones) to prevent orphaned rows. It returns ErrSessionNotFound if no session with that ID exists.

When the session belongs to an enrolled project, this operation also enqueues a session/delete mutation so cloud replicas can remove the session.

func (*Store) DetectProject added in v3.1.0

func (s *Store) DetectProject(directory string) project.DetectionResult

DetectProject applies the first-Git-binding precaution using this local store.

func (*Store) DiagnoseCloudUpgradeLegacyMutations

func (s *Store) DiagnoseCloudUpgradeLegacyMutations(project string) (CloudUpgradeLegacyMutationReport, error)

func (*Store) EndRuntimeSession added in v3.2.0

func (s *Store) EndRuntimeSession(root, project, directory, mode, summary string) (string, error)

EndRuntimeSession resolves and ends within one writer reservation. Generic EndSession retains its exact-ID behavior for all existing callers.

func (*Store) EndSession

func (s *Store) EndSession(id string, summary string) error

func (*Store) EnqueueDeferredRelation

func (s *Store) EnqueueDeferredRelation(targetKey string, mutation SyncMutation) error

EnqueueDeferredRelation durably records a relation upsert as deferred retry state before its chunk applies (issue #1135): when the cloud import skips an edge as permanently unsatisfiable, this row is what keeps the skip recoverable — ReplayDeferredForScope re-applies the edge once its endpoint reappears, and the apply's success path deletes the row. It writes exactly the row recordRelationApplyFailureTx writes for a deferred FK miss, so a later delivery of the same edge collapses onto this row instead of queueing it twice. Re-enqueueing a live deferred row never resets its retry state; re-enqueueing a row that died at the replay retry cap re-arms it to 'deferred' with retry_count reset to 0, giving the edge a fresh bounded replay window. Hash-keyed dead evidence is never re-armed.

func (*Store) EnrollProject

func (s *Store) EnrollProject(project string) error

EnrollProject registers a project for cloud sync. Idempotent — re-enrolling an already-enrolled project is a no-op.

func (*Store) EnsureEnrolledProjectSyncMutations

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

EnsureEnrolledProjectSyncMutations repairs legacy enrolled-project journal entries before a sync operation reads them. A successful repair is memoized for this Store's lifetime; failures are returned to callers and retried by a later synchronization attempt.

func (*Store) EstimateSessionProjectReclassification

func (s *Store) EstimateSessionProjectReclassification(actions []SessionProjectReclassification) (SessionProjectReclassificationCounts, error)

func (*Store) Export

func (s *Store) Export() (*ExportData, error)

func (*Store) ExportLocalDeleteTombstones

func (s *Store) ExportLocalDeleteTombstones(project string) ([]SyncMutation, error)

ExportLocalDeleteTombstones projects locally retained hard-delete intent into canonical mutations.

func (*Store) ExportProject

func (s *Store) ExportProject(project string) (*ExportData, error)

ExportProject returns an export restricted to records relevant to a single normalized project. This avoids full-database exports when only one project needs to sync.

func (*Store) ExportRelationMutations

func (s *Store) ExportRelationMutations(project string) ([]SyncMutation, error)

ExportRelationMutations returns relation upsert mutations for non-orphaned relation rows whose source and target observations are available locally.

func (*Store) ExportReviewClearEvents added in v3.3.0

func (s *Store) ExportReviewClearEvents() ([]ReviewClearEvent, error)

ExportReviewClearEvents returns retained events whose observation is still live and undated. Restoring a date or deleting the observation suppresses it.

func (*Store) FindCandidates

func (s *Store) FindCandidates(savedID int64, opts CandidateOptions) ([]Candidate, error)

FindCandidates runs a post-transaction FTS5 candidate query for the given savedID and returns at most opts.Limit candidates satisfying the rank predicate and, when opted in, the save relevance gate.

For each candidate, a pending memory_relations row is inserted and the row's sync_id is exposed as Candidate.JudgmentID. Candidates with an existing judged relation in either direction are excluded.

Errors from this method are expected to be logged and swallowed by callers — detection failure must never fail the originating save.

func (*Store) FormatCompactionContext

func (s *Store) FormatCompactionContext(sessionID string) (string, error)

FormatCompactionContext returns runtime context that is strictly limited to one persisted session. The session's project is derived from the store and is never supplied by the caller.

func (*Store) FormatContext

func (s *Store) FormatContext(project, scope string) (string, error)

FormatContext is a thin wrapper around FormatContextWithOptions using a zero-value ContextOptions, preserving the pre-ContextOptions call signature so existing callers and tests keep working unchanged.

func (*Store) FormatContextWithOptions

func (s *Store) FormatContextWithOptions(project, scope string, opts ContextOptions) (string, error)

FormatContextWithOptions renders the "## Memory from Previous Sessions" markdown block for the given project/scope, honoring the per-section caps and Compact rendering in opts. See ContextOptions for the cap convention.

func (*Store) GenerationError added in v3.1.0

func (s *Store) GenerationError() error

GenerationError returns only an already observed permanent invalidation. It performs no filesystem probes and does not initiate a generation check.

func (*Store) GetCloudUpgradeState

func (s *Store) GetCloudUpgradeState(project string) (*CloudUpgradeState, error)

func (*Store) GetDeferred

func (s *Store) GetDeferred(syncID string) (DeferredRow, error)

GetDeferred returns a single row from sync_apply_deferred by sync_id. Returns an error wrapping "not found" when no row exists (matches FindCandidates style).

func (*Store) GetObservation

func (s *Store) GetObservation(id int64) (*Observation, error)

func (*Store) GetObservationBySyncID

func (s *Store) GetObservationBySyncID(syncID string) (*Observation, error)

func (*Store) GetObservationSaveResult added in v3.3.0

func (s *Store) GetObservationSaveResult(operationID string) (observationID int64, err error)

GetObservationSaveResult returns the committed observation ID for a previous observation save operation, if any. It is intended for clients that lost the response and want to recover without replaying the write. A zero result with a nil error means no operation was recorded. ErrObservationOperationExpired means the operation is retained but its observation was deleted, so it must not be replayed.

func (*Store) GetObservationVersionPage added in v3.1.0

func (s *Store) GetObservationVersionPage(id int64, limit int, cursor int64) (ObservationVersionPage, error)

GetObservationVersionPage returns one bounded page of an observation's version history, oldest first (ascending version number) so rendering stays chronological. Cursor 0 starts from the most recent versions; a cursor from a previous page's NextCursor (set only when HasMore is true) fetches the older page continuing before it. Total is the full stored count and is truthful even when the page is truncated. Versions survive soft deletes and a missing or untouched observation yields an empty page with Total 0, never an error.

limit must be between 1 and MaxObservationVersionPageSize; cursor must be non-negative.

func (*Store) GetObservationVersions added in v3.1.0

func (s *Store) GetObservationVersions(id int64) ([]ObservationVersion, error)

GetObservationVersions returns every captured version for an observation, oldest first, numbering starting at 1. Versions survive soft deletes: they are immutable history, read directly from the version table. A missing or untouched observation yields an empty slice, never an error.

Callers serving one bounded response should prefer GetObservationVersionPage, which never materializes the full unbounded history.

func (*Store) GetRelation

func (s *Store) GetRelation(syncID string) (*Relation, error)

GetRelation retrieves a single relation row by its sync_id.

func (*Store) GetRelationByIntID

func (s *Store) GetRelationByIntID(id int64) (*RelationListItem, error)

GetRelationByIntID retrieves a single relation enriched with source/target observation titles by its integer primary key. Returns a *RelationListItem (same shape as ListRelations rows) so HTTP handlers share one response type. Returns an error wrapping "not found" when the id does not exist.

func (*Store) GetRelationStats

func (s *Store) GetRelationStats(project string) (RelationStats, error)

GetRelationStats returns aggregate counts for a project's relations plus the deferred and dead queue totals. Two queries are executed: one GROUP BY and one delegated to CountDeferredAndDead.

func (*Store) GetRelationsForObservations

func (s *Store) GetRelationsForObservations(syncIDs []string) (map[string]ObservationRelations, error)

GetRelationsForObservations returns a map of observation sync_id → ObservationRelations for all observations in syncIDs. Relations with judgment_status='orphaned' are excluded.

A single SQL query with IN/OR and LEFT JOINs avoids N+1 queries. The returned Relation values are enriched with source/target integer IDs and titles via LEFT JOIN, used by the MCP annotation builder (REQ-005, REQ-012). Missing or soft-deleted observations set the corresponding *Missing flag to true.

func (*Store) GetRelationsForObservationsContext

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

GetRelationsForObservationsContext enriches observations with relations while honoring cancellation from the caller, including while materializing rows.

func (*Store) GetSession

func (s *Store) GetSession(id string) (*Session, error)

func (*Store) GetSyncState

func (s *Store) GetSyncState(targetKey string) (*SyncState, error)

func (*Store) GetSyncedChunks

func (s *Store) GetSyncedChunks() (map[string]bool, error)

GetSyncedChunks returns local-target chunk IDs for backwards compatibility.

func (*Store) GetSyncedChunksForTarget

func (s *Store) GetSyncedChunksForTarget(targetKey string) (map[string]bool, error)

GetSyncedChunksForTarget returns chunk IDs tracked for a specific sync target.

func (*Store) HasObservationBySyncIDAnyState

func (s *Store) HasObservationBySyncIDAnyState(syncID string) (bool, error)

HasObservationBySyncIDAnyState reports whether an observation with the given sync_id exists locally in any deletion state, tombstones included. It mirrors the tombstone-inclusive relation FK precondition (getObservationBySyncIDTx with includeDeleted) for callers outside Store transactions and answers through the idx_obs_sync_id index without materializing an export.

func (*Store) HasPendingSyncMutationsForProject

func (s *Store) HasPendingSyncMutationsForProject(project string) (bool, error)

func (*Store) HasProjectOwnedSessions

func (s *Store) HasProjectOwnedSessions() (bool, error)

HasProjectOwnedSessions reports whether any session requires ownership-mode sync.

func (*Store) HasProjectOwnedSessionsForProject

func (s *Store) HasProjectOwnedSessionsForProject(project string) (bool, error)

HasProjectOwnedSessionsForProject reports whether project requires ownership-mode sync.

func (*Store) Import

func (s *Store) Import(data *ExportData) (*ImportResult, error)

func (*Store) InspectProject added in v3.1.0

func (s *Store) InspectProject(directory string) project.DetectionResult

InspectProject cannot create a binding or elevate an unbound Git candidate.

func (*Store) InstanceID

func (s *Store) InstanceID() string

InstanceID returns this store's stable local-server identity.

func (*Store) IsProjectEnrolled

func (s *Store) IsProjectEnrolled(project string) (bool, error)

func (*Store) JudgeBySemantic

func (s *Store) JudgeBySemantic(p JudgeBySemanticParams) (string, error)

JudgeBySemantic persists a semantic verdict produced by an AgentRunner into the memory_relations table with system provenance (marked_by_kind="system", marked_by_actor="engram", marked_by_model=params.Model).

Idempotency: if a row already exists for (source_id, target_id) in either direction, the existing row is updated (UPSERT). The returned sync_id is always the canonical row's sync_id.

Returns ErrCrossProjectRelation when source and target belong to different projects. Returns a validation error when required fields are missing or Confidence is out of [0.0, 1.0].

func (*Store) JudgeRelation

func (s *Store) JudgeRelation(p JudgeRelationParams) (*Relation, error)

JudgeRelation records a verdict on an existing pending relation row.

Re-judge policy: OVERWRITE the existing row (design decision). The updated row is returned on success.

Phase 2: wraps the UPDATE in a transaction to atomically enqueue a sync mutation when the source observation's project is enrolled for cloud sync. Returns ErrCrossProjectRelation if source and target belong to different projects.

Returns an error if the judgment_id is unknown or the relation verb is invalid.

func (*Store) ListDeferred

func (s *Store) ListDeferred(opts ListDeferredOptions) ([]DeferredRow, error)

ListDeferred returns rows from sync_apply_deferred with optional status filter and pagination. The payload field is decoded to map[string]any; on malformed JSON, PayloadValid is false and PayloadRaw is preserved.

func (*Store) ListDeferredProjectsForTarget

func (s *Store) ListDeferredProjectsForTarget(targetKey string) ([]string, error)

func (*Store) ListDiagnosticObservationRequiredFields

func (s *Store) ListDiagnosticObservationRequiredFields(project string) ([]ObservationRequiredFieldsEvidence, error)

ListDiagnosticObservationRequiredFields reports active source observations whose cloud-required title, content, or type is NULL, empty, or whitespace. It is deliberately independent of sync_mutations so doctor can find source corruption even when no journal row remains.

func (*Store) ListDiagnosticSessions

func (s *Store) ListDiagnosticSessions(project string) ([]DiagnosticSessionEvidence, error)

ListDiagnosticSessions returns session evidence scoped by project when provided. The query is read-only and ordered for deterministic diagnostics.

project is read through ifnull() for the same reason directory already is: a database upgraded from the schema where sessions.project was nullable still carries NULL ownership, and no migration rewrites the column. Scanning that raw would abort every diagnostic on exactly the databases doctor exists to report on. NULL and blank both mean "identifies no project" to every caller here, so collapsing them to the empty string loses nothing.

func (*Store) ListEnrolledProjects

func (s *Store) ListEnrolledProjects() ([]EnrolledProject, error)

ListEnrolledProjects returns all projects currently enrolled for cloud sync, ordered alphabetically by project name.

func (*Store) ListInvalidSessionIdentityEvidence

func (s *Store) ListInvalidSessionIdentityEvidence(project string) ([]InvalidSessionIdentityEvidence, error)

ListInvalidSessionIdentityEvidence reports blank source session IDs together with affected references and invalid session journal entries. It is read-only.

The source-row predicate uses the shared whitespace trim set rather than SQLite's bare trim(), so a legacy identity made of tabs, newlines or carriage returns cannot bypass the scan while still being rejected by the Go guards.

project is read through ifnull() because a corrupt session row on an upgraded database can also carry the legacy NULL ownership; reporting the corrupt identity must not depend on whether that row's project survived the upgrade.

func (*Store) ListObservationSyncPayloads

func (s *Store) ListObservationSyncPayloads() ([]any, error)

ListObservationSyncPayloads returns the decoded payloads of all sync_mutations rows whose entity = 'observation'. Used by integration tests to assert that local-only observation columns (expires_at, embedding*) remain absent from the sync wire format; review_after is replicated.

func (*Store) ListOrphanedObservationSessionEvidence

func (s *Store) ListOrphanedObservationSessionEvidence(project string) ([]OrphanedObservationSessionEvidence, error)

ListOrphanedObservationSessionEvidence reports grouped observation references whose parent sessions are absent. It includes soft-deleted observations because they remain local data that can block inspection or recovery.

func (*Store) ListOrphanedPendingRelationEvidence

func (s *Store) ListOrphanedPendingRelationEvidence() (OrphanedPendingRelationEvidenceReport, error)

ListOrphanedPendingRelationEvidence reports legacy pending relation rows whose source and target observations are both absent from the active observation set. It additionally counts pending rows missing exactly one endpoint and fully live pending rows, so a finding can show that a repair will not touch still-reviewable relations. The listing is deliberately unscoped: a relation with both endpoints absent belongs to no project.

func (*Store) ListOrphanedPendingRelationEvidenceBounded

func (s *Store) ListOrphanedPendingRelationEvidenceBounded(limit int) (OrphanedPendingRelationEvidenceBounded, error)

ListOrphanedPendingRelationEvidenceBounded reports the same evidence as ListOrphanedPendingRelationEvidence with the candidate rows bounded: it returns the total candidate count plus only the first limit evidence rows, so the doctor check can render a large legacy backlog without materializing every candidate. The full candidate set remains available to the repair planner and apply path through ListOrphanedPendingRelationEvidence. The listing is deliberately unscoped: a relation with both endpoints absent belongs to no project.

func (*Store) ListPendingProjectMutations

func (s *Store) ListPendingProjectMutations(project string) ([]SyncMutation, error)

ListPendingProjectMutations returns pending cloud mutations for one project, or all projects when project is empty, without enrollment filtering. Doctor needs to diagnose blocked metadata even when a project is not enrolled.

func (*Store) ListPendingSyncMutations

func (s *Store) ListPendingSyncMutations(targetKey string, limit int) ([]SyncMutation, error)

func (*Store) ListPendingSyncMutationsAfterSeq

func (s *Store) ListPendingSyncMutationsAfterSeq(targetKey string, afterSeq int64, limit int) ([]SyncMutation, error)

func (*Store) ListProjectNames

func (s *Store) ListProjectNames() ([]string, error)

ListProjectNames returns all distinct project names from observations, ordered alphabetically. Used for fuzzy matching and consolidation.

func (*Store) ListProjectsForCloudEnrollment

func (s *Store) ListProjectsForCloudEnrollment() ([]string, error)

ListProjectsForCloudEnrollment returns every local identity that can be enrolled, normalized and ordered deterministically for the cloud TUI.

func (*Store) ListProjectsWithStats

func (s *Store) ListProjectsWithStats() ([]ProjectStats, error)

ListProjectsWithStats returns all projects with aggregated counts. Ordered by observation count descending.

func (*Store) ListQuarantinedPulledSessionEvidence

func (s *Store) ListQuarantinedPulledSessionEvidence(project string) ([]QuarantinedPulledSessionEvidence, error)

ListQuarantinedPulledSessionEvidence returns the pulled session mutations the apply path skipped because their identity is blank or inconsistent.

The pull deliberately does not fail closed on these mutations: halting would pin the cursor forever on a historical chunk written before the identity rule existed. Instead each one is quarantined here so doctor can report exactly what remote data was dropped. It is read-only.

func (*Store) ListRelations

func (s *Store) ListRelations(opts ListRelationsOptions) ([]RelationListItem, error)

ListRelations returns a paginated list of relation rows filtered by the given options. Project filtering is done via LEFT JOIN to observations (no schema change). Uses idx_memrel_status_created for efficient status+date ordering.

func (*Store) ListSyncStates

func (s *Store) ListSyncStates() ([]SyncTargetState, error)

ListSyncStates returns every sync_state row with its pending mutation count, ordered by target key. Doctor uses it to verify the closed set of legitimate sync targets.

func (*Store) LocalPromptCreationIdentity

func (s *Store) LocalPromptCreationIdentity(syncID string) (session, inbox, project string, eligible bool, err error)

LocalPromptCreationIdentity returns verified local creation identity by exact sync ID. A tombstone is eligible only when its separately recorded local origin matches.

func (*Store) LocalSessionProvenance

func (s *Store) LocalSessionProvenance(id string) (owner string, eligible bool, err error)

LocalSessionProvenance returns the persisted owner and whether this store independently created the session. An unknown or absent row is never eligible for automatic cloud registration; project ownership alone is not proof.

func (*Store) MarkReviewed

func (s *Store) MarkReviewed(id int64) error

MarkReviewed resets an observation's review_after using its type's configured decay offset. Types without a decay offset return to a NULL review_after value. The reset and its sync upsert are committed atomically.

func (*Store) MarkReviewedForProject

func (s *Store) MarkReviewedForProject(id int64, project string) error

MarkReviewedForProject resets an observation's review lifecycle only when it remains owned by project at the mutation boundary.

func (*Store) MarkSyncAuthRequired

func (s *Store) MarkSyncAuthRequired(targetKey, message string) error

func (*Store) MarkSyncBlocked

func (s *Store) MarkSyncBlocked(targetKey, reasonCode, message string) error

func (*Store) MarkSyncBlockedAfterSuccess

func (s *Store) MarkSyncBlockedAfterSuccess(targetKey, reasonCode, message string) error

MarkSyncBlockedAfterSuccess atomically records a successful inbound poll while retaining the final degraded state that blocks outbound replication.

func (*Store) MarkSyncFailure

func (s *Store) MarkSyncFailure(targetKey, message string, backoffUntil time.Time) error

func (*Store) MarkSyncFailureWithReason

func (s *Store) MarkSyncFailureWithReason(targetKey, reasonCode, message string, backoffUntil time.Time) error

MarkSyncFailureWithReason records a degraded failure while preserving its reason code.

func (*Store) MarkSyncHealthy

func (s *Store) MarkSyncHealthy(targetKey string) error

func (*Store) MarkSyncPaused

func (s *Store) MarkSyncPaused(targetKey, message string) error

func (*Store) MarkSyncPending

func (s *Store) MarkSyncPending(targetKey string) error

func (*Store) MaxObservationLength

func (s *Store) MaxObservationLength() int

MaxObservationLength returns the configured maximum content length for observations.

func (*Store) MaxPendingSyncMutationSeq

func (s *Store) MaxPendingSyncMutationSeq(targetKey string) (int64, error)

MaxPendingSyncMutationSeq returns the highest sequence eligible for pending sync on targetKey, or zero when none exists. Like ListPendingSyncMutationsAfterSeq, only unacknowledged pending mutations for enrolled or global projects qualify; sync_state counters do not determine this bound.

func (*Store) MergeExplicitProjectVariants

func (s *Store) MergeExplicitProjectVariants(sources []string, canonical string) (*MergeResult, error)

MergeExplicitProjectVariants admits explicitly named separator and normalization variants for admin use.

func (*Store) MergeProjects

func (s *Store) MergeProjects(sources []string, canonical string) (*MergeResult, error)

MergeProjects migrates all records from each source project name into the canonical name. Every source must normalize to the canonical name; sources that exactly equal the canonical name or have no records are skipped. All updates are performed inside a single transaction for atomicity.

func (*Store) MigrateProject

func (s *Store) MigrateProject(oldName, newName string) (*MigrateResult, error)

func (*Store) ObservationOperationRecorded added in v3.3.0

func (s *Store) ObservationOperationRecorded(operationID string) (bool, error)

ObservationOperationRecorded is a cheap existence probe for the operation ledger. It returns true when any row exists for the operation ID, including tombstones left by hard deletion, so callers can short-circuit session ownership pre-validation for committed operations.

func (*Store) ObservationsNeedingReview

func (s *Store) ObservationsNeedingReview(project string, limit int) ([]Observation, error)

ObservationsNeedingReview returns non-deleted observations whose review_after has passed. An empty project searches all projects, matching existing browse/search conventions.

func (*Store) PassiveCapture

func (s *Store) PassiveCapture(p PassiveCaptureParams) (*PassiveCaptureResult, error)

PassiveCapture extracts learnings from text and saves them as observations. It deduplicates against existing observations using content hash matching.

func (*Store) PinObservation

func (s *Store) PinObservation(id int64) error

func (*Store) PinnedObservations

func (s *Store) PinnedObservations(project, scope string) ([]Observation, error)

PinnedObservations returns every pinned observation for project/scope, most-recent-first, with no row limit — pinning is an explicit, hand-bounded action, so returning all pinned rows has always been the legacy default. Callers that need a cap (e.g. FormatContextWithOptions via ContextOptions.Pinned) use the unexported pinnedObservationsLimit helper, which this delegates to with limit=0 ("no LIMIT clause", i.e. unbounded).

func (*Store) PlanLegacyEmptyPromptDiscard

func (s *Store) PlanLegacyEmptyPromptDiscard(project string, seq int64) (LegacyEmptyPromptDiscardPlan, error)

PlanLegacyEmptyPromptDiscard is database- and filesystem-read-only. It accepts one exact project and journal sequence, never an implicit bulk selection.

func (*Store) PlanSessionIdentityRepair

func (s *Store) PlanSessionIdentityRepair(sourceID, replacementID string) (SessionIdentityRepairPlan, error)

func (*Store) PreviewExplicitProjectMerge

func (s *Store) PreviewExplicitProjectMerge(source, canonical string) (*ExplicitProjectMergePreview, error)

PreviewExplicitProjectMerge reads one consistent SQLite transaction without writing. A later apply can observe different data and return different counts.

func (*Store) PreviewPromptSource

func (s *Store) PreviewPromptSource(syncID string) (PromptSourcePreview, bool, error)

PreviewPromptSource reads one exact sync ID from live prompts or tombstones. Ambiguous or incomplete observations return no preview; project is prompt data only.

func (*Store) ProjectExists

func (s *Store) ProjectExists(name string) (bool, error)

ProjectExists returns true if the named project has at least one record in any of observations, sessions, prompts, or enrollment tables. Uses a single UNION ALL LIMIT 1 query for efficiency (REQ-315). The sync_enrolled_projects branch ensures a project enrolled via EnrollProject() without any other data is still recognized (JC1).

func (*Store) ProjectHistory added in v3.1.0

func (s *Store) ProjectHistory(directory string) ([]string, error)

ProjectHistory returns weak scope evidence for an exact directory. Session history can be explicit or imported; it does not establish project ownership. This query is needed only before first binding creation, not on each read.

func (*Store) PruneProject

func (s *Store) PruneProject(project string) (*PruneResult, error)

PruneProject removes prompts and sessions without observations for a project that has zero active observations. Soft-deleted observations and their sessions are retained.

func (*Store) QuarantineIrreparableSyncMutations

func (s *Store) QuarantineIrreparableSyncMutations(targetKey, project string, apply bool) (SyncMutationQuarantineReport, error)

QuarantineIrreparableSyncMutations explicitly disposes of pending mutations the existing legacy validator proves cannot be repaired from local state. It never acknowledges, rewrites, or deletes a mutation.

func (*Store) ReadSQLiteLockSnapshot

func (s *Store) ReadSQLiteLockSnapshot(ctx context.Context) (SQLiteLockSnapshot, error)

ReadSQLiteLockSnapshot returns SQLite lock-related PRAGMA values without starting an application write transaction.

func (*Store) RearmEligibleDeadRelationsForScope

func (s *Store) RearmEligibleDeadRelationsForScope(targetKey, project string) (int, error)

RearmEligibleDeadRelationsForScope gives a retry-cap dead relation one fresh replay window only when its original retryable mutation is now satisfiable in this exact target/project scope. Terminal dead evidence is deliberately left untouched: malformed payloads, mismatched identities, unsupported operations, hash-keyed rows, session dead letters, and non-FK failures cannot satisfy every eligibility predicate below.

func (*Store) RecentObservations

func (s *Store) RecentObservations(project, scope string, limit int) ([]Observation, error)

func (*Store) RecentPrompts

func (s *Store) RecentPrompts(project string, limit int) ([]Prompt, error)

func (*Store) RecentSessions

func (s *Store) RecentSessions(project string, limit int) ([]SessionSummary, error)

A database upgraded from the schema where sessions.project was nullable still carries rows that identify no project, so the column is read through ifnull(): an unscoped listing reads every session row and must not die on one of them.

func (*Store) ReclassifyOrphanedPendingRelations

func (s *Store) ReclassifyOrphanedPendingRelations() (OrphanedPendingRelationReclassificationResult, error)

ReclassifyOrphanedPendingRelations moves every pending relation whose source AND target observations are absent from the active observation set into the audited `orphaned` disposition, the same terminal state the hard-delete orphaning writers already produce. The SQLite writer lock is acquired first and covers both the pre-apply backup and the reclassification, so no concurrent write can land between the snapshot and the apply it protects (the backup comes from a separate connection — the store pool is single-connection and is pinned by the held transaction). The predicate is revalidated inside the transaction so a stale or concurrent judgment can never be overwritten, and — matching those existing orphaning writers — no sync journal mutation is emitted: the reclassification is local audit hygiene, not replicated state. The operation is idempotent; a second run finds no pending candidates and changes nothing.

func (*Store) RecordSyncedChunk

func (s *Store) RecordSyncedChunk(chunkID string) error

RecordSyncedChunk marks a local-target chunk as imported/exported.

func (*Store) RecordSyncedChunkForTarget

func (s *Store) RecordSyncedChunkForTarget(targetKey, chunkID string) error

RecordSyncedChunkForTarget marks a chunk as imported/exported for a target.

func (*Store) RegisterIsolatedSession

func (s *Store) RegisterIsolatedSession(id, project string, resume bool) (string, error)

RegisterIsolatedSession atomically validates the root and selected continuation before any ownership repair, sync mutation, or lease renewal. It never clears a nonblank directory and uses project-owned registration rules with no directory. An existing shared row is refused whatever produced it (runtime registration, Cloud pull or local import), because isolated registration never changes a persisted ownership mode and a shared lease would accept cross-project writes.

func (*Store) RegisterIsolatedSessionTimed added in v3.3.0

func (s *Store) RegisterIsolatedSessionTimed(id, project string, resume bool, timings *WriteTimings) (string, error)

RegisterIsolatedSessionTimed records database-stage timings without changing isolated-session validation or persistence.

func (*Store) ReleaseSyncLease

func (s *Store) ReleaseSyncLease(targetKey, owner string) error

func (*Store) RemirrorProject

func (s *Store) RemirrorProject(project string) error

RemirrorProject enqueues a fresh current-state replay for an enrolled project. It leaves historical delivery rows untouched so recovery remains auditable.

func (*Store) RepairCloudUpgrade

func (s *Store) RepairCloudUpgrade(project string, apply bool) (CloudUpgradeRepairReport, error)

func (*Store) RepairObservationMutationTitles

func (s *Store) RepairObservationMutationTitles(project string, apply bool) (SyncMutationTitleRepairReport, error)

RepairObservationMutationTitles restores a missing payload title from a matching local observation's current title, or derives a blank source title from its content. It deliberately does not enqueue a replacement mutation: the existing journal row keeps its sequence and all delivery state while only its frozen payload is corrected.

func (*Store) RepairObservationSourceTitles

func (s *Store) RepairObservationSourceTitles(project string, apply bool) (ObservationSourceTitleRepairReport, error)

RepairObservationSourceTitles restores only source titles that can be derived from the first non-empty line of their own content. It never invents content or type. When no pending local mutation exists and the repaired row is otherwise sync-valid, it emits one canonical upsert for the local repair.

func (*Store) RepairPendingSessionDirectories

func (s *Store) RepairPendingSessionDirectories(project string, apply bool) ([]SyncMutationDirectoryRepairAction, error)

RepairPendingSessionDirectories plans or applies only session directory backfills. It does not require cloud enrollment and leaves journal metadata unchanged.

func (*Store) ReplayDeferred

func (s *Store) ReplayDeferred() (ReplayDeferredResult, error)

ReplayDeferred retries every deferred row, including legacy-unscoped rows. It is reserved for deliberate administrative operations such as conflict recovery.

func (*Store) ReplayDeferredForScope

func (s *Store) ReplayDeferredForScope(targetKey, project string) (result ReplayDeferredResult, err error)

ReplayDeferredForScope retries deferred rows for one sync target and optional project. An empty project includes all safely target-scoped rows for that target; an empty target is reserved for ReplayDeferred's administrative global replay. Legacy-unscoped rows are excluded whenever a target or project scope is supplied.

It retries rows with apply_status='deferred' (up to 50 per call, ordered by first_seen_at). For each row:

  • Calls applyPulledMutationTx inside a transaction.
  • On success: the apply itself deletes the deferred row (applyRelationUpsertTx already includes DELETE FROM sync_apply_deferred on success path).
  • On ErrRelationFKMissing or ErrRelationProjectMismatch: increments retry_count; if retry_count reaches 5, marks apply_status='dead'. Otherwise updates last_error + last_attempted_at.
  • On ErrApplyDead or other decode errors: marks apply_status='dead'.

Dead rows are never retried. Idempotent: calling twice in one cycle does not double-retry because successful rows are deleted and failed rows update retry_count in place.

Returns counts (retried, succeeded, failed, dead) for caller logging.

func (*Store) RescueNullProjectOwnership

func (s *Store) RescueNullProjectOwnership(p ProjectRescueParams) (*ProjectRescueResult, error)

RescueNullProjectOwnership assigns an explicit project only to selected legacy records with NULL ownership and enqueues their missing canonical mutations.

func (*Store) ResolveRuntimeSessionWithOwnershipMode added in v3.2.0

func (s *Store) ResolveRuntimeSessionWithOwnershipMode(root, project, directory, mode string) (string, error)

ResolveRuntimeSessionWithOwnershipMode reads the authoritative live binding; unlike resume registration it cannot create or advance a continuation.

func (*Store) RestoreOrphanedObservationSessions

func (s *Store) RestoreOrphanedObservationSessions(actions []OrphanedSessionPlaceholder) ([]OrphanedSessionPlaceholder, error)

RestoreOrphanedObservationSessions creates immediately-ended, local-only placeholders for confirmed missing session references. It validates the current observation evidence and any existing session ownership inside one transaction, so a stale or direct caller cannot attach a session ID to the wrong project. The placeholder's observation count and start time are re-derived from the current observations in the same transaction, so stale planned metadata can never persist. It never updates observations or emits sync mutations.

func (*Store) ResumeSessionWithOwnershipMode

func (s *Store) ResumeSessionWithOwnershipMode(id, project, directory, mode string) (string, error)

ResumeSessionWithOwnershipMode atomically selects and registers a live runtime identity. Ended rows remain terminal; ordinary and MCP registrations do not opt in.

func (*Store) ResumeSessionWithOwnershipModeTimed added in v3.3.0

func (s *Store) ResumeSessionWithOwnershipModeTimed(id, project, directory, mode string, timings *WriteTimings) (string, error)

ResumeSessionWithOwnershipModeTimed records database-stage timings without changing runtime-session continuation selection.

func (*Store) RollbackCloudUpgrade

func (s *Store) RollbackCloudUpgrade(project string) (CloudUpgradeState, error)

func (*Store) SaveCloudUpgradeState

func (s *Store) SaveCloudUpgradeState(state CloudUpgradeState) error

func (*Store) SaveRelation

func (s *Store) SaveRelation(p SaveRelationParams) (*Relation, error)

SaveRelation inserts a new pending relation row. The SyncID field must be unique (enforced by the UNIQUE constraint on memory_relations.sync_id).

func (*Store) ScanAllProjects

func (s *Store) ScanAllProjects(opts ScanOptions) (ScanResult, error)

func (*Store) ScanProject

func (s *Store) ScanProject(opts ScanOptions) (ScanResult, error)

ScanProject walks one ID-ordered observation page in the given project (filtered by Since) and calls FindCandidates with SkipInsert=true for each row. If Apply is true and below MaxInsert cap, each new candidate pair is inserted as a pending relation (after a pre-check to skip already-related pairs).

Phase 4 extension: when ScanOptions.Semantic is true, after the FTS5 candidate collection a bounded worker pool calls Runner.Compare on each pair. Applied scans persist judged verdicts via JudgeBySemantic, while dry-runs report their semantic results without persistence. Semantic=false (zero value) preserves Phase 3 behaviour exactly.

Returns a ScanResult with counts, a continuation cursor when another page remains, and whether an insert or semantic cap was hit.

func (*Store) Search

func (s *Store) Search(query string, opts SearchOptions) ([]SearchResult, error)

Search preserves the original non-context API for callers that do not need cancellation.

func (*Store) SearchContext

func (s *Store) SearchContext(ctx context.Context, query string, opts SearchOptions) ([]SearchResult, error)

SearchContext searches observations while honoring cancellation from the caller, including while materializing rows.

func (*Store) SearchPreviewsContext

func (s *Store) SearchPreviewsContext(ctx context.Context, query string, opts SearchOptions) ([]SearchPreviewResult, error)

SearchPreviewsContext searches using the same ranking and filters as SearchContext while selecting only a bounded Unicode-safe content preview.

func (*Store) SearchPrompts

func (s *Store) SearchPrompts(query string, project string, limit int) ([]Prompt, error)

func (*Store) SessionObservations

func (s *Store) SessionObservations(sessionID string, limit int) ([]Observation, error)

SessionObservations returns all observations for a specific session.

func (*Store) SkipAckNonEnrolledMutations

func (s *Store) SkipAckNonEnrolledMutations(targetKey string) (int64, error)

SkipAckNonEnrolledMutations acks (marks as skipped) all pending mutations that belong to non-enrolled projects, preventing journal bloat. Empty-project mutations are never skipped — they always sync regardless of enrollment.

func (*Store) StartSession

func (s *Store) StartSession(id, project, directory string) error

StartSession registers a shared runtime session or renews its local lease.

func (*Store) StartSessionWithOwnershipMode

func (s *Store) StartSessionWithOwnershipMode(id, project, directory, mode string) error

StartSessionWithOwnershipMode registers a runtime session or renews its local lease. It preserves the existing session identity and refuses to reopen an ended session; EndSession remains terminal truth.

func (*Store) StartSessionWithOwnershipModeTimed added in v3.3.0

func (s *Store) StartSessionWithOwnershipModeTimed(id, project, directory, mode string, timings *WriteTimings) error

StartSessionWithOwnershipModeTimed records database-stage timings without changing session-registration behavior.

func (*Store) Stats

func (s *Store) Stats() (*Stats, error)

func (*Store) StatsProject

func (s *Store) StatsProject(project string) (*Stats, error)

StatsProject returns aggregate counts restricted to one project. Selection policy belongs to callers; this method only applies the supplied scope.

func (*Store) SupersedeUnenrolledLegacyMutations

func (s *Store) SupersedeUnenrolledLegacyMutations(targetKey, project string, apply bool) (SyncMutationSupersedeReport, error)

SupersedeUnenrolledLegacyMutations retires pending local upserts only when local delete evidence proves the entity is no longer current. It preserves the mutation, acknowledgement state, and recorded evidence; it never claims cloud delivery. An empty project evaluates every unenrolled project for doctor.

func (*Store) Timeline

func (s *Store) Timeline(observationID int64, before, after int) (*TimelineResult, error)

func (*Store) UnenrollProject

func (s *Store) UnenrollProject(project string) error

UnenrollProject removes a project from cloud sync enrollment. Idempotent — unenrolling a non-enrolled project is a no-op.

func (*Store) UnpinObservation

func (s *Store) UnpinObservation(id int64) error

func (*Store) UpdateObservation

func (s *Store) UpdateObservation(id int64, p UpdateObservationParams) (*Observation, error)

func (*Store) UpdateObservationForProject

func (s *Store) UpdateObservationForProject(id int64, expected string, p UpdateObservationParams) (*Observation, error)

UpdateObservationForProject atomically asserts stored ownership and updates the row.

type SyncMutation

type SyncMutation struct {
	Seq                 int64   `json:"seq"`
	TargetKey           string  `json:"target_key"`
	Entity              string  `json:"entity"`
	EntityKey           string  `json:"entity_key"`
	Op                  string  `json:"op"`
	Payload             string  `json:"payload"`
	Source              string  `json:"source"`
	Project             string  `json:"project"`
	OccurredAt          string  `json:"occurred_at"`
	AckedAt             *string `json:"acked_at,omitempty"`
	Disposition         string  `json:"disposition"`
	DispositionReason   string  `json:"disposition_reason,omitempty"`
	DispositionEvidence string  `json:"disposition_evidence,omitempty"`
	DispositionAt       *string `json:"disposition_at,omitempty"`
}

type SyncMutationDirectoryRepairAction

type SyncMutationDirectoryRepairAction struct {
	Seq       int64  `json:"seq"`
	Project   string `json:"project"`
	EntityKey string `json:"entity_key"`
}

SyncMutationDirectoryRepairAction identifies a pending session payload repaired from local state.

type SyncMutationPayloadValidation

type SyncMutationPayloadValidation struct {
	Entity        string   `json:"entity"`
	Op            string   `json:"op"`
	EntityKey     string   `json:"entity_key,omitempty"`
	MissingFields []string `json:"missing_fields,omitempty"`
	ReasonCode    string   `json:"reason_code,omitempty"`
	Message       string   `json:"message,omitempty"`
}

SyncMutationPayloadValidation describes deterministic required-field issues in a pending sync mutation payload.

func ValidateSyncMutationPayload

func ValidateSyncMutationPayload(entity, op, payload, entityKey string) SyncMutationPayloadValidation

ValidateSyncMutationPayload performs pure required-field validation for sync payloads. It is intentionally conservative: malformed/empty/unsupported payloads are reported as manual blocks, while complete payloads return an empty validation.

type SyncMutationQuarantineAction

type SyncMutationQuarantineAction struct {
	Seq        int64  `json:"seq"`
	Project    string `json:"project"`
	Entity     string `json:"entity"`
	EntityKey  string `json:"entity_key"`
	Op         string `json:"op"`
	ReasonCode string `json:"reason_code"`
	Message    string `json:"message"`
	Evidence   string `json:"evidence"`
}

SyncMutationQuarantineAction records one deterministic local quarantine.

type SyncMutationQuarantineReport

type SyncMutationQuarantineReport struct {
	Project string                         `json:"project,omitempty"`
	Applied bool                           `json:"applied"`
	Actions []SyncMutationQuarantineAction `json:"actions"`
}

SyncMutationQuarantineReport is the explicit local recovery result.

type SyncMutationSupersedeAction

type SyncMutationSupersedeAction struct {
	Seq        int64  `json:"seq"`
	Project    string `json:"project"`
	Entity     string `json:"entity"`
	EntityKey  string `json:"entity_key"`
	Op         string `json:"op"`
	ReasonCode string `json:"reason_code"`
	Evidence   string `json:"evidence"`
}

SyncMutationSupersedeAction records a local journal row retired because a tombstone or deleted source row proves that its prior upsert is obsolete.

type SyncMutationSupersedeReport

type SyncMutationSupersedeReport struct {
	Project string                        `json:"project,omitempty"`
	Applied bool                          `json:"applied"`
	Actions []SyncMutationSupersedeAction `json:"actions"`
}

SyncMutationSupersedeReport is the explicit, non-acknowledging local recovery result for stale pending upserts.

type SyncMutationTitleRepairAction

type SyncMutationTitleRepairAction struct {
	Seq       int64  `json:"seq"`
	Project   string `json:"project"`
	Entity    string `json:"entity"`
	EntityKey string `json:"entity_key"`
	Op        string `json:"op"`
	Title     string `json:"title"`
}

SyncMutationTitleRepairAction records a title restored from its matching local observation without creating another sync mutation.

type SyncMutationTitleRepairReport

type SyncMutationTitleRepairReport struct {
	Project string                          `json:"project,omitempty"`
	Applied bool                            `json:"applied"`
	Actions []SyncMutationTitleRepairAction `json:"actions"`
}

SyncMutationTitleRepairReport is the local recovery result for title-only observation upserts. Repairs remain pending so the original sequence is delivered normally.

type SyncState

type SyncState struct {
	TargetKey           string  `json:"target_key"`
	Lifecycle           string  `json:"lifecycle"`
	LastEnqueuedSeq     int64   `json:"last_enqueued_seq"`
	LastAckedSeq        int64   `json:"last_acked_seq"`
	LastPulledSeq       int64   `json:"last_pulled_seq"`
	ConsecutiveFailures int     `json:"consecutive_failures"`
	BackoffUntil        *string `json:"backoff_until,omitempty"`
	LeaseOwner          *string `json:"lease_owner,omitempty"`
	LeaseUntil          *string `json:"lease_until,omitempty"`
	ReasonCode          *string `json:"reason_code,omitempty"`
	ReasonMessage       *string `json:"reason_message,omitempty"`
	LastError           *string `json:"last_error,omitempty"`
	LastSuccessAt       *string `json:"last_success_at,omitempty"`
	UpdatedAt           string  `json:"updated_at"`
}

type SyncTargetState

type SyncTargetState struct {
	TargetKey        string `json:"target_key"`
	Lifecycle        string `json:"lifecycle"`
	UnackedMutations int    `json:"unacked_mutations"`
}

SyncTargetState summarizes one sync_state row for diagnostics. UnackedMutations counts journal rows still awaiting delivery for the target: rows stored directly under the target key, plus the project-scoped journal rows for cloud:<project> targets (those mutations live under the default cloud key and carry the project in their own column).

type TimelineEntry

type TimelineEntry struct {
	ID             int64   `json:"id"`
	SessionID      string  `json:"session_id"`
	Type           string  `json:"type"`
	Title          string  `json:"title"`
	Content        string  `json:"content"`
	ToolName       *string `json:"tool_name,omitempty"`
	Project        *string `json:"project,omitempty"`
	Scope          string  `json:"scope"`
	TopicKey       *string `json:"topic_key,omitempty"`
	RevisionCount  int     `json:"revision_count"`
	DuplicateCount int     `json:"duplicate_count"`
	LastSeenAt     *string `json:"last_seen_at,omitempty"`
	CreatedAt      string  `json:"created_at"`
	UpdatedAt      string  `json:"updated_at"`
	DeletedAt      *string `json:"deleted_at,omitempty"`
	IsFocus        bool    `json:"is_focus"` // true for the anchor observation
}

type TimelineResult

type TimelineResult struct {
	Focus        Observation     `json:"focus"`        // The anchor observation
	Before       []TimelineEntry `json:"before"`       // Observations before the focus (chronological)
	After        []TimelineEntry `json:"after"`        // Observations after the focus (chronological)
	SessionInfo  *Session        `json:"session_info"` // Session that contains the focus observation
	TotalInRange int             `json:"total_in_range"`
}

type TruncationMetadata

type TruncationMetadata struct {
	OriginalBytes int  `json:"original_bytes"`
	LimitBytes    int  `json:"limit_bytes"`
	Truncated     bool `json:"truncated"`
}

TruncationMetadata describes storage content processing after private-tag redaction.

type UpdateObservationParams

type UpdateObservationParams struct {
	Type     *string `json:"type,omitempty"`
	Title    *string `json:"title,omitempty"`
	Content  *string `json:"content,omitempty"`
	Find     *string `json:"find,omitempty"`
	Replace  *string `json:"replace,omitempty"`
	Project  *string `json:"project,omitempty"`
	Scope    *string `json:"scope,omitempty"`
	TopicKey *string `json:"topic_key,omitempty"`
}

type WriteTimings added in v3.3.0

type WriteTimings struct {
	ConnectionWait time.Duration
	Transaction    time.Duration
	Commit         time.Duration
	Attempts       int
}

WriteTimings collects one write operation's local database stages. It holds durations only; callers must not add request values or identifiers.

Jump to

Keyboard shortcuts

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