D28 — Generated Table Map Implementation — Agent Prompt (Phase 1B)
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 =
.mjschạ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.mdLessons: 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 printe.message(có thể chứa URL) - HTTP non-200: print
HTTP_<status>only, KHÔNG print response body - Auth failures: print
HTTP_401hoặcHTTP_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 draftnode generate-table-maps.mjs --check: compute fresh, compare, exit 1 if driftnode generate-table-maps.mjs --print-hash: print SHA-256 onlynode 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