state

package
v0.1.0-rc.2 Latest Latest
Warning

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

Go to latest
Published: Jul 5, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

snapshot_reads.go holds the producer-side reads for full-state snapshot production (P4-HUB-11, `devstrap hub compact`): the derived namespace map, surviving tombstones, per-device chain anchors, and the current local HLC. They mirror the wire shapes in internal/sync/snapshot.go but live in state to avoid an import cycle (sync imports state); internal/sync.BuildSnapshot maps them across.

Index

Constants

This section is empty.

Variables

View Source
var ErrDivergentEvent = errors.New("event id already exists with different immutable content")
View Source
var ErrEventHashChain = errors.New("event prev_event_hash chain break")
View Source
var ErrEventVerification = errors.New("event verification failed")
View Source
var ErrNotInitialized = errors.New("workspace is not initialized; run devstrap init")
View Source
var ErrWorkspaceIDMismatch = errors.New("workspace id mismatch")

ErrWorkspaceIDMismatch reports that an explicitly supplied workspace id (init --workspace-id) conflicts with the id this store was initialized under. There is no in-place rewrite path — the remedy is to remove the state home and re-run `devstrap init --join --workspace-id <id>` (P4-SEC-07).

Functions

func ContentHash

func ContentHash(payloadJSON string) string

func EffectiveKeyCustody

func EffectiveKeyCustody(recorded devicekeys.Custody) devicekeys.Custody

EffectiveKeyCustody applies the DEVSTRAP_NO_KEYCHAIN override to a recorded custody decision: when set, file custody is forced regardless of what was recorded, so headless/CI runs always use the file store (P6-XP-04). Otherwise the recorded decision (possibly unset) stands.

func EventSignaturePayload

func EventSignaturePayload(event Event) []byte

EventSignaturePayload is the LEGACY v1 signed payload (no DeviceID/Seq). It is retained only so verification can fall back to v1 for historical events; new signatures use EventSignaturePayloadV2.

func EventSignaturePayloadV2

func EventSignaturePayloadV2(event Event) []byte

EventSignaturePayloadV2 is the v2 signed payload: the v1 tuple plus DeviceID and Seq (P6-SYNC-04), keys in alphabetical order.

func ValidateDBFile

func ValidateDBFile(ctx context.Context, path string) error

ValidateDBFile runs quick_check + foreign_key_check on a standalone SQLite file (P6-DATA-04). It is the same validation Backup runs after VACUUM INTO, exported so `db restore` can prove an extracted state.db is intact before it is promoted into place.

Types

type AgentRun

type AgentRun struct {
	ID          string `json:"id"`
	NamespaceID string `json:"namespace_id"`
	WorktreeID  string `json:"worktree_id,omitempty"`
	Engine      string `json:"engine"`
	Task        string `json:"task"`
	PolicyID    string `json:"policy_id,omitempty"`
	Status      string `json:"status"`
	BaseRef     string `json:"base_ref,omitempty"`
	BaseSHA     string `json:"base_sha,omitempty"`
	Branch      string `json:"branch,omitempty"`
	LogPath     string `json:"log_path,omitempty"`
	DiffSummary string `json:"diff_summary,omitempty"`
	TestSummary string `json:"test_summary,omitempty"`
	RunnerPID   int    `json:"runner_pid,omitempty"`
}

type Conflict

type Conflict struct {
	ID          string `json:"id"`
	NamespaceID string `json:"namespace_id,omitempty"`
	Type        string `json:"type"`
	Status      string `json:"status"`
	DetailsJSON string `json:"details_json"`
}

type Device

type Device struct {
	ID               string `json:"id"`
	Name             string `json:"name"`
	OS               string `json:"os"`
	Arch             string `json:"arch"`
	Hostname         string `json:"hostname,omitempty"`
	PublicKey        string `json:"public_key,omitempty"`
	SigningPublicKey string `json:"signing_public_key,omitempty"`
	TrustState       string `json:"trust_state"`
}

type DraftSnapshot

type DraftSnapshot struct {
	ID                  string
	NamespaceID         string
	BlobRef             string
	ByteSize            int64
	FileCount           int64
	SourceEventHLC      int64
	SourceEventDeviceID string
	SourceEventID       string
}

DraftSnapshot records a content-addressed age_blob bundle for a non-git project (DRAFT-02).

type DraftSnapshotRef

type DraftSnapshotRef struct {
	NamespaceID string
	Path        string
	ByteSize    int64
	FileCount   int64
}

DraftSnapshotRef is the metadata needed to re-emit a superseding draft.snapshot.created event when a draft blob is rewrapped (P5-SEC-01).

type EnvProfile

type EnvProfile struct {
	ID          string `json:"id"`
	WorkspaceID string `json:"workspace_id"`
	Name        string `json:"name"`
	Provider    string `json:"provider"`
	Mode        string `json:"mode"`
}

type Event

type Event struct {
	ID            string
	WorkspaceID   string
	DeviceID      string
	Seq           int64
	HLC           int64
	Type          string
	PayloadJSON   string
	ContentHash   string
	DeviceSig     string
	PrevEventHash string
	CreatedAt     string
}

type GitRepo

type GitRepo struct {
	NamespaceID   string `json:"namespace_id"`
	RemoteURL     string `json:"remote_url"`
	RemoteKey     string `json:"remote_key"`
	DefaultBranch string `json:"default_branch"`
	CloneFilter   string `json:"clone_filter,omitempty"`
	LFSPolicy     string `json:"lfs_policy"`
	ForgeKind     string `json:"forge_kind,omitempty"`
}

type HeldKey

type HeldKey struct {
	Epoch  int64
	KID    string
	Origin string
}

