Documentation
¶
Index ¶
- Constants
- Variables
- func ApplyCompiled(root string, result CompileResult, preserveCreatedAt string) error
- func BlobID(content []byte) string
- func CleanMaterializedEnv(root string) (bool, error)
- func DeclarationPath(root string) string
- func DecryptBlob(key CryptoKey, envelope EncryptedEnvelope, aad []byte) ([]byte, error)
- func EncryptionSecretRef(config Config) string
- func EnvAssetPath(root string) string
- func EnvDocumentDigest(data []byte) string
- func EnvRuntimePath(root string) string
- func FormatDotenv(values map[string]string) []byte
- func HexDigest(data []byte) string
- func IsNotConfigured(err error) bool
- func IsSupportedScheme(scheme string) bool
- func IsSyncConfigError(err error, code string) bool
- func KeyID(secretRef string) string
- func Logout(root string) error
- func MarshalForDigest(c SyncConfig) []byte
- func MaterializeEnv(root string, snapshot *EnvSnapshot, allowlist []string) (string, error)
- func ParseDotenv(data []byte) (map[string]string, error)
- func PathHash(path string) string
- func RedactedData(state State) map[string]any
- func Register(scheme string, factory StoreFactory)
- func SaveEnvAsset(root string, asset EnvAsset) error
- func SaveSecretEnvelope(root string, env SecretEnvelope) error
- func SecretsAssetPath(root string) string
- func SourceMarkerPath(root string) string
- func SupportedSyncBackendKind(kind string) bool
- func SupportedSyncBackendKings(kind string) bool
- func WriteConfig(root string, config Config) error
- type BlobStore
- type CachedBlobStore
- func (c *CachedBlobStore) Delete(ctx context.Context, key string) error
- func (c *CachedBlobStore) Get(ctx context.Context, key string) ([]byte, string, error)
- func (c *CachedBlobStore) Put(ctx context.Context, key string, data []byte, baseRev string) (string, error)
- func (c *CachedBlobStore) Stat(ctx context.Context, key string) (string, error)
- type CompileRequest
- type CompileResult
- type ConditionalWriteCapability
- type Config
- type CryptoKey
- type DeviceSession
- type DoctorResult
- type DotenvError
- type DriftItem
- type DriftReport
- type EncryptedEnvelope
- type EnvAsset
- type EnvAssetError
- type EnvReloader
- type EnvSnapshot
- func (s *EnvSnapshot) AllKeys() []string
- func (s *EnvSnapshot) ApplyAllowlist(allowlist []string) map[string]string
- func (s *EnvSnapshot) Digest() string
- func (s *EnvSnapshot) IsEmpty() bool
- func (s *EnvSnapshot) Lookup(key string) (string, bool)
- func (s *EnvSnapshot) OverlayEnv(base []string, allowlist []string) []string
- func (s *EnvSnapshot) Source() string
- type EnvUnlockProvider
- type ExtendedBlobStore
- type FakeBlobCheckRequest
- type FakeBlobCheckResponse
- type FakeContractError
- type FakeContractErrorResponse
- type FakeErrorResponse
- type FakeManifestPutRequest
- type FakeManifestResponse
- type FakeRevisionCommitRequest
- type FakeRevisionCommitResponse
- type FakeRevisionResponse
- type FakeServer
- type FakeUnlockProvider
- type FileBackend
- func (b *FileBackend) BatchStat(ctx context.Context, keys []string) (map[string]string, error)
- func (b *FileBackend) Delete(ctx context.Context, key string) error
- func (b *FileBackend) Exists(ctx context.Context, key string) (bool, error)
- func (b *FileBackend) Get(ctx context.Context, key string) ([]byte, string, error)
- func (b *FileBackend) List(ctx context.Context, prefix string) ([]ObjectInfo, error)
- func (b *FileBackend) Put(ctx context.Context, key string, data []byte, baseRev string) (string, error)
- func (b *FileBackend) Stat(ctx context.Context, key string) (string, error)
- func (b *FileBackend) SupportsConditionalWrites() bool
- type LoginRequest
- type Manifest
- type ManifestDelete
- type ManifestEntry
- type ManifestFileTooLargeError
- type ManifestIdentity
- type ManifestUnsafePathError
- type ObjectInfo
- type RcloneBackend
- func (b *RcloneBackend) BatchStat(ctx context.Context, keys []string) (map[string]string, error)
- func (b *RcloneBackend) Delete(ctx context.Context, key string) error
- func (b *RcloneBackend) Exists(ctx context.Context, key string) (bool, error)
- func (b *RcloneBackend) Get(ctx context.Context, key string) ([]byte, string, error)
- func (b *RcloneBackend) List(ctx context.Context, prefix string) ([]ObjectInfo, error)
- func (b *RcloneBackend) Put(ctx context.Context, key string, data []byte, _ string) (string, error)
- func (b *RcloneBackend) Stat(ctx context.Context, key string) (string, error)
- func (b *RcloneBackend) SupportsConditionalWrites() bool
- type ReloadStatus
- type S3Backend
- func (s *S3Backend) BatchStat(ctx context.Context, keys []string) (map[string]string, error)
- func (s *S3Backend) Delete(ctx context.Context, key string) error
- func (s *S3Backend) Exists(ctx context.Context, key string) (bool, error)
- func (s *S3Backend) Get(ctx context.Context, key string) ([]byte, string, error)
- func (s *S3Backend) List(ctx context.Context, prefix string) ([]ObjectInfo, error)
- func (s *S3Backend) Put(ctx context.Context, key string, data []byte, baseRev string) (string, error)
- func (s *S3Backend) Stat(ctx context.Context, key string) (string, error)
- func (s *S3Backend) SupportsConditionalWrites() bool
- type S3BackendOptions
- type S3Config
- type SecretEntry
- type SecretEntryKind
- type SecretEnvelope
- type SecretEnvelopeError
- type SecretMetadata
- type SourceMarker
- type State
- type StoreFactory
- type SyncBackend
- type SyncConfig
- type SyncConfigError
- type SyncPolicy
- type SyncSecretRefs
- type SyncWorkspace
- type UnlockProvider
Constants ¶
const ( ManifestSchemaVersionV1 = "pinax.cloud.manifest.v1" ManifestSchemaVersionV2 = "pinax.cloud.manifest.v2" ManifestSchemaVersion = ManifestSchemaVersionV1 )
const ( ConfigSchemaVersion = "pinax.cloud.config.v1" SessionSchemaVersion = "pinax.cloud.session.v1" )
const ( SyncConfigSchemaVersion = "pinax.sync.config.v1" SyncSecretsSchemaVersion = "pinax.sync.secrets.v1" SourceMarkerSchemaVersion = "pinax.sync.source-marker.v1" )
Declaration schema versions. These are the repository-tracked, portable declaration layer — distinct from the device-owned pinax.cloud.config.v1 runtime state that the compiler emits.
const ( DeclarationFileName = "pinax-sync.yaml" SecretsAssetFileName = "pinax-sync.secrets.yaml" SourceMarkerFileName = "pinax-sync.source.yaml" )
DeclarationFilePaths are the CLI-authored structured assets inside the vault. Business code and agents MUST NOT assemble these by hand.
const CreateIfAbsentRevision = "__pinax_create_if_absent__"
const CryptoEnvelopeSchemaVersion = "pinax.cloud.envelope.v1"
const EnvAssetFileName = "pinax-sync.env.age"
EnvAssetFileName is the fixed, repository-tracked ciphertext dotenv asset. The path is fixed (not user-selectable) to prevent path-escape and protected- path bypass. Plaintext runtime files live under a separate managed directory.
const EnvAssetSchemaVersion = "pinax.sync.env.v1"
EnvAssetSchemaVersion is the frozen contract version for the encrypted dotenv asset. The asset is repository-tracked; its plaintext is never required to be committed and lives only in the in-memory EnvSnapshot at runtime.
const EnvRuntimeDir = "runtime"
EnvRuntimeDir is the managed, 0600, Git-ignored directory for materialized plaintext env files. Only --materialize writes here; default runs stay in memory.
const EnvRuntimeFileName = "pinax-sync.env"
EnvRuntimeFileName is the single managed materialized plaintext filename.
const MaxManifestFileBytes = 100 * 1024 * 1024
Variables ¶
var ( ErrObjectNotFound = errors.New("object not found") ErrConflict = errors.New("revision conflict") )
var ErrEnvAssetMissing = errors.New("sync env asset not found")
ErrEnvAssetMissing is returned when no pinax-sync.env.age exists.
var ErrNotConfigured = errors.New("cloud not configured")
var ErrSecretsAssetMissing = errors.New("sync secrets asset not found")
ErrSecretsAssetMissing is returned when no pinax-sync.secrets.yaml exists.
var ErrSyncDeclarationMissing = errors.New("sync repository declaration not found")
ErrSyncDeclarationMissing is returned when no pinax-sync.yaml exists.
var SupportedSyncBackendKinds = map[string]bool{ "s3-direct": true, "rclone-direct": true, "server": true, "embedded": true, }
SupportedSyncBackendKinds is the closed set of backend kinds the declaration layer accepts. Unknown kinds are rejected so an unsupported backend cannot silently compile to a half-usable runtime config.
var ValidRemoteDeletePolicies = map[string]bool{ "": true, "deny": true, "require-approval": true, }
ValidRemoteDeletePolicies enumerates the conservative policy values.
Functions ¶
func ApplyCompiled ¶ added in v0.1.6
func ApplyCompiled(root string, result CompileResult, preserveCreatedAt string) error
ApplyCompiled writes the runtime config and source marker atomically through the canonical authoring boundary, preserving the existing CreatedAt when regenerating the same workspace. It backs up the prior config before writing so apply is restorable.
func CleanMaterializedEnv ¶ added in v0.1.6
CleanMaterializedEnv removes only the Pinax-managed materialized env file. It never removes arbitrary user-selected files and refuses symlink targets.
func DeclarationPath ¶ added in v0.1.6
DeclarationPath returns the path to the repository sync declaration.
func DecryptBlob ¶
func DecryptBlob(key CryptoKey, envelope EncryptedEnvelope, aad []byte) ([]byte, error)
func EncryptionSecretRef ¶
func EnvAssetPath ¶ added in v0.1.6
EnvAssetPath returns the fixed path to the encrypted dotenv asset.
func EnvDocumentDigest ¶ added in v0.1.6
EnvDocumentDigest returns a short stable hex digest of a plaintext dotenv document, used for daemon reload identity checks. It is computed over the raw bytes so it does not reveal structure beyond the digest.
func EnvRuntimePath ¶ added in v0.1.6
EnvRuntimePath returns the fixed path to the materialized plaintext env file.
func FormatDotenv ¶ added in v0.1.6
FormatDotenv renders values back into a canonical strict dotenv document with stable key ordering. Values are single-quoted when they contain special chars so the round-trip survives ParseDotenv without ambiguity.
func HexDigest ¶ added in v0.1.6
HexDigest returns a short stable hex digest of arbitrary bytes, used for the source marker's declaration_digest.
func IsNotConfigured ¶
func IsSupportedScheme ¶
IsSupportedScheme returns true if the scheme has a registered factory.
func IsSyncConfigError ¶ added in v0.1.6
IsSyncConfigError reports whether err is a *SyncConfigError with the given code.
func KeyID ¶ added in v0.1.6
KeyID resolves the secret reference and returns the stable key identifier for secretRef, or an empty string when the reference cannot be resolved.
func MarshalForDigest ¶ added in v0.1.6
func MarshalForDigest(c SyncConfig) []byte
MarshalForDigest serializes a SyncConfig canonically so equal configs produce equal digests regardless of map ordering or whitespace.
func MaterializeEnv ¶ added in v0.1.6
func MaterializeEnv(root string, snapshot *EnvSnapshot, allowlist []string) (string, error)
MaterializeEnv writes the snapshot's plaintext to the managed runtime path with 0600 permissions. It is the compatibility exit for external tools that cannot consume in-memory injection. Callers MUST pass an allowlist so only declared keys are written; the full snapshot is never dumped.
func ParseDotenv ¶ added in v0.1.6
ParseDotenv parses a strict, safe dotenv subset. It accepts:
KEY=value KEY="quoted value" KEY='quoted value'
and rejects shell execution, include directives, recursive ${...} expansion, command substitution $() and backticks, NUL / control characters, empty keys, duplicate keys and multi-line heredocs. Errors report line number and key name only — never the rejected value.
This is intentionally a strict subset of POSIX dotenv so Pinax never evaluates attacker-controlled shell syntax stored in the repository.
func RedactedData ¶
func Register ¶
func Register(scheme string, factory StoreFactory)
Register registers a new BlobStore factory for a URI scheme.
func SaveEnvAsset ¶ added in v0.1.6
SaveEnvAsset persists the encrypted dotenv asset with restrictive permissions. It never writes plaintext; ciphertext + redacted metadata only.
func SaveSecretEnvelope ¶ added in v0.1.6
func SaveSecretEnvelope(root string, env SecretEnvelope) error
SaveSecretEnvelope persists the envelope with restrictive permissions. It never writes plaintext; entries carry only ciphertext.
func SecretsAssetPath ¶ added in v0.1.6
SecretsAssetPath returns the path to the encrypted secrets asset.
func SourceMarkerPath ¶ added in v0.1.6
SourceMarkerPath returns the device-local source marker path (under cloud/ so it is treated as device runtime state, never shared).
func SupportedSyncBackendKind ¶ added in v0.1.6
SupportedSyncBackendKind reports whether kind is a supported declaration backend.
func SupportedSyncBackendKings ¶ added in v0.1.6
func WriteConfig ¶ added in v0.1.6
WriteConfig persists the Capsa runtime config through the application-layer authoring boundary: atomic directory creation, restrictive permissions, and removal of the legacy JSON path. The declaration→runtime compiler reuses this writer so generated config never bypasses the canonical write path.
Types ¶
type BlobStore ¶
type BlobStore interface {
// Get retrieves the object. If not found, returns ErrObjectNotFound.
Get(ctx context.Context, key string) (data []byte, rev string, err error)
// Put uploads the object. baseRev is the expected current revision.
// If baseRev is CreateIfAbsentRevision, the object must not exist.
// If baseRev is not empty and doesn't match, returns ErrConflict.
// Returns the new revision string.
Put(ctx context.Context, key string, data []byte, baseRev string) (newRev string, err error)
// Stat retrieves the revision of the object. If not found, returns ErrObjectNotFound.
Stat(ctx context.Context, key string) (rev string, err error)
// Delete removes the object.
Delete(ctx context.Context, key string) error
}
BlobStore abstracts the underlying blind storage system (S3, File, etc.).
type CachedBlobStore ¶
type CachedBlobStore struct {
// contains filtered or unexported fields
}
CachedBlobStore decorates a BlobStore with local file caching for Get operations.
func NewCachedBlobStore ¶
func NewCachedBlobStore(inner BlobStore, cacheDir string, maxSize int64) *CachedBlobStore
NewCachedBlobStore creates a new caching decorator.
func (*CachedBlobStore) Delete ¶
func (c *CachedBlobStore) Delete(ctx context.Context, key string) error
Delete delegates to inner store and invalidates cache.
type CompileRequest ¶ added in v0.1.6
type CompileRequest struct {
Declaration SyncConfig
ResolvedSecret string // device-local secret ref resolved from credential identity
ResolvedEncrypt string // device-local encryption secret ref resolved from key identity
DeviceID string
Now time.Time
}
CompileRequest bundles the inputs the declaration→runtime compiler needs: the portable declaration, the device-local resolved secret references, the unique device id and a clock for receipts/marker timestamps.
type CompileResult ¶ added in v0.1.6
type CompileResult struct {
RuntimeConfig Config
SourceMarker SourceMarker
DeclarationDigest string
}
CompileResult reports what the compiler would write, without touching disk. Apply uses Apply(); plan/doctor read Result fields directly.
func Compile ¶ added in v0.1.6
func Compile(req CompileRequest) (CompileResult, error)
Compile produces the device runtime Config and source marker from a declaration and resolved secrets. It does NOT write to disk; callers use ApplyCompiled or inspect the result for plan output. The compiler reuses the existing Capsa config normalization so generated state is identical to a manually-logged-in device.
type ConditionalWriteCapability ¶
type ConditionalWriteCapability interface {
SupportsConditionalWrites() bool
}
ConditionalWriteCapability reports whether Put enforces baseRev preconditions durably.
type Config ¶
type Config struct {
SchemaVersion string `json:"schema_version" yaml:"schema_version"`
BackendKind string `json:"backend_kind,omitempty" yaml:"backend_kind,omitempty"`
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
WorkspaceID string `json:"workspace_id" yaml:"workspace_id"`
DeviceID string `json:"device_id" yaml:"device_id"`
SecretRef string `json:"secret_ref,omitempty" yaml:"secret_ref,omitempty"`
EncryptionSecretRef string `json:"encryption_secret_ref,omitempty" yaml:"encryption_secret_ref,omitempty"`
S3 *S3Config `json:"s3,omitempty" yaml:"s3,omitempty"`
CreatedAt string `json:"created_at" yaml:"created_at"`
UpdatedAt string `json:"updated_at" yaml:"updated_at"`
}
type DeviceSession ¶
type DoctorResult ¶
type DoctorResult struct {
Configured bool `json:"configured"`
Status string `json:"status"`
Code string `json:"code,omitempty"`
Message string `json:"message"`
BackendKind string `json:"backend_kind,omitempty"`
AuthBoundary string `json:"auth_boundary,omitempty"`
ServerAudit bool `json:"server_audit"`
Endpoint string `json:"endpoint,omitempty"`
Workspace string `json:"workspace_id,omitempty"`
DeviceID string `json:"device_id,omitempty"`
}
func Doctor ¶
func Doctor(root string) DoctorResult
type DotenvError ¶ added in v0.1.6
DotenvError reports a strict dotenv parse failure with a stable English code and line number. Key is included only when known; the rejected value is never attached so errors stay safe to log and surface in receipts.
func (*DotenvError) Error ¶ added in v0.1.6
func (e *DotenvError) Error() string
type DriftItem ¶ added in v0.1.6
type DriftItem struct {
Field string `json:"field" yaml:"field"`
Reason string `json:"reason" yaml:"reason"`
}
DriftItem is one redacted divergence between declaration and runtime state.
type DriftReport ¶ added in v0.1.6
type DriftReport struct {
InDrift bool `json:"in_drift" yaml:"in_drift"`
Reasons []DriftItem `json:"reasons,omitempty" yaml:"reasons,omitempty"`
}
DriftReport describes the difference between a declaration and the local generated runtime config. Field is a redacted path; Value is never included.
func DetectDrift ¶ added in v0.1.6
func DetectDrift(declaration SyncConfig, runtime Config, marker SourceMarker) DriftReport
DetectDrift compares a declaration against the loaded runtime state and the recorded source marker. It returns redacted field names only.
type EncryptedEnvelope ¶
type EncryptedEnvelope = capsa.EncryptedEnvelope
func EncryptBlob ¶
func EncryptBlob(key CryptoKey, plaintext, aad []byte) (EncryptedEnvelope, error)
func EncryptManifest ¶
func EncryptManifest(key CryptoKey, manifest Manifest) (EncryptedEnvelope, error)
type EnvAsset ¶ added in v0.1.6
type EnvAsset struct {
SchemaVersion string `json:"schema_version" yaml:"schema_version"`
Provider string `json:"provider" yaml:"provider"`
// Ciphertext is provider-specific opaque encrypted material for the whole
// dotenv document (not per-key). Keeping one ciphertext preserves key
// ordering and avoids leaking key names into the asset metadata.
Ciphertext string `json:"ciphertext" yaml:"ciphertext"`
// Digest is a short stable hex digest of the plaintext document, used for
// daemon reload identity checks. It does NOT reveal plaintext contents.
Digest string `json:"digest,omitempty" yaml:"digest,omitempty"`
// KeyNames is the redacted list of declared keys (no values) so list/doctor
// can report what is present without unlocking.
KeyNames []string `json:"key_names,omitempty" yaml:"key_names,omitempty"`
}
EnvAsset is the repository-tracked encrypted dotenv asset. The schema carries ciphertext and redacted metadata only; plaintext key/value pairs live only in the in-memory EnvSnapshot. The asset is safe to commit, while plaintext env files are Git-ignored and Capsa-protected.
func LoadEnvAsset ¶ added in v0.1.6
LoadEnvAsset reads the encrypted dotenv asset. A missing file is reported via ErrEnvAssetMissing so init/doctor can distinguish absent from corrupt.
type EnvAssetError ¶ added in v0.1.6
EnvAssetError is the stable error type for env asset operations. Code is a stable English identifier; Message never carries plaintext values.
func (*EnvAssetError) Error ¶ added in v0.1.6
func (e *EnvAssetError) Error() string
type EnvReloader ¶ added in v0.1.6
type EnvReloader struct {
// contains filtered or unexported fields
}
EnvReloader is the daemon-side reload coordinator. It checks the encrypted env asset identity (content digest) between sync runs; on change it unlocks and parses a new snapshot, then atomically swaps it in for the NEXT run. The current run retains its original snapshot. On failure it keeps the last successful snapshot and reports a structured degraded code.
func NewEnvReloader ¶ added in v0.1.6
func NewEnvReloader(root string, provider UnlockProvider) *EnvReloader
NewEnvReloader creates a reloader. The initial snapshot is loaded lazily on the first RunSnapshot call so a missing asset does not block daemon startup.
func (*EnvReloader) CurrentDigest ¶ added in v0.1.6
func (r *EnvReloader) CurrentDigest() string
CurrentDigest returns the digest of the currently-active snapshot (for doctor).
func (*EnvReloader) DegradedCode ¶ added in v0.1.6
func (r *EnvReloader) DegradedCode() string
DegradedCode returns the last reload error code ("" when healthy).
func (*EnvReloader) RunSnapshot ¶ added in v0.1.6
func (r *EnvReloader) RunSnapshot() (*EnvSnapshot, ReloadStatus)
type EnvSnapshot ¶ added in v0.1.6
type EnvSnapshot struct {
// contains filtered or unexported fields
}
EnvSnapshot is the immutable, in-memory decrypted dotenv view used at a command or sync-run boundary. It is frozen for the lifetime of one run: the daemon may reload a NEW snapshot between runs but a single run always sees the same values. Plaintext lives only here and is never persisted, logged or passed whole to child processes.
func NewEnvSnapshot ¶ added in v0.1.6
func NewEnvSnapshot(values map[string]string, digest, source string) *EnvSnapshot
NewEnvSnapshot wraps an unlocked value map into an immutable snapshot. The caller must not mutate values after hand-off.
func ResolveEnvSnapshot ¶ added in v0.1.6
func ResolveEnvSnapshot(root string, provider UnlockProvider) (*EnvSnapshot, error)
ResolveEnvSnapshot unlocks and parses the encrypted dotenv asset into an immutable snapshot. It returns (nil, ErrEnvAssetMissing) when no asset exists so callers can distinguish "not configured" from "failed to unlock".
func (*EnvSnapshot) AllKeys ¶ added in v0.1.6
func (s *EnvSnapshot) AllKeys() []string
AllKeys returns the sorted list of declared keys (no values) for redacted reporting in list/doctor output.
func (*EnvSnapshot) ApplyAllowlist ¶ added in v0.1.6
func (s *EnvSnapshot) ApplyAllowlist(allowlist []string) map[string]string
ApplyAllowlist returns a NEW environment map suitable for a child process: only the keys in the allowlist are copied from the snapshot. The full decrypted environment is never handed to a subprocess.
func (*EnvSnapshot) Digest ¶ added in v0.1.6
func (s *EnvSnapshot) Digest() string
Digest returns the plaintext document digest used for reload identity checks.
func (*EnvSnapshot) IsEmpty ¶ added in v0.1.6
func (s *EnvSnapshot) IsEmpty() bool
IsEmpty reports whether the snapshot carries no values.
func (*EnvSnapshot) Lookup ¶ added in v0.1.6
func (s *EnvSnapshot) Lookup(key string) (string, bool)
Lookup returns the value for a key and whether it was present. It is the typed-secret read path — callers must NOT dump the whole snapshot into a child process environment.
func (*EnvSnapshot) OverlayEnv ¶ added in v0.1.6
func (s *EnvSnapshot) OverlayEnv(base []string, allowlist []string) []string
OverlayEnv returns a child-process environment built from a base environment plus the allowlisted snapshot values, respecting the precedence contract:
explicit process environment > decrypted env snapshot
Explicit flags are applied by callers BEFORE this function (they choose the base). The snapshot only fills keys that are not already set in base and are on the allowlist.
func (*EnvSnapshot) Source ¶ added in v0.1.6
func (s *EnvSnapshot) Source() string
Source returns a redacted source description (e.g. "pinax-sync.env.age").
type EnvUnlockProvider ¶ added in v0.1.6
type EnvUnlockProvider struct{}
EnvUnlockProvider resolves secret values from environment variables referenced by identity (e.g. identity "personal-sync-key" resolves from PINAX_SYNC_SECRET_PERSONAL_SYNC_KEY). It keeps the no-plaintext-in-repo invariant while supporting ephemeral CI bootstrap without a keychain.
func (EnvUnlockProvider) Lock ¶ added in v0.1.6
func (p EnvUnlockProvider) Lock(name, plaintext, identity string, kind SecretEntryKind) (SecretEntry, error)
func (EnvUnlockProvider) Name ¶ added in v0.1.6
func (EnvUnlockProvider) Name() string
func (EnvUnlockProvider) Unlock ¶ added in v0.1.6
func (p EnvUnlockProvider) Unlock(env SecretEnvelope) (map[string]string, error)
type ExtendedBlobStore ¶
type ExtendedBlobStore interface {
BlobStore
List(ctx context.Context, prefix string) ([]ObjectInfo, error)
Exists(ctx context.Context, key string) (bool, error)
BatchStat(ctx context.Context, keys []string) (map[string]string, error)
}
ExtendedBlobStore extends BlobStore with list and batch operations.
type FakeBlobCheckRequest ¶
type FakeBlobCheckRequest struct {
BlobIDs []string `json:"blob_ids"`
}
type FakeBlobCheckResponse ¶
type FakeBlobCheckResponse struct {
MissingBlobIDs []string `json:"missing_blob_ids"`
}
type FakeContractError ¶
type FakeContractErrorResponse ¶
type FakeContractErrorResponse struct {
Error FakeContractError `json:"error"`
}
type FakeErrorResponse ¶
type FakeManifestPutRequest ¶
type FakeManifestResponse ¶
type FakeRevisionResponse ¶
type FakeServer ¶
type FakeServer struct {
URL string
// contains filtered or unexported fields
}
func NewFakeServer ¶
func NewFakeServer() *FakeServer
func (*FakeServer) Close ¶
func (s *FakeServer) Close()
type FakeUnlockProvider ¶ added in v0.1.6
FakeUnlockProvider is a deterministic AES-GCM provider keyed by a passphrase resolved from PINAX_SYNC_FAKE_KEY (or a fixed test key). It exists so the declaration layer is testable end-to-end without an age/keychain dependency. Production deployments must register a reviewed provider; doctor flags fake.
func (FakeUnlockProvider) Lock ¶ added in v0.1.6
func (p FakeUnlockProvider) Lock(name, plaintext, identity string, kind SecretEntryKind) (SecretEntry, error)
func (FakeUnlockProvider) Name ¶ added in v0.1.6
func (p FakeUnlockProvider) Name() string
func (FakeUnlockProvider) Unlock ¶ added in v0.1.6
func (p FakeUnlockProvider) Unlock(env SecretEnvelope) (map[string]string, error)
type FileBackend ¶
type FileBackend struct {
// contains filtered or unexported fields
}
func NewFileBackend ¶
func NewFileBackend(baseDir string) (*FileBackend, error)
func (*FileBackend) List ¶
func (b *FileBackend) List(ctx context.Context, prefix string) ([]ObjectInfo, error)
List returns objects under the given prefix.
func (*FileBackend) SupportsConditionalWrites ¶
func (b *FileBackend) SupportsConditionalWrites() bool
type LoginRequest ¶
type Manifest ¶
type Manifest struct {
SchemaVersion string `json:"schema_version"`
GeneratedAt string `json:"generated_at"`
EntryCount int `json:"entry_count"`
Entries []ManifestEntry `json:"entries"`
Deletes []ManifestDelete `json:"deletes,omitempty"`
}
func BuildManifest ¶
func BuildManifestV2 ¶ added in v0.1.6
func BuildManifestV2(root, deviceID string, identities map[string]ManifestIdentity) (Manifest, error)
func DecryptManifest ¶
func DecryptManifest(key CryptoKey, envelope EncryptedEnvelope) (Manifest, error)
func (Manifest) ValidateV2 ¶ added in v0.1.6
type ManifestDelete ¶ added in v0.1.3
type ManifestDelete struct {
PathHash string `json:"path_hash"`
ObjectKind string `json:"object_kind"`
ObjectID string `json:"object_id,omitempty"`
TombstoneID string `json:"tombstone_id"`
DeletedAt string `json:"deleted_at,omitempty"`
TrashBlobID string `json:"trash_blob_id,omitempty"`
RevisionID string `json:"revision_id,omitempty"`
DeviceID string `json:"device_id,omitempty"`
}
type ManifestEntry ¶
type ManifestEntry struct {
ObjectID string `json:"object_id,omitempty"`
RevisionID string `json:"revision_id,omitempty"`
DeviceID string `json:"device_id,omitempty"`
Path string `json:"path"`
PathHash string `json:"path_hash"`
BlobID string `json:"blob_id"`
Size int64 `json:"size"`
SHA256 string `json:"sha256"`
ObjectKind string `json:"object_kind,omitempty"`
Mode uint32 `json:"mode,omitempty"`
MediaType string `json:"media_type,omitempty"`
UpdatedAt string `json:"updated_at"`
}
type ManifestFileTooLargeError ¶ added in v0.1.3
func (*ManifestFileTooLargeError) Error ¶ added in v0.1.3
func (e *ManifestFileTooLargeError) Error() string
type ManifestIdentity ¶ added in v0.1.6
type ManifestUnsafePathError ¶ added in v0.1.3
type ManifestUnsafePathError struct{ Path string }
func (*ManifestUnsafePathError) Error ¶ added in v0.1.3
func (e *ManifestUnsafePathError) Error() string
type ObjectInfo ¶
ObjectInfo describes a remote object.
type RcloneBackend ¶
type RcloneBackend struct {
// contains filtered or unexported fields
}
func NewRcloneBackend ¶
func NewRcloneBackend(endpoint string) (*RcloneBackend, error)
func (*RcloneBackend) Delete ¶
func (b *RcloneBackend) Delete(ctx context.Context, key string) error
func (*RcloneBackend) List ¶
func (b *RcloneBackend) List(ctx context.Context, prefix string) ([]ObjectInfo, error)
func (*RcloneBackend) SupportsConditionalWrites ¶
func (b *RcloneBackend) SupportsConditionalWrites() bool
type ReloadStatus ¶ added in v0.1.6
RunSnapshot returns the snapshot a sync run should use, reloading first if the encrypted asset changed since the last check. The returned snapshot is frozen for the run even if a subsequent reload succeeds. When no asset exists it returns (nil, nil). When reload fails it returns the last successful snapshot (which may be nil) and a structured ReloadStatus describing the failure.
type S3Backend ¶
type S3Backend struct {
// contains filtered or unexported fields
}
func NewS3Backend ¶
func NewS3BackendWithOptions ¶
func (*S3Backend) SupportsConditionalWrites ¶
type S3BackendOptions ¶
type S3Config ¶
type S3Config struct {
Bucket string `json:"bucket" yaml:"bucket"`
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
Region string `json:"region,omitempty" yaml:"region,omitempty"`
Profile string `json:"profile,omitempty" yaml:"profile,omitempty"`
AddressingStyle string `json:"addressing_style,omitempty" yaml:"addressing_style,omitempty"`
PathStyle bool `json:"path_style,omitempty" yaml:"path_style,omitempty"`
}
type SecretEntry ¶ added in v0.1.6
type SecretEntry struct {
Identity string `json:"identity" yaml:"identity"`
Ciphertext string `json:"ciphertext" yaml:"ciphertext"`
}
SecretEntry is one logical identity's encrypted material. Identity is the stable logical key id; ciphertext is provider-specific and opaque.
type SecretEntryKind ¶ added in v0.1.6
type SecretEntryKind string
SecretEntryKind classifies a stored secret so the unlock path and doctor can distinguish credential vs encryption-key material. Stored as opaque metadata in the envelope; unknown kinds degrade rather than fail-open.
const ( SecretKindCredential SecretEntryKind = "credential" SecretKindEncryptionKey SecretEntryKind = "encryption_key" )
type SecretEnvelope ¶ added in v0.1.6
type SecretEnvelope struct {
SchemaVersion string `json:"schema_version" yaml:"schema_version"`
Provider string `json:"provider" yaml:"provider"`
Secrets map[string]SecretEntry `json:"secrets,omitempty" yaml:"secrets,omitempty"`
}
SecretEnvelope is the repository-tracked encrypted secrets asset (pinax-sync.secrets.yaml). Plaintext values are available only during an authenticated runtime unlock; the ciphertext is safe to commit.
func LoadSecretEnvelope ¶ added in v0.1.6
func LoadSecretEnvelope(root string) (SecretEnvelope, error)
LoadSecretEnvelope reads the secrets asset. A missing file is reported via ErrSecretsAssetMissing so init/doctor can distinguish absent from corrupt.
type SecretEnvelopeError ¶ added in v0.1.6
SecretEnvelopeError is the stable error type for envelope operations.
func (*SecretEnvelopeError) Error ¶ added in v0.1.6
func (e *SecretEnvelopeError) Error() string
type SecretMetadata ¶ added in v0.1.6
type SecretMetadata struct {
Name string `json:"name" yaml:"name"`
Kind string `json:"kind" yaml:"kind"`
Identity string `json:"identity" yaml:"identity"`
Provider string `json:"provider" yaml:"provider"`
}
SecretMetadata is the redacted, safe-to-show view of an entry.
type SourceMarker ¶ added in v0.1.6
type SourceMarker struct {
SchemaVersion string `json:"schema_version" yaml:"schema_version"`
DeclarationDigest string `json:"declaration_digest,omitempty" yaml:"declaration_digest,omitempty"`
GeneratedAt string `json:"generated_at" yaml:"generated_at"`
DeviceID string `json:"device_id" yaml:"device_id"`
}
SourceMarker is the device-local record that a runtime config was generated from a declaration. It stores the declaration digest so doctor can detect drift without re-reading the (possibly absent) original declaration.
func LoadSourceMarker ¶ added in v0.1.6
func LoadSourceMarker(root string) (SourceMarker, error)
LoadSourceMarker reads the device-local source marker. Missing marker is not an error — it means the runtime config predates the declaration layer.
type State ¶
type State struct {
Config Config `json:"config"`
Session DeviceSession `json:"session"`
}
type StoreFactory ¶
StoreFactory is a function signature for building a BlobStore.
type SyncBackend ¶ added in v0.1.6
type SyncBackend struct {
Kind string `json:"kind" yaml:"kind"` // s3-direct, rclone-direct, server, embedded
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
S3 *S3Config `json:"s3,omitempty" yaml:"s3,omitempty"`
}
SyncBackend describes the portable transport topology. Endpoint and S3 are topology only; provider credential values resolve at device-local unlock.
type SyncConfig ¶ added in v0.1.6
type SyncConfig struct {
SchemaVersion string `json:"schema_version" yaml:"schema_version"`
Backend SyncBackend `json:"backend" yaml:"backend"`
Workspace SyncWorkspace `json:"workspace" yaml:"workspace"`
Secrets SyncSecretRefs `json:"secrets" yaml:"secrets"`
Policy SyncPolicy `json:"policy,omitempty" yaml:"policy,omitempty"`
}
SyncConfig is the versioned, portable repository sync declaration. It holds backend topology, logical credential/encryption identities and sync policy, but never plaintext credentials, tokens, absolute device paths or device runtime state.
func LoadSyncConfig ¶ added in v0.1.6
func LoadSyncConfig(root string) (SyncConfig, error)
LoadSyncConfig reads and validates the repository declaration. A missing file is reported via ErrSyncDeclarationMissing so callers can distinguish "not initialized" from "corrupt".
func (SyncConfig) EffectiveNamespace ¶ added in v0.1.6
func (c SyncConfig) EffectiveNamespace() string
EffectiveNamespace derives the deterministic remote namespace prefix from the stable workspace fields. It does NOT replace server-side authorization; it only makes the on-wire object layout collision-resistant.
func (SyncConfig) Normalized ¶ added in v0.1.6
func (c SyncConfig) Normalized() SyncConfig
Normalized returns a copy with trimmed/derived fields, suitable for stable comparison (drift detection) and compilation.
func (SyncConfig) Validate ¶ added in v0.1.6
func (c SyncConfig) Validate() error
Validate enforces the declaration contract: schema version, supported backend, required workspace/encryption identity and absence of plaintext-sensitive or absolute-path fields. It returns stable English error codes for every failure.
type SyncConfigError ¶ added in v0.1.6
SyncConfigError is the stable validation error returned by Validate. Code is a stable English identifier; Field is the redacted field path (never the offending value).
func (*SyncConfigError) Error ¶ added in v0.1.6
func (e *SyncConfigError) Error() string
type SyncPolicy ¶ added in v0.1.6
type SyncPolicy struct {
// RemoteDeletePolicy controls whether local deletions may propagate to the
// remote. "deny" (default) refuses; "require-approval" needs explicit --yes.
RemoteDeletePolicy string `json:"remote_delete_policy,omitempty" yaml:"remote_delete_policy,omitempty"`
// NewDeviceMode defaults to "pull-only": a device with no local sync receipt
// must not upload local deletions or replace remote state on first bootstrap.
NewDeviceMode string `json:"new_device_mode,omitempty" yaml:"new_device_mode,omitempty"`
}
SyncPolicy captures approval-gated, safety-critical knobs. The default is the most conservative posture so an accidental apply cannot widen the blast radius.
type SyncSecretRefs ¶ added in v0.1.6
type SyncSecretRefs struct {
CredentialID string `json:"credential_id,omitempty" yaml:"credential_id,omitempty"`
EncryptionKeyID string `json:"encryption_key_id" yaml:"encryption_key_id"`
}
SyncSecretRefs holds logical credential and encryption key identities. Each device resolves these to its local profile, keychain or secret manager; raw values never enter the declaration.
type SyncWorkspace ¶ added in v0.1.6
type SyncWorkspace struct {
TenantID string `json:"tenant_id,omitempty" yaml:"tenant_id,omitempty"`
AppID string `json:"app_id,omitempty" yaml:"app_id,omitempty"`
WorkspaceID string `json:"workspace_id" yaml:"workspace_id"`
}
SyncWorkspace carries stable namespace fields. The effective remote namespace is derived deterministically; full multi-tenant authorization is a separate server capability and is NOT provided by direct transport.
type UnlockProvider ¶ added in v0.1.6
type UnlockProvider interface {
// Name is the stable provider identifier written into the envelope.
Name() string
// Unlock decrypts the envelope into plaintext values. Plaintext MUST NOT
// escape the caller; it lives only in the unlocking runtime.
Unlock(env SecretEnvelope) (map[string]string, error)
// Lock encrypts a single plaintext value under the given identity, returning
// an entry suitable for SecretEnvelope.Secrets.
Lock(name string, plaintext string, identity string, kind SecretEntryKind) (SecretEntry, error)
}
UnlockProvider resolves a SecretEnvelope to plaintext name→value pairs using a device-local identity. Implementations must fail-closed on any missing or wrong identity and never log plaintext. The interface is provider-neutral so the first version can ship a deterministic fake + env provider and later swap in age/keychain without changing the CLI contract.
func ResolveUnlockProvider ¶ added in v0.1.6
func ResolveUnlockProvider(providerName string) (UnlockProvider, error)
ResolveUnlockProvider selects the provider for an envelope. Unknown providers fail-closed; the caller surfaces a runnable recovery command.