wiki-vault/Sources/Homelab/herbylab-project-management.md

174 lines
8.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
created: '2026-05-11'
path: Sources/Homelab
project: herbylab-project-management
status: active
tags:
- mcp
- automation
- n8n
- obsidian
type: project-plan
updated: '2026-05-11'
---
## Goal
Extend `herbylab` MCP to manage projects and notes as first-class artifacts backed by Postgres, with Trello as the visual projection. Vault remains the immutable content store in Phase 1; Postgres becomes sole source in a later phase.
Solves the pain of status hygiene and project visibility — 44+ vault entries, almost all flagged `active`, no aggregate view, no real query surface, frontmatter status edits feel wrong.
## Locked Decisions
**Three-layer model.** Mirrors Trellis:
- Vault md files — content, narrative, immutable. Feeds wiki-vault.
- Postgres (`herbylab` schema) — state, metadata, queryable index.
- Trello — visual projection of project state. Read-mostly.
**Single MCP, not split.** Project tools live alongside vault tools in `herbylab`. One connector, one tool surface.
**Polymorphic tool shape.** `create_artifact(kind, ...)` and `update_artifact(kind, slug, ...)` dispatch on `kind`. Adds future artifact types without growing the tool surface.
**Postgres co-located with Trellis.** Same instance, separate schema (`herbylab.*`). Trellis's schema-namespace pattern leaves room for this by design.
**Phase 1: dual-write, immutable entries.**
- `create_artifact` writes Postgres row + vault file atomically.
- No content updates via MCP — entries immutable once created.
- `update_artifact` only mutates metadata (status, tags, title, summary, domain).
- Vault stays the read surface for Obsidian and wiki-vault.
- New phases/addenda get new files, matching existing pattern (`ob1-deployment` + `ob1-main-deployment`).
**Phase N: vault retires, Postgres becomes sole source.**
- Viewer TBD — wiki-vault adaptation or new web UI served from Postgres.
- Out of scope for this plan.
**Source of truth for status: Postgres.** Status changes mid-conversation via `update_artifact` — never a file edit.
**Status propagates via NOTIFY → n8n → Trello.** Postgres trigger fires `pg_notify('project_changed', ...)` on insert/update. n8n LISTENs, projects to Trello.
**Frontmatter scope simplifies.** Vault frontmatter holds identity (`project`, `type`, `path`, `tags`, `created`). Status and `updated` leave frontmatter — Postgres concerns.
## Schema (Phase 1)
```sql
CREATE SCHEMA herbylab;
CREATE TYPE herbylab.artifact_kind AS ENUM ('note', 'project');
CREATE TYPE herbylab.project_status AS ENUM ('active', 'paused', 'blocked', 'completed', 'archived');
CREATE TYPE herbylab.project_domain AS ENUM ('homelab', 'dev', 'venture', 'reference');
CREATE TABLE herbylab.artifacts (
slug TEXT NOT NULL,
kind herbylab.artifact_kind NOT NULL,
title TEXT NOT NULL,
domain herbylab.project_domain,
tags TEXT[] DEFAULT '{}',
vault_path TEXT NOT NULL,
body TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (kind, slug)
);
CREATE TABLE herbylab.projects (
slug TEXT PRIMARY KEY,
status herbylab.project_status NOT NULL DEFAULT 'active',
summary TEXT,
trello_card_id TEXT,
FOREIGN KEY (kind, slug) REFERENCES herbylab.artifacts (kind, slug)
DEFERRABLE INITIALLY DEFERRED
);
CREATE INDEX idx_artifacts_kind ON herbylab.artifacts(kind);
CREATE INDEX idx_artifacts_tags ON herbylab.artifacts USING GIN(tags);
CREATE INDEX idx_projects_status ON herbylab.projects(status);
```
`body` stores the markdown blob. Vault file holds the same content. Dual-write keeps them in lockstep; immutability eliminates drift risk.
## NOTIFY trigger
```sql
CREATE OR REPLACE FUNCTION herbylab.notify_project_change() RETURNS TRIGGER AS $$
DECLARE payload JSON;
BEGIN
payload := json_build_object(
'op', TG_OP,
'slug', COALESCE(NEW.slug, OLD.slug),
'status', COALESCE(NEW.status, OLD.status)
);
PERFORM pg_notify('project_changed', payload::text);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER projects_notify
AFTER INSERT OR UPDATE OR DELETE ON herbylab.projects
FOR EACH ROW EXECUTE FUNCTION herbylab.notify_project_change();
```
## Tool surface
**Vault tools (existing, unchanged):**
- `save_vault_note`
- `get_vault_project`
- `check_vault_file`
- `list_vault_projects`
- `get_vault_schema`
**Artifact tools (new):**
- `create_artifact(kind, slug, title, vault_path, content, domain?, tags?, summary?)` — writes Postgres row + vault file. For `kind="project"`, also inserts `projects` row with default `status='active'`.
- `update_artifact(kind, slug, **fields)` — mutates metadata only (status, title, tags, summary, domain). Never touches body or vault file.
- `get_artifact(kind, slug)` — returns row + linked file path.
- `list_artifacts(kind?, status?, domain?, tag?)` — filtered query.
`list_vault_projects` deprecates in favor of `list_artifacts` once cutover completes.
## Open Items
- [ ] Postgres deployment shape — depends on Trellis decision (LXC vs container)
- [ ] Migration tooling — match whatever Trellis lands on
- [ ] Trello board layout — single board with status lists vs board per domain
- [ ] Duplicate cleanup before backfill: `ob1-deployment` + `ob1-main-deployment`, `editor-stack-upgrade` (Homelab + Venture), `content-hub-phase5-architecture` + `-planning`
- [ ] Naming pin-down: memory has both `herbylab` and `herby-dev` — settle on one
## Phases
### Phase 1 — Infrastructure
- [ ] Postgres instance available with `herbylab` schema on the Trellis-shared instance
- [ ] Migration tooling chosen (match Trellis decision)
- [ ] Apply schema, types, indexes, NOTIFY trigger
### Phase 2 — MCP tools
- [ ] `create_artifact` (dual-write, transactional)
- [ ] `update_artifact` (metadata only)
- [ ] `get_artifact`, `list_artifacts` (read paths)
- [ ] Tag normalization mirroring Trellis pattern
### Phase 3 — Trello projection
- [ ] Trello board structure: lists for `active`, `paused`, `blocked`, `completed`. Labels for domain.
- [ ] n8n workflow: Postgres LISTEN node on `project_changed`
- [ ] Branch on op: INSERT → create card, UPDATE → move/edit card, DELETE → archive
- [ ] Store `trello_card_id` back to `projects` row on creation
### Phase 4 — Backfill and cutover
- [ ] Script walks existing vault project-plan files, populates `artifacts` + `projects` rows
- [ ] Status defaults to current frontmatter value (42 of 44 are `active`)
- [ ] Manual triage pass in Trello — drag to honest columns
- [ ] Strip `status` / `updated` from vault frontmatter going forward
- [ ] Backfill of existing frontmatter optional
### Phase N — Vault retirement
- Deferred. Revisit once Phase 14 are operational and read patterns are clearer.
## Notes
**Why dual-write in Phase 1.** Keeps two readers (Obsidian, wiki-vault) functional during transition. Immutability of entries kills the main drift risk — entries land once, neither side updates content. Update tool only touches metadata, which lives only in Postgres.
**Why immutable entries.** Matches existing behavior — new phases get new files (`-phase2`, `-addendum`) rather than edits. Codifying this as a constraint removes a class of bugs and makes Phase N (Postgres-only) trivially achievable later.
**Why Postgres co-located with Trellis.** Trellis's plan explicitly chose schema-namespacing to leave room for consolidation. Same instance, separate schemas, no new infrastructure. Backup/recovery story is shared.
**Why not Trellis itself for project tracking.** Trellis is for conversation threads — coarse session-level checkpoints, append-only events, paused/resumed work. Projects are different: durable, named, status-bearing, visualized externally. Different lifecycle, different reader, different surface.
**Why the polymorphic tool shape.** Avoids tool sprawl. Adding a future artifact type (task, decision log, etc.) is a `kind` value, not a new MCP tool. Tradeoff is a fatter schema in the tool description.