Commit Graph

3 Commits

Author SHA1 Message Date
864f7f203c Add S3 backend and storage verification 2026-05-16 23:26:03 +02:00
28ec2922d3 WP-0001-T003: storage adapter SPI and local filesystem backend
src/artifactstore/storage/:
- spi.py: StorageBackend Protocol (backend_id, put, get, head, delete,
  health) and result dataclasses (StorageReceipt, StorageObjectMetadata,
  DeletionResult, BackendStatus). ObjectNotFoundError exception type.
- registry.py: backend lookup by string ID (register/get/list_backends/
  clear) per ADR-0004.
- backends/local.py: LocalBackend implementation.
  * Object layout <root>/<algorithm>/<hex[0:2]>/<hex[2:4]>/<hex>.
  * Atomic writes: tmpfile + fsync + rename (idempotent re-puts drain the
    stream without rewriting).
  * Defence in depth: resolves the final path and asserts it remains under
    the configured root.
  * Range reads honour HTTP-style inclusive (start, end) tuples.
  * health() returns disk usage via shutil.disk_usage and surfaces an
    unhealthy status when the root has disappeared.
  * delete() cleans up emptied shard directories opportunistically.

tests/unit/test_storage_local.py (14 cases): put/get round-trip; object
key layout matches blueprint; head returns metadata; head/get missing
raise ObjectNotFoundError; put is idempotent; delete returns True then
False; range read returns subrange; range read rejects invalid range;
health reports disk usage; health reports unhealthy when root vanished;
ContentAddress validation blocks path-traversal-flavoured inputs;
registry register/get/list/clear round-trip; idempotent re-put leaves
bytes intact.

Gates: ruff clean, mypy --strict clean on 41 files, 59 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 02:01:25 +02:00
a6b6746f91 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>
2026-05-16 01:30:22 +02:00