Documentation
¶
Index ¶
Constants ¶
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.
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.
type ArtifactShare ¶
type ArtifactShare struct {
ExpiresAt *time.Time `json:"expires_at,omitempty"`
// RevokedAt withdraws the link. A revoked share resolves to the same 404 a
// never-existed token gives.
}
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.
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 {
}
CreateShareInput is everything the store needs to record one link. The service has already generated the token and hashed it.
type Expired ¶
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 Ref ¶
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 {
}
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 {
// 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)
// revoked and expired ones for the management view.
ListArtifactShares(ctx context.Context, artifactID string) ([]ArtifactShare, error)
// 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)
// 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.