HeldKey is one WCK this device holds metadata for: an (epoch, kid) pair plus how the key was obtained (P6-SEC-02).

type KeyGrantWait

type KeyGrantWait struct {
	Epoch     int64
	KID       string
	FirstSeen time.Time
}

KeyGrantWait is one still-open grace-window wait for a workspace key this device has seen ciphertext for but holds no key to (P6-SEC-03). KID is ” when the whole epoch is missing.

type NamespaceEntry

type NamespaceEntry struct {
	ID                    string `json:"id"`
	Path                  string `json:"path"`
	PathKey               string `json:"path_key"`
	Type                  string `json:"type"`
	DisplayName           string `json:"display_name,omitempty"`
	MaterializationPolicy string `json:"materialization_policy"`
	Status                string `json:"status"`
	SourceEventHLC        int64  `json:"source_event_hlc,omitempty"`
	SourceEventDeviceID   string `json:"source_event_device_id,omitempty"`
	SourceEventID         string `json:"source_event_id,omitempty"`
}

type ProjectStatus

type ProjectStatus struct {
	NamespaceEntry
	RemoteURL            string `json:"remote_url,omitempty"`
	RemoteKey            string `json:"remote_key,omitempty"`
	DefaultBranch        string `json:"default_branch,omitempty"`
	LFSPolicy            string `json:"lfs_policy,omitempty"`
	ForgeKind            string `json:"forge_kind,omitempty"`
	LocalPath            string `json:"local_path,omitempty"`
	MaterializationState string `json:"materialization_state,omitempty"`
	DirtyState           string `json:"dirty_state,omitempty"`
}

type RenameOutcome

type RenameOutcome int

RenameOutcome reports how a project.renamed event was applied.

const (
	// RenameApplied means the namespace entry was moved to the new path.
	RenameApplied RenameOutcome = iota
	// RenameSourceMissing means no active entry existed at the old path.
	RenameSourceMissing
	// RenameTargetConflict means the new path is already an active, distinct entry.
	RenameTargetConflict
	// RenameStale means the new path holds a newer tombstone (lost-delete guard).
	RenameStale
)

type SecretBinding

type SecretBinding struct {
	ID                string `json:"id"`
	EnvProfileID      string `json:"env_profile_id"`
	VarName           string `json:"var_name"`
	ProviderRef       string `json:"provider_ref,omitempty"`
	EncryptedValueRef string `json:"encrypted_value_ref,omitempty"`
	Required          bool   `json:"required"`
	NeedsRotation     bool   `json:"needs_rotation,omitempty"`
}

type SkippedEvent

type SkippedEvent struct {
	EventID     string
	DeviceID    string
	Seq         int64
	HLC         int64
	Reason      string
	FirstSeenAt time.Time
}

SkippedEvent is one durable record of an event EncryptedHub.Pull dropped from the batch (P6-SYNC-02): under the per-device Seq cursor the drop holds the origin device's cursor at a seq gap, and this row is the visibility for that retry wedge. Reasons: "unknown-envelope-version" (recoverable by upgrading devstrap; first_seen_at is its grace clock), "retired-enc-v1" (re-found the workspace), "plaintext-anti-downgrade" (the hub is serving plaintext where ciphertext is required).

type SnapshotChainAnchor

type SnapshotChainAnchor struct {
	DeviceID    string
	Seq         int64
	ContentHash string
	HLC         int64
}

SnapshotChainAnchor is one origin device's hash-chain anchor for the snapshot: the content hash of the last event the snapshot covers for that device (at seq = floor-1).

type SnapshotEntry

type SnapshotEntry struct {
	Path                  string
	PathKey               string
	Type                  string
	DisplayName           string
	MaterializationPolicy string
	Status                string
	SourceEventHLC        int64
	SourceEventDeviceID   string
	SourceEventID         string
	Git                   *SnapshotEntryGit
	Draft                 *SnapshotEntryDraft
}

SnapshotEntry is one active namespace-map row with its source-event coordinates plus optional git/draft sub-objects, read for snapshot production.

type SnapshotEntryDraft

type SnapshotEntryDraft struct {
	BlobRef             string
	ByteSize            int64
	FileCount           int64
	SourceEventHLC      int64
	SourceEventDeviceID string
	SourceEventID       string
}

SnapshotEntryDraft is the latest draft-bundle pointer for a draft project.

type SnapshotEntryGit

type SnapshotEntryGit struct {
	RemoteURL     string
	RemoteKey     string
	DefaultBranch string
	LFSPolicy     string
	ForgeKind     string
}

SnapshotEntryGit mirrors the git_repos row attached to a namespace entry. CloneFilter/SparseConfig are deliberately omitted: like the event apply path, import re-derives the default blobless filter and a bootstrapped device re-derives sparse config on materialization.

type SnapshotTombstone

type SnapshotTombstone struct {
	PathKey             string
	TombstoneHLC        int64
	SourceEventDeviceID string
	SourceEventID       string
}

SnapshotTombstone is one surviving deleted entry (kept so a stale add cannot resurrect a deleted path on a bootstrapped device).

type Store

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

func Open

func Open(ctx context.Context, path string) (*Store, error)

func (*Store) ActiveKeyEpochAge

func (s *Store) ActiveKeyEpochAge(ctx context.Context) (int64, time.Time, error)

ActiveKeyEpochAge returns the ACTIVE (highest) WCK epoch and when this device first recorded a key at it (P4-SEC-07 periodic rotation). When several kids coexist at the epoch (P6-SEC-02 collision), the EARLIEST created_at wins — the conservative choice: the rotation clock starts at the first key this device saw at the epoch, so coexisting kids can only make a rotation earlier, never suppress one. Epoch 0 (no key yet) returns a zero time and no error.

