5.0 KiB
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)
- 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.
- Fixed statuses, no per-project workflow customization. Consistent UX, no admin screens, kanban columns are stable. Labels absorb remaining taxonomy needs.
- 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.
- Owner/member + instance admin only. A viewer role and permission matrices were considered and rejected.
- PostgreSQL over SQLite (owner choice; first iteration used SQLite). Compose file keeps ops one-command.
- Meilisearch for search (owner choice) rather than Postgres tsvector — better typo-tolerant UX, at the cost of a second service and event-driven sync.
- 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).
- 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.
oauth_accountstable over a singleprovidercolumn (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.- SSE over WebSockets. One-way push is all the app needs; SSE is stdlib-friendly, auto-reconnecting, proxy-tolerant.
Architecture decisions
- 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 LOCKEDkeeps accidental multi-instance safe, but the SSE hub is per-process). - 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.
- 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.
- Polymorphic
links/attachmentstables (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). - Native Postgres enums for statuses/priorities. Safe because sets are fixed by product decision (#2). Adding a value later is one
ALTER TYPE … ADD VALUEmigration. - Fractional lexicographic ranks (
board_rank/epic_ranktext) for drag ordering — O(1) writes per move, no renumbering transactions; rebalancing deferred (not needed at target scale). ON DELETE RESTRICTeverywhere + 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.- Denormalized
project_idon audit_log so activity feeds are one indexed query. Zero-maintenance denormalization (audit rows are immutable). - Per-project issue numbers via a
next_issue_numbercounter updated in the insert transaction — no sequence-per-project, no gaps from rollbacks visible in practice, concurrency-safe. - 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
- No project import (export format is versioned and designed to be importable later).
- No wiki page history, no comment edit-history;
updated_at+ conflict warning only. - No email invitations — members are picked from existing instance users (registration is open on the instance).
- No server-side thumbnails, no image processing.
- No custom fields, votes/likes, due-date buckets, CSV-feed tokens, videoconferencing, telemetry — Taiga features consciously excluded.
- Audit log kept forever (first iteration left retention open; forever is the safe default at this scale — revisit if instances grow).
- Incoming push-event automation (commit messages moving issues) recorded but ignored in v1; only PR-merged
closes #Nacts.