Docs
Tabula Kifuliiru
Applications
Tabula Kifuliiru
Overview
IntroductionArchitectureDevelopment setup
Publish pipelinePublish to LabPublish to cloud
Platform integration
HomeBrowse applications

© 2026 Wekify LLC · Kifuliiru Lab

Homellms.txtkifuliiru.com

GuidesPublish to Lab

Publish to Lab

Step 1: local Postgres to lab_* staging.

Status: Operational — use as Step 1 of PUBLISH_PIPELINE.md Audience: Admins, developers Last updated: August 2026

Related:

  • PUBLISH_PIPELINE.md — Local → lab → production (recommended flow)
  • PUBLISH_TO_CLOUD.md — full publish reference (lab + production)
  • IMPACT_VARIABLES.md — impact metrics fed from published corpus
  • PROJECT_OVERVIEW.md — architecture
  • UI: /data-management/publish-to-cloud
  • Compare UI: /data-generation/db-mapper
  • Audit: npm run audit:cloud

Purpose

Lab staging copies local PostgreSQL authoring data into Supabase tables prefixed with lab_. These tables are:

  • Isolated from production (verbs, sentences, … used by Tabula / public apps)
  • Protected by RLS — admin and super_admin only
  • The target for deployed Kifuliiru Lab when local Postgres is not available

Without publishing to lab_*, the deployed Lab shows empty overview, dictionary, and verb pages even though your machine has thousands of rows locally.


Editing on deployed Lab (cloud writes)

After publish, the deployed app reads and writes lab_* directly when:

  • CORPUS_DATA_SOURCE is unset or auto (production defaults to Supabase lab), and
  • You are signed in as lab admin (admin / super_admin), or the server has SUPABASE_SERVICE_ROLE_KEY.

| Action | Local dev | Deployed Lab | |--------|-----------|--------------| | View overview, dictionary, verbs | Local Postgres | lab_* | | Create / update / delete rows | Local Postgres | lab_* | | CSV import (dictionary, sentences, longer texts) | Local Postgres | lab_* | | Migrate dictionary → verbs | Local Postgres | lab_* |

Two valid workflows:

  1. Edit in cloud — change data in the deployed UI; changes go straight to lab_*.
  2. Edit locally + re-publish — change local Postgres, then run Publish to Cloud to refresh lab_*.

If writes fail with auth errors, set SUPABASE_SERVICE_ROLE_KEY on Vercel Production or confirm admin login. Check /api/corpus-source (admin only) for corpusClientMode.


Lab vs production

| | Lab staging (lab_*) | Production (live) | |---|---------------------------|------------------------| | Examples | lab_verbs, lab_sentences | verbs, sentences | | Used by | Deployed Lab, admin review | Tabula, learner apps | | Kifuliiru required? | Optional on local dev; enforced on deployed Lab | Always | | Risk | Low — admin-only staging | High — affects live users | | Current row count | 0 (Aug 2026 — never published) | Partial (e.g. verbs 321, sentences 940) |

Rule: Always publish to lab_* first, validate on deployed Lab, then publish Kifuliiru-ready rows to production when approved.


Prerequisites

1. Local PostgreSQL

Local kifuliiru database populated (authoring machine).

