Implements ports.Sink over Postgres (pgx/v5 + pgxpool, DSN from env per estate convention). This is the primary sink (ADR-003) and the source of the engine's durable, cross-restart dedup — the in-engine processed map is process-lifetime only. - Migrations (golang-migrate, NNN_name.up/down.sql per estate convention, applied from an embedded FS on New): users, videos, transcripts, summaries, sink_deliveries. Every user-owned table carries user_id (Stage-0 per-user isolation promise, data-model.md). summaries has UNIQUE(user_id, video_id) — at most one summary per video; highlights / takeaways are jsonb. - Deliver upserts the summary idempotently on (user_id, video_id) (ON CONFLICT DO UPDATE) inside one tx with its sink_delivery row. Re- delivering the same summary updates in place, never duplicates or errors. - Dedup reads (store methods, not a new port): HasSummary(ctx,userID, videoID) and SeenVideoIDs(ctx,userID) — both user_id-scoped, so one user never sees another's videos. summaries.video_id is intentionally not FK-constrained to videos at Stage 0: the sink receives only a Summary, so the dedup key stands alone; video-row persistence is the engine/source's concern, deferred. Tested against a real in-process Postgres via embedded-postgres (real SQL: constraints, ON CONFLICT, jsonb, user_id scoping) — no docker, no live cluster, no creds, fully offline. Deps: golang-migrate/migrate/v4 and jackc/pgx/v5 (runtime), fergusstrange/embedded-postgres + stretchr/testify (test-only). go mod tidy raised the go directive to 1.25.0 (minimum required by the dep graph; estate elsewhere already runs 1.26.1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tapir
Watches a user's YouTube/Vimeo subscriptions and, when a subscribed channel posts a new video, summarizes it into highlights and takeaways using a local-first AI stack with an optional, per-user BYO-AI fallback. Standalone-first; feeding a personal knowledge base ("brain") is one optional sink, not the reason Tapir exists. Written in Go.
Status
Pre-code. The repository currently holds the guardrail documentation — vision, decisions, architecture, data model, and behavior specs — committed before implementation so the design intent is version-controlled and the build has something to be checked against.
Read these first (the guardrails)
| Doc | What it is |
|---|---|
VISION.md |
Product vision, principles, and the staged Definition of Success. Stage 0 ("useful to me") is the gate before any multi-user work. |
DECISIONS.md |
Architecture Decision Records (append-only). Why Go, why no Supabase, standalone-first, captions-first, etc. |
docs/architecture/architecture.md |
C4 context + container diagrams, key sequence diagrams, and the Clean Architecture layering (Mermaid). |
docs/data-model.md |
Entities and the per-user isolation model (Stage 0 / Stage 1 scope). |
docs/use-cases/ |
Gherkin .feature files — the BDD behavior spec that seeds the test suite. |
Approach
- Clean Architecture / ports & adapters. A provider- and sink-agnostic engine depends only
on interfaces (
VideoSource,Summarizer,Sink,SecretStore). YouTube, Vimeo, the AI router, the user store, and the brain sink are adapters. "Standalone vs homelab" is a wiring choice, not two codebases. - TDD/BDD. The
.featurefiles are the living behavior spec; the use-case core is tested through fake adapters. Behavior is specified as executable scenarios, not prose that drifts. - Trunk-Based Development. Commit directly to
main, one logical change per commit, every commit deployable (see ADR-009). CI is the quality gate.
Conventions
Reuses homelab conventions: Go, Dex for identity, ESO + 1Password for secrets, Postgres for persistence. No new auth or secrets system (ADR-002).
Next
First implementation step: scaffold the Go service (engine + interfaces + the copied llm
package), captions-first, with the store and brain sink adapters — decomposable into
independent units suitable for a Claude Code swarm. Tracked as the first build issue.