syncer

package
v0.1.4 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package syncer coordinates canonical Agent records, encrypted remote objects, and safe local restoration.

The first part of that job is deliberately independent of I/O: this file defines the immutable record shard and its digest chain. Remote objects are untrusted bytes until this layer validates them.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidCompressedPayload reports a malformed compressed payload.
	ErrInvalidCompressedPayload = errors.New("syncer: invalid compressed payload")

	// ErrUnsupportedCompression reports a compression version or codec that
	// this build does not understand.
	ErrUnsupportedCompression = errors.New("syncer: unsupported compression format")

	// ErrCompressedPayloadTooLarge reports a payload that exceeds the
	// decompression or shard size limit.
	ErrCompressedPayloadTooLarge = errors.New("syncer: compressed payload exceeds size limit")
)
View Source
var (
	// ErrInvalidDeviceRecord reports a malformed or incomplete device record.
	ErrInvalidDeviceRecord = errors.New("syncer: invalid device record")

	// ErrUnsupportedDeviceRecord reports a device record format newer than this build.
	ErrUnsupportedDeviceRecord = errors.New("syncer: device record format is newer than this build")

	// ErrDuplicateDeviceRecord reports multiple objects for one device ID.
	ErrDuplicateDeviceRecord = errors.New("syncer: duplicate remote device record")

	// ErrRemoteDeviceRecordTooLarge reports an encrypted record above the read bound.
	ErrRemoteDeviceRecordTooLarge = errors.New("syncer: remote device record is too large")
)
View Source
var (
	ErrInvalidEnvironmentMetadata = errors.New("syncer: invalid environment metadata")
	ErrRemoteEnvironmentTooLarge  = errors.New("syncer: remote environment metadata is too large")
)
View Source
var (
	// ErrCursorCommit reports a remote shard that succeeded but whose local
	// cursor could not be persisted.
	ErrCursorCommit = errors.New("syncer: cursor commit failed")

	// ErrExecutorLayoutMismatch reports a cursor store for a different remote
	// identity tuple than the executor's object layout.
	ErrExecutorLayoutMismatch = errors.New("syncer: executor layout does not match cursor store")
)
View Source
var (
	ErrRemoteGitStateTooLarge    = errors.New("syncer: remote Git state is too large")
	ErrRemoteGitTransferTooLarge = errors.New("syncer: remote Git transfer is too large")
)
View Source
var (
	// ErrNoRemoteKeyfile reports a backend that has not been initialised for
	// CtxHop yet.
	ErrNoRemoteKeyfile = errors.New("syncer: remote keyfile is not present")

	// ErrRemoteKeyfileExists prevents a later init from replacing the envelope
	// that protects every session already stored in the backend.
	ErrRemoteKeyfileExists = errors.New("syncer: remote keyfile already exists")

	// ErrRemoteKeyfileTooLarge reports an object that is not a plausible
	// keyfile before it is parsed or allocated without a bound.
	ErrRemoteKeyfileTooLarge = errors.New("syncer: remote keyfile is too large")
)
View Source
var (
	// ErrInvalidMetadata reports an invalid or unsupported metadata envelope.
	ErrInvalidMetadata = errors.New("syncer: invalid session metadata")

	// ErrNoRemoteMetadata reports a session prefix without readable metadata
	// objects.
	ErrNoRemoteMetadata = errors.New("syncer: remote session has no metadata")

	// ErrDuplicateMetadata reports multiple list entries for one device's
	// mutable metadata object.
	ErrDuplicateMetadata = errors.New("syncer: duplicate remote metadata")

	// ErrRemoteMetadataTooLarge reports metadata that exceeds the bounded
	// encrypted object size before decryption.
	ErrRemoteMetadataTooLarge = errors.New("syncer: remote metadata is too large")
)
View Source
var (
	// ErrInvalidProjectAnnouncement reports malformed or incomplete project
	// discovery metadata.
	ErrInvalidProjectAnnouncement = errors.New("syncer: invalid project announcement")

	// ErrUnsupportedProjectAnnouncement reports a project announcement newer
	// than this build understands.
	ErrUnsupportedProjectAnnouncement = errors.New("syncer: project announcement format is newer than this build")

	// ErrDuplicateProjectAnnouncement reports duplicate objects for one
	// project/device announcement key.
	ErrDuplicateProjectAnnouncement = errors.New("syncer: duplicate project announcement")

	// ErrConflictingProjectAnnouncement reports different identities advertised
	// for the same opaque project ID.
	ErrConflictingProjectAnnouncement = errors.New("syncer: conflicting project announcement")

	// ErrRemoteProjectAnnouncementTooLarge reports an encrypted announcement
	// above the bounded read size.
	ErrRemoteProjectAnnouncementTooLarge = errors.New("syncer: remote project announcement is too large")
)
View Source
var (
	// ErrInvalidPullTipState reports damaged or internally inconsistent local
	// observed-tip state.
	ErrInvalidPullTipState = errors.New("syncer: invalid pull tip state")

	// ErrUnsupportedPullTipState reports a state file written by a newer
	// version.
	ErrUnsupportedPullTipState = errors.New("syncer: pull tip state is newer than this version")

	// ErrDuplicatePullTip reports repeated device entries in one state file.
	ErrDuplicatePullTip = errors.New("syncer: duplicate pull tip")
)
View Source
var (
	// ErrInvalidPushCursor reports a cursor that cannot describe a durable
	// local device prefix.
	ErrInvalidPushCursor = errors.New("syncer: invalid push cursor")

	// ErrLocalHistoryChanged reports a local stream that no longer has the
	// prefix recorded by the push cursor.
	ErrLocalHistoryChanged = errors.New("syncer: local history changed")

	// ErrInvalidPushPart reports a shard that cannot be the next cursor step.
	ErrInvalidPushPart = errors.New("syncer: invalid push shard transition")

	// ErrShardTooLarge reports a record or shard that exceeds the configured
	// encoded envelope limit.
	ErrShardTooLarge = errors.New("syncer: shard is too large")

	// ErrShardSequenceExhausted reports that the fixed-width object namespace
	// has no sequence number left.
	ErrShardSequenceExhausted = errors.New("syncer: shard sequence exhausted")
)
View Source
var (
	// ErrInvalidQueue reports malformed or internally inconsistent queue data.
	ErrInvalidQueue = errors.New("syncer: invalid pending queue")

	// ErrUnsupportedQueue reports a queue written by a newer format version.
	ErrUnsupportedQueue = errors.New("syncer: pending queue is newer than this version")

	// ErrQueueItemMissing reports an operation for a task that is not queued.
	ErrQueueItemMissing = errors.New("syncer: pending queue item does not exist")

	// ErrQueueFileMissing reports that the queue file itself disappeared while
	// a queue operation was running. It wraps ErrQueueItemMissing for callers
	// that still need to classify the missing task.
	ErrQueueFileMissing = errors.New("syncer: pending queue file does not exist")

	// ErrQueueItemBlocked reports an attempt to retry a terminally blocked task.
	ErrQueueItemBlocked = errors.New("syncer: pending queue item is blocked")

	// ErrRetryExhausted reports that the attempt counter cannot advance safely.
	ErrRetryExhausted = errors.New("syncer: pending queue retry count is exhausted")
)
View Source
var (
	// ErrInvalidRecord reports a value that cannot be part of a canonical
	// record stream.
	ErrInvalidRecord = errors.New("syncer: invalid canonical record")

	// ErrInvalidShard reports an invalid or unsupported shard envelope.
	ErrInvalidShard = errors.New("syncer: invalid shard")

	// ErrIncompleteBranch reports a device stream with a missing or unusable
	// shard. The available prefix must not be treated as a complete session.
	ErrIncompleteBranch = errors.New("syncer: incomplete device branch")

	// ErrSessionTooLarge reports a complete record stream that would exceed the
	// bounded memory budget used by planning, assembly, and restore validation.
	ErrSessionTooLarge = errors.New("syncer: session exceeds size limit")
)
View Source
var (
	// ErrNoRemoteBranches reports a session prefix that contains no readable
	// shard objects.
	ErrNoRemoteBranches = errors.New("syncer: remote session has no shard branches")

	// ErrIncompleteRemoteSession reports a remote session whose authenticated
	// metadata and visible shard branches do not describe the same complete
	// stream. Callers must retry rather than restore the visible prefix.
	ErrIncompleteRemoteSession = errors.New("syncer: remote session is incomplete")

	// ErrDuplicateShard reports two list entries claiming the same device-local
	// shard sequence.
	ErrDuplicateShard = errors.New("syncer: duplicate remote shard")

	// ErrRemoteObjectTooLarge reports an object that exceeds the syncer's
	// encrypted shard bound before decryption.
	ErrRemoteObjectTooLarge = errors.New("syncer: remote shard is too large")
)
View Source
var (
	// ErrInvalidRestoreStats reports damaged or internally inconsistent local
	// restore statistics.
	ErrInvalidRestoreStats = errors.New("syncer: invalid restore statistics")

	// ErrUnsupportedRestoreStats reports statistics written by a newer version.
	ErrUnsupportedRestoreStats = errors.New("syncer: restore statistics are newer than this version")
)
View Source
var (
	// ErrNoPushCursor reports that no local progress has been established for
	// the project, session, and device tuple.
	ErrNoPushCursor = errors.New("syncer: push cursor does not exist")

	// ErrInvalidCursorState reports damaged or unsupported cursor contents.
	ErrInvalidCursorState = errors.New("syncer: invalid push cursor state")

	// ErrUnsupportedCursorState reports a cursor written by a newer format.
	ErrUnsupportedCursorState = errors.New("syncer: push cursor state is newer than this version")
)
View Source
var (
	ErrRemoteWorkspaceTooLarge = errors.New("syncer: remote workspace snapshot is too large")
	ErrInvalidWorkspaceObject  = errors.New("syncer: invalid workspace object")
)