Code
DB_HOST=localhost
DB_PORT=5432
DB_USER=...
DB_PASSWORD=...
DB_NAME=kifuliiru
```text

### 2. Supabase credentials

```bash
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your_anon_key
```text

Same project as Tabula Kifuliiru auth.

### 3. Lab tables exist in Supabase

Migration: `supabase/migrations/20250623000000_lab_tables.sql`

Apply via Supabase SQL editor or:

```bash
npm run db:generate-lab-migration # if regenerating from local schema
# then apply in Supabase dashboard
```text

Creates `lab_verbs`, `lab_dictionary_nouns`, `lab_sentences`, … with RLS policies using `is_lab_admin()`.

### 4. Admin role

Your Supabase user must have `admin` or `super_admin` in `user_roles` (same as Lab login).

---

## Table mapping

Local names are unchanged. Staging adds the `lab_` prefix.

| Local table | Supabase staging | Production (when ready) | |-------------|------------------|-------------------------| | `verbs` | `lab_verbs` | `verbs` | | `dictionary_nouns` | `lab_dictionary_nouns` | `dictionary_nouns` | | `dictionary_adjectives` | `lab_dictionary_adjectives` | `dictionary_adjectives` | | `dictionary_adverbs` | `lab_dictionary_adverbs` | `dictionary_adverbs` | | `sentences` | `lab_sentences` | `sentences` | | `generated_sentences` | `lab_generated_sentences` | `generated_sentences` | | `longer_texts` | `lab_longer_texts` | `longer_texts` | | `semantics_dictionary` | `lab_semantics_dictionary` | `semantics_dictionary` | | `conjugation_rules` | `lab_conjugation_rules` | `conjugation_rules` | | … | `lab_{name}` | `{name}` |

Full list: `getAllMappableTables()` in `src/lib/utils/table-mapping.ts`.

**Not published:** `wemp_users` (legacy local auth). Cloud auth uses Supabase `auth.users`.

---

## Step-by-step: first lab publish

### Step 1 — Audit gap

```bash
npm run audit:cloud
```text

Confirm `lab_*` columns show **0** and local columns show your real counts.

### Step 2 — Open publish UI

Navigate to **`/data-management/publish-to-cloud`**.

### Step 3 — Test connection

Click **Test connection** — must reach Supabase with your env credentials.

### Step 4 — Select destination

Choose **Lab staging (`lab_*`)** — not Production.

### Step 5 — Kifuliiru filter (local dev)

| Setting | First batch recommendation | |---------|----------------------------| | **Require Kifuliiru-side text** | **Off** for initial smoke test (publish all rows); **On** to mirror deployed Lab behavior |

Production destination **always** requires Kifuliiru — filter is locked.

Env override: `NEXT_PUBLIC_REQUIRE_KIFULIIRU_FOR_PUBLISH=true|false`

### Step 6 — Scan all tables

Click **Scan all tables**. Review per table:

| Metric | Meaning | |--------|---------| | Local count | Rows in your Postgres | | Cloud count | Rows already in `lab_*` | | Pending / new in local | Rows eligible to insert | | Publish-ready pending | Subset with Kifuliiru text |

### Step 7 — Select tables and publish

**Recommended first batch (high value, validates deploy):**

1. `verbs` → `lab_verbs`
2. `sentences` → `lab_sentences`
3. `dictionary_nouns` → `lab_dictionary_nouns`
4. `conjugation_rules` → `lab_conjugation_rules`

Optional second batch:

- `generated_sentences`, `longer_texts`, dictionary adjectives/adverbs, `concrete_questions`

Select tables → **Publish selected** → confirm.

### Step 8 — Verify

```bash
npm run audit:cloud
```text

`lab_*` counts should match pending rows inserted.

Re-open publish UI → **Scan** — status should move toward **In sync** for published tables.

### Step 9 — Deployed Lab smoke test

On deployed Lab (after app reads `lab_*` — see [IMPACT_VARIABLES.md](IMPACT_VARIABLES.md) Phase 3):

- `/overview` — should show non-zero stats
- `/verbs` — should list published verbs
- Dictionary pages — should show published nouns

---

## API (programmatic publish)

Same mapper engine as the UI. Always set `"target": "lab"`.

### Compare one table

```http
GET /api/mapper/compare?table=verbs&target=lab
```text

### Compare all tables

```http
GET /api/mapper/compare?target=lab
GET /api/mapper/compare?target=lab&publishReady=true
```text

### Sync one table to lab

```http
POST /api/mapper/sync
Content-Type: application/json

{
 "table": "verbs",
 "direction": "to-supabase",
 "target": "lab",
 "publishReady": false
}
```text

### Batch sync

```http
POST /api/mapper/sync-batch
Content-Type: application/json

