Artifacts
Overview
Artifacts are persistent files or directories produced by cikit workflow steps. They provide a common exchange mechanism between otherwise independent features.
A producer creates and registers an artifact:
producer
|
+-- file or directory
|
+-- env.copy_artifact(...)
|
+-- persistent output below PUBLIC_DIR
+-- ArtifactRegistry metadata
A consumer can later query the registry:
ArtifactRegistry
|
+-- report generator
+-- Sphinx preparation
+-- later workflow step
+-- project-specific logic
The consumer does not need to know where or how the artifact was originally created. It only needs the logical run, artifact type, or target name it is interested in.
Artifact Model
The Artifact object describes one registered artifact.
It contains six fields:
Field |
Meaning |
|---|---|
|
Absolute filesystem path of the registered file or directory. |
|
Final file or directory name used to identify the artifact. |
|
Semantic artifact category represented by |
|
Logical cikit run that owns the artifact. |
|
File size in bytes. Directory artifacts use |
|
Stored artifact hash. File artifacts receive a SHA-256 hash automatically unless one is supplied explicitly. |
Artifact.path is always absolute. Creating an Artifact with a relative
path is rejected.
size and hash default to None when an Artifact is created
without these metadata values.
The object is immutable after creation.
Artifact Types
Artifact types describe the purpose of an artifact independently from its file extension or physical source directory.
The current ArtifactType values are:
Type |
Persisted value |
Typical use |
|---|---|---|
|
|
Generated status or metric badge. |
|
|
Rendered timeline chart. |
|
|
XML result artifact used by test and coverage workflows. |
|
|
Machine-readable lint output. |
|
|
Generated reStructuredText report page. |
|
|
Directory artifact such as generated HTML documentation or coverage. |
|
|
Generated project metadata output. |
|
|
Extension-related generated output. |
|
|
Persisted command or build log. |
|
|
Direct build output such as a firmware image. |
|
|
Packaged archive containing one or more deliverables. |
The string values are persisted in registry JSON files and are therefore part of the persistent artifact format.
Creating Artifacts
Artifact handling belongs to CiRunEnvironment. The base CiEnvironment
does not manage artifacts because artifact ownership is tied to a logical run.
The normal producer API is copy_artifact():
from pathlib import Path
from cikit.artifacts import ArtifactType
artifact = env.copy_artifact(
Path("build/firmware.bin"),
ArtifactType.BUILD,
)
For a relative source path, cikit resolves the source below PROJECT_ROOT.
Absolute source paths may point to any accessible location, including temporary directories outside the project tree.
copy_artifact() performs four operations:
Resolve and validate the source file or directory.
Copy it into the persistent output area selected for the artifact type.
Determine the artifact metadata.
Register the copied artifact for the current logical run.
For regular files, cikit stores the file size and calculates a SHA-256 hash after the file has been copied.
If the producing tool already provides a SHA-256 hash, it can be supplied explicitly:
artifact = env.copy_artifact(
Path("build/firmware.bin"),
ArtifactType.BUILD,
artifact_hash=firmware_hash,
)
The supplied hash is stored instead of calculating it again.
The returned object is the registered Artifact and therefore contains the
resolved path together with its size and hash metadata:
print(artifact.path)
print(artifact.size)
print(artifact.hash)
Custom Target Names
A different destination name can be supplied with target_name:
artifact = env.copy_artifact(
Path("build/firmware.bin"),
ArtifactType.BUILD,
target_name="controller.bin",
)
Nested destinations are also supported:
artifact = env.copy_artifact(
Path("build/firmware.bin"),
ArtifactType.BUILD,
target_name="firmware/controller.bin",
)
The persistent file is then placed below the type-specific target directory using the supplied relative destination.
artifact.target_name represents the final file or directory name, while
the complete registered path is available through artifact.path.
Directory Artifacts
copy_artifact() supports both files and directories.
For example, a complete generated HTML tree can be registered as one
FOLDER artifact:
documentation = env.copy_artifact(
Path("build/html"),
ArtifactType.FOLDER,
)
Directories are copied recursively before being registered.
Folder artifacts do not have a file size, so artifact.size is None.
No hash is calculated automatically for a folder, so artifact.hash also
defaults to None.
An explicitly supplied SHA-256 artifact_hash is preserved even for a folder
artifact.
Persistent Layout
CiRunEnvironment owns the mapping from artifact type to persistent
destination below PUBLIC_DIR.
The current layout is:
public/
|-- artifacts/
|-- badges/
|-- cikit_data/
`-- rst/
BADGE artifacts are stored below:
public/badges/
RST artifacts are stored below:
public/rst/
All other currently supported artifact types are stored below:
public/artifacts/
The physical layout is intentionally owned by the environment layer. Producers and consumers should use the artifact API instead of constructing these paths themselves.
Artifact Registry
ArtifactRegistry persists artifact metadata locally.
CiRunEnvironment creates the registry with:
cache directory = CIKIT_DATA_DIR
artifact root = PUBLIC_DIR
One metadata file is stored per logical run:
CIKIT_DATA_DIR/
|-- build.artifacts.json
|-- test.artifacts.json
`-- docs.artifacts.json
The registry stores artifact paths relative to PUBLIC_DIR.
For example, an in-memory artifact may contain:
path = /workspace/project/public/artifacts/firmware.bin
target_name = firmware.bin
artifact_type = build
run = build
size = 524288
hash = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
The corresponding build.artifacts.json entry is stored as:
{
"artifacts/firmware.bin": {
"target_name": "firmware.bin",
"type": "build",
"size": 524288,
"hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
}
size and hash are part of the persisted artifact metadata. Folder
artifacts normally store both values as null unless an explicit hash was
supplied.
Persisting relative paths keeps artifact metadata portable across workspaces and CI jobs.
When registry entries are loaded, the relative path is resolved below the
configured artifact root and returned as an absolute Artifact.path.
Paths that are absolute in persisted metadata or escape the configured artifact root are rejected.
Updating Registry Entries
Within one logical run, the persisted path is the registry key.
Registering another artifact for the same path updates the existing metadata entry rather than creating a duplicate entry.
Artifacts at different paths remain separate even if they share the same
target_name or artifact type.
Querying Artifacts
CiRunEnvironment provides general and run-specific lookup methods.
All Locally Known Runs
Calling get_artifacts() without a run filter searches all locally available
artifact registry files:
artifacts = env.get_artifacts()
A type filter can be supplied:
artifacts = env.get_artifacts(
artifact_type=ArtifactType.BUILD,
)
Specific Run
Pass the logical run explicitly:
artifacts = env.get_artifacts(
"build",
ArtifactType.BUILD,
)
The run=None convention intentionally means all locally known runs.
Active Run
Most report and macro code operates on the current logical run and uses the convenience wrapper:
artifacts = env.get_run_artifacts(ArtifactType.TIMELINE)
This is equivalent to filtering get_artifacts() by env.name.
Getting One Artifact
get_artifact() resolves one uniquely identifiable artifact:
artifact = env.get_artifact(
"firmware.bin",
"build",
ArtifactType.BUILD,
)
If no run is supplied, all runs are searched.
The lookup fails when no artifact matches or when the result is ambiguous.
For the active logical run, use:
artifact = env.get_run_artifact(
"firmware.bin",
ArtifactType.BUILD,
)
This avoids repeatedly passing env.name.
Producer and Consumer Decoupling
Artifact registration is the integration boundary between cikit features.
A producer only needs to register the artifact with the appropriate type:
timeline.create(env, "build")
Internally this eventually registers a TIMELINE artifact.
A consumer decides independently whether it supports that artifact type:
timelines = env.get_run_artifacts(ArtifactType.TIMELINE)
There is no global rule that automatically injects every artifact into every report or documentation page.
For example, a report that supports TIMELINE artifacts can include all
timeline charts registered for its run. A report that does not query
ArtifactType.TIMELINE remains unaffected by them.
The same principle applies to build outputs, bundles, logs, folders, and other artifact categories.
Report Integration
Reports may consume artifacts through the artifact registry. They do not receive files or producer objects directly.
Each report implementation decides which artifact types it supports and which artifacts of the active logical run it includes.
For example, a report can query timeline artifacts with:
timelines = env.get_run_artifacts(ArtifactType.TIMELINE)
Registering a TIMELINE artifact therefore does not automatically make it
part of every report. It only becomes visible in reports that explicitly
consume ArtifactType.TIMELINE.
Current Built-in Reports
The built-in report implementations currently consume artifacts as follows:
Report |
Artifact types |
Notes |
|---|---|---|
Test report |
|
Includes timeline artifacts of the active run. |
Build report |
|
Includes timeline charts, build artifacts and bundles with download links, file sizes, and SHA-256 hashes, plus command logs. |
Coverage report |
|
Includes timeline artifacts and links to the registered HTML coverage directory. |
Ruff report |
|
The main report includes timelines. Filtered reports intentionally do not. |
Pylint report |
|
The main report includes timelines. Filtered reports intentionally do not. |
This behavior is report-specific. Other report implementations may consume different artifact types or none at all.
Producer and Report Independence
Artifact producers do not need to know which reports will consume their outputs.
For example:
timeline = chart_timeline(
env,
keys=["warnings", "errors"],
title="Build diagnostics",
limit=20,
)
timeline.create(env, "build_diagnostics")
reports.write_build_report(env)
TimelineChart.create() registers the rendered SVG as a TIMELINE
artifact. write_build_report() later discovers that artifact through the
registry.
No TimelineChart object or SVG path is passed directly to the report.
The same pattern is used for build outputs, bundles, logs, coverage folders, and other artifact types:
producer
|
+-- register artifact
|
v
ArtifactRegistry
|
+-- report supporting that artifact type
This keeps producers and report generators independent from each other.
Sphinx Integration
The Sphinx preparation step also consumes registered artifacts.
The current mapping into the Sphinx source tree is:
RST
-> docs/_generated/
BADGE
-> docs/_static/badges/
all other artifact types
-> docs/_static/artifacts/
This mapping is based on artifact metadata rather than on producer-specific output paths.
For example, a registered TIMELINE artifact can be copied into the Sphinx
static artifact area and later referenced through the cikit-chart
directive.
The completed Sphinx HTML tree is itself registered as a FOLDER artifact.
Bundles
Bundles are ordinary artifacts with type BUNDLE.
The bundle macro creates an archive from a file or directory and then registers
the generated archive through copy_artifact().
Supported bundle formats are:
.zip.tar.tar.gz
Because bundles use the normal artifact mechanism, consumers such as the build report can discover them through:
bundles = env.get_run_artifacts(ArtifactType.BUNDLE)
Artifact Handling and Publication
The artifact mechanism manages local persistent outputs and their run-related metadata. It does not itself publish files to a CI service.
Publication remains a responsibility of the surrounding CI configuration.
For example, a GitLab pipeline may publish files from public/ as CI
artifacts or publish the registered Sphinx output through GitLab Pages.
This separation keeps artifact creation and discovery provider-independent.
Relationship to Cikit Data
Artifact metadata and cikit run data are separate concepts.
Both are associated with a logical run, but they store different information:
cikit data |
artifact registry |
|
|---|---|---|
Stores |
Metrics and structured run values |
File and directory metadata |
Example |
|
|
Run-bound |
Yes |
Yes |
Timeline source |
Yes |
No |
Physical files |
No |
References persistent files or directories |
CIKIT_DATA_DIR is used as the local storage location for both run data and
artifact registry metadata, but the two mechanisms remain independent.