SPB Git

Repository Public

architecture.md 7.1 KB

# Architecture

text
                         Internet (TLS, Let's Encrypt)
                                   │
                    Caddy on BHS64 (MacLustr Tunnel gateway)
                                   │ WireGuard
                 ┌─────────────────▼──────────────────────────────┐
                 │ node M2M32c (macOS, PM2, placed by mld)        │
                 │                                                │
  visitor ─────▶ │ clip-web   Next.js 16 (App Router)             │
  admin   ─────▶ │   public cinema  /, /feed, /film, /series …    │
                 │   studio         /studio/* (session + RBAC)    │
                 │   API            /api/* (public + studio)      │
                 │   media          /media/* (range, signed)      │
                 │        │ enqueue            ▲ read             │
                 │        ▼                    │                  │
                 │ Redis 8 (BullMQ)      PostgreSQL 17 (db clip)  │
                 │        │                    ▲                  │
                 │        ▼                    │                  │
                 │ clip-worker  Node (tsx)                        │
                 │   pipeline stages, scheduler, GC, backups      │
                 │   FFmpeg / ffprobe                             │
                 │        │                                       │
                 │        ▼                                       │
                 │ asset store  ~/data/clip-spboucher-ai          │
                 │   content-addressed (SHA-256), outside the repo│
                 └────────┬───────────────────────────────────────┘
                          │ HTTPS (server side only)
                          ▼
                 xAI API: grok-4.3 · Grok Imagine image/video · TTS

# Processes

Process Role
clip-web Next.js server: public pages (ISR-free, server-rendered from PostgreSQL), studio, API, media delivery with HTTP range
clip-worker BullMQ workers (pipeline queue, concurrency from the budget policy), the programming scheduler, garbage collection, nightly backup

They share one codebase (src/). Only server code imports src/lib/env.ts; the client bundle never sees a secret (scripts/secrets-scan.ts greps the built client chunks in CI).

# Pipeline

text
Idea ─▶ Pitches ─▶ [Research] ─▶ Script ─▶ Shot plan + bibles ─▶ Keyframes ─▶ Voice ─▶ Video ─▶ Assembly ─▶ QA ─▶ Approval ─▶ Publication
        Creative    Research     Screen-   Storyboard / Visual    Visual      Audio    Motion    Editor      Critic   policy     Publisher
        Director    Editor       writer    Director               Director    Director Director

Stage order differs from the brief in one place: voice is synthesised before video. Narration length is the only reliable clock, so each shot's video duration is derived from its measured narration (cheap call first, expensive call sized correctly, no re-buying a clip because it was a second short).

Each stage is a pure function (project state) → (rows + assets), run by one BullMQ job:

  • Typed contracts — src/lib/contracts.ts (Zod): CreativeBrief, Pitch, Script, ShotPlan, Bible, QualityReport, FilmManifest. LLM output is parsed against the schema; a parse failure is a stage failure.
  • Idempotency — every paid call goes through runProviderJob() (src/pipeline/provider-job.ts) with an idempotency key = SHA-256 of (kind, model, canonical input). The provider_jobs row is unique on that key: a retry or a double click finds the finished row and reuses its asset; a crashed video job is resumed from its stored request_id rather than resubmitted.
  • Budget gate — before any paid call: kill switch, per-shot / per-film / daily / monthly ceilings, and the estimate from src/providers/pricing.ts. A refusal raises BudgetError and parks the project in blocked.
  • Bounded retries — HTTP layer: exponential backoff + jitter, 3 attempts, circuit breaker per endpoint (src/providers/http.ts). Shot regeneration: capped by max_regenerations_per_shot, then human approval.
  • Provenance — each provider_jobs row stores provider, model, settings, prompt, input hash, timestamps, cost and output asset. The published FilmManifest is a public projection without prompts.

Autonomy modes (per project): manual (each stage is started by an operator), assisted (free and cheap stages run on their own, the pipeline pauses before paid video generation), autonomous (runs to QA inside the budget). Publication always requires a human approval unless auto_publish is explicitly enabled in the policy, and never happens on a failed QA without a recorded override.

# Data model

db/migrations/001_init.sql. Groups:

  • Identity: users, sessions, audit_log, rate_limits
  • Editorial: series, collections, collection_films, tags, film_tags
  • Production (private): projects (brief), concept_candidates, script_versions, bibles, storyboards, shots, prompt_versions, provider_jobs (= generation attempts), assets, audio_tracks, subtitle_tracks, film_versions (manifest), quality_reviews, approvals
  • Public: films (metadata only), publications, reactions, analytics_events, reports
  • Control: budget_policy, cost_entries, schedules, settings

Public pages query films + delivery assets only; prompts live in shots / prompt_versions / provider_jobs and are never selected by a public query (src/lib/films.ts is the single public read path).

# Storage

AssetStorage (src/lib/storage.ts) with one driver today: a content-addressed filesystem store (ASSET_ROOT/ab/cd/<sha256>.<ext>). Published delivery assets are served at /media/<sha>.<ext> with range requests and immutable caching; everything else needs a studio session or an HMAC-signed, expiring URL (getSignedUrl). An S3 driver is the documented migration path (same interface) — it is not implemented, because the cluster has no object store and the brief asks to confirm real infrastructure first.

# Search and recommendations

No xAI embedding model exists (live check), so CLIP ships an explainable lexical embedding: hashed TF-IDF over title, synopsis, thesis, tags, series and style (512 dimensions, cosine). Search = PostgreSQL full-text rank

  • trigram similarity + cosine, and every result reports which signal matched. Related films = cosine + shared tags/series, with the reason shown ("same series", "shared tags: …"). No engagement signal feeds the ranking.

# Migration path to distributed workers

Workers are stateless apart from the asset store. To scale out: run clip-worker on more nodes against the same Redis/PostgreSQL and move AssetStorage to S3-compatible storage (MinIO on the LAN). docker-compose.yml describes that topology for a non-macOS host.

Files main
Menu

Theme