state

package
v0.1.2-rc.1 Latest Latest
Warning

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

Go to latest
Published: Jul 13, 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

View Source
const WCKRotationPendingMetaKey = "wck_rotation_pending"

WCKRotationPendingMetaKey is the local_meta row recording that a WCK rotation is OWED: a device learned of a `devices revoke`/`lost` (its own failed revoke-path rotation, or a synced/snapshot-imported trust flip) and must mint epoch+1 that excludes the revoked device before the fleet stops sealing under a key the revoked device still holds (issue #134, P7-SYNC-04). Machine-local retry bookkeeping only — it never rides the event log.

The format is defined here (not in internal/cli) so the sync apply path can arm the marker transactionally with the trust flip without importing the cli package; the cli helpers alias these identifiers.

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 TimestampNow added in v0.1.2

func TimestampNow() string

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.

func ValidateDBFileReadOnly added in v0.1.2

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

ValidateDBFileReadOnly runs the same quick_check + foreign_key_check through the read-only snapshot DSN, so validation cannot alter a manifest-verified staged database before it is promoted (P7-DATA-04).

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"`
	RunnerStartedAt    int64  `json:"runner_started_at,omitempty"`
	SandboxBackend     string `json:"sandbox_backend,omitempty"`
	SandboxMode        string `json:"sandbox_mode,omitempty"`
	SandboxLimitations string `json:"sandbox_limitations,omitempty"` // JSON array string, "" when none
}

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 EnvProfileParams added in v0.1.2

type EnvProfileParams struct {
	Name     string
	Provider string            // devstrap_encrypted or provider name
	Mode     string            // hydrate_or_runtime | runtime_only
	BlobRef  string            // devstrap_encrypted only
	VarNames []string          // devstrap_encrypted only
	Refs     map[string]string // provider profiles only
}

EnvProfileParams carries the full desired state of a project's env profile for the transactional upsert (ENV-SYNC-01).

type EnvProfileRef added in v0.1.2

type EnvProfileRef struct {
	NamespaceID string
	Path        string
	Name        string
	Provider    string
	Mode        string
	VarNames    []string
}

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 SandboxViolation added in v0.1.2

type SandboxViolation struct {
	RunID      string `json:"run_id"`
	ObservedAt string `json:"observed_at"`
	Backend    string `json:"backend"`
	Operation  string `json:"operation"`
	Path       string `json:"path,omitempty"`
	Detail     string `json:"detail,omitempty"`
	Source     string `json:"source"`
}

SandboxViolation is one kernel denial the OS agent sandbox reported for a run (P4-GIT-03 slice 5). Coordinates + reason only, no secret material — the detail field is scrubbed before persist. Unsigned local visibility, not the signed audit_log.

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
	Env                   *SnapshotEntryEnv
}

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 SnapshotEntryEnv added in v0.1.2

type SnapshotEntryEnv struct {
	Name                string
	Provider            string
	Mode                string
	BlobRef             string
	VarNames            []string
	Refs                map[string]string
	SourceEventHLC      int64
	SourceEventDeviceID string
	SourceEventID       string
}

SnapshotEntryEnv is the env-profile pointer attached to a namespace entry (ENV-SYNC-01). A snapshot ships it so env profiles survive event-log compaction: without it, a snapshot-bootstrapped device would never learn a profile whose env.profile.updated carrier was compacted away.

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 SnapshotTrustRow added in v0.1.2

type SnapshotTrustRow struct {
	DeviceID   string
	TrustState string
}

SnapshotTrustRow is one terminal device-trust row (revoked/lost) read for snapshot production (P7-SYNC-01): compaction deletes the device.revoked/ device.lost event, so the snapshot must carry the derived terminal state or a snapshot-bootstrapped device keeps the revoked device approved forever.

type Store

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

func Open

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

func OpenSnapshot added in v0.1.2

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

OpenSnapshot opens a standalone SQLite database file read-only for enumeration (P7-DATA-03/04): no migration, no chmod, no WAL side files.

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) CountRunsWithSandboxViolations added in v0.1.2

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

