Package Publishing Guide¶
Table of Contents¶
- Overview
- Package Structure
- Prerequisites
- Version Management
- Building Packages
- Testing Packages
- Publishing to TestPyPI
- Publishing to PyPI
- Automated Publishing (CI/CD)
- Troubleshooting
- Best Practices
Overview¶
Cogniverse consists of 12 independent packages organized in a layered architecture. The publishing scripts build and publish 4 main packages (core, agents, vespa, runtime) together with the internal packages they require:
Packages Supported by Publishing Scripts¶
| Package | Description | Dependencies |
|---|---|---|
| cogniverse-core | Business logic and system configuration | cogniverse-foundation, cogniverse-sdk, cogniverse-evaluation |
| cogniverse-agents | Agent implementations | cogniverse-sdk, cogniverse-core, cogniverse-synthetic |
| cogniverse-vespa | Vespa backend integration | cogniverse-sdk, cogniverse-core |
| cogniverse-runtime | FastAPI server runtime | cogniverse-sdk, cogniverse-core, cogniverse-synthetic (agents/vespa optional) |
Other Packages¶
| Package | Description | Layer |
|---|---|---|
| cogniverse-sdk | Base types and protocols | Foundation |
| cogniverse-foundation | Telemetry, config, caching, DSPy helpers, registry | Foundation |
| cogniverse-evaluation | Evaluation framework and Phoenix analytics | Core |
| cogniverse-telemetry-phoenix | Phoenix telemetry implementation | Implementation |
| cogniverse-synthetic | Synthetic data generation | Implementation |
| cogniverse-finetuning | Model fine-tuning and optimization | Implementation |
| cogniverse-cli | cogniverse CLI for deploying and managing the platform (Helm charts, cluster, sandbox, secrets) | Application |
| cogniverse-messaging | Messaging gateway for Telegram/Slack integration with the runtime | Application |
cogniverse-cli ships the deployment assets its path helpers resolve when no checkout is present: the git-tracked files under charts/cogniverse, workflows and configs, as cogniverse_cli/data/. libs/cli/hatch_build.py adds them to the sdist and the wheel, so a wheel built from the unpacked sdist carries the same files, and fails the build when an asset listed in required-assets in libs/cli/pyproject.toml is missing. uv build --package cogniverse-cli builds the sdist and then the wheel from it.
Publishing Workflow¶
flowchart LR
A["<span style='color:#000'><b>Tag</b><br/>make release VERSION=x.y.z</span>"]
B["<span style='color:#000'><b>Build</b><br/>build release set<br/>in dependency order</span>"]
C["<span style='color:#000'><b>Test</b><br/>pytest by layer<br/>test dependencies</span>"]
D["<span style='color:#000'><b>Publish</b><br/>publish the manifest's artifacts<br/>in dependency order</span>"]
E["<span style='color:#000'><b>Release</b><br/>GitHub Release</span>"]
A --> B --> C --> D --> E
style A fill:#ffcc80,stroke:#ef6c00,color:#000
style B fill:#90caf9,stroke:#1565c0,color:#000
style C fill:#a5d6a7,stroke:#388e3c,color:#000
style D fill:#ce93d8,stroke:#7b1fa2,color:#000
style E fill:#b0bec5,stroke:#546e7a,color:#000 Publishing Scripts Handle the Release Set
Pushing a v* tag drives CI, which builds + publishes these packages (build_packages.sh / publish_packages.sh); hatch-vcs stamps each from the tag. build_packages.sh builds them together with every internal package they require — the release set, in dependency order: sdk, foundation, core, evaluation, synthetic, vespa, agents, telemetry-phoenix, runtime.
Note: publish_packages.sh uploads exactly the artifacts listed in dist/BUILD_MANIFEST.json — the whole release set, sdk through runtime. finetuning, cli and messaging are not built or published by the scripts.
Package Structure¶
Each package follows UV workspace structure with layer organization:
libs/
├── FOUNDATION LAYER
├── sdk/ # cogniverse-sdk
│ ├── cogniverse_sdk/
│ │ ├── __init__.py
│ │ ├── document.py
│ │ └── interfaces/
│ ├── pyproject.toml
│ └── README.md
├── foundation/ # cogniverse-foundation
│ ├── cogniverse_foundation/
│ │ ├── __init__.py
│ │ ├── confidence.py
│ │ ├── telemetry/
│ │ ├── config/
│ │ ├── caching/
│ │ ├── dspy/
│ │ └── registry/
│ ├── pyproject.toml
│ └── README.md
├── CORE LAYER
├── evaluation/ # cogniverse-evaluation
├── core/ # cogniverse-core
├── IMPLEMENTATION LAYER
├── telemetry-phoenix/ # cogniverse-telemetry-phoenix
├── agents/ # cogniverse-agents
├── vespa/ # cogniverse-vespa
├── synthetic/ # cogniverse-synthetic
├── finetuning/ # cogniverse-finetuning
├── APPLICATION LAYER
├── runtime/ # cogniverse-runtime
├── cli/ # cogniverse-cli (standalone, no cogniverse deps)
└── messaging/ # cogniverse-messaging (standalone, no cogniverse deps)
Key File: pyproject.toml (Foundation Layer Example)
# libs/foundation/pyproject.toml
[project]
name = "cogniverse-foundation"
dynamic = ["version"] # derived from git tags by hatch-vcs
description = "Cogniverse Foundation - Cross-cutting concerns and shared infrastructure"
requires-python = ">=3.12"
dependencies = [
"cogniverse-sdk",
"dspy-ai==3.4.0",
"opentelemetry-api==1.41.0",
"opentelemetry-sdk==1.41.0",
"pydantic==2.13.5",
"sqlalchemy==2.0.49",
"pandas==2.3.3",
]
[tool.uv.sources]
cogniverse-sdk = { workspace = true }
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[tool.hatch.version]
source = "vcs"
[tool.hatch.version.raw-options]
root = "../.." # package is at libs/<pkg>; git root is two up
Key File: pyproject.toml (Implementation Layer Example)
# libs/agents/pyproject.toml
[project]
name = "cogniverse-agents"
dynamic = ["version"] # hatch-vcs, as above
description = "Agent implementations, routing logic, and search enhancement for Cogniverse"
requires-python = ">=3.12"
dependencies = [
"cogniverse-sdk",
"cogniverse-core",
"cogniverse-synthetic",
# Optimization and ML (xgboost gate routing — small, no torch dep)
"xgboost==3.2.0",
"scikit-learn==1.8.0",
"scipy==1.17.1",
# NLP and language (spacy + langextract pure Python, no torch)
"spacy==3.8.14",
"langextract==1.2.1",
# MLflow for experiment tracking
"mlflow==3.11.1",
]
[project.optional-dependencies]
# Heavy ML stack for the in-process inference fallback paths.
# Production routes through vLLM / pylate sidecars and never imports
# these — install only for offline dev / local-path tests.
torch-local = [
"torch==2.8.0",
"transformers==4.56.2",
"colpali-engine==0.3.13",
"sentence-transformers==5.1.1",
"gliner==0.2.26",
]
[tool.uv.sources]
cogniverse-sdk = { workspace = true }
cogniverse-core = { workspace = true }
cogniverse-synthetic = { workspace = true }
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Prerequisites¶
Required Tools¶
# UV package manager
pip install uv
# Python dependency for versioning (reads use the stdlib tomllib).
# Already provided by the dev dependency group: `uv sync --group dev`.
pip install tomli-w
# Twine: publish_packages.sh installs twine==7.0.0 and its dependencies from the
# hash-pinned scripts/publish-requirements.txt into a throwaway Python 3.12 venv;
# nothing to install.
PyPI Account Setup¶
1. Create PyPI Accounts¶
- PyPI (Production): https://pypi.org/account/register/
- TestPyPI (Testing): https://test.pypi.org/account/register/
2. Generate API Tokens¶
PyPI:
-
Go to https://pypi.org/manage/account/token/
-
Create token with scope: "Entire account"
-
Save token securely (starts with
pypi-)
TestPyPI:
-
Go to https://test.pypi.org/manage/account/token/
-
Create token with scope: "Entire account"
-
Save token securely (starts with
pypi-)
3. Configure Credentials¶
Option A: Environment Variables (Recommended for CI/CD)
Option B: .pypirc File (Local Development)
# ~/.pypirc
[distutils]
index-servers =
pypi
testpypi
[pypi]
username = __token__
password = pypi-your-production-token
[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi-your-test-token
Security Note: Never commit .pypirc or tokens to version control!
Version Management¶
Semantic Versioning¶
Cogniverse follows Semantic Versioning 2.0.0:
MAJOR.MINOR.PATCH[-PRERELEASE]
↓ ↓ ↓ ↓
0 . 1 . 0 - alpha.0
MAJOR: Breaking changes
MINOR: New features (backward compatible)
PATCH: Bug fixes
PRERELEASE: alpha, beta, rc
The git tag is the single source of truth¶
There is no hardcoded version in any pyproject.toml. Every package declares dynamic = ["version"] and derives its version from git tags via hatch-vcs (the hatchling front-end to setuptools-scm):
- At a tag
v0.2.0→ the wheel is exactly0.2.0. - Between tags → a PEP 440 dev version such as
0.2.1.dev5+g<sha>("5 commits pastv0.2.0"). That honestly marks an unreleased build; it is not a defect.
What a build resolves to right now (the wheel filename carries the version):
The chart carries a static appVersion in charts/cogniverse/Chart.yaml (Helm needs a concrete version at install time). It is the release line the Docker image tags derive from, and make release keeps it in step with the tag.
Cutting a release¶
First release only: the publish steps need registry/PyPI credentials that are not configured yet — set them once (see Required GitHub Secrets) before pushing a tag.
One command bumps the chart and creates the tag:
It sets Chart.yaml version/appVersion + the chart image tags to 0.2.0, commits, and creates the annotated v0.2.0 tag. Pushing that tag triggers CI, which builds and publishes everything at 0.2.0:
| Artifact | Versioned by | Published by |
|---|---|---|
| Python wheels + sdists (release set) | hatch-vcs, from the tag | publish-packages.yml → PyPI |
| Docker images (runtime ×3, web, gliner, 3 sidecars) | the tag (github.ref_name) | release-images.yml → docker.io/cogniverse |
| Helm chart | Chart.yaml appVersion/version | release-images.yml (publish-chart job) → OCI oci://registry-1.docker.io/cogniverse |
Each release image also gets an SBOM + build provenance attached and is signed (keyless cosign / Sigstore) in the same job, so consumers can scan and verify it. For air-gapped clusters, mirror-third-party.yml (workflow_dispatch) copies the third-party images (vespa/phoenix/ollama/redis/ minio/vllm/envoy/…) — enumerated from a chart render — into a registry you name.
The old
scripts/version_bump.py(rewriting a hardcodedversionin every file) is superseded — the version lives in git, not in the files.
Image tags: release vs. dev¶
- Release (
release-images.ymlon av*tag) →docker.io/cogniverse/<name>:<version>plus an immutable:<git-sha>,pullPolicy: IfNotPresent(a clean cluster pulls). - Local dev (
cogniverse up→build_images) →cogniverse/<name>:<git-version>, where each image's<git-version>comes from its own Dockerfile and localCOPY/ADDinputs after applying.dockerignore; the ignore file itself is also an input. Tags replace+with-(e.g.0.1.dev2137-g9ba33d3f). A source change therefore retags only the images that consume it. Existing host tags are not rebuilt, and existing k3d-node tags are not re-imported. PyLate appends-cpu,-cuda, or-rocmbecause its build argument changes image bytes; runtime encodes that backend in its repositories.pullPolicy: Neverselects those local images.values.k3s.yamlcarries a static<line>-devplaceholder thatcogniverse upreplaces via per-image--setvalues fromdev_image_set_values.
.dockerignore excludes .git, so the runtime images (which install the workspace and thus trigger hatch-vcs) receive the version through the SETUPTOOLS_SCM_PRETEND_VERSION build-arg that build_images / release-images.yml set from the host, where git is available. Each package therefore carries the version derived for its containing image.
Building Packages¶
Build Script¶
Script: scripts/build_packages.sh
Basic Build¶
# Build all packages
./scripts/build_packages.sh
# Clean build (remove previous artifacts)
./scripts/build_packages.sh --clean
# Verbose output
./scripts/build_packages.sh --verbose
Advanced Options¶
# Build with tests
./scripts/build_packages.sh --test
# Strict mode (fail on test failures)
./scripts/build_packages.sh --test --strict
# Continue on errors
CONTINUE_ON_ERROR=true ./scripts/build_packages.sh
Build Output¶
dist/
├── cogniverse_sdk-0.2.0-py3-none-any.whl
├── cogniverse_sdk-0.2.0.tar.gz
├── ... # one wheel + sdist per release package
├── cogniverse_runtime-0.2.0-py3-none-any.whl
├── cogniverse_runtime-0.2.0.tar.gz
└── BUILD_MANIFEST.json
BUILD_MANIFEST.json lists exactly the artifacts this invocation built, in build order:
{
"version": "0.2.0",
"packages": [
{
"name": "cogniverse-sdk",
"version": "0.2.0",
"requires": [],
"wheel": {"filename": "cogniverse_sdk-0.2.0-py3-none-any.whl", "sha256": "..."},
"sdist": {"filename": "cogniverse_sdk-0.2.0.tar.gz", "sha256": "..."}
}
]
}
requires holds the package's internal requirements (base and extras).
Build Process¶
- Version:
uv build --no-sources --sdistbuilds each package's sdist from the workspace, so hatch-vcs computes the version from git. All work happens in a per-invocation staging directory (printed asStaging directory:), removed on exit, success or failure; the backend's temporary files go inside it. - Pinned source:
scripts/release_manifest.py stageunpacks that sdist into the staging directory and pins every internal requirement (base and extras) in itspyproject.tomlto==<version>. The workspacepyproject.tomlfiles anduv.lockare not modified. - Build:
uv build --no-sourcesbuilds the release sdist from the pinned source, then its wheel from that sdist, withSETUPTOOLS_SCM_PRETEND_VERSION=<version>. The published sdist carries the pinnedpyproject.toml, so a wheel rebuilt from it has the sameRequires-Dist. - Validation:
scripts/release_manifest.py build(run withuv run --no-sync, so the project environment must be synced) readsMETADATA/PKG-INFOfrom each wheel and sdist. Name and version must agree across the pyproject, both filenames and both metadata files; versions are PEP 440 (packaging.version.Version), so tag builds (0.2.0) and dev builds (0.2.1.dev1+g<sha>) are both valid. All packages must share one version, wheel and sdistRequires-Distmust match, every internal requirement must be pinned to==<version>, and every internal requirement must be built earlier in the release set. An internal requirement that already has a version specifier in the workspace fails the build. - Collection: Only after every package validates are the artifacts copied into
dist/and the manifest written. Existing files indist/are left untouched; an existing file with the same name but different bytes fails the build.--cleanremovesdist/first.
Any failure exits nonzero and leaves no BUILD_MANIFEST.json.
Testing Packages¶
Local Testing¶
Install from Built Distributions¶
# Create test environment
python -m venv test-env
source test-env/bin/activate
# Install built packages in dependency order
# Core package (built first)
pip install dist/cogniverse_core-*.whl
# Agent and backend packages (depend on core)
pip install dist/cogniverse_agents-*.whl
pip install dist/cogniverse_vespa-*.whl
# Application packages (depend on core, agents, vespa)
pip install dist/cogniverse_runtime-*.whl
# Verify imports
python -c "from cogniverse_foundation.config.unified_config import SystemConfig"
python -c "from cogniverse_agents.gateway_agent import GatewayAgent"
python -c "from cogniverse_vespa import VespaBackend"
python -c "from cogniverse_runtime.main import app"
python -c "print('All packages imported successfully')"
Run Test Suite¶
# Run all tests
JAX_PLATFORM_NAME=cpu uv run pytest
# Run package-specific tests
JAX_PLATFORM_NAME=cpu uv run pytest tests/common/
JAX_PLATFORM_NAME=cpu uv run pytest tests/agents/
Publishing to TestPyPI¶
Why TestPyPI?¶
- Safe testing environment for PyPI without affecting production
- Validate package metadata before production release
- Test installation from PyPI-like repository
Manual Publishing¶
# 1. Build packages
./scripts/build_packages.sh --clean
# 2. Publish to TestPyPI (with dry run first)
TEST_PYPI_TOKEN="your-test-token" ./scripts/publish_packages.sh --test --dry-run
# 3. Actual publish
TEST_PYPI_TOKEN="your-test-token" ./scripts/publish_packages.sh --test
# Output:
# [INFO] Publishing package: cogniverse-sdk 0.1.0
# [SUCCESS] Uploaded or already present: cogniverse-sdk 0.1.0
# ...
# [INFO] Uploaded or already present: 10 package(s)
# Verified 20 file(s) at https://test.pypi.org/simple/ against the manifest digests
What the script checks¶
- Manifest only: it uploads the wheel and sdist of each package in
dist/BUILD_MANIFEST.json, in manifest order, and nothing else indist/. A missing manifest, a missing listed file, or a file whose sha256 differs from the manifest fails before any upload. - Local versions refused: a build of an untagged commit (
0.2.1.dev1+g<sha>) carries a local version, which PyPI and TestPyPI reject; the script exits 1 before any upload (also with--dry-run). twine checkruns on exactly the manifest's files.- Child results: each package is one
twine upload --skip-existingof its wheel and sdist, and twine's exit status is the result. A nonzero exit is a failure, never counted as "already exists". Without--continuethe remaining packages are not attempted; with--continuethey are, and the script still exits 1. - Duplicates: twine 7.0.0 supports
--skip-existingonly for PyPI and TestPyPI; it skips a file when the index answers409, or400with "already exist", and exits 0. - Index verification: after every upload succeeds, the script reads the index's simple page for each package and requires every manifest file to be served with the manifest's sha256 (waiting up to
VERIFY_TIMEOUTseconds, default 300; a 5xx answer or failed request is retried within that time, any other non-404 error fails at once). A same-named file with other bytes — a skipped duplicate that is not this build — fails the publication. - Target only: the upload goes to PyPI, or TestPyPI with
--test. IfTWINE_REPOSITORY_URLorTWINE_REPOSITORYis set, the script exits 1 before doing anything. - Confirmation: without
--yesthe script asks; any answer butyes, or no input, exits 1 with nothing uploaded. --dry-runruns the manifest checks andtwine check, lists the uploads, and neither uploads nor queries the index for the release packages (uv may still download the pinned publish tool from PyPI when it is not cached). Its exit 0 is not evidence of publication.
Test Installation¶
# Install from TestPyPI
pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
cogniverse-core
# Note: --extra-index-url allows dependencies from production PyPI
Verification¶
# Test package functionality
python -c "
from cogniverse_foundation.config.unified_config import SystemConfig
config = SystemConfig()
print(f'Successfully imported: {config.search_backend}')
"
Publishing to PyPI¶
Pre-publish Checklist¶
- All tests passing
- Version bumped correctly
- Git tag created (
v0.1.0) - CHANGELOG updated (optional - file not currently maintained)
- README accurate
- License included
- Tested on TestPyPI
Manual Publishing¶
# 1. Build packages
./scripts/build_packages.sh --clean --test
# 2. Publish to PyPI (with dry run first)
PYPI_TOKEN="your-production-token" ./scripts/publish_packages.sh --dry-run
# 3. Actual publish
PYPI_TOKEN="your-production-token" ./scripts/publish_packages.sh
# Output ends with:
# Verified 20 file(s) at https://pypi.org/simple/ against the manifest digests
Note: The scripts publish the ten release-set packages listed in the manifest. finetuning, cli and messaging require manual publishing.
The CLI cannot join the staged release set as is: scripts/release_manifest.py deletes PKG-INFO from each staged source, and libs/cli/hatch_build.py takes PKG-INFO as its only sign of an sdist build. A CLI published by hand (uv build --package cogniverse-cli) requires cogniverse-foundation with no version pin.
Verify Publication¶
# Check PyPI page
open https://pypi.org/project/cogniverse-core/
# Test installation
pip install cogniverse-core==0.1.0
# Test functionality
python -c "from cogniverse_foundation.config.unified_config import SystemConfig; print('Success!')"
Automated Publishing (CI/CD)¶
GitHub Actions Workflow¶
File: .github/workflows/publish-packages.yml
Trigger: Version Tags¶
# Create and push version tag
git tag -a v0.1.0 -m "Release 0.1.0"
git push origin v0.1.0
# GitHub Actions automatically:
# 1. Builds packages
# 2. Runs tests
# 3. Publishes to PyPI
# 4. Creates GitHub Release
Trigger: Manual Workflow Dispatch¶
# Via GitHub UI:
# 1. Go to Actions → "Publish SDK Packages"
# 2. Click "Run workflow"
# 3. Select target: testpypi or pypi
# 4. Optional: Enable dry run
Run it from a v* tag ref ("Use workflow from" → Tags). A branch build carries a local version, and "Verify release artifacts" refuses it, dry run included.
Workflow Stages¶
flowchart TD
A["<span style='color:#000'><b>Build</b><br/>Build release set<br/>(5 published packages + internal dependencies)</span>"]
B["<span style='color:#000'><b>Test</b><br/>Install manifest wheels in a fresh venv,<br/>run test suite with Vespa</span>"]
C["<span style='color:#000'><b>TestPyPI</b><br/>Dry run, publish, verify index digests</span>"]
D["<span style='color:#000'><b>PyPI</b><br/>Dry run, publish, verify index digests</span>"]
E["<span style='color:#000'><b>GitHub Release</b><br/>Create release with notes</span>"]
A --> B
B --> C
B --> D
D --> E
style A fill:#90caf9,stroke:#1565c0,color:#000
style B fill:#a5d6a7,stroke:#388e3c,color:#000
style C fill:#ffcc80,stroke:#ef6c00,color:#000
style D fill:#ce93d8,stroke:#7b1fa2,color:#000
style E fill:#b0bec5,stroke:#546e7a,color:#000 Note: GitHub Release only depends on the PyPI publish job (release tags skip TestPyPI); prerelease tags publish to TestPyPI without cutting a GitHub Release.
Tag-Based Publishing Rules¶
| Tag Format | Target | Example |
|---|---|---|
v*.*.* | PyPI | v0.1.0 |
v*.*.*-alpha.* | TestPyPI | v0.1.0-alpha.0 |
v*.*.*-beta.* | TestPyPI | v0.1.0-beta.1 |
v*.*.*-rc.* | TestPyPI | v0.1.0-rc.1 |
Required GitHub Secrets¶
Set these under Settings → Secrets and variables → Actions before cutting a release. The repo currently has none configured, so a v* tag would fail at the credential steps (Docker login, chart push, PyPI upload) until they exist.
| Secret | Used by | For |
|---|---|---|
DOCKERHUB_USERNAME | release-images.yml, mirror-third-party.yml | Docker Hub account name |
DOCKERHUB_TOKEN | same | Docker Hub access token (not the password) — image + OCI chart push |
PYPI_TOKEN | publish-packages.yml | production PyPI wheel upload |
TEST_PYPI_TOKEN | publish-packages.yml | TestPyPI upload (-rc / -beta / -alpha tags) |
GITHUB_TOKEN is injected automatically, and keyless cosign signing uses the workflow's OIDC identity — so no signing-key secret is needed.
gh secret set DOCKERHUB_USERNAME --body "<dockerhub-user>"
gh secret set DOCKERHUB_TOKEN --body "<dockerhub-access-token>"
gh secret set PYPI_TOKEN --body "<pypi-token>"
gh secret set TEST_PYPI_TOKEN --body "<testpypi-token>"
Troubleshooting¶
Common Issues¶
Version Already Exists on PyPI¶
Error:
error: https://pypi.org/simple/: cogniverse_core-0.1.0-py3-none-any.whl has sha256 <on index>, manifest has <this build>
A same-named file that is byte-identical to this build is skipped and passes; one with other bytes fails. With --no-skip-existing, twine reports HTTPError: 400 Bad Request ... File already exists for either.
Solution:
# PyPI doesn't allow overwriting a version — cut the next one.
make release VERSION=0.1.1
git push origin main --follow-tags # CI rebuilds + republishes at 0.1.1
Missing Dependencies in Build¶
Error:
Solution:
Twine Upload Fails¶
Error:
Solution:
# Check credentials
cat ~/.pypirc
# Or use environment variables
export PYPI_TOKEN="pypi-your-token"
./scripts/publish_packages.sh
Package Import Fails After Install¶
Error:
Solution:
# Check package contents
unzip -l dist/cogniverse_foundation-*.whl | grep config
# Verify __init__.py exports
cat libs/foundation/cogniverse_foundation/__init__.py
# Rebuild with correct exports
./scripts/build_packages.sh --clean
Best Practices¶
Version Management¶
-
Pick the version per semantic versioning — the number goes in the tag, not in any file:
-
Test prereleases first — a
-rc/-beta/-alphatag publishes to TestPyPI (not PyPI), perpublish-packages.yml: -
The tag is the release — pushing it drives both the PyPI and Docker publishes; there is nothing else to run:
Building¶
-
Always use clean builds for releases
-
Run tests before publishing
-
Verify build artifacts
Publishing¶
-
Always test on TestPyPI first
-
Use dry runs for validation
-
Document changes in CHANGELOG (optional)
Note: CHANGELOG.md is not currently maintained in this project. This is a recommended best practice if you choose to implement it.
Security¶
-
Never commit secrets
-
Use environment variables in CI/CD
-
Rotate API tokens regularly
- PyPI tokens should be rotated every 6 months
- Use scoped tokens when possible
Quality Assurance¶
- Maintain package READMEs
- Each package should have a clear README
-
Include installation and usage examples
-
Keep pyproject.toml updated
- Accurate dependencies
- Correct Python version requirements
-
Proper classifiers
-
Test cross-package dependencies
Complete Publishing Workflow¶
Standard Release Process¶
# 1. Land your changes on main with CI green
git switch main && git pull
# 2. Cut the release — bumps Chart.yaml + chart image tags, commits, tags v0.2.0
make release VERSION=0.2.0
git show --stat v0.2.0 # review the release commit + tag before pushing
# 3. Push main + the tag. The tag drives CI, which:
# - stamps every Python wheel 0.2.0 (hatch-vcs) and publishes to PyPI
# (publish-packages.yml — a -rc/-beta/-alpha tag goes to TestPyPI instead)
# - builds + pushes docker.io/cogniverse/*:0.2.0 (+ :<git-sha>)
# (release-images.yml)
git push origin main --follow-tags
# 4. Verify
open https://pypi.org/project/cogniverse-core/
docker pull cogniverse/runtime-cpu:0.2.0
No manual build_packages.sh / publish_packages.sh run is needed for a normal release — pushing the tag does it. Those scripts remain for local/offline builds.
Related Documentation¶
- Package Development - SDK development guide
- SDK Architecture - Package structure details
- Testing Guide - Testing practices
Support¶
- PyPI Issues: Check PyPI Help
- Build Issues: Review the
build_packages.shoutput;dist/BUILD_MANIFEST.jsonexists only after a successful build - CI/CD Issues: Check GitHub Actions logs