db

package
v0.0.0-...-31b1c06 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Index

Constants

View Source
const (

	// ProvisionalBlockReferenceTTLSeconds bounds how long an in-flight upload's
	// provisional reference survives. A committed fs_object gets a separate
	// permanent reference; the up: row still expires only by TTL. The duration must
	// exceed the longest realistic gap between uploading blocks and committing the
	// fs_object (large resumable/chunked uploads). Abandoned rows expire on their
	// own — no permanent leak.
	ProvisionalBlockReferenceTTLSeconds = 48 * 60 * 60 // 48h

	// PublishAttemptReferenceTTLSeconds bounds how long a pub:<attempt> crash
	// backstop can keep blocks alive after a winner commit becomes visible but
	// permanent fs_object refs have not been promoted yet. It must comfortably
	// exceed the stale-owner sweep window, repair lag, and prolonged restart
	// windows so reachable commits still have time to self-heal on recovery.
	PublishAttemptReferenceTTLSeconds = 35 * 24 * 60 * 60 // 35d

	// BlockGCStateDeleting marks a block row claimed by the GC worker for an
	// imminent S3 delete. Writers that observe it must back off and retry.
	BlockGCStateDeleting = "deleting"
	// BlockGCStateRepairingStub is a short-lived upload-owned claim used only to
	// remove a released metadata-free claim stub. Other readers fail closed on it.
	BlockGCStateRepairingStub = "repairing_stub"
)
View Source
const (
	GCLibraryPolicyVersionTTL = "version_ttl"
	GCLibraryPolicyAutoDelete = "auto_delete"
)
View Source
const BlockFenceReadConsistency = gocql.LocalQuorum

BlockFenceReadConsistency is the weakest read that still intersects an EACH_QUORUM fence publication in the reader's own DC.

View Source
const BlockReferenceWriteConsistency = gocql.LocalQuorum

BlockReferenceWriteConsistency is the consistency level EVERY write to block_references must reach, pinned per statement rather than inherited from the session.

It is the write half of the destructive-GC liveness argument (ISSUE-GC-CROSS-DC-REFERENCE-VISIBILITY-01). BlockHasReferencesGlobal reads at EACH_QUORUM so a FALSE answer may authorize destroying bytes, and that answer is only trustworthy because a reference acknowledged at LOCAL_QUORUM in some datacenter necessarily intersects the read's quorum in that same datacenter. Under ONE a single replica can acknowledge a reference that a later per-DC read quorum of 2-of-3 never sees — and `ONE` is an accepted `database.consistency`, so inheriting the session made that a deployment typo away.

PINNED HERE BECAUSE THE READER CANNOT ENFORCE IT. ValidateDestructiveGCTopology checks the consistency of the process running GC; references are written by API nodes, which are different processes with their own configuration, and no gate the worker runs can see them. The invariant is a property of the writers, so it belongs at the writers.

THE FLOOR IS ALSO THE CEILING, which is a real trade and not a detail. A deployment configured for EACH_QUORUM or ALL now writes references at LOCAL_QUORUM — this pin LOWERS a stronger configured level, and it is worth being explicit that it does. The reason is that a pin varying with configuration gives back the exact property the constant exists to establish: "references reached a quorum" would once again mean "whatever that process was configured with", which is unverifiable from the reader's side and is how ONE got in. What is given up is only cross-DC promptness, never safety: the destructive read is EACH_QUORUM regardless, so it still intersects; every other reader of block_references is a local check whose false zero costs a redundant re-upload or an enqueued candidate that the global verify then declines to delete. Every shipped profile already runs LOCAL_QUORUM, so no deployment changes behaviour.

TestBlockReferenceProducersPinWriteConsistency enumerates the statements this applies to; a new producer fails that test until it is pinned or exempted with a reason.

View Source
const BlockUploadSessionTTLSeconds = ProvisionalBlockReferenceTTLSeconds

BlockUploadSessionTTLSeconds bounds how long a server-issued web block-upload session survives. Aligned with the provisional block reference TTL so a session and the blocks it pins expire together: the last /blocks/upload under the session refreshes the provisional ref TTL, and the session row carries the same window. A successful commit re-writes the row (resetting the TTL) so the idempotency record outlives client retries.

View Source
const BlockUploadStagedBlockBuckets = 64

BlockUploadStagedBlockBuckets is the MAXIMUM number of ledger buckets a session's staged blocks are spread across. Bucketing keeps the per-block admission read O(1) rows (one small bucket) instead of an O(N^2) partition re-scan for large files: for a 12 GB file (~1536 8 MB blocks) each of 64 buckets holds ~24 rows. The EFFECTIVE bucket count is chosen per session as min(this, maxBlocks) so a tiny ceiling does not fan out into 64 near-empty buckets (which, times a per-bucket slack, would inflate the real bound). See docs/WEB-BLOCK-UPLOAD.md item 1 and the caller's stagedBlockBucketCap.

View Source
const GCDiscoveryBucketCount = 32
View Source
const PendingPublishedFSObjectOwnerTTLSeconds = 35 * 24 * 60 * 60 // 35d
View Source
const PlainBlockRepresentationID = "plain:v1"
View Source
const (
	// ProvisionalBlockRefTrackerTTLGraceSeconds is the bounded period for which
	// the canonical tracker normally outlives its provisional reference. It
	// matches the default 24-hour GC scanner interval, giving the usual scan a
	// full extra cycle to observe the canonical row. Correctness does not depend
	// on scanning inside this window: the durable by-day projection remains after
	// the canonical TTL and drives canonical-missing recovery on a later scan.
	ProvisionalBlockRefTrackerTTLGraceSeconds = 24 * 60 * 60
)

Variables

View Source
var ErrBlockIDMappingConflict = errors.New("block id mapping conflict")

ErrBlockIDMappingConflict indicates an external SHA-1 already maps to a DIFFERENT internal SHA-256 inside the same representation domain. Writers fail closed instead of overwriting such a row.

View Source
var ErrBlockMetadataPermanent = errors.New("block metadata write is permanently failed")

ErrBlockMetadataPermanent marks a metadata-write failure that is deterministically irrecoverable — an invalid argument (missing storage class, malformed sha1/representation id), a conflicting first-writer identity, or a corrupt row. Retrying only burns the caller's bounded budget, so the upload materialization wrapper must NOT retry it. Everything left unmarked (raw Cassandra driver I/O, lost CAS races, transient stub/fence states) is treated as transient and retried. Keeping the permanent set small is the safe bias: a transient mislabeled permanent fails a recoverable upload, while a permanent mislabeled transient only wastes a bounded budget before failing.

View Source
var ErrBlockRepairAuthorityChanged = errors.New("block repair authority changed")

ErrBlockRepairAuthorityChanged means the canonical row is absent or no longer names the physical tuple the caller verified. The caller may re-probe and retry against the newly observed incarnation, but must not repair the stale tuple.

View Source
var ErrBlockRepairAuthorityPermanent = errors.New("block repair authority is permanently invalid")

ErrBlockRepairAuthorityPermanent marks malformed input or canonical row state. Re-reading cannot make corrupt locator, ownership, or immutable metadata valid.

View Source
var ErrBlockRepairBlocked = errors.New("block repair blocked by GC")

ErrBlockRepairBlocked means GC currently owns or fences the logical block. Repair must wait until both the in-row claim and the A+ orphan fence are clear.

View Source
var ErrBlockUploadSessionSlotsExhausted = errors.New("block upload session slots exhausted")

ErrBlockUploadSessionSlotsExhausted is returned by CreateAdmittedBlockUploadSession when the caller already holds the maximum number of concurrent uncommitted block-upload sessions (all per-user slots claimed). The API maps it to 429.

View Source
var ErrFileLockStatusUnavailable = errors.New("file lock status unavailable")

ErrFileLockStatusUnavailable indicates the lock table could not be queried, so callers must fail closed instead of assuming the file is unlocked.

View Source
var ErrInstallBlockMetadataIdentityContradiction = errors.New("single-use block install identity was already present")

ErrInstallBlockMetadataIdentityContradiction means a definite non-applied create-only CAS returned the exact proposed tuple. That tuple may have come from an earlier use of the minted identity, so it grants neither success nor cleanup authority and must never be retried.

View Source
var ErrLibraryDeleted = errors.New("library deleted")

ErrLibraryDeleted indicates the canonical libraries row still exists but the library has been soft-deleted and must be treated as unavailable for live reads and writes.

Functions

func AddDeleteAdminGroupReadModelQuery

func AddDeleteAdminGroupReadModelQuery(batch *gocql.Batch, row AdminGroupProjectionRow)

func AddDeleteAdminLibraryReadModelQueries

func AddDeleteAdminLibraryReadModelQueries(session *gocql.Session, batch *gocql.Batch, orgID, libraryID string) error

func AddDeleteAdminLibraryReadModelQuery

func AddDeleteAdminLibraryReadModelQuery(batch *gocql.Batch, row AdminLibraryProjectionRow)

func AddDeleteAdminLinkReadModelQuery

func AddDeleteAdminLinkReadModelQuery(batch *gocql.Batch, linkType string, createdAt time.Time, orgID, token string)

func AddDeleteAdminOrganizationReadModelQuery

func AddDeleteAdminOrganizationReadModelQuery(batch *gocql.Batch, state AdminOrganizationProjectionState)

func AddDeleteAdminOrganizationStatusProjectionEntryQuery

func AddDeleteAdminOrganizationStatusProjectionEntryQuery(batch *gocql.Batch, state AdminOrganizationProjectionState)

func AddDeleteAdminUserReadModelQuery

func AddDeleteAdminUserReadModelQuery(batch *gocql.Batch, state AdminUserProjectionState)

func AddDeleteAdminUserStatusProjectionEntryQuery

