4.6 KiB
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-lab-advisor | 01 | store |
|
|
|
|
|
|
Phase 06 Plan 01: PostgreSQL Store Package Summary
One-liner: pgx/v5 connection pool with idempotent schema migrations and typed CRUD for conversations + messages, backed by live PostgreSQL at 10.5.0.109.
Tasks Completed
| # | Name | Commit | Files |
|---|---|---|---|
| 1 | Add pgx/v5 dep and create Store with RunMigrations | 4bc22dc |
store.go, migrations.go, go.mod, go.sum |
| 2 | Conversation and message CRUD methods | 623cff0 |
conversations.go, store_test.go |
What Was Built
internal/store/store.go
Store struct wrapping *pgxpool.Pool. NewStore(ctx, dsn) opens the pool and pings before returning. Close() drains the pool. Pool() accessor gives migrations and tests direct pool access.
internal/store/migrations.go
RunMigrations(ctx, pool) executes two CREATE TABLE IF NOT EXISTS statements in dependency order (conversations first, then messages which references it via FK). Safe to call on every startup.
Schema:
conversations(id UUID PK DEFAULT gen_random_uuid(), started_at TIMESTAMPTZ DEFAULT now(), model TEXT DEFAULT '')messages(id UUID PK, conversation_id UUID FK REFERENCES conversations ON DELETE CASCADE, role TEXT CHECK IN ('user','assistant','system'), content TEXT, created_at TIMESTAMPTZ DEFAULT now())
internal/store/conversations.go
Types: Message, Conversation, ConversationSummary, ErrNotFound.
Methods on *Store:
CreateConversation(ctx, model) (id string, err error)AddMessage(ctx, conversationID, role, content) (id string, err error)— DB CHECK constraint rejects invalid rolesGetConversation(ctx, id) (*Conversation, error)— LEFT JOIN to handle zero-message conversations; returnsErrNotFoundon missListConversations(ctx) ([]ConversationSummary, error)— COUNT(m.id) aggregation with LIMIT 100 soft guard
internal/store/store_test.go
Integration tests (build tag: integration) covering all behaviors from the plan spec. Each sub-test creates its own fixtures and cleans up via deferred DELETE. Tests connect to the real PostgreSQL instance.
Deviations from Plan
None — plan executed exactly as written. The dot-import (import . "git.georgsen.dk/hwlab/internal/store") in the test file is a minor stylistic choice for readability in external test packages, consistent with common Go testing practice for the same package's integration tests.
Threat Mitigations Applied
| Threat ID | Mitigation |
|---|---|
| T-06-01-01 | All queries use $1/$2 parameterized args via pgx; zero string interpolation in SQL |
| T-06-01-02 | NewStore wraps error without including DSN; only pgx error text is forwarded |
| T-06-01-03 | DB-level CHECK (role IN ('user','assistant','system')) — verified by test |
| T-06-01-04 | LIMIT 100 added to ListConversations |
Known Stubs
None — all methods are fully wired to the live database.