KB-EB08

D28 — Generated Table Map Design (Option C)

17 min read Revision 1
dieu28designgenerated-mapoption-c2026-05-09

D28 — Generated Table Map Design (Option C)

Date: 2026-05-09 Author: claude-opus-4-7 (VPS reverify + design only) Status: DESIGN — chờ GPT/User duyệt trước implementation Scope: thay 3 hardcoded maps bằng generated module duy nhất sinh từ table_registry + convention rules. KHÔNG implement trong pack này.


1. Problem statement (verified on VPS 2026-05-09)

3 hardcoded maps trên Nuxt repo (/opt/incomex/docker/nuxt-repo/web) chứa cùng một sự thật về registry routes nhưng được sao chép thủ công:

# File Symbol Entries Pattern
1 pages/knowledge/registries/[entityType]/index.vue (~L39–58) tableIdMap 18 entityType → tbl_xxx
2 config/detail-sections.ts (~L227) collectionMap 20 entityType → collection
3 config/detail-sections.ts (~L252) reverseCollectionMap 20 collection → entityType (derived Object.fromEntries)
4 server/api/discovery/relations.get.ts (~L18) COLLECTION_ENTITY_MAP 14 collection → entityType (mirror, partial)

Drift đã tồn tại: relations.get.ts mirror chỉ có 14/20 entries (thiếu system_issues, registry_changelog, tasks, meta_catalog đôi khi, …) — đúng motivation của Option C.

Live table_registry có 21 rows. Schema KHÔNG có column entity_type (xem Phần 4.1). Đây là data problem, không chỉ code problem: entityType chỉ tồn tại trong tableIdMap ở Nuxt.


2. Goals / Non-goals

