internal/infra/pg/artifactstore.go
internal/infra/pg · 355 lines · 22 declarations · source
Declarations
const MaxArtifactBytes
const MaxArtifactBytes = 512 << 10
var ErrInvalidArtifact, ErrArtifactNotFound, ErrArtifactConflict and 1 more
var (
ErrInvalidArtifact = errors.New("invalid artifact request")
ErrArtifactNotFound = errors.New("artifact not found")
ErrArtifactConflict = errors.New("artifact version or identity conflict")
ErrArtifactCapacity = errors.New("artifact storage capacity exceeded")
)
type ArtifactStore
type ArtifactStore struct {
pool *pgxpool.Pool
schema Schema
}
func NewArtifactStore
func NewArtifactStore(pool *pgxpool.Pool, schema Schema) *ArtifactStore
type ArtifactPut
type ArtifactPut struct {
ID string `json:"id"`
ExpectedVersion string `json:"expected_version,omitempty"`
DataSubjectID string `json:"data_subject_id"`
Kind string `json:"kind"`
Name string `json:"name"`
Content ArtifactContent `json:"content"`
}
type ArtifactMetadata
type ArtifactMetadata struct {
ID string `json:"id"`
Version string `json:"version"`
DataSubjectID string `json:"data_subject_id"`
Kind string `json:"kind"`
Name string `json:"name"`
SourceObservationID string `json:"source_observation_id"`
Bytes int `json:"bytes"`
AuthoredBy string `json:"authored_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ExpiresAt time.Time `json:"expires_at"`
}
type ArtifactWrite
type ArtifactWrite struct {
ArtifactMetadata
Replayed bool `json:"replayed"`
}
type Artifact
type Artifact struct {
ArtifactMetadata
Content []byte `json:"content"`
}
type ArtifactPage
type ArtifactPage struct {
Artifacts []ArtifactMetadata `json:"artifacts"`
Next *RecordCursor `json:"next,omitempty"`
}
func artifactUUID
func artifactUUID(value string) (string, error)
func artifactDigest
func artifactDigest(r ArtifactPut) []byte
func artifactKey
func artifactKey(id string) [32]byte
func lockArtifact
func lockArtifact(ctx context.Context, tx pgx.Tx, schema Schema, scope, id string) error
const artifactColumns
const artifactColumns = `a.artifact_id::text,a.version::text,a.data_subject_id,a.kind,a.name,a.source_observation_id::text,
octet_length(a.content),a.authored_by::text,a.created_at,a.updated_at,o.retention_until`
const artifactFrom
const artifactFrom = ` FROM {schema}.agent_artifact a JOIN {schema}.observation o ON o.scope=a.scope AND o.observation_id=a.source_observation_id `
func artifactDest
func artifactDest(m *ArtifactMetadata) []any
func artifactFailure
func artifactFailure(err error) error
method ArtifactStore.Put
func (s *ArtifactStore) Put(ctx context.Context, scope, principal string, r ArtifactPut) (ArtifactWrite, error)
Put stores opaque bytes under one owner. Creation identities have erasure-safe tombstones; overwrites use versions and retain only the latest bytes, never an implicit content archive.
method ArtifactStore.Get
func (s *ArtifactStore) Get(ctx context.Context, scope, id, dataSubjectID string) (Artifact, error)
Get reads one artifact, optionally requiring it to belong to a named data subject.
Why the subject is a predicate and not a check afterwards
A credential opens a project, and a project holds every end user's objects. An application that serves many people through one credential therefore has nothing stopping it handing user A the object of user B — the credential permits it, so no amount of care in the application is a boundary, only a habit. Naming the subject puts the requirement in the statement that reads the row: a mismatch selects nothing, and the caller is told what it would be told about an object that does not exist, because to that caller it does not.
Absent, the behaviour is what it always was, which is what the frozen contract requires.
type ArtifactFilter
type ArtifactFilter struct {
Kind string
NamePrefix string
}
method ArtifactStore.List
func (s *ArtifactStore) List(ctx context.Context, scope, subject, after string, limit int, filters ...ArtifactFilter) (ArtifactPage, error)
method ArtifactStore.Delete
func (s *ArtifactStore) Delete(ctx context.Context, scope, principal, id, version, dataSubjectID string) error
Delete removes one artifact, optionally requiring it to belong to a named data subject. The subject is a predicate for the reason it is one on Get: deleting another person's object is the same mistake, already spent.