blob

package
v0.2.32-rc1 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2026 License: MPL-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package blob is the name layer of the runtime blob store behind the txco://blob/* ops: validation of blob names and filenames, the derived-name recipe for user uploads, the permission vocabulary attached to names, and the Index — the mutable name → sha256 pointer table — over the tenant KV store. Bytes themselves live in the content-addressed filecas (immutable, global by hash); this package never touches them.

Names identify mutable concepts. Hashes identify immutable facts.

Three planes, two of which are here: the CAS holds immutable values, the Index holds mutable application pointers (+ required permissions), and an application's provenance store (onepony's projections) hangs meaning off unnamed hashes. See docs/advanced/blobs.md.

Index

Constants

View Source
const (
	// MaxNameBytes bounds a full blob name. The KV-backed Index stores a name
	// as one key segment ("n:" + name) under the store's 256-byte segment cap,
	// so 250 leaves room; a derived name (<under>/<64 hex>) is ~80 bytes.
	MaxNameBytes = 250
	// MaxSegmentBytes bounds one '/'-separated segment of a name.
	MaxSegmentBytes = 128
	// MaxFilenameBytes bounds the verbatim `filename` metadata (UTF-8 bytes).
	MaxFilenameBytes = 255

	// IndexNamespace is the reserved KV namespace holding name rows
	// ("n:<name>"); ShaNamespace holds the tenant's sha ownership rows
	// ("s:<sha256>"). Both carry kv.ReservedNamespacePrefix, so author-facing
	// KV writers refuse them.
	IndexNamespace = "_txc.blob"
	ShaNamespace   = "_txc.blob.sha"
)
View Source
const CASRead = "blob:cas:read"

CASRead is the privileged capability for addressing bytes by sha256 directly — the infrastructure path (projection transforms re-reading artifacts). Without it the named, policy-enforced interface is the only door, so names are never a fence with an open gate.

View Source
const Domain = "blob"

Domain is the capability domain every blob requirement lives in.

Variables

This section is empty.

Functions

func Allowed

func Allowed(grants, required []string) bool

Allowed reports whether grants cover EVERY requirement (AND semantics: a name requiring two permissions needs both; authors express alternatives with wildcard grants). An empty requirement set is open within the tenant.

func CanReadByHash

func CanReadByHash(grants []string) bool

CanReadByHash reports whether grants include the privileged by-sha path.

func DefaultContentType

func DefaultContentType(nameOrFilename string) string

DefaultContentType guesses a content type from the extension of a name or filename, falling back to application/octet-stream. Used when a put or a seeded pack declares none.

func DerivedName

func DerivedName(under, filename string) (string, error)

DerivedName is the drag-and-drop address: <under>/<hex sha256(NFC(filename))>. It is a TOTAL function of any real filename — spaces, unicode, emoji, 200-byte titles — so no upload can fail on its name, while the same filename always derives the same name, which is what turns a re-upload into a REPLACE (the name repoints; the CAS keeps the old bytes as history). NFC normalisation is applied before hashing so a file dragged from a macOS Finder (NFD) and the same title typed on Linux (NFC) address one blob. The filename itself is stored verbatim (not normalised) as metadata.

func SubjectGrants

func SubjectGrants(audience string, grants []string) ([]string, error)

SubjectGrants builds the permission set an op holds from its declared context: an optional `audience` token (⇒ blob:<audience>:read) unioned with explicit `grants`. Either may be empty; the result is sorted and deduplicated so two ops declaring the same context compare equal.

func ValidFilename

func ValidFilename(s string) error

ValidFilename checks the verbatim `filename` metadata: non-empty, valid UTF-8, at most MaxFilenameBytes, no control characters. Anything else goes — the derived-name recipe (DerivedName) is what makes any real filename safe to address.

func ValidGrant

func ValidGrant(p string) error

ValidGrant checks a string an op may DECLARE as held: three non-empty segments, wildcards allowed ("*" alone and "admin:all" are accepted by the matcher too, but an op declaring them is a smell — keep grants concrete or blob-scoped).

func ValidName