Functions

func DeleteDeviceData

func DeleteDeviceData(ctx context.Context, store remote.Remote, deviceID string) (int, error)

DeleteDeviceData removes all device-owned remote objects and returns the number removed. A failure after a partial cleanup returns both values.

func DeleteRemoteAll

func DeleteRemoteAll(ctx context.Context, store remote.Remote) (int, error)

DeleteRemoteAll removes every valid object visible in the configured Remote, including the keyfile and device records. This operation is intentionally separate from the scoped deletion helpers because it cannot be undone by retaining the keyfile.

func DeleteRemoteDeviceBranch

func DeleteRemoteDeviceBranch(ctx context.Context, store remote.Remote, projectID, sessionID, deviceID string) (int, error)

DeleteRemoteDeviceBranch removes one device-owned branch, including its mutable metadata and immutable shards. It leaves every other device branch in the session untouched.

func DeleteRemoteProject

func DeleteRemoteProject(ctx context.Context, store remote.Remote, projectID string) (int, error)

DeleteRemoteProject removes every session and project-scoped object for one project. Device records and the global keyfile remain untouched.

func DeleteRemoteSession

func DeleteRemoteSession(ctx context.Context, store remote.Remote, projectID, sessionID string) (int, error)

DeleteRemoteSession removes every object belonging to one project session. It does not touch the project's other sessions, device records, or keyfile.

func DeviceDataKeys

func DeviceDataKeys(ctx context.Context, store remote.Remote, deviceID string) ([]string, error)

DeviceDataKeys returns the device record and session branch keys owned by a device.

func DeviceKey

func DeviceKey(deviceID string) (string, error)

DeviceKey returns the encrypted self-description key for one device ID.

func DigestRecords

func DigestRecords(records [][]byte) ([32]byte, error)

DigestRecords returns the digest after all records in order.

func EmptyDigest

func EmptyDigest() [32]byte

EmptyDigest returns the digest for an empty record prefix.

func FetchKeyfile

func FetchKeyfile(ctx context.Context, store remote.Remote) (*crypto.Keyfile, error)

FetchKeyfile reads and parses the bounded remote envelope. It never unlocks the keyfile: passphrases and recovery keys belong to the CLI interaction layer, not to remote storage plumbing.

func OpenGitState