func AddDeleteAdminUserStatusProjectionEntryQuery(batch *gocql.Batch, state AdminUserProjectionState)

func AddDeleteBlockGCCandidateDiscoveryQuery

func AddDeleteBlockGCCandidateDiscoveryQuery(batch *gocql.Batch, orgID, blockID string, candidateAt time.Time)

func AddDeleteDeletedAdminLibraryReadModelFallbackQueries

func AddDeleteDeletedAdminLibraryReadModelFallbackQueries(session *gocql.Session, batch *gocql.Batch, row AdminDeletedLibraryProjectionRow) error

func AddDeleteDeletedAdminLibraryReadModelQuery

func AddDeleteDeletedAdminLibraryReadModelQuery(batch *gocql.Batch, row AdminDeletedLibraryProjectionRow)

func AddDeleteDeletedUserDiscoveryQuery

func AddDeleteDeletedUserDiscoveryQuery(batch *gocql.Batch, orgID, userID string, deletedAt time.Time)

func AddDeleteFailedItemExpiryQuery

func AddDeleteFailedItemExpiryQuery(batch *gocql.Batch, orgID string, failedAt time.Time, itemType, itemID string, expiresAt time.Time)

func AddDeleteLibraryPolicyQuery

func AddDeleteLibraryPolicyQuery(batch *gocql.Batch, policyType, orgID, libraryID string)

func AddDeletePendingPublishedFSObjectOwnerQueries

func AddDeletePendingPublishedFSObjectOwnerQueries(batch *gocql.Batch, repoID, fsID, ownerID string, createdAt time.Time)

func AddDeleteProvisionalBlockRefExpiryDiscoveryQuery

func AddDeleteProvisionalBlockRefExpiryDiscoveryQuery(batch *gocql.Batch, orgID, blockID, referrer string, expiresAt time.Time)

func AddDeleteShareExpiryQuery

func AddDeleteShareExpiryQuery(batch *gocql.Batch, shareID string, expiresAt time.Time, orgID, libraryID string)

func AddDeleteShareLinkExpiryQuery

func AddDeleteShareLinkExpiryQuery(batch *gocql.Batch, token string, expiresAt time.Time)

func AddDeleteShareReadModelQuery

func AddDeleteShareReadModelQuery(batch *gocql.Batch, row ShareReadModelRow)

func AddPublishAttemptReferences

func AddPublishAttemptReferences(database *DB, orgID, repoID, attemptID string, blockIDs []string) error

AddPublishAttemptReferences stages temporary pub:<attempt> references for an in-flight metadata publish. Input IDs are normalized with TrimSpace + dedup so retrying callers can safely pass repeated or padded block IDs.

func AddRefreshAdminLibraryReadModelQueries

func AddRefreshAdminLibraryReadModelQueries(batch *gocql.Batch, row AdminLibraryProjectionRow, previous *AdminLibraryProjectionRow)

func AddUpdateAdminLinkActiveQuery

func AddUpdateAdminLinkActiveQuery(batch *gocql.Batch, linkType string, createdAt time.Time, orgID, token string, active bool)

func AddUpdateAdminLinkCountersQuery

func AddUpdateAdminLinkCountersQuery(batch *gocql.Batch, linkType string, createdAt time.Time, orgID, token string, viewCount, uploadCount int)

func AddUpsertAdminGroupReadModelQuery

func AddUpsertAdminGroupReadModelQuery(batch *gocql.Batch, row AdminGroupProjectionRow)

func AddUpsertAdminLibraryReadModelQuery

func AddUpsertAdminLibraryReadModelQuery(batch *gocql.Batch, row AdminLibraryProjectionRow)

func AddUpsertAdminLinkReadModelQuery

func AddUpsertAdminLinkReadModelQuery(
	batch *gocql.Batch,
	token, linkType, orgID, libraryID, filePath, createdBy, permission string,
	repoName, objName, creatorEmail, creatorName string,
	expiresAt *time.Time,
	hasPassword, active bool,
	viewCount, uploadCount int,
	ttlSeconds int,
	createdAt time.Time,
)

func AddUpsertAdminOrganizationReadModelQuery

func AddUpsertAdminOrganizationReadModelQuery(batch *gocql.Batch, row AdminOrganizationProjectionRow)

func AddUpsertAdminUserReadModelQuery

func AddUpsertAdminUserReadModelQuery(batch *gocql.Batch, row AdminUserProjectionRow)

func AddUpsertBlockGCCandidateDiscoveryQuery

func AddUpsertBlockGCCandidateDiscoveryQuery(batch *gocql.Batch, orgID, blockID, storageClass string, candidateAt time.Time)

func AddUpsertDeletedUserDiscoveryQuery

func AddUpsertDeletedUserDiscoveryQuery(batch *gocql.Batch, orgID, userID string, deletedAt time.Time)

func AddUpsertFailedItemExpiryQuery

func AddUpsertFailedItemExpiryQuery(batch *gocql.Batch, orgID string, failedAt time.Time, itemType, itemID string, expiresAt time.Time)

func AddUpsertLibraryPolicyQuery

func AddUpsertLibraryPolicyQuery(batch *gocql.Batch, policyType, orgID, libraryID string, days int, cachedHeadCommitID string, policyUpdatedAt time.Time)

func AddUpsertPendingPublishedFSObjectOwnerQueries

func AddUpsertPendingPublishedFSObjectOwnerQueries(batch *gocql.Batch, repoID, fsID, ownerID string, createdAt time.Time, orgID, attemptID string)

func AddUpsertProvisionalBlockRefExpiryDiscoveryQuery

func AddUpsertProvisionalBlockRefExpiryDiscoveryQuery(batch *gocql.Batch, orgID, blockID, referrer, storageClass string, expiresAt time.Time)

func AddUpsertShareExpiryQuery

func AddUpsertShareExpiryQuery(batch *gocql.Batch, orgID, libraryID, shareID, sharedTo, sharedToType, sharedBy string, createdAt, expiresAt time.Time)

func AddUpsertShareLinkExpiryQuery

func AddUpsertShareLinkExpiryQuery(batch *gocql.Batch, token, orgID, libraryID, createdBy, linkType string, createdAt, expiresAt time.Time)

func AddUpsertShareReadModelQuery

func AddUpsertShareReadModelQuery(batch *gocql.Batch, row ShareReadModelRow)

func AdjustAdminOrgLinkCount

func AdjustAdminOrgLinkCount(session *gocql.Session, orgID, linkType string, delta int) error

func AdminGroupBucketDay

func AdminGroupBucketDay(createdAt time.Time) string

func AdminLibraryBucketDay

func AdminLibraryBucketDay(createdAt time.Time) string

func AdminLinkBucketDay

func AdminLinkBucketDay(createdAt time.Time) string

func AdminLinkObjName

func AdminLinkObjName(filePath, repoName string) string

func AdminOrgLinkCountDelta

func AdminOrgLinkCountDelta(delta int) int

func AdminOrganizationBucketDay

func AdminOrganizationBucketDay(createdAt time.Time) string

func AdminUserBucketDay

func AdminUserBucketDay(createdAt time.Time) string

func BestEffortAdjustAdminOrgLinkCount

func BestEffortAdjustAdminOrgLinkCount(session *gocql.Session, orgID, linkType string, delta int)

func BlockReferrerForFSObject

func BlockReferrerForFSObject(libraryID, fsID string) string

BlockReferrerForFSObject builds the permanent referrer for a block referenced by an fs_object: "fs:<library_id>:<fs_id>". Because fs_id is content-addressed, the same file content always yields the same referrer, so registering the reference is naturally idempotent under client retries (no counter inflation).

func BlockReferrerForPublishAttempt

func BlockReferrerForPublishAttempt(attemptID string) string

BlockReferrerForPublishAttempt builds a temporary referrer for an in-flight metadata publish attempt: "pub:<attempt_id>". Writers use it while preparing a new commit so a failed head-CAS cleanup can remove only the attempt-local referrer instead of touching shared fs:<library>:<fs_id> rows.

func BlockReferrerForUpload

func BlockReferrerForUpload(operationID string) string

BlockReferrerForUpload builds the provisional referrer for an in-flight upload: "up:<operation_id>". It is written with a TTL and remains until that TTL even after the upload creates a separate permanent fs_object reference.

func CanonicalBlockRepresentationIDForLibrary

func CanonicalBlockRepresentationIDForLibrary(libraryID string, encrypted bool, stored string) (string, error)

CanonicalBlockRepresentationIDForLibrary derives the ONE block representation a library may legally use from its own identity and encrypted flag. A blank stored value is defaulted to that expected representation; a non-blank stored value must match exactly or it is rejected as drift/corruption.

func ClearLocksUnder

func ClearLocksUnder(session *gocql.Session, repoID, root, userID string) error

ClearLocksUnder removes every lock at root or beneath it. Intended to run AFTER a successful delete of root: the subtree pre-check guarantees any remaining locks are the operator's own, so this only drops the operator's own (now-orphaned) locks. Best-effort: a maintenance failure is returned but must not undo the delete itself.

func CountAdminOrgLinks(session *gocql.Session, orgID, linkType string) (int, error)

func CountAdminOrganizationUsers

func CountAdminOrganizationUsers(session *gocql.Session, orgID string) (int, error)

func CreateOrganizationWithUsersAndReadModels

func CreateOrganizationWithUsersAndReadModels(session *gocql.Session, org AdminOrganizationWriteSpec, users []AdminUserWriteSpec) error

func CreateUserWithLookupsAndReadModels

func CreateUserWithLookupsAndReadModels(session *gocql.Session, user AdminUserWriteSpec) error

func DeleteAdminGroupReadModel

func DeleteAdminGroupReadModel(session *gocql.Session, row AdminGroupProjectionRow) error

func DeleteAdminOrganizationReadModel

