Files

5.0 KiB

17 — Decision Log

Judgment calls made during planning, with rationale. Build agents: do not relitigate these. If implementation reveals a hard blocker, note it here and surface it to the owner instead of silently deviating. (Prose by design — these aren't work items.)

Product decisions (owner-confirmed)

  1. Two work-item levels (epics + issues), not Taiga's four. Taiga's Epic→Story→Task + Issue split is its single biggest complexity driver (4 status sets, 4 custom-field sets, promotion paths). Sub-issues + epics cover the same needs at small scale.
  2. Fixed statuses, no per-project workflow customization. Consistent UX, no admin screens, kanban columns are stable. Labels absorb remaining taxonomy needs.
  3. No sprints, backlog view, swimlanes, WIP limits, custom roles, or discovery/social features. Deliberately out of scope for <10-user teams; Taiga's enterprise surface was surveyed and dropped.
  4. Owner/member + instance admin only. A viewer role and permission matrices were considered and rejected.
  5. PostgreSQL over SQLite (owner choice; first iteration used SQLite). Compose file keeps ops one-command.
  6. Meilisearch for search (owner choice) rather than Postgres tsvector — better typo-tolerant UX, at the cost of a second service and event-driven sync.
  7. Story points (1,2,3,5,8,13) over t-shirt sizes. Owner preference; points also make epic rollups meaningful (points sums shown on epic progress).
  8. Sessions + PATs (not JWT). Server-side scs sessions are simplest/safest for a same-origin SPA; PATs cover scripting. PATs have no scopes in v1 — they act as the user; revisit if the API gains external consumers.
  9. oauth_accounts table over a single provider column (owner-approved). Kills the duplicate-account problem documented in the first iteration; enables local+GitHub+Gitea on one identity. Link only on verified provider email.
  10. SSE over WebSockets. One-way push is all the app needs; SSE is stdlib-friendly, auto-reconnecting, proxy-tolerant.

Architecture decisions

  1. Hand-rolled transactional outbox + Postgres job queue, not River / external brokers / fire-and-forget goroutines. Goroutines lose work on crash (unacceptable for notifications/webhooks/search); River adds dependency weight for a <10-user single-instance app. ~300 LOC buys at-least-once delivery surviving restarts. Explicit constraint: one app instance (SKIP LOCKED keeps accidental multi-instance safe, but the SSE hub is per-process).
  2. Audit log written synchronously in the mutation transaction (exactly-once), while everything else is async via the outbox. Activity feeds are therefore never stale or lossy.
  3. Single firehose SSE stream per user, client filters. Per-page topic subscriptions would cut noise but add protocol complexity; at <10 users the firehose is trivially cheap.
  4. Polymorphic links/attachments tables (resource_type + resource_id) replacing three tables each. Loses FK integrity on the target; acceptable because hard deletes flow exclusively through the culling job, which handles cascades (and must anyway, for disk files).
  5. Native Postgres enums for statuses/priorities. Safe because sets are fixed by product decision (#2). Adding a value later is one ALTER TYPE … ADD VALUE migration.
  6. Fractional lexicographic ranks (board_rank/epic_rank text) for drag ordering — O(1) writes per move, no renumbering transactions; rebalancing deferred (not needed at target scale).
  7. ON DELETE RESTRICT everywhere + app-level culling cascades (carried from first iteration). A DB cascade cannot delete attachment files from disk; one deletion path (the cull job) keeps rows and files consistent.
  8. Denormalized project_id on audit_log so activity feeds are one indexed query. Zero-maintenance denormalization (audit rows are immutable).
  9. Per-project issue numbers via a next_issue_number counter updated in the insert transaction — no sequence-per-project, no gaps from rollbacks visible in practice, concurrency-safe.
  10. Server extracts mentions/#refs; client renders markdown. Rendering in one place (client) avoids double-sanitization drift; the server only needs entities for notifications, which must not depend on client honesty.

Scope cuts (v1) — revisit only when the owner asks

  1. No project import (export format is versioned and designed to be importable later).
  2. No wiki page history, no comment edit-history; updated_at + conflict warning only.
  3. No email invitations — members are picked from existing instance users (registration is open on the instance).
  4. No server-side thumbnails, no image processing.
  5. No custom fields, votes/likes, due-date buckets, CSV-feed tokens, videoconferencing, telemetry — Taiga features consciously excluded.
  6. Audit log kept forever (first iteration left retention open; forever is the safe default at this scale — revisit if instances grow).
  7. Incoming push-event automation (commit messages moving issues) recorded but ignored in v1; only PR-merged closes #N acts.