{
 "tables": ["verbs", "sentences", "dictionary_nouns"],
 "target": "lab",
 "publishReady": false
}
```text

**Insert-only:** Rows already in `lab_*` (matched by dedup key) are skipped — not overwritten.

---

## Dedup and matching

Rows are considered “already in cloud” when the **dedup key** matches:

1. **`english`** if non-empty
2. Else first non-empty Kifuliiru-side field: `verb_kifuliiru`, `kifuliiru`, `igambo`, `question_kifuliiru`, `conjugated_form`, …

Field-level diffs are shown in compare UI but **not auto-synced** (updates require manual edit or future sync-v2).

Implementation: `src/lib/utils/publish-readiness.ts`, `src/lib/services/db-mapper.ts`.

---

## Kifuliiru publish readiness

A row is **publish-ready** when any Kifuliiru-side field is non-empty:

`verb_kifuliiru`, `kifuliiru`, `igambo`, `igambo_plural`, `question_kifuliiru`, `conjugated_form`

| Context | Filter | |---------|--------| | Local dev → `lab_*` | Off by default (publish everything) | | Deployed Lab policy | On (matches production discipline) | | Local dev → **production** | **Always on** |

---

## What publish does NOT do

| Not included | Notes | |--------------|-------| | UPDATE existing cloud rows | Insert new rows only | | Publish to `impact_variables` | Separate metrics sync — [IMPACT_VARIABLES.md](IMPACT_VARIABLES.md) | | Create missing Supabase tables | Run migration first | | Sync `semantic_relationships` | Not in `lab_*` migration yet — see [SEMANTICS_CURRENT.md](SEMANTICS_CURRENT.md) | | Pull from cloud by default | Use `direction: "from-supabase"` explicitly |

---

## Troubleshooting

| Symptom | Cause | Fix | |---------|-------|-----| | All `lab_*` counts 0 after publish | Publish never run or wrong target | Re-run with `target=lab` | | Insert permission denied | RLS / not admin | Verify `user_roles` | | 0 pending but local has rows | All dedup keys already in cloud | Check compare details | | Rows skipped | `publishReady=true` without Kifuliiru | Turn filter off or add Kifuliiru | | Table missing in scan | Not in Supabase | Apply `20250623000000_lab_tables.sql` | | Deployed Lab still empty | App reads local PG, not Supabase | Wire `/api/stats` to `lab_*` (planned) |

---

## Recommended publish phases

### Phase A — Smoke test (admin)

- [ ] `verbs`, `sentences` → `lab_*`
- [ ] Run `npm run audit:cloud`
- [ ] Confirm deployed Lab can read staging data (when wired)

### Phase B — Dictionary core

- [ ] All `dictionary_*` split tables → `lab_*`
- [ ] `generated_sentences`, `longer_texts`

### Phase C — Templates & questions

- [ ] `conversation_templates`, `concrete_questions`, template vocabulary
- [ ] `semantics_dictionary`

### Phase D — Promote to production (Step 2)

- [ ] **Scan promotion** on publish page
- [ ] Promote Kifuliiru-ready rows from `lab_*` → production
- [ ] See [PUBLISH_PIPELINE.md](/tabula-kifuliiru/guides/publish-pipeline)

### Phase E — Impact metrics

- [ ] After production/lab counts stable → sync `impact_variables` corpus slugs
- [ ] See [IMPACT_VARIABLES.md](IMPACT_VARIABLES.md)

---

## Security

- Never commit `.env` with service role key to git.
- `NEXT_PUBLIC_*` = browser-visible — anon key only.
- **`lab_*`** is admin-only RLS — safe for draft data.
- **Production publish** affects Tabula live data — double-check destination toggle.

---

## Code references

| File | Role | |------|------| | `src/app/data-management/publish-to-cloud/page.tsx` | Publish UI | | `src/app/data-generation/db-mapper/page.tsx` | Compare UI | | `src/lib/services/db-mapper.ts` | Compare & sync engine | | `src/lib/utils/table-mapping.ts` | `local` → `lab_*` names | | `src/lib/utils/publish-target.ts` | `lab` vs `production` | | `src/lib/utils/publish-policy.ts` | Kifuliiru filter by environment | | `src/app/api/mapper/compare/route.ts` | Compare API | | `src/app/api/mapper/sync/route.ts` | Single-table sync | | `supabase/migrations/20250623000000_lab_tables.sql` | Staging schema + RLS |

---

## Related documentation

| Doc | Focus | |-----|--------| | [PUBLISH_TO_CLOUD.md](/tabula-kifuliiru/guides/publish-to-cloud) | Lab + production, API summary | | [IMPACT_VARIABLES.md](IMPACT_VARIABLES.md) | KPI sync after publish | | [DATABASE_MAPPER.md](DATABASE_MAPPER.md) | Bidirectional mapper UI | | [TABLE_MAPPING_CONFIG.md](TABLE_MAPPING_CONFIG.md) | Mapping config details |
PreviousPublish pipelineNextPublish to cloud

On this page

  • Purpose
  • Editing on deployed Lab (cloud writes)
  • Lab vs production
  • Prerequisites
  • 1. Local PostgreSQL