func DeleteAdminOrganizationReadModel(session *gocql.Session, orgID string) error

func DeleteAdminUserReadModel

func DeleteAdminUserReadModel(session *gocql.Session, userID string) error

func EncryptedLibraryBlockRepresentationID

func EncryptedLibraryBlockRepresentationID(libraryID string) string

func FileLockedByOther

func FileLockedByOther(session *gocql.Session, repoID, path, userID string) (blocked bool, ownerID string, err error)

FileLockedByOther reports whether (repoID, path) is locked by a user other than userID. ownerID is the lock holder's UUID (empty when the path is unlocked). The lock holder themselves and any unlocked path are never reported as blocked, so a user can always overwrite or refresh their own lock. A malformed repoID is reported as "not blocked" so the caller's own id validation produces the right error.

func GCDiscoveryBucket

func GCDiscoveryBucket(parts ...string) int

func GCProjectionDateString

func GCProjectionDateString(ts time.Time) string

func GCProjectionUTCDate

func GCProjectionUTCDate(ts time.Time) time.Time

func InvalidateAdminOrgLinkCount

func InvalidateAdminOrgLinkCount(session *gocql.Session, orgID, linkType string) error

func IsAdminLinkType

func IsAdminLinkType(linkType string) bool

func IsCanonicalBlockRepresentationForLibrary

func IsCanonicalBlockRepresentationForLibrary(representationID string, libraryID uuid.UUID) bool

func IsCanonicalBlockRepresentationID

func IsCanonicalBlockRepresentationID(representationID string) bool

IsCanonicalBlockRepresentationID reports whether representationID is one of the exact forms this system mints: the plaintext default "plain:v1", or a per-library encrypted id "library:<uuid>". A non-empty value that matches neither indicates a corrupt/foreign id that would resolve mappings in a nonexistent namespace, so callers that require a *usable* representation validate format, not just presence.

func IsSHA1BlockID

func IsSHA1BlockID(id string) bool

IsSHA1BlockID reports whether id is a 40-char hex external SHA-1 block id, and IsSHA256BlockID whether it is a 64-char hex internal content address. Both validate hex CONTENT, not just length, so a 40/64-char non-hex string is rejected. Callers should normalize with NormalizeBlockID first.

func IsSHA256BlockID

func IsSHA256BlockID(id string) bool

func ListAdminGroupBucketDays

func ListAdminGroupBucketDays(session *gocql.Session) ([]string, error)

func ListAdminLibraryBucketDays

func ListAdminLibraryBucketDays(session *gocql.Session) ([]string, error)

func NewLibraryBlockRepresentationID

func NewLibraryBlockRepresentationID(libraryID string, encrypted bool) string

NewLibraryBlockRepresentationID returns the representation stamped when a library is first created. Persisted rows must instead be resolved through CanonicalBlockRepresentationIDForLibrary so drift cannot bypass validation.

func NormalizeBlockID

func NormalizeBlockID(id string) string

NormalizeBlockID canonicalizes a hex block identifier (external SHA-1 or internal SHA-256) to trimmed lowercase. Hex is case-insensitive, so without this the same content-address could land in two different partition keys or miss a lookup purely on letter case. Server-derived IDs are already lowercase; applying this at every mapping read/write/delete and on blocks.sha1 keeps the canonicalization consistent (and defensive if a non-server-derived uppercase id ever reaches these paths).

func NormalizeBlockIDs

func NormalizeBlockIDs(blockIDs []string) []string

NormalizeBlockIDs trims, drops empties, and de-duplicates block IDs while preserving first-seen order. Returns nil when nothing usable remains. Callers that stage/remove references share this so the same key set is produced on both sides of an add/remove pair.

func ParseGCProjectionDate

func ParseGCProjectionDate(value string) (time.Time, error)

func PromotePublishAttemptReferences

func PromotePublishAttemptReferences(database *DB, orgID, attemptID string, blockIDs []string, registerPermanent func() error) error

PromotePublishAttemptReferences promotes an already-published fs_object to its permanent refs and then removes the temporary attempt-local pub:<attempt> rows. Both steps are idempotent, so bounded retries safely heal transient failures after HEAD is already visible without leaking attempt-local refs forever.

func RefreshAdminOrgLinkCount

func RefreshAdminOrgLinkCount(session *gocql.Session, orgID, linkType string) (int, error)

func ReleaseFileLock

func ReleaseFileLock(session *gocql.Session, repoID, path, userID string) (released bool, ownerID string, err error)

ReleaseFileLock atomically deletes the lock on (repoID, path) only if userID holds it (DELETE ... IF locked_by = ?). It returns released=true when the lock was removed or was already absent (idempotent), and released=false with the current owner when another user holds it. This replaces the previous check-then-DELETE race.

func RelocateLocksUnder

func RelocateLocksUnder(session *gocql.Session, repoID, oldRoot, newRoot, userID string) error

RelocateLocksUnder rewrites every lock path at oldRoot or beneath it so its prefix becomes newRoot, preserving the holder and timestamp. Intended to run AFTER a successful rename/move oldRoot→newRoot so the operator's own locks follow the file instead of being left dangling at the old path. Paths are expected pre-normalized.

func RemovePublishAttemptReferences

func RemovePublishAttemptReferences(database *DB, orgID, attemptID string, blockIDs []string) error

RemovePublishAttemptReferences removes temporary pub:<attempt> references. It is safe to call repeatedly and collapses repeated delete errors with errors.Join.

func ReplaceAdminOrgLinkCount

func ReplaceAdminOrgLinkCount(session *gocql.Session, orgID, linkType string, exactCount int) error

func ResolveAdminGroupOwnerFields

func ResolveAdminGroupOwnerFields(session *gocql.Session, orgID, creatorID string) (string, string)

func ResolveAdminLibraryOwnerFields

func ResolveAdminLibraryOwnerFields(session *gocql.Session, orgID, ownerID string) (string, string)

func ResolveAdminLinkDisplayFields

func ResolveAdminLinkDisplayFields(session *gocql.Session, orgID, libraryID, filePath, createdBy string) (string, string, string, string)

func ResolveAdminOrganizationOwnerFields

func ResolveAdminOrganizationOwnerFields(session *gocql.Session, orgID string) (string, string)

func ResolveBlockRepresentationID

func ResolveBlockRepresentationID(session *gocql.Session, orgID, libraryID string) (string, error)

func ResolveBlockRepresentationIDByLibraryID

func ResolveBlockRepresentationIDByLibraryID(session *gocql.Session, libraryID string) (string, error)

func ResolveBlockRepresentationIDContext

func ResolveBlockRepresentationIDContext(ctx context.Context, session *gocql.Session, orgID, libraryID string) (string, error)

ResolveBlockRepresentationIDContext is ResolveBlockRepresentationID bound to ctx for request-scoped mapping preparation.

func ResolveBlockRepresentationIDForDelete

func ResolveBlockRepresentationIDForDelete(session *gocql.Session, orgID, libraryID string) (string, error)

ResolveBlockRepresentationIDForDelete resolves the effective block representation for a library that is being soft- or permanently deleted. Unlike ResolveBlockRepresentationID it reads the row even when deleted_at is already set (a permanent delete acts on a library that is already in trash), so callers can stamp block_representation_id onto the deleted_libraries GC marker before the libraries row disappears. GC relies on that stamp to purge the library later; without it the cascade cannot resolve the SHA-1 mapping domain once the live row is gone.

The result is guaranteed non-empty AND canonical for this library (plain:v1, or library:<this-library-id>); a stored value that is malformed or belongs to a different library is a hard error, so a hard-delete caller can fail closed instead of stamping a marker GC would later reject as non-canonical and strand in trash.

func ScheduleAdminOrgLinkCountRefresh

func ScheduleAdminOrgLinkCountRefresh(session *gocql.Session, orgID, linkType string)

func StagePublishAttemptReferences

func StagePublishAttemptReferences(database *DB, orgID, repoID, attemptID string, blockIDs []string, resolve BlockIDResolver) ([]string, error)

StagePublishAttemptReferences resolves block IDs when needed, then records the attempt-local pub:<attempt> rows that keep blocks alive until HEAD publish wins. If a partial stage fails, this helper cleans up the rows written by this call before returning so direct callers do not leak stuck publish-attempt refs.

func StagedBlockBucket

func StagedBlockBucket(blockID string, bucketCount int) int

StagedBlockBucket maps a block id to its ledger bucket for a session using `bucketCount` buckets. Deterministic for a fixed bucketCount, so the same block always lands in the same (session_id, bucket) partition and the reserve is idempotent under retries. bucketCount must be >= 1.

func SubtreeLockedByOther

func SubtreeLockedByOther(session *gocql.Session, repoID, targetPath, userID string) (blocked bool, ownerID string, err error)

SubtreeLockedByOther reports whether targetPath itself OR any descendant beneath it is locked by a user other than userID. Directory-capable operations (rename, move, delete) use this so a folder action cannot bypass a lock on a file inside it. When targetPath is a plain file this degenerates to the same answer as FileLockedByOther.

It scans the (small) per-repo lock partition once. A query failure is surfaced as ErrFileLockStatusUnavailable so callers fail closed rather than silently allowing a write past an unverifiable lock.

func SyncAdminGroupReadModel

func SyncAdminGroupReadModel(session *gocql.Session, orgID, groupID string) error

func SyncAdminLibraryReadModel

func SyncAdminLibraryReadModel(session *gocql.Session, orgID, libraryID string) error

func SyncAdminLinkReadModel

func SyncAdminLinkReadModel(session *gocql.Session, token string) error

func SyncAdminOrganizationReadModel

func SyncAdminOrganizationReadModel(session *gocql.Session, orgID string) error

func SyncAdminUserReadModel

func SyncAdminUserReadModel(session *gocql.Session, orgID, userID string) error

