wiki-vault/Sources/Dev/2026-06-21-pbs-content-hub-sync-2026-06-20-21.md

13 KiB

created path project tags type
2026-06-21 Sources/Dev pbs-membership-platform-overview
pbs
membership
content-hub
pbs-video-manager
mysql
schema
seeder
docker
ansible
garden
session-notes

Outcome

Continuing the LOE1 push from 2026-06-19 (see pbs-membership-phases-1-2-3a-deploy). Today's chain (2026-06-20 → 2026-06-21) was supposed to be a quick fix for one pbs-api crash but became a 4-worker reconciliation of the content-hub schema across three sources of truth. Final state: sandbox is fully synced to current pbs-video-manager main — content hub schema, app, and seeder all in lockstep.

Workers run this chain (W7 → W10, plus the in-flight W11 for Phase 3b):

  1. W7 — 00_base_schema.j2 install + retire 01 (2026-06-20 → 2026-06-21). Travis provided a fresh prod dump of pbs_automation. Placed as 00_base_schema.j2 (478 lines), deleted the stale 01_pbs_custom_tables.j2, applied to the live sandbox after a DROP DATABASE pbs_automation (provably empty per COUNT(*)=0). pbs-api crash-loop resolved — root cause was a signed/unsigned FK type mismatch (instagram_posts.pbs_post_id signed BIGINT vs stale 01's pbs_recipes.post_id bigint UNSIGNED), not the reported "missing pbs_recipes". Branch fix/pbs-base-schema @ 3b524f9 on gitea.

  2. W8 — enable pbs-seeder (2026-06-21). Uncommented the seeder import in roles/dockers/tasks/main.yml, provisioned the pbs-seeder container. Three distinct failures fixed in-scope: (a) compose --profile seed interaction with shared common/deploy.yml doing up -d with no default-profile service (added container_skip_start flag); (b) vendored seeder models ahead of deployed schema; (c) stale pbs-api image whose /api/seed predated project_types support. W8 trimmed the seeder to the 10-model subset and added a direct-DB _seed_project_types workaround — got data flowing but masked the deeper drift. Branch feat/enable-pbs-seeder @ 5412b1a on gitea. Content hub UI started rendering populated cards.

  3. W9 — content hub schema sync (step 1 of 2) (2026-06-21). Derived 01_content_hub.j2 from pbs-video-manager/sql/migrations/001_scene_management.sql (correctly chose authoritative SQL over the broken ORM). Added 6 tables + 2 columns + 5 FKs + a small Prep default-scene seed, all idempotent via IF NOT EXISTS and information_schema-guarded ALTERs (because MySQL 8 doesn't support ADD COLUMN IF NOT EXISTS — that's MariaDB-only). Applied to sandbox; pbs-api still healthy. Then stopped at the upstream blocker: pbs-video-manager/pbs_manager/db/models.py on local main is half-reverted — the synthetic-base commit 7083e2d from the promote-branch merge stripped 191 lines (6 scene/metadata classes + Project.outline/Project.treatment + relationships); never restored. App boots because api/routes.py doesn't import import_service/outline_service — they're stranded dead code. Branch feat/content-hub-schema-sync @ 55ba539 on gitea.

  4. W10 — forward-fix + close out content-hub chain (2026-06-21). Touched both repos (cwd /opt/projects/pbs/). pbs-video-manager fix: branch fix/restore-scene-metadata-models → merged --no-ff to main at 429383d. Restored 6 classes + 2 columns + 4 relationships, AND the Phase-8 STATUSES tuple (had silently lost planned/scripted). Hand-spliced rather than 3-way merge (3-way would have deleted the instagram models — synthetic base has them, last-good state c7856d6 doesn't). Pushed to gitea main + master, GitHub untouched. pbs-api rebuilt on the sandbox (force-recreate; herbylab is built-image mode, dev-mode is pbswp-group only). Recovered from a latent init_db() create_all multi-worker race (self-healed via docker restart; latent pre-existing flake). Re-vendored seeder models — full 9-table restoration (the 6 + 3 Phase-6 ones W8's trim had also dropped). Un-trimmed db_seeder.py, removed W8's _seed_project_types workaround. Extended 01_content_hub.j2 with 3 additive Phase-6 columns (platform_posts.title, projects.trello_card_id, projects.trello_last_synced_at) when the seed crashed on Unknown column 'title' — judged in-scope (additive, idempotent, same pattern as W9). Re-seeded with SEEDER_RESET=true. All 16 tables populated, including 4 scenes, 6 scene_items, 2 default_scenes, 6 default_scene_items, 4 project_metadata_fields, 11 project_metadata rows. Garden side unaffected (regression-checked: /the-garden/ 302; /api/interactions/check 401). wordpress-install branch feat/content-hub-schema-sync @ af34f4c on gitea.

  5. W11 — Phase 3b: Saved Recipes card live count (in flight at time of writing). Wires the Garden home's Saved Recipes card subtitle to a real count(user_interactions) instead of the placeholder. Branch feat/phase-3b-saved-count off feat/phase-3a-garden-skeleton. Lands in thread #602.

Topics Covered

  • The "MariaDB" note in 582 was wrong. No MariaDB anywhere — herbylab and prod are both MySQL 8. The 582 reference came from somewhere stale (possibly an earlier rough draft). Worth removing if 582 gets re-checkpointed.
  • pbs_automation schema management had three sources of truth (01_pbs_custom_tables.j2 in wordpress-install, SQLAlchemy models in pbs-video-manager, plus Travis's hand modifications in prod). The mismatch chain we untangled today is the predictable outcome of that. Travis raised the Alembic-centralization conversation mid-session and asked Lovebug to consider scoping it as a separate project — answer was "yes, exhibit A is right in front of us" (the silent ORM revert that nobody noticed because dead-code services masked it). Not yet scoped into a vault plan — left open at session end.
  • The synthetic-base commit pattern from 585's promote-branch merge has a real failure mode: any file that exists in the synthetic base AND the last-good upstream state AND was being patched will silently end up at whichever wins the manual merge — usually the synthetic base, because it's the "newer" commit in the branch's eyes. Both models.py and conftest.py got hit by this. The hand-splice diff strategy W10 used (against the last-good upstream commit c7856d6, not the synthetic base) is the right reconciliation method for future cases.
  • Seeder ergonomics: the seeder is a one-shot container behind compose --profile seed. The shared common/deploy.yml task in the dockers role ran up -d with no service argument, which fails on profile-gated services because no default-profile services exist. W8 fixed this with a container_skip_start flag — generic, useful for any future profile-gated containers.
  • MySQL 8 idempotency idioms: CREATE TABLE IF NOT EXISTS and CREATE USER IF NOT EXISTS work natively. ADD COLUMN IF NOT EXISTS does NOT (MariaDB-only); use information_schema.COLUMNS guarded prepared statements instead. ADD CONSTRAINT IF NOT EXISTS similarly doesn't exist; either guard via information_schema or accept that the patch is first-time-init-only.
  • W10's scope expansion within a single worker (vs the W9→W10 split originally planned) proved out the "keep interconnected work in one worker" feedback — W10's extending 01_content_hub.j2 mid-flow when the seed crashed was a judgment call only possible because the same context had the schema patch, the seeder code, and the live sandbox state all in mind. A handoff to a second worker would have stalled there.

Key Learnings

  • a-review picks up stale working-tree files. W10's a-review on the seeder un-trim flagged a CRITICAL + WARNING that turned out to be false positives — it had ingested a stale ansible.log from before the schema patch landed and "found" the old Unknown column 'projects.outline' crash. Verification against the live DB voided both findings. Worth knowing for future runs — if a-review flags something dramatic, check whether it's reading old logs or scratch files in the working tree before reacting.
  • docker_compose_v2 doesn't rebuild on source changes by default (already learned in W3's deploy report; reinforced this session). Member-garden has container_compose_build: always; pbs-api doesn't (per W10's mechanism notes — required manual docker compose build --no-cache && up -d --force-recreate). Worth normalizing across containers.
  • The github role pulls master, not main, per the dockers role's github_repo_version default. To make a "main-as-canonical-source-of-truth" workflow work, you either push to both branches or change the role default. W10 did both-branches as the lowest-friction path.
  • pytest can't run pbs-video-manager scene-management tests because conftest.py lost its session/project fixtures in the same synthetic revert that hit models.py. Fixture reconciliation is a separate cleanup — flagged.
  • init_db() create_all racing across gunicorn workers is a real flake in built-image mode (dev-mode bind-mount + single-process avoids it; non-dev multi-worker hits MySQL 1050 on the first boot, then docker restart policy recovers). Latent pre-existing; should be gated to a single worker or moved out of app-boot entirely (init-SQL layer authority).

