Skip to content
Back to skills

Db Deprecate Table

ASecurity

Retiring a dead table by moving it to the deprecated schema and clearing every reference in both repos. Use when asked to deprecate or retire a table, when a table is no longer used, or to take a table offline without dropping it. NOT for relocating a still-used table (use db-move-table-schema).

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 3, 2026
databasespythongoshellsqlexpressdatabase

Security analysis

A100/100

Scanned October 3, 2026

npx -y skills add armanisadeghi/ai-matrx --skill db-deprecate-table --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Db Deprecate Table?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Db Deprecate Table
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/armanisadeghi-db-deprecate-table/badge)](https://www.skillsdirectory.com/skills/armanisadeghi-db-deprecate-table)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: db-deprecate-table
description: "Retiring a dead table by moving it to the deprecated schema and clearing every reference in both repos. Use when asked to deprecate or retire a table, when a table is no longer used, or to take a table offline without dropping it. NOT for relocating a still-used table (use db-move-table-schema)."
---

# Deprecate a table

> Once a table is here it is dead to every database check: findings on deprecated tables are never reported or fixed — [canonical-first triage](../../../../common-docs/policies/canonical-first-triage.md).

Goal: get the table **offline and reversible** (`SET SCHEMA deprecated`, never `DROP`), then erase every reference. **Getting it offline is priority #1; reference cleanup follows and must not block the move.** Read [`../db-change/TOOLKIT.md`](../db-change/TOOLKIT.md) + [`../db-change/SKILL.md`](../db-change/SKILL.md) first. Project: `brsgrqvjdzwihsvnfqkf`.

## Step 1 — Discover every reference (this is the real work)

**🚨 THE ORACLE FIRST — `audit.relation_usage(schema, table)` (built 2026-08-20, deprecated-sweep postmortem).** One call returns EVERY DB-side mention: inbound/outbound FKs, dependent views/matviews **recursively** (each dependent view joins the closure, so its consumers are found too), triggers on it, every function/procedure body (qualified vs bare matches labeled), RLS policy expressions on other tables, trigger string-arguments (`_version_capture` tokens, org-inherit args), cron jobs, realtime publications, and the entity/shareable/deprecation registries.

```sql
select * from audit.relation_usage('<schema>', '<table>') order by via, kind, ref;
```

The transitive hop is the whole point: single-hop name searches missed `deprecated.prompt_builtins` → `public.context_menu_unified_view` → `get_ssr_shell_data` (a LIVE session hydrator) in the 2026-08-20 sweep — the table name appears nowhere in the function, only the view's name does. Never substitute a plain `pg_proc ilike` scan for this call. `function_body_bare` rows can be same-named objects in another schema — read each; `function_body_qualified` rows are certain.

Two checks the oracle does not cover:

```sql
-- is anything still actually reading it? (needs pg_stat_statements)
select calls, query from pg_stat_statements where query ~* '\m<table>\M' order by calls desc limit 20;
```
Code (both repos): grep `<table>` for `.from('<table>')`, `.schema(...).from('<table>')`, generated type names, Python model/manager names (`aidream/db/models*.py`, `db/managers/**`), package wiring in `aidream/package_integration.py`, **and raw SQL strings** (`from <table>`, `SELECT 1 FROM <table>` ACL joins in `.py`/`.sql`). **Also grep for every `ref` the oracle returned** — a dependent view or RPC it surfaces can be called from code by ITS name with the table name appearing nowhere in the repo.

> **A 0-row table can still be LIVE.** Verified: `note_shares` had 0 rows but was joined by RAG-search ACL (`matrx-rag/search.py: SELECT 1 FROM public.note_shares`) — deprecating it turned an empty result into a missing-relation error, breaking search. **Row count ≠ usage.** The query string is what breaks; grep it before you move. (Recovery: `alter table deprecated.<t> set schema public` — reversible, which is why we never `DROP`.)

## Step 2 — Confirm it's truly dead
If reads remain: repoint or delete those consumers if quick; otherwise deprecate now (reversible) and **track the remaining cleanup** in the Open items queue of `../common-docs/operations/db-changeover-board.md` (the tracker that replaced the deleted `docs/db_rebuild/CHANGEOVER_PROGRESS.md`). Do not block the move on a long repoint — but never deprecate a table with live, load-bearing reads you haven't accounted for.

## Step 3 — Resolve FKs in BOTH directions

**Inbound** (tables that point AT this one): a cross-schema FK keeps working after the move, so the move won't *break* — but a dead table shouldn't be referenced. Drop or repoint inbound FK constraints that shouldn't exist. If an inbound FK represents real data you can't yet sever, that table isn't dead — reconsider.

🚨 **Outbound** (this table pointing at LIVE tables) — **the step that gets forgotten, and it breaks live pages.** `SET SCHEMA` carries every outbound FK along, so the retired table keeps advertising itself in `pg_constraint` as a child of live tables. Any catalog-walking function then discovers it and queries it **as the signed-in user**, who has no `USAGE` on `deprecated` → `42501 permission denied for schema deprecated`, and the whole call dies. That is exactly how every `/projects/<id>` page broke on 2026-08-13: `get_project_references` walked the FKs into `projects.projects` and hit two retired flashcard tables. Always finish the move with the idempotent sweep — **it is safe and expected to re-run**:
```sql
do $$ declare r record; begin
  for r in select conname, conrelid::regclass as from_table from pg_constraint
    where contype='f' and conrelid::regclass::text like 'deprecated.%'
      and confrelid::regclass::text not like 'deprecated.%'
  loop execute format('alter table %s drop constraint %I', r.from_table, r.conname); end loop;
end $$;
```
`migrations/drop_graveyard_to_live_fks.sql` is this sweep. It was run ONCE (2026-07-28) and treated as done; every table retired afterwards re-created the problem — 78 such FKs had accumulated by 2026-08-13. It is not a one-time migration, it is a **post-condition of every deprecated move**.

## Step 4 — Deactivate the registration FIRST (enforced, 2026-08-12)
`platform._enforce_entity_is_table` now ERRORs when an ACTIVE `entity_types` row ends up
pointing at `deprecated` — the DDL-sync trigger repoints `schema_name` during your `SET SCHEMA`,
so **moving a still-active registered table into deprecated fails with a check_violation.**
Deactivate before you move (this is the guard doing its job, not a bug):
```sql
update platform.entity_types set is_active=false where token='<token>';   -- or delete the row
```

## Step 5 — Move it (idempotent, verify no data lost)
```sql
do $$ begin
  if to_regclass('public.<table>') is not null then
    execute 'alter table public.<table> set schema deprecated';
  end if;
end $$;
select count(*) from deprecated.<table>;   -- equals the pre-move count
```

## Step 6 — De-register the rest
Remove the remaining platform footprint so nothing resolves to it:
```sql
delete from platform.entity_relationships where child_type='<token>' or parent_type='<token>';
delete from platform.shareable_resource_registry where table_name='<table>';
```
Leave satellite rows (`associations`/`comments`/…) keyed by the token in place unless they're now orphaned — sweep separately; they're harmless and reversible.

## Step 7 — Cross-repo cleanup + finalize
`deprecated` IS in the `db-types` schema list, so the table still appears under the `deprecated` schema in FE types — that's fine; the point is to **delete every code usage**. Then run the finalize SOP (db-change/SKILL.md): `pnpm db-types` → remove FE usages → `pnpm sync-types` (fix TS); `python db/generate.py` → remove aidream usages + `package_integration.py` entry → `python db/detect_applied.py` → `python run.py` clean boot. Record the migration in the ledger. Commit + push `main` on both repos.

## Clean cut — no silent shim (SKILL `db-change` → THE CUT)
`SET SCHEMA deprecated` makes `public.<t>` vanish → stale refs error (correct). **Register it** in `scripts/dead-relations.json` + `platform.deprecated_relations` and clear `pnpm check:dead-relations` (it finds the raw-SQL/Python/comment refs `tsc` misses). Never leave a readable old table or a compat view as a "fallback"; if it can't be moved yet, **tripwire it** (`platform.deprecate_relation` — data preserved, reads/writes RAISE).

## NEVER
- `DROP TABLE` — `SET SCHEMA deprecated` only (DROP is a separate PITR-gated step).
- Leave the old name readable as a silent fallback — move it (vanishes → errors) or tripwire it (RAISES). Never a passthrough view.
- Deprecate a table that still has live reads you haven't repointed or tracked.
- Leave a generated type / Python model / `package_integration.py` reference pointing at the moved table.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…