CountRunsWithSandboxViolations returns how many distinct runs have >=1 row.

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) DeleteLocalMeta added in v0.1.2

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

DeleteLocalMeta removes a local, never-synced key/value metadata row. Deleting an absent key is a no-op. Used to clear self-resolved flags such as the owed-WCK-rotation marker (issue #134).

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). Env blobs sync through the hub blob plane and are rewrapped with superseding env.profile.updated events on revoke.

func (*Store) EnvProfileForProject

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

func (*Store) EnvProfilesForBlobRef added in v0.1.2

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

EnvProfilesForBlobRef returns every active project env profile whose encrypted bindings reference ref, so revoke rewrap can emit superseding env.profile.updated events before old hub ciphertext is deleted (ENV-SYNC-01).

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) InsertSandboxViolations added in v0.1.2

func (s *Store) InsertSandboxViolations(ctx context.Context, vs []SandboxViolation) error

InsertSandboxViolations appends violation rows for a run (best-effort visibility; callers ignore the error path only after logging). Each row's Detail is assumed already scrubbed by the caller.

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). Selection uses the canonical (hlc, source_event_device_id, source_event_id) coordinate so every device picks the same snapshot on an HLC tie. Legacy rows without source-event coordinates all collapse to (0, ”, ”), so local created_at/id ride as FINAL tiebreakers only — stamped rows are unique on the canonical triple (migration 00012), so the local fields never influence cross-device selection.

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. Survival order uses the same canonical (hlc, source_event_device_id, source_event_id) coordinate as LatestDraftSnapshot so every device keeps the same snapshots on an HLC tie (P7-SYNC-03). 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. Ranks by the same canonical (hlc, source_event_device_id, source_event_id) coordinate as PruneDraftSnapshots so the dry-run preview never names a different blob than the real prune actually keeps (P7-SYNC-03).

func (*Store) RunningAgentRunsByWorktree added in v0.1.2

func (s *Store) RunningAgentRunsByWorktree(ctx context.Context, worktreeID string) ([]AgentRun, error)

RunningAgentRunsByWorktree returns agent runs still marked running that are bound to the given worktree (P7-GIT-01: cleanup must not reap a live run).

func (*Store) RunningAgentRunsWithPID

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

func (*Store) SandboxViolationsByRun added in v0.1.2

func (s *Store) SandboxViolationsByRun(ctx context.Context, runID string) ([]SandboxViolation, error)

SandboxViolationsByRun returns a run's violation rows, oldest first.

func (*Store) SaveCapturedEnvProfile

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

SaveCapturedEnvProfile stores an encrypted env profile without emitting a sync event. Production CLI paths must use the event-emitting transaction in internal/cli so the profile syncs; this wrapper exists for tests/legacy callers.

func (*Store) SaveProviderEnvProfile

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

SaveProviderEnvProfile stores a provider env profile without emitting a sync event. Production CLI paths must use the event-emitting transaction in internal/cli so the profile syncs; this wrapper exists for tests/legacy callers.

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) SnapshotTrust added in v0.1.2

func (s *Store) SnapshotTrust(ctx context.Context) ([]SnapshotTrustRow, error)

SnapshotTrust returns every device row in a TERMINAL trust state (revoked/lost) in deterministic id order (P7-SYNC-01). pending/approved rows are excluded by design — approval re-grants ride as fresh events above the floor — and the local device can never match (its state is 'local').

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) ApplyRemoteDeviceTrustTx added in v0.1.2

func (tx *Tx) ApplyRemoteDeviceTrustTx(ctx context.Context, deviceID, trustState string) (bool, error)

ApplyRemoteDeviceTrustTx applies a synced device.revoked/device.lost event to the local trust table (TRUST-01). The transition is STICKY/monotonic: only pending or approved rows flip (pending -> revoked is the more fail-closed direction, consistent with hasEnrolledDevices counting revoked rows); revoked <-> lost churn and replays are no-ops; only the local fingerprint ceremony (`devices approve`) resurrects a device. The local device never flips from a remote event — a hub cannot talk this device into distrusting itself; the operator sees the fleet's decision via peers quarantining its events instead. Returns whether a row actually changed so the caller can gate side effects (needs_rotation flagging) off replays.

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) CurrentKeyEpochTx added in v0.1.2

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

