KB-57BD rev 20

D28 — Generated Table Map Implementation — Agent Prompt (Phase 1B)

17 min read Revision 20
dieu28promptimplementationgenerated-mapphase-1b2026-05-09

D28 — Generated Table Map Implementation — Agent Prompt

Rev: 8 | 2026-05-10 | GPT 8 patches: artifact content safety, no URL leak, PARTIAL ceiling, STATIC_EXTRAS legacy framing Agent: claude-go (VPS SSH + git + node host) | Scope: Phase 1B | KHÔNG DEPLOY Major change vs rev6: Generator = .mjs chạy trên host bằng Node built-ins. KHÔNG container exec. KHÔNG install gì. Design: dieu28-trien-khai/design/d28-generated-table-map-design.md Lessons: rev5 host-side .env wrong, rev6 container-exec wrong (production minimal). rev7 = host node + .mjs.


Hard boundaries

NO_DEPLOY=true
NO_LIVE_ROUTE_SMOKE=true
NO_DIRECTUS_MUTATION=true
NO_PG_MUTATION=true
NO_PUBLISH_EVENT_OUTBOX=true
NO_CHANGE_TABLE_REGISTRY=true
NO_SECRET_IN_CODE_OR_LOG=true
NO_PRINT_ENV_TOKEN_URL=true
NO_PACKAGE_INSTALL=true
NO_NPX_AUTO_INSTALL=true
NO_LOCKFILE_CHANGE=true
NO_CONTAINER_RESTART=true
NO_DOCKER_COMPOSE_RESTART=true

Execution model — Host Node .mjs (rev7 critical)

execution_model=HOST_NODE_MJS_NO_DEPS

Generator viết bằng .mjs chạy trực tiếp trên host bằng Node binary. Chỉ dùng Node built-ins (fetch, crypto, fs, path). KHÔNG external dependency. KHÔNG container exec. KHÔNG install package.

Pattern:

ssh contabo "cd /opt/incomex/docker/nuxt-repo/web \
  && set -a; source /opt/incomex/docker/.env 2>/dev/null; set +a \
  && export NUXT_DIRECTUS_SERVICE_TOKEN=\"\$DIRECTUS_ADMIN_TOKEN\" \
  && export NUXT_PUBLIC_DIRECTUS_URL=\"\$DIRECTUS_PUBLIC_URL\" \
  && export DIRECTUS_URL=\"\$DIRECTUS_PUBLIC_URL\" \
  && node scripts/generate-table-maps.mjs"

KHÔNG print token/URL value.


Bước 0 — Preflight

0A. Host Node version check

ssh contabo "node --version 2>/dev/null || echo NODE_NOT_FOUND"

Verify: Node ≥ 18 (cho fetch built-in). Missing hoặc < 18 → STOP HOST_NODE_UNAVAILABLE. Recommend Path A as fallback.

ssh contabo "node -e 'console.log(typeof fetch === \"function\" ? \"FETCH_OK\" : \"FETCH_MISSING\")'"

FETCH_MISSING → STOP.

0B. Host repo clean

ssh contabo "cd /opt/incomex/docker/nuxt-repo && git status --porcelain && git rev-parse HEAD"

Empty porcelain = PASS. Branch divergence acceptable.

0C. /opt/incomex/docker/.env exists + has needed vars (KHÔNG print giá trị)

ssh contabo "test -f /opt/incomex/docker/.env && echo ENV_FILE_OK || echo ENV_FILE_MISSING"
ssh contabo "grep -q '^DIRECTUS_ADMIN_TOKEN=' /opt/incomex/docker/.env && echo TOKEN_KEY_PRESENT=true || echo TOKEN_KEY_PRESENT=false"
ssh contabo "grep -q '^DIRECTUS_PUBLIC_URL=' /opt/incomex/docker/.env && echo URL_KEY_PRESENT=true || echo URL_KEY_PRESENT=false"

Bất kỳ false → STOP ENV_SOURCE_INCOMPLETE.

0D. Env runtime export verify