func OpenGitState(identity *ecdh.PrivateKey, objectKey string, sealed []byte) (gitstate.State, error)

func OpenGitTransfer

func OpenGitTransfer(identity *ecdh.PrivateKey, objectKey string, sealed []byte) (gitstate.Transfer, error)

func OpenWorkspace

func OpenWorkspace(identity *ecdh.PrivateKey, objectKey string, sealed []byte) (workspacepkg.Snapshot, error)

OpenWorkspace decrypts, decompresses and validates a remote workspace snapshot.

func ParseShardNumber

func ParseShardNumber(name string) (uint64, error)

ParseShardNumber converts a six-digit shard object name to its sequence. It is kept separate from ParseShard so remote listing code can reject metadata and foreign objects before attempting decryption.

func ProjectAnnouncementKey

func ProjectAnnouncementKey(projectID, deviceID string) (string, error)

ProjectAnnouncementKey returns the device-owned key for a project discovery record. Each device writes only its own key, so discovery needs no lock.

func ProjectRemotePrefix

func ProjectRemotePrefix(projectID string) (string, error)

ProjectRemotePrefix returns the slash-terminated namespace for one project. The trailing separator is intentional: a project ID must not match another identifier that merely starts with the same bytes.

func PublishKeyfile

func PublishKeyfile(ctx context.Context, store remote.Remote, keyfile *crypto.Keyfile) error

PublishKeyfile creates the remote keyfile without replacing an existing envelope. The keyfile is the only object whose replacement can make all existing encrypted sessions unreadable, so a caller must explicitly choose a future key-rotation operation rather than getting overwrite semantics from the generic Remote.Put method.

func PutDeviceRecord

func PutDeviceRecord(ctx context.Context, store remote.Remote, recipient *ecdh.PublicKey, record DeviceRecord) error

PutDeviceRecord encrypts and publishes a device self-description.

func PutEnvironmentManifest

func PutEnvironmentManifest(ctx context.Context, store remote.Remote, recipient *ecdh.PublicKey, layout ObjectLayout, references []environment.Reference, components []environment.ComponentContent) error

func PutEnvironmentReferences

func PutEnvironmentReferences(ctx context.Context, store remote.Remote, recipient *ecdh.PublicKey, layout ObjectLayout, references []environment.Reference) error

func PutGitState

func PutGitState(ctx context.Context, store remote.Remote, recipient *ecdh.PublicKey, layout ObjectLayout, state gitstate.State) error

func PutGitTransfer

func PutGitTransfer(ctx context.Context, store remote.Remote, recipient *ecdh.PublicKey, layout ObjectLayout, transfer gitstate.Transfer) error

func PutMetadata

func PutMetadata(ctx context.Context, store remote.Remote, recipient *ecdh.PublicKey, layout ObjectLayout, metadata Metadata) error

PutMetadata encrypts and publishes metadata to the local device branch.

func PutProjectAnnouncement

func PutProjectAnnouncement(ctx context.Context, store remote.Remote, recipient *ecdh.PublicKey, record ProjectAnnouncement) error

PutProjectAnnouncement publishes a device-owned encrypted discovery record.

func PutWorkspaceSnapshot

func PutWorkspaceSnapshot(ctx context.Context, store remote.Remote, recipient *ecdh.PublicKey, layout ObjectLayout, snapshot workspacepkg.Snapshot) error

PutWorkspaceSnapshot publishes the current device's workspace snapshot.

func ReadGitState

func ReadGitState(ctx context.Context, store remote.Remote, layout ObjectLayout, identities []*ecdh.PrivateKey) (gitstate.State, error)

func ReadGitTransfer

func ReadGitTransfer(ctx context.Context, store remote.Remote, layout ObjectLayout, identities []*ecdh.PrivateKey) (gitstate.Transfer, error)

func ReadWorkspaceSnapshot

func ReadWorkspaceSnapshot(ctx context.Context, store remote.Remote, layout ObjectLayout, identities []*ecdh.PrivateKey) (workspacepkg.Snapshot, error)

ReadWorkspaceSnapshot reads the current workspace snapshot for one device branch. It is intentionally not part of metadata-only listing.

func ReplaceKeyfile

func ReplaceKeyfile(ctx context.Context, store remote.Remote, keyfile *crypto.Keyfile) error

ReplaceKeyfile updates an already initialised remote keyfile.

Replacing the envelope is an explicit lifecycle operation: the caller must fetch and validate the current keyfile first. Refusing to create a missing keyfile prevents a mistyped backend from looking like a successful password rotation that created a second, empty storage.

func SealDeviceRecord

func SealDeviceRecord(recipient *ecdh.PublicKey, objectKey string, record DeviceRecord) ([]byte, error)

SealDeviceRecord encodes and encrypts a record for its exact object key.

func SealEnvironment

func SealEnvironment(recipient *ecdh.PublicKey, objectKey string, metadata EnvironmentMetadata) ([]byte, error)

func SealGitState

func SealGitState(recipient *ecdh.PublicKey, objectKey string, state gitstate.State) ([]byte, error)

func SealGitTransfer

func SealGitTransfer(recipient *ecdh.PublicKey, objectKey string, transfer gitstate.Transfer) ([]byte, error)

func SealMetadata

func SealMetadata(recipient *ecdh.PublicKey, objectKey string, metadata Metadata) ([]byte, error)

SealMetadata encodes, compresses and encrypts metadata for its exact object key. The compression wrapper is optional for small metadata payloads.

func SealProjectAnnouncement

func SealProjectAnnouncement(recipient *ecdh.PublicKey, objectKey string, record ProjectAnnouncement) ([]byte, error)

SealProjectAnnouncement encodes and encrypts a project discovery record.

func SealShard

func SealShard(recipient *ecdh.PublicKey, objectKey string, shard Shard) ([]byte, error)

SealShard encodes and encrypts a shard for storage at objectKey.

The exact key is authenticated by the crypto layer. The returned bytes are safe to pass to remote.Remote, but the plaintext must never be passed there.

func SealWorkspace

func SealWorkspace(recipient *ecdh.PublicKey, objectKey string, snapshot workspacepkg.Snapshot) ([]byte, error)

