12 KiB
| created | path | project | status | tags | type | updated | worker | phase | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-05-15 | Sources/Homelab | postgres-consolidation-reflection-layer | active |
|
session-note | 2026-05-15 | 2 | 2a |
Postgres Consolidation — Worker 2, Phase 2a result
Light Phase 2a complete. Disruption-free: no consumer connections
broken, no live writers paused, no schema rename. openbrain is still
the live DB name; petalbrain rename is Worker 3's cutover.
What landed
Inside openbrain (the existing DB):
New roles (no grants on ob1 yet — that schema doesn't exist)
| Role | Schema-level grants | search_path |
|---|---|---|
vault_mcp |
OWNER of wiki |
wiki, public |
trellis_mcp |
OWNER of trellis |
trellis, public |
ob1_mcp |
USAGE + SELECT on wiki and trellis |
ob1, public (ob1 not yet created) |
lovebug |
USAGE + SELECT + INSERT/UPDATE/DELETE on wiki and trellis |
wiki, ob1, trellis, public |
All four: LOGIN, NOSUPERUSER, NOCREATEDB, NOCREATEROLE, NOREPLICATION, NOBYPASSRLS, INHERIT.
Default privileges configured so future tables owned by vault_mcp
(in wiki) and trellis_mcp (in trellis) inherit the same grant
model automatically.
Credentials at /opt/backups/postgres-consolidation/credentials.env
(mode 0600, herbyadmin-only). 64-char hex passwords. For KeePassXC
transcription.
Schemas in openbrain
-
wiki— restored from Phase 1herbylab-pg_dump, thenALTER SCHEMA herbylab RENAME TO wiki. Contains:- tables
alembic_version(1 row),artifacts(0 rows),projects(0 rows) - sequences
artifacts_id_seq,projects_id_seq(linked to IDENTITY columns) - enums
artifact_type,project_status,project_domain - functions
artifacts_set_updated_at,notify_project_change - triggers
artifacts_updated_at_bump,projects_notify - FK constraint
projects.artifact_id→artifacts.id - all OID references updated transparently across the rename
- tables
-
trellis— restored from Phase 1trellis-pg_dump(same name in source and target, no rename). Contains:- tables
alembic_version(1),events(50),tags(42),thread_tags(55),threads(13) - sequences
events_id_seq,tags_id_seq,threads_id_seq(OWNED BY linked) - FK chain across
events ⇒ threads,thread_tags ⇒ threads,thread_tags ⇒ tags,threads.parent_id ⇒ threads
- tables
-
public— untouched.public.thoughtsstill owned bypostgres, still receiving writes fromob1-watcher(75 rows at end of Phase 2a, vs 74 at Phase 1 dump start, vs 70 at Phase 0 recon —ob1-watcheris healthy). Worker 3 moves this toob1. -
public.embeddings— new, empty. Shared cross-schema embedding store per locked spec:public.embeddings ( id BIGSERIAL PRIMARY KEY, source_schema TEXT NOT NULL, source_table TEXT NOT NULL, source_id BIGINT NOT NULL, embedding VECTOR(768) NOT NULL, embedding_model TEXT NOT NULL, embedded_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (source_schema, source_table, source_id, embedding_model) )Indexes:
embeddings_pkey(id)embeddings_source_uniqUNIQUE (source_schema, source_table, source_id, embedding_model)idx_embeddings_hnswHNSW (embedding vector_cosine_ops) WITH (m=16, ef_construction=64) — matches the existingidx_thoughts_embedding_hnswconvention exactlyidx_embeddings_source(source_schema, source_table, source_id) — for orphan cleanup + source-aware queriesidx_embeddings_model(embedding_model) — for the multi-model coexistence case the plan calls out
Owned by
postgres(neutral shared infrastructure). All four service users have SELECT+INSERT+UPDATE+DELETE on the table and SELECT+USAGE on theembeddings_id_seqsequence. No FK — orphan cleanup is a separate periodic job (out of scope here per plan).
What Phase 2a did NOT do
- Did not rename
openbrain→petalbrain. That's a destructive cutover (live consumers must disconnect forALTER DATABASE RENAME). Deferred to Worker 3. - Did not create the
ob1schema. Worker 3 owns theALTER TABLE public.thoughts SET SCHEMA ob1step + accompanying shape rewrite. - Did not touch
public.thoughtsin any way. Still inpublic, still writable, still being written to. - Did not pause
ob1-watcherorob1-enricher. Both still live. - Did not modify any MCP container connection strings or env files.
All MCPs still connect to
openbrainas before. - Did not touch the source
herbylabortrellisdatabases. Both still exist on the instance with their original content; cleanup is Phase 8 of the plan.
Verification done
Row counts in openbrain match dump-source counts exactly
| Schema/Table | Live count | Dump-source count |
|---|---|---|
wiki.projects |
0 | 0 (herbylab) |
wiki.artifacts |
0 | 0 (herbylab) |
wiki.alembic_version |
1 | 1 (herbylab) |
trellis.threads |
13 | 13 |
trellis.events |
50 | 50 |
trellis.tags |
42 | 42 |
trellis.thread_tags |
55 | 55 |
trellis.alembic_version |
1 | 1 |
public.thoughts |
75 | (live; was 74 at dump time) |
public.embeddings |
0 | (new) |
Source herbylab DB and source trellis DB confirmed unchanged.
Permission boundary smoke tests (positive isolation per plan Phase 3)
All ran via direct postgres connection on
postgresql://<role>:<pw>@localhost:5432/openbrain:
| Test | Expected | Result |
|---|---|---|
vault_mcp SELECT wiki.artifacts |
succeed | OK |
vault_mcp INSERT trellis.threads |
DENY | ERROR: permission denied for schema trellis |
trellis_mcp SELECT wiki.artifacts |
DENY | ERROR: permission denied for schema wiki |
ob1_mcp SELECT wiki.artifacts + trellis.threads |
succeed | OK (counts 0 + 13) |
ob1_mcp INSERT wiki.artifacts |
DENY | ERROR: permission denied for table artifacts |
vault_mcp INSERT public.embeddings |
succeed | OK (id 1) |
ob1_mcp INSERT public.embeddings |
succeed | OK (id 2) |
trellis_mcp INSERT public.embeddings |
succeed | OK (id 3) |
lovebug INSERT public.embeddings |
succeed | OK (id 4) |
Smoke-test rows (4) deleted after verification;
public.embeddings back to 0 rows.
Surprises / notes
- Sequence ownership followed table. First pass on
ALTER OWNERfailed withcannot change owner of sequence "artifacts_id_seq" — Sequence "artifacts_id_seq" is linked to table "artifacts". The herbylab dump usesGENERATED ALWAYS AS IDENTITY, whose sequence is owned by the table and cannot be reassigned independently. Fixed by excludingrelkind='S'from the table loop; sequence ownership now follows the table automatically. Trellis sequences use SERIAL/OWNED BY which has the same behavior. Caught + handled in one retry; final state correct. ob1_mcp.search_path = ob1, publicis set now even though theob1schema doesn't exist yet. Once Worker 3 creates the schema in the same DB (whatever its final name), unqualified table refs fromob1-mcp's connection will resolve there automatically.- No
vault-mcpenv update was needed for Phase 2a. The currentHERBYLAB_DATABASE_URLinvault-mcppoints at hostnamevault-postgres(still anomalous — part of Travis's rename rollout) and DBherbylab. Neither is wired into the consolidated state yet. Phase 3 of the original plan handles MCP connection cutover.
Worker 3 readiness assessment
Worker 3's cutover window is the next destructive step. From the priming, Worker 3 must:
- Pause
ob1-watcher+ob1-enricher+ob1-mcp. - Take Phase 2-start fresh
pg_dumpofopenbrain,herbylab,trellisto/opt/backups/postgres-consolidation/with suffix-phase2-start-<UTC-timestamp>. Verify via test restore. ALTER DATABASE openbrain RENAME TO petalbrain(now safe — no live connections).- Connect to
petalbrainandALTER TABLE public.thoughts SET SCHEMA ob1, plus all dependent objects (indexes are intra-table so they follow; thematch_thoughtsfunction referencesthoughtsdirectly without schema qualification so search_path must includeob1when ob1_mcp calls it — already set; thethoughts_updated_attrigger follows the table;update_updated_at()function lives inpublicand should be moved or be referenced cross-schema). - Create
ob1schema ifSET SCHEMAdidn't auto-create it (SET SCHEMAdoes create the schema if it didn't exist — but actually it requires the schema to pre-exist; Worker 3 shouldCREATE SCHEMA ob1 AUTHORIZATION ob1_mcpfirst). - ob1-specific grants:
ob1_mcpownsob1.*;lovebuggets full RW;vault_mcpandtrellis_mcpget nothing. - Backfill embeddings: for each of the 75-ish rows in
ob1.thoughts, insert intopublic.embeddingswithsource_schema='ob1',source_table='thoughts',source_id=<row id>,embedding=<existing embedding column value>,embedding_model='nomic-embed-text'. - Decide: keep the
ob1.thoughts.embeddingcolumn post-backfill (redundant withpublic.embeddings) or drop it. Plan says all embeddings live inpublic.embeddings; suggests dropping. But that changes the row shape forob1-mcp's code. Worker 3 call. - Rewrite
ob1-mcp'sadd_thoughtandsearch_thoughtsfor the new shape (target tableob1.thoughts, embedding pushed topublic.embeddingsseparately, search joins). - Update connection strings on
ob1-mcp,ob1-watcher,ob1-enricherfrom DBopenbrainto DBpetalbrain. Watcher talks to ob1-mcp HTTP only, so its env may not need DB host change — verify. - Update
trellis-mcpandherbydev-mcp/vault-mcpconnection strings from their current DBs (trellis,herbylab) to the consolidatedpetalbrainwith their service-user credentials fromcredentials.env. - Resume
ob1-watcher+ob1-enricher. - Verify: row counts, semantic search query roundtrip, NOTIFY
trigger on
wiki.projectsstill emits.
Phase 2a outputs Worker 3 will need:
- Service user passwords in
/opt/backups/postgres-consolidation/credentials.env. - Phase 1 backup set in
/opt/backups/postgres-consolidation/plus the rollback runbook there. - This session note.
Holding pattern
ob1-watcher and ob1-enricher continue writing to
public.thoughts. Each minute that passes, the gap between the Phase
1 dump's thoughts snapshot (74) and the cutover-time count grows.
Phase 2-start fresh dump shrinks that gap to seconds when Worker 3
runs. Until then: no degradation, no risk; just normal capture.
Worker 2 standing by. No further actions until Worker 3 spawns or Travis redirects.