ssh contabo "set -a; source /opt/incomex/docker/.env 2>/dev/null; set +a; node -e \"const t=process.env.DIRECTUS_ADMIN_TOKEN, u=process.env.DIRECTUS_PUBLIC_URL; console.log('TOKEN_RUNTIME='+(t?'true':'false')); console.log('URL_RUNTIME='+(u?'true':'false'))\""

Cả hai true = PASS. False → STOP ENV_RUNTIME_FAIL.

KHÔNG print giá trị.

0E. Snapshot consumer files

ssh contabo "cd /opt/incomex/docker/nuxt-repo && wc -l \
  web/pages/knowledge/registries/\\[entityType\\]/index.vue \
  web/config/detail-sections.ts \
  web/server/api/discovery/relations.get.ts \
  web/package.json \
  web/.gitignore"

Verify counts (best effort).

0F. Live status values (host node + aliased env)

ssh contabo "set -a; source /opt/incomex/docker/.env 2>/dev/null; set +a; node -e \"const url=process.env.DIRECTUS_PUBLIC_URL;const tok=process.env.DIRECTUS_ADMIN_TOKEN;fetch(url+'/items/table_registry?fields=id,table_id,status&limit=-1',{headers:{Authorization:'Bearer '+tok}}).then(r=>{if(!r.ok){console.log('HTTP_'+r.status);process.exit(1)}return r.json()}).then(d=>{const s=[...new Set(d.data.map(r=>r.status))];console.log('STATUSES='+JSON.stringify(s));console.log('DRAFT_ROWS='+JSON.stringify(d.data.filter(r=>r.status==='draft').map(r=>r.table_id)))}).catch(()=>console.log('API_FAIL'))\""

API_FAIL → STOP. Generator KHÔNG in e.message (có thể chứa URL), chỉ in API_FAIL hoặc HTTP_<status>. Report: live_status_values, production_statuses=['active','published'], excluded_rows_by_status.


Bước 1 — Generator script (.mjs, no deps)

File: web/scripts/generate-table-maps.mjs

Tạo via host write (heredoc):

ssh contabo "mkdir -p /opt/incomex/docker/nuxt-repo/web/scripts && cat > /opt/incomex/docker/nuxt-repo/web/scripts/generate-table-maps.mjs << 'GENEOF'
<file content>
GENEOF"

Script requirements

#!/usr/bin/env node
// @ts-check
/**
 * D28 Phase 1B — Generated table maps from table_registry
 * Path F: Host-side Node .mjs, no external deps
 * Source: GET /items/table_registry (table_registry collection only — no URL/host stored in output)
 * Output: ../generated/table-maps.generated.ts
 */

Generated artifact content safety (CRITICAL):

Generated .ts file PHẢI chứa CHỈ:

  • source_table='table_registry' (literal string, không phải URL)
  • row_count=<int>
  • status_filter=['active','published']
  • content_hash='sha256:...'
  • generated_at='<ISO_DATE>'
  • generator_path='scripts/generate-table-maps.mjs'
  • 3 maps + META object

PHẢI KHÔNG chứa:

  • Directus URL (vps.incomex... hoặc bất kỳ hostname nào)
  • Token, Authorization header, Bearer prefix
  • Env var values
  • Hostname derived từ env

Generator code: KHÔNG include url hoặc process.env.* values trong output string. Chỉ include literal source_table='table_registry'.

Error handling safety:

  • Network errors: .catch(() => 'API_FAIL') — KHÔNG print e.message (có thể chứa URL)
  • HTTP non-200: print HTTP_<status> only, KHÔNG print response body
  • Auth failures: print HTTP_401 hoặc HTTP_403, KHÔNG print headers/body
  • Env missing: print ENV_<NAME>_MISSING, KHÔNG print value

Env var support (rev8):

Script PHẢI support cả 2 nhóm tên (host vs container conventions):

const TOKEN = process.env.NUXT_DIRECTUS_SERVICE_TOKEN || process.env.DIRECTUS_ADMIN_TOKEN;
const URL = process.env.NUXT_PUBLIC_DIRECTUS_URL || process.env.DIRECTUS_PUBLIC_URL || process.env.DIRECTUS_URL;