func (*Store) AdvanceHubCursor

func (s *Store) AdvanceHubCursor(ctx context.Context, hubID string, hlc int64) error

AdvanceHubCursor records that all events up to hlc have been applied from the given hub source (EAGER-02). It only moves the cursor forward: a smaller hlc than the stored value is ignored so a re-pull of stale events cannot regress the cursor.

func (*Store) AdvanceHubDeviceCursor

func (s *Store) AdvanceHubDeviceCursor(ctx context.Context, hubID, deviceID string, seq int64) error

AdvanceHubDeviceCursor records that every event from deviceID up to seq has been pulled and consumed from the given hub (P5-SYNC-01). Forward-only: a smaller seq than the stored value is ignored so a stale re-pull cannot regress the cursor.

func (*Store) AdvancePushSeqCursor

func (s *Store) AdvancePushSeqCursor(ctx context.Context, hubID string, seq int64) error

AdvancePushSeqCursor advances the local device's push watermark for a hub (P5-SYNC-01). Forward-only, like AdvanceHubDeviceCursor.

func (*Store) AgentRunByID

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

func (*Store) AllBlobRefs

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

AllBlobRefs returns every distinct age_blob:<sha256> reference in the store (env bindings + draft snapshots) (HUB-04/HUB-05). These are the blobs that may need rewrapping on device revoke or GC when unreferenced.

func (*Store) ApprovedDeviceSigningKey

func (s *Store) ApprovedDeviceSigningKey(ctx context.Context, deviceID string) (string, bool, error)

ApprovedDeviceSigningKey returns the signing public key of a locally known, APPROVED device, or ok=false when the device is unknown, not approved, or has no signing key recorded. It is the trust gate for snapshot recovery (P4-SYNC-02): a retention manifest / snapshot producer must be a locally pinned approved device before its wholesale state replacement is trusted, the same fail-closed posture VerifyRemoteEvent applies to must-verify events.

func (*Store) ApprovedRecipients

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

ApprovedRecipients returns the age recipient public keys for the local device plus all approved remote devices (HUB-04 re-encryption recipient set).

func (*Store) Backup

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

func (*Store) BlobRefCount

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

BlobRefCount returns a map of age_blob ref → reference count (HUB-05). A blob is safe to GC only when its count is zero and it is older than the retention/snapshot horizon (the latter gate is deferred until full-state snapshot exchange exists).

func (*Store) ChainAnchorsForFloors

func (s *Store) ChainAnchorsForFloors(ctx context.Context, floors map[string]int64) ([]SnapshotChainAnchor, error)

ChainAnchorsForFloors returns, for each device in floors, the hash-chain anchor a snapshot-bootstrapped device needs to verify that device's first post-floor event (P4-HUB-11): the content hash of the event at seq = floor-1. It reads the local events table first; when that row is absent — possible only for a device this machine itself imported via snapshot, so it holds no events below the floor — it falls back to the existing sync_chain_anchors row. Devices with floor <= 1 (nothing below the floor) and devices for which neither source exists are skipped. Deterministic device order.

func (*Store) ClearAllBindingRotation

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

ClearAllBindingRotation clears the needs_rotation flag on every binding (P5-PROD-03 `--all`), used when the operator asserts all flagged secrets have been rotated at their source. Returns the number of bindings cleared.

func (*Store) ClearPendingHubDelete

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

ClearPendingHubDelete removes a drained entry from the queue (P5-PROD-02).

func (*Store) ClearRotationForProject

func (s *Store) ClearRotationForProject(ctx context.Context, namespaceID string) (int, error)

ClearRotationForProject clears the needs_rotation flag on a project's env bindings (P5-PROD-03), used after `devstrap env rotate` re-captures the value at its rotated source. Returns the number of bindings cleared.

func (*Store) Close

func (s *Store) Close() error

func (*Store) ConflictByID

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

ConflictByID returns a single conflict by id (any status), used by `conflicts show`/`resolve` (PROD-06).

func (*Store) CountAgentRunsByStatus

func (s *Store) CountAgentRunsByStatus(ctx context.Context, status string) (int, error)

func (*Store) CountOpenConflicts

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

func (*Store) CountSecretBindingsNeedingRotation

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

CountSecretBindingsNeedingRotation reports how many secret values are flagged for rotation (e.g. after a device revocation).

func (*Store) CountTombstonesBelowHLC

func (s *Store) CountTombstonesBelowHLC(ctx context.Context, beforeHLC int64) (int, error)

CountTombstonesBelowHLC returns how many deleted namespace entries have a tombstone HLC strictly below beforeHLC, i.e. how many rows GCTombstones would purge. It backs the `hub compact --dry-run` tombstone-GC preview without mutating state.

func (*Store) CurrentDevice

func (s *Store) CurrentDevice(ctx context.Context) (Device, error)

func (*Store) CurrentHLC

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

CurrentHLC returns the local device's current HLC clock without minting an event (P4-HUB-11): the max of the recorded device clock and the highest event HLC held in the workspace. A produced snapshot is stamped with this so that, after an importer applies ReceiveRemoteHLC(snap.HLC), every later local event sorts after everything the snapshot covers.

func (*Store) CurrentKeyEpoch

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

CurrentKeyEpoch returns the highest WCK epoch this device holds a key for (P4-SEC-07), or 0 if none has been bootstrapped. The active epoch is the one under which new outgoing namespace events are envelope-encrypted; revoke rotates it to epoch+1 for forward secrecy.

func (*Store) Down

func (s *Store) Down() error

func (*Store) DraftBlobRefs

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

