執行期與 IaC 權威
後端以 prd 啟動、GraphQL 回應 HTTP 200,並觀察到 Cloud SQL + Upstash 連線。CMS 與 web 在 run.app 上健康。infra/terraform/envs/gcp-cloudrun/ 是宣告式權威;實際後端資源已 import 並 0-destroy reconcile。
本手冊已加入 2026-06-27 正式部署增補:後端、CMS 與 web 已在 GCP Cloud Run run.app 上線,搭配 Cloud SQL PG17、Upstash Valkey TLS、Secret Manager 與宣告式 Terraform IaC。正式 Cloud SQL 上的 shared-table RLS 也已完成佈建並接入後端。下方截圖仍是既有 2026-06-16 本機視覺證據與 2026-06-21 受治理金流截圖;本次新增的是部署狀態,不是新的正式環境截圖證據。
平台現在具備受管理的正式部署路徑:後端、CMS、web 使用 GCP Cloud Run;資料庫為透過 Auth-Proxy unix socket 存取的 Cloud SQL PG17;快取為 TLS 連線的 Upstash Valkey;密鑰在 Google Secret Manager;並建立 api、www、admin 的 Cloud Run domain mappings。本次手冊更新記錄部署狀態與操作邊界,不取代既有本機/manual 截圖。
後端以 prd 啟動、GraphQL 回應 HTTP 200,並觀察到 Cloud SQL + Upstash 連線。CMS 與 web 在 run.app 上健康。infra/terraform/envs/gcp-cloudrun/ 是宣告式權威;實際後端資源已 import 並 0-destroy reconcile。
正式 Cloud SQL 現在有 cms_tenant_app 非 superuser app role、41 張資料表 ENABLE+FORCE RLS,且 verify-rls PASS。後端 env/secrets 已透過 Secret Manager 接入 SCHEMA_TIER_APP_USER / password。
api、www、admin 的 Google-managed certificates 仍在佈建。因此本手冊尚不宣稱自訂網域端到端瀏覽器證明,也不宣稱公開網域 /mcp 跨租戶證明。
Worker runtime、AGE/GraphRAG、多區域與 UAT sign-off 不屬於本次證據。既有截圖維持 predecessor/local 證據;金流捐款截圖則保留其受治理 browser-full-integration 層級。
一批後端/測試層級的正式環境就緒工作以多個 PR(#107/#108/#110/#111/#112/#113)提交至 dev,以可執行的 chaos 與 fail-closed 測試強化平台的失效行為,並修復過程中發現的一個真實接線缺陷。這些變更的是後端行為而非操作畫面,因此下方截圖仍然成立;此為依據 spec/PR 的內容說明,並非重新執行期擷取。對操作者的實際效果是:訊息/金流 webhook 在並發洪峰下能安全去重、資產上傳與 AI 生成皆 fail-closed(不留孤兒記錄、不洩漏 DB 連線),且 AI 配額在真正的交易路徑上獲得驗證。
IngestMessage → 僅有一筆草稿與一筆日誌留存,其餘去重且無孤兒記錄。封閉最後一個相鄰相依的 chaos 缺口。一條後端/設定層級的強化工作以 PR #71 形式提交至 dev,源自一次全庫 mock/stub/false-green 稽核(prod-readiness-wireup-hardening)。它變更的是安全性與接線行為,而非操作畫面,因此下方截圖仍然成立;此為依據 spec/PR 的內容說明,並非重新執行期擷取。
ENV=production 時會拒絕預設帳號/密碼並要求 TLS(require/verify-ca/verify-full),與平台資料庫的驗證一致。本機/開發行為不變。NEXT_PUBLIC_*_API_KEY(會把密鑰打包進瀏覽器);產生內容改走後端 BYOK 路徑,且管理端 endpoint 退回機制會在 Vercel 式正式環境標記下 fail-closed。chaos/resilience 測試證明平台會有界地優雅降級、絕不卡死:連線池耗盡時不超過上限且能恢復;租戶資料庫短暫中斷不會被快取、資料庫回來後路由自我修復;AI provider 卡住時以有界的具型別錯誤失敗(客戶端逾時為有限值)。一個通用、site-scoped 的金流 / 捐款 / 電商模組已以 PR #86 併入 dev(payment-plugin)。捐款只是通用 Payment* 核心的其中一個 purpose 預設。以下是直接由 DB 頁面資料、對真實後端渲染的捐款頁。
PaymentForm(DB 驅動)已 seed 的 NAELT /site/naelt/support/donate 頁完全由 DB 頁面資料渲染通用 PaymentForm(donation-form manifest 預設):預設金額、自訂金額、付款人欄位,以及由供應商驅動的送出(Stripe 轉址 / 藍新金流 form-post)。密鑰不會進入瀏覽器 — 後端依 siteID 解析該站的 BYOK 供應商。

CMS 設定頁新增 捐款與金流 分頁(供應商型錄、引導設定 精靈、遮罩 BYOK 密鑰輪替、捐款/訂單清單 + ezPay 電子發票狀態)與 商品與服務 分頁(型錄 CRUD、SKU 變體、Udemy 式數位內容編輯:公開預覽 vs 付費)。後台 GraphQL 受 RBAC 控管(site.payment.* / site.catalog.*)。
能力 8 — 金流 / 捐款 / 電商。 通用公開 createPaymentCheckout(purpose) + 供應商註冊表驅動(Stripe Checkout 一次性 + 訂閱;藍新金流 MPG 2.0 + 定期定額)+ ezPay 電子發票自動開立 + BYOK 各站密鑰 + 商品/服務型錄 + 數位授權(付款後自動授予、fail-closed 存取);donation-form page-builder 元件渲染通用 PaymentForm;單一掛載開關 PAYMENT_MODULE_ENABLED。權威:.agents/specs/payment-plugin/review.md、PR #86。
以 showcase 租戶作為標準範例(其首頁 + /components 元件展示頁已涵蓋全部 11 個 page-builder 元件),提供平台「內容創作與執行期」能力、依真實契約撰寫的詳細範例。證據層級:依真實 GraphQL/路由/manifest 契約撰寫的示意範例(後端/CLI 層級的命令與 payload 證據),非 live-demo 截圖;各能力下方標註 claim-cap。就緒與否的權威仍為各 spec 的 review.md。
頁面是 sections 陣列;每個 section 指定一個來自 component-manifest SSOT 的元件(componentManifests GraphQL ← backend/internal/componentmanifest/manifests.json)與其 props。CMS 的 Pages 建構器編輯 sections,並以 updatePageSections(id, sections: [JSON!]!) 整個 section 陣列覆寫儲存。公開路由為 force-dynamic,因此把已註冊元件加到頁面是 純 DB 資料變更 — 不需重建映像。
# showcase 首頁由這些 manifest 元件組成(backend/seeds/showcase-pages.json):
HeroSection · FeatureGrid · ContentList · EventSection · ImpactStats · CTASection
# /components 展示頁額外示範:
ResourceGrid · ContentSection · EnhancedContentList · EnhancedEventCalendar
# 儲存已編輯頁面(整陣列覆寫)— 來自 CMS 頁面建構器:
mutation { updatePageSections(id: "<pageId>", sections: [ /* 完整且有序的 section 陣列 */ ]) { id } }
生成在伺服器端透過 generateAIContent(prompt, providerId) 執行;CMS 不在瀏覽器持有 provider 密鑰(見 2026-06-18 安全強化)。每個站台宣告自己的 provider 設定檔(Google Vertex/Gemini、AWS Bedrock、Azure AI/OpenAI、OpenAI、Anthropic、TensorZero、OpenCode),含 BYOK 密鑰 metadata 與用量帳本。
mutation { generateAIContent(prompt: "為我們的新工作室撰寫一篇上線部落格文章", providerId: "") { content model tokensUsed } }
# providerId "" = 使用站台預設設定檔;後端解析 BYOK 金鑰,瀏覽器永遠不接觸金鑰。
內容以內容型別建模,並經 draft → published → unpublished 狀態生命週期(依狀態列表)。公開表單投稿進入受治理的投稿流程(送出 → 核准/退回)於 CMS 審核。每個操作皆經權限檢查(例如 site.content.*、site.social.read/manage)。
# 公開站台表單區塊(showcase /contact)→ 後端發出 submissionId → CMS 審核佇列(核准/退回)。
# 內容列表/詳情頁依 contentType(article/event)透過 ContentList/ContentSection 渲染 DB 內容。
每個站台提供 agent 友善的讀取介面:Model Context Protocol 端點 /mcp(list/get/search contents)、MCP 探索文件 /.well-known/mcp.json、以及每站台與平台層級的 llms.txt。非 VIP 租戶的讀取在啟用 SCHEMA_TIER_APP_USER 時由 D2 Row-Level Security(路由的非超級使用者角色)限制。
# 透過 MCP(HTTP/JSON-RPC)探索 + 讀取站台:
GET /.well-known/mcp.json # server 描述
POST /mcp → tools: list_contents(site) · get_content(site, slug) · search_contents(site)
GET /site/showcase/llms.txt # agent 友善站台摘要
站台定義宣告式 skill/prompt 範本(internal/skilltmpl;seeds skills-platform.json/skills-naelt.json)用於轉換/優化內容 — 無腳本/程式碼執行、無 sandbox。範本有版本控制,透過 upsertSkillTemplate 管理(建立 ⇒ v1;更新 ⇒ 版本遞增;已發布範本不可變;跨站台 fail-closed;@requirePermission("site.social.manage"))。
mutation { upsertSkillTemplate(input: { id: 0, kind: "post-conversion", name: "LINE→部落格", body: "..." }) { id version kind } }
# id:0 → 建立版本 1;id>0 → 更新 + 版本遞增。
A2UI agent(backend/internal/adk)從同一份 component-manifest 目錄創作 page-builder sections,經 manifest 驗證關卡 + design-contract「不杜撰事實」誠實邊界 + 不可信內容邊界。它提出符合 manifest 的 sections,由你透過正常的 updatePageSections 儲存 — 因此 agent 產出與人工頁面建構編輯受完全相同的契約約束。
# A2UI 流程:目錄提示(componentManifests)→ agent 提出 sections → manifest 驗證關卡
# → design-contract 誠實檢查 → 人工審核 → updatePageSections(儲存)。
平台無關、整合優先的管線(messaging-ingest-social-publishing):入站訊息進入單一標準 webhook、正規化、經站台 AI provider + 宣告式 skill 範本轉為貼文草稿、排入審核佇列,再透過可插拔 publisher drivers 發布。全程站台範圍 RBAC(site.social.read/manage/publish/secret);密鑰為站台 BYOK。
# 1) 接收 — providers 自我註冊;每平台/站台單一標準入站路由:
POST /api/webhooks/messaging/{platform}/{site} # 例如 line、telegram、whatsapp(簽章驗證、fail-closed)
# 2) 轉換 — 正規化訊息 → 貼文草稿,經 SiteAIProviderResolver + 每站台 skill 範本(無 sandbox)。
# 3) 審核 — 草稿進入 CMS「Messaging & Social」審核佇列:
mutation { updatePostDraft(input: { id: "<draftId>", blocks: [...], metadata: {...} }) { id status version } } # 已發布草稿不可變
# 4) 發布 — 透過可插拔 SocialPublisher driver(例如 Facebook Graph):
mutation { publishPostDraft(id: "<draftId>") { id status } } # @requirePermission("site.social.publish")
在下方 2026-06-16 執行期擷取之後,有兩條後端/API 層級的工作以 PR 形式提交至 dev。它們新增的是後端行為,而非新的操作畫面,因此本頁截圖仍然成立;此為依據 spec/PR 的內容說明,並非重新執行期擷取。
cms_showcase 的原因)。SpawnSite 現在會為 VIP 站台建立實體 cms_<slug> 資料庫並將 schema 遷移進去;非 VIP(SCHEMA/D2)站台則依設計維持「僅紀錄」。失敗即關閉(fail-closed):若佈建失敗,會回滾站台紀錄,不留下懸空項目。updatePostDraft(於發佈前編輯草稿的 blocks/metadata——已發佈貼文不可變更)與 upsertSkillTemplate(建立,或更新並遞增版本,技能範本)兩個 GraphQL 操作現已實作,皆以 site.social.manage 權限把關。CMS 內對應的編輯 UI 為已追蹤的後續項目。請優先使用已納管的 seed/demo 資料。請勿自行捏造一次性的手動範例。
backend/seeds/{platform,naelt,showcase,tenant-demo}-*.json 提供頁面、內容、提交類型、主題、導覽與技能。tenant-demo 帶有 "tenancyTier":"schema",因此會以非 VIP 站台的形式進行 seed。使用 python scripts/ops.py db seed {platform|naelt|showcase|tenant-demo} 進行 seed。
scripts/seed_e2e_data.py / scripts/seed_e2e_data.sql 記載了可重複測試/demo 流程所需的標準角色與範例租戶(8 個執行期使用者:platform_admin、site_admin、editor、viewer、MFA、兩位租戶管理員)。標記為 MOCK-DEV-ONLY-* 的密碼絕非正式環境憑證。
最新的截圖與 CLI 文字記錄存放於 docs/manual/assets/,命名為 *-manual-2026-06-16.png 與 *-cli-2026-06-16.txt,於 2026-06-16 從執行中的本機堆疊(真實資料/API)擷取。
除非已明確指定並佈建了正式環境目標,否則請在本機執行期上依循這些路由。
開啟 http://localhost:23000,透過設定好的流程驗證身分,並確認儀表板範圍。平台管理員會看到平台控制平面外殼,內含平台站台指標(現在共 4 個站台)、最近活動 (Recent Activity) 與快速操作 (Quick Actions);站台管理員則看到站台範圍的工作區。範圍權威來自已驗證的角色與當前的 site/tenant 情境,而非視覺提示。

