artifact

package
v0.2.0-alpha.13 Latest Latest
Warning

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

Go to latest
Published: Sep 18, 2026 License: Apache-2.0 Imports: 2 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// SourceAgent is an agent that chose to publish a file. SourceID is
	// its run or session ID where one is known.
	SourceAgent = "agent"
	// SourceTaskRun is a worker's run output. SourceID is the task run.
	SourceTaskRun = "task_run"
	// SourceUserUpload is a member uploading a file directly.
	SourceUserUpload = "user_upload"
	// SourceSystem is BuildMax generating a file with no agent call.
	SourceSystem = "system"
)

Artifact source types record which operation produced the file. They are persisted, so they are permanent in the same way audit actions are.

View Source
const (
	CreatorUser   = "user"
	CreatorAgent  = "agent"
	CreatorWorker = "worker"
	CreatorSystem = "system"
)

Artifact creator kinds. These answer "what kind of actor", not "which user": automated work does not get a person's ID invented for it, which is the same rule the audit trail follows.

Variables

This section is empty.

Functions

This section is empty.

Types

type Artifact

type Artifact struct {
	ID        string `json:"id"`
	SpaceID   string `json:"space_id"`
	Filename  string `json:"filename"`
	MediaType string `json:"media_type"`
	SizeBytes int64  `json:"size_bytes"`
	SHA256    string `json:"sha256"`
	// StorageKey is where the object store put the bytes. It is never
	// serialized: an API response, tool output, trace, or audit event that
	// carried it would leak deployment layout and outlive the layout's freedom
	// to change.
	//
	// It is also the record of whether the bytes are still held. Every artifact
	// gets a key when it is created, so an empty one means retention has
	// reclaimed the object — see Purged. That is why the purge sweep clears it
	// rather than adding a column: "no key" and "nothing stored" are the same
	// fact, and two columns that must agree eventually will not.
	StorageKey    string `json:"-"`
	CreatedByType string `json:"created_by_type"`
	CreatedByID   string `json:"created_by_id"`
	SourceType    string `json:"source_type"`
	SourceID      string `json:"source_id,omitempty"`
	Title         string `json:"title,omitempty"`
	// DeletedAt tombstones the artifact. Metadata and content stop being
	// served the moment it is set; removing the object itself is a later,
	// separate step under retention policy.
	DeletedAt *time.Time `json:"deleted_at,omitempty"`
	// ExpiresAt tombstones the artifact once it passes, applied by the
	// retention sweep. An artifact with none is kept until somebody deletes it.
	ExpiresAt *time.Time `json:"expires_at,omitempty"`
	CreatedAt time.Time  `json:"created_at"`
}

Artifact is a durable file BuildMax holds on a space's behalf.

It is a first-class object rather than a by-product of whatever produced it: an agent, a background run, or a person uploading a file all create the same record, and the producer is kept as provenance rather than as a parent. That is the whole difference from the earlier artifact/artifact_item tables, which a task run owned and which migration 0001 removed. See docs/design/unified-artifacts.md.

Content is immutable. There is no update path: a changed file is a new Artifact, because a reference someone saved must not quietly come to mean something else.

func (*Artifact) Deleted

func (a *Artifact) Deleted() bool

Deleted reports whether the artifact has been tombstoned.

func (*Artifact) Purged

func (a *Artifact) Purged() bool

Purged reports whether retention has reclaimed the object.

Only meaningful for a tombstoned artifact: a live one always has its bytes.

type ArtifactShare

type ArtifactShare struct {
	ShareID       string `json:"share_id"`
	ArtifactID    string `json:"artifact_id"`
	SpaceID       string `json:"space_id"`
	CreatedByType string `json:"created_by_type"`
	CreatedByID   string `json:"created_by_id,omitempty"`
	// ExpiresAt bounds the link; nil is a link with no expiry (still revocable).
	ExpiresAt *time.Time `json:"expires_at,omitempty"`
	// RevokedAt withdraws the link. A revoked share resolves to the same 404 a
	// never-existed token gives.
	RevokedAt       *time.Time `json:"revoked_at,omitempty"`
	RetrievalCount  int64      `json:"retrieval_count"`
	LastRetrievedAt *time.Time `json:"last_retrieved_at,omitempty"`
	CreatedAt       time.Time  `json:"created_at"`
}

ArtifactShare is a revocable public link to one artifact.

It is a separate record, not a flag on the artifact: an artifact may carry more than one live link with different lifetimes, each revocable on its own, and the artifact row stays immutable. The link's secret is a high-entropy token held only as its SHA-256 — the plaintext is returned once at creation and never stored, the same pattern login codes and webhook keys follow. See docs/design/artifact-public-sharing-and-preview.md §5.

func (*ArtifactShare) Live

func (s *ArtifactShare) Live(now time.Time) bool

Live reports whether the share still grants access at now: not revoked, and not past its expiry. It says nothing about the artifact behind it — a caller resolving a link must also check the artifact is not tombstoned.

type CreateInput

type CreateInput struct {
	SpaceID       string
	ArtifactID    string
	Filename      string
	MediaType     string
	SizeBytes     int64
	SHA256        string
	StorageKey    string
	CreatedByType string
	CreatedByID   string
	SourceType    string
	SourceID      string
	Title         string
	ExpiresAt     *time.Time
}

CreateInput is everything the store needs to record one artifact. The caller has already stored the content and measured it.

type CreateShareInput

type CreateShareInput struct {
	ArtifactID    string
	SpaceID       string
	TokenSHA256   string
	CreatedByType string
	CreatedByID   string
	ExpiresAt     *time.Time
}