DraftBlobRefs returns the distinct age_blob refs held by draft snapshots (P5-SEC-04). These are the only blobs synced through the hub.

func (*Store) DraftProjectLimits

func (s *Store) DraftProjectLimits(ctx context.Context, namespaceID string) (int64, int64, error)

DraftProjectLimits returns the per-project max_bytes and max_files for a draft project (DRAFT-04). Defaults are applied when no row exists.

func (*Store) DraftSnapshotsForBlobRef

func (s *Store) DraftSnapshotsForBlobRef(ctx context.Context, ref string) ([]DraftSnapshotRef, error)

DraftSnapshotsForBlobRef returns the (namespace, path, size, count) of every active draft snapshot referencing ref (P5-SEC-01), so a rewrap can emit a superseding event carrying the new ref before the old hub blob is deleted.

func (*Store) EnsureDevice

func (s *Store) EnsureDevice(ctx context.Context, name string) (Device, error)

func (*Store) EnsureDraftProject

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

EnsureDraftProject creates a draft_projects row for a namespace if one does not exist, so limits and snapshot pointers are available (DRAFT-02/04).

func (*Store) EnsureRemoteDevice

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

EnsureRemoteDevice creates a placeholder device row for a remote device if it does not exist, so events from that device can satisfy the events FK constraint. The placeholder has trust_state='pending' and no keys; it is enriched via `devices enroll` before its events are signature-verified.

func (*Store) EnsureWorkspace

func (s *Store) EnsureWorkspace(ctx context.Context, name, rootPath string) error

func (*Store) EnsureWorkspaceWithID

func (s *Store) EnsureWorkspaceWithID(ctx context.Context, workspaceID, name, rootPath string) error