Follow-ons

From W7-W10 specifically

  • pbs-video-manager/conftest.py fixture reconciliation — same synthetic revert dropped session/project fixtures. The current conftest uses a Flask-app paradigm with make_* fixtures only. Scene-management pytest suite cannot run until reconciled. Last good state is c7856d6 for the conftest too.
  • init_db() multi-worker race in non-dev mode — gate create_all to a single worker (gunicorn --preload + first-worker-only pattern, or move schema authority entirely to the init-SQL layer).
  • Normalize container_compose_build: always across the dockers role for containers that build from clones (pbs-api especially). Already in place for member-garden; pbs-api needed manual override this session.
  • pbs-video-manager two-branch confusion — local main, gitea main, gitea master, GitHub origin, GitHub main, GitHub master. Different things on each. Worth a cleanup pass: pick main as canonical, retire master, update the github role default.

Schema management (the bigger conversation Travis raised)

  • Scope an Alembic-centralization vault plan for pbs_automation. Same pattern member-garden already uses for pbs_garden. Alembic in pbs-video-manager owns the schema; init-SQL in wordpress-install shrinks to CREATE DATABASE + CREATE USER + grants. Drift between sandbox/prod becomes a migration backlog (alembic current), not silent column divergence. Today's session is exhibit A for why this matters. One-time cost: retro-generate Alembic baseline from current prod with --autogenerate + manual review pass to capture Travis's hand-rolled prod changes. Maybe a half-day of work; permanent ergonomics win. Travis acknowledged the case; not yet scoped into a doc.