CreateShareInput is everything the store needs to record one link. The service has already generated the token and hashed it.

type Expired

type Expired struct {
	ArtifactID string
	SpaceID    string
}

Expired is an artifact the retention sweep tombstoned, named so the trail can say which one went.

type PurgeStore

type PurgeStore interface {
	// ExpireArtifacts tombstones live artifacts whose ExpiresAt has passed,
	// at most limit of them, and reports which.
	ExpireArtifacts(ctx context.Context, now time.Time, limit int) ([]Expired, error)
	// PurgeableArtifacts returns artifacts tombstoned at or before the cutoff
	// whose objects have not been reclaimed, at most limit of them.
	PurgeableArtifacts(ctx context.Context, before time.Time, limit int) ([]Purgeable, error)
	// MarkArtifactPurged clears the storage key, recording that the object is
	// gone. It reports whether it changed anything, so two sweeps racing on one
	// artifact count it once.
	MarkArtifactPurged(ctx context.Context, artifactID string) (bool, error)
}

PurgeStore applies retention: it tombstones what has expired and reclaims the objects of what is already tombstoned.

Narrow on purpose, in the same way audit's PruneStore is. Every other holder of a Store can tombstone but cannot reach an object removal, and nothing here names a particular artifact to destroy — a sweep takes a cutoff and a batch size, and marking one purged is only permitted to follow a removal the sweep just performed.

type Purgeable

type Purgeable struct {
	ArtifactID string
	SpaceID    string
	SizeBytes  int64
}

Purgeable is a tombstoned artifact whose bytes are still held.

type Ref

type Ref struct {
	SpaceID    string
	ArtifactID string
}

Ref names one artifact's content object.

It lives in core rather than beside either user because the service and the storage adapters both have to name it and neither may own it: a service that took this type from the storage package would depend on an implementation, and a storage adapter that took it from the service would depend on a caller.

SpaceID is here because it partitions the key space, not because callers address an artifact by space: an artifact is reached by its opaque ID, and the service that holds the record supplies the space it belongs to.

type ResolvedShare

type ResolvedShare struct {
	Share    ArtifactShare
	Artifact Artifact
}

ResolvedShare pairs a share with the artifact it points at, as one lookup returns them. Both are needed together: the share to check liveness and count a retrieval, the artifact to authorize and stream.

type ShareStore

type ShareStore interface {
	CreateArtifactShare(ctx context.Context, in CreateShareInput) (*ArtifactShare, error)
	// GetArtifactShareByTokenHash returns the share and its artifact by the
	// token's hash, or (nil, nil) when the token matches nothing. It does NOT
	// filter revoked, expired, or tombstoned: the caller applies liveness so a
	// public route answers every non-live case with the same 404, and a
	// management view can still show a revoked link. A tombstoned artifact is
	// returned like GetArtifact returns one.
	GetArtifactShareByTokenHash(ctx context.Context, tokenHash string) (*ResolvedShare, error)
	// ListArtifactShares returns an artifact's shares, newest first, including
	// revoked and expired ones for the management view.
	ListArtifactShares(ctx context.Context, artifactID string) ([]ArtifactShare, error)
	// RevokeArtifactShare marks a share revoked if it belongs to the artifact
	// and is not already revoked, reporting whether it changed anything so a
	// repeat revoke is distinguishable from a first.
	RevokeArtifactShare(ctx context.Context, artifactID, shareID string, revokedAt time.Time) (bool, error)
	// RecordArtifactShareRetrieval increments the count and stamps the time.
	// Best-effort telemetry: a failure must never block content delivery.
	RecordArtifactShareRetrieval(ctx context.Context, shareID string, at time.Time) error
}

ShareStore persists public share links.

Separate from Store because sharing is an additive capability layered over artifacts, not part of the artifact object itself. A deployment with the artifact capability but no share store simply cannot create public links.

type Store

type Store interface {
	CreateArtifact(ctx context.Context, in CreateInput) (*Artifact, error)
	// GetArtifact returns the artifact by its opaque ID, or (nil, nil) when there
	// is none. A tombstoned artifact is returned, not hidden: the caller has to
	// tell "never existed" from "deleted" to answer either one correctly.
	GetArtifact(ctx context.Context, artifactID string) (*Artifact, error)
	// ListArtifactsBySpace returns live artifacts newest first, with the total.
	ListArtifactsBySpace(ctx context.Context, spaceID string, limit, offset int) ([]Artifact, int, error)
	// ListArtifactsBySource returns live artifacts produced by any of the given
	// operations, newest first, keyed by source ID. It is how a work object
	// finds what its runs published without owning them.
	ListArtifactsBySource(ctx context.Context, sourceIDs []string) (map[string][]Artifact, error)
	// SoftDeleteArtifact tombstones the artifact and reports whether it changed
	// anything, so a repeat delete is distinguishable from a first one.
	SoftDeleteArtifact(ctx context.Context, artifactID string, deletedAt time.Time) (bool, error)
	// SpaceArtifactBytes sums what the space's live artifacts hold. Tombstoned
	// ones are excluded whether or not their objects have gone yet: the space
	// has given them up, and charging for storage the deployment has merely not
	// swept would make a quota depend on sweep timing.
	SpaceArtifactBytes(ctx context.Context, spaceID string) (int64, error)
}

Store persists artifact metadata.

It knows nothing about task runs, issues, or conversations. Provenance reaches it as two opaque strings, which is what keeps an artifact from acquiring an owner it should not have.

Jump to

Keyboard shortcuts

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