tegwick 0a49798699 WP-0001-T011: append-only event log — write, fetch_since, tail, replay
src/artifactstore/events/:
- model.py: Event frozen dataclass (event_type, subject_kind, subject_id,
  actor, payload, payload_digest; sequence + created_at populated by the
  DB on write). make_event() helper computes payload_digest as raw BLAKE3
  (32 bytes) of payload. ViewWriter Protocol with reset() + apply().
- log.py:
  * write(connection, event) — inserts one row in the caller's transaction
    and returns Event with sequence + created_at populated via RETURNING.
  * fetch_since(connection, since_sequence, limit) — read events after a
    cursor in order.
  * tail(engine, since_sequence) — async-iterator long-poll over the log;
    SQLite uses interval polling, PG LISTEN/NOTIFY is a future workplan.
  * replay(engine, view_writer, reset=True) — drains the event log through
    a ViewWriter inside one transaction; returns the highest sequence
    applied.
- views.py: RegistryViewWriter — canonical event handlers shared by direct
  write and replay paths. Ships handlers for v1.package.created (inserts
  artifact_packages + retention_state) and v1.package.finalized (updates
  status, finalized_at, manifest_digest). Unknown event types tolerated;
  additional handlers register here as later tasks land.

src/artifactstore/db/schema.py: events.sequence type is now
BigInteger().with_variant(Integer(), 'sqlite') so SQLite's autoincrement
(INTEGER PRIMARY KEY rowid alias) works while PostgreSQL keeps BIGSERIAL.

tests/integration/test_event_log.py (6 cases):
- write() assigns monotonic sequence numbers (1, 2, ...) and a created_at.
- fetch_since(since_sequence=2) returns the ordered tail.
- tail() yields events and exits cleanly on consumer break.
- Direct write path (write + apply) and replay path produce byte-identical
  materialised state — the key ADR-0002 invariant.
- Replay handles multiple event types (package.created -> finalized).
- Unknown event types are tolerated (no-op apply).
- payload_digest equals BLAKE3 of payload.

Gates: ruff clean, mypy --strict clean on 36 files, 45 tests pass.
make migrate-fresh end-to-end ok.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 01:56:04 +02:00
2026-05-15 20:08:32 +02:00
2026-05-15 16:14:36 +00:00
2026-05-15 20:08:32 +02:00

artifact-store

Generic artifact registry and storage gateway for generated outputs, 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).

The shape is library-first (artifactstore Python package); the HTTP server and the CLI are thin consumers. Content is addressed by digest; state is authoritative in an append-only event log; materialised views are rebuildable.

Status

Scaffold landed. The core kernels and local FS backend follow in the remaining tasks of WP-0001.

Develop

Requires Python ≥ 3.12 and uv on the path.

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

Active workplans

Agent operating notes

See AGENTS.md for the StateHub-integrated session protocol, workplan conventions, and progress-logging contract.

Description
Generic shared service artifact store to integrat S3 Buckets or scalable selfhosted Storage based on Ceph.
Readme MIT-0 787 KiB
Languages
Python 99.4%
Makefile 0.4%
Mako 0.2%