SealWorkspace serializes, compresses and encrypts a workspace snapshot for its exact device branch object key.

func SessionRemotePrefix

func SessionRemotePrefix(projectID, sessionID string) (string, error)

SessionRemotePrefix returns the slash-terminated namespace for one session. It contains every device branch, metadata object, and immutable shard for the session.

Types

type AppendExecutor

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

AppendExecutor publishes planned append shards and commits their cursor one step at a time. Its fields are private so a constructed executor cannot be changed into one that writes a different namespace without validation.

func NewAppendExecutor

func NewAppendExecutor(store remote.Remote, recipient *ecdh.PublicKey, layout ObjectLayout, state CursorStore, options PlanOptions) (AppendExecutor, error)

NewAppendExecutor validates the dependencies for durable append execution.

func (AppendExecutor) Execute

func (e AppendExecutor) Execute(ctx context.Context, cursor PushCursor, records [][]byte) (PushCursor, error)

Execute plans and publishes the suffix after cursor.

The cursor returned after an execution error is the last cursor known to be durable in the local state file once remote execution has started. If the error occurs during validation or planning, the returned cursor is zero.

func (AppendExecutor) PublishMetadata

func (e AppendExecutor) PublishMetadata(ctx context.Context, cursor PushCursor, payload []byte) error

PublishMetadata publishes the metadata tip represented by a durable cursor. It is intentionally separate from Execute: shard and cursor durability are established first, so a metadata retry never republishes immutable shards.

type AppendPlan

type AppendPlan struct {
	Parts []ShardPart
	Next  PushCursor
}

AppendPlan is the immutable work and resulting cursor for one local push. Parts are ordered by their device-local object sequence.

func PlanAppend

func PlanAppend(cursor PushCursor, records [][]byte, options PlanOptions) (AppendPlan, error)

PlanAppend validates a local canonical stream and plans only its suffix after cursor. It never infers progress from remote storage.

type Branch

type Branch struct {
	// DeviceID identifies the device that wrote this stream. It is an opaque
	// derived identifier, not a local path or a user-facing device name.
	DeviceID string

	// Records is the complete canonical record sequence assembled from the
	// device's contiguous shards.
	Records [][]byte

	// HeadDigest is the digest after the final record in Records.
	HeadDigest [32]byte
}

Branch is one complete device stream for a session.

func AssembleBranch

func AssembleBranch(deviceID string, parts []ShardPart) (Branch, error)

AssembleBranch validates and assembles one device's shard stream.

Shard parts may arrive in arbitrary list order. A gap, duplicate sequence, base mismatch, or digest mismatch is an error: callers must not silently use the available prefix as a complete session.

func FetchBranches

func FetchBranches(ctx context.Context, store remote.Remote, projectID, sessionID string, identity *ecdh.PrivateKey) ([]Branch, error)

FetchBranches lists, decrypts, validates, and assembles every complete device branch visible under one remote session prefix.

A remotely listed gap is never treated as an absent suffix. The operation fails with ErrIncompleteBranch so callers can retry after eventual consistency settles instead of restoring a silently truncated session.

func FetchBranchesWithIdentities

func FetchBranchesWithIdentities(ctx context.Context, store remote.Remote, projectID, sessionID string, identities []*ecdh.PrivateKey) ([]Branch, error)

FetchBranchesWithIdentities reads branches encrypted under any retained content-key generation.

func FetchBranchesWithIdentitiesAndDevices

func FetchBranchesWithIdentitiesAndDevices(ctx context.Context, store remote.Remote, projectID, sessionID string, identities []*ecdh.PrivateKey, allowed map[string]struct{}) ([]Branch, error)

FetchBranchesWithIdentitiesAndDevices reads branches and optionally filters out branches from revoked devices.

func FetchCompleteBranches

func FetchCompleteBranches(ctx context.Context, store remote.Remote, projectID, sessionID string, identity *ecdh.PrivateKey) ([]Branch, error)

FetchCompleteBranches reads and validates the authenticated metadata and every visible shard branch for a session.

A List call can be eventually consistent. FetchBranches can prove that the shards it sees are contiguous, but contiguity alone cannot prove that the final shard is visible: a stale listing can end at a perfectly valid shard. The per-device metadata tip supplies that missing upper bound. If the metadata record count or digest does not match the assembled branch, the session is incomplete and must not be restored.

func FetchCompleteBranchesWithIdentities

func FetchCompleteBranchesWithIdentities(ctx context.Context, store remote.Remote, projectID, sessionID string, identities []*ecdh.PrivateKey) ([]Branch, error)

FetchCompleteBranchesWithIdentities validates metadata and branches using every retained content-key generation.

func FetchCompleteBranchesWithIdentitiesAndDevices

func FetchCompleteBranchesWithIdentitiesAndDevices(ctx context.Context, store remote.Remote, projectID, sessionID string, identities []*ecdh.PrivateKey, allowed map[string]struct{}) ([]Branch, error)

FetchCompleteBranchesWithIdentitiesAndDevices validates metadata and branches after filtering the current membership set.

type Comparison

type Comparison struct {
	Relation     Relation
	CommonPrefix uint64
	LeftCount    uint64
	RightCount   uint64
}

Comparison is the result of comparing two canonical record sequences.

func CompareRecords

func CompareRecords(left, right [][]byte) Comparison

CompareRecords compares two complete canonical sequences byte-for-byte.

type CursorStore

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

CursorStore persists one device-local push cursor under a CtxHop configuration root.

The root is kept separate from ObjectLayout because the former is a local filesystem location while the latter contains only opaque remote IDs.

func NewCursorStore

func NewCursorStore(root string, layout ObjectLayout) (CursorStore, error)

NewCursorStore validates a local state root and remote identity tuple.

func (CursorStore) Load

func (s CursorStore) Load(ctx context.Context) (PushCursor, error)

Load reads and strictly validates the local push cursor.

func (CursorStore) Save

func (s CursorStore) Save(ctx context.Context, cursor PushCursor) error

Save atomically replaces the local push cursor after validating it.

type DeviceActivity