Report: env_names_supported=['NUXT_DIRECTUS_SERVICE_TOKEN','DIRECTUS_ADMIN_TOKEN','NUXT_PUBLIC_DIRECTUS_URL','DIRECTUS_PUBLIC_URL','DIRECTUS_URL'].

import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

// Constants
const PRODUCTION_STATUSES = ['active', 'published'];
const OVERRIDES = {
  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',
};
const SKIP = ['tbl_workflow_timeline'];

// STATIC_EXTRAS = LEGACY EXCEPTIONS (rev8)
// These collections are referenced by Nuxt code but DO NOT have table_registry rows.
// They are LEGACY artifacts kept here only to maintain current Nuxt behavior.
// MUST NOT EXPAND without separate D28 design review.
// Goal: migrate these to proper table_registry rows or remove from Nuxt → empty STATIC_EXTRAS.
const STATIC_EXTRAS = {
  trigger: 'trigger_registry',
  comment: 'task_comments',
  taxonomy: 'taxonomy',
};

// Modes: generate (default) | --check | --print-hash | --include-draft
// E4 derivation: OVERRIDES → SKIP → STATIC_EXTRAS (legacy) → convention
// Convention: tbl_registry_X → singularize(X), tbl_X_list → X, tbl_X → X
// Fail-fast unresolvable
// reverseCollectionMap = literal emit
// SHA-256 sorted keys content hash
// Output: web/generated/table-maps.generated.ts (4 named exports)

Modes

  • node generate-table-maps.mjs (default): write artifact, exclude draft
  • node generate-table-maps.mjs --check: compute fresh, compare, exit 1 if drift
  • node generate-table-maps.mjs --print-hash: print SHA-256 only
  • node generate-table-maps.mjs --include-draft: include status=draft (preview only, NOT for commit)

Bước 2 — Generated artifact

Run host:

ssh contabo "cd /opt/incomex/docker/nuxt-repo/web \
  && set -a; source /opt/incomex/docker/.env 2>/dev/null; set +a \
  && export NUXT_DIRECTUS_SERVICE_TOKEN=\"\$DIRECTUS_ADMIN_TOKEN\" \
  && export NUXT_PUBLIC_DIRECTUS_URL=\"\$DIRECTUS_PUBLIC_URL\" \
  && node scripts/generate-table-maps.mjs"

Verify (KHÔNG print artifact content):

ssh contabo "test -f /opt/incomex/docker/nuxt-repo/web/generated/table-maps.generated.ts && echo ARTIFACT_OK || echo ARTIFACT_MISSING"
ssh contabo "wc -c /opt/incomex/docker/nuxt-repo/web/generated/table-maps.generated.ts"
ssh contabo "grep -q 'AUTO-GENERATED' /opt/incomex/docker/nuxt-repo/web/generated/table-maps.generated.ts && echo HEADER_OK || echo HEADER_MISSING"
ssh contabo "grep -q 'source_table' /opt/incomex/docker/nuxt-repo/web/generated/table-maps.generated.ts && echo SOURCE_LITERAL_OK || echo SOURCE_LITERAL_MISSING"
ssh contabo "grep -q -i -E 'http[s]?://|bearer|authorization' /opt/incomex/docker/nuxt-repo/web/generated/table-maps.generated.ts && echo ARTIFACT_LEAK_FAIL || echo ARTIFACT_LEAK_PASS"

ARTIFACT_LEAK_FAIL → STOP, do not commit. Generated file leaked URL/token → fix generator.

Output: web/generated/table-maps.generated.ts với:

  • Header: AUTO-GENERATED, generator path, source, row count, status filter, hash, date
  • 4 named exports
  • Default exclude draft

.gitignore

ssh contabo "cat /opt/incomex/docker/nuxt-repo/web/.gitignore | grep -i 'generated' || echo NO_GENERATED_RULE"

Patch only if needed.


Bước 3 — Replace 3 consumers (host edit)

3A. pages/knowledge/registries/[entityType]/index.vue

Replace hardcoded tableIdMap:

import { tableIdMap } from '~/generated/table-maps.generated';

3B. config/detail-sections.ts