CurrentKeyEpochTx is the transactional form of CurrentKeyEpoch: the highest WCK epoch this device holds a key for (0 if none). Callers arming the owed-rotation marker read it inside the same transaction as the trust flip so a keyless device (epoch 0) never arms a rotation it cannot perform.

func (*Tx) DeleteLocalMetaTx added in v0.1.2

func (tx *Tx) DeleteLocalMetaTx(ctx context.Context, key string) error

DeleteLocalMetaTx is the transactional form of DeleteLocalMeta.

func (*Tx) EnsureRemoteDeviceTx

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

EnsureRemoteDeviceTx is the transaction-scoped version of EnsureRemoteDevice.

func (*Tx) EnvProfileSourceCoords added in v0.1.2

func (tx *Tx) EnvProfileSourceCoords(ctx context.Context, namespaceID string) (hlc int64, deviceID, eventID string, ok bool, err error)

func (*Tx) GetLocalMetaTx added in v0.1.2

func (tx *Tx) GetLocalMetaTx(ctx context.Context, key string) (string, bool, error)

GetLocalMetaTx is the transactional form of GetLocalMeta.

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) MarkEncryptedBindingsNeedingRotationTx added in v0.1.2

func (tx *Tx) MarkEncryptedBindingsNeedingRotationTx(ctx context.Context) (int, error)

MarkEncryptedBindingsNeedingRotationTx is the Tx variant, used by the device.revoked/device.lost apply path so the flip and the rotation flags land atomically (TRUST-01).

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) SetDeviceTrustStateTx added in v0.1.2

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

SetDeviceTrustStateTx is the transactional core of SetDeviceTrustState (TRUST-01): `devices revoke`/`lost` write the trust flip and insert the synced device.revoked/device.lost event in ONE transaction (P6-DATA-03), so a failure between the two can never orphan either side.

func (*Tx) SetLocalMetaTx added in v0.1.2

func (tx *Tx) SetLocalMetaTx(ctx context.Context, key, value string) error

SetLocalMetaTx is the transactional form of SetLocalMeta.

func (*Tx) SetWCKRotationPendingTx added in v0.1.2

func (tx *Tx) SetWCKRotationPendingTx(ctx context.Context, epoch int64) error

SetWCKRotationPendingTx arms the owed-rotation marker transactionally, atomic with the trust flip that owes it (P7-SYNC-04). It makes the receiver's next sync rotation gate (maybeRotateWorkspaceKey) mint epoch+1 excluding the just-revoked device, so a fleet device that only LEARNS of a revocation (via a synced device.revoked/lost event or a snapshot import) — not just the device that ran the revoke — still owes the forward-secrecy rotation.

epoch is the active epoch at flip time; epoch<=0 is a no-op because a keyless device holds no key the revoked device could read AND its rotation gate skips epoch 0, which would otherwise strand the marker unresolved forever.

Storm-guard: an existing marker (parseable or not) is left UNTOUCHED so a later revoke flip cannot reset the "owed since" clock and replayed/re-imported flips are inert. A malformed existing marker therefore stays pending, matching the cli reader's fail-closed treatment.

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) UpsertEnvProfileTx added in v0.1.2

func (tx *Tx) UpsertEnvProfileTx(ctx context.Context, namespaceID string, p EnvProfileParams, event Event) (EnvProfile, error)

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 WCKRotationPendingRecord added in v0.1.2

type WCKRotationPendingRecord struct {
	Epoch int64     `json:"epoch"`
	Since time.Time `json:"since"`
}

WCKRotationPendingRecord is the JSON value of the pending row. Epoch records the epoch that was ACTIVE when the rotation was owed (diagnostic only — a newer epoch alone is NOT proof the revoked device was excluded; only a local Rotate is, so the marker resolves solely via its explicit delete). Since is the "owed since" timestamp surfaced by `doctor` and sync warnings.

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