generated from coulomb/repo-seed
WP-0001-T001: service scaffold (Python, FastAPI, uv, ruff, mypy, pytest)
Lands the smallest credible foundation per ADR-0005:
- pyproject.toml: hatchling build, runtime deps (FastAPI, uvicorn, SQLAlchemy 2.0,
asyncpg, aiosqlite, alembic, blake3, cbor2, typer, structlog, pydantic,
pydantic-settings); dev deps (pytest, pytest-asyncio, httpx, hypothesis, ruff,
mypy); ruff + mypy --strict + pytest configured.
- uv.lock committed.
- Makefile thin shims: install / dev / test / lint / format / type / migrate / clean.
- src/artifactstore/ package skeleton with placeholder __init__.py per concern:
identity, manifest, events, retention, audit, storage, dataplane, registry,
api/http (minimal FastAPI app, GET / scaffold banner), cli (typer app with
version subcommand), config (pydantic-settings).
- tests/{unit,integration}/conftest.py present; unit smoke tests assert package
imports, HTTP root route, CLI version round-trip, settings defaults.
- .env.example documents ARTIFACTSTORE_DATABASE_URL,
ARTIFACTSTORE_STORAGE_LOCAL_ROOT, ARTIFACTSTORE_LOG_LEVEL.
- README updated with install / dev / test instructions.
- .gitignore: claude local state, local runtime data (var/, sqlite db).
make lint && make type && make test pass on a clean checkout (4 tests, 20
source files type-clean under mypy --strict).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
16
.env.example
Normal file
16
.env.example
Normal file
@@ -0,0 +1,16 @@
|
||||
# artifact-store environment configuration
|
||||
#
|
||||
# Copy to .env for local development. All keys are prefixed with
|
||||
# ARTIFACTSTORE_; pydantic-settings reads them automatically.
|
||||
|
||||
# Database connection string. SQLite (with aiosqlite) for dev; PostgreSQL
|
||||
# (with asyncpg) for shared deployments.
|
||||
# sqlite+aiosqlite:///./var/artifactstore.db
|
||||
# postgresql+asyncpg://user:pass@host:5432/artifactstore
|
||||
ARTIFACTSTORE_DATABASE_URL=sqlite+aiosqlite:///./var/artifactstore.db
|
||||
|
||||
# Local filesystem storage root. Used by the LocalBackend (WP-0001-T003).
|
||||
ARTIFACTSTORE_STORAGE_LOCAL_ROOT=./var/storage
|
||||
|
||||
# Python logging level: DEBUG | INFO | WARNING | ERROR
|
||||
ARTIFACTSTORE_LOG_LEVEL=INFO
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -174,3 +174,11 @@ cython_debug/
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# Claude Code local state (ralph loop, local permissions)
|
||||
.claude/*.local.*
|
||||
|
||||
# artifact-store local runtime data
|
||||
/var/
|
||||
artifactstore.db
|
||||
artifactstore.db-journal
|
||||
|
||||
|
||||
43
Makefile
Normal file
43
Makefile
Normal file
@@ -0,0 +1,43 @@
|
||||
.PHONY: help install dev test lint format type migrate clean
|
||||
|
||||
help:
|
||||
@echo "artifact-store — make targets"
|
||||
@echo " install install / sync dependencies via uv"
|
||||
@echo " dev run the FastAPI app with reload (uvicorn)"
|
||||
@echo " test run the pytest suite"
|
||||
@echo " lint ruff check + ruff format --check"
|
||||
@echo " format ruff format (write changes)"
|
||||
@echo " type mypy --strict over src and tests"
|
||||
@echo " migrate alembic upgrade head (configured by WP-0001-T002)"
|
||||
@echo " clean remove caches and build artefacts"
|
||||
|
||||
install:
|
||||
uv sync --all-extras
|
||||
|
||||
dev:
|
||||
uv run uvicorn artifactstore.api.http:app --reload --host 127.0.0.1 --port 8000
|
||||
|
||||
test:
|
||||
uv run pytest
|
||||
|
||||
lint:
|
||||
uv run ruff check .
|
||||
uv run ruff format --check .
|
||||
|
||||
format:
|
||||
uv run ruff format .
|
||||
uv run ruff check --fix .
|
||||
|
||||
type:
|
||||
uv run mypy
|
||||
|
||||
migrate:
|
||||
@if [ -f alembic.ini ]; then \
|
||||
uv run alembic upgrade head; \
|
||||
else \
|
||||
echo "alembic.ini not present yet — see ARTIFACT-STORE-WP-0001-T002"; \
|
||||
fi
|
||||
|
||||
clean:
|
||||
rm -rf .pytest_cache .mypy_cache .ruff_cache build dist *.egg-info
|
||||
find . -type d -name __pycache__ -prune -exec rm -rf {} +
|
||||
51
README.md
51
README.md
@@ -5,9 +5,8 @@ evidence packages, reports, logs, snapshots, exports, and release
|
||||
artifacts.
|
||||
|
||||
The registry owns artifact identity, metadata, provenance, retention
|
||||
policy, and retrieval records. Bytes are delegated to configured
|
||||
storage backends (local filesystem in v1, S3-compatible / Ceph RGW
|
||||
next).
|
||||
policy, and retrieval records. Bytes are delegated to configured storage
|
||||
backends (local filesystem in v1, S3-compatible / Ceph RGW next).
|
||||
|
||||
The shape is library-first (`artifactstore` Python package); the HTTP
|
||||
server and the CLI are thin consumers. Content is addressed by digest;
|
||||
@@ -16,26 +15,40 @@ are rebuildable.
|
||||
|
||||
## Status
|
||||
|
||||
Concept / service-baseline planning. No runnable scaffold yet —
|
||||
`workplans/ARTIFACT-STORE-WP-0001-service-baseline.md` is the next step.
|
||||
Scaffold landed. The core kernels and local FS backend follow in the
|
||||
remaining tasks of [WP-0001](workplans/ARTIFACT-STORE-WP-0001-service-baseline.md).
|
||||
|
||||
## Start here
|
||||
## Develop
|
||||
|
||||
Requires Python ≥ 3.12 and [`uv`](https://docs.astral.sh/uv/) on the path.
|
||||
|
||||
```sh
|
||||
make install # uv sync --all-extras
|
||||
cp .env.example .env
|
||||
|
||||
make dev # uvicorn artifactstore.api.http:app --reload
|
||||
make test # pytest
|
||||
make lint # ruff check + ruff format --check
|
||||
make type # mypy --strict
|
||||
make migrate # alembic upgrade head (configured in WP-0001-T002)
|
||||
```
|
||||
|
||||
The dev server listens on `127.0.0.1:8000`. The scaffold root route
|
||||
returns `{"service": "artifact-store", "status": "scaffold"}`; the real
|
||||
`/health` endpoint lands in `WP-0001-T014`.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [INTENT.md](INTENT.md) — purpose, product thesis, scope, boundary.
|
||||
- [SCOPE.md](SCOPE.md) — lightweight orientation.
|
||||
- [docs/ARCHITECTURE-BLUEPRINT.md](docs/ARCHITECTURE-BLUEPRINT.md) — the
|
||||
v2 architecture: modules, data model, API shape.
|
||||
- [docs/ARCHITECTURE-BLUEPRINT.md](docs/ARCHITECTURE-BLUEPRINT.md) — v2
|
||||
architecture: modules, data model, API shape.
|
||||
- [docs/PLATFORM-AMBITION.md](docs/PLATFORM-AMBITION.md) — longer-horizon
|
||||
thesis, ffmpeg / VLC reference points, the schema commitments v1
|
||||
preserves.
|
||||
- [docs/ROADMAP.md](docs/ROADMAP.md) — workplan sequencing across
|
||||
phases.
|
||||
- [docs/adr/](docs/adr/) — architecture decision records (ADR-0001 …
|
||||
ADR-0006).
|
||||
thesis and v1 schema commitments.
|
||||
- [docs/ROADMAP.md](docs/ROADMAP.md) — workplan sequencing across phases.
|
||||
- [docs/adr/](docs/adr/) — architecture decision records.
|
||||
- [docs/ASSEMBLY-EXPERIMENT.md](docs/ASSEMBLY-EXPERIMENT.md) — opt-in
|
||||
research line on hand-tuned assembly for hot kernels.
|
||||
- [docs/REVIEW-2026-05-15-intent-and-blueprint.md](docs/REVIEW-2026-05-15-intent-and-blueprint.md)
|
||||
— the SWOT review that triggered this cleanup.
|
||||
research line on hand-tuned asm for hot kernels.
|
||||
|
||||
## Active workplans
|
||||
|
||||
@@ -47,5 +60,5 @@ Concept / service-baseline planning. No runnable scaffold yet —
|
||||
|
||||
## Agent operating notes
|
||||
|
||||
See [AGENTS.md](AGENTS.md) for the StateHub-integrated session
|
||||
protocol, workplan conventions, and progress-logging contract.
|
||||
See [AGENTS.md](AGENTS.md) for the StateHub-integrated session protocol,
|
||||
workplan conventions, and progress-logging contract.
|
||||
|
||||
99
pyproject.toml
Normal file
99
pyproject.toml
Normal file
@@ -0,0 +1,99 @@
|
||||
[build-system]
|
||||
requires = ["hatchling>=1.25"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "artifactstore"
|
||||
version = "0.1.0"
|
||||
description = "Generic artifact registry and storage gateway"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
license = { file = "LICENSE" }
|
||||
authors = [{ name = "artifact-store contributors" }]
|
||||
keywords = ["artifact", "storage", "registry", "evidence", "retention"]
|
||||
classifiers = [
|
||||
"Development Status :: 2 - Pre-Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: System :: Archiving",
|
||||
]
|
||||
dependencies = [
|
||||
"fastapi >= 0.115",
|
||||
"uvicorn[standard] >= 0.30",
|
||||
"sqlalchemy >= 2.0",
|
||||
"asyncpg >= 0.29",
|
||||
"aiosqlite >= 0.20",
|
||||
"alembic >= 1.13",
|
||||
"blake3 >= 0.4",
|
||||
"cbor2 >= 5.6",
|
||||
"typer >= 0.12",
|
||||
"structlog >= 24.1",
|
||||
"pydantic >= 2.7",
|
||||
"pydantic-settings >= 2.4",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest >= 8.0",
|
||||
"pytest-asyncio >= 0.23",
|
||||
"httpx >= 0.27",
|
||||
"hypothesis >= 6.100",
|
||||
"ruff >= 0.6",
|
||||
"mypy >= 1.10",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
artifactstore = "artifactstore.cli:app"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/artifactstore"]
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = [
|
||||
"pytest >= 8.0",
|
||||
"pytest-asyncio >= 0.23",
|
||||
"httpx >= 0.27",
|
||||
"hypothesis >= 6.100",
|
||||
"ruff >= 0.6",
|
||||
"mypy >= 1.10",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py312"
|
||||
line-length = 100
|
||||
src = ["src", "tests"]
|
||||
extend-exclude = [".venv", "var", "migrations"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "W", "I", "N", "UP", "B", "C4", "RET", "SIM", "RUF"]
|
||||
ignore = [
|
||||
"B008", # FastAPI dependency injection idiom
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"tests/**" = ["S101"]
|
||||
"src/artifactstore/cli/__init__.py" = ["UP007"]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.12"
|
||||
strict = true
|
||||
files = ["src", "tests"]
|
||||
mypy_path = "src"
|
||||
explicit_package_bases = true
|
||||
namespace_packages = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
testpaths = ["tests"]
|
||||
pythonpath = ["src"]
|
||||
addopts = "-q --strict-markers"
|
||||
markers = [
|
||||
"integration: marks tests as integration (requires DB / backend)",
|
||||
]
|
||||
8
src/artifactstore/__init__.py
Normal file
8
src/artifactstore/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""artifact-store: generic artifact registry and storage gateway.
|
||||
|
||||
The top-level package re-exports nothing yet. Submodules are documented in
|
||||
``docs/ARCHITECTURE-BLUEPRINT.md`` and each carries its own placeholder for
|
||||
the implementation tasks in workplan ARTIFACT-STORE-WP-0001.
|
||||
"""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
4
src/artifactstore/api/__init__.py
Normal file
4
src/artifactstore/api/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
"""HTTP and future RPC API surfaces.
|
||||
|
||||
Submodules: :mod:`artifactstore.api.http`.
|
||||
"""
|
||||
24
src/artifactstore/api/http/__init__.py
Normal file
24
src/artifactstore/api/http/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""FastAPI application entry point.
|
||||
|
||||
The full registry-aware ``/health`` endpoint lands in
|
||||
ARTIFACT-STORE-WP-0001-T014. This scaffold exposes a minimal root route so
|
||||
``make dev`` has something to serve while the registry layer is being built.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
from artifactstore import __version__
|
||||
|
||||
app = FastAPI(title="artifact-store", version=__version__)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root() -> dict[str, str]:
|
||||
"""Return a service banner indicating the scaffold is up."""
|
||||
return {
|
||||
"service": "artifact-store",
|
||||
"version": __version__,
|
||||
"status": "scaffold",
|
||||
}
|
||||
5
src/artifactstore/audit/__init__.py
Normal file
5
src/artifactstore/audit/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Audit surface over the event log.
|
||||
|
||||
Audit is a view over :mod:`artifactstore.events` filtered to access and
|
||||
lifecycle event types; it has no separate write path.
|
||||
"""
|
||||
36
src/artifactstore/cli/__init__.py
Normal file
36
src/artifactstore/cli/__init__.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""artifact-store command-line interface.
|
||||
|
||||
The CLI is a thin consumer of :mod:`artifactstore.registry` (per ADR-0005).
|
||||
The scaffold exposes only ``version``; richer subcommands land in later
|
||||
tasks of ARTIFACT-STORE-WP-0001 and follow-on workplans.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import typer
|
||||
|
||||
from artifactstore import __version__
|
||||
|
||||
app = typer.Typer(
|
||||
help="artifact-store: artifact registry and storage gateway",
|
||||
no_args_is_help=True,
|
||||
)
|
||||
|
||||
|
||||
@app.callback()
|
||||
def main() -> None:
|
||||
"""Top-level CLI entry point.
|
||||
|
||||
Forces typer into multi-command mode so subcommands behave consistently
|
||||
even while the scaffold only ships ``version``.
|
||||
"""
|
||||
|
||||
|
||||
@app.command()
|
||||
def version() -> None:
|
||||
"""Print the artifactstore version and exit."""
|
||||
typer.echo(__version__)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
app()
|
||||
30
src/artifactstore/config.py
Normal file
30
src/artifactstore/config.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""Application configuration loaded from environment variables.
|
||||
|
||||
All settings are read from environment variables prefixed with
|
||||
``ARTIFACTSTORE_``. A ``.env`` file at the repository root is honoured for
|
||||
local development; see ``.env.example``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Top-level service configuration."""
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="ARTIFACTSTORE_",
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
database_url: str = "sqlite+aiosqlite:///./var/artifactstore.db"
|
||||
storage_local_root: str = "./var/storage"
|
||||
log_level: str = "INFO"
|
||||
|
||||
|
||||
def get_settings() -> Settings:
|
||||
"""Return a freshly-loaded :class:`Settings` instance."""
|
||||
return Settings()
|
||||
5
src/artifactstore/dataplane/__init__.py
Normal file
5
src/artifactstore/dataplane/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Data plane SPI and in-process implementation.
|
||||
|
||||
The SPI lands in ARTIFACT-STORE-WP-0001-T012. See ADR-0004 for the
|
||||
control-plane / data-plane contract that this module isolates.
|
||||
"""
|
||||
5
src/artifactstore/events/__init__.py
Normal file
5
src/artifactstore/events/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Append-only event log and materialised-view replayer.
|
||||
|
||||
Real implementation lands in ARTIFACT-STORE-WP-0001-T011. See ADR-0002 for
|
||||
the event-log-as-source-of-truth contract.
|
||||
"""
|
||||
5
src/artifactstore/identity/__init__.py
Normal file
5
src/artifactstore/identity/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Content addresses and digest abstraction.
|
||||
|
||||
Real implementation lands in ARTIFACT-STORE-WP-0001-T009. See ADR-0001 for
|
||||
the dual-digest contract (BLAKE3 primary, SHA-256 retained for interop).
|
||||
"""
|
||||
5
src/artifactstore/manifest/__init__.py
Normal file
5
src/artifactstore/manifest/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Package manifest model and canonical-CBOR codec.
|
||||
|
||||
Real implementation lands in ARTIFACT-STORE-WP-0001-T010. See ADR-0003 for
|
||||
the canonicalisation pin (RFC 8949 §4.2.2).
|
||||
"""
|
||||
6
src/artifactstore/registry/__init__.py
Normal file
6
src/artifactstore/registry/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""Registry orchestrator.
|
||||
|
||||
Real implementation lands in ARTIFACT-STORE-WP-0001-T013. The orchestrator
|
||||
combines identity, manifest, events, retention, and dataplane into the
|
||||
operations exposed by the HTTP API and CLI.
|
||||
"""
|
||||
5
src/artifactstore/retention/__init__.py
Normal file
5
src/artifactstore/retention/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Retention policy engine.
|
||||
|
||||
Seed classes land in ARTIFACT-STORE-WP-0001-T002 (data model). Active policy
|
||||
operations (extensions, holds, sweeper) land in workplan WP-0003.
|
||||
"""
|
||||
5
src/artifactstore/storage/__init__.py
Normal file
5
src/artifactstore/storage/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Storage adapter SPI and backend registry.
|
||||
|
||||
The SPI and local filesystem backend land in ARTIFACT-STORE-WP-0001-T003.
|
||||
The S3-compatible backend lands in workplan WP-0004.
|
||||
"""
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
5
tests/conftest.py
Normal file
5
tests/conftest.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Top-level pytest configuration.
|
||||
|
||||
Shared fixtures land in this module as the test suite grows. The scaffold
|
||||
keeps it empty so unit and integration suites configure themselves.
|
||||
"""
|
||||
0
tests/integration/__init__.py
Normal file
0
tests/integration/__init__.py
Normal file
5
tests/integration/conftest.py
Normal file
5
tests/integration/conftest.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Integration-test pytest configuration.
|
||||
|
||||
Integration suites require a database and storage backend; configuration
|
||||
fixtures land here as those layers come online in later tasks.
|
||||
"""
|
||||
0
tests/unit/__init__.py
Normal file
0
tests/unit/__init__.py
Normal file
1
tests/unit/conftest.py
Normal file
1
tests/unit/conftest.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Unit-test pytest configuration."""
|
||||
35
tests/unit/test_smoke.py
Normal file
35
tests/unit/test_smoke.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""Smoke tests asserting the scaffold imports cleanly."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typer.testing import CliRunner
|
||||
|
||||
import artifactstore
|
||||
from artifactstore.api.http import app as http_app
|
||||
from artifactstore.cli import app as cli_app
|
||||
from artifactstore.config import Settings, get_settings
|
||||
|
||||
|
||||
def test_package_version_exposed() -> None:
|
||||
assert isinstance(artifactstore.__version__, str)
|
||||
assert artifactstore.__version__
|
||||
|
||||
|
||||
def test_http_app_root_route_registered() -> None:
|
||||
routes = {getattr(r, "path", None) for r in http_app.routes}
|
||||
assert "/" in routes
|
||||
|
||||
|
||||
def test_cli_version_command_round_trips() -> None:
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli_app, ["version"])
|
||||
assert result.exit_code == 0, result.output
|
||||
assert artifactstore.__version__ in result.output
|
||||
|
||||
|
||||
def test_settings_defaults_loadable() -> None:
|
||||
settings = get_settings()
|
||||
assert isinstance(settings, Settings)
|
||||
assert settings.log_level
|
||||
assert settings.database_url
|
||||
assert settings.storage_local_root
|
||||
@@ -66,7 +66,7 @@ cli (just `artifactstore version`, `artifactstore migrate`, `artifactstore repla
|
||||
|
||||
```task
|
||||
id: ARTIFACT-STORE-WP-0001-T001
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "84209430-ec3b-4c5e-924e-019c25434230"
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user