開啟 http://localhost:23000/pages。此清單是進入 manifest 驅動頁面建構器的入口:每頁的版面 (Layout)(default / content-detail / event-detail)、狀態、依標題/slug 搜尋、狀態與版面篩選,以及每列的 預覽/編輯/刪除。把一個已註冊的平台元件加入頁面屬於資料庫資料變更——不需要重建前端。

開啟 http://localhost:23001/site/platform。此 seed 支援的行銷介面建立在 shadcn/Tailwind-v4 基礎上:漸層 hero、功能格、上手步驟、平台指標與 CTA 區塊。

tenant-demo(SCHEMA 層級 · D2 共用資料表 RLS) 新增開啟 http://localhost:23001/site/tenant-demo。這第四個 seed 站台為非 VIP (non-VIP):其內容存放於共用平台資料表中,並透過以站台 slug 為鍵的 PostgreSQL Row-Level Security 進行隔離(即「D2」模型),而非存放於專屬資料庫。它的渲染與 VIP 站台完全相同——證明此隔離模型對終端使用者而言是不可見的。

開啟 http://localhost:23001/site/showcase——「Showcase Studio」,一個獨立的 DB 驅動 demo 站台,證明可透過 site config/theme/page seed 從單一程式碼庫交付多站台。

開啟 http://localhost:23001/site/platform/contact 以檢視平台的公開提交路由。