Replace hardcoded collectionMap + reverseCollectionMap:

import { collectionMap, reverseCollectionMap } from '~/generated/table-maps.generated';

3C. server/api/discovery/relations.get.ts

Import alias precheck:

ssh contabo "grep -rn '~/\\|@/' /opt/incomex/docker/nuxt-repo/web/server/api/ --include='*.ts' | head -5"

If ~/ works → use it. Else relative path.

rev7 limitation: Build/typecheck verification KHÔNG chạy được (host không có deps, container không có source). Report:

server_import_alias_precheck=TILDE_FOUND|NO_TILDE_FOUND
server_import_final_verification=NEEDS_BUILD_VERIFY

Build verify deferred sang follow-up deploy pack.


Bước 4 — package.json (host edit)

Add scripts:

"generate:table-maps": "node scripts/generate-table-maps.mjs",
"verify:table-maps": "node scripts/generate-table-maps.mjs --check"

KHÔNG prebuild. KHÔNG modify lockfile. KHÔNG add tsx dependency.


Bước 5 — CI (conditional, không bắt buộc Phase 1B)

Same as rev6:

ci_token_status=UNKNOWN_NOT_VERIFIABLE
ci_check_status=ADDED_ASSUMES_SECRET|NOT_MODIFIED

Workflow step (nếu thêm) dùng node scripts/generate-table-maps.mjs --check, không cần tsx setup.


Bước 6 — Verify (host-side, NO build/typecheck)

6A. --check verify

ssh contabo "cd /opt/incomex/docker/nuxt-repo/web \
  && set -a; source /opt/incomex/docker/.env 2>/dev/null; set +a \
  && export NUXT_DIRECTUS_SERVICE_TOKEN=\"\$DIRECTUS_ADMIN_TOKEN\" \
  && export NUXT_PUBLIC_DIRECTUS_URL=\"\$DIRECTUS_PUBLIC_URL\" \
  && node scripts/generate-table-maps.mjs --check; echo EXIT=\$?"

Expected: exit 0. Drift → fix trước commit.

6B. Build/Typecheck — NOT_RUN

Host KHÔNG có deps. Production container KHÔNG có source. Build/typecheck không thể chạy ở Phase 1B.

Report:

build_typecheck_status=NOT_RUN_RUNTIME_CONTAINER_NO_SOURCE_AND_HOST_NO_DEPS

Verify dependencies cho consumer changes (especially server import alias) sẽ làm trong follow-up deploy/build pack. Phase 1B = sources committed only.

6C. Static syntax check (best-effort)

ssh contabo "node --check /opt/incomex/docker/nuxt-repo/web/scripts/generate-table-maps.mjs && echo SYNTAX_OK || echo SYNTAX_FAIL"

For .mjs script only. .ts files cannot be syntax-checked without tsc.


Bước 7 — Git commit (host-side, controlled)

File whitelist