func SyncShareReadModel

func SyncShareReadModel(session *gocql.Session, libraryID, shareID string) error

func UpdateOrganizationLifecycleAndReadModel

func UpdateOrganizationLifecycleAndReadModel(session *gocql.Session, orgID string, next AdminOrganizationLifecycleUpdate) error

func UpdateUserAndAdminReadModels

func UpdateUserAndAdminReadModels(session *gocql.Session, orgID, userID string, next AdminUserUpdateSpec) error

func UpdateUserRoleAndAdminReadModels

func UpdateUserRoleAndAdminReadModels(session *gocql.Session, orgID, userID, role string) error

func UpdateUserRoleAttachOIDCIdentityAndAdminReadModels

func UpdateUserRoleAttachOIDCIdentityAndAdminReadModels(session *gocql.Session, orgID, userID, email, issuer, oidcSub, role string) error

func ValidateBlockRepresentationID

func ValidateBlockRepresentationID(representationID string) error

Types

type AccessToken

type AccessToken struct {
	Token   string
	Type    TokenType
	OrgID   string
	RepoID  string
	Path    string // File path for downloads, parent dir for uploads
	Replace bool   // Default overwrite behavior for upload tokens
	UserID  string
	// Source is "" for a regular user token and "link" for a share/upload link.
	// Those are the only two values ever written. Sync authentication allowlists
	// Source == "" exactly (see isRepositorySyncToken in internal/api), so a new
	// value is refused there until it is admitted on purpose. (An earlier
	// version of this comment listed "web" as an equivalent regular-user value;
	// nothing has ever minted it.)
	Source    string
	SourceID  string // Stable non-secret identity for the originating public link
	CreatedAt time.Time
}

AccessToken represents a temporary access token for file operations

type AdminDeletedLibraryProjectionRow

type AdminDeletedLibraryProjectionRow struct {
	OrgID      string
	LibraryID  string
	OwnerID    string
	OwnerEmail string
	OwnerName  string
	Name       string
	Encrypted  bool
	SizeBytes  int64
	DeletedAt  time.Time
}

func ListDeletedAdminLibraryRowsByOrg

func ListDeletedAdminLibraryRowsByOrg(session *gocql.Session, orgID string) ([]AdminDeletedLibraryProjectionRow, error)

func ReadDeletedAdminLibraryProjectionRow

func ReadDeletedAdminLibraryProjectionRow(session *gocql.Session, orgID, libraryID string) (AdminDeletedLibraryProjectionRow, error)

func ReconcileDeletedAdminLibraryRowsByOrg

func ReconcileDeletedAdminLibraryRowsByOrg(session *gocql.Session, orgID string) ([]AdminDeletedLibraryProjectionRow, int, error)

type AdminGroupProjectionRow

type AdminGroupProjectionRow struct {
	OrgID         string
	GroupID       string
	Name          string
	CreatorID     string
	OwnerEmail    string
	OwnerName     string
	ParentGroupID string
	IsDepartment  bool
	CreatedAt     time.Time
}

func ListAdminGlobalGroupRows

func ListAdminGlobalGroupRows(session *gocql.Session) ([]AdminGroupProjectionRow, error)

func ReadAdminGroupProjectionRow

func ReadAdminGroupProjectionRow(session *gocql.Session, orgID, groupID string) (AdminGroupProjectionRow, error)

type AdminLibraryProjectionRow

type AdminLibraryProjectionRow struct {
	OrgID        string
	LibraryID    string
	OwnerID      string
	OwnerEmail   string
	OwnerName    string
	Name         string
	Encrypted    bool
	StorageClass string
	SizeBytes    int64
	FileCount    int64
	CreatedAt    time.Time
	UpdatedAt    time.Time
	DeletedAt    *time.Time
}

func ListAdminGlobalLibraryRows

func ListAdminGlobalLibraryRows(session *gocql.Session) ([]AdminLibraryProjectionRow, error)

func ListAdminOrgLibraryRows

func ListAdminOrgLibraryRows(session *gocql.Session, orgID string) ([]AdminLibraryProjectionRow, error)

func ListAdminOwnerLibraryRows

func ListAdminOwnerLibraryRows(session *gocql.Session, orgID, ownerID string) ([]AdminLibraryProjectionRow, error)

func ReadAdminLibraryProjectionRow

func ReadAdminLibraryProjectionRow(session *gocql.Session, orgID, libraryID string) (AdminLibraryProjectionRow, error)

type AdminOrgLinkIndexRow

type AdminOrgLinkIndexRow struct {
	LinkType  string
	Token     string
	CreatedBy string
	CreatedAt time.Time
	Active    bool
}

func ListAdminOrgLinkIndexRows

func ListAdminOrgLinkIndexRows(session *gocql.Session, orgID string) ([]AdminOrgLinkIndexRow, error)

type AdminOrganizationLifecycleUpdate

type AdminOrganizationLifecycleUpdate struct {
	Status              string
	DeletedAt           *time.Time
	DeletedMarkerName   string
	DeletedMarkerAt     *time.Time
	DeleteDeletedMarker bool
}

type AdminOrganizationProjectionRow

type AdminOrganizationProjectionRow struct {
	OrgID        string
	Name         string
	OwnerEmail   string
	OwnerName    string
	Status       string
	Plan         string
	StorageQuota int64
	DeletedAt    *time.Time
	UsersCount   int
	CreatedAt    time.Time
}

func BuildAdminOrganizationProjectionRowForNewUser

func BuildAdminOrganizationProjectionRowForNewUser(session *gocql.Session, orgID, email, name, role string) (AdminOrganizationProjectionRow, error)

func BuildAdminOrganizationProjectionRowForUpdatedUser

func BuildAdminOrganizationProjectionRowForUpdatedUser(session *gocql.Session, orgID, updatedUserID, updatedName, updatedRole string) (AdminOrganizationProjectionRow, error)

func ListAdminOrganizationRows

func ListAdminOrganizationRows(session *gocql.Session, statusFilter string) ([]AdminOrganizationProjectionRow, error)

func ReadAdminOrganizationProjectionRow

func ReadAdminOrganizationProjectionRow(session *gocql.Session, orgID string) (AdminOrganizationProjectionRow, error)

type AdminOrganizationProjectionState

type AdminOrganizationProjectionState struct {
	OrgID     string
	Status    string
	CreatedAt time.Time
}

func ReadAdminOrganizationProjectionState

func ReadAdminOrganizationProjectionState(session *gocql.Session, orgID string) (AdminOrganizationProjectionState, error)

type AdminOrganizationWriteSpec

type AdminOrganizationWriteSpec struct {
	OrgID                  string
	Name                   string
	Status                 string
	Settings               map[string]string
	StorageQuota           int64
	StorageUsed            int64
	ChunkingPolynomial     int64
	StorageConfig          map[string]string
	CreatedAt              time.Time
	Plan                   string
	QuotaPolicy            string
	BillingCycle           string
	TrafficQuota           int64
	TrafficUploadQuota     int64
	TrafficDownloadQuota   int64
	MaxUsers               int
	CurrentPeriodStartedAt time.Time
	CurrentPeriodEndsAt    time.Time
	DeletedAt              *time.Time
}

type AdminUserProjectionRow

type AdminUserProjectionRow struct {
	OrgID       string
	UserID      string
	Email       string
	Name        string
	Role        string
	Status      string
	QuotaBytes  int64
	QuotaUsage  int64
	LastLoginAt *time.Time
	CreatedAt   time.Time
}

func ListAdminUserRows

func ListAdminUserRows(session *gocql.Session, statusFilter string) ([]AdminUserProjectionRow, error)

func ReadAdminUserProjectionRow

func ReadAdminUserProjectionRow(session *gocql.Session, orgID, userID string) (AdminUserProjectionRow, error)

type AdminUserProjectionState

type AdminUserProjectionState struct {
	UserID    string
	OrgID     string
	Status    string
	CreatedAt time.Time
}

func ReadAdminUserProjectionState

func ReadAdminUserProjectionState(session *gocql.Session, userID string) (AdminUserProjectionState, error)

type AdminUserUpdateSpec

type AdminUserUpdateSpec struct {
	Name                 string
	Role                 string
	Status               string
	DeletedAt            *time.Time
	QuotaBytes           int64
	TrafficUploadQuota   int64
	TrafficDownloadQuota int64
	AttachOIDCIssuer     string
	AttachOIDCSub        string
	AttachEmail          string
}

type AdminUserWriteSpec

type AdminUserWriteSpec struct {
	OrgID                string
	UserID               string
	Email                string
	Name                 string
	Role                 string
	Status               string
	QuotaBytes           int64
	UsedBytes            int64
	CreatedAt            time.Time
	DeletedAt            *time.Time
	TrafficUploadQuota   int64
	TrafficDownloadQuota int64
	LastLoginAt          *time.Time
	OIDCIssuer           string
	OIDCSub              string
}

type AppliedMigration

type AppliedMigration struct {
	Version   int
	Name      string
	Checksum  string
	AppliedAt time.Time
}

AppliedMigration is a row read from the schema_migrations table.

type BlockAuthorityRead

type BlockAuthorityRead int

BlockAuthorityRead selects how strongly a writer-side fence read must observe the GC lifecycle.

BlockAuthorityStrong is a linearizable SERIAL read. It costs a global Paxos round trip, so it is reserved for the one decision that must observe a fence published moments earlier: the exact-incarnation revalidation immediately before a physical repair PUT (R10).

BlockAuthorityAdvisory is a quorum read, not an inherited one. It is correct wherever the answer gates an early-out and the real authority is enforced structurally downstream — by the single-use INSTALL LWT for a fresh incarnation, or by the tuple-bound, non-creating CAS in RepairBlockMetadataIfCurrent (R17). Because ClaimBlockDelete and StartBlockDeleteOrphan publish with EACH_QUORUM commit visibility, a LOCAL_QUORUM read intersects that commit in every DC and therefore observes every committed fence; what it may miss is a fence whose Paxos commit is still in flight, and every such caller has downstream authority that rejects the stale observation.

