Documentation
¶
Index ¶
- Constants
- Variables
- func AddDeleteAdminGroupReadModelQuery(batch *gocql.Batch, row AdminGroupProjectionRow)
- func AddDeleteAdminLibraryReadModelQueries(session *gocql.Session, batch *gocql.Batch, orgID, libraryID string) error
- func AddDeleteAdminLibraryReadModelQuery(batch *gocql.Batch, row AdminLibraryProjectionRow)
- func AddDeleteAdminLinkReadModelQuery(batch *gocql.Batch, linkType string, createdAt time.Time, orgID, token string)
- func AddDeleteAdminOrganizationReadModelQuery(batch *gocql.Batch, state AdminOrganizationProjectionState)
- func AddDeleteAdminOrganizationStatusProjectionEntryQuery(batch *gocql.Batch, state AdminOrganizationProjectionState)
- func AddDeleteAdminUserReadModelQuery(batch *gocql.Batch, state AdminUserProjectionState)
- func AddDeleteAdminUserStatusProjectionEntryQuery(batch *gocql.Batch, state AdminUserProjectionState)
- func AddDeleteBlockGCCandidateDiscoveryQuery(batch *gocql.Batch, orgID, blockID string, candidateAt time.Time)
- func AddDeleteDeletedAdminLibraryReadModelFallbackQueries(session *gocql.Session, batch *gocql.Batch, ...) error
- func AddDeleteDeletedAdminLibraryReadModelQuery(batch *gocql.Batch, row AdminDeletedLibraryProjectionRow)
- func AddDeleteDeletedUserDiscoveryQuery(batch *gocql.Batch, orgID, userID string, deletedAt time.Time)
- func AddDeleteFailedItemExpiryQuery(batch *gocql.Batch, orgID string, failedAt time.Time, itemType, itemID string, ...)
- func AddDeleteLibraryPolicyQuery(batch *gocql.Batch, policyType, orgID, libraryID string)
- func AddDeletePendingPublishedFSObjectOwnerQueries(batch *gocql.Batch, repoID, fsID, ownerID string, createdAt time.Time)
- func AddDeleteProvisionalBlockRefExpiryDiscoveryQuery(batch *gocql.Batch, orgID, blockID, referrer string, expiresAt time.Time)
- func AddDeleteShareExpiryQuery(batch *gocql.Batch, shareID string, expiresAt time.Time, ...)
- func AddDeleteShareLinkExpiryQuery(batch *gocql.Batch, token string, expiresAt time.Time)
- func AddDeleteShareReadModelQuery(batch *gocql.Batch, row ShareReadModelRow)
- func AddPublishAttemptReferences(database *DB, orgID, repoID, attemptID string, blockIDs []string) error
- func AddRefreshAdminLibraryReadModelQueries(batch *gocql.Batch, row AdminLibraryProjectionRow, ...)
- func AddUpdateAdminLinkActiveQuery(batch *gocql.Batch, linkType string, createdAt time.Time, orgID, token string, ...)
- func AddUpdateAdminLinkCountersQuery(batch *gocql.Batch, linkType string, createdAt time.Time, orgID, token string, ...)
- func AddUpsertAdminGroupReadModelQuery(batch *gocql.Batch, row AdminGroupProjectionRow)
- func AddUpsertAdminLibraryReadModelQuery(batch *gocql.Batch, row AdminLibraryProjectionRow)
- func AddUpsertAdminLinkReadModelQuery(batch *gocql.Batch, ...)
- func AddUpsertAdminOrganizationReadModelQuery(batch *gocql.Batch, row AdminOrganizationProjectionRow)
- func AddUpsertAdminUserReadModelQuery(batch *gocql.Batch, row AdminUserProjectionRow)
- func AddUpsertBlockGCCandidateDiscoveryQuery(batch *gocql.Batch, orgID, blockID, storageClass string, candidateAt time.Time)
- func AddUpsertDeletedUserDiscoveryQuery(batch *gocql.Batch, orgID, userID string, deletedAt time.Time)
- func AddUpsertFailedItemExpiryQuery(batch *gocql.Batch, orgID string, failedAt time.Time, itemType, itemID string, ...)
- func AddUpsertLibraryPolicyQuery(batch *gocql.Batch, policyType, orgID, libraryID string, days int, ...)
- func AddUpsertPendingPublishedFSObjectOwnerQueries(batch *gocql.Batch, repoID, fsID, ownerID string, createdAt time.Time, ...)
- func AddUpsertProvisionalBlockRefExpiryDiscoveryQuery(batch *gocql.Batch, orgID, blockID, referrer, storageClass string, ...)
- func AddUpsertShareExpiryQuery(batch *gocql.Batch, ...)
- func AddUpsertShareLinkExpiryQuery(batch *gocql.Batch, token, orgID, libraryID, createdBy, linkType string, ...)
- func AddUpsertShareReadModelQuery(batch *gocql.Batch, row ShareReadModelRow)
- func AdjustAdminOrgLinkCount(session *gocql.Session, orgID, linkType string, delta int) error
- func AdminGroupBucketDay(createdAt time.Time) string
- func AdminLibraryBucketDay(createdAt time.Time) string
- func AdminLinkBucketDay(createdAt time.Time) string
- func AdminLinkObjName(filePath, repoName string) string
- func AdminOrgLinkCountDelta(delta int) int
- func AdminOrganizationBucketDay(createdAt time.Time) string
- func AdminUserBucketDay(createdAt time.Time) string
- func BestEffortAdjustAdminOrgLinkCount(session *gocql.Session, orgID, linkType string, delta int)
- func BlockReferrerForFSObject(libraryID, fsID string) string
- func BlockReferrerForPublishAttempt(attemptID string) string
- func BlockReferrerForUpload(operationID string) string
- func CanonicalBlockRepresentationIDForLibrary(libraryID string, encrypted bool, stored string) (string, error)
- func ClearLocksUnder(session *gocql.Session, repoID, root, userID string) error
- func CountAdminOrgLinks(session *gocql.Session, orgID, linkType string) (int, error)
- func CountAdminOrganizationUsers(session *gocql.Session, orgID string) (int, error)
- func CreateOrganizationWithUsersAndReadModels(session *gocql.Session, org AdminOrganizationWriteSpec, ...) error
- func CreateUserWithLookupsAndReadModels(session *gocql.Session, user AdminUserWriteSpec) error
- func DeleteAdminGroupReadModel(session *gocql.Session, row AdminGroupProjectionRow) error
- func DeleteAdminOrganizationReadModel(session *gocql.Session, orgID string) error
- func DeleteAdminUserReadModel(session *gocql.Session, userID string) error
- func EncryptedLibraryBlockRepresentationID(libraryID string) string
- func FileLockedByOther(session *gocql.Session, repoID, path, userID string) (blocked bool, ownerID string, err error)
- func GCDiscoveryBucket(parts ...string) int
- func GCProjectionDateString(ts time.Time) string
- func GCProjectionUTCDate(ts time.Time) time.Time
- func InvalidateAdminOrgLinkCount(session *gocql.Session, orgID, linkType string) error
- func IsAdminLinkType(linkType string) bool
- func IsCanonicalBlockRepresentationForLibrary(representationID string, libraryID uuid.UUID) bool
- func IsCanonicalBlockRepresentationID(representationID string) bool
- func IsSHA1BlockID(id string) bool
- func IsSHA256BlockID(id string) bool
- func ListAdminGroupBucketDays(session *gocql.Session) ([]string, error)
- func ListAdminLibraryBucketDays(session *gocql.Session) ([]string, error)
- func NewLibraryBlockRepresentationID(libraryID string, encrypted bool) string
- func NormalizeBlockID(id string) string
- func NormalizeBlockIDs(blockIDs []string) []string
- func ParseGCProjectionDate(value string) (time.Time, error)
- func PromotePublishAttemptReferences(database *DB, orgID, attemptID string, blockIDs []string, ...) error
- func RefreshAdminOrgLinkCount(session *gocql.Session, orgID, linkType string) (int, error)
- func ReleaseFileLock(session *gocql.Session, repoID, path, userID string) (released bool, ownerID string, err error)
- func RelocateLocksUnder(session *gocql.Session, repoID, oldRoot, newRoot, userID string) error
- func RemovePublishAttemptReferences(database *DB, orgID, attemptID string, blockIDs []string) error
- func ReplaceAdminOrgLinkCount(session *gocql.Session, orgID, linkType string, exactCount int) error
- func ResolveAdminGroupOwnerFields(session *gocql.Session, orgID, creatorID string) (string, string)
- func ResolveAdminLibraryOwnerFields(session *gocql.Session, orgID, ownerID string) (string, string)
- func ResolveAdminLinkDisplayFields(session *gocql.Session, orgID, libraryID, filePath, createdBy string) (string, string, string, string)
- func ResolveAdminOrganizationOwnerFields(session *gocql.Session, orgID string) (string, string)
- func ResolveBlockRepresentationID(session *gocql.Session, orgID, libraryID string) (string, error)
- func ResolveBlockRepresentationIDByLibraryID(session *gocql.Session, libraryID string) (string, error)
- func ResolveBlockRepresentationIDContext(ctx context.Context, session *gocql.Session, orgID, libraryID string) (string, error)
- func ResolveBlockRepresentationIDForDelete(session *gocql.Session, orgID, libraryID string) (string, error)
- func ScheduleAdminOrgLinkCountRefresh(session *gocql.Session, orgID, linkType string)
- func StagePublishAttemptReferences(database *DB, orgID, repoID, attemptID string, blockIDs []string, ...) ([]string, error)
- func StagedBlockBucket(blockID string, bucketCount int) int
- func SubtreeLockedByOther(session *gocql.Session, repoID, targetPath, userID string) (blocked bool, ownerID string, err error)
- func SyncAdminGroupReadModel(session *gocql.Session, orgID, groupID string) error
- func SyncAdminLibraryReadModel(session *gocql.Session, orgID, libraryID string) error
- func SyncAdminLinkReadModel(session *gocql.Session, token string) error
- func SyncAdminOrganizationReadModel(session *gocql.Session, orgID string) error
- func SyncAdminUserReadModel(session *gocql.Session, orgID, userID string) error
- func SyncShareReadModel(session *gocql.Session, libraryID, shareID string) error
- func UpdateOrganizationLifecycleAndReadModel(session *gocql.Session, orgID string, next AdminOrganizationLifecycleUpdate) error
- func UpdateUserAndAdminReadModels(session *gocql.Session, orgID, userID string, next AdminUserUpdateSpec) error
- func UpdateUserRoleAndAdminReadModels(session *gocql.Session, orgID, userID, role string) error
- func UpdateUserRoleAttachOIDCIdentityAndAdminReadModels(session *gocql.Session, orgID, userID, email, issuer, oidcSub, role string) error
- func ValidateBlockRepresentationID(representationID string) error
- type AccessToken
- type AdminDeletedLibraryProjectionRow
- func ListDeletedAdminLibraryRowsByOrg(session *gocql.Session, orgID string) ([]AdminDeletedLibraryProjectionRow, error)
- func ReadDeletedAdminLibraryProjectionRow(session *gocql.Session, orgID, libraryID string) (AdminDeletedLibraryProjectionRow, error)
- func ReconcileDeletedAdminLibraryRowsByOrg(session *gocql.Session, orgID string) ([]AdminDeletedLibraryProjectionRow, int, error)
- type AdminGroupProjectionRow
- type AdminLibraryProjectionRow
- func ListAdminGlobalLibraryRows(session *gocql.Session) ([]AdminLibraryProjectionRow, error)
- func ListAdminOrgLibraryRows(session *gocql.Session, orgID string) ([]AdminLibraryProjectionRow, error)
- func ListAdminOwnerLibraryRows(session *gocql.Session, orgID, ownerID string) ([]AdminLibraryProjectionRow, error)
- func ReadAdminLibraryProjectionRow(session *gocql.Session, orgID, libraryID string) (AdminLibraryProjectionRow, error)
- type AdminOrgLinkIndexRow
- type AdminOrganizationLifecycleUpdate
- type AdminOrganizationProjectionRow
- func BuildAdminOrganizationProjectionRowForNewUser(session *gocql.Session, orgID, email, name, role string) (AdminOrganizationProjectionRow, error)
- func BuildAdminOrganizationProjectionRowForUpdatedUser(session *gocql.Session, orgID, updatedUserID, updatedName, updatedRole string) (AdminOrganizationProjectionRow, error)
- func ListAdminOrganizationRows(session *gocql.Session, statusFilter string) ([]AdminOrganizationProjectionRow, error)
- func ReadAdminOrganizationProjectionRow(session *gocql.Session, orgID string) (AdminOrganizationProjectionRow, error)
- type AdminOrganizationProjectionState
- type AdminOrganizationWriteSpec
- type AdminUserProjectionRow
- type AdminUserProjectionState
- type AdminUserUpdateSpec
- type AdminUserWriteSpec
- type AppliedMigration
- type BlockAuthorityRead
- type BlockIDResolver
- type BlockPhysicalLocation
- type BlockRepairAuthorityOutcome
- type BlockReuseDecision
- type BlockReuseProbe
- type BlockS3OrphanInfo
- type BlockStorageLocation
- type BlockUploadSession
- type BlockUploadSessionAdmission
- type DB
- func (db *DB) AddBlockReference(orgID, blockID, referrer, libraryID string, ttlSeconds int) error
- func (db *DB) AddProvisionalBlockReferenceWithExpiry(orgID, blockID, referrer, libraryID, storageClass string, expiresAt time.Time) error
- func (db *DB) BlockDeleteFenceActive(orgID, blockID string) (bool, error)
- func (db *DB) BlockHasReferences(orgID, blockID string) (bool, error)
- func (db *DB) BlockHasReferencesGlobal(orgID, blockID string) (bool, error)
- func (db *DB) BlockHasReferrer(orgID, blockID, referrer string) (bool, error)
- func (db *DB) BlockReferenceExists(orgID, blockID, referrer string) (bool, error)
- func (db *DB) ClaimBlockUploadSessionForCommit(sessionID, manifestDigest string) (bool, error)
- func (db *DB) CleanupCommittedBlockUploadSessionCaps(s BlockUploadSession) error
- func (db *DB) Close()
- func (db *DB) CountSessionStagedBlocksInBucket(sessionID string, bucket, limit int) (int, error)
- func (db *DB) CreateAdmittedBlockUploadSession(orgID, userID, repoID, parentDir string, admission BlockUploadSessionAdmission, ...) (BlockUploadSession, error)
- func (db *DB) DeleteClaimedBlockStub(orgID, blockID, claimID string) (bool, error)
- func (db *DB) DeletePendingPublishedFSObjectOwner(repoID, fsID, ownerID string, createdAt time.Time) error
- func (db *DB) DeleteProvisionalBlockReferenceExpiry(orgID, blockID, referrer string, expiresAt time.Time) error
- func (db *DB) GetBlockIDMapping(orgID, representationID, externalID string) (internalID string, ok bool, err error)
- func (db *DB) GetBlockIDMappingContext(ctx context.Context, orgID, representationID, externalID string) (internalID string, ok bool, err error)
- func (db *DB) GetBlockS3OrphanInfo(orgID, blockID string) (BlockS3OrphanInfo, bool, error)
- func (db *DB) GetBlockStorageLocation(ctx context.Context, orgID, blockID string) (BlockStorageLocation, bool, error)
- func (db *DB) GetBlockUploadSession(sessionID string) (BlockUploadSession, bool, error)
- func (db *DB) InstallBlockMetadata(ctx context.Context, orgID, representationID, blockID, sha1 string, ...) InstallBlockMetadataResult
- func (db *DB) ListBlockReferrers(orgID, blockID string) ([]string, error)
- func (db *DB) ListPendingPublishedFSObjectOwnersByDay(day time.Time, bucket int) ([]PendingPublishedFSObjectOwner, error)
- func (db *DB) LoadPendingPublishedFSObjectOwner(repoID, fsID, ownerID string) (PendingPublishedFSObjectOwner, error)
- func (db *DB) MarkBlockUploadSessionCommitted(s BlockUploadSession, ...) error
- func (db *DB) Migrate() error
- func (db *DB) PendingPublishedFSObjectOwnerExists(repoID, fsID string) (bool, error)
- func (db *DB) Ping(ctx context.Context) error
- func (db *DB) ProbeBlockReuse(orgID, blockID string) (BlockReuseProbe, error)
- func (db *DB) ReleaseBlockUploadSessionCommit(sessionID string) error
- func (db *DB) RemoveBlockReference(orgID, blockID, referrer string) error
- func (db *DB) RepairBlockMetadataIfCurrent(orgID, representationID, blockID, sha1 string, sizeBytes int, ...) error
- func (db *DB) RepairReleasedBlockStub(orgID, blockID string) (bool, error)
- func (db *DB) ReserveSessionStagedBlock(sessionID string, bucket int, blockID string, sizeBytes int64) error
- func (db *DB) SeedDatabase(cfg *config.Config, devMode bool, firstSuperAdminEmail string) error
- func (db *DB) Session() *gocql.Session
- func (db *DB) SessionStagedBlockExists(sessionID string, bucket int, blockID string) (bool, error)
- func (db *DB) UpsertPendingPublishedFSObjectOwner(repoID, fsID, ownerID string, createdAt time.Time, orgID, attemptID string, ...) error
- func (db *DB) ValidateBlockRepairAuthority(orgID, blockID string, expected BlockPhysicalLocation) (BlockRepairAuthorityOutcome, error)
- func (db *DB) ValidateDestructiveGCTopology() error
- func (db *DB) WriteBlockIDMapping(orgID, representationID, externalID, internalID string, createdAt time.Time) error
- func (db *DB) WriteVerifiedWebBlockMapping(orgID, representationID, externalID, internalID string, createdAt time.Time) error
- type FileLock
- type InstallBlockMetadataOutcome
- type InstallBlockMetadataResult
- type LibraryState
- func ReadLibraryState(session *gocql.Session, orgID, libraryID string) (LibraryState, error)
- func ReadLibraryStateContext(ctx context.Context, session *gocql.Session, orgID, libraryID string) (LibraryState, error)
- func ReadLiveLibraryState(session *gocql.Session, orgID, libraryID string) (LibraryState, error)
- func ReadLiveLibraryStateContext(ctx context.Context, session *gocql.Session, orgID, libraryID string) (LibraryState, error)
- func ResolveLiveLibraryStateByID(session *gocql.Session, libraryID string) (LibraryState, error)
- type LockAcquireResult
- type MigrationFile
- type MigrationStatus
- type Migrator
- type PendingPublishedFSObjectOwner
- type RepoLockedFile
- type ShareReadModelRow
- type TokenCreator
- type TokenStore
- func (ts *TokenStore) CreateDownloadToken(orgID, repoID, path, userID string) (string, error)
- func (ts *TokenStore) CreateLinkDownloadToken(orgID, repoID, path, userID, sourceID string) (string, error)
- func (ts *TokenStore) CreateLinkUploadToken(orgID, repoID, path, userID, sourceID string) (string, error)
- func (ts *TokenStore) CreateSyncToken(orgID, repoID, userID string) (string, error)
- func (ts *TokenStore) CreateToken(tokenType TokenType, orgID, repoID, path, userID, source string) (*AccessToken, error)
- func (ts *TokenStore) CreateUpdateToken(orgID, repoID, path, userID string) (string, error)
- func (ts *TokenStore) CreateUploadToken(orgID, repoID, path, userID string) (string, error)
- func (ts *TokenStore) DeleteToken(tokenStr string) error
- func (ts *TokenStore) GetToken(tokenStr string, expectedType TokenType) (*AccessToken, bool)
- type TokenType
Constants ¶
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" )
const ( GCLibraryPolicyVersionTTL = "version_ttl" GCLibraryPolicyAutoDelete = "auto_delete" )
const BlockFenceReadConsistency = gocql.LocalQuorum
BlockFenceReadConsistency is the weakest read that still intersects an EACH_QUORUM fence publication in the reader's own DC.
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.
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.
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.
const GCDiscoveryBucketCount = 32
const PendingPublishedFSObjectOwnerTTLSeconds = 35 * 24 * 60 * 60 // 35d
const PlainBlockRepresentationID = "plain:v1"
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 ¶
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.
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.
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.
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.
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.
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.
ErrFileLockStatusUnavailable indicates the lock table could not be queried, so callers must fail closed instead of assuming the file is unlocked.
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.
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 AddDeleteAdminLibraryReadModelQuery ¶
func AddDeleteAdminLibraryReadModelQuery(batch *gocql.Batch, row AdminLibraryProjectionRow)
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 AddDeleteDeletedAdminLibraryReadModelQuery ¶
func AddDeleteDeletedAdminLibraryReadModelQuery(batch *gocql.Batch, row AdminDeletedLibraryProjectionRow)
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 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 AddUpsertShareReadModelQuery ¶
func AddUpsertShareReadModelQuery(batch *gocql.Batch, row ShareReadModelRow)
func AdjustAdminOrgLinkCount ¶
func AdminGroupBucketDay ¶
func AdminLibraryBucketDay ¶
func AdminLinkBucketDay ¶
func AdminLinkObjName ¶
func AdminOrgLinkCountDelta ¶
func AdminUserBucketDay ¶
func BlockReferrerForFSObject ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 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 GCProjectionDateString ¶
func IsAdminLinkType ¶
func IsCanonicalBlockRepresentationID ¶
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 ¶
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 NewLibraryBlockRepresentationID ¶
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 ¶
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 ¶
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 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 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 ¶
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 ¶
RemovePublishAttemptReferences removes temporary pub:<attempt> references. It is safe to call repeatedly and collapses repeated delete errors with errors.Join.
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 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 ¶
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 SyncAdminLinkReadModel ¶
func SyncAdminUserReadModel ¶
func SyncShareReadModel ¶
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
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)
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 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 ¶
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 AdminUserWriteSpec ¶
type AppliedMigration ¶
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 ¶
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 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 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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) CountSessionStagedBlocksInBucket ¶
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) DeletePendingPublishedFSObjectOwner ¶
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 ¶
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) 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) PendingPublishedFSObjectOwnerExists ¶
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 ¶
ReleaseBlockUploadSessionCommit reverts a commit claim so a retry can proceed after a failed finalize. Idempotent.
func (*DB) RemoveBlockReference ¶
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) 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 ¶
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) SessionStagedBlockExists ¶
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) 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 ¶
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 ¶
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 ¶
NewMigrator creates a Migrator backed by the given Cassandra session.
func (*Migrator) Check ¶
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 ¶
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 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 {
}
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" )
Source Files
¶
- admin_group_read_models.go
- admin_identity_read_models.go
- admin_identity_write_helpers.go
- admin_library_read_models.go
- admin_link_read_models.go
- block_references.go
- block_representation.go
- block_storage_location.go
- block_upload_sessions.go
- block_upload_staging.go
- db.go
- file_locks.go
- gc_projection_write_helpers.go
- library_state.go
- migrator.go
- pending_published_fs_object_owners.go
- provisional_block_ref_expiry.go
- seed.go
- share_read_models.go
- tokens.go