func ValidName(name string) error

ValidName reports why name is not a well-formed blob name (nil = valid): '/'-separated segments matching [A-Za-z0-9._-]{1,128}; no empty, "." or ".." segments (so no leading/trailing '/'); no leading-'_' segment (reserved); at most MaxNameBytes in total.

func ValidRequirement

func ValidRequirement(p string) error

ValidRequirement checks a permission string a NAME may require: exactly blob:<x>:<y> with concrete (non-wildcard, non-empty) segments.

func ValidSha256

func ValidSha256(s string) bool

ValidSha256 reports whether s is a 64-char lowercase hex sha256 — the only hash form the CAS and the Index accept.

Types

type Index

type Index interface {
	GetName(ctx context.Context, tenant, name string) (NameRow, bool, error)
	PutName(ctx context.Context, tenant string, row NameRow) error
	DeleteName(ctx context.Context, tenant, name string) error
	ListNames(ctx context.Context, tenant string, opts ListOpts) (ListPage, error)

	GetSha(ctx context.Context, tenant, sha string) (ShaRow, bool, error)
	// PutShaIfAbsent records ownership once; created is false when the row
	// already existed (its FirstSeen is preserved).
	PutShaIfAbsent(ctx context.Context, tenant string, row ShaRow) (created bool, err error)
}

Index is the name plane: mutable pointers + the tenant's sha ownership. Every method is tenant-scoped by the trusted request tenant (the slug processor.TenantScope carries). Implementations need not be transactional — callers order writes CAS → sha row → name row so every crash point self-heals (see docs/advanced/blobs.md).

func NewKVIndex

func NewKVIndex(k *kvstore.KV) Index

NewKVIndex returns the KV-backed Index.

type ListOpts

type ListOpts struct {
	Prefix string
	After  string
	Limit  int
}

ListOpts windows a name listing: names with Prefix (byte-wise), sorted, strictly after the After cursor, at most Limit rows (Limit <= 0 = all — for internal callers; the op clamps). Every listing reads the tenant's whole index (the KV backing has no sub-namespace prefix scan); a SQL index can lift that behind the same interface.

type ListPage

type ListPage struct {
	Names []NameRow
	Next  string
}

ListPage is a window of rows (never nil) plus the cursor for the next call ("" when exhausted).

type NameRow

type NameRow struct {
	Name        string    `json:"-"`
	SHA256      string    `json:"sha256"`
	Size        int64     `json:"size"`
	ContentType string    `json:"content_type,omitempty"`
	Filename    string    `json:"filename,omitempty"`
	Permissions []string  `json:"permissions,omitempty"`
	SeededBy    string    `json:"seeded_by,omitempty"`
	SeededSHA   string    `json:"seeded_sha,omitempty"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
}

NameRow is one mutable name → sha pointer with its metadata. Name is the key (not stored in the value); Permissions are the strings an op must cover to touch the name at all (read, repoint, delete).

SeededBy / SeededSHA are the pack bookkeeping: the stack whose BLOBS/ tree last shipped this name, and the content it shipped. A runtime put keeps both (it only moves SHA256), so a seeded name that the runtime repointed is detectable as DRIFT — the same idea as a remote branch having moved past your last push. `txco data apply` refuses to push over drift unless forced; `txco data pull` brings the live content into the tree first.

func (NameRow) Drifted

func (r NameRow) Drifted() bool

Drifted reports whether a pack-seeded name was repointed at runtime since the pack last shipped it: the live content is no longer what the tree says.

type ShaRow

type ShaRow struct {
	SHA256      string    `json:"-"`
	Size        int64     `json:"size"`
	ContentType string    `json:"content_type,omitempty"`
	FirstSeen   time.Time `json:"first_seen"`
}

ShaRow is the tenant's OWNERSHIP record for a hash: it exists iff this tenant has put (or seeded) those bytes. It is the tenant fence over the global-by-hash CAS — `existed` on put and every by-sha read consult it, never the CAS itself, so one tenant can never learn whether another holds a given document.

Jump to

Keyboard shortcuts

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