開啟 http://localhost:23001/site/naelt/volunteer/register。已 seed 的 manifest 會渲染繁體中文欄位群組(志工報名)與必填欄位。

平台從單一 VIP 旗標決定每個站台的資料隔離架構:VIP ⇒ database-per-tenant(站台取得自己的實體資料庫);非 VIP ⇒ 共用資料表 Row-Level Security(即「D2」模型——共用資料庫與 schema,以站台為鍵透過 RLS 隔離,並於執行期透過非 superuser 的應用程式角色強制執行)。VIP 目前由平台控管。
/platform/sites)平台超級管理員開啟 http://localhost:23000/platform/sites,即可從單一表格管理所有站台。每一列顯示站台、VIP 星號、層級 (tier)(SCHEMA 或 DATABASE)、base URL,以及自訂網域驗證狀態。每列操作:
pg_dump/restore 序列(佈建目標、複製、驗證,最後才刪除來源)。注意 tenant-demo 顯示設定 VIP,而三個 DATABASE 站台顯示移除 VIP。Spawn site 與 Backup(configure/run/restore-test)操作呈現為停用狀態——那些操作尚未可用。

/settings/dr)站台/租戶管理員開啟 http://localhost:23000/settings/dr 以管理自己的站台。層級 (Tier) 與 VIP 顯示為唯讀(由平台控管;若要變更,請聯絡平台)。此面板也顯示 base URL 與自訂網域 DNS-TXT 驗證狀態,再加上一個 URL 設定表單:選擇站台的存取方式——子網域(slug.platform)、路徑 fallback(platform/slug),或選用的自訂網域——並儲存。
後端/CLI 介面(無瀏覽器)。在將非 VIP 讀取路徑指向任何 PostgreSQL 主機之前,你要先確認該主機能滿足 D2 模型,然後執行單一的冪等佈建腳本。此處的證據是命令文字記錄,而非截圖。
migrate db-doctor對任何候選 Postgres 執行,以確認它能執行 D2 讀取路徑(建立一個非 superuser 的應用程式角色,並讓 FORCE RLS 確實將其侷限),以及它是否具備執行期 GraphRAG 所需的 pgvector + Apache AGE(屬建議性質——它們的缺席不會阻擋 D2)。db-doctor inspect 為唯讀;預設模式會執行一個用完即丟的角色往返。對照受治理 PG15 的文字記錄:
$ migrate db-doctor ℹ️ [INFO] server-version PostgreSQL 15.18 ✅ [PASS] privileged-role-can-create-app-role ✅ [PASS] app-role-is-non-superuser ✅ [PASS] app-role-non-bypassrls ✅ [PASS] force-rls-confines-app-role ⚠️ [WARN] tls NOT using TLS — set sslmode=require for a hosted DB ℹ️ [INFO] max-connections max_connections=100 ⚠️ [WARN] extension:age age NOT available — runtime GraphRAG cannot run here ⚠️ [WARN] extension:vector vector NOT available — runtime GraphRAG cannot run here ✅ HOST OK for the D2 shared-table-RLS read path (advisory WARN items do not block D2).
scripts/provision_schema_tier_rls.py單一冪等命令會佈建非 superuser 的應用程式角色、seed 非 VIP 站台、安裝共用資料表 RLS,並驗證隔離。先用 --dry-run 預覽確切計畫(印出命令 + 一個已遮蔽密鑰的 DSN,不執行任何動作):
$ provision_schema_tier_rls.py --dry-run tenant-demo D2 shared-table RLS on host=db.example.com … password=*** sslmode=require | app_role=cms_tenant_app app_password=*** --dry-run: the following idempotent steps WOULD run (nothing executed): ▶ 1/4 provision non-superuser app role: migrate provision-app-role ▶ 2/4 seed schema-tier site 'tenant-demo': migrate migrate tenant-demo ▶ 3/4 install shared-table RLS + grant: migrate rls-rollout-shared ▶ 4/4 verify RLS isolation: migrate verify-rls tenant-demo
執行期環境契約記載於 .env.schema-tier.example;經路由的 /mcp 公開讀取路徑僅在後端啟動時設定了 SCHEMA_TIER_APP_USER 時才會開啟(預設關閉/可逆)。部署後,一個部署後的 /mcp 跨租戶 UAT 冒煙測試(環境變數 MCP_UAT_BASE_URL)會對照真實 URL 確認隔離。
這些是本次手冊更新所使用的確切輸入。RTM.md 僅為可追溯性情境;spec 內的審查與執行期報告仍為證明權威。
| 來源 | 使用方式 |
|---|---|
.agents/specs/{SPECS,NEXT_STEPS,ISSUE_LOG,RTM}.md | 2026-06-27 rollup — Cloud Run 部署、正式 Cloud SQL RLS follow-up,以及既有 D2/manual 證據。RTM 僅為 rollup,並非就緒權威。 |
.agents/specs/schema-per-tenant-routing-rls/{tasks.md (Slice 8), review.md, adr/ADR-RLS-002-NON-VIP-CANONICAL-D2.md} | D2(非 VIP)標準模型 + 主機預檢區段中記載的主機預檢/dry-run/UAT-smoke/transaction-pool-safety 交付項目。RLS 僅在應用程式以非 superuser 角色連線時才會強制執行。 |
.agents/specs/tenant-lifecycle-dr-admin/{design-cms-ui.md, requirements.md, tasks.md} | 平台 Sites & DR 表格與租戶 DR & Domain 面板的 DR 管理 UI 形態。(此 spec 沒有 review.md — 依指南允許,退而採用 design/requirements/tasks 鏈。) |
backend/seeds/{platform,naelt,showcase,tenant-demo}-*.json | 4 個 seed 站台的標準 seed/demo/範例資料(新的 tenant-demo 帶有 tenancyTier:schema)。 |
docs/manual/assets/*-2026-06-16.{png,txt} | 本次透過 scripts/capture_manual_2026_06_16.mjs 與 migrate CLI 從執行中本機堆疊擷取的 9 張最新截圖 + 2 份 CLI 文字記錄。 |
docs/MANUAL_GENERATION_GUIDE.md + 審查指南 | 手冊工作流程與來源清單規則(已加入 2026-06-16 備註)。 |
docs/*FEATURE*.md | 本次此 repo 中沒有符合的檔案(依指南允許,清單退回 spec/report 鏈)。 |
對照 2026-06-15 的手冊版本。
tenant-demo,因此 DR 管理表格現在會以真實資料顯示 SCHEMA 標章與設定 VIP(相對於移除 VIP)的區別,且非 VIP 的 D2 公開站台會渲染於 /site/tenant-demo。migrate db-doctor 能力探測(含 pgvector/AGE 偵測)與 provision_schema_tier_rls.py --dry-run 計畫現以真實 CLI 文字記錄呈現。scripts/ops.py db seed 原本呼叫舊的單檔 go run cmd/migrate/main.go(在 migrate 套件被拆成多個檔案後失效);已更正為 go run ./cmd/migrate,使 seed 再次可用。/site/tenant-demo(共 6 條路由)。/settings/dr 面板:四個 seed 站台都將網域設為 none,因此自訂網域的待處理/驗證狀態仍未被 seed 演練,且本次未擷取每租戶的 DR & Domain 面板。請新增一個帶有待處理自訂網域挑戰的站台。db-doctor 將它們顯示為建議性 WARN;尚未擷取兩者皆已安裝的主機。target_id 仍未決定;後端主機尚未佈建;app/API 主機名稱缺少 DNS/TLS/部署綁定。
目前截圖為本機執行期擷取;正式環境的 DNS/TLS/CDN/快取失效與爬蟲廣度仍屬外部/最終關卡工作。
D2 讀取路徑在受治理 PG15 上已端對端 runtime-backed,且主機預檢/操作者流程已建置,但對照真實託管 Postgres 執行(並確認透過公開網域的 /mcp)仍屬擁有者/基礎設施動作。
租戶生命週期 DR 管理 UI 在 mocked-GraphQL 元件層級 + 本次 hybrid 後端支援擷取下獲得證明——並非完整的後端支援瀏覽器 e2e。
CMS 儀表板/頁面建構器/DR 管理擷取使用驗證 API cookie 注入;屬 hybrid 證據,非完整瀏覽器登入。
頁面建構器截圖顯示已 seed 的 Pages 介面;即時頁面儲存/發佈與 A2UI agent apply 屬獨立工作流程證明,未在此擷取。