The level is pinned rather than inherited BECAUSE that intersection is the whole argument. `database.consistency` accepts ONE (config.go), and a ONE read can land on a replica that never received the commit — which would let a writer see no fence at all and mint a new incarnation while the previous lifecycle's orphan is still live. A fence read therefore declares the consistency its own correctness requires instead of trusting operator configuration, exactly as BlockHasReferencesGlobal does for the destructive side.

const (
	BlockAuthorityAdvisory BlockAuthorityRead = iota
	BlockAuthorityStrong
)

type BlockIDResolver

type BlockIDResolver func(blockIDs []string) ([]string, error)

BlockIDResolver optionally resolves caller-facing block IDs into the block ID partition key used in block_references before staging a publish attempt.

type BlockPhysicalLocation

type BlockPhysicalLocation struct {
	StorageClass string
	StorageKey   string
}

type BlockRepairAuthorityOutcome

type BlockRepairAuthorityOutcome int

BlockRepairAuthorityOutcome classifies authority to repair one exact existing physical incarnation. Callers must not treat an unknown outcome as authority.

const (
	BlockRepairAuthorityUnknown BlockRepairAuthorityOutcome = iota
	BlockRepairAuthorityAuthorized
	BlockRepairAuthorityChanged
	BlockRepairAuthorityBlocked
	BlockRepairAuthorityPermanent
)

type BlockReuseDecision

type BlockReuseDecision int
const (
	BlockReuseUnknownError BlockReuseDecision = iota
	BlockReuseReusable
	BlockReuseNeedsPut
	BlockReuseBlockedByGC
	BlockReuseRepairableStub
)

type BlockReuseProbe

type BlockReuseProbe struct {
	Decision     BlockReuseDecision
	Sha1         string
	SizeBytes    int
	StorageClass string
	StorageKey   string
}

type BlockS3OrphanInfo

type BlockS3OrphanInfo struct {
	StorageClass string
	FirstSeenAt  time.Time
}

type BlockStorageLocation

type BlockStorageLocation struct {
	SizeBytes    int64
	StorageClass string
	StorageKey   string
	GCState      string
	CreatedAt    *time.Time
}

BlockStorageLocation is the canonical physical location recorded for a block.

type BlockUploadSession

type BlockUploadSession struct {
	SessionID      string
	OrgID          string
	UserID         string
	RepoID         string
	ParentDir      string
	CreatedAt      time.Time
	ExpiresAt      time.Time
	Committed      bool
	ManifestDigest string
	ResultPath     string
	ResultFilename string
	ResultCommitID string
	// Slot is the per-user concurrency slot this session claimed at creation
	// (0..cap-1), or -1 when the per-user session cap is disabled. Stored so
	// commit cleanup frees the exact slot without scanning. See item 1 in
	// docs/WEB-BLOCK-UPLOAD.md.
	Slot int
	// ExpectedSize is the client-declared file size, validated against the
	// per-session ceiling at creation (fail-fast) and re-checked against the
	// manifest at commit when ExpectedSizeDeclared is true.
	ExpectedSize int64
	// ExpectedSizeDeclared distinguishes an explicit size=0 (empty file) from
	// "size missing", so commit can enforce manifest.size == expected_size only
	// when the client actually declared a size.
	ExpectedSizeDeclared bool
	// BlockSizeBytes is the authoritative CAS block size echoed to the client at
	// session creation and reused by /blocks/upload for this session, so a config
	// change during the 48h TTL cannot change the accepted body size mid-upload.
	BlockSizeBytes int64
	// StagedBucketCount / StagedBucketCap freeze the per-session staged-block
	// ledger parameters at session creation. Retries must keep hashing into the
	// same bucket partitions even if live config changes after the session is
	// minted; otherwise the same block could appear "unreserved" and be counted
	// again under a different bucket number.
	StagedBucketCount int
	StagedBucketCap   int
}

BlockUploadSession is a server-issued handle for the web content-addressed upload flow. It scopes a batch of /blocks/upload calls and the final file-from-blocks commit to one (org, user, repo). SessionID doubles as the provisional block reference owner ("up:<session_id>"). ResultCommitID is a generic stable result token for the idempotent winner; the web block-upload flow currently stores the published file fs_id there because this path does not create a standalone commit object of its own.

type BlockUploadSessionAdmission

type BlockUploadSessionAdmission struct {
	ExpectedSize         int64
	ExpectedSizeDeclared bool
	BlockSizeBytes       int64
	StagedBucketCount    int
	StagedBucketCap      int
}

BlockUploadSessionAdmission is the immutable admission contract frozen onto a server-issued upload session at creation time. Freezing these values keeps the session stable across restarts/config changes without adding coordination to the /blocks/upload hot path.

type DB

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

DB wraps the Cassandra session.

func New

func New(cfg config.DatabaseConfig) (*DB, error)

New creates a new database connection. It first connects without a keyspace to ensure the keyspace exists, then reconnects with the keyspace set.

func (*DB) AddBlockReference

func (db *DB) AddBlockReference(orgID, blockID, referrer, libraryID string, ttlSeconds int) error

AddBlockReference registers a reference to a block. Idempotent: re-adding the same (block, referrer) overwrites a row with identical key. ttlSeconds > 0 makes the row expire (for example publish-attempt references); 0 means permanent.

func (*DB) AddProvisionalBlockReferenceWithExpiry

func (db *DB) AddProvisionalBlockReferenceWithExpiry(orgID, blockID, referrer, libraryID, storageClass string, expiresAt time.Time) error

AddProvisionalBlockReferenceWithExpiry writes an in-flight upload's block reference AND its GC expiry tracking in ONE logged batch (F10). When these rows were written separately, a failure between them left a reference with no discovery projection: GC Phase 0 enumerates provisional refs through `gc_provisional_block_refs_by_day`, so an unprojected reference pins the block forever with nothing able to find it.

What a logged batch does and does not buy, precisely: it gives **atomicity** — the batchlog is replicated before anything is applied and replayed if the coordinator dies, so a permanently half-applied batch is not an ordinary failure mode the way a split write is. It does NOT give **isolation**: a concurrent reader can observe one statement before the other. That is harmless here in both directions — Phase 0 only ever discovers through the projection, and a projection this batch has just written points ~48h into the future, so nothing acts on it until long after every statement has landed.

The Cassandra TTL on the reference row is DERIVED from expiresAt rather than passed in, which is load-bearing beyond tidiness: GC Phase 0 no longer deletes expired provisional references, it waits for this TTL to retire them (F9). A caller that could pass a TTL outliving expiresAt would strand the scanner waiting on a row that never goes away; deriving it makes "the row cannot outlive its tracker by more than the rounding second" true by construction.

A zero/past expiresAt is rejected rather than silently written: an untracked provisional reference is exactly the leak this function exists to prevent.

func (*DB) BlockDeleteFenceActive

func (db *DB) BlockDeleteFenceActive(orgID, blockID string) (bool, error)

BlockDeleteFenceActive reports whether GC still owns the physical object for this block. Writers must treat both an in-row gc_state='deleting' claim and a pending gc_s3_orphans row as an active fence; otherwise a re-upload can race with orphan recovery and lose the object after the canonical block row was already deleted. The canonical row is read FIRST and the orphan LAST, and that order is the whole correctness argument. GC writes gc_state, then the orphan, then removes the row. Reading the orphan first admits this sequence:

writer reads orphan   -> absent
GC     StartBlockDeleteOrphan  -> orphan(P1) now exists
GC     FinalizeBlockDelete     -> blocks(L) removed
writer reads blocks   -> absent, reported as "no fence"
writer installs P2    -> blocks(L) -> P2 while orphan(P1) is live

which is precisely the overlapped state conservative A+ forbids (R13). Reading the row first inverts the dependency: an absent row proves the orphan of that lifecycle was already durable, so the orphan read that follows observes it. Both reads are ordinary. The fence publishers commit at EACH_QUORUM, so an ordinary read already sees every committed fence, and the authority that actually admits a write is downstream and structural -- the single-use INSTALL LWT for a fresh incarnation, the tuple-bound non-creating CAS for a repair.

func (*DB) BlockHasReferences

func (db *DB) BlockHasReferences(orgID, blockID string) (bool, error)

BlockHasReferences reports whether any reference row still exists for the block. This single-partition point read replaces reading the mutable blocks.ref_count.

It runs at the session consistency (LOCAL_QUORUM in every shipped profile), so a TRUE answer is proof — a row visible locally is a real reference — while a FALSE answer proves only that the local DC has not seen one. That asymmetry is why this call is safe for discovery and short-circuit aborts but MUST NOT authorize a physical delete. Use BlockHasReferencesGlobal for that (ISSUE-GC-CROSS-DC-REFERENCE-VISIBILITY-01).

func (*DB) BlockHasReferencesGlobal

func (db *DB) BlockHasReferencesGlobal(orgID, blockID string) (bool, error)

BlockHasReferencesGlobal is the destructive-authorization liveness read: the only form whose FALSE answer may authorize deleting physical bytes.

It pins EACH_QUORUM per query rather than inheriting the session default, so the read must obtain a quorum in EVERY datacenter. A reference write acknowledged at LOCAL_QUORUM in any DC therefore intersects this read's quorum in that same DC, and GC cannot conclude "zero references" while one exists somewhere else. If any DC is unreachable the read fails and the caller must fail closed — deleting on an uncertain read is exactly the defect this closes.

The per-DC argument presumes NetworkTopologyStrategy with every replica-holding DC in the keyspace map; under SimpleStrategy EACH_QUORUM does not carry it. The destructive path gates on that separately.