type DeviceActivity struct {
	DeviceID       string
	LastActivityAt time.Time
}

DeviceActivity contains a device ID inferred from session branch keys.

func DiscoverDeviceBranches

func DiscoverDeviceBranches(ctx context.Context, store remote.Remote) ([]DeviceActivity, error)

DiscoverDeviceBranches lists device IDs present in valid session branch keys. The timestamp is backend metadata and is advisory; it is never used to order or resolve session versions.

type DeviceRecord

type DeviceRecord struct {
	DeviceID     string
	Name         string
	System       string
	LastActiveAt time.Time
}

DeviceRecord is the encrypted self-description published by one device.

func FetchDeviceRecords

func FetchDeviceRecords(ctx context.Context, store remote.Remote, identity *ecdh.PrivateKey) ([]DeviceRecord, error)

FetchDeviceRecords reads and decrypts every explicit device record.

func FetchDeviceRecordsWithIdentities

func FetchDeviceRecordsWithIdentities(ctx context.Context, store remote.Remote, identities []*ecdh.PrivateKey) ([]DeviceRecord, error)

FetchDeviceRecordsWithIdentities reads records encrypted under any retained content-key generation.

func NewDeviceRecord

func NewDeviceRecord(deviceID, name, system string, lastActiveAt time.Time) (DeviceRecord, error)

NewDeviceRecord validates and normalizes one device self-description.

func OpenDeviceRecord

func OpenDeviceRecord(identity *ecdh.PrivateKey, objectKey string, sealed []byte) (DeviceRecord, error)

OpenDeviceRecord decrypts and validates a record read from its exact object key.

func (DeviceRecord) Validate

func (r DeviceRecord) Validate() error

Validate checks the fields that may be persisted in a device record.

type EnvironmentMetadata

type EnvironmentMetadata struct {
	References []environment.Reference
	Components []environment.ComponentContent
}

EnvironmentMetadata is the optional, encrypted dependency manifest for one device branch. Component bodies are limited to filtered, non-sensitive text; it never contains tokens, credentials, commands, or project files.

func NewEnvironmentMetadata

func NewEnvironmentMetadata(references []environment.Reference) (EnvironmentMetadata, error)

func NewEnvironmentMetadataWithComponents

func NewEnvironmentMetadataWithComponents(references []environment.Reference, components []environment.ComponentContent) (EnvironmentMetadata, error)

func OpenEnvironment

func OpenEnvironment(identity *ecdh.PrivateKey, objectKey string, sealed []byte) (EnvironmentMetadata, error)

func ParseEnvironmentMetadata

func ParseEnvironmentMetadata(payload []byte) (EnvironmentMetadata, error)

func ReadEnvironmentManifest

func ReadEnvironmentManifest(ctx context.Context, store remote.Remote, layout ObjectLayout, identities []*ecdh.PrivateKey) (EnvironmentMetadata, error)

ReadEnvironmentManifest reads and decrypts the optional environment object for one device-owned session branch. Component bodies remain encrypted at rest and are only returned to an explicit caller that is preparing apply.

func (EnvironmentMetadata) MarshalBinary

func (m EnvironmentMetadata) MarshalBinary() ([]byte, error)

func (EnvironmentMetadata) Validate

func (m EnvironmentMetadata) Validate() error

type FailureClass

type FailureClass string

FailureClass is the safe, finite classification persisted for a failed task. It intentionally does not contain the original error text.

const (
	FailureNone           FailureClass = ""
	FailureNetwork        FailureClass = "network"
	FailureUnknown        FailureClass = "unknown"
	FailureCredentials    FailureClass = "credentials"
	FailurePermission     FailureClass = "permission"
	FailureStorageFull    FailureClass = "storage-full"
	FailureSessionCorrupt FailureClass = "session-corrupt"
	FailureExcluded       FailureClass = "excluded"
)

func (FailureClass) Retryable

func (f FailureClass) Retryable() bool

Retryable reports whether this failure should receive exponential backoff.

func (FailureClass) Validate

func (f FailureClass) Validate() error

Validate checks whether a failure class is part of the stable queue enum.

type LocalFileLock added in v0.1.2

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

LocalFileLock is an advisory lock shared by CtxHop processes on one device. The lock file contains no application data and can remain on disk after the process exits; the operating system releases the lock with the file handle.

func AcquireLocalFileLock added in v0.1.2

func AcquireLocalFileLock(ctx context.Context, path string) (*LocalFileLock, error)

AcquireLocalFileLock opens path and waits until this process owns its exclusive advisory lock. The wait observes ctx so a cancelled push does not remain blocked behind another process forever.

func (*LocalFileLock) Close added in v0.1.2

func (l *LocalFileLock) Close() error

Close releases the operating-system lock and closes the lock file.

type Metadata

type Metadata struct {
	RecordCount uint64
	HeadDigest  [32]byte
	Payload     []byte
}

Metadata is the format-neutral plaintext carried by one device's mutable metadata object. Payload is an opaque compact JSON value for higher layers.

func NewMetadata

func NewMetadata(recordCount uint64, headDigest [32]byte, payload []byte) (Metadata, error)

NewMetadata validates and copies one metadata payload.

func OpenMetadata

func OpenMetadata(identity *ecdh.PrivateKey, objectKey string, sealed []byte) (Metadata, error)

OpenMetadata decrypts, decompresses and validates metadata read from its exact object key. Payloads written before compression are accepted as-is.

func ParseMetadata

func ParseMetadata(data []byte) (Metadata, error)

ParseMetadata strictly decodes metadata received from an untrusted source.

func (Metadata) MarshalBinary

func (m Metadata) MarshalBinary() ([]byte, error)

MarshalBinary encodes a deterministic plaintext metadata envelope.

func (Metadata) Validate

func (m Metadata) Validate() error

Validate checks the metadata envelope and its opaque JSON payload.

type MetadataRef

type MetadataRef struct {
	DeviceID              string
	Metadata              Metadata
	Environment           []environment.Reference
	EnvironmentComponents []environment.Component
}

MetadataRef is one validated metadata object associated with a device.

func FetchMetadata