EnsureWorkspaceWithID adopts an explicitly supplied workspace id (P4-SEC-07 pairing: a joiner adopts the founder's id at init so both devices read the same hub prefix). The id must be born-correct — the singleton workspace index plus ON DELETE CASCADE on the child tables make post-hoc rewriting hazardous, so a store already initialized under a different id is refused with ErrWorkspaceIDMismatch rather than rewritten.

func (*Store) EnvBlobRefs

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

EnvBlobRefs returns the distinct age_blob refs held by encrypted env bindings (P5-SEC-04). These blobs are local-only and are never pushed to the hub, so the revoke rewrap must not upload or delete them there.

func (*Store) EnvProfileForProject

func (s *Store) EnvProfileForProject(ctx context.Context, namespaceID string) (EnvProfile, []SecretBinding, error)

func (*Store) EventByID

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

EventByID returns a single event by id. Used by conflict resolution (P5-SYNC-04) to recover the full payload of a losing variant so a chosen remote can be re-asserted with a fresh dominating event.

func (*Store) ForeignKeyCheck

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

func (*Store) GCTombstones

func (s *Store) GCTombstones(ctx context.Context, beforeHLC int64) (int, error)

GCTombstones permanently removes deleted namespace entries whose tombstone HLC is strictly below beforeHLC. Callers must pass the minimum HLC that every approved sync cursor has already passed, so no peer can still resurrect the entry with a stale add. Its first production caller is `hub compact --gc-tombstones`, which derives beforeHLC as the minimum HLC watermark across every approved device's signed sync ack (P4-SYNC-06). Returns the number of rows purged.

func (*Store) GetLocalMeta

func (s *Store) GetLocalMeta(ctx context.Context, key string) (string, bool, error)

GetLocalMeta reads a local, never-synced key/value metadata row (migration 00019), returning ok=false when the key is absent. Used by snapshot recovery to cache the highest verified per-device retention floor (P4-SYNC-02).

func (*Store) HasHubDeviceCursors

func (s *Store) HasHubDeviceCursors(ctx context.Context, hubID string) (bool, error)

HasHubDeviceCursors reports whether ANY per-device cursor row (pull or push) with a non-zero position exists for the given hub (P5-SYNC-01 founder gate): a device that has ever consumed hub content must never self-found a workspace key (P6-SEC-02).

func (*Store) HeldKeyEpochs

func (s *Store) HeldKeyEpochs(ctx context.Context) ([]int64, error)

HeldKeyEpochs returns every distinct epoch this device holds at least one WCK for, ascending (P4-SEC-07). Used by devices approve to wrap every held epoch's WCK to a newly-approved device so it can decrypt the entire history.

func (*Store) HeldKeys

func (s *Store) HeldKeys(ctx context.Context) ([]HeldKey, error)

HeldKeys returns every (epoch, kid) this device holds a WCK for, ordered by epoch then kid (P6-SEC-02).

func (*Store) HubCursor

func (s *Store) HubCursor(ctx context.Context, hubID string) (int64, error)

HubCursor returns the last HLC applied from the given hub source (EAGER-02). Returns 0 when no cursor exists yet (a fresh device pulls from the beginning).

func (*Store) HubDeviceCursors

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

HubDeviceCursors returns the per-origin-device transport cursor for a hub (P5-SYNC-01): device_id -> highest contiguous per-device seq pulled AND consumed. Devices with no row are absent (cursor 0 — pull from the beginning). Push-watermark rows live under a "push:<hubID>" hub_id and are not returned here.

func (*Store) InsertAgentRun

func (s *Store) InsertAgentRun(ctx context.Context, run AgentRun) (AgentRun, error)

func (*Store) InsertConflict

func (s *Store) InsertConflict(ctx context.Context, namespaceID, typ, detailsJSON string) error

func (*Store) InsertEvent

func (s *Store) InsertEvent(ctx context.Context, event Event) error

func (*Store) InsertLocalEvent

func (s *Store) InsertLocalEvent(ctx context.Context, event Event) (Event, error)

func (*Store) InsertLocalEventTx

func (s *Store) InsertLocalEventTx(ctx context.Context, tx *Tx, event Event) (Event, error)

InsertLocalEventTx stamps, signs, and inserts a local event in tx.

func (*Store) InsertWorktree

func (s *Store) InsertWorktree(ctx context.Context, wt Worktree) (Worktree, error)

func (*Store) KeyCustody

func (s *Store) KeyCustody(ctx context.Context) (devicekeys.Custody, error)

KeyCustody reports the recorded key-custody backend, or devicekeys.CustodyUnset when init has not recorded one yet (P6-XP-04).

func (*Store) LatestDraftSnapshot

func (s *Store) LatestDraftSnapshot(ctx context.Context, namespaceID string) (*DraftSnapshot, error)

LatestDraftSnapshot returns the most recent draft bundle snapshot for a project, or nil with no error when no snapshot exists (DRAFT-02).

func (*Store) ListAgentRuns

func (s *Store) ListAgentRuns(ctx context.Context) ([]AgentRun, error)

func (*Store) ListDevices

func (s *Store) ListDevices(ctx context.Context) ([]Device, error)

func (*Store) ListProjects

func (s *Store) ListProjects(ctx context.Context) ([]ProjectStatus, error)

func (*Store) ListWorktrees

func (s *Store) ListWorktrees(ctx context.Context) ([]Worktree, error)

func (*Store) LocalPendingEvents

func (s *Store) LocalPendingEvents(ctx context.Context, afterHLC int64) ([]Event, error)

LocalPendingEvents returns events originated by the local device with HLC strictly greater than afterHLC (SYNC-04). It bounds the push side of sync so a cycle re-uploads only new local-origin events, not the entire event log (including remote-origin events the hub already holds from their origin device). The push cursor is stored per hub as a "push:<hubID>" row in hub_cursors.

func (*Store) LocalPendingEventsBySeq

func (s *Store) LocalPendingEventsBySeq(ctx context.Context, afterSeq int64) ([]Event, error)

LocalPendingEventsBySeq returns events originated by the local device with Seq strictly greater than afterSeq, in Seq order (P5-SYNC-01 push side). Seq is the authoritative gapless per-device key, so unlike the retired `hlc >` selection this can never strand an event behind the watermark.

func (*Store) MarkEncryptedBindingsNeedingRotation

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

MarkEncryptedBindingsNeedingRotation flags every encrypted secret binding as requiring value rotation. It is invoked when a device is revoked or marked lost: that device could decrypt any blob it was a recipient of, so the values must be rotated at their source — rewrapping recipients alone does not revoke historical access. Returns the number of bindings flagged.

func (*Store) MarkWorktreeRemoved

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

func (*Store) Migrate

func (s *Store) Migrate() error

func (*Store) NoteMissingKeyGrant

func (s *Store) NoteMissingKeyGrant(ctx context.Context, epoch int64, kid string) (time.Time, error)

NoteMissingKeyGrant records (idempotently) that an event sealed under (epoch, kid) could not be decrypted because the key is not held, and returns the STABLE start of that epoch's grace window (P6-SEC-03). The returned time is the earliest first_seen_at across every kid recorded at the epoch — never reset by later sightings — so re-pulls cannot restart the window, and a hostile hub relabeling the unauthenticated envelope kid hint on each pull (the forged-kid stall from the P6-SEC-03 audit note) cannot mint a fresh window per label: the epoch's clock keeps running from the first sighting.

func (*Store) NoteSkippedEvent

func (s *Store) NoteSkippedEvent(ctx context.Context, ev Event, reason string) (time.Time, error)

NoteSkippedEvent records (idempotently) that an event was dropped by the pull for the given reason and returns the STABLE first-seen time of that (event, reason) — re-sightings on later pulls never restart the clock (P6-SYNC-02, mirroring NoteMissingKeyGrant).

func (*Store) OpenConflicts

func (s *Store) OpenConflicts(ctx context.Context) ([]Conflict, error)

func (*Store) OpenConflictsByType

func (s *Store) OpenConflictsByType(ctx context.Context, typ string) ([]Conflict, error)

func (*Store) OpenKeyGrantWaits

func (s *Store) OpenKeyGrantWaits(ctx context.Context) ([]KeyGrantWait, error)

OpenKeyGrantWaits lists every still-open key-grant wait, ordered by epoch then kid (P6-SEC-03). Rows are cleared by RecordKeyEpoch when the key arrives, so anything returned here is a key this device has seen ciphertext for and still cannot decrypt — surfaced by `doctor` as "awaiting key grants" and consulted by the `devices approve` epoch-contiguity guard.

func (*Store) OpenSkippedEvents

func (s *Store) OpenSkippedEvents(ctx context.Context) ([]SkippedEvent, error)

OpenSkippedEvents lists every still-open skipped-event record, oldest first (P6-SYNC-02). Rows clear when their event finally applies (Tx.ClearSkippedEventTx from the apply path), so anything returned here is an object the hub is still serving that this device still cannot consume — surfaced by `status` and `doctor`, and a `hub gc` sweep refusal.

func (*Store) PendingEvents

func (s *Store) PendingEvents(ctx context.Context) ([]Event, error)

func (*Store) PendingHubDeletes

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

PendingHubDeletes returns the queued orphaned blob refs (P5-PROD-02).

func (*Store) ProjectByID

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

func (*Store) ProjectByPath

func (s *Store) ProjectByPath(ctx context.Context, path string) (ProjectStatus, error)

func (*Store) PruneDraftSnapshots

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

PruneDraftSnapshots deletes superseded draft snapshot rows, keeping the most recent `keep` per project (P5-HUB-02). RecordDraftSnapshot only ever INSERTs, so without pruning every superseded snapshot keeps its blob "referenced" forever and neither local nor hub GC can reclaim a stale draft blob. keep is clamped to >= 1 so the current snapshot (highest HLC) is always retained. Returns the number of rows pruned.

func (*Store) PushSeqCursor

func (s *Store) PushSeqCursor(ctx context.Context, hubID string) (int64, error)

PushSeqCursor returns the local device's push watermark for a hub as a Seq (P5-SYNC-01, replacing the SYNC-04 HLC watermark, whose `hlc >` selection would silently strand events if the local HLC ever regressed relative to seq order). There is deliberately NO backfill from the legacy HLC watermark: inferring "already pushed" from `hlc <= watermark` would bake in the exact loss mode this cursor fixes — an unpushed local event stamped with a regressed HLC below the old watermark would be marked pushed forever (post-#59 Codex review, P2). A fresh watermark of 0 merely re-pushes local history once — idempotent per event ID (conditional-put dedup) and an opportunistic re-key of this device's legacy-layout events into the seq-keyed layout — never lossy.

func (*Store) QueuePendingHubDelete

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

QueuePendingHubDelete records a blob ref orphaned by a local-only revoke rewrap so the next hub-enabled sync/gc deletes it (P5-PROD-02). Idempotent.

func (*Store) QuickCheck

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

func (*Store) RecordDraftSnapshot

func (s *Store) RecordDraftSnapshot(ctx context.Context, namespaceID, blobRef string, byteSize, fileCount int64, event Event) error

RecordDraftSnapshot inserts a draft bundle snapshot and points the project's current_snapshot_id at it (DRAFT-02). It is idempotent on source_event_id: re-applying the same event does not create a duplicate.

func (*Store) RecordKeyCustody

func (s *Store) RecordKeyCustody(ctx context.Context, c devicekeys.Custody) error

RecordKeyCustody records the key-custody decision once (P6-XP-04). A decision already on disk is left untouched — custody is chosen at init and honored thereafter, never silently rewritten.

func (*Store) RecordKeyEpoch

func (s *Store) RecordKeyEpoch(ctx context.Context, epoch int64, kid, origin string) error

RecordKeyEpoch records that this device holds a WCK for (epoch, kid) (idempotent) (P4-SEC-07 / P6-SEC-02). The secret key itself is stored in the keychain via devicekeys.HybridStore.StoreWCK; this row is the non-secret local metadata. kid is the 64-lowercase-hex-char fingerprint hex(sha256(wck)); origin must be "self" (founder bootstrap or rotate), "grant" (a verified device.key.granted event), or "legacy" (migration backfill) — those are the only three paths permitted to write a row here (P6-SEC-01c).

func (*Store) RecordKeyGrant

func (s *Store) RecordKeyGrant(ctx context.Context, epoch int64, kid string, recipient, sourceEventID string, sourceEventHLC int64, sourceEventDeviceID string) error

RecordKeyGrant records a workspace key grant: a WCK epoch was age-wrapped to recipient by a device.key.granted event (P4-SEC-07). Idempotent on (workspace_id, epoch, recipient) so a re-delivered grant is a no-op. This is a membership audit trail only; the wrapped key rides the event log. kid is the non-secret fingerprint of the granted WCK (P6-SEC-02) and may be empty for legacy grants.

func (*Store) RenameDevice

func (s *Store) RenameDevice(ctx context.Context, deviceID, name string) error

func (*Store) ResolveConflict

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

ResolveConflict marks a conflict resolved and records the chosen resolution (PROD-06). The resolution_json captures the user's keep-local/keep-remote/ keep-both decision for audit and cross-device sync.

func (*Store) RetainedBlobRefs

func (s *Store) RetainedBlobRefs(ctx context.Context, keep int) ([]string, error)

RetainedBlobRefs returns the blob refs that remain referenced AFTER pruning draft snapshots to the latest `keep` per project (P5 review): env binding refs plus the top-`keep` draft snapshot refs per namespace. `hub gc --dry-run` uses this so its preview matches what a real run (prune + delete) would leave referenced, instead of counting soon-to-be-pruned superseded snapshots as live.

func (*Store) RunningAgentRunsWithPID

func (s *Store) RunningAgentRunsWithPID(ctx context.Context) ([]AgentRun, error)

func (*Store) SaveCapturedEnvProfile

func (s *Store) SaveCapturedEnvProfile(ctx context.Context, namespaceID, name string, varNames []string, encryptedValueRef string) (EnvProfile, error)

func (*Store) SaveProviderEnvProfile

func (s *Store) SaveProviderEnvProfile(ctx context.Context, namespaceID, name, provider string, refs map[string]string) (EnvProfile, error)

func (*Store) SetDevicePublicKey

func (s *Store) SetDevicePublicKey(ctx context.Context, deviceID, publicKey string) error

func (*Store) SetDeviceSigningPublicKey

func (s *Store) SetDeviceSigningPublicKey(ctx context.Context, deviceID, publicKey string) error

func (*Store) SetDeviceTrustState

func (s *Store) SetDeviceTrustState(ctx context.Context, deviceID, trustState string) error

func (*Store) SetLocalMeta

func (s *Store) SetLocalMeta(ctx context.Context, key, value string) error

SetLocalMeta upserts a local, never-synced key/value metadata row. Unlike RecordKeyCustody's write-once semantics, this overwrites so the cached retention floor can advance (P4-SYNC-02).

func (*Store) SetProjectForgeKind

func (s *Store) SetProjectForgeKind(ctx context.Context, namespaceID, kind string) error

SetProjectForgeKind persists a per-project forge override (GIT-05) so a self-hosted GitLab/Gitea instance routes to glab/tea instead of degrading to a compare URL. An empty kind clears the override (fall back to detection).

func (*Store) SkeletonProjects

func (s *Store) SkeletonProjects(ctx context.Context) ([]ProjectStatus, error)

SkeletonProjects returns all active projects whose local materialization state is "skeleton" or "failed" — the set the eager materialization pass (EAGER-01) must touch. A re-run only revisits projects that still need work, making the pass idempotent and resumable (EAGER-04).

func (*Store) SnapshotEntries

func (s *Store) SnapshotEntries(ctx context.Context) ([]SnapshotEntry, error)

SnapshotEntries returns every active namespace entry with its git_repos row and latest draft pointer, carrying the source-event coordinates that make import a pure LWW merge (P4-HUB-11). Entries are returned in path order for a deterministic snapshot document.

func (*Store) SnapshotTombstones

func (s *Store) SnapshotTombstones(ctx context.Context) ([]SnapshotTombstone, error)

SnapshotTombstones returns every deleted namespace entry that carries a tombstone HLC, ordered by path_key (P4-HUB-11). A snapshot ships these so a bootstrapped device blocks a stale add from resurrecting a deleted path.

func (*Store) Summary

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

func (*Store) UpdateAgentRunResult

func (s *Store) UpdateAgentRunResult(ctx context.Context, id, status, diffSummary, testSummary string) error

func (*Store) UpdateAgentRunStatus

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

func (*Store) UpdateBlobRef

func (s *Store) UpdateBlobRef(ctx context.Context, oldRef, newRef string) error

UpdateBlobRef repoints every reference from oldRef to newRef across secret_bindings and draft_snapshots (HUB-04 re-encryption).

func (*Store) UpdateGitDefaultBranch

func (s *Store) UpdateGitDefaultBranch(ctx context.Context, namespaceID, branch string) error

func (*Store) UpdateKeyKid

func (s *Store) UpdateKeyKid(ctx context.Context, epoch int64, kid string) error

UpdateKeyKid transactionally upgrades a legacy kid="" row at epoch to the given kid, preserving its origin and created_at (P6-SEC-02). Used once a caller computes the fingerprint for a key that was recorded before kids existed. A no-op (beyond deleting the legacy row) if a row for (epoch, kid) already exists.

func (*Store) UpdateProjectLocalState

func (s *Store) UpdateProjectLocalState(ctx context.Context, namespaceID, localPath, materialization, dirty string) error

func (*Store) UpsertDevice

func (s *Store) UpsertDevice(ctx context.Context, device Device) error

func (*Store) UpsertProject

func (s *Store) UpsertProject(ctx context.Context, params UpsertProjectParams) (NamespaceEntry, error)

func (*Store) VerifyRemoteEvent

func (s *Store) VerifyRemoteEvent(ctx context.Context, event Event) error

VerifyRemoteEvent runs the SAME permanent-verification checks insertEvent enforces — content-hash self-consistency then signature/trust — WITHOUT inserting. It is the EncryptedHub grant-ingestion seam (P6-SEC-01) so a WCK is never written from a carrier the apply path would reject: the pre-ingest gate rejects exactly the set of events that would land in the event_verification_ failure quarantine, so the keyring can never advance from an event that never enters the log. Returns nil during the pre-enrollment bootstrap window for non-destructive events (the documented P4-SEC-04 residual); fails closed once any device is approved.

func (*Store) Version

func (s *Store) Version() (int64, error)

func (*Store) WithTx

func (s *Store) WithTx(ctx context.Context, fn func(*Tx) error) error

func (*Store) WorkspaceID

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

func (*Store) WorktreeByID

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

type Summary

type Summary struct {
	WorkspaceName string          `json:"workspace_name"`
	WorkspaceID   string          `json:"workspace_id"`
	RootPath      string          `json:"root_path"`
	ProjectCount  int             `json:"project_count"`
	DeviceID      string          `json:"device_id,omitempty"`
	Projects      []ProjectStatus `json:"projects,omitempty"`
}

type Tx

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

func (*Tx) ClearSkippedEventTx

func (tx *Tx) ClearSkippedEventTx(ctx context.Context, eventID string) error

ClearSkippedEventTx deletes every skip record for an event that finally applied (P6-SYNC-02): a post-upgrade pull consuming a once-unknown-version event, or a hub replacing a garbled object with the real one, closes the wedge and the record with it. Idempotent no-op when no record exists.

func (*Tx) EnsureRemoteDeviceTx

func (tx *Tx) EnsureRemoteDeviceTx(ctx context.Context, deviceID string) error

EnsureRemoteDeviceTx is the transaction-scoped version of EnsureRemoteDevice.

func (*Tx) InsertConflict

func (tx *Tx) InsertConflict(ctx context.Context, namespaceID, typ, detailsJSON string) error

func (*Tx) InsertEvent

func (tx *Tx) InsertEvent(ctx context.Context, event Event) (bool, error)

func (*Tx) ProjectByPath

func (tx *Tx) ProjectByPath(ctx context.Context, path string) (ProjectStatus, error)

func (*Tx) ProjectByPathKey

func (tx *Tx) ProjectByPathKey(ctx context.Context, pathKey string) (ProjectStatus, error)

ProjectByPathKey reads a project by its case-folded path_key within the transaction (P4-SYNC-02 snapshot import: tombstones/entries carry path_key). Returns the same "unknown namespace path" error as ProjectByPath when no active row exists.

func (*Tx) ReceiveRemoteHLC

func (tx *Tx) ReceiveRemoteHLC(ctx context.Context, remoteHLC int64) error

ReceiveRemoteHLC advances the local device clock to be causally after a received remote HLC (standard HLC receive rule), so future local events sort after events already observed. It assumes the caller has already rejected remote timestamps beyond the trusted skew.

func (*Tx) RecordDraftSnapshotTx

func (tx *Tx) RecordDraftSnapshotTx(ctx context.Context, namespaceID, blobRef string, byteSize, fileCount int64, event Event) error

RecordDraftSnapshotTx records a draft bundle snapshot within the current transaction (DRAFT-02). It ensures a draft_projects row exists, inserts the snapshot (idempotent on source_event_id), and points draft_projects.current_snapshot_id at it.

func (*Tx) RecordKeyGrantTx

func (tx *Tx) RecordKeyGrantTx(ctx context.Context, epoch int64, kid string, recipient string, event Event) error

RecordKeyGrantTx records a workspace key grant audit row transactionally with the event insert that carried it (P4-SEC-07). Idempotent on (workspace_id, epoch, recipient) so a re-delivered grant is a no-op. This is the membership audit trail only; the age-wrapped WCK rides the event payload and the secret WCK lives in the keychain (ingested by the decorator on the recipient device). Runs on every device that applies the grant event. kid is the non-secret fingerprint of the granted WCK (P6-SEC-02) and is audit metadata only; it may be empty for legacy grants.

func (*Tx) RenameProject

func (tx *Tx) RenameProject(ctx context.Context, oldPath, newPath string, event Event) (RenameOutcome, error)

RenameProject moves an active namespace entry from oldPath to newPath, re-keying path_key. It returns a RenameOutcome so the caller can record a conflict on a target collision rather than overwriting.

func (*Tx) ResolveConflict

func (tx *Tx) ResolveConflict(ctx context.Context, id, resolutionJSON string) error

ResolveConflict marks a conflict resolved inside the caller's transaction.

func (*Tx) ResolveConflictByFingerprint

func (tx *Tx) ResolveConflictByFingerprint(ctx context.Context, typ, detailsJSON, resolutionJSON string) error

ResolveConflictByFingerprint marks the open conflict matching the stable (workspace_id, type, details_json) fingerprint resolved (PROD-06). Used by the conflict.resolved event apply handler so cross-device convergence does not depend on per-device conflict IDs. P5-SYNC-02: namespace_id is a locally-minted prj_<uuid> that differs per device, so it must NOT be part of the match — details_json already embeds the stable path and event-coordinate winner/loser, making (type, details_json) globally unique. It is idempotent: a duplicate event for an already-resolved (or absent) row affects zero rows and returns nil.

func (*Tx) ResolveOpenConflictsByEventID

func (tx *Tx) ResolveOpenConflictsByEventID(ctx context.Context, typ, eventID, resolutionJSON string) error

ResolveOpenConflictsByEventID marks every open conflict of the given type whose details_json names event_id resolved (P6-SEC-03). Unlike ResolveConflictByFingerprint it does not need the full details string — hash-chain-break details embed the volatile cause error, so an exact fingerprint cannot be reconstructed when the event finally applies. Idempotent: zero matching rows is a no-op.

func (*Tx) TombstoneByPathKey

func (tx *Tx) TombstoneByPathKey(ctx context.Context, pathKey string, hlc int64) error

TombstoneByPathKey tombstones an entry addressed only by its case-folded path_key (P4-SYNC-02 snapshot import: a snapshot tombstone carries no display path). When a local row for the path_key exists its display path is preserved; when none exists a deleted placeholder is created keyed on the path_key so a later stale add cannot resurrect the path.

func (*Tx) TombstoneHLC

func (tx *Tx) TombstoneHLC(ctx context.Context, path string) (int64, bool, error)

func (*Tx) TombstoneProject

func (tx *Tx) TombstoneProject(ctx context.Context, path string, hlc int64) error

func (*Tx) UpsertChainAnchor

func (tx *Tx) UpsertChainAnchor(ctx context.Context, deviceID string, anchorSeq int64, contentHash string, anchorHLC int64, snapshotSHA string) error

UpsertChainAnchor records a per-device hash-chain anchor imported from a snapshot (P4-SYNC-02): the content hash of the last event the snapshot covers for an origin device (at seq = floor-1), so the prev-hash verification of that device's first post-floor event has a fallback predecessor. Keeps the HIGHEST anchor_seq on conflict — a later snapshot's floor only ever moves forward, so a stale re-import must never lower a device's anchor.

func (*Tx) UpsertProject

func (tx *Tx) UpsertProject(ctx context.Context, params UpsertProjectParams) (NamespaceEntry, error)

type UpsertProjectParams

type UpsertProjectParams struct {
	Path                  string
	Type                  string
	RemoteURL             string
	RemoteKey             string
	DefaultBranch         string
	LFSPolicy             string
	MaterializationPolicy string
	LocalPath             string
	MaterializationState  string
	DirtyState            string
	SourceEventHLC        int64
	SourceEventDeviceID   string
	SourceEventID         string
	ForgeKind             string
}

type Worktree

type Worktree struct {
	ID          string `json:"id"`
	NamespaceID string `json:"namespace_id"`
	DeviceID    string `json:"device_id"`
	Path        string `json:"path"`
	Branch      string `json:"branch"`
	BaseRef     string `json:"base_ref"`
	BaseSHA     string `json:"base_sha"`
	CreatedBy   string `json:"created_by"`
	Status      string `json:"status"`
	DirtyState  string `json:"dirty_state"`
}

Jump to

Keyboard shortcuts

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