distributable, please
Some checks failed
CI / Rustfmt (push) Successful in 22s
CI / Cargo Audit & Deny (push) Successful in 36s
CI / Security Blocking Checks (push) Successful in 6s
CI / Web Blocking Checks (push) Successful in 53s
CI / Web Advisory Checks (push) Successful in 34s
Publish Images / Resolve Publish Metadata (push) Successful in 1s
CI / Security Advisory Checks (push) Successful in 38s
CI / Clippy (push) Successful in 2m7s
Publish Images / Publish Docker Dist Bundle (push) Failing after 19s
Publish Images / Publish web (amd64) (push) Successful in 49s
Publish Images / Publish web (arm64) (push) Successful in 3m31s
CI / Tests (push) Successful in 8m48s
Publish Images / Build Rust Bundles (amd64) (push) Successful in 12m42s
Publish Images / Build Rust Bundles (arm64) (push) Successful in 12m19s
Publish Images / Publish agent (amd64) (push) Successful in 26s
Publish Images / Publish api (amd64) (push) Successful in 38s
Publish Images / Publish notifier (amd64) (push) Successful in 42s
Publish Images / Publish executor (amd64) (push) Successful in 46s
Publish Images / Publish agent (arm64) (push) Successful in 56s
Publish Images / Publish api (arm64) (push) Successful in 1m52s
Publish Images / Publish executor (arm64) (push) Successful in 2m2s
Publish Images / Publish notifier (arm64) (push) Successful in 2m3s
Publish Images / Publish manifest attune/agent (push) Successful in 6s
Publish Images / Publish manifest attune/api (push) Successful in 11s
Publish Images / Publish manifest attune/executor (push) Successful in 10s
Publish Images / Publish manifest attune/notifier (push) Successful in 8s
Publish Images / Publish manifest attune/web (push) Successful in 8s
Some checks failed
CI / Rustfmt (push) Successful in 22s
CI / Cargo Audit & Deny (push) Successful in 36s
CI / Security Blocking Checks (push) Successful in 6s
CI / Web Blocking Checks (push) Successful in 53s
CI / Web Advisory Checks (push) Successful in 34s
Publish Images / Resolve Publish Metadata (push) Successful in 1s
CI / Security Advisory Checks (push) Successful in 38s
CI / Clippy (push) Successful in 2m7s
Publish Images / Publish Docker Dist Bundle (push) Failing after 19s
Publish Images / Publish web (amd64) (push) Successful in 49s
Publish Images / Publish web (arm64) (push) Successful in 3m31s
CI / Tests (push) Successful in 8m48s
Publish Images / Build Rust Bundles (amd64) (push) Successful in 12m42s
Publish Images / Build Rust Bundles (arm64) (push) Successful in 12m19s
Publish Images / Publish agent (amd64) (push) Successful in 26s
Publish Images / Publish api (amd64) (push) Successful in 38s
Publish Images / Publish notifier (amd64) (push) Successful in 42s
Publish Images / Publish executor (amd64) (push) Successful in 46s
Publish Images / Publish agent (arm64) (push) Successful in 56s
Publish Images / Publish api (arm64) (push) Successful in 1m52s
Publish Images / Publish executor (arm64) (push) Successful in 2m2s
Publish Images / Publish notifier (arm64) (push) Successful in 2m3s
Publish Images / Publish manifest attune/agent (push) Successful in 6s
Publish Images / Publish manifest attune/api (push) Successful in 11s
Publish Images / Publish manifest attune/executor (push) Successful in 10s
Publish Images / Publish manifest attune/notifier (push) Successful in 8s
Publish Images / Publish manifest attune/web (push) Successful in 8s
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
-- Migration: Artifact Content System
|
||||
-- Description: Enhances the artifact table with content fields (name, description,
|
||||
-- content_type, size_bytes, execution link, structured data, visibility)
|
||||
-- and creates the artifact_version table for versioned file/data storage.
|
||||
--
|
||||
-- The artifact table now serves as the "header" for a logical artifact,
|
||||
-- while artifact_version rows hold the actual immutable content snapshots.
|
||||
-- Progress-type artifacts store their live state directly in artifact.data
|
||||
-- (append-style updates without creating new versions).
|
||||
--
|
||||
-- Version: 20250101000010
|
||||
|
||||
-- ============================================================================
|
||||
-- ENHANCE ARTIFACT TABLE
|
||||
-- ============================================================================
|
||||
|
||||
-- Human-readable name (e.g. "Build Log", "Test Results")
|
||||
ALTER TABLE artifact ADD COLUMN IF NOT EXISTS name TEXT;
|
||||
|
||||
-- Optional longer description
|
||||
ALTER TABLE artifact ADD COLUMN IF NOT EXISTS description TEXT;
|
||||
|
||||
-- MIME content type (e.g. "application/json", "text/plain", "image/png")
|
||||
ALTER TABLE artifact ADD COLUMN IF NOT EXISTS content_type TEXT;
|
||||
|
||||
-- Total size in bytes of the latest version's content (NULL for progress artifacts)
|
||||
ALTER TABLE artifact ADD COLUMN IF NOT EXISTS size_bytes BIGINT;
|
||||
|
||||
-- Execution that produced/owns this artifact (plain BIGINT, no FK — execution is a hypertable)
|
||||
ALTER TABLE artifact ADD COLUMN IF NOT EXISTS execution BIGINT;
|
||||
|
||||
-- Structured data for progress-type artifacts and small structured payloads.
|
||||
-- Progress artifacts append entries here; file artifacts may store parsed metadata.
|
||||
ALTER TABLE artifact ADD COLUMN IF NOT EXISTS data JSONB;
|
||||
|
||||
-- Visibility: public artifacts are viewable by all authenticated users;
|
||||
-- private artifacts are restricted based on the artifact's scope/owner.
|
||||
-- The scope (identity, action, pack, etc.) + owner fields define who can access
|
||||
-- a private artifact. Full RBAC enforcement is deferred — for now the column
|
||||
-- enables filtering and is available for future permission checks.
|
||||
ALTER TABLE artifact ADD COLUMN IF NOT EXISTS visibility artifact_visibility_enum NOT NULL DEFAULT 'private';
|
||||
|
||||
-- New indexes for the added columns
|
||||
CREATE INDEX IF NOT EXISTS idx_artifact_execution ON artifact(execution);
|
||||
CREATE INDEX IF NOT EXISTS idx_artifact_name ON artifact(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_artifact_execution_type ON artifact(execution, type);
|
||||
CREATE INDEX IF NOT EXISTS idx_artifact_visibility ON artifact(visibility);
|
||||
CREATE INDEX IF NOT EXISTS idx_artifact_visibility_scope ON artifact(visibility, scope, owner);
|
||||
|
||||
-- Comments for new columns
|
||||
COMMENT ON COLUMN artifact.name IS 'Human-readable artifact name';
|
||||
COMMENT ON COLUMN artifact.description IS 'Optional description of the artifact';
|
||||
COMMENT ON COLUMN artifact.content_type IS 'MIME content type (e.g. application/json, text/plain)';
|
||||
COMMENT ON COLUMN artifact.size_bytes IS 'Size of latest version content in bytes';
|
||||
COMMENT ON COLUMN artifact.execution IS 'Execution that produced this artifact (no FK — execution is a hypertable)';
|
||||
COMMENT ON COLUMN artifact.data IS 'Structured JSONB data for progress artifacts or metadata';
|
||||
COMMENT ON COLUMN artifact.visibility IS 'Access visibility: public (all users) or private (scope/owner-restricted)';
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- ARTIFACT_VERSION TABLE
|
||||
-- ============================================================================
|
||||
-- Each row is an immutable snapshot of artifact content. File-type artifacts get
|
||||
-- a new version on each upload; progress-type artifacts do NOT use versions
|
||||
-- (they update artifact.data directly).
|
||||
|
||||
CREATE TABLE artifact_version (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
|
||||
-- Parent artifact
|
||||
artifact BIGINT NOT NULL REFERENCES artifact(id) ON DELETE CASCADE,
|
||||
|
||||
-- Monotonically increasing version number within the artifact (1-based)
|
||||
version INTEGER NOT NULL,
|
||||
|
||||
-- MIME content type for this specific version (may differ from parent)
|
||||
content_type TEXT,
|
||||
|
||||
-- Size of the content in bytes
|
||||
size_bytes BIGINT,
|
||||
|
||||
-- Binary content (file uploads, DB-stored). NULL for file-backed versions.
|
||||
content BYTEA,
|
||||
|
||||
-- Structured content (JSON payloads, parsed results, etc.)
|
||||
content_json JSONB,
|
||||
|
||||
-- Relative path from artifacts_dir root for disk-stored content.
|
||||
-- When set, content BYTEA is NULL — file lives on shared volume.
|
||||
-- Pattern: {ref_slug}/v{version}.{ext}
|
||||
-- e.g., "mypack/build_log/v1.txt"
|
||||
file_path TEXT,
|
||||
|
||||
-- Free-form metadata about this version (e.g. commit hash, build number)
|
||||
meta JSONB,
|
||||
|
||||
-- Who or what created this version (identity ref, action ref, "system", etc.)
|
||||
created_by TEXT,
|
||||
|
||||
-- Immutable — no updated column
|
||||
created TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Unique constraint: one version number per artifact
|
||||
ALTER TABLE artifact_version
|
||||
ADD CONSTRAINT uq_artifact_version_artifact_version UNIQUE (artifact, version);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_artifact_version_artifact ON artifact_version(artifact);
|
||||
CREATE INDEX idx_artifact_version_artifact_version ON artifact_version(artifact, version DESC);
|
||||
CREATE INDEX idx_artifact_version_created ON artifact_version(created DESC);
|
||||
CREATE INDEX idx_artifact_version_file_path ON artifact_version(file_path) WHERE file_path IS NOT NULL;
|
||||
|
||||
-- Comments
|
||||
COMMENT ON TABLE artifact_version IS 'Immutable content snapshots for artifacts (file uploads, structured data)';
|
||||
COMMENT ON COLUMN artifact_version.artifact IS 'Parent artifact this version belongs to';
|
||||
COMMENT ON COLUMN artifact_version.version IS 'Version number (1-based, monotonically increasing per artifact)';
|
||||
COMMENT ON COLUMN artifact_version.content_type IS 'MIME content type for this version';
|
||||
COMMENT ON COLUMN artifact_version.size_bytes IS 'Size of content in bytes';
|
||||
COMMENT ON COLUMN artifact_version.content IS 'Binary content (file data)';
|
||||
COMMENT ON COLUMN artifact_version.content_json IS 'Structured JSON content';
|
||||
COMMENT ON COLUMN artifact_version.meta IS 'Free-form metadata about this version';
|
||||
COMMENT ON COLUMN artifact_version.created_by IS 'Who created this version (identity ref, action ref, system)';
|
||||
COMMENT ON COLUMN artifact_version.file_path IS 'Relative path from artifacts_dir root for disk-stored content. When set, content BYTEA is NULL — file lives on shared volume.';
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- HELPER FUNCTION: next_artifact_version
|
||||
-- ============================================================================
|
||||
-- Returns the next version number for an artifact (MAX(version) + 1, or 1 if none).
|
||||
|
||||
CREATE OR REPLACE FUNCTION next_artifact_version(p_artifact_id BIGINT)
|
||||
RETURNS INTEGER AS $$
|
||||
DECLARE
|
||||
v_next INTEGER;
|
||||
BEGIN
|
||||
SELECT COALESCE(MAX(version), 0) + 1
|
||||
INTO v_next
|
||||
FROM artifact_version
|
||||
WHERE artifact = p_artifact_id;
|
||||
|
||||
RETURN v_next;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
COMMENT ON FUNCTION next_artifact_version IS 'Returns the next version number for the given artifact';
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- RETENTION ENFORCEMENT FUNCTION
|
||||
-- ============================================================================
|
||||
-- Called after inserting a new version to enforce the artifact retention policy.
|
||||
-- For 'versions' policy: deletes oldest versions beyond the limit.
|
||||
-- Time-based policies (days/hours/minutes) are handled by a scheduled job (not this trigger).
|
||||
|
||||
CREATE OR REPLACE FUNCTION enforce_artifact_retention()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
v_policy artifact_retention_enum;
|
||||
v_limit INTEGER;
|
||||
v_count INTEGER;
|
||||
BEGIN
|
||||
SELECT retention_policy, retention_limit
|
||||
INTO v_policy, v_limit
|
||||
FROM artifact
|
||||
WHERE id = NEW.artifact;
|
||||
|
||||
IF v_policy = 'versions' AND v_limit > 0 THEN
|
||||
-- Count existing versions
|
||||
SELECT COUNT(*) INTO v_count
|
||||
FROM artifact_version
|
||||
WHERE artifact = NEW.artifact;
|
||||
|
||||
-- If over limit, delete the oldest ones
|
||||
IF v_count > v_limit THEN
|
||||
DELETE FROM artifact_version
|
||||
WHERE id IN (
|
||||
SELECT id
|
||||
FROM artifact_version
|
||||
WHERE artifact = NEW.artifact
|
||||
ORDER BY version ASC
|
||||
LIMIT (v_count - v_limit)
|
||||
);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Update parent artifact size_bytes with the new version's size
|
||||
UPDATE artifact
|
||||
SET size_bytes = NEW.size_bytes,
|
||||
content_type = COALESCE(NEW.content_type, content_type)
|
||||
WHERE id = NEW.artifact;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trg_enforce_artifact_retention
|
||||
AFTER INSERT ON artifact_version
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION enforce_artifact_retention();
|
||||
|
||||
COMMENT ON FUNCTION enforce_artifact_retention IS 'Enforces version-count retention policy and syncs size to parent artifact';
|
||||
Reference in New Issue
Block a user