Goals

  • 1 nguồn sự thật cho mapping (registry rows + convention).
  • Generated artifact import được từ cả pages/*.vue (client/SSR) và server/api/*.ts.
  • Build-time deterministic, drift-detectable bằng CI check.
  • Không lộ secret trong artifact, không phá quy ước file existing.

Non-goals

  • Không thay registry data model toàn cục.
  • Không thay route resolution thành dynamic runtime fetch (giữ static).
  • Không xử lý 3 entity-only entries (trigger, comment, taxonomy) trong collectionMap mà không có row tương ứng trong table_registry (xem §6 — handle qua static_extras trong script config).

3. Hard boundaries

NO_NUXT_CODE_MODIFICATION=true
NO_GENERATED_FILE_CREATION_NOW=true   # design pack only
NO_DIRECTUS_MUTATION=true
NO_PG_MUTATION=true
NO_SECRET_IN_REPORT=true

4. Decision matrix

4.1 entityType derivation strategy — RECOMMEND E4 (Phase 1) → E3 (Phase 2)

table_registry columns hiện tại (live, verified):

id, table_id, name, collection, fields(json), default_sort, default_filter(json),
page_url, enable_insert_marks, enable_proposals, enable_search, enable_pagination,
rows_per_page, status, module, description, _dot_origin

Không có entity_type. Cần derive.

Option Pros Cons Verdict
E1 — Seed từ tableIdMap hiện tại Zero risk, exact preserve Hardcode lại trong script, không tự nhiên cho row mới ❌ chỉ tốt làm starting set, không làm rule
E2 — Derive từ page_url last segment Tự nhiên cho REGISTRY_ENTITY_ROUTE Fail cho 8/21 non-registry rows (workflow tabs, admin, index) ❌ không cover toàn 21 rows
E3 — Add entity_type column vào table_registry Cleanest, single source of truth Cần Directus field-add + backfill 21 rows; out-of-scope cho design pack Phase 2 target
E4 — Convention script + per-row override (recommended) Không cần schema migration; rule-based + explicit overrides; backfill mechanism dễ chuyển sang E3 sau Convention nằm trong script, không trong DB Phase 1 recommendation
E5 — Drop entityType, dùng page_url làm key Eliminates concept Fundamental redesign; cần rewrite cả routing + Nuxt page params ❌ blast radius quá lớn

E4 derivation rules (đề xuất, in script config):

# Step 1 — convention: tbl_xxx → entityType
  tbl_registry_<X>     → <X>          (ví dụ tbl_registry_dot_tools → dot_tool — phải singularize)
  tbl_<X>_list         → <X>          (tbl_workflow_list → workflow, tbl_modules_list → module)
  tbl_<X>              → <X>          (tbl_table_registry → table; explicit override needed)

# Step 2 — explicit overrides (script config dict, seeded từ tableIdMap):
  tbl_meta_catalog          → catalog
  tbl_table_registry        → table
  tbl_proposals_list        → table_proposal
  tbl_tasks_list            → task
  tbl_modules_list          → module
  tbl_system_issues         → system_issue
  tbl_event_outbox          → event_outbox
  tbl_workflow_timeline     → (skipped — duplicate collection workflow_steps; xem §6)

# Step 3 — fail-fast: nếu row không match convention và không có override → script lỗi (build break).

Migration path Phase 1 → Phase 2:

  1. Phase 1 land script (E4) + generated file. Existing hardcoded maps removed.
  2. Phase 2 (separate pack D28_TABLE_REGISTRY_SCHEMA_EXTENSION): add entity_type column, backfill bằng chính output dict của E4 script, switch script sang đọc DB cell. Convention rules giữ làm fallback validation.

4.2 Generation script

Decision Recommendation Rationale
Location web/scripts/generate-table-maps.ts Cùng package, chạy được qua tsx, không cần monorepo cross-link. Existing web/scripts/ đã có ts conventions.
Language TypeScript via tsx Output là .ts file → cùng ngôn ngữ giảm typing error; jq path requires bash plumbing và mất type safety.
Input GET /items/table_registry?fields=id,table_id,collection,page_url,status&limit=-1 Đủ cho derivation; tránh fields(json) vì lớn và không dùng.
Output Single file web/generated/table-maps.generated.ts (new dir web/generated/ — currently absent, an established Nuxt convention) Pure static, 3 named exports + metadata header; one file dễ verify hash.

Output skeleton:

// AUTO-GENERATED — DO NOT EDIT
// source: directus.table_registry @ 2026-05-09T...Z
// row_count: 21  status_filter: active|published(+overrides)
// content_hash: sha256:...
export const tableIdMap: Record<string, string> = { ... };
export const collectionMap: Record<string, string> = { ...registryDerived, ...staticExtras };
export const reverseCollectionMap: Record<string, string> = Object.fromEntries(
  Object.entries(collectionMap).map(([k, v]) => [v, k])
);
export const __META__ = { generatedAt: '...', rowCount: 21, contentHash: '...' };

4.3 Client/server import safety

Verified pure-static design: file không reference process.env, không runtime fetch, không browser/Node-only API. Importable an toàn từ:

  • pages/knowledge/registries/[entityType]/index.vue (Vue SFC, client+SSR) — replaces local tableIdMap.
  • config/detail-sections.ts (universal config module) — replaces collectionMap + reverseCollectionMap.
  • server/api/discovery/relations.get.ts (Nitro server) — replaces COLLECTION_ENTITY_MAP (uses reverseCollectionMap).

No split needed. Import alias ~/generated/table-maps.generated works in cả 3 contexts (~ resolves to web/ từ Nuxt config defaults; auto-import alias xác nhận trong nuxt.config.ts không có override).

4.4 Build-time credentials — RECOMMEND C2 (build token via env)

Option Verdict
C1 — Public read on table_registry ❌ table_registry chứa schema metadata (fields json, filters) — không nên expose Public.
C2 — Build token in env (NUXT_DIRECTUS_SERVICE_TOKEN) ✅ token đã tồn tại trong VPS .env cho relations.get.ts runtime; reuse cho build script; token KHÔNG bundled vào generated file (chỉ values).
C3 — VPS-only generation ❌ ràng buộc dev local — họ muốn regen sau khi sửa script phải SSH; tăng friction.

Token hygiene: script đọc NUXT_DIRECTUS_SERVICE_TOKEN (đã có trong runtimeConfig), không in vào output, không log; trong CI dùng GitHub Actions secret.

4.5 Build/deploy timing — RECOMMEND A (prebuild) + commit fallback

Option Verdict
A — "prebuild": "tsx scripts/generate-table-maps.ts" in web/package.json ✅ chạy trước nuxt build mọi nơi (local, CI, VPS). Chuẩn Nuxt convention.
B — GitHub Actions step Redundant nếu A có; dùng làm --check step thay vì regen.
C — Commit generated file kèm theo A — commit để diff visible trong PR; prebuild đảm bảo update; CI verify hash khớp.
D — Deploy hook ❌ trễ; build artifact đã shipped khi đến đó.
E — VPS-only script ❌ same friction như C3.

Kết hợp A+C: prebuild regen → developer commit; CI runs --check to fail nếu file lệch.

4.6 Status/draft handling — RECOMMEND --include-draft flag + explicit force_include

Live status distribution của 21 rows:

  • active: 6 (id 1-6, 8)
  • published: 14 (id 7, 9-20)
  • draft: 1 (id 21 — tbl_event_outbox)

Default (production build): include status IN (active, published). Exclude draft.

Chicken-egg cho tbl_event_outbox: route smoke cần map → map cần row → row đang draft.

Đề xuất sequence (safe):

  1. Dev chạy npm run generate:table-maps -- --include-draft local.
  2. Smoke /knowledge/registries/event_outbox trên dev preview.
  3. Nếu OK → publish row trong Directus (status='published').
  4. CI prebuild regen với default flag → row tự nhiên có mặt → deploy.

Backup mechanism: script config có force_include: string[] để hardcode-include row theo table_id ngay cả khi draft (fallback nếu publish chưa kịp). Không recommend dùng làm long-term.

4.7 Drift verification

Modes:
  generate (default)  : write artifact + update header
  --check             : compute fresh content, exit 1 nếu khác file hiện tại; in diff vắn tắt (no secret)
  --print-hash        : print content_hash only (CI summary)

Header (in artifact):
  // generatedAt, rowCount, statusFilter, contentHash (sha256 over canonicalized JSON of 3 maps)

CI integration:
  nuxt-ci.yml job step: `tsx scripts/generate-table-maps.ts --check`
  deploy-vps.yml: same --check before build (block deploy on mismatch)
  post-deploy: optional smoke fetch /knowledge/registries/<each entityType> head request

Manual edit detection:
  --check recompute hash; nếu file hash header ≠ recomputed → fail.

4.8 Rollback

  • Vì artifact committed → git revert <generation-commit> đủ.
  • Không cần backup directory: git history là backup.
  • Nếu schema bug làm regen sai: tạm thêm row vào force_include hoặc revert + hotfix script.

5. Row-by-row classification (21 rows)

id table_id collection status page_url derived_entity_type row_class in_tableIdMap in_collectionMap notes
1 tbl_workflow_list workflows active /knowledge/workflows workflow NON_REGISTRY_PAGE YES YES dùng map cho DirectusTable lookup nội dung, không cho route
2 tbl_workflow_steps workflow_steps active /knowledge/workflows/[id]?tab=matrix workflow_step WORKFLOW_TAB YES YES tab inside workflow detail
3 tbl_workflow_timeline workflow_steps active /knowledge/workflows/[id]?tab=diagram (skip — collection collision) WORKFLOW_TAB NO NO cùng collection với id 2; không thêm vào generated map
4 tbl_wcr_list workflow_change_requests active /knowledge/workflows/[id]?tab=wcr wcr WORKFLOW_TAB YES YES
5 tbl_modules_list tasks ⚠ active /knowledge/modules (override → module) NON_REGISTRY_PAGE YES (module) YES (module) ⚠ collection field= 'tasks' is suspicious; entityType=module per existing maps; flag for data fix
6 tbl_tasks_list tasks active /knowledge/current-tasks task NON_REGISTRY_PAGE YES YES
7 tbl_proposals_list table_proposals published /admin/proposals table_proposal ADMIN_PAGE YES NO (collectionMap missing 'table_proposal') drift: collectionMap thiếu entry này — generated map sẽ tự fix
8 tbl_meta_catalog meta_catalog active /knowledge/registries catalog INDEX_PAGE YES YES index page
9 tbl_registry_dot_tools dot_tools published /knowledge/registries/dot_tool dot_tool REGISTRY_ENTITY_ROUTE YES YES
10 tbl_registry_ui_pages ui_pages published /knowledge/registries/page page REGISTRY_ENTITY_ROUTE YES YES
11 tbl_registry_collections collection_registry published /knowledge/registries/collection collection REGISTRY_ENTITY_ROUTE YES YES
12 tbl_registry_agents agents published /knowledge/registries/agent agent REGISTRY_ENTITY_ROUTE YES YES
13 tbl_registry_modules modules published /knowledge/registries/module module REGISTRY_ENTITY_ROUTE YES YES
14 tbl_registry_checkpoint_types checkpoint_types published /knowledge/registries/checkpoint_type checkpoint_type REGISTRY_ENTITY_ROUTE YES YES
15 tbl_registry_checkpoint_sets checkpoint_sets published /knowledge/registries/checkpoint_set checkpoint_set REGISTRY_ENTITY_ROUTE YES YES
16 tbl_registry_entity_dependencies entity_dependencies published /knowledge/registries/entity_dependency entity_dependency REGISTRY_ENTITY_ROUTE YES YES
17 tbl_checkpoint_instances checkpoint_instances published /knowledge/registries/checkpoint_instance checkpoint_instance REGISTRY_ENTITY_ROUTE YES YES
18 tbl_registry_changelog registry_changelog published /knowledge/registries/changelog changelog REGISTRY_ENTITY_ROUTE YES YES
19 tbl_table_registry table_registry published /knowledge/registries/table table REGISTRY_ENTITY_ROUTE YES YES
20 tbl_system_issues system_issues published /knowledge/registries/system_issue system_issue REGISTRY_ENTITY_ROUTE YES YES
21 tbl_event_outbox event_outbox draft /knowledge/registries/event_outbox event_outbox REGISTRY_ENTITY_ROUTE NO NO new row; cần --include-draft hoặc publish trước build

Counts:

  • REGISTRY_ENTITY_ROUTE: 13 (id 9-21)
  • NON_REGISTRY_PAGE: 3 (id 1, 5, 6)
  • WORKFLOW_TAB: 3 (id 2, 3, 4)
  • ADMIN_PAGE: 1 (id 7)
  • INDEX_PAGE: 1 (id 8)
  • Total: 21 ✓

6. Static extras (non-registry collections)

collectionMap hiện chứa 3 entries không tương ứng row nào trong table_registry:

trigger     → trigger_registry
comment     → task_comments
taxonomy    → taxonomy

Đây là collections dùng cho relations resolution nhưng chưa được "promote" thành registry-managed table. Đề xuất:

  • Script config có static_extras: Record<string,string> chứa 3 entries trên.
  • Generated collectionMap = {...derivedFromRegistry, ...staticExtras}.
  • Conflict detection: nếu key xuất hiện cả 2 → script fail (build break).
  • Theo dõi: nếu sau này có row table_registry cho 3 collections này → xóa entry tương ứng khỏi static_extras. Add comment cảnh báo trong script config.

7. Open data quality issues (flag, không sửa trong pack này)

  1. id 5 — tbl_modules_list.collection = 'tasks' — gần như chắc là sai (nên là modules). Đề xuất pack riêng: data fix + verify.
  2. id 3 — tbl_workflow_timeline — cùng collection với id 2 (workflow_steps). Không có entityType riêng. Generated script skip — không gây regression vì hiện tại cũng không xuất hiện trong tableIdMap.
  3. relations.get.ts mirror drift — thiếu 6 entries vs collectionMap. Generated map sẽ tự fix khi swap import.

8. Implementation handoff checklist (cho pack tiếp theo)

  • Tạo web/scripts/generate-table-maps.ts (E4 strategy).
  • Tạo dir + file web/generated/table-maps.generated.ts (initial run committed).
  • Update web/package.json scripts: prebuild, generate:table-maps.
  • Update 3 consumer files import từ ~/generated/table-maps.generated, xóa local hardcoded.
  • CI: thêm step tsx scripts/generate-table-maps.ts --check vào nuxt-ci.yml + deploy-vps.yml.
  • Add web/generated/ vào .gitignore exception (commit generated file).
  • Smoke test 21 routes (13 registry + 3 non-registry + 3 workflow tab + 1 admin + 1 index).
  • Phase 2 follow-up pack: entity_type column add + backfill.

9. Risks

Risk Mitigation
Convention E4 mismatch cho row mới Script fail-fast; CI catches.
Token leak in artifact Pure-static check + --check includes "no process.env reference" assertion.
Hash drift after manual edit CI --check block deploy.
event_outbox draft never published Smoke + publish workflow ghi rõ trong runbook.
module=null cho 14 rows Out of scope; không ảnh hưởng map.

D28 Generated Table Map Design | 2026-05-09 | READ + DESIGN ONLY