func (*DB) BlockHasReferrer

func (db *DB) BlockHasReferrer(orgID, blockID, referrer string) (bool, error)

BlockHasReferrer reports whether a specific (block, referrer) reference row exists. Used to verify that a block is owned by a given upload session's provisional reference ("up:<session_id>") before a commit publishes it.

func (*DB) BlockReferenceExists

func (db *DB) BlockReferenceExists(orgID, blockID, referrer string) (bool, error)

BlockReferenceExists reports whether one specific (block, referrer) reference row is still present. GC Phase 0 uses it to tell "this provisional reference has been retired by its Cassandra TTL" from "the row is still there", which is what lets the scanner wait for the TTL instead of deleting a reference an upload may have just renewed (F9).

func (*DB) ClaimBlockUploadSessionForCommit

func (db *DB) ClaimBlockUploadSessionForCommit(sessionID, manifestDigest string) (bool, error)

ClaimBlockUploadSessionForCommit atomically claims the session for committing the given manifest digest, via a Cassandra LWT. Exactly one concurrent caller gets applied=true (the winner, which then runs finalize); the rest get applied=false and must wait for / return the winner's idempotent result. The claim refreshes the row TTL so a crash after claiming cannot leave immortal session columns behind; a successful commit later re-writes the whole row with a fresh TTL, and a failed finalize releases the claim.

func (*DB) CleanupCommittedBlockUploadSessionCaps

func (db *DB) CleanupCommittedBlockUploadSessionCaps(s BlockUploadSession) error

CleanupCommittedBlockUploadSessionCaps releases the session's per-user slot so the user's concurrent-session budget recovers immediately at commit. It is BEST-EFFORT and MUST be called only after the critical idempotency write (MarkBlockUploadSessionCommitted) has succeeded — never inside it — so a cleanup failure can never make a committed file look uncommitted. A failed release only lets the slot linger until its TTL (≤48h), which fails safe (toward rejecting new sessions). The staged-block ledger rows are intentionally left to self-expire via TTL (keyed by the now-dead session id, never re-read), avoiding a burst of partition tombstones per commit.

func (*DB) Close

func (db *DB) Close()

Close closes the database connection.

func (*DB) CountSessionStagedBlocksInBucket

func (db *DB) CountSessionStagedBlocksInBucket(sessionID string, bucket, limit int) (int, error)

CountSessionStagedBlocksInBucket returns how many distinct blocks the session has already staged in the given bucket, reading at most `limit` rows (the caller passes bucketCap+1 so it only needs to know whether the cap is reached).

func (*DB) CreateAdmittedBlockUploadSession

func (db *DB) CreateAdmittedBlockUploadSession(orgID, userID, repoID, parentDir string, admission BlockUploadSessionAdmission, cap int) (BlockUploadSession, error)

CreateAdmittedBlockUploadSession mints a new server-issued session bound to the caller's (org, user, repo), enforcing the per-user concurrent-session cap atomically. The whole admission + creation lives here so a caller can never leave block_upload_session_slots_by_user and block_upload_sessions out of sync.

When cap > 0, a slot 0..cap-1 is claimed via a Cassandra LWT (INSERT ... IF NOT EXISTS); if every slot is taken it returns ErrBlockUploadSessionSlotsExhausted (Paxos runs only here, at session creation — never per block). cap <= 0 disables the cap (slot = -1). admission is the immutable session contract frozen at creation time: expected size metadata plus the ledger bucketing/body-size parameters derived from the then-current config. The session row and its slot both carry the session TTL so an abandoned session self-expires.

func (*DB) DeleteClaimedBlockStub

func (db *DB) DeleteClaimedBlockStub(orgID, blockID, claimID string) (bool, error)

func (*DB) DeletePendingPublishedFSObjectOwner

func (db *DB) DeletePendingPublishedFSObjectOwner(repoID, fsID, ownerID string, createdAt time.Time) error

func (*DB) DeleteProvisionalBlockReferenceExpiry

func (db *DB) DeleteProvisionalBlockReferenceExpiry(orgID, blockID, referrer string, expiresAt time.Time) error

DeleteProvisionalBlockReferenceExpiry removes the canonical expiry row and, when available, its by-day discovery projection. It is retained for controlled teardown paths; production Phase 0 never deletes canonical trackers and relies on their TTL plus canonical-missing recovery instead.

func (*DB) GetBlockIDMapping

func (db *DB) GetBlockIDMapping(orgID, representationID, externalID string) (internalID string, ok bool, err error)

GetBlockIDMapping resolves one external SHA-1 block ID to its internal SHA-256 storage identity using the forward row scoped to one representation domain. ok == false means no mapping row exists.

This contextless form is for callers that resolve a single mapping as part of a write, where there is no per-request budget to respect and the driver's own timeout is the bound. Anything that resolves mappings in bulk must use GetBlockIDMappingContext instead: a loop of contextless reads cannot be stopped by a client disconnect or a request deadline, which is precisely the unbounded work subcontract C exists to close.

func (*DB) GetBlockIDMappingContext

func (db *DB) GetBlockIDMappingContext(ctx context.Context, orgID, representationID, externalID string) (internalID string, ok bool, err error)

GetBlockIDMappingContext is GetBlockIDMapping bound to a context, so an in-flight read is abandoned when the caller's deadline expires or its client goes away.

func (*DB) GetBlockS3OrphanInfo

func (db *DB) GetBlockS3OrphanInfo(orgID, blockID string) (BlockS3OrphanInfo, bool, error)

func (*DB) GetBlockStorageLocation

func (db *DB) GetBlockStorageLocation(ctx context.Context, orgID, blockID string) (BlockStorageLocation, bool, error)

GetBlockStorageLocation reads one block's canonical physical location.

func (*DB) GetBlockUploadSession

func (db *DB) GetBlockUploadSession(sessionID string) (BlockUploadSession, bool, error)

GetBlockUploadSession reads a session by id. ok=false when the row is missing (expired via TTL or never existed).

func (*DB) InstallBlockMetadata

func (db *DB) InstallBlockMetadata(ctx context.Context, orgID, representationID, blockID, sha1 string, sizeBytes int, proposed BlockPhysicalLocation) InstallBlockMetadataResult

InstallBlockMetadata performs a create-only canonical install for one freshly minted physical incarnation. It submits exactly one non-idempotent LWT. If the mutation result is unknown, it performs one bounded SERIAL read; it never repeats the proposed install.

Production upload paths use this only for a freshly minted-and-PUT target; canonical reuse and repair continue through RepairBlockMetadataIfCurrent.

func (*DB) ListBlockReferrers

func (db *DB) ListBlockReferrers(orgID, blockID string) ([]string, error)

ListBlockReferrers returns all referrer strings currently keeping a block alive. The (org, block) partition holds only this block's reference rows, so the scan is a single-partition read. Used to distinguish permanent ("fs:"/ "pub:") liveness from provisional ("up:") upload references.

func (*DB) ListPendingPublishedFSObjectOwnersByDay

func (db *DB) ListPendingPublishedFSObjectOwnersByDay(day time.Time, bucket int) ([]PendingPublishedFSObjectOwner, error)

func (*DB) LoadPendingPublishedFSObjectOwner

func (db *DB) LoadPendingPublishedFSObjectOwner(repoID, fsID, ownerID string) (PendingPublishedFSObjectOwner, error)

func (*DB) MarkBlockUploadSessionCommitted

func (db *DB) MarkBlockUploadSessionCommitted(s BlockUploadSession, manifestDigest, resultPath, resultFilename, resultCommitID string) error

MarkBlockUploadSessionCommitted records the committed result so a retried commit with the same manifest is idempotent (returns the same file instead of auto-renaming a duplicate). The whole row is re-written with a fresh TTL so the idempotency record stays consistent and survives client retries.

func (*DB) Migrate

func (db *DB) Migrate() error

Migrate runs the schema migration runner.

func (*DB) PendingPublishedFSObjectOwnerExists

func (db *DB) PendingPublishedFSObjectOwnerExists(repoID, fsID string) (bool, error)

func (*DB) Ping

func (db *DB) Ping(ctx context.Context) error

Ping verifies database connectivity by executing a lightweight query.

func (*DB) ProbeBlockReuse

func (db *DB) ProbeBlockReuse(orgID, blockID string) (BlockReuseProbe, error)

ProbeBlockReuse classifies whether an uploaded block can safely skip S3 PUT, needs a direct PUT, or must back off because GC still owns the object.

func (*DB) ReleaseBlockUploadSessionCommit

func (db *DB) ReleaseBlockUploadSessionCommit(sessionID string) error

ReleaseBlockUploadSessionCommit reverts a commit claim so a retry can proceed after a failed finalize. Idempotent.

func (*DB) RemoveBlockReference

func (db *DB) RemoveBlockReference(orgID, blockID, referrer string) error

RemoveBlockReference deletes a single (block, referrer) reference. Idempotent: deleting a non-existent row is a no-op, so a retried GC pass or publish-attempt cleanup is always safe. Upload up: references are not removed through this API.

DELIBERATELY NOT PINNED to BlockReferenceWriteConsistency, and the asymmetry is the point — but NOT for the reason an earlier version of this comment gave. It claimed an under-replicated DELETE "leaves the row visible, so GC declines to collect and the bytes survive one more pass", i.e. that a weak delete biases toward keeping data. That is not a property of Cassandra. A DELETE writes a timestamped tombstone, the mutation is sent to every replica regardless of consistency level, and reconciliation is last-write-wins — so a quorum read that touches the tombstone resolves to "absent" and repairs the others with it. A delete acknowledged by one replica can absolutely make the row invisible to a later per-DC read quorum. There is no structural bias toward keeping data here to lean on.