func FetchMetadata(ctx context.Context, store remote.Remote, projectID, sessionID string, identity *ecdh.PrivateKey) ([]MetadataRef, error)

FetchMetadata lists, reads, decrypts, and validates every device metadata object under one remote session prefix.

func FetchMetadataWithIdentities

func FetchMetadataWithIdentities(ctx context.Context, store remote.Remote, projectID, sessionID string, identities []*ecdh.PrivateKey) ([]MetadataRef, error)

FetchMetadataWithIdentities reads metadata encrypted under any retained content-key generation.

func FetchMetadataWithIdentitiesAndDevices

func FetchMetadataWithIdentitiesAndDevices(ctx context.Context, store remote.Remote, projectID, sessionID string, identities []*ecdh.PrivateKey, allowed map[string]struct{}) ([]MetadataRef, error)

FetchMetadataWithIdentitiesAndDevices reads metadata and optionally ignores branches whose device ID is not in the current membership set.

type ObjectLayout

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

ObjectLayout identifies the remote namespace for one session and one device. The identifiers must be keyed, opaque values from the crypto layer.

func NewObjectLayout

func NewObjectLayout(projectID, sessionID, deviceID string) (ObjectLayout, error)

NewObjectLayout validates the opaque identifiers used in remote object keys.

func (ObjectLayout) DevicePrefix

func (l ObjectLayout) DevicePrefix() (string, error)

DevicePrefix returns the only prefix this device may write.

func (ObjectLayout) EnvironmentKey

func (l ObjectLayout) EnvironmentKey() (string, error)

EnvironmentKey returns the optional dependency manifest for this device branch.

func (ObjectLayout) GitStateKey

func (l ObjectLayout) GitStateKey() (string, error)

ShardKey returns the immutable key for one device-local shard sequence.

func (ObjectLayout) GitTransferKey

func (l ObjectLayout) GitTransferKey() (string, error)

func (ObjectLayout) MetadataKey

func (l ObjectLayout) MetadataKey() (string, error)

MetadataKey returns this device's mutable metadata object key.

func (ObjectLayout) SessionPrefix

func (l ObjectLayout) SessionPrefix() (string, error)

SessionPrefix returns the key prefix containing all device branches of the session.

func (ObjectLayout) ShardKey

func (l ObjectLayout) ShardKey(number uint64) (string, error)

func (ObjectLayout) WorkspaceKey

func (l ObjectLayout) WorkspaceKey() (string, error)

WorkspaceKey returns the optional Git/workspace snapshot for this device branch.

type PlanOptions

type PlanOptions struct {
	// MaxRecords is the largest number of records in one shard.
	MaxRecords int

	// MaxEncodedBytes is the largest deterministic plaintext envelope. It may
	// be lower than the format hard cap, but never higher.
	MaxEncodedBytes int
}

PlanOptions controls how a local suffix is split into immutable shards.

func DefaultPlanOptions

func DefaultPlanOptions() PlanOptions

DefaultPlanOptions returns conservative defaults for a push plan.

type ProjectAnnouncement

type ProjectAnnouncement struct {
	ProjectID    string
	DeviceID     string
	IdentityKind string
	Identity     string
	AnnouncedAt  time.Time
}

ProjectAnnouncement is an encrypted, device-owned record that makes one project discoverable to other authorized devices. It contains no local path; paths have meaning only on the device that owns them.

func FetchProjectAnnouncements

func FetchProjectAnnouncements(ctx context.Context, store remote.Remote, identities []*ecdh.PrivateKey, allowed map[string]struct{}) ([]ProjectAnnouncement, error)

FetchProjectAnnouncements discovers projects from the encrypted, device-owned announcement keys. It returns an empty slice when no projects have been announced.

func NewProjectAnnouncement

func NewProjectAnnouncement(projectID, deviceID, identityKind, identity string, announcedAt time.Time) (ProjectAnnouncement, error)

NewProjectAnnouncement validates one project discovery record.

func OpenProjectAnnouncement

func OpenProjectAnnouncement(identity *ecdh.PrivateKey, objectKey string, sealed []byte) (ProjectAnnouncement, error)

OpenProjectAnnouncement decrypts and validates a discovery record.

func (ProjectAnnouncement) Validate

func (r ProjectAnnouncement) Validate() error

Validate checks fields that are persisted in a project announcement.

type ProjectMetadataRef

type ProjectMetadataRef struct {
	SessionID string
	Devices   []MetadataRef
}

ProjectMetadataRef groups the authenticated metadata objects found under one opaque remote session identifier.

The session identifier is deliberately kept separate from MetadataRef: MetadataRef describes one device branch, while this type is the result of a project-level listing. No shard object is read by FetchProjectMetadata.

func FetchProjectMetadata

func FetchProjectMetadata(ctx context.Context, store remote.Remote, projectID string, identity *ecdh.PrivateKey) ([]ProjectMetadataRef, error)

FetchProjectMetadata lists and decrypts metadata for every session in one project. It never reads immutable shard bodies, and it never infers a session from a shard-only prefix.

The remote listing is collected before any object body is read. This makes the set of session/device metadata objects stable for the duration of this call and lets the reader reject duplicate metadata entries deterministically.

func FetchProjectMetadataWithIdentities

func FetchProjectMetadataWithIdentities(ctx context.Context, store remote.Remote, projectID string, identities []*ecdh.PrivateKey) ([]ProjectMetadataRef, error)

FetchProjectMetadataWithIdentities reads project metadata under any retained content-key generation.

func FetchProjectMetadataWithIdentitiesAndDevices

func FetchProjectMetadataWithIdentitiesAndDevices(ctx context.Context, store remote.Remote, projectID string, identities []*ecdh.PrivateKey, allowed map[string]struct{}) ([]ProjectMetadataRef, error)

FetchProjectMetadataWithIdentitiesAndDevices reads project metadata while optionally filtering revoked device branches.

type PullTip

type PullTip struct {
	DeviceID    string
	RecordCount uint64
	HeadDigest  [32]byte
}

PullTip is the content-free remote progress retained for a foreign device.

func NewPullTip