web/scripts/generate-table-maps.mjs       (NEW — note .mjs extension)
web/generated/table-maps.generated.ts     (NEW)
web/pages/knowledge/registries/[entityType]/index.vue (MOD)
web/config/detail-sections.ts             (MOD)
web/server/api/discovery/relations.get.ts (MOD)
web/package.json                          (MOD)
web/.gitignore                            (MOD, optional)
.github/workflows/*                       (MOD, optional)

Lockfile = STOP. Unexpected file = STOP.

Pre-commit verify

ssh contabo "cd /opt/incomex/docker/nuxt-repo && git status --porcelain && git diff --name-only"

Verify only whitelisted. Verify lockfile unchanged.

Commit

ssh contabo "cd /opt/incomex/docker/nuxt-repo && git add <whitelisted files> && git diff --cached --stat && git commit -m 'D28 Phase 1B: replace 3 hardcoded maps with generated table-maps from table_registry (Option C, E4, host-mjs no-deps)'"

CI blocked + build verify deferred → commit allowed (PARTIAL). Follow-up packs required.


Phase 1B status ceiling (rev8)

best_possible_status=PARTIAL_UNTIL_BUILD_VERIFY

Vì rev7/rev8 KHÔNG chạy build/typecheck (host no deps + container no source), Phase 1B status MAX = PARTIAL, KHÔNG được claim PASS.

PASS chỉ đạt được sau follow-up D28_DEPLOY_BUILD_VERIFY_PACK (build trong CI hoặc dev compose).

Outcome Phase 1B status
All preflight + generate + commit OK, build/typecheck NOT_RUN PARTIAL
--check fail FAIL
Preflight fail (Node missing, env missing) BLOCKED
Build verify FAIL (post follow-up) FAIL (in deploy pack)
Build verify PASS (post follow-up) PASS (in deploy pack)

Bước 8 — Report

Path: knowledge/dev/laws/dieu28-trien-khai/reports/d28-generated-table-map-implementation-report.md (update rev2 → rev3).

## Execution model
execution_model=HOST_NODE_MJS_NO_DEPS

## Preflight
host_node_version=
host_node_fetch_available=
host_repo_clean=
head_commit=
env_file_present=
token_key_present=
url_key_present=
token_runtime=
url_runtime=
tableIdMap_before=
collectionMap_before=
relations_mirror_before=
live_status_values=
production_statuses=['active','published']
excluded_rows_by_status=

## Implementation
generator_extension=.mjs
generator_dependencies=node_builtins_only (fetch, crypto, fs, path)
package_install_used=false
env_names_supported=['NUXT_DIRECTUS_SERVICE_TOKEN','DIRECTUS_ADMIN_TOKEN','NUXT_PUBLIC_DIRECTUS_URL','DIRECTUS_PUBLIC_URL','DIRECTUS_URL']
tableIdMap_entries=
collectionMap_entries=
reverseCollectionMap_entries= (literal)
content_hash=sha256:
e4_overrides_used=
e4_convention_derived=
skipped_rows_with_reason=
static_extras_with_reason=
static_extras_are_legacy_exceptions=true
static_extras_must_not_expand_without_D28_design_review=true
draft_included=NO
artifact_leak_check=PASS|FAIL
artifact_no_url=true
artifact_no_token=true
server_import_alias_precheck=
server_import_final_verification=NEEDS_BUILD_VERIFY (deferred)

## Verify
check_verify=PASS|FAIL
syntax_check_mjs=PASS|FAIL
build_typecheck_status=NOT_RUN_RUNTIME_CONTAINER_NO_SOURCE_AND_HOST_NO_DEPS

## CI
ci_token_status=UNKNOWN_NOT_VERIFIABLE
ci_check_status=ADDED_ASSUMES_SECRET|NOT_MODIFIED

## Git (host)
expected_files_only=
lockfile_changed=false
git_commit_created=
git_commit_hash=

## Deploy
deployed=NO
live_route_smoke=SKIPPED_NO_DEPLOY

## Attestation
no_deploy=true
no_live_route_smoke=true
no_directus_mutation=true
no_pg_mutation=true
no_publish_event_outbox=true
no_table_registry_mutation=true
no_secret_printed=true
no_npx_auto_install=true
no_package_install=true
no_lockfile_change=true
no_container_restart=true
no_docker_compose_restart=true
no_external_deps_in_generator=true

## Status
phase1b_status=PARTIAL|FAIL|BLOCKED  # PASS NOT POSSIBLE in rev8 — see ceiling above
best_possible_status=PARTIAL_UNTIL_BUILD_VERIFY
follow_up_packs=
  - D28_DEPLOY_BUILD_VERIFY_PACK (mandatory: server import + build/typecheck verification)
  - D28_CI_TOKEN_SETUP_PACK (if CI not configured)
rollback_command=git revert <hash>

Rollback

ssh contabo "cd /opt/incomex/docker/nuxt-repo && git revert <commit_hash> --no-edit"

(File cleanup tự động qua git revert. Không container restart.)


D28 Implementation | Rev8 | Phase 1B | HOST_NODE_MJS_NO_DEPS | NO DEPLOY | PARTIAL CEILING | 2026-05-10

Back to Knowledge Hub knowledge/dev/laws/dieu28-trien-khai/prompts/d28-generated-table-map-implementation-prompt.md