What actually makes the exemption safe is the PROTOCOL, not the consistency level. The X2 premise concerns CREATING a live reference: that write must reach a quorum, because the destructive read's zero is only trustworthy if it intersects whatever acknowledged it. Removing a reference creates no such obligation — its safety rests on this call only ever being made once the referrer has lost authority over the block. Both callers satisfy that by construction: the publish-attempt cleanup retires a TTL'd provisional reference, and the GC cascade removes an fs_object's reference as that fs_object is being deleted. The window between publishing a new reference and removing an old one is the publication fence, which belongs to X1 (ISSUE-GC-UPLOAD-FENCE-REMATERIALIZATION-01), not to the consistency of this statement.

Pinning the delete to LOCAL_QUORUM as well would be harmless — it is already the effective level in every shipped profile — and would remove the need to explain the asymmetry at all. It is left inheriting the session because the pin is a safety mechanism with a specific justification, and applying it where that justification does not hold would blur what it means everywhere else.

func (*DB) RepairBlockMetadataIfCurrent

func (db *DB) RepairBlockMetadataIfCurrent(orgID, representationID, blockID, sha1 string, sizeBytes int, expected BlockPhysicalLocation) error

RepairBlockMetadataIfCurrent repairs immutable identity metadata only while the canonical row still names expected and remains outside every A+ GC fence. It never executes INSERT and its conditional UPDATE statements cannot create a row.

func (*DB) RepairReleasedBlockStub

func (db *DB) RepairReleasedBlockStub(orgID, blockID string) (bool, error)

func (*DB) ReserveSessionStagedBlock

func (db *DB) ReserveSessionStagedBlock(sessionID string, bucket int, blockID string, sizeBytes int64) error

ReserveSessionStagedBlock records a new staged block in the session's ledger BEFORE the block is stored (reserve-before-PUT, fail-closed). The insert is idempotent by ((session_id, bucket), block_id) — a retried block rewrites the same row and is never double-counted — and carries the session TTL so an abandoned session's ledger self-expires (no Cassandra COUNTER, no drift).

func (*DB) SeedDatabase

func (db *DB) SeedDatabase(cfg *config.Config, devMode bool, firstSuperAdminEmail string) error

SeedDatabase creates platform org, default organization, and admin users if they don't exist. This runs automatically on application startup.

firstSuperAdminEmail: if non-empty, seeds a superadmin in the platform org with this email so the user can log in via OIDC and be matched to the superadmin account on first login.

Each org-scoped seed runs in a single LoggedBatch so canonical rows and admin read-model projections are written atomically — there is no state where the org/user exists but its projection is missing.

func (*DB) Session

func (db *DB) Session() *gocql.Session

Session returns the underlying gocql session.

func (*DB) SessionStagedBlockExists

func (db *DB) SessionStagedBlockExists(sessionID string, bucket int, blockID string) (bool, error)

SessionStagedBlockExists reports whether a block is already reserved in the session's ledger. Used to let a retry through even when its bucket is at the cap (e.g. the block was reserved but its PUT failed and is being retried) — the block is already counted, so admitting it does not grow the bound.

func (*DB) UpsertPendingPublishedFSObjectOwner

func (db *DB) UpsertPendingPublishedFSObjectOwner(repoID, fsID, ownerID string, createdAt time.Time, orgID, attemptID string, blockIDs []string) error

func (*DB) ValidateBlockRepairAuthority

func (db *DB) ValidateBlockRepairAuthority(orgID, blockID string, expected BlockPhysicalLocation) (BlockRepairAuthorityOutcome, error)

ValidateBlockRepairAuthority grants authority only for the exact canonical physical incarnation the caller supplies. Under conservative A+, either GC ownership shape or any gc_s3_orphans row blocks repair. ValidateBlockRepairAuthority is the pre-PUT boundary (R10) and therefore always reads at BlockAuthorityStrong: it is the one decision that must observe a fence published moments earlier, and it runs only when an existing physical object turned out to be missing and needs repair -- a cold path, not the dedup path.

func (*DB) ValidateDestructiveGCTopology

func (db *DB) ValidateDestructiveGCTopology() error

ValidateDestructiveGCTopology reports whether the live keyspace replication makes the per-DC EACH_QUORUM liveness argument sound.

Closing ISSUE-GC-CROSS-DC-REFERENCE-VISIBILITY-01 rests on a quorum-intersection argument that is stated PER DATACENTER: an EACH_QUORUM read must obtain a quorum in every DC, so it intersects the quorum that acknowledged a LOCAL_QUORUM reference write in whichever DC accepted it. That argument presumes NetworkTopologyStrategy with every replica-holding DC in the keyspace map. Under SimpleStrategy there are no per-DC quorums for EACH_QUORUM to intersect, and the closure does not hold — so the destructive path must refuse to run rather than delete under an argument that does not apply.

This reads live keyspace metadata rather than configuration, because the deployment map comes from the environment (CASSANDRA_REPLICATION_DCS) and the checked-in profiles are not the source of truth about the fleet. It then requires the live map to equal the declared one — see the comment on that comparison for what it does and does not prove, which is narrower than "the topology cannot change".

A single-DC NetworkTopologyStrategy map passes deliberately. There, EACH_QUORUM and LOCAL_QUORUM denote the same quorum, so the cross-DC argument is vacuous — but it is vacuously TRUE, not violated: there is no second DC whose acknowledged write could be missed. The gate exists to reject topologies where EACH_QUORUM carries no per-DC meaning at all, not to require multi-DC.

func (*DB) WriteBlockIDMapping

func (db *DB) WriteBlockIDMapping(orgID, representationID, externalID, internalID string, createdAt time.Time) error

WriteBlockIDMapping writes the forward external SHA-1 -> internal SHA-256 mapping used to resolve a bare-SHA-1 compatibility read inside one block representation domain. The write is idempotent for the same internal ID and fails closed on a conflicting remap, except for the documented tiny read-before-write race between two same-key concurrent SHA-1 collisions.

func (*DB) WriteVerifiedWebBlockMapping

func (db *DB) WriteVerifiedWebBlockMapping(orgID, representationID, externalID, internalID string, createdAt time.Time) error

WriteVerifiedWebBlockMapping writes the forward external SHA-1 -> internal SHA-256 mapping for the WEB block-upload (session) flow ONLY. Both hashes are computed server-side from the block's real bytes in UploadBlock, so the mapping is verified content, never client-asserted.

The actual write contract is shared with WriteBlockIDMapping: create when absent, succeed when the same row already exists, and fail closed when the same (org, representation, external) key points at a different internal ID. The guard is a plain read-before-write, NOT a Cassandra LWT/Paxos: per-block Paxos on the upload hot path causes latency, contention, and timeouts in multi-DC deployments, and the commit-side forward-mapping check remains the integrity authority regardless. The only residual gap versus LWT is two *colliding* blocks (same SHA-1, different content) racing the tiny read->write window — astronomically unlikely.

type FileLock

type FileLock struct {
	LockedBy string // canonical UUID string of the lock holder
	LockedAt time.Time
}

FileLock is the current exclusive lock on a path inside a library.

The schema (locked_files) carries no lock_type or expiry yet, so every lock is treated as a manual exclusive lock: a single writer who must be the only one allowed to mutate the file until they release it. See docs/FILE-LOCKING-DESIGN.md for the planned OnlyOffice (online_office) lock type.

func ReadFileLock

func ReadFileLock(session *gocql.Session, repoUUID gocql.UUID, path string) (FileLock, bool, error)

ReadFileLock returns the lock on (repoUUID, path), or ok=false when the path is not locked. Query failures are reported to the caller so write paths can fail closed instead of silently bypassing lock enforcement.

type InstallBlockMetadataOutcome

type InstallBlockMetadataOutcome int

InstallBlockMetadataOutcome is the authority returned by the single-use canonical metadata install. Callers must branch on this value, not Cause.

const (
	// InstallBlockMetadataAmbiguous authorizes neither use nor cleanup of the
	// proposed physical object.
	InstallBlockMetadataAmbiguous InstallBlockMetadataOutcome = iota
	// InstallBlockMetadataApplied means Canonical is the proposed physical tuple.
	InstallBlockMetadataApplied
	// InstallBlockMetadataKnownLost means Canonical names a different complete
	// tuple, or is empty when settlement proved that no canonical row exists.
	InstallBlockMetadataKnownLost
	// InstallBlockMetadataIdentityContradiction is a definite direct CAS result
	// that found the exact proposed tuple already present. It authorizes neither
	// use nor cleanup of the single-use proposal.
	InstallBlockMetadataIdentityContradiction
)

type InstallBlockMetadataResult

type InstallBlockMetadataResult struct {
	Outcome   InstallBlockMetadataOutcome
	Canonical BlockPhysicalLocation
	// Submitted is true once the single-use INSTALL LWT has been entered. It is
	// provenance, not an authority signal; callers still branch on Outcome.
	Submitted bool
	// Cause is diagnostic only. It never grants authority to use or clean up a
	// physical object; Outcome is the complete authority contract.
	Cause error
}

type LibraryState

type LibraryState struct {
	OrgID                 string
	LibraryID             string
	OwnerID               string
	Name                  string
	Encrypted             bool
	BlockRepresentationID string
	HeadCommitID          string
	StorageClass          string
	DeletedAt             *time.Time
}

LibraryState captures the canonical fields that write/read fences need in order to treat soft-deleted libraries as unavailable.

func ReadLibraryState

func ReadLibraryState(session *gocql.Session, orgID, libraryID string) (LibraryState, error)

ReadLibraryState loads the canonical libraries row for a known org/library pair, including deleted_at so callers can distinguish live vs soft-deleted.

func ReadLibraryStateContext

func ReadLibraryStateContext(ctx context.Context, session *gocql.Session, orgID, libraryID string) (LibraryState, error)