func NewPullTip(deviceID string, recordCount uint64, headDigest [32]byte) (PullTip, error)

NewPullTip validates one observed remote tip.

func (PullTip) Validate

func (t PullTip) Validate() error

Validate checks the opaque identity and empty-prefix digest rule.

type PullTipStore

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

PullTipStore persists observed foreign tips for one local device/session. The state is advisory: it suppresses repeated body reads, while remote metadata remains authoritative when a tip changes.

func NewPullTipStore

func NewPullTipStore(root string, layout ObjectLayout) (PullTipStore, error)

NewPullTipStore validates a local state root and remote identity tuple.

func (PullTipStore) Load

func (s PullTipStore) Load(ctx context.Context) ([]PullTip, error)

Load returns observed tips in deterministic device-ID order. An absent file means that no foreign tip has been observed yet.

func (PullTipStore) Save

func (s PullTipStore) Save(ctx context.Context, tips []PullTip) error

Save atomically replaces observed tips after validation and deterministic sorting. The caller's slice is not modified.

type PushCursor

type PushCursor struct {
	NextShard   uint64
	RecordCount uint64
	HeadDigest  [32]byte
}

PushCursor is the local source of truth for the next device-owned shard.

The cursor is intentionally local state. A remote listing may be stale, and using it to choose the next sequence can overwrite a shard after an interrupted or eventually consistent write.

func NewPushCursor

func NewPushCursor() PushCursor

NewPushCursor returns the state before the first shard is published.

func PutShard

func PutShard(ctx context.Context, store remote.Remote, recipient *ecdh.PublicKey, layout ObjectLayout, cursor PushCursor, part ShardPart) (PushCursor, error)

PutShard encrypts and publishes one cursor-checked shard. The returned cursor is durable only when the remote write returns nil; callers should persist it atomically with their local queue state.

func (PushCursor) Advance

func (c PushCursor) Advance(part ShardPart) (PushCursor, error)

Advance verifies and applies one successful shard publication.

func (PushCursor) Validate

func (c PushCursor) Validate() error

Validate checks that a cursor describes a possible local device prefix.

type QueueItem

type QueueItem struct {
	Key           QueueKey
	Attempt       uint32
	NextAttemptAt time.Time
	State         QueueState
	Failure       FailureClass
}

QueueItem is one durable pending task and its retry metadata.

func (QueueItem) Validate

func (i QueueItem) Validate() error

Validate checks both the item fields and their state-machine invariants.

type QueueKey

type QueueKey struct {
	ProjectID string
	SessionID string
	DeviceID  string
}

QueueKey identifies a pending task without retaining any local path or session content.

func NewQueueKey

func NewQueueKey(projectID, sessionID, deviceID string) (QueueKey, error)

NewQueueKey validates the opaque identifiers used by a pending task.

func (QueueKey) Validate

func (k QueueKey) Validate() error

Validate checks the identifier shape accepted by the queue wire format.

type QueueSnapshot

type QueueSnapshot struct {
	Items []QueueItem
}

QueueSnapshot is the in-memory representation of the durable queue.

func (*QueueSnapshot) Complete

func (q *QueueSnapshot) Complete(key QueueKey) error

Complete removes a task after a successful durable sync.

func (QueueSnapshot) Due

func (q QueueSnapshot) Due(now time.Time) []QueueItem

Due returns pending items whose retry time has arrived, sorted deterministically by time and then by opaque task key.

func (*QueueSnapshot) Enqueue

func (q *QueueSnapshot) Enqueue(key QueueKey) error

Enqueue adds a task if it is not already present. Re-enqueueing an existing item is idempotent and does not clear a terminal block.

func (QueueSnapshot) Item

func (q QueueSnapshot) Item(key QueueKey) (QueueItem, error)

Item returns one queued task without changing the snapshot.

func (*QueueSnapshot) RecordFailure

func (q *QueueSnapshot) RecordFailure(key QueueKey, failure FailureClass, now time.Time, policy RetryPolicy) (QueueItem, error)

RecordFailure updates a queued task after a failed attempt and returns the resulting durable item. The caller must persist the changed snapshot.

func (*QueueSnapshot) Reopen

func (q *QueueSnapshot) Reopen(key QueueKey, failure FailureClass) error

Reopen clears a terminal failure after the caller has revalidated the source data. Excluded and session-corrupt failures are source-data decisions; credential, permission and storage failures remain blocked until the user addresses the backend configuration.

func (QueueSnapshot) Validate

func (q QueueSnapshot) Validate() error

Validate checks all items and rejects duplicate task keys.

type QueueState

type QueueState string

QueueState describes whether an item can be selected for another attempt.

const (
	QueuePending QueueState = "pending"
	QueueBlocked QueueState = "blocked"
)

type QueueStore

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

QueueStore persists queue metadata below a local configuration root.

func NewQueueStore

func NewQueueStore(root string) (QueueStore, error)

NewQueueStore validates a local queue root.

func (QueueStore) Complete

func (s QueueStore) Complete(ctx context.Context, key QueueKey) error

Complete removes a successfully synchronized task from the queue.

func (QueueStore) Due

func (s QueueStore) Due(ctx context.Context, now time.Time) ([]QueueItem, error)

Due returns pending tasks eligible at the supplied time.

func (QueueStore) Enqueue

func (s QueueStore) Enqueue(ctx context.Context, key QueueKey) error

Enqueue loads the queue, adds a task idempotently, and saves it.

func (QueueStore) Item

func (s QueueStore) Item(ctx context.Context, key QueueKey) (QueueItem, error)

Item loads one queued task without changing the queue.

func (QueueStore) Load

func (s QueueStore) Load(ctx context.Context) (QueueSnapshot, error)

Load reads and strictly validates the queue. A missing file is an empty queue, which is safe because queue metadata never establishes sync progress.

func (QueueStore) RecordFailure

func (s QueueStore) RecordFailure(ctx context.Context, key QueueKey, failure FailureClass, now time.Time, policy RetryPolicy) (QueueItem, error)

RecordFailure records a classified failure and persists its retry state.

func (QueueStore) Reopen

func (s QueueStore) Reopen(ctx context.Context, key QueueKey, failure FailureClass) error