Carryover (from yesterday's session note, still open)

  • CSRF nonce on save/unsave — Phase 2 production-readiness; needs X-WP-Nonce contract added.
  • member-garden migrations not automated — bake into image OR add ansible task.
  • X-Forwarded-Proto not honored in member-garden Flask — ProxyFix.
  • WP logout confirmation nonce — needs WP-side mu-plugin route for seamless logout.
  • WPCode save-button browser test — needs real recipe content on sandbox + browser session.
  • Brand color + Sunnie SVG assets for save button + garden.
  • pbs-video-manager GitHub push — local main + gitea main/master at 429383d. GitHub stays behind until Travis is ready for prod release. The merge-to-prod is its own event (with the half-revert mistake also being repaired in this same merge, archaeology-friendly).

Decisions worth keeping

  • Hand-splice over 3-way merge for reverted code when the synthetic base and the last-good upstream both have legitimate-but-disjoint content. 3-way against the synthetic base would have deleted the instagram models; 3-way against c7856d6 would have deleted post-revert Phase-6 work. Hand-splicing both directions onto current HEAD is the cleaner reconciliation.
  • Forward-commit-and-restore rather than history rewrite for repairing a stripped file. Mistake stays visible in git log for archaeology and learning; working state is fixed. Travis explicitly checked the motivation here — destructive history rewrite would have been wrong even though it'd "look cleaner".
  • Extending an upstream worker's patch when the dependent step crashes with an additive, in-pattern fix (W10 extending W9's 01_content_hub.j2 with 3 more columns). The alternative — stop, hand off to a third worker with full context, repeat — burns wall-clock and risks context loss. Judged in-scope = additive, on-branch, same idempotent pattern as the original patch.
  • Sandbox-throwaway gives latitude. Drop pbs_automation DB, force-rebuild images, SEEDER_RESET=true — all routine when nothing on the sandbox is worth preserving. Snapshot rollback stays gated because it touches WP install state (different concern).
  • Two-remote push policy enforced consistently across all today's commits. Gitea = test/sandbox truth; GitHub = production; Travis controls when prod gets the changes. No worker pushed to GitHub origin this session.