ReadLibraryStateContext is ReadLibraryState bound to ctx. Request-scoped callers use it so metadata work stops when the client disconnects or its preparation deadline expires.

func ReadLiveLibraryState

func ReadLiveLibraryState(session *gocql.Session, orgID, libraryID string) (LibraryState, error)

ReadLiveLibraryState returns the canonical library row only when the library is still live. Soft-deleted libraries are reported via ErrLibraryDeleted.

func ReadLiveLibraryStateContext

func ReadLiveLibraryStateContext(ctx context.Context, session *gocql.Session, orgID, libraryID string) (LibraryState, error)

ReadLiveLibraryStateContext is ReadLiveLibraryState bound to ctx.

func ResolveLiveLibraryStateByID

func ResolveLiveLibraryStateByID(session *gocql.Session, libraryID string) (LibraryState, error)

ResolveLiveLibraryStateByID resolves the org partition through libraries_by_id and then returns the canonical live library row.

type LockAcquireResult

type LockAcquireResult int

LockAcquireResult is the outcome of an attempt to acquire or refresh a lock.

const (
	// LockAcquired means the lock was newly taken by the requester.
	LockAcquired LockAcquireResult = iota
	// LockRefreshed means the requester already held the lock; its timestamp was bumped.
	LockRefreshed
	// LockConflict means another user holds the lock; ownerID identifies them.
	LockConflict
)

func AcquireFileLock

func AcquireFileLock(session *gocql.Session, repoID, path, userID string, lockedAt time.Time) (LockAcquireResult, string, error)

AcquireFileLock atomically takes the lock on (repoID, path) for userID, or refreshes it when userID already holds it. It uses a compare-and-set (INSERT ... IF NOT EXISTS) so two concurrent acquirers cannot both win — the loser is reported as LockConflict with the current owner. This replaces the previous check-then-INSERT, which raced in multi-region clusters (both requests could pass a read check, then upsert).

type MigrationFile

type MigrationFile struct {
	Version    int
	Name       string   // human-readable portion, e.g. "initial_schema"
	Filename   string   // original filename, e.g. "001_initial_schema.cql"
	Content    string   // raw file content
	Checksum   string   // SHA-256 hex digest of Content
	Statements []string // individual CQL statements (comments stripped, split on ;)
}

MigrationFile is a parsed migration file ready to apply.

type MigrationStatus

type MigrationStatus struct {
	MigrationFile
	Applied   bool
	AppliedAt time.Time
}

MigrationStatus combines a migration file with its applied state.

type Migrator

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

Migrator applies versioned CQL schema migrations to a Cassandra keyspace.

Migration files live in internal/db/migrations/NNN_description.cql and are embedded into the binary at compile time via go:embed. Each applied migration is recorded in the schema_migrations table with a SHA-256 checksum. If a migration file is modified after it has been applied, the checksum mismatch is detected at startup and the server refuses to boot — preventing silent schema drift.

File naming

Files must follow the NNN_description.cql convention where NNN is a zero-padded integer (e.g. 001, 042). The version number determines application order; gaps are allowed.

Idempotency

All statements in 001_initial_schema.cql use CREATE TABLE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS, making them safe to re-run on any database state. Any later incremental migrations may use ALTER TABLE or other non-idempotent statements and are only executed once — tracked via schema_migrations.

func NewMigrator

func NewMigrator(session *gocql.Session) *Migrator

NewMigrator creates a Migrator backed by the given Cassandra session.

func (*Migrator) Check

func (m *Migrator) Check() error

Check returns a non-nil error if any migrations are pending OR if any previously-applied migration file has been modified since application. Intended for CI pipelines: exit non-zero whenever the binary and database are out of sync in either direction.

func (*Migrator) DryRun

func (m *Migrator) DryRun() ([]MigrationFile, error)

DryRun returns the list of migrations that would be applied by Run, without executing or stamping anything.

func (*Migrator) Run

func (m *Migrator) Run() error

Run applies all pending migrations in version order.

Checksum validation runs first: if any previously-applied migration file has been modified, Run returns an error before touching the database.

func (*Migrator) Status

func (m *Migrator) Status() ([]MigrationStatus, error)

Status returns the applied state of every known migration file.

type PendingPublishedFSObjectOwner

type PendingPublishedFSObjectOwner struct {
	RepoID    string
	FSID      string
	OwnerID   string
	CreatedAt time.Time
	OrgID     string
	AttemptID string
	BlockIDs  []string
}

type RepoLockedFile

type RepoLockedFile struct {
	Path     string
	LockedBy string // canonical UUID string of the lock holder
}

RepoLockedFile is one active lock, as returned by the desktop/SeaDrive client's locked-files polling endpoint.

func ListRepoLocks

func ListRepoLocks(session *gocql.Session, repoID string) ([]RepoLockedFile, error)

ListRepoLocks returns every active lock in repoID. Unlike ReadFileLock this is not scoped to a single path: locked_files is partitioned by repo_id, so a full per-repo scan is cheap and this is what the desktop client's locked-files endpoint needs (it wants the whole repo's lock state in one call). An unparseable repoID is reported as "no locks" rather than an error, matching how the sync protocol's other unauthenticated per-repo endpoints degrade.

type ShareReadModelRow

type ShareReadModelRow struct {
	OrgID         string
	LibraryID     string
	ShareID       string
	SharedBy      string
	SharedByEmail string
	SharedByName  string
	SharedTo      string
	SharedToType  string
	Permission    string
	CreatedAt     time.Time
	ExpiresAt     *time.Time
	RepoName      string
	Encrypted     bool
	SizeBytes     int64
}

func HydrateShareReadModelRows

func HydrateShareReadModelRows(session *gocql.Session, rows []ShareReadModelRow) ([]ShareReadModelRow, error)

func ReadShareReadModelRow

func ReadShareReadModelRow(session *gocql.Session, libraryID, shareID string) (ShareReadModelRow, error)

type TokenCreator

type TokenCreator interface {
	CreateUploadToken(orgID, repoID, path, userID string) (string, error)
	CreateUpdateToken(orgID, repoID, path, userID string) (string, error)
	CreateDownloadToken(orgID, repoID, path, userID string) (string, error)
	CreateSyncToken(orgID, repoID, userID string) (string, error)
	CreateLinkUploadToken(orgID, repoID, path, userID, sourceID string) (string, error)
	CreateLinkDownloadToken(orgID, repoID, path, userID, sourceID string) (string, error)
}

TokenCreator interface for compatibility with existing code

type TokenStore

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

TokenStore provides distributed token management using Cassandra Tokens are stored with TTL for automatic expiration

func NewTokenStore

func NewTokenStore(db *DB, ttl time.Duration) *TokenStore

NewTokenStore creates a new distributed token store

func (*TokenStore) CreateDownloadToken

func (ts *TokenStore) CreateDownloadToken(orgID, repoID, path, userID string) (string, error)

CreateDownloadToken creates a download token for a regular (web) user.

func (*TokenStore) CreateLinkDownloadToken

func (ts *TokenStore) CreateLinkDownloadToken(orgID, repoID, path, userID, sourceID string) (string, error)

CreateLinkDownloadToken creates a download token for a share link — tagged as source="link".

func (*TokenStore) CreateLinkUploadToken

func (ts *TokenStore) CreateLinkUploadToken(orgID, repoID, path, userID, sourceID string) (string, error)

CreateLinkUploadToken creates an upload token for a share/upload link — tagged as source="link".

func (*TokenStore) CreateSyncToken

func (ts *TokenStore) CreateSyncToken(orgID, repoID, userID string) (string, error)

CreateSyncToken creates the repository sync credential.

It takes no path: a sync token is always scoped to the repository root, and leaving the caller unable to pass anything else is the point. The previous design minted these through CreateDownloadToken with a literal "/" argument, which meant a file-scoped download token and a sync credential differed only by the value one caller happened to pass.

func (*TokenStore) CreateToken

func (ts *TokenStore) CreateToken(tokenType TokenType, orgID, repoID, path, userID, source string) (*AccessToken, error)

CreateToken creates a new access token and stores it in Cassandra.

It refuses TokenTypeSync. The generic constructor takes a path, and a sync credential's root path is meant to be a property of its constructor rather than a value a caller supplies — see CreateSyncToken. Without this guard "CreateSyncToken is the only way to mint one" would be a convention rather than a fact, and the whole point of the separate type is that it cannot be produced by accident.

func (*TokenStore) CreateUpdateToken

func (ts *TokenStore) CreateUpdateToken(orgID, repoID, path, userID string) (string, error)

CreateUpdateToken creates an upload token that overwrites the target path by default.

func (*TokenStore) CreateUploadToken

func (ts *TokenStore) CreateUploadToken(orgID, repoID, path, userID string) (string, error)

CreateUploadToken creates an upload token for a regular (web) user.

func (*TokenStore) DeleteToken

func (ts *TokenStore) DeleteToken(tokenStr string) error

DeleteToken removes a token (for single-use tokens like upload)

func (*TokenStore) GetToken

func (ts *TokenStore) GetToken(tokenStr string, expectedType TokenType) (*AccessToken, bool)

GetToken retrieves and validates a token

type TokenType

type TokenType string

TokenType represents the type of access token

const (
	TokenTypeUpload   TokenType = "upload"
	TokenTypeDownload TokenType = "download"
	// TokenTypeSync is the repository sync credential the desktop client gets
	// from download-info. It is a distinct type rather than a download token of
	// a particular shape so that a download bearer cannot authenticate the sync
	// surface by construction — see ISSUE-SYNC-LINK-TOKEN-AUTH-01, where a
	// public share-link download token did exactly that. GetToken compares the
	// stored type exactly, so the separation is enforced at the store.
	TokenTypeSync TokenType = "sync"
)

Jump to

Keyboard shortcuts

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