Reopen clears an excluded terminal task after the caller has independently revalidated its source session.

func (QueueStore) Save

func (s QueueStore) Save(ctx context.Context, snapshot QueueSnapshot) error

Save validates and atomically replaces the queue contents.

func (QueueStore) Update added in v0.1.2

func (s QueueStore) Update(ctx context.Context, update func(*QueueSnapshot) error) error

Update serializes a queue read-modify-write operation. It is used by the queued pusher when independent sessions are uploaded concurrently, so one worker cannot overwrite another worker's queue transition.

type Relation

type Relation int

Relation describes the prefix relationship between two record sequences.

const (
	// Equal means both sequences contain the same records.
	Equal Relation = iota
	// LeftPrefix means left is a strict prefix of right.
	LeftPrefix
	// RightPrefix means right is a strict prefix of left.
	RightPrefix
	// Diverged means neither sequence is a prefix of the other.
	Diverged
)

func (Relation) String

func (r Relation) String() string

String returns a stable name for a relation.

type Resolution

type Resolution struct {
	Kind         ResolutionKind
	CommonPrefix uint64
	Versions     []Version
}

Resolution is the non-destructive result of combining device branches.

func ResolveBranches

func ResolveBranches(branches []Branch) (Resolution, error)

ResolveBranches removes only redundant prefix branches and preserves every incomparable maximal version.

func ResolveBranchesOwned

func ResolveBranchesOwned(branches []Branch) (Resolution, error)

ResolveBranchesOwned resolves branches by transferring their record buffers into the result. Callers must not mutate or reuse branches after this call; the function is intended for one-shot restore flows that release the input branches after resolution.

type ResolutionKind

type ResolutionKind int

ResolutionKind describes the maximal versions found across devices.

const (
	// ResolutionConsistent means every device exposed the same sequence.
	ResolutionConsistent ResolutionKind = iota
	// ResolutionFastForward means one sequence strictly extends the others.
	ResolutionFastForward
	// ResolutionFork means multiple incomparable sequences must be preserved.
	ResolutionFork
)

func (ResolutionKind) String

func (k ResolutionKind) String() string

String returns a stable name for a resolution kind.

type RestoreStats

type RestoreStats struct {
	CrossDeviceRestores uint64
	LastRestoredAt      time.Time
}

RestoreStats is the content-free local measurement of successful cross-device restores.

It deliberately contains no project, session, device, path, or backend information. The state never leaves the local configuration directory.

func (RestoreStats) Validate

func (s RestoreStats) Validate() error

Validate checks the timestamp representation used by the local state file.

type RestoreStatsStore

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

RestoreStatsStore persists aggregate restore statistics below one local configuration root.

func NewRestoreStatsStore

func NewRestoreStatsStore(root string) (RestoreStatsStore, error)

NewRestoreStatsStore validates a local statistics root.

func (RestoreStatsStore) Load

Load returns local statistics. An absent file means that no restore has been recorded yet and does not create any local state.

func (RestoreStatsStore) RecordRestore

func (s RestoreStatsStore) RecordRestore(ctx context.Context, localDeviceID string, sourceDeviceIDs []string, now time.Time) (RestoreStats, error)

RecordRestore increments the aggregate only when the selected version has at least one source device other than localDeviceID.

sourceDeviceIDs is the complete source-device set for the selected restore version. Device IDs are validated but never persisted.

type RetryPolicy

type RetryPolicy struct {
	BaseDelay time.Duration
	MaxDelay  time.Duration
}

RetryPolicy controls deterministic exponential backoff.

func DefaultRetryPolicy

func DefaultRetryPolicy() RetryPolicy

DefaultRetryPolicy returns the product defaults for transient failures.

func (RetryPolicy) Delay

func (p RetryPolicy) Delay(attempt uint32) (time.Duration, error)

Delay computes the delay for a one-based failed-attempt number.

func (RetryPolicy) Validate

func (p RetryPolicy) Validate() error

Validate checks a retry policy before it is used to update queue state.

type SessionLayout

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

SessionLayout identifies the remote prefix shared by every device branch of one session.

func NewSessionLayout

func NewSessionLayout(projectID, sessionID string) (SessionLayout, error)

NewSessionLayout validates the identifiers used by a session prefix.

func (SessionLayout) Prefix

func (l SessionLayout) Prefix() (string, error)

Prefix returns the exact remote prefix containing all device branches.

type Shard

type Shard struct {
	Base         uint64
	PrefixDigest [32]byte
	Records      [][]byte
}

Shard is an immutable range of canonical session records.

Base is the number of records before this shard. PrefixDigest is the digest after those records. Records are copied when a Shard is constructed or decoded, so callers can safely retain their input buffers.

func NewShard

func NewShard(base uint64, prefixDigest [32]byte, records [][]byte) (Shard, error)

NewShard validates and constructs a shard for records beginning at base.

func OpenShard

func OpenShard(identity *ecdh.PrivateKey, objectKey string, sealed []byte) (Shard, error)

OpenShard decrypts and validates a shard read from objectKey.

func ParseShard

func ParseShard(data []byte) (Shard, error)

ParseShard validates and decodes a shard received from an untrusted source.

func (Shard) Count

func (s Shard) Count() uint64

Count returns the number of records in the shard.

func (Shard) Digest

func (s Shard) Digest() [32]byte

Digest returns the digest immediately after this shard.

func (Shard) MarshalBinary

func (s Shard) MarshalBinary() ([]byte, error)

MarshalBinary encodes a shard as its deterministic plaintext envelope. Callers must encrypt the returned bytes before sending them to a Remote.

func (Shard) Validate

func (s Shard) Validate() error

Validate checks the record shape and the shard's own digest chain.

type ShardPart

type ShardPart struct {
	Number uint64
	Shard  Shard
}

ShardPart associates an immutable shard with the device-local sequence number used in its remote object name.

type Version

type Version struct {
	Records    [][]byte
	Devices    []string
	HeadDigest [32]byte
}

Version is one maximal session version. Devices contains every device whose assembled branch has exactly this content.

Jump to

Keyboard shortcuts

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