agentleFS
cogovercogover/cogover-skills/skills/cogover-custom-module/SKILL.md

cogover-custom-module

Skill13 starsChanged 33 days agoMITView on GitHub

Điều phối vòng đời Cogover Custom Module: khảo sát App/Object, chọn backend/frontend và dạng frontend (single page app, custom component, Federation Page), người dùng xác nhận trước khi code, schema duyệt bằng Excel, security rules, quy tắc nghiệp vụ theo Super Admin/role của người gọi, record trigger before/after-change, background job (enqueue, lịch cron, retry) hoặc Process, Custom Module Action cho node Process và tool AI Agent gọi (`defineAction`, schema `s`, `effect`, danh tính chạy), khởi chạy Process/AI Agent từ code (`processes.start`, `agents.start`, job nhận kết quả, mục `processes`/`agents` của identity policy, `deliverTo` để agent chủ động nhắn chat một nhân sự), push message (làm mới record, toast) và nhận push ở frontend, dùng `@cogover/client-sdk` (UI component, data hook, socket listener), gửi notification và email từ hộp thư được grant, đọc cơ cấu tổ chức (phòng ban, vị trí, chuỗi quản lý người duyệt), secret/credential cho fetch, crypto (SHA-256, HMAC, mã hoá AES/RSA, chữ ký RSA/ECDSA), inbound webhook (KEY/HMAC), đọc record chỉ field cần dùng (`fields`, `expandLookups` khi cần), số liệu đơn giản bằng `records.aggregate` và báo cáo/dashboard lấy từ saved report của $report-builder thay vì quét record, thiết kế trong ngân sách mỗi lần thực thi (`limits.usage()`, việc nặng chia trang sang background job), ghi hàng loạt bằng batch và gọi song song bằng `Promise.all`, xử lý HTTP `429`/`Retry-After` khi Workspace bận, lời gọi chạy đồng thời (`locks`, `expectedVersion`, `maxConcurrentRuns`), Project/policy/key, code/test theo template, E2E frontend trên trình duyệt, review và kiểm thử backend, review bảo mật và đo hiệu năng bằng sub-agent độc lập, publish qua Dev CLI, bàn giao. Không thay skill cấu hình Object/Process đơn lẻ.

  • Reads credentials

The file

Files
SKILL.md

Cogover Custom Module

  • Phiên bản: 1.24.0
  • Ngày phát hành: 2026-10-24

Tổng quan và phạm vi

Skill điều phối toàn bộ vòng đời Custom Module. Dùng $cogover-overview làm kiến thức nền; không chuyển vòng về orchestrator khác khi yêu cầu đã thuộc Custom Module. Chỉ dùng public API, metadata Workspace và tài liệu sản phẩm để xác định khả năng; không yêu cầu mã nguồn/hạ tầng của Cogover.

Custom Module dành cho nghiệp vụ mà Cogover Object/Process chuẩn chưa đáp ứng đủ: logic phía server, giao diện riêng hoặc cả hai, vẫn dùng dữ liệu và quyền của Workspace.

LoạiChọn khiVí dụ
Custom Frontend ModuleCần màn hình hoặc thành phần giao diện riêng; API hiện có đã đủ dữ liệu và logic với quyền người dùng. Ba dạng: single page app, custom component nhúng layout Object, Federation Page trong ứng dụng CogoverDashboard toàn màn hình, khối kiểm tra mã giảm giá trên form đơn hàng, trang công cụ trong menu ứng dụng
Custom Backend ModuleCần xử lý phía server, tổng hợp/ghi Object, API riêng, tích hợp có thông tin bí mật do Workspace giữ (secret/credential), quy tắc kiểm tra/điều chỉnh record trước khi lưu và xử lý tiếp sau khi lưu áp cho mọi nguồn ghi (record trigger), việc nền dài hoặc chạy theo lịch cron có retry (background job), nhận webhook từ hệ thống ngoài không có phiên người dùng (inbound webhook), ký/kiểm tra chữ ký, mã hoá/giải mã dữ liệu trao đổi với đối tác (crypto), làm mới record/toast cho người đang mở record (push message), gửi notification vào danh sách thông báo hoặc email từ hộp thư của Workspace (notification, email), dựa vào phòng ban, vị trí và chuỗi quản lý để chọn người duyệt, kiểm tra quyền duyệt (cơ cấu tổ chức), cung cấp thao tác ngắn có input/output định kiểu cho node Process hoặc tool của AI Agent gọi (Custom Module Action), hoặc khởi chạy Process, AI Agent từ code và xử lý kết quả trong background jobTính giá, đồng bộ ERP hằng đêm, API tổng hợp tồn kho, chặn xoá đơn đã hoàn thành, nhận sự kiện thanh toán từ cổng thanh toán, ký và mã hoá request gửi ngân hàng, tính lại tổng đơn rồi tải lại trang record đang mở, báo quản lý trực tiếp khi có yêu cầu nghỉ phép, chỉ cho quản lý của người tạo duyệt đề nghị thanh toán, gửi báo giá kèm file từ hộp thư dùng chung, chấm điểm lead làm một bước của Process và một tool của AI Agent, gửi đơn sang Process phê duyệt rồi cập nhật đơn khi Process kết thúc, nhờ AI Agent phân loại ticket rồi lưu kết quả
Cả haiMàn hình riêng cần gọi logic riêng phía backendMàn hình xử lý đơn hàng gọi API tính giá và cập nhật đơn

Mã frontend được tải xuống trình duyệt: logic nhạy cảm, quyền nâng cao và credential tích hợp phải nằm ở backend.

Số liệu tổng hợp không lấy bằng cách quét record. Số liệu đơn giản trên một Object mà backend tính theo điều kiện của lời gọi (đếm, tổng, trung bình, min/max, nhóm theo tối đa 3 field, không quá 5.000 nhóm, chấp nhận trễ khoảng 1 giây) dùng records.aggregate của @cogover/sdk (từ 0.13.0), chạy dưới danh tính của lời gọi; nhóm và metric ngoài count trên "id"/metric trên field số chỉ có với người gọi HTTP route hoặc data.asSystem(), không có với data.asUser() và data.object() khi lần chạy đọc dưới danh tính người dùng khác người gọi HTTP (trigger cho thay đổi do người dùng, job có người dùng, Development Session). Báo cáo, dashboard, KPI hoặc số liệu tổng hợp cần nhiều Object, nhóm theo kỳ, formula báo cáo hoặc do người dùng tự cấu hình và chia sẻ: trước hết kiểm tra bằng $report-builder có tạo được saved report tính số liệu đó không. Được thì agent đọc $report-builder để xây hoặc tái sử dụng báo cáo và phân quyền báo cáo đúng đối tượng dùng module; module đọc kết quả báo cáo bằng phiên của người dùng cuối thay vì quét record của Object (chậm, đặc biệt khi Object có từ hàng trăm record trở lên); chỉ cần xem bảng/biểu đồ chuẩn thì dùng dashboard qua $dashboard-builder. Chỉ quét record khi cả records.aggregate và báo cáo không biểu diễn được, kèm lý do; khi đó quét trong background job chia trang theo ngân sách của lần thực thi (dòng cuối bảng Chọn capability backend), không quét trong route phục vụ dashboard. Chi tiết và bảng chọn: Tận dụng báo cáo cho số liệu tổng hợp.

Ba dạng frontend khác nhau về bộ khung code và nơi hiển thị: single page app dùng stack tự chọn và mở qua /{slugSlot}/index.html; custom component và Federation Page bắt buộc dùng template React custom-frontend-module-template của Cogover, lần lượt nhúng vào layout Object qua item Federation component và hiển thị tại /{APP_SLUG}/c{N}/{PATH}. Chọn theo Các loại Custom Module và dạng frontend; lựa chọn phải được người dùng xác nhận theo mục 1A trước khi phát triển.

Môi trường cần sub-agent, shell/HTTP, công cụ build/test và trình duyệt khi làm frontend (chạy được script E2E tự động, ví dụ Playwright). Không có sub-agent: hoàn tất phần tư vấn/chuẩn bị làm được độc lập, nêu rõ điều kiện còn thiếu trước các bước bắt buộc giao agent, không tuyên bố đã đáp ứng yêu cầu phân công và không tự nhận vai reviewer để giả lập đánh giá độc lập.

Đầu vào

  • WORKSPACE_DOMAIN: hostname đầy đủ hoặc HTTPS origin. Chấp nhận tên viết nhầm WORKSAPCE_DOMAIN và chuẩn hóa về WORKSPACE_DOMAIN; không tự nối domain suffix, không giữ path/query trong origin.
  • Workspace API Key. Credential, header và quy ước response/lỗi chung: theo $cogover-api-auth. Skill dùng cả /bapi/v1 (API Key Bearer: tạo phiên, metadata) lẫn /api/v1 (phiên Web App: App, quản lý Project, gọi module); ngoại lệ riêng: mọi request /api/v1/ts-projects/... gửi cả x-req-type: 9 lẫn x-req-service (3 production, 4 quản lý, 6 preview), thiếu x-req-type: 9 thì service 4 bị Authorization Server xử lý như logout và thu hồi phiên; quản lý TS Project dùng POST cho cả đọc/list và trả object trực tiếp, không bắt buộc envelope r: 0; với x-req-type: 9 response là nguyên status/header/body của Runtime, không bọc {serviceVersion, service, id, type, body}, lỗi do Authorization Server sinh ra có header x-proxy-error: 1. Cogover Dev CLI đọc key theo tên COGOVER_API_KEY với thứ tự nguồn credential riêng: CLI, session và bàn giao.
  • Yêu cầu người dùng: luồng nghiệp vụ, người sử dụng, dữ liệu vào/ra, thao tác đọc/ghi, UI, trigger/lịch/tích hợp, kết quả mong đợi. Chỉ làm rõ phần chưa biết có ảnh hưởng thiết kế.
  • Sửa dự án: tra project ID/slug, thư mục source và version đang active; resolve đúng project trước khi sửa.
  • Không ghi key/secret vào source, cogover.json, frontend bundle, archive, log hay báo cáo. Không hardcode credential tích hợp kể cả trong source backend; chỉ dùng secret/credential của Project (bước 6A) và đọc qua fetch({ credential }), secrets.get hoặc key { secret: name } của thao tác crypto (HMAC, AES, RSA, sign); AES key và private key luôn lưu thành secret, không viết trong source. Giá trị secret, inbound key và HMAC secret do người dùng/quản trị viên nhập qua prompt ẩn hoặc stdin của CLI, không qua chat. Backend không mặc nhiên có mọi API của Node.js.

Tài liệu theo nhánh

Đọc bản hiện tại của mỗi skill được gọi và reference bắt buộc trước khi thao tác; truyền lại Workspace và yêu cầu đã xác định, không hỏi lặp đầu vào.

Khi thực hiệnĐọc
Chọn loại module và dạng frontend, chuẩn bị xác nhận 1ACác loại Custom Module và dạng frontend
Xác thực, quản lý Project hoặc bàn giao cURL$cogover-api-auth và CLI, session và bàn giao
Thiết kế quyền, tạo security rules cho Object mới$user-permission và Object Security Rules API, gồm rule giữ chỗ không chọn nhân sự
Quản lý backend, policy, key, version, production/previewCustom Backend Module API Reference
Quản lý frontend, version và asset URLCustom Frontend Module API Reference
Lưu source của Project trên Git server của Workspace (cogover-dev git setup), quản lý repository, tài khoản Git, quyền và token; đặt tài liệu, ảnh, E2E vào repository nàoCLI, session và bàn giao và mục Bố cục repository; mục Repository của module và Git repository của Backend API Reference, Repository của module của Frontend API Reference
Viết hoặc sửa code backend, chọn API đặc biệtBảng Chọn capability backend bên dưới, rồi SDK usage guide đọc trước khi code (mẫu handler, chọn field đọc record, record liên kết, đếm và tính tổng record, filter, fetch, secret/credential, state, lock, push message, background job, Custom Module Action, khởi chạy Process và AI Agent, notification, email, cơ cấu tổ chức, danh tính, quy tắc theo role người gọi, TypeScript config, router, nhận webhook), rồi SDK API reference: nền tảng handler/router/request/response, errors và các mục capability đang dùng, gồm Push message khi cần làm mới record, toast hoặc message ngầm cho web client
Cần ví dụ chạy được cho một API SDK cụ thểRepo cogover-sdk-samples: mỗi file src/samples/<area>/<name>.ts là một route minh họa một nhóm API (router, response, invocation, records, filters, identity, state, locks, fetch, schema, logging, errors, push, jobs, secrets, crypto, inbound webhook, cơ cấu tổ chức org, notification, email), src/triggers/ và src/jobs/ là trigger/job mẫu, GET / là catalog kèm cURL; README (Anh/Việt) có bảng tra API → file. Đọc file sample tương ứng trước khi code một API chưa quen; API chưa có dòng trong bảng của README thì theo usage guide và quick start, không suy đoán từ sample khác; kiểm tra phiên bản @cogover/sdk ghim trong package.json của repo khớp bản dự án (từ 0.12.0 mới có sample org, notification, email; từ 0.13.0 mọi lệnh đọc trong sample có fields và nhóm đọc record có sample fields: "*", expandLookups, records.aggregate; Custom Module Action, processes, agents cần bản ghim từ 0.15.0, deliverTo của agents.start từ 0.16.0, và dòng tương ứng trong bảng README); sample notification/email gửi tới người thật nên chỉ chạy trên Workspace thử nghiệm, cần notification channel của Workspace và mục email trong identity policy theo README của repo; không sao chép cogover.json, Object mẫu sample_*, sample ghi/xoá record hay lịch mẫu vào project nghiệp vụ
Đọc record (records.get/getMany/list): chọn fields, tên hoặc field của record liên kết, đếm/tổng hợp đơn giản bằng records.aggregate, nâng project lên SDK 0.13.0Đọc record hiệu quả (route mẫu đọc opportunity/account từ fields tối thiểu, expandLookups, tới records.aggregate có nhóm, cùng cách xử lý lookup name rỗng), rồi các mục Chọn field cần đọc, Đọc record liên kết, Đếm và tính tổng record trong SDK usage guide và Chọn field, Record liên kết, Aggregate trong SDK API reference; chọn giữa records.aggregate và saved report theo Tận dụng báo cáo
Ghi nhiều record hoặc gọi nhiều SDK call trong một lời gọi: gộp bằng records.getMany/batchInsert/batchUpdate, chạy call độc lập song song bằng Promise.all; client xử lý HTTP 429 có Retry-After khi Workspace bậnGhi record hiệu quả, mục Lời gọi song song của SDK API reference
Thiết kế route, trigger, job hoặc dashboard xử lý nhiều record hay nhiều lời gọi; gặp RateLimitError/RATE_LIMITED; đo mức đã dùngGiới hạn của một lần thực thi và Đọc giới hạn trong code trong SDK API reference, mục Quyền và giới hạn của SDK usage guide; usage/operations của Preview chính xác một version và durationMs/usage của lần chạy job trong Backend API Reference; chạy local theo CLI, session và bàn giao
Khởi tạo/code/test backend localBackend quick start
Viết record trigger before-change/after-change, chạy trigger local, publish/activate và kiểm thử bằng thay đổi bản ghi thậtRecord trigger quick start, mục Record trigger trong SDK usage guide và SDK API reference
Việc nền dài, chạy trễ, chạy theo lịch cron, retry lỗi tạm thời, enqueue từ route/trigger/webhookBackground job quick start, mục Chạy việc nền bằng job trong SDK usage guide và Background job trong SDK API reference; xem run/lịch và enqueue thủ công theo mục Background job của Backend API Reference hoặc cogover-dev jobs
Cho node Process hoặc tool AI Agent gọi logic của module (Custom Module Action): khai báo defineAction, schema s, effect, danh tính chạy, lỗi bên gọi nhận; nối action vào Process hoặc AI AgentMục Cho Process và AI Agent gọi code của bạn trong SDK usage guide, Custom Module Action trong SDK API reference (giới hạn schema, context action/invocation.source, quy tắc thực thi, bảng lỗi bên gọi nhận); danh mục action GET /api/v1/ts-projects/actions và mã lỗi theo mục Custom Module Action của Backend API Reference; node Process theo Custom Module Action Task của $process-creator, tool AI Agent theo mục CUSTOM_MODULE của $agent-builder
Khởi chạy Process (Normal Flow) hoặc lượt chạy AI Agent từ route, trigger after-change, job hoặc action và xử lý kết quả bằng job; cho AI Agent chủ động nhắn chat một nhân sự (deliverTo)Mục Khởi chạy Process và AI Agent từ code trong SDK usage guide, Process và AI Agent trong SDK API reference (job onComplete/onResult, idempotency, giới hạn, lỗi; deliverTo và kết quả delivery ở mục Gửi tin nhắn chat cho một nhân sự); mục processes/agents của identity policy theo Process và AI Agent của Backend API Reference; thiết kế Normal Flow và biến input/output bằng $process-creator (Normal Flow), agent bằng $agent-builder
Gọi API ngoài có token, lưu/xoay secret, ký bằng secretSecrets và credentials quick start, mục Dùng secret và credential trong SDK usage guide, Secret và credential và Xác thực bằng credential trong SDK API reference; quản lý theo mục Secret của Backend API Reference hoặc cogover-dev secrets
Băm, ký/kiểm tra HMAC, token ngẫu nhiên, so sánh an toàn; mã hoá/giải mã AES hoặc RSA, ký/xác minh RSA hoặc ECDSA (API ngân hàng, JWT)Crypto quick start, mục Mã hoá, giải mã và ký dữ liệu trong SDK usage guide và Mã hoá và chữ ký trong SDK API reference (định dạng key ở mục Key, bảng JWT alg ở mục crypto.sign)
Nhận webhook từ hệ thống ngoài không có phiên CogoverInbound webhook quick start, mục Nhận webhook trong SDK usage guide và Inbound webhook trong SDK API reference; tạo/rotate/revoke inbound access và cách bên gửi gọi theo mục Inbound access và Gọi inbound webhook của Backend API Reference hoặc cogover-dev inbound
Gửi notification (danh sách thông báo, web/mobile push, bản sao email theo notification channel) hoặc email từ hộp thư dùng chung/hộp thư cá nhân của actor, ghi nhận email trên timeline recordMục Gửi notification và email trong SDK usage guide, Notification và Email trong SDK API reference; grant hộp thư gửi theo mục Người gửi email của Backend API Reference; notification channel là record của Object notification_channel, resolve record ID bằng $object-record và ý nghĩa kênh theo Thông báo ở Tầng 2; danh mục thông báo (categories) là record của Object notification_category, truyền theo code (ổn định giữa các Workspace) thay vì record ID, tra code bằng $object-record
Đọc phòng ban, vị trí, nhân sự của người gọi; chuỗi quản lý, chọn người duyệt, kiểm tra quyền duyệt hoặc thuộc phòng banCơ cấu tổ chức quick start, mục Đọc phòng ban, vị trí và quản lý trong SDK usage guide và Cơ cấu tổ chức trong SDK API reference; resolve hoặc chuẩn bị dữ liệu phòng ban, nhân sự, vị trí, cấp quản lý (level) và phòng ban chính (is_primary) để thiết kế và kiểm thử bằng $user-permission (Departments, Personnels, Positions API)
Khởi tạo/build/test frontend single page appFrontend quick start
Khởi tạo/code/test custom component nhúng layoutCustom component quick start cùng README.vi.md, .agents/skills/custom-module-foundation, .agents/skills/custom-module-form-builder và src/types/form-builder.d.ts trong template đã clone
Khởi tạo/code/test Federation PageFederation Page quick start cùng README.vi.md, .agents/skills/custom-module-foundation và .agents/skills/custom-module-api trong template đã clone
Frontend gọi backendFull-stack integration
Dùng @cogover/client-sdk ở frontend: UI component, data hook (chỉ custom component và Federation Page), nhận push của backend (useSocketListener; single page app dùng createSocketListener từ @cogover/client-sdk/socket), kiểm tra phiên bản SDK của nền tảngDùng @cogover/client-sdk ở frontend
Viết và chạy E2E frontend, tạo phiên trình duyệt testKiểm thử E2E frontend, $cogover-api-auth khi người dùng không cung cấp tài khoản; persona theo Kiểm thử quyền runtime
Review code và kiểm thử end-to-end backend ghi dữ liệuReview và kiểm thử backend độc lập
Review bảo mật code backend, frontend, dependency, repository và gói publish trước khi publishReview bảo mật
Đặt ngưỡng hiệu năng; đo route, action, trigger, job, báo cáo hoặc frontend với dữ liệu đại diện và sửa theo vòngKiểm thử hiệu năng
Báo cáo, dashboard, KPI hoặc số liệu tổng hợp từ một hoặc nhiều ObjectTận dụng báo cáo cho số liệu tổng hợp (chọn records.aggregate hay saved report), $report-builder để kiểm tra khả năng và tạo saved report; $dashboard-builder khi dashboard chuẩn đáp ứng
Preview/apply, ghi hàng loạt, decimal, concurrency hoặc ghi field mà backend của App chuẩn hay hệ thống khác cũng cập nhậtBatch writes và checkpoint, gồm mục Ghi số liệu do backend khác duy trì, cùng contract state/locks/records trong SDK
Nhiều lời gọi của module chạy cùng lúc (route, webhook, trigger after-change, job) cùng sửa một record/state; cần chạy lần lượt (maxConcurrentRuns) hoặc client gửi lại sau timeout/429Mục Lời gọi đồng thời của cùng module, Lời gọi module chạy đồng thời và Cập nhật module

Hai API reference là nguồn chuẩn cho HTTP contract; quick start chỉ là cách làm với CLI và sample. Tất cả là snapshot tài liệu sản phẩm tiếng Việt ngày 2026-09-06, riêng ba tài liệu về các dạng frontend, custom component và Federation Page ngày 2026-09-15, Record trigger quick start, Background job quick start và hướng dẫn Đọc record hiệu quả ngày 2026-09-29, Backend quick start, Frontend quick start, Backend API Reference và Frontend API Reference ngày 2026-10-01 (gồm Git repository của Workspace và repository của module), Secrets/credentials quick start ngày 2026-09-22, Crypto quick start ngày 2026-09-23, hai quick start inbound webhook và cơ cấu tổ chức ngày 2026-09-24, hướng dẫn Ghi record hiệu quả cùng các mục về lời gọi đồng thời, 429/Retry-After, maxConcurrentRuns và lời gọi song song ngày 2026-10-24; SDK usage guide và SDK API reference theo @cogover/sdk 0.16.0 (snapshot ngày 2026-10-13; tài liệu mô tả ngân sách nền tảng áp cho mỗi lần thực thi, mặc định HTTP route, Custom Module Action và trigger after-change 100 capability call, 200 lời gọi cục bộ (crypto, org không có withDisplay), 10.000 record đọc, 10.000 record ghi; trigger before-change 20 capability call, 50 lời gọi cục bộ, 4.000 record đọc, không ghi; background job lần lượt 1.000, 1.000, 50.000, 50.000; vượt thì RateLimitError có details.budget; limits.usage()/ctx.limits đọc mức đã dùng; giá trị có thể khác giữa môi trường nên code đọc limit từ limits.usage() thay vì ghi cứng; từ 0.13.0 mọi lệnh đọc records.get/getMany/list bắt buộc fields (danh sách slug hoặc "*"), kiểu kết quả chỉ gồm field đã chọn, lookup trả name: "" và system.createdBy.name là "" trừ khi bật expandLookups, có records.aggregate, và typecheck cần TypeScript từ 5.0; project publish bằng SDK cũ mà đọc không có fields bị từ chối với fields is required; record trigger và records.getMany cần SDK từ 0.6.0, push message từ 0.7.0, background job, secret/credential, crypto hash/HMAC/random và inbound webhook từ 0.8.0, mã hoá AES/RSA và chữ ký RSA/ECDSA (aesEncrypt, aesDecrypt, rsaEncrypt, rsaDecrypt, sign, verify) từ 0.9.0, cờ Super Admin và danh sách role của người gọi (invocation.user.membership.isSuperAdmin, roles) từ 0.10.0, cơ cấu tổ chức (org) từ 0.11.0, notification và email (notifications.send, email.send, email.senders) từ 0.12.0, fields bắt buộc, expandLookups và records.aggregate từ 0.13.0, limits (limits.usage(), ctx.limits) từ 0.14.0, Custom Module Action (defineAction, schema builder s, named export actions), processes và agents từ 0.15.0, deliverTo của agents.start và kết quả delivery (kiểu AgentDeliveryTarget, AgentDelivery) từ 0.16.0; chạy trigger local cần Dev CLI từ 0.10.0, các lệnh secrets, inbound, jobs cần Dev CLI từ 0.13.1, truyền isSuperAdmin/roles vào script local và đọc org trên local cần Dev CLI từ 0.14.0, thử notifications.send/email.send trên local cần Dev CLI từ 0.15.0, bản xếp hai thao tác này là ghi nên session --allow-writes=false từ chối; chạy local áp cùng ngân sách theo loại lần thực thi cần Dev CLI từ 0.19.0 và SDK từ 0.14.0 (--no-enforce-limits chỉ đếm, không từ chối), jobs runs/jobs run hiện durationMs và usage của lần thử gần nhất từ Dev CLI 0.19.0, xếp processes.start/agents.start là thao tác ghi (session --allow-writes=false từ chối), áp trần 20 processes.start và 5 agents.start mỗi lần gọi handler và từ chối onComplete/onResult trên local với NOT_SUPPORTED từ Dev CLI 0.20.0, lệnh git để lưu source trên Git server của Workspace từ Dev CLI 0.21.0), SDK cài đặt khác phiên bản thì đối chiếu thêm tài liệu public đi kèm package. Với custom component và Federation Page, các skill trong .agents/skills của template đã clone là nguồn chuẩn cho quy ước code (expose, routing, Link, asset, API client, i18n); skill này quyết định workflow, phê duyệt, kiểm thử và publish. Template thay đổi thì theo bản đã clone, không theo trích dẫn trong quick start. ID, field slug, policy và dữ liệu mẫu chỉ minh họa, không thay dữ liệu thật của Workspace. Không suy ra API từ tên gọi hoặc dùng API không được hỗ trợ.

Chọn capability backend

Chọn theo nhu cầu thực tế của contract ở bước 1 mục 5; mỗi capability có giới hạn riêng trong SDK API reference, ngoài ra mọi route, trigger, job và action chạy trong ngân sách chung của một lần thực thi (dòng cuối bảng). Mọi capability ngoài route đều là một phần của version backend (áp dụng khi activate) trừ secret/credential và inbound access được cấu hình trên Project và dùng ngay cho version active; grant hộp thư gửi email và mục processes/agents nằm trong identity policy nên phải duyệt cho từng version.

Nhu cầuDùngLưu ý, không dùng khi
Trả kết quả ngay trong HTTP response cho người dùng hoặc frontendHTTP route (createRouter) hoặc defineScript một endpointViệc quá vài giây, có thể vượt ngân sách của route (dòng cuối bảng), phải sống sót qua lỗi tạm thời hoặc restart: route chỉ kiểm tra input rồi jobs.enqueue và trả 202 với runId
Chỉ Super Admin hoặc người giữ role nhất định được thực hiện thao tác nghiệp vụ (duyệt, mở khoá, điều chỉnh)invocation.user.membership.isSuperAdmin và invocation.user.membership.roles ({ id, name }, sắp theo tên); SDK từ 0.10.0Chỉ là quy tắc nghiệp vụ trong code: không thay security rules, quyền record/field vẫn theo danh tính người gọi và không có danh sách quyền của role; so theo role id (tên đổi được, ID khác nhau giữa Workspace); user là null với danh tính system/inbound; record trigger và background job có thể nhận thay đổi role chậm khoảng một phút
Quy tắc bắt buộc trước khi record được lưu, áp cho mọi nguồn ghiRecord trigger before-changeChỉ đọc, fail-closed, ≤ 2 giây; không ghi record, fetch, state, lock, push, notification, email (kể cả email.senders()), jobs.enqueue, secrets.get, processes, agents hay thao tác crypto bằng key { secret }; đọc org được
Xử lý tiếp ngay sau khi record được lưuRecord trigger after-changeBest-effort, có thể chạy lặp; việc dài hơn timeoutMs hoặc cần state/locks thì enqueue background job
Việc nền dài chia trang, gọi hệ thống ngoài có retry, chạy trễ (delayMs/runAt tối đa 30 ngày), chạy theo lịch cronBackground job (defineJob, named export jobs, jobs.enqueue, schedule cron năm trường với múi giờ IANA)Workspace phải bật job; mỗi lần thử ≤ 60 giây, ít nhất một lần nên handler idempotent; run theo lịch và run từ webhook chạy danh tính system (policy allowInternalSystem), run do quản trị viên enqueue qua CLI/management API chạy dưới danh tính Super Admin đã enqueue; không tự làm luồng có người tham gia (chờ phê duyệt bằng User Task) hay AI Agent: khởi chạy Process/AI Agent và nhận kết quả bằng job (dòng Khởi chạy Process hoặc AI Agent)
Node Process hoặc tool AI Agent cần gọi logic của module (tính, kiểm tra, đọc/ghi dữ liệu theo quy tắc riêng) và nhận ngay kết quả có cấu trúc cho bước sauCustom Module Action: defineAction (key, label, description, exposeTo "process"/"agent", effect "read"/"write", input/output bằng schema builder s, timeoutMs 1.000–8.000, mặc định 8.000) liệt kê trong named export actions (tối đa 50); SDK từ 0.15.0Đồng bộ, tối đa 8 giây, ngân sách của HTTP route: việc dài hơn thì jobs.enqueue với action.runId làm idempotency key rồi trả ngay; effect: "read" từ chối mọi thao tác ghi (jobs.enqueue, processes.start cũng vậy), "write" làm tool AI Agent mặc định cần phê duyệt; schema chỉ tạo bằng s (không pattern, lồng tối đa 3 cấp, 50 property mỗi object, input + output ≤ 16 KiB) và cùng label/mô tả hiển thị với mọi thành viên Workspace nên không chứa bí mật; bên gọi chỉ nhận mã lỗi và thông báo cố định, không nhận message của handler, nên kết quả dự kiến (không tìm thấy, không đủ điều kiện) trả trong output; danh tính do node/tool chọn (người khởi tạo Process, nhân sự chỉ định, danh tính của agent, hoặc không có người dùng cần allowInternalSystem); không dùng thay route cho HTTP client/frontend, không thay trigger cho quy tắc áp mọi nguồn ghi; không gọi module bằng node Send HTTP Request hay HTTP tool
Khởi chạy Process (phê duyệt, luồng có User Task người dùng tự cấu hình) hoặc lượt chạy AI Agent (phân loại, tóm tắt, soạn thảo, chủ động nhắn chat một nhân sự) từ code và xử lý kết quảprocesses.start(processInfoId, { input, instanceName, idempotencyKey, onComplete }), processes.get; agents.start(agentId, { instruction, variables, records, resultSchema, runAs, approvalPolicy, timeoutSeconds, idempotencyKey, onResult, deliverTo }), agents.get; SDK từ 0.15.0, deliverTo từ 0.16.0Chạy nền: start trả ngay instanceId/runId, kết quả giao cho job của version active qua onComplete/onResult, không chờ trong route; identity policy phải có mục processes/agents (thiếu thì PermissionDeniedError PROCESSES_NOT_ALLOWED/AGENTS_NOT_ALLOWED), execution không có người dùng cần allowSystem và agent runAs: "agent", approvalPolicy: "autoApprove" cần allowAutoApprove và execution có người dùng; Process phải là Normal Flow đã kích hoạt và xuất bản; tối đa 20 processes.start và 5 agents.start mỗi lần thực thi, 1.000 Process mỗi ngày UTC mỗi project, maxRunsPerDay của agent (mặc định 200); deliverTo: { personnelId, conversation, title } cho agent gửi tin vào AI chat của agent với nhân sự đó (người nhận phải được dùng agent và có seat AI Agent, không cần mục policy riêng), kết quả gửi ở delivery, tách khỏi status của lượt chạy; không có trong before-change, action "read" và session chỉ đọc; Dev CLI tạo lượt chạy thật, gửi tin deliverTo thật, nhưng từ chối onComplete/onResult (NOT_SUPPORTED)
Gọi API ngoài cần token trong HTTP headerfetch(url, { credential }) với credential loại BEARER/BASIC/HEADER và host được phépCode không thấy giá trị; request tới host ngoài danh sách, tự đặt cùng header hoặc tên không active bị FETCH_BLOCKED; không có trong before-change
Cần chính giá trị bí mật: ký, đặt vào body hay định dạng khácsecrets.get (secret OPAQUE) hoặc truyền { secret: name } làm key của crypto (HMAC, AES, RSA, sign/verify) để ký, mã hoá, giải mã mà không đọc giá trị≤ 20 thao tác secret mỗi invocation, giá trị cache trong invocation; không ghi vào log/record/state/response; Development Session mặc định bị từ chối (SECRETS_NOT_ALLOWED); không có trong before-change
Băm nội dung, ký/kiểm tra HMAC, token và nonce ngẫu nhiên, so sánh chữ kýcrypto.sha256, hmacSha256, randomBytes, randomUUID, timingSafeEqualChỉ SHA-256; hash không che được giá trị dễ đoán và không dùng lưu mật khẩu, cần giữ bí mật thì mã hoá (dòng dưới); so sánh chữ ký/token luôn bằng timingSafeEqual; ký/kiểm tra trên đúng chuỗi byte gửi đi (request.rawBody); dùng được cả trong before-change trừ key { secret }
Mã hoá/giải mã dữ liệu trao đổi với hệ thống ngoài; ký hoặc xác minh chữ ký khoá công khai (API ngân hàng, JWT RS*/PS*/ES*)crypto.aesEncrypt/aesDecrypt (AES-GCM mặc định, CBC), rsaEncrypt/rsaDecrypt (OAEP-SHA256 mặc định, OAEP-SHA1, PKCS1), sign/verify (RSA PKCS #1 v1.5, RSA-PSS, ECDSA) với key { secret: name }; SDK từ 0.9.0AES key và private key luôn là secret OPAQUE, không viết trong source; mode/padding/IV/tag/định dạng chữ ký/encoding phải khớp tài liệu bên kia; RSA chỉ mã hoá giá trị nhỏ (190 byte với key 2048 bit, OAEP-SHA256), dữ liệu lớn mã hoá bằng AES rồi chỉ mã hoá AES key bằng RSA; giải mã thất bại ném DECRYPTION_FAILED, verify trả false thay vì ném; key từ secret không có trong before-change
Hệ thống ngoài gọi vào không có phiên người dùng CogoverInbound webhook: route dưới /hooks/ + inbound access KEY hoặc HMAC do quản trị viên tạo (bước 6A)Cogover xác thực trước handler (401 khi sai); handler kiểm tra invocation.identity === "inbound", giữ ngắn, chống trùng theo event ID rồi enqueue job; record dùng danh tính system (policy); Workspace session không thay được inbound credential
Làm mới record đang mở, toast, message ngầm cho custom componentPush message (push.refreshRecords, push.toast, push.message)Best-effort, không thu hồi; không có trong before-change và Development Session chỉ đọc; chỉ tới người đang mở web client, không lưu lại: việc người nhận cần xử lý sau thì dùng notification
Báo cho người dùng một việc cần xử lý sau (yêu cầu duyệt, nhắc hạn, kết quả xử lý), lưu trong danh sách thông báo và gửi qua web push, mobile push, bản sao email theo kênhnotifications.send (to 1–200 personnel ID, exclude: ["actor"], channel là record ID của notification_channel, categories là tối đa 10 code hoặc record ID của notification_category (SDK từ 0.17.0), link, sender, email: false, idempotencyKey); SDK từ 0.12.0Không cần grant trong identity policy; best-effort, không thu hồi; tối đa 5.000 người nhận mỗi giờ mỗi project; personnel chưa có tài khoản bị bỏ qua (skippedPersonnelIds); bản sao email gửi từ địa chỉ thông báo của Cogover và tính vào hạn mức email thông báo hằng ngày theo gói; không có trong before-change, preview và Development Session chỉ đọc
Gửi email thật từ hộp thư của Workspace tới khách hàng hoặc nhân sự, ghi nhận trên timeline record, đính kèm file của recordemail.send với from: { mailbox: id } (hộp thư dùng chung) hoặc from: "actor" (hộp thư cá nhân của người khởi đầu execution), record, recordEmailFields, attachments, idempotencyKey; email.senders() chọn hộp thư theo địa chỉ; SDK từ 0.12.0Hộp thư phải được grant trong mục email của identity policy đã duyệt cho đúng version (workspaceMailboxIds, allowActorMailbox), nếu không ném EMAIL_SENDER_NOT_GRANTED; execution không có người dùng cần allowInternalSystem: true và không dùng được "actor"; tối đa 50 người nhận, 10 file/20 MB, 500 email mỗi giờ mỗi project; lời gọi xong khi Cogover tiếp nhận, lỗi gửi sau đó không báo lại; không có trong before-change, email.send bị từ chối trong preview và Development Session chỉ đọc
Biết người gọi thuộc phòng ban/vị trí nào, liệt kê thành viên, tìm quản lý hoặc chuỗi người duyệt, kiểm tra ai quản lý ai hoặc ai thuộc phòng banorg (me, personnel.get/getMany/managerChain, departments.get/tree/ancestors/members/managers/managerChain, positions.list/get/members, isManagerOf, isInDepartment); SDK từ 0.11.0Chỉ đọc, dùng được cả trong before-change; cấu trúc lấy từ bản cơ cấu đã cache, thay đổi có hiệu lực sau vài giây; withDisplay: true mới có tên, email, avatar (thêm lượt đọc, trang tối đa 200; đọc có withDisplay là capability call, không có là lời gọi cục bộ); field khác của nhân sự đọc qua data.object("personnel"); job, inbound webhook và trigger do hệ thống ghi cần allowInternalSystem: true (IDENTITY_NOT_GRANTED); level của membership là cấp quản lý (0 nhân viên, 1 cao nhất), không phải độ sâu cây
Đọc record để xử lý nghiệp vụrecords.get/getMany/list với fields là danh sách slug tối thiểu code dùng; "*" chỉ khi thực sự cần mọi field được đọc; expandLookups (true hoặc { lookup: [...] | "*" }) chỉ khi cần tên hoặc field của record đích; SDK từ 0.13.0Thiếu fields ném ValidationError trước khi gửi; truy cập field không yêu cầu lỗi compile; "*" là mọi field danh tính đang dùng được đọc (với data.asUser()/data.asSystem() chỉ các field identity policy cho phép; data.object() không cần policy), dễ vượt giới hạn 1 MiB mỗi lời gọi với getMany/list; mỗi record trả về, kể cả record liên kết của expandLookups, tính vào ngân sách record đọc, list giữ chỗ bằng limit trước khi gửi; lookup không mở rộng trả name: "" (field reference vẫn có tên), system.createdBy.name là ""; mở rộng tốn thêm lượt đọc Object đích, một cấp, tối đa 1.000 record liên kết mỗi lời gọi, kiểm quyền theo danh tính gọi; khi mở rộng, createdBy.name là field name của record nhân sự (có thể là mã sinh tự động), tên hiển thị lấy bằng org.personnel.get với withDisplay: true; record.new/record.old của trigger đã có tên lookup
Số liệu đơn giản trên một Object theo điều kiện của lời gọi (đếm, tổng, trung bình, min/max, theo nhóm) để trả về hoặc quyết định trong backendrecords.aggregate (where, metrics 1–20, groupBy 1–3 field, limit tối đa 5.000 nhóm); SDK từ 0.13.0Một capability call, không đọc record (không tính ngân sách record đọc), dưới danh tính của lời gọi. groupBy chỉ nhận field choice (single_choice, multi_choices, radio_button, checkbox), boolean, lookup_normal, date/date_time và số, không nhận text hay reference; count/countDistinct nhận field số, ngày, lựa chọn, boolean, lookup_normal, text, email, phone, auto_number, không nhận reference, file, url, formula, rollup_summary; không record khớp thì count/sum là 0, avg/min/max là null. data.asUser() và data.object() khi đọc dưới danh tính người dùng khác người gọi HTTP (trigger cho thay đổi do người dùng, job có người dùng, Development Session) không group được và chỉ có count trên "id", count/sum/avg/min/max trên field số: nhóm hoặc metric khác ở đó dùng data.asSystem() (cần grant trong identity policy); dữ liệu trễ khoảng 1 giây, countDistinct xấp xỉ: không dùng cho quy tắc cần chính xác tại thời điểm ghi; nhiều Object, nhóm theo kỳ, formula hoặc báo cáo người dùng tự cấu hình thì dùng saved report (dòng dưới); gọi được trong before-change nhưng làm chậm thao tác ghi
Số liệu tổng hợp (đếm, tổng, trung bình, min/max, trung vị theo nhóm hoặc kỳ) từ một hoặc nhiều Object cho báo cáo, dashboard, KPISaved report xây bằng $report-builder với ACL theo đối tượng dùng module (bước 2B); frontend gọi /api/v1/report service 200 (setting: true) bằng phiên người dùng cuối, backend chỉ xử lý phần nghiệp vụ khácKhông quét record khi báo cáo tính được. Backend chưa gọi được báo cáo bằng phiên người dùng cuối (script không nhận cookie/authorization, SDK chưa có API chạy saved report): khi backend cần số liệu để ra quyết định thì dùng records.aggregate nếu số liệu thuộc phạm vi dòng trên, còn lại chọn phương án theo Giới hạn khi backend cần số liệu báo cáo; không nhận số liệu client gửi làm căn cứ quyết định cần bảo vệ
Lưu tiến độ, checkpoint nhỏ; chặn chạy đồng thờiProject state (CAS expectedVersion) và distributed locksKhông transaction, không exactly-once; không lưu secret vào state; không có trong trigger context
Route, trigger, job, action hoặc dashboard xử lý nhiều record hay nhiều lời gọi; biết lần thực thi còn bao nhiêu ngân sáchThiết kế theo ngân sách của một lần thực thi (mặc định route, action và after-change 100 capability call, 200 lời gọi cục bộ, 10.000 record đọc, 10.000 record ghi; before-change 20, 50, 4.000, không ghi; job 1.000, 1.000, 50.000, 50.000) và đọc mức đã dùng bằng limits.usage()/ctx.limits (SDK từ 0.14.0, không tốn lời gọi)Ngân sách thuộc lần thực thi: chờ hay chạy lại không giúp. Số tổng dùng records.aggregate hoặc saved report, không quét record; việc lớn chia trang trong background job và enqueue trang kế khi remaining sắp hết; capability call chạy lần lượt kể cả với Promise.all, nên nhiều lời gọi vẫn phải lọt 8 giây của route; không ghi cứng con số, đọc limit từ usage()

Workflow

Điều phối sub-agent

Agent chính giữ yêu cầu, quyết định kiến trúc, schema đã duyệt, contract tích hợp và nghiệm thu cuối; tự làm bước 4–6 và 9–10 (trừ E2E trình duyệt do sub-agent E2E chạy, review và kiểm thử backend do sub-agent Review backend chạy, review bảo mật do sub-agent Review bảo mật chạy, đo hiệu năng do sub-agent Hiệu năng chạy), tổng hợp các yêu cầu duyệt còn thiếu. Phân công theo nhánh thực sự cần, mỗi nhánh một sub-agent riêng:

Sub-agentPhạm vi
Khảo sátBước 1 mục 2–3 và bảng sự kiện → thay đổi của bước 1 mục 6, chỉ đọc cấu hình và metadata Workspace. Spike ghi dữ liệu test của mục 6 giao sub-agent riêng, khác sub-agent Backend, sau khi người dùng đồng ý phạm vi ghi
Thiết kế dữ liệu/ExcelBước 2 mục 1–4 khi cần bổ sung schema; khác sub-agent khảo sát
SecurityBước 2A cho từng Object mới, dùng $user-permission
ProcessBước 3, dùng $process-creator, gồm node Custom Module Action gọi action của module và Normal Flow do module khởi chạy bằng processes.start; phần phụ thuộc backend hoàn tất sau bước 9
AI AgentBước 3 khi action mở cho agent hoặc module khởi chạy agent: dùng $agent-builder tạo/tái sử dụng agent, skill, tool CUSTOM_MODULE và phân quyền, kiểm thử bằng chat; phần phụ thuộc backend hoàn tất sau bước 9
Báo cáoBước 2B khi contract có chỉ số lấy từ saved report: đọc $report-builder, xây hoặc tái sử dụng Report Type/saved report, đặt ACL theo đối tượng dùng module, chạy service 200 bằng phiên persona xác minh số liệu và quyền, bàn giao payload và mẫu response đã redact
BackendBước 7–8 phần backend: khởi tạo local, code HTTP handler, record trigger, background job, Custom Module Action, khởi chạy Process/AI Agent, inbound webhook, notification/email, logic theo cơ cấu tổ chức, unit tests/coverage, kiểm thử tích hợp
FrontendBước 7–8 phần frontend theo dạng đã xác nhận ở 1A: khởi tạo local, code, unit tests/coverage, tự kiểm tra trên browser khi phát triển. Custom component/Federation Page: clone template và đọc .agents/skills trước khi code
E2E frontendBắt buộc khi có frontend: viết E2E test tự động từ contract và tiêu chí nghiệm thu, chạy trên trình duyệt ở bước 8 (các ca chạy được trên local) và bước 9 (Workspace); tạo phiên test bằng $cogover-api-auth khi người dùng không cung cấp tài khoản; không sửa source ứng dụng. Theo Kiểm thử E2E frontend
Review backendBắt buộc khi backend có record trigger, background job, Custom Module Action effect: "write" hoặc route ghi dữ liệu: bước 8 review code tại commit sẽ publish, bước 9 kiểm thử end-to-end trên version đã activate bằng script đặt ở e2e/ của repository backend; không sửa source ứng dụng, không commit, không publish/activate, không sửa tay số liệu, không xoá record. Theo Review và kiểm thử backend độc lập
Review bảo mậtBắt buộc với mọi project có code: bước 8 review code backend, frontend, dependency, repository và gói publish tại commit sẽ publish, review phần chênh lệch của mỗi commit publish sau đó; phát hiện Nghiêm trọng/Cao chặn publish; không sửa source, không commit, không thử khai thác trên Workspace. Theo Review bảo mật
Hiệu năngBắt buộc khi route, action, trigger hoặc job xử lý nhiều record, có trigger before-change, báo cáo/dashboard hoặc frontend tải nhiều dữ liệu: bước 9 đo trên version đã activate với dữ liệu đại diện, so với ngưỡng trong contract, tối đa 2 vòng sửa; không sửa source, không commit, không load test. Theo Kiểm thử hiệu năng
  • Backend và frontend không giao chung một sub-agent. Sub-agent E2E tách khỏi sub-agent Frontend/Backend: không sửa source ứng dụng; sub-agent Frontend không sửa, nới hay skip test E2E để test pass; agent chính không tự nhận vai E2E. Sub-agent Review backend cũng khác sub-agent Backend; sub-agent Backend không sửa ca kiểm thử hay kỳ vọng để kết quả PASS. Sub-agent E2E và Review backend chỉ ghi vào e2e/ của repository được giao và không commit; agent chính xem lại rồi commit e2e/; sub-agent Frontend/Backend không sửa và không stage e2e/. Sub-agent Review bảo mật và Hiệu năng cũng khác sub-agent Backend/Frontend, chỉ báo lỗi kèm bằng chứng; sub-agent phụ trách sửa, số vòng sửa theo reference của từng vai. Tái sử dụng sub-agent phụ trách xuyên suốt bước 7–9 và các vòng sửa lỗi; không tạo sub-agent cho nhánh đã bỏ qua.
  • Gói giao việc: yêu cầu và tiêu chí nghiệm thu; loại module và dạng frontend đã được người dùng xác nhận ở 1A; đường dẫn skill/reference cần đọc; Workspace origin; metadata đã có; phạm vi đọc/ghi và file được sửa; đầu ra bàn giao; trạng thái phê duyệt và dependency còn chờ. Nhánh triển khai thêm metadata Object/field đã xác minh, project ID/slug/slot, contract route/input/output/error và fixture được sửa. Không đòi kết quả khảo sát hoặc project chưa tạo làm đầu vào của chính bước khảo sát. Chỉ truyền cách truy cập credential an toàn hoặc tên profile.
  • Thứ tự: khảo sát → agent chính kiểm tra bằng chứng, chốt phạm vi → gửi đề xuất loại module/dạng frontend (1A), song song thiết kế Excel nếu cần và schema không phụ thuộc lựa chọn (không bắt đầu workbook khi còn thiếu metadata ảnh hưởng schema) → người dùng xác nhận 1A và duyệt workbook → agent chính ghi và đọc lại schema. Có Object mới: tiếp → sub-agent Security hoàn tất 2A → agent chính nghiệm thu. Có chỉ số lấy từ báo cáo: sub-agent Báo cáo hoàn tất 2B sau khi schema liên quan đã ghi (song song 2A khi báo cáo không phụ thuộc rule mới) → agent chính nghiệm thu → khởi tạo/code. Trong lúc chờ chỉ khảo sát, thiết kế contract, ma trận quyền và kế hoạch test; không dựng source, scaffold, interface code hay test bằng fixture để đi trước.
  • Sau 2A và 2B (nếu có), chốt contract chung rồi cho các nhánh độc lập chạy song song trong thư mục riêng; sub-agent E2E viết test song song với code frontend. Frontend có thể dùng fixture theo contract khi chờ backend nhưng chưa thay test tích hợp thật. Điều phối lịch test ghi khi dùng chung dữ liệu để các agent không reset hoặc sửa fixture của nhau.
  • Chỉ giao quyền thao tác Workspace đã được phép; delegation không thay thế duyệt Excel, duyệt Process hoặc duyệt cấu hình AI Agent.
  • Nhận lại source, lệnh build/test, coverage riêng từng project, kết quả kiểm thử và dependency còn thiếu; kiểm tra bằng chứng, giao lại lỗi cho sub-agent phụ trách, nghiệm thu tích hợp. Thông báo “xong” không phải bằng chứng PASS.

1. Khảo sát và chọn frontend/backend

  1. Đọc $cogover-overview rồi các reference tính năng của miền nghiệp vụ liên quan. Danh mục App trong overview không phải danh sách App đã cài.
  2. Giao sub-agent khảo sát mục 2–3; truyền yêu cầu nghiệp vụ, phạm vi khảo sát và kết quả đọc overview. Sub-agent dùng $app-menu-manager cùng $cogover-api-auth liệt kê App thật trên Workspace (GET /api/v1/apps, phân trang page/limit theo contract), đọc detail/menu của App liên quan; ghi ID, slug, standard/custom, trạng thái, phạm vi quyền, chức năng có sẵn. App bị ẩn/thiếu quyền hoặc list chưa đủ trang không chứng minh App chưa cài: phân biệt chưa có, inactive và chưa xác minh được.
  3. Cùng sub-agent dùng $object-info đọc Objects, fields, options, metadata, related lists liên quan; có automation cũ thì khảo sát Process theo $process-creator trước khi đề xuất thêm. Yêu cầu có số liệu tổng hợp: chỉ số đơn giản trên một Object mà backend tính theo điều kiện của lời gọi thì kết luận records.aggregate; các chỉ số còn lại đọc $report-builder, liệt kê Report Type và saved report liên quan (service 215, 220, 229, chỉ đọc) và đánh giá khả năng báo cáo cho từng chỉ số theo mục 2 của Tận dụng báo cáo. Kết quả khảo sát: App/Menu và chức năng có sẵn; mapping Object/field/option/quan hệ đã xác minh; Process liên quan; báo cáo có sẵn và khả năng báo cáo cho từng chỉ số; phần thiếu dự kiến; nguồn bằng chứng API/metadata, phạm vi phân trang và điểm chưa xác minh kèm lý do; không chứa credential.
  4. Agent chính kiểm tra báo cáo, giao lại phần thiếu hoặc mâu thuẫn trước khi chốt quyết định phụ thuộc vào phần đó. Lập bảng: yêu cầu → khả năng chuẩn và bằng chứng trên Workspace → phần thiếu → nơi xử lý (cấu hình chuẩn/báo cáo/dashboard/frontend/backend/Process). Số liệu tổng hợp: ưu tiên saved report (có sẵn hoặc tạo mới); chỉ chọn quét record khi báo cáo không biểu diễn được, ghi lý do cụ thể; chỉ cần xem bảng/biểu đồ chuẩn thì dashboard qua $dashboard-builder là chức năng chuẩn. Chọn loại module theo phần thiếu thực tế: chức năng chuẩn đáp ứng trọn vẹn thì giải thích và dùng skill chuyên trách, không tạo project rỗng cho đủ bước; người dùng yêu cầu rõ trải nghiệm riêng thì vẫn thiết kế phần custom cần thiết. Có frontend: chọn dạng single page app, custom component hoặc Federation Page theo bảng chọn nhanh trong Các loại Custom Module và dạng frontend, dựa trên nơi hiển thị, mức tương tác với form, yêu cầu đồng nhất giao diện và stack sẵn có; hỏi phần chưa rõ thay vì suy đoán.
  5. Chốt contract nghiệp vụ dự kiến: routes/methods, input/output, caller (thao tác chỉ dành cho Super Admin hoặc role nhất định: tên role, role ID đã xác minh trên Workspace qua $user-permission và phản hồi khi bị từ chối), dữ liệu thay đổi và tiêu chí nghiệm thu. Record trigger (bước 3): từng trigger gồm Object, timing, operations, fields, changedFields/when/runWhen, writableFields, mã lỗi và message; ghi rõ quy tắc bắt buộc đặt ở before-change và việc chỉ xử lý tiếp đặt ở after-change. Trước khi chọn Object đặt trigger, liệt kê mọi đường làm đổi giá trị được kiểm soát, mỗi đường có trigger hoặc lý do bỏ qua: ghi trực tiếp record; tạo/sửa/xoá record con làm đổi rollup hoặc formula của record cha (tính lại rollup/formula là thao tác ghi bảo trì, không chạy trigger, nên quy tắc dựa trên tổng của record cha cần trigger trên Object con với đủ create, update, delete; ví dụ hạn mức trên tổng tiền đơn hàng rollup từ dòng hàng cần before-change trên dòng hàng); chuyển trạng thái do người dùng, Process hoặc backend của App chuẩn ghi. Ưu tiên từ chối ở bước người dùng thao tác (ví dụ gửi duyệt) thay vì ở bước Process ghi: tài liệu không nêu Process xử lý thế nào khi before-change từ chối thao tác ghi của nó (chưa kiểm chứng; dự kiến node đó lỗi, không tự rẽ sang nhánh từ chối), nên vẫn chặn ở bước Process thì thiết kế nhánh xử lý và kiểm thử cách lỗi hiển thị ở bước 9. Background job (bước 3): từng job gồm key, payload dự kiến, timeoutMs, maxAttempts, schedule (cron năm trường, múi giờ IANA) hoặc nơi enqueue (route/trigger/webhook/job khác), quy tắc idempotency key và điều kiện dừng khi tự enqueue. Custom Module Action (bước 3): từng action gồm key ổn định, label, description (bắt buộc khi mở cho agent: viết cho AI Agent đọc, nêu khi nào dùng, input và kết quả), exposeTo, effect, input/output (property, kiểu s.*, optional, describe, giới hạn), timeoutMs, Process/node hoặc agent/tool sẽ dùng, danh tính chạy dự kiến (Process: người khởi tạo, nhân sự chỉ định hay không có người dùng; agent: danh tính của agent và có cần kiểm tra theo người đang chat initiatorPersonnelId), kết quả dự kiến trả trong output thay vì ném lỗi và việc dài chuyển sang job. Khởi chạy Process/AI Agent từ code: Process Normal Flow (Process ID PI... đã xác minh, biến input/output) hoặc agent ID, instruction/resultSchema, runAs, approvalPolicy, job nhận kết quả, quy tắc idempotencyKey và mục processes/agents cần thêm vào identity policy. Tích hợp ngoài: tên và loại secret/credential (OPAQUE, BEARER, BASIC, HEADER), host được phép, nơi dùng trong code; giá trị do người dùng/quản trị viên nhập ở bước 6A. Mã hoá hoặc chữ ký với hệ thống ngoài: thao tác và bên giữ từng key (ai giữ private key, AES key dùng chung do ai cấp), thuật toán theo tài liệu bên kia (AES mode, cách truyền IV và tag nối hay tách, aad; RSA padding và digest MGF1; thuật toán chữ ký, der hay ieee-p1363), encoding (base64, base64url, hex), chuỗi byte được ký, tên secret chứa key và mẫu ciphertext/chữ ký của bên kia để kiểm chứng tương thích. Inbound webhook: route dưới /hooks/, bên gửi, chế độ KEY hay HMAC (header chữ ký, prefix, timestamp header, dung sai, encoding theo tài liệu bên gửi), định danh sự kiện để chống trùng và job xử lý tiếp. Notification: sự kiện gửi, người nhận (personnel ID lấy từ org, field record hoặc danh sách cố định; có loại người gây ra execution không), notification channel (record ID của notification_channel đã xác minh trên Workspace hoặc không dùng kênh), tiêu đề, nội dung, link tới record, người gửi hiển thị (workspace/actor), có gửi bản sao email không và quy tắc idempotencyKey. Email: hộp thư gửi (ID hộp thư dùng chung do quản trị viên cung cấp hoặc hộp thư cá nhân của actor) cùng grant cần thêm vào mục email của identity policy, người nhận (địa chỉ, personnel, field email của record), record ghi nhận timeline, file field đính kèm, chữ ký và quy tắc idempotencyKey. Cơ cấu tổ chức: câu hỏi cần trả lời (quản lý gần nhất hay cả chuỗi, thuộc phòng ban hay cả phòng con), phòng ban dùng làm gốc (phòng ban của record hay phòng ban chính của nhân sự), có bỏ nhân sự chưa có tài khoản (accountOnly), cách xử lý chuỗi rỗng, vacantDepartmentIds, truncated, và phòng ban/nhân sự test đã xác minh qua $user-permission. Số liệu tổng hợp: từng chỉ số → records.aggregate (Object, where, groupBy, metric, danh tính gọi), saved report có sẵn hoặc sẽ tạo (primary object, relation, group, aggregate, filter nền, filter theo phạm vi người gọi) hoặc quét record kèm lý do; đối tượng được xem báo cáo (role, phòng ban, vị trí) để đặt ACL; nơi gọi báo cáo: frontend bằng phiên người dùng cuối, hoặc phương án đã chọn khi backend cần số liệu để ra quyết định. Ngân sách: route, trigger hoặc job đọc/ghi nhiều record ước lượng capability call, lời gọi cục bộ, record đọc/ghi với khối lượng dữ liệu lớn nhất dự kiến so với ngân sách của loại lần thực thi; vượt hoặc sát ngân sách thì đổi thiết kế (records.aggregate, saved report, job chia trang tự enqueue) trước khi code, không trông vào retry. Hiệu năng (khi thuộc điều kiện của sub-agent Hiệu năng): khối lượng dữ liệu lớn nhất dự kiến và ngưỡng cho từng route/action/trigger/job/báo cáo/trang theo mục Ngưỡng; người dùng chưa nêu thì dùng ngưỡng đề xuất và xin xác nhận cùng 1A. Custom component: Object, layout, slug item Related List/field sẽ thao tác và expose key dự kiến. Federation Page: App/slug, danh sách route và mục menu sẽ gắn. Frontend: tiêu chí nghiệm thu dùng làm ca E2E, quy ước selector (role/label hoặc data-testid), persona và nguồn phiên test (tài khoản người dùng cung cấp hay phiên tạo từ Workspace API key qua $cogover-api-auth).
  6. Module ghi hoặc chặn ghi trên field mà backend của App chuẩn hay hệ thống khác cũng cập nhật (số tổng, số dư, bộ đếm, trạng thái): khảo sát và đo hành vi của backend đó trước khi chốt contract ở mục 5.
    • Sub-agent khảo sát (chỉ đọc) lập bảng sự kiện → thay đổi cho từng field: sự kiện nào làm đổi, bên nào ghi và dưới danh tính nào, ghi delta theo giá trị đang lưu hay tính lại tuyệt đối, có kẹp về 0 không, field là rollup/formula hay giá trị lưu sẵn.
    • Đọc transition rule và field bị khoá theo trạng thái của các Object liên quan; kiểm tra history tracking có bật không (không bật thì chỉ có giá trị hiện tại); đối chiếu bất biến số học trên dữ liệu hiện có; ghi rõ điểm nào là suy luận.
    • Điểm chưa xác minh mà thiết kế phụ thuộc (backend có ghi đè giá trị module đã sửa không, giải phóng theo giá trị hiện tại hay tính lại, gán hay cộng dồn) đo bằng spike sau khi người dùng đồng ý phạm vi ghi: dữ liệu test có marker, snapshot record tổng hợp trước, mô phỏng bằng chính lệnh ghi module sẽ dùng, đọc lại ở vài mốc thời gian sau mỗi sự kiện, log máy đọc được, dọn và báo phần dư theo bước 9 mục 12.
    • Chỉ chốt công thức, thời điểm module chạy và cách ghi khi bảng đã có bằng chứng; thiết kế ghi theo Ghi số liệu do backend khác duy trì.
    • Tiêu chí nghiệm thu không giả định đường chuyển trạng thái hoặc field mà transition rule hay khoá field không cho phép; ca không đi được ghi BLOCKED kèm mã lỗi và cách kiểm chứng gián tiếp.

1A. Xác nhận loại module và dạng frontend với người dùng

Bắt buộc trước khi phát triển, kể cả khi yêu cầu ban đầu đã nói "tạo module" hoặc đã nêu sẵn một dạng.

  1. Agent chính gửi đề xuất gồm: loại module (frontend, backend hoặc cả hai); với frontend là dạng single page app, custom component hoặc Federation Page; lý do theo bảng chọn nhanh và bằng chứng khảo sát; dạng thay thế gần nhất và lý do không chọn; tác động của lựa chọn (template bắt buộc hay stack tự chọn, nơi hiển thị, cấu hình cần thêm trên layout hoặc menu, cách kiểm thử gồm E2E bằng sub-agent độc lập và nguồn phiên test, review bảo mật, đo hiệu năng cùng ngưỡng đề xuất khi thuộc điều kiện, giới hạn); nguồn số liệu tổng hợp (records.aggregate trong backend, saved report có sẵn, báo cáo sẽ tạo hoặc quét record kèm lý do), đối tượng được xem báo cáo và nơi gọi báo cáo (frontend bằng phiên người dùng cuối; backend cần số liệu để ra quyết định thì nêu giới hạn và phương án) khi có; câu hỏi còn mở. Người dùng đã chỉ định dạng: vẫn tóm tắt lựa chọn, nêu khác biệt một lần nếu phân tích cho thấy dạng khác phù hợp hơn, rồi theo quyết định của người dùng.
  2. Chờ người dùng xác nhận rõ ràng trong hội thoại. Yêu cầu chung ban đầu, im lặng, đồng ý của sub-agent hoặc việc đã duyệt workbook không thay thế xác nhận này. Người dùng chọn phương án khác thì lập lại kế hoạch theo phương án đó trước khi tiếp tục.
  3. Trước khi có xác nhận chỉ làm: khảo sát bổ sung, contract, ma trận quyền, kế hoạch test và thiết kế workbook ở bước 2 mục 1–5 khi schema không phụ thuộc lựa chọn; có thể gửi yêu cầu xác nhận 1A cùng yêu cầu duyệt Excel nhưng mỗi mục phải được trả lời riêng. Không tạo Project, không clone template hoặc scaffold, không ghi schema, không tạo báo cáo, policy/key/Process, không viết code.
  4. Sau xác nhận: ghi loại module, dạng frontend, lý do và thời điểm xác nhận vào gói giao việc cho sub-agent và báo cáo bàn giao. Đổi dạng giữa chừng, ví dụ custom component sang Federation Page, phải xin xác nhận lại theo cùng cách kể cả khi đã có code.

2. Thiết kế dữ liệu và duyệt Excel

Agent chính xác định có cần bổ sung schema. Nếu cần, giao sub-agent thiết kế dữ liệu/Excel (vai chuyên gia thiết kế cơ sở dữ liệu) làm mục 1–4 với báo cáo khảo sát, mapping metadata, yêu cầu dữ liệu/quyền/truy vấn và phạm vi delta dự kiến; agent chính làm mục 5–7. Schema đã đủ thì ghi kết luận, bỏ qua workbook và tạo Object.

  1. Xác định thực thể, nguồn dữ liệu chuẩn, quan hệ một-nhiều/nhiều-nhiều, vòng đời, khóa nghiệp vụ, yêu cầu chống trùng, field bắt buộc, kiểu số/ngày/tiền, quyền và truy vấn chính. Tái sử dụng Object nền tảng và Object nghiệp vụ đã có; không tạo bản sao chỉ vì khác tên hiển thị.
  2. Phân loại: tái sử dụng nguyên trạng, bổ sung field/option/quan hệ hoặc tạo Object mới; chỉ thêm phần thiếu, ghi rõ lý do và tác động với dữ liệu/cấu hình đang dùng. Mỗi Object mới có ma trận Create/View/Edit/Delete: người dùng nào được thao tác trực tiếp, phạm vi record/field và action nào chỉ backend được thực hiện. Object do backend quản lý là bảng dữ liệu cho logic ứng dụng, có thể chặn toàn bộ hoặc một phần CRUD trực tiếp; không nhầm với Object nền tảng có sẵn.
  3. Schema đã đủ: sub-agent báo lại kết luận và lý do, không tạo workbook rỗng. Cần bổ sung: dùng $create-cogover-objects tạo workbook .xlsx theo hướng dẫn spreadsheet và validator của skill đó, sửa mọi lỗi trước khi bàn giao; phân biệt Object mới với delta trên Object hiện có, chỉ rõ lookup tới Object đã tồn tại để không tạo trùng. Bàn giao file .xlsx tại đường dẫn agent chính truy cập được, tóm tắt delta/lý do/tác động, ma trận quyền, mapping lookup, kết quả kiểm tra và các điểm còn cần quyết định; không tạo hoặc sửa schema trên Workspace.
  4. Mỗi Object có đúng một record-name slug name, type Short text hoặc Auto number; tiền dạng số dùng Decimal. Giữ slug/ID tham chiếu thật, không tạo lại field hệ thống.
  5. Agent chính mở workbook, đối chiếu với yêu cầu, metadata đã khảo sát và bằng chứng kiểm tra; giao lại sub-agent thiết kế sửa nếu thiếu hoặc sai. Bắt buộc đưa file Excel đã rà soát cùng tóm tắt delta và ma trận quyền để người dùng duyệt trước khi ghi schema; yêu cầu chung “tạo module” không thay thế duyệt này. Trong lúc chờ chỉ hoàn thiện thiết kế và kế hoạch test, chưa tạo Object/field hoặc viết code.
  6. Sau khi workbook được duyệt: dùng $object-info đọc lại state mới nhất, tạo Object/field theo dependency và đúng bản đã duyệt; quan hệ vòng thì tạo Object trước rồi thêm lookup sau khi có ID. Field chỉ backend ghi (bộ đếm, snapshot, trạng thái do backend cập nhật) tạo với creatable: 0 và manual_modify_allow: false theo mục Cờ tạo và sửa thủ công của $object-info; backend ghi bằng data.asSystem() hoặc trigger before-change gán qua writableFields (thiết lập nhập thủ công và quyền sửa field của bên ghi không áp dụng cho field do trigger sửa; field formula, auto number, rollup và field hệ thống vẫn không sửa được), không dùng security rule exclude thay cho hai cờ này. Delta thay đổi do Workspace đã đổi thì chỉ xin duyệt lại phần thay đổi thực chất.
  7. Đọc lại toàn bộ schema đã ghi, so với workbook; ghi mapping ID/slug/options dùng cho code và Process. Thao tác dở dang: liệt kê phần đã tạo, tra lại trước retry, không tự xóa để làm lại.

2A. Tạo security rules cho từng Object mới trước khi code

Bắt buộc khi bước 2 tạo Object mới; giao một sub-agent Security riêng. Chỉ tái sử dụng Object hoặc bổ sung field thì không tự tạo lại bộ rules hay đổi quyền ngoài phạm vi yêu cầu; đánh giá quyền hiện có và xử lý delta được yêu cầu bằng $user-permission.

  1. Agent chính giao mapping Object/field đã đọc lại, ma trận quyền đã chốt, phạm vi thao tác được phép và cách truy cập credential an toàn. Sub-agent đọc bản hiện tại của $user-permission (mục “Object do backend quản lý và rule giữ chỗ”), Rule giữ chỗ không chọn nhân sự và $cogover-api-auth; đọc rules/Role liên quan trước khi ghi để tránh trùng khi retry và phát hiện quyền đã được cấp. Không tự mở rộng Role để làm test thành công.
  2. Mỗi Object mới có bộ mặc định bốn rules riêng: một Create, một View, một Edit, một Delete, kể cả action không cho người dùng thực hiện trực tiếp; không gộp action, chỉ bổ sung rule cùng action khi audience, điều kiện record hoặc field scope khác nhau. Create dùng type: 1 với duy nhất scope create và vẫn gửi filter rỗng theo $user-permission (server bắt buộc); ba action còn lại dùng type: 2, chỉ cấp scope của action chính, các scope khác none/no. Action chỉ backend được thực hiện: rule giữ chỗ active (status: 1) với đúng personnelFilters: [{"type": 1, "op": "include", "personnelId": null}] và scope hợp lệ của action. Audience này không khớp nhân sự nào: rule giữ slot action và duy trì mặc định từ chối nhưng không ghi đè quyền do active rule khác cấp, nên phải rà mọi active rule để chắc không rule nào cấp lại action đang muốn chặn. Không bỏ rule, thay null bằng ID giả hoặc tắt rule giữ chỗ để biểu diễn chặn.
  3. Sub-agent tạo rules trong phạm vi được phép; đọc lại danh sách không lọc trạng thái và detail của từng rule. Bàn giao mapping Object ID/slug → rule ID/action/type/status/audience/filter/scopes, đối chiếu ma trận quyền, kết quả kiểm thử trực tiếp theo $user-permission và phần chưa kiểm thử. Request dở dang: đọc lại trước retry, không tạo bốn rule mới chồng lên bộ đã có.
  4. Agent chính chờ sub-agent hoàn tất và nghiệm thu trước khi chuyển sang bước 7–8: mọi Object mới đủ bốn slot action active, rule giữ chỗ có personnelId là JSON null, phạm vi quyền khớp thiết kế, không có grant ngoài ý muốn từ rule khác và có bằng chứng đọc lại. Thiếu rule, cấu hình sai, thao tác lỗi hoặc sub-agent chưa hoàn tất thì chưa được code; không có công cụ sub-agent thì báo rõ dependency và dừng trước bước code. Kiểm tra cấu hình không thay thế kiểm thử runtime: thiếu credential/fixture thì ghi rõ giới hạn và theo dõi ca test còn thiếu. Ca backend dùng quyền hệ thống chạy sau khi backend sẵn sàng ở bước 8–9; không yêu cầu backend tồn tại để hoàn tất bước cấu hình này.

2B. Tạo hoặc tái sử dụng báo cáo cho số liệu tổng hợp

Bắt buộc khi contract ở bước 1 mục 5 có chỉ số lấy từ saved report và người dùng đã xác nhận 1A; giao một sub-agent Báo cáo riêng dùng $report-builder. Không có chỉ số tổng hợp thì bỏ qua.

  1. Chạy sau khi schema liên quan đã ghi và đọc lại ở bước 2, vì report field tham chiếu field thật. Tái sử dụng saved report khi cùng Report Type, field, group, aggregate và filter; không sửa saved report đang dùng chung theo cách làm đổi kết quả của người khác, cần cấu hình khác thì tạo mới.
  2. Sub-agent làm theo workflow của $report-builder (report contract, graph, Report Type, report field, saved report, chạy service 200 và kiểm tra với ví dụ nghiệm thu); slug ổn định, mô tả nêu module sử dụng. Phân quyền theo Xây báo cáo và phân quyền: ACL của Report Type và saved report cho người dùng module xem/chạy theo role, phòng ban hoặc vị trí, chỉ người phụ trách được sửa/xoá, không cấp cho tất cả khi đối tượng hẹp hơn; ACL chưa rõ thì hỏi người dùng trước khi ghi.
  3. Chạy service 200 (setting: true) bằng phiên của persona trong ACL có phạm vi record hẹp và persona ngoài ACL: người trong ACL nhận đúng số liệu trong phạm vi quyền của họ, người ngoài ACL bị từ chối; kết quả khác thiết kế thì báo agent chính trước khi module dựa vào báo cáo. Đo thời gian chạy với dữ liệu đại diện; lưu payload đã chạy thành công và mẫu response đã redact làm fixture cho parser của module.
  4. Người dùng module phải có quyền View Object/field nguồn; thiếu thì phối hợp sub-agent Security xử lý đúng delta được duyệt, không mở rộng quyền Object chỉ để báo cáo có số liệu.
  5. Agent chính nghiệm thu: Report Type ID, saved report ID/slug, mapping object field → report field ID, ACL đã đặt, payload, mẫu response, kết quả theo persona, thời gian chạy và hai link theo $report-builder. Báo cáo không đáp ứng như đánh giá ở bước 1 thì chốt lại nguồn số liệu với người dùng trước khi chuyển sang quét record.

3. Chọn record trigger, background job, Custom Module Action hoặc Process khi cần chạy theo sự kiện, việc nền, lịch hoặc nối với Process/AI Agent

  • Cần chạy theo sự kiện create/update/delete của record và quy tắc phải áp cho mọi nguồn ghi (giao diện web, API, import, Process, module khác): từ chối hoặc điều chỉnh giá trị trước khi lưu, hoặc xử lý tiếp ngay sau khi lưu (ghi record liên quan, gọi hệ thống ngoài) thì dùng record trigger của Custom Backend Module (@cogover/sdk từ 0.6.0): before-change cho quy tắc bắt buộc, after-change cho việc tiếp theo best-effort. Trigger là một phần của version backend, được áp dụng khi activate và không cần Process; thiết kế ở bước 1 mục 5, code/test ở bước 8, kiểm thử trên Workspace ở bước 9 theo Record trigger quick start. After-change gửi được notification/email cho người liên quan, ví dụ quản lý tìm bằng org. Không đủ cho việc dài hơn timeoutMs, cần retry hay chạy theo lịch (background job bên dưới) và cho luồng nhiều bước có người tham gia, chờ phê duyệt bằng User Task, gọi AI Agent (Process).
  • Cần việc nền của backend: xử lý dài chia trang, gọi hệ thống ngoài có retry lỗi tạm thời, chạy trễ (delayMs/runAt tối đa 30 ngày) hoặc chạy theo lịch cron (schedule với múi giờ IANA, mốc bị lỡ khi Project không active không chạy bù) thì dùng background job (@cogover/sdk từ 0.8.0, Workspace đã bật job; JOBS_DISABLED thì báo dependency): khai báo defineJob, liệt kê trong named export jobs, enqueue từ route, trigger after-change, inbound webhook, job khác, CLI hoặc management API. Job là một phần của version backend: lịch có hiệu lực khi activate, activate version khác thay lịch, deactivate gỡ lịch. Run theo lịch và run enqueue từ webhook chạy danh tính system (policy allowInternalSystem: true ở bước 5); run enqueue từ route/trigger giữ danh tính người gọi/người sửa record; run do quản trị viên enqueue qua CLI hoặc management API chạy dưới danh tính Super Admin đứng sau Workspace API key hoặc phiên đã gọi (enqueuedBy: "management", actorPersonnelId là người đó). Job gửi được notification/email, ví dụ nhắc hạn hằng ngày. Không dùng job cho việc phải trả kết quả trong cùng HTTP response, cho luồng cần người tham gia (User Task)/AI Agent, hoặc để thay Process mà người dùng muốn tự cấu hình không cần code. Thiết kế ở bước 1 mục 5, code/test ở bước 8, kiểm thử trên Workspace ở bước 9 theo Background job quick start.
  • Process hoặc AI Agent cần gọi logic của module (tính toán, kiểm tra điều kiện, đọc/ghi dữ liệu theo quy tắc riêng) và dùng ngay kết quả có cấu trúc ở bước sau thì khai báo Custom Module Action (@cogover/sdk từ 0.15.0): defineAction liệt kê trong named export actions, là một phần của version backend và được Process Builder/AI Agent Builder liệt kê khi version activate. Process dùng node Custom Module Action theo Custom Module Action Task (sub-agent Process); AI Agent dùng tool category CUSTOM_MODULE theo mục CUSTOM_MODULE của $agent-builder (sub-agent AI Agent). Một action có thể mở cho cả hai (exposeTo). Action đồng bộ, tối đa 8 giây: việc dài hơn enqueue job. Process không có người khởi tạo (Scheduled, Triggered record/webhook) phải chọn danh tính PERSONNEL hoặc SYSTEM cho node (SYSTEM cần allowInternalSystem, bước 5); Triggered webhook không lấy nhân sự chạy từ dữ liệu webhook. Action do AI Agent gọi chạy dưới danh tính của agent, có thể khác người đang chat. Không nối Process/AI Agent tới module bằng node Send HTTP Request hay HTTP tool.
  • Module cần khởi chạy Process (luồng có User Task, phê duyệt mà người dùng muốn tự cấu hình) hoặc AI Agent (phân loại, tóm tắt, soạn nội dung) từ route, trigger after-change, job hoặc action thì dùng processes.start/agents.start (@cogover/sdk từ 0.15.0): trả về ngay, kết quả giao cho background job khai báo trong onComplete/onResult. Process phải là Normal Flow đã kích hoạt và xuất bản, nhận dữ liệu qua biến được đánh dấu input và trả qua biến được đánh dấu output (thiết kế bằng sub-agent Process); agent cấu hình bằng sub-agent AI Agent. Identity policy cần mục processes/agents (bước 5). Cần AI Agent chủ động nhắn chat cho một nhân sự (nhắc việc, báo điều agent phát hiện, người nhận trả lời tiếp với agent) thì thêm deliverTo vào agents.start (@cogover/sdk từ 0.16.0): agent tự soạn tin và tự quyết định có gửi hay không, tin tới AI chat của agent với người đó; nội dung cố định do code soạn sẵn thì dùng notifications.send, email.send hoặc push.toast.
  • Các trường hợp còn lại cần Process: luồng nhiều bước có người tham gia, chờ phê duyệt, gọi AI Agent, hoặc automation/lịch/thông báo mà người dùng muốn tự cấu hình trên giao diện. Thông báo hoặc email gắn với logic của backend (người nhận tính từ cơ cấu tổ chức hay dữ liệu module, nội dung từ kết quả xử lý) gửi bằng notifications.send/email.send trong backend; không gửi cùng một thông báo bằng cả backend và node Send Notification/Send Email của Process. Cần Process thì giao sub-agent Process riêng dùng $process-creator xác định event, conditions, timezone/lịch, chống lặp, output cần kiểm chứng và thiết kế Triggered Flow hoặc Scheduled Flow phù hợp. Custom Module xử lý trọn vẹn yêu cầu trong request hiện tại thì bỏ qua cả ba. Không dùng đồng thời record trigger và Triggered Flow cho cùng một quy tắc trên cùng Object, không dùng đồng thời job theo lịch và Scheduled Flow cho cùng một việc; ghi nơi xử lý vào bảng ở bước 1 mục 4.
  • Backend đăng ký trigger qua named export triggers, action qua named export actions và lịch qua schedule của job trong named export jobs; không có cơ chế lịch nào khác. Không dùng timer trình duyệt hoặc tiến trình local thay cho lịch chạy trên Workspace; máy chủ local không chạy job hay cron.
  • Sub-agent gửi flow cụ thể để agent chính tổng hợp bước xác nhận của $process-creator, tái sử dụng xác nhận đã có cho đúng flow. Đủ phê duyệt thì sub-agent tạo, đọc lại, kích hoạt và kiểm thử theo skill đó, trả Process ID/link, instance và bằng chứng hiệu ứng nghiệp vụ.
  • Process hoặc AI Agent phải gọi backend chưa publish: thiết kế ở bước này, tạo node/tool và kích hoạt sau khi version có action đã activate ở bước 9 (Process Builder và AI Agent Builder chỉ thấy action của version active; Process có node tham chiếu action chưa có được lưu nhưng không hợp lệ với mã 71 và không kích hoạt được, tool bị từ chối với r: 4047). Không kích hoạt flow có URL/ID giả hoặc dependency chưa sẵn sàng.
  • Process → backend chỉ qua node Custom Module Action (danh tính theo runAs của node); không dùng Project key (không gọi được production) hay session export ngắn hạn (không phải credential bền vững) trong node Send HTTP Request. Hệ thống ngoài cần gọi backend không có phiên người dùng thì dùng inbound webhook (bước 6A), không nhúng session export. Chưa có cơ chế được hỗ trợ thì báo chính xác dependency còn thiếu và hoàn thành các phần độc lập.

4. Tạo Project trên Workspace

  1. Đọc API reference đúng nhánh. Qua phiên Workspace với service 4, list đủ các trang cần thiết để kiểm tra trùng slug, rồi create hoặc tái sử dụng đúng project đã được chỉ định. CLI publish/activate không tạo Project.
  2. Backend: POST /api/v1/ts-projects. Frontend: POST /api/v1/ts-projects/frontend. Body gồm name, slug, description khi cần; không gửi createGitRepository: server mặc định tạo kèm repository trên Git server của Workspace (field git; tạo lỗi thì Project vẫn được tạo, có git.error, git setup ở bước 7 tạo bù). Slug bất biến theo [A-Za-z_][A-Za-z0-9_]{0,99}, không áp regex slug Process; ID do server cấp.
  3. Đọc lại project vừa tạo. Ghi backend projectId/slug; frontend projectId/slug/slugSlot. Full-stack có hai project với cấu hình riêng. Ba dạng frontend dùng cùng loại Project và cùng slugSlot: single page app mở /{slugSlot}/index.html, custom component dùng {slugSlot}/Components/<Tên> trên layout, Federation Page dùng đoạn c{N} với N lấy từ _cm_{N}. Một project theo template có thể chứa đồng thời custom component và Federation Page; single page app là project riêng.

5. Cấu hình Project policy nếu cần

  • Frontend thuần: bỏ qua, frontend không có identity-policy endpoint trong contract này. Backend dùng quyền caller mặc định, không gọi data.asSystem()/data.asUser(), không gửi email, không khởi chạy Process/AI Agent và không có execution thiếu người dùng cần đọc dữ liệu hay org (kể cả node Process chạy action với danh tính SYSTEM): không tạo policy vô cớ. Notification không cần grant. Các API đặc biệt khác kiểm tra theo SDK contract; identity policy không mở được mọi capability.
  • Action bị chặn trực tiếp trên Object do backend quản lý (bước 2A): backend dùng data.asSystem() với policy giới hạn đúng caller/Object/operation cần thiết. Code backend dùng quyền caller không tự vượt security rules; trước khi dùng quyền hệ thống phải kiểm tra người gọi có quyền thực hiện nghiệp vụ, policy không thay thế kiểm tra này.
  • Dùng quyền ủy quyền: đọc mục Identity policy trong Backend API Reference. Thiết kế schema version 2, giới hạn caller personnel, Object và operation cần thiết; data.asUser giới hạn thêm personnel đích. Resolve ID/slug thật; khi sửa giữ grant ngoài phạm vi.
  • Gửi email (email.send): thêm mục email theo mục Người gửi email của Backend API Reference. workspaceMailboxIds chỉ gồm hộp thư dùng chung contract cần (tối đa 50, ID do quản trị viên cung cấp, không đoán), allowActorMailbox: true chỉ khi gửi from: "actor"; mục phải cấp ít nhất một người gửi và policy có thể chỉ gồm email. Grant chỉ áp dụng cho caller trong callerPersonnelIds; job theo lịch, inbound webhook và trigger do hệ thống ghi cần thêm allowInternalSystem: true và không dùng được "actor". Lưu policy không kiểm tra hộp thư tồn tại: hộp thư sai hoặc bị vô hiệu hoá chỉ lộ ra khi gửi (NotFoundError với resource là "mailbox"), nên sau khi duyệt xác minh bằng email.senders() trước khi gửi thật. Khi sửa mục email giữ nguyên grant data.asSystem/data.asUser ngoài phạm vi và ngược lại.
  • Khởi chạy Process/AI Agent (processes.start, agents.start): thêm mục processes và/hoặc agents theo mục Process và AI Agent của Backend API Reference. processes.processes là selector (ALL, ALL_EXCEPT, ONLY) trên processInfoIds là Process ID (PI...), không phải ID version hay lượt chạy; agents.agents trên agentIds; ưu tiên ONLY với ID đã xác minh (tối đa 200 ID). allowSystem: true chỉ khi execution không có người dùng (job theo lịch, inbound webhook, node SYSTEM) cần khởi chạy; khi đó agent phải runAs: "agent". agents.maxRunsPerDay (1–10.000, mặc định 200) là trần agents.start mỗi ngày UTC của project; agents.allowAutoApprove (mặc định false) chỉ bật khi người dùng đồng ý cho agent chạy tool cần phê duyệt mà không hỏi, và không áp cho execution không có người dùng. Hai mục không phụ thuộc callerPersonnelIds; execution có người dùng vẫn bị Cogover kiểm tra quyền khởi chạy Process/chạy agent của chính người đó (PROCESS_START_DENIED, AGENT_RUN_DENIED). deliverTo của agents.start không cần mục policy riêng và dùng được cả khi execution không có người dùng: Cogover kiểm tra người nhận là nhân sự của Workspace, được dùng agent và có seat AI Agent. processes.get/agents.get cũng cần mục tương ứng và chỉ đọc lượt chạy do project khởi tạo. Đổi một trong hai mục tạo revision mới, phải duyệt lại.
  • Custom Module Action: node Process chọn SYSTEM (lời gọi không có người dùng) chỉ chạy khi identity policy đã duyệt của version active đặt allowInternalSystem: true, nếu không bị từ chối SYSTEM_IDENTITY_NOT_ALLOWED trước khi chạy; allowInternalSystem áp cho cả Project (cả job theo lịch, inbound webhook), nên chỉ bật khi thực sự cần. Action chạy dưới danh tính người dùng dùng quyền của người đó (data.object()); version active chưa có policy được duyệt thì action chạy như route: lời gọi có người dùng không có grant nào, lời gọi không có người dùng bị từ chối. Action do AI Agent gọi chạy dưới danh tính của agent (nhân sự run-as của agent hoặc người chat với agent nội bộ): khi action chỉ được làm điều người dùng thật được phép, kiểm tra invocation.source.initiatorPersonnelId trong handler (ví dụ data.asUser(initiatorPersonnelId) với grant data.asUser giới hạn đúng Object/operation), từ chối khi thiếu; runAs: "PERSONNEL" của Process có thể lấy từ dữ liệu người dùng nhập, không phải bằng chứng ai đã yêu cầu.
  • Policy hợp lệ phải có ít nhất một trong data.asSystem, data.asUser, email, fetch, processes và agents; policy chỉ đặt allowInternalSystem mà không có grant nào là không hợp lệ. Execution không có người dùng chỉ đọc org hoặc chỉ gửi notification vẫn cần policy hợp lệ có allowInternalSystem: true: dùng grant module thực sự cần, không thêm grant rộng chỉ để policy hợp lệ; không có grant nào cần thiết thì nêu với người dùng để chọn grant hẹp nhất hoặc chuyển việc sang execution có người dùng.
  • Lưu editable policy → đọc lại → activate policy cho nhu cầu phát triển. Editable policy khác snapshot bất biến đã duyệt cho từng version; mỗi lần sửa đưa editable policy về DRAFT.
  • Sau publish có version READY: approve policy cho đúng version rồi đọc snapshot, kiểm tra revision/hash/grants theo reference. Activate version không tự duyệt policy; policy đã approve trước đây không mặc nhiên áp dụng cho version mới.
  • Record trigger: data.object() trong handler chạy dưới danh tính người tạo ra thay đổi với quyền của người đó, nên record người này không thấy sẽ nằm trong missingIds của getMany; cấp quyền View cho Object tham chiếu hoặc dùng data.asSystem() (before-change vẫn chỉ đọc) với policy đúng phạm vi. Thay đổi không có người dùng (invocation.identity là "system") chỉ gọi được data.object() khi identity policy đã duyệt đặt allowInternalSystem: true; nếu không, handler vẫn nhận records nhưng lời gọi dữ liệu và org ném PermissionDeniedError với reason IDENTITY_NOT_GRANTED. Xác định nguồn ghi thực tế của Object trước khi quyết định policy.
  • Background job và inbound webhook: run theo lịch, lời gọi inbound webhook (invocation.identity là "inbound", user là null) và job được enqueue từ lời gọi đó đều thực hiện data.object() và đọc org dưới danh tính system, nên cần policy đã duyệt với allowInternalSystem: true và grant giới hạn vào Object/thao tác thật sự dùng (gửi email thêm grant hộp thư); duyệt lại cho từng version mới trước khi activate, chỉ activate version chưa cấp quyền record. Run enqueue từ route hoặc trigger giữ danh tính người gọi/người sửa record; run do quản trị viên enqueue qua CLI/management API chạy như lời gọi module của chính Super Admin đó (data.object() theo quyền của họ, không cần allowInternalSystem), nên run thử bằng cogover-dev jobs enqueue không chứng minh policy cho run theo lịch hoặc từ webhook; data.asUser()/data.asSystem() vẫn theo policy như thường.
  • Policy không cấp quyền chạy module nói chung và không thay thế quyền của user đích; permission ceiling của Project key chỉ thu hẹp quyền. Không thêm grant rộng để che lỗi 403.

6. Tạo Project key nếu backend local cần kết nối Workspace

  1. Frontend bỏ qua. Backend xử lý thuần túy có thể test bằng fixture không cần key; backend local cần gọi SDK capability thật hoặc chạy record trigger với bản ghi đã lưu thì cần key; thử handler after-change có ghi, jobs.enqueue, push, notifications.send, email.send, processes.start hoặc agents.start trên local cần key cho phép writes; chỉ đọc org (và email.senders()) thì key chỉ đọc là đủ, khi đó chạy máy chủ local với --allow-writes=false (bắt buộc với key chỉ đọc). Development Session mặc định không đọc secret, không dùng credential và không dùng key { secret } cho crypto (SECRETS_NOT_ALLOWED, FETCH_BLOCKED) trừ khi quản trị viên cho phép; máy chủ local không chạy job, lịch hay xác thực inbound, nên các phần này kiểm chứng trên version đã publish ở bước 9.
  2. Đọc mục Project key; resolve caller personnel, hạn dùng và permissionCeiling schema version 1 theo hành vi test. Cho phép writes/asSystem/asUser chỉ khi cần trong phạm vi bài toán.
  3. Tạo key bằng POST /api/v1/ts-projects/{projectId}/keys, hoặc tái sử dụng key còn hiệu lực đúng project/caller và quyền. Không rotate key đang được người khác dùng chỉ để lấy lại secret.
  4. projectKey chỉ trả một lần lúc create/rotate: chuyển trực tiếp vào credential store hoặc prompt ẩn của CLI qua kênh an toàn, không in raw response; lưu metadata key ID/caller/hạn dùng để kiểm chứng, không lưu secret vào tài liệu.
  5. cogover-dev login --profile <PROFILE> rồi doctor theo reference CLI. Không dùng Workspace API key thay Project key, hoặc ngược lại.

6A. Tạo secret, credential và inbound access nếu backend tích hợp hệ thống ngoài

Chỉ khi contract ở bước 1 mục 5 có tích hợp ngoài; các nhóm dưới đây là thao tác quản lý dùng Workspace API key/session với service 4 và quyền Workspace SuperAdmin (Project key không đủ; 403 hoặc SECRETS_DISABLED thì báo dependency, không tự nới quyền). Cách dùng trong code và giới hạn: Secrets và credentials quick start, Inbound webhook quick start và các mục Secret, Inbound access của Backend API Reference.

  1. Secret/credential: tạo theo đúng tên đã chốt ([A-Za-z][A-Za-z0-9_]{0,63}, cùng tên dùng được giữa các môi trường) bằng cogover-dev secrets set <name> --kind opaque|bearer|basic|header [--header-name <Header>] [--allowed-host <host>]... (giá trị nhập qua prompt ẩn, --value-stdin hoặc --value-file; không truyền trên dòng lệnh, không in) hoặc POST /api/v1/ts-projects/{projectId}/secrets. Credential (BEARER/BASIC/HEADER) bắt buộc danh sách host được phép không rỗng (host chính xác hoặc *.suffix); OPAQUE không có host và là loại duy nhất code đọc được. Secret thuộc Project, dùng ngay cho version active không cần publish; xoay bằng secrets set cùng tên (giữ loại/host, tăng valueVersion), không xoá rồi tạo lại. Agent chuẩn bị lệnh và xác minh bằng cogover-dev secrets list hoặc secrets/list (chỉ metadata); người dùng/quản trị viên nhập giá trị thật. Dữ liệu thử nghiệm của quick start (httpbin.org, token demo) chỉ dùng trên Workspace test. Key cho crypto là secret OPAQUE: AES key 16/24/32 byte lưu dạng văn bản, base64 hoặc hex (code khai báo encoding tương ứng); RSA/EC key lưu văn bản PEM nhiều dòng (giá trị secret tối đa 8 KiB) qua --value-file rồi xoá file tạm, private key ở dạng PKCS #8 (hoặc PKCS #1, SEC1) chưa mã hoá, không nhận ENCRYPTED PRIVATE KEY hay key OpenSSH; RSA 2048–4096 bit, EC P-256/P-384/P-521; public key hoặc certificate của đối tác cũng nên lưu thành secret để xoay không cần publish. Cặp key do phía Cogover giữ được tạo ngoài Cogover bởi người dùng/quản trị viên; chỉ public key được gửi cho bên kia.
  2. Inbound access: tạo sau khi version có route /hooks/... đã activate ở bước 9, một access cho mỗi bên gửi, --route-prefix hẹp nhất có thể, --expires-at <ISO-8601> cho quyền tạm thời: cogover-dev inbound create <name> --mode key|hmac --route-prefix /hooks/<route> [--json] hoặc POST /api/v1/ts-projects/{projectId}/inbound. Chế độ KEY: inboundKey chỉ trả về một lần khi create/rotate, chuyển cho bên gửi qua kênh an toàn, không in vào chat/log/báo cáo; rotate cấp key mới và vô hiệu key cũ ngay. Chế độ HMAC: secret dùng chung qua --hmac-secret-stdin, cấu hình --hmac-header, --hmac-prefix, --hmac-timestamp-header, --hmac-tolerance, --hmac-encoding khớp cách ký của bên gửi (không timestamp thì chỉ ký raw body; có thì ký <timestamp>.<raw body>); đổi secret qua update, không rotate. Thu hồi bằng revoke là vĩnh viễn và không đổi được chế độ. Ghi metadata (inbound ID, name, mode, route prefix, URL gốc kết thúc bằng /hooks/{inboundId}, hạn dùng) để kiểm thử ở bước 9 và bàn giao; inbound list chỉ trả metadata.
  3. Credential chạy báo cáo từ backend: chỉ khi người dùng đã chọn phương án 3 của Giới hạn khi backend cần số liệu báo cáo (số liệu ở danh tính hệ thống): loại BEARER, --allowed-host chỉ là hostname Workspace, giá trị là Workspace API key của tài khoản tích hợp quyền tối thiểu do quản trị viên nhập qua prompt ẩn hoặc stdin; xoay bằng secrets set cùng tên.

7. Khởi tạo local để build/test

  • Có Object mới: chỉ giao khởi tạo sau khi agent chính đã nghiệm thu 2A và cung cấp mapping rules/quyền cho sub-agent backend/frontend.
  • Backend (sub-agent backend, tiếp tục code/test ở bước 8): theo Backend quick start với starter public được liên kết; cogover.json có Workspace origin, Project ID/slug thật và projectType: "backend". src/main.ts là entrypoint: default export HTTP handler, named export triggers khi có record trigger, named export jobs khi có background job và named export actions khi có Custom Module Action (default export là tuỳ chọn với Project chỉ khai báo trigger/job/action, khi đó mọi URI gọi module trả 404); local/ chỉ là local runner (HTTP runner, trigger-runner.ts và test chạy bằng npm test), không import từ src/ và không upload. Project có trigger: npm ls @cogover/sdk phải từ 0.6.0 và cogover-dev --version từ 0.10.0; Project có job, secret/credential, crypto hoặc inbound webhook: SDK từ 0.8.0 và CLI từ 0.13.1; dùng aesEncrypt/aesDecrypt, rsaEncrypt/rsaDecrypt hoặc sign/verify: SDK từ 0.9.0; đọc membership.isSuperAdmin/roles: SDK từ 0.10.0 và CLI từ 0.14.0 khi chạy local (CLI cũ hơn không truyền hai giá trị này nên script local thấy isSuperAdmin: false, roles: []); dùng org: SDK từ 0.11.0 (bản cũ hơn build báo org không tồn tại) và CLI từ 0.14.0; dùng notifications/email: SDK từ 0.12.0 và CLI từ 0.15.0; SDK từ 0.13.0 (bắt buộc fields khi đọc record, có expandLookups và records.aggregate) cần TypeScript từ 5.0 (npx tsc --version); nâng project cũ lên 0.13.0 thì sinh lại workspace.d.ts bằng generator của bản mới, thêm fields vào mọi lệnh đọc và sửa code dựa vào tên lookup hoặc createdBy.name; dùng limits/ctx.limits: SDK từ 0.14.0; chạy local bị áp ngân sách của lần thực thi cần CLI từ 0.19.0 với SDK từ 0.14.0 (project SDK cũ hơn không bị áp ở local nhưng vẫn bị áp trên Cogover); dùng defineAction/s, processes hoặc agents: SDK từ 0.15.0 (bản cũ hơn build báo không có export) và CLI từ 0.20.0 khi chạy processes.start/agents.start trên local (bản này xếp hai lời gọi là thao tác ghi và áp trần 20/5 mỗi lần gọi như trên Cogover); dùng deliverTo của agents.start hoặc đọc delivery: SDK từ 0.16.0 (bản cũ hơn không nhận tuỳ chọn này: typecheck báo lỗi, start ném ValidationError).
  • Frontend single page app (sub-agent frontend, tiếp tục code/test ở bước 8): theo Frontend quick start với Vite TypeScript hoặc stack phù hợp yêu cầu; base: "./", build ra dist/index.html, hash routing nếu có SPA. cogover.json phải có projectType: "frontend" (bỏ qua sẽ mặc định backend).
  • Frontend custom component hoặc Federation Page (sub-agent frontend, tiếp tục code/test ở bước 8): trước khi code, clone https://github.com/cogover/custom-frontend-module-template vào thư mục riêng của project, chạy npm ci, đọc README.vi.md và toàn bộ .agents/skills/*/SKILL.md trong bản đã clone (custom-module-foundation bắt buộc; custom-module-form-builder cùng src/types/form-builder.d.ts cho custom component; custom-module-api khi gọi API; custom-module-i18n khi có đa ngôn ngữ) và báo lại agent chính các quy tắc ảnh hưởng contract. Không tự dựng project React khác, không sao chép rời rạc vài file của template. Giữ nguyên './CustomApp': './src/App.tsx' trong exposes, base: './', route tương đối trong src/routes.tsx, Link nội bộ, asset trong src/assets; không hardcode _cm_N; không đưa key/secret vào biến VITE_*. .env.local chỉ chứa VITE_WORKSPACE_NAME (và VITE_ENVIRONMENT khi suffix domain khác mặc định của vite.config.ts), không commit. cogover.json đặt tại root template với projectType: "frontend". Template chưa có unit-test runner: bổ sung runner phù hợp (ví dụ Vitest cùng Testing Library) đạt điều kiện coverage của bước 8; npm run lint và npm run build (typecheck và Vite build) phải pass.
  • Mỗi sub-agent cấu hình unit-test runner và coverage phù hợp stack ngay khi khởi tạo: lệnh tái chạy được (ví dụ npm run test:unit:coverage), ngưỡng line coverage >= 70% cho riêng project đó, exit code khác 0 khi có unit test thất bại hoặc coverage dưới ngưỡng. Backend theo starter: test nghiệp vụ đặt ngoài src/ (publish đóng gói src/) và ngoài local/ (bộ công cụ local), ví dụ test/ có trong include của tsconfig.json; sửa script test của starter để npm test chạy cả test nghiệp vụ; script coverage đo trên src/**/*.ts trừ src/workspace.d.ts, ví dụ dùng node --test --experimental-test-coverage với --test-coverage-include/--test-coverage-exclude/--test-coverage-lines=70 khi bản Node.js hỗ trợ (kiểm tra node --help), nếu không dùng công cụ tương đương; coverage/ nằm trong .gitignore. Fake context của test ném lỗi khi code dùng danh tính ngoài thiết kế (ví dụ data.object() khi mọi lệnh phải qua data.asSystem()) và ghi lại lời gọi để test khẳng định số lời gọi, khoá và state.
  • Kiểm tra Node.js >= 20, CLI version/help và dependency thực tế; auth session cần CLI >= 0.9.0; cogover-dev git ... cần CLI >= 0.21.0, Git và credential store của hệ điều hành (npm từ 12 cài CLI kèm --allow-scripts=keytar). Dùng Workspace origin đã xác định, không để CLI rơi về runtime mặc định.
  • Git mặc định cho mọi Project, trừ khi người dùng không muốn: khởi tạo xong thì cogover-dev git setup, commit và push bản đầu; sau đó commit, push mỗi tính năng hoặc lần sửa lỗi đã qua test local và trước mỗi lần publish. Không lưu được thì báo một lần rồi làm tiếp, không chặn việc chính. Quy tắc: Lưu source trên Git server của Workspace.
  • Full-stack: hai thư mục/cấu hình độc lập, mỗi thư mục là một Project và một repository; proxy frontend local tới backend loopback theo Full-stack integration.
  • Bố cục repository: mọi file của dự án nằm trong thư mục của một Project; thư mục cha chỉ chứa các thư mục Project, file đặt ở đó không thuộc repository nào và không được lưu. Tài liệu riêng một phía ở docs/ của repository đó, tài liệu chung ở docs/ của repository backend; ảnh dùng trong giao diện ở repository frontend (src/assets/ với template), ảnh minh hoạ tài liệu đi cùng tài liệu (ví dụ docs/images/); E2E gọi API ở e2e/ của repository backend, E2E trình duyệt ở e2e/ của repository frontend; file phiên, artifact và log của lần chạy test vào .gitignore. Bảng đầy đủ, lệnh chạy E2E tách khỏi unit test và người commit e2e/: Bố cục repository.
  • E2E frontend (sub-agent E2E, tiếp tục ở bước 8–9): thư mục e2e/ trong repository frontend, tách khỏi src/, runner trình duyệt (khuyến nghị Playwright) với lệnh tái chạy được và exit code khác 0 khi có test fail, cấu hình qua biến môi trường không chứa secret, nguồn phiên test theo Xác thực trình duyệt test; script E2E được commit cùng repository frontend, file phiên, trace và báo cáo test nằm trong .gitignore, e2e/ không vào ZIP publish.

8. Code và test local

Sub-agent backend và frontend làm phần của mình, viết unit tests cùng lúc code và tự sửa lỗi tới khi đạt điều kiện bên dưới; agent chính phối hợp kiểm thử xuyên hai project.

  1. Code theo contract nghiệp vụ và schema đã xác minh. Backend:
    • Dùng public @cogover/sdk, chỉ import package được hỗ trợ và local source; không đưa Node built-in, local runner, dynamic import hoặc require() vào code publish. Đọc SDK usage guide rồi SDK API reference cho router, data, identity và API tích hợp thực sự sử dụng; TypeScript config theo mục khuyến nghị trong usage guide. API chưa quen: tra bảng README của cogover-sdk-samples rồi đọc file sample tương ứng; sample là minh hoạ cách gọi, không thay contract nghiệp vụ.
    • Sinh workspace.d.ts từ metadata Workspace bằng npx cogover-generate-workspace-types theo mục Khai báo Workspace. Đầu vào objects.json là response nguyên trạng của POST /bapi/v1/objects/list theo $object-info, với slugs gồm các Object code dùng (thêm Object đích của lookup khi cần kiểu đầy đủ) và includeFields: true (includeOptions, includeMetaData mặc định true); lệnh mẫu ở mục Sinh kiểu cho Workspace, nhưng agent đọc Workspace API key theo $cogover-api-auth thay vì nạp .env vào shell; không commit objects.json. Sinh lại sau mỗi thay đổi schema và sau khi nâng lên SDK 0.13.0: file sinh bởi bản cũ không import type RecordReference/FileValue/UrlValue, nên lỗi Cannot find name 'RecordReference' hoặc, với skipLibCheck: true, field lookup/file/URL âm thầm thành any và mất kiểm tra fields, expandLookups, aggregate. SDK Schema API chỉ đọc; tạo/sửa schema vẫn qua $object-info.
    • HTTP ngoài: ưu tiên import fetch từ SDK để local không âm thầm dùng native fetch. Project state không dùng lưu secret. Cần checkpoint hoặc concurrency thì đọc mục state/locks; lock không tạo transaction hay exactly-once. Job ghi field do backend khác duy trì: lấy khoá rồi mới đọc mọi input dùng để tính, ghi delta, lưu nhật ký state cho từng bước ghi và có đối soát theo Ghi số liệu do backend khác duy trì.
    • Dùng caller identity mặc định, kiểm tra input, chỉ đọc field cần thiết, xử lý kết quả thiếu/null, phân trang và lỗi từng phần.
    • Đọc record (SDK từ 0.13.0, mẫu đầy đủ trong Đọc record hiệu quả): mọi records.get/getMany/list truyền fields là danh sách slug tối thiểu code dùng, khai báo một lần thành hằng số as const cạnh hàm dùng; "*" chỉ khi thực sự cần mọi field được đọc (ví dụ sao chép record), không dùng để tránh liệt kê. Không truyền fields thì SDK ném ValidationError trước khi gửi. expandLookups chỉ bật khi cần tên hoặc field của record đích, chỉ cho đúng lookup đó và liệt kê field đích ({ customer: ["credit_limit"] }) thay vì true/"*"; mỗi Object đích là thêm lượt đọc, record đích không đọc được thì không được mở rộng (lookup name: "", reference giữ tên đã lưu) và không có fields. Không mở rộng thì không dựa vào name của lookup hay system.createdBy.name (đều "", trừ field reference); chỉ cần ID thì đọc id; tên hiển thị của người tạo lấy bằng org.personnel.get với withDisplay: true vì createdBy.name khi mở rộng có thể là mã sinh tự động. Số liệu đơn giản trên một Object dùng records.aggregate thay vì list nhiều trang rồi cộng; chỉ groupBy field choice, boolean, lookup_normal, ngày, số; xử lý metric null (avg/min/max khi không có giá trị) và truncated. Trong trigger cho thay đổi do người dùng và job có người dùng, data.object() có giới hạn của data.asUser() (không group, chỉ count trên "id" và metric trên field số): cần nhóm thì gọi qua data.asSystem() với grant tối thiểu và tự giới hạn where theo phạm vi nghiệp vụ. Không tin personnelId do client gửi để tự nâng quyền. Thông báo API trả client dùng tiếng Anh.
    • Số liệu tổng hợp đã chốt lấy từ báo cáo: không quét record để tính lại. Frontend gọi /api/v1/report (x-req-type: 9) service 200 với setting: true bằng phiên người dùng cuối theo Gọi báo cáo bằng phiên người dùng cuối: payload là hằng số dựng từ bước 2B, tham số người dùng chọn chỉ vào filters đã xác nhận, kiểm tra r ở root, gom nhiều chỉ số cùng Report Type vào một lần chạy, để báo cáo group/aggregate và giao diện chỉ biến đổi nhẹ. Backend không nhận số liệu báo cáo do client gửi làm căn cứ cho quyết định cần bảo vệ; khi cần thì theo phương án người dùng đã chọn ở mục Giới hạn của reference. Tách hàm biến đổi/parser thành hàm thuần, unit test bằng mẫu response đã redact.
    • Ngân sách của một lần thực thi (limits; SDK từ 0.14.0, mục Giới hạn của một lần thực thi): code cho khối lượng dữ liệu lớn nhất dự kiến, không cho fixture. Số tổng dùng records.aggregate hoặc saved report; đọc bằng getMany hoặc list với fields tối thiểu, không get trong vòng lặp; list giữ chỗ bằng limit nên đặt limit theo số record thực cần. Việc có thể vượt ngân sách route hoặc trigger chạy trong background job chia trang: sau mỗi trang đọc limits.usage() (recordsRead.remaining, capabilityCalls.remaining, elapsedMs so với timeLimitMs) và jobs.enqueue trang kế với cursor trước khi hết, theo ví dụ ở mục Đọc giới hạn trong code. RateLimitError có details.budget nghĩa là lần thực thi đã hết ngân sách đó: không chờ, không bắt rồi gọi tiếp (sau 1.000 lời gọi bị từ chối lần thực thi bị kết thúc, script không bắt được), không chuyển thành RetryableError trong job hay after-change vì lần chạy lại gặp đúng ngân sách đó; sửa thiết kế. Quota riêng của lần thực thi (20 fetch, 20 thao tác secret, 50 enqueue) cũng không hết khi chạy lại; chỉ HTTP 429 RATE_LIMITED không có details.budget do Workspace có quá nhiều lần thực thi đang chờ (bị từ chối trước khi chạy) và hạn mức theo giờ của notification/email mới thử lại sau được. Bắt lỗi SDK để trả response riêng thì giữ code và details an toàn (budget, limit, used, requested) trong response lỗi và log, không thay bằng thông báo chung làm mất dấu hiệu ngân sách. limits.usage() ném CogoverApiError CAPABILITY_UNAVAILABLE khi gọi ngoài handler (lúc nạp module, unit test chạy ngoài Cogover): truyền kết quả usage() vào hàm thuần để unit test quyết định bàn giao sang job.
    • Ghi nhiều và gọi song song (mẫu đầy đủ trong Ghi record hiệu quả): giảm số call trước, song song sau. Đọc nhiều record bằng một getMany, tạo/sửa nhiều record bằng một batchInsert/batchUpdate (1–200 dòng, ánh xạ results theo referenceId), không create/update trong vòng lặp. Các call không phụ thuộc nhau bắt đầu cùng lúc bằng Promise.all để chạy song song (tối đa một số call đồng thời do nền tảng đặt, vẫn tính ngân sách như cũ); call cần kết quả của call trước thì await theo thứ tự; await thao tác ghi xong rồi mới push.refreshRecords; không dùng cùng idempotencyKey cho hai call trong một Promise.all; call không await vẫn chạy xong trước khi lời gọi kết thúc nhưng lỗi của nó không được xử lý, nên luôn await. Dev CLI chạy call lần lượt: local chậm hơn, kết quả như nhau.
    • Lời gọi đồng thời: trên Cogover nhiều lời gọi của Workspace chạy cùng lúc (route, webhook, trigger after-change, job), local thì lần lượt. Chuỗi đọc → sửa → ghi cùng record/state key giữ locks.withLock hoặc dùng state.set với expectedVersion; module chưa an toàn khi chạy song song thì tạm đặt maxConcurrentRuns: 1. Frontend hoặc hệ thống gọi module xử lý HTTP 429 RATE_LIMITED không có details.budget (Workspace bận, chưa chạy gì) bằng cách chờ Retry-After giây rồi gửi lại có giới hạn, kèm Idempotency-Key cho thao tác ghi; việc hàng loạt do người dùng bấm thì chuyển sang background job thay vì gửi nhiều request song song. Chi tiết: Lời gọi đồng thời của cùng module.
    • Quy tắc theo Super Admin/role (SDK từ 0.10.0): chỉ đọc invocation.user.membership.isSuperAdmin và roles từ context, không nhận role hay cờ quản trị do client gửi. So theo role id đã xác minh trên Workspace đích (không theo tên), đặt ID thành hằng số hoặc cấu hình có tên rõ ràng; invocation.user là null với danh tính system/inbound nên xử lý rõ nhánh này (từ chối hoặc bỏ qua theo contract) thay vì truy cập thẳng. Tách hàm kiểm tra thuần để unit test các ca Super Admin, có role, không role và user: null; route từ chối trả 403 kèm message tiếng Anh. Đây là quy tắc nghiệp vụ bổ sung: quyền record/field vẫn do security rules theo danh tính người gọi (Super Admin không tự bỏ qua Object Security Rules); khi cờ/role mở đường cho data.asSystem(), kiểm tra trước lệnh ghi đầu tiên và giữ grant của policy giới hạn đúng thao tác. Chạy local: các giá trị được lấy lúc development session bắt đầu, đổi role của user test thì khởi động lại cogover-dev.
    • Record trigger: khai báo bằng defineTrigger trong module riêng và liệt kê đủ trong named export triggers; tách quy tắc nghiệp vụ thành hàm thuần để unit test. key ổn định giữa các version; fields chỉ gồm field handler đọc; changedFields, when và runWhen: "onEnter" thu hẹp lần chạy và ngăn trigger after-change tự kích hoạt lại; writableFields chỉ với before-change và đúng field handler gán. Handler nhận cả lô tới 200 record: gom ID, đọc một lần bằng records.getMany với fields tối thiểu, ghi một lần bằng batch; lookup trong record.new/record.old đã có tên, không cần đọc lại chỉ để lấy tên; không đọc/ghi trong vòng lặp. Before-change chỉ đọc (ghi record, fetch, lock, state, push, notification, email, jobs.enqueue, secrets.get, processes, agents và mọi thao tác crypto bằng key { secret } bị từ chối với reason TRIGGER_READ_ONLY; đọc org, hash, random và thao tác crypto với key truyền trực tiếp, ví dụ verify bằng public key, vẫn dùng được), fail-closed và phải xong trong timeoutMs (mặc định 2 giây, tổng 8 giây cho mọi trigger before-change của một thao tác ghi): đặt quy tắc bắt buộc ở đây, từ chối bằng record.addError(field hoặc null, CODE, message) với code viết hoa, message tiếng Anh. After-change best-effort, có thể chạy lặp hoặc không chạy: idempotent theo trigger.changeId cùng record.id, ném RetryableError chỉ cho lỗi tạm thời, không sửa record.new, sửa record bằng records.update/batch (thao tác ghi mới, chạy lại trigger trong ngân sách 10 bước) rồi push.refreshRecords cho các record đã sửa để người đang mở, kể cả người vừa lưu, tải lại; việc dài hơn timeoutMs hoặc cần state/locks thì jobs.enqueue với idempotency key theo changeId. Trigger hoặc job phản ứng với thay đổi của record con không lấy rollup/formula của record cha làm căn cứ: trong before-change giá trị đó chưa gồm thay đổi đang lưu, còn thời điểm tính lại sau khi lưu tài liệu không nêu (đã quan sát có độ trễ, xem rollup_summary của $object-info); tính từ record nguồn (before-change: tổng đã lưu của record cha trừ giá trị cũ cộng giá trị mới của record con; job: list record con theo record cha). Before-change đọc nhiều Object thì đọc theo tầng và thoát sớm: đọc trước dữ liệu quyết định có cần kiểm tra (trạng thái, liên kết), bỏ qua ngay khi không thể vi phạm, chỉ đọc record tổng hoặc cấu hình khi thay đổi làm tăng giá trị được kiểm soát; capability call chạy lần lượt nên chuỗi đọc dài có thể hết timeoutMs và fail-closed chặn mọi thao tác ghi liên quan (suy luận từ contract, chưa đo). Unit test khẳng định số lời gọi đọc của ca không cần kiểm tra và của lô lớn nhất dự kiến.
    • Push message (push.refreshRecords, push.toast, push.message; SDK từ 0.7.0): có trong script, route, trigger after-change và Development Session ghi được; cùng cơ chế với action Push Message của Workflow nên trang record chuẩn tự xử lý refreshRecords và toast. Người nhận mặc định "viewers" (người đang mở record đích, bắt buộc object và recordIds), hoặc danh sách personnel ID đích danh; exclude: ["actor"] bỏ người gây ra execution. Gửi là best-effort, không thu hồi được và có thể gửi lặp khi execution chạy lại; mỗi lời gọi tốn một capability call; tối đa 200 record ID và 200 personnel ID; ID/slug không tồn tại ném NotFoundError, deployment tắt push ném PUSH_DISABLED. Không đưa secret hoặc dữ liệu người dùng không được xem vào toast/message; links của toast phải là URL tuyệt đối http(s). Frontend nhận push bằng @cogover/client-sdk (từ 0.3.0): custom component và Federation Page dùng useSocketListener, single page app dùng createSocketListener từ @cogover/client-sdk/socket, theo mục Nhận push message trong Dùng @cogover/client-sdk ở frontend; không tự mở WebSocket hay suy đoán API ngoài tài liệu đó và README của gói.
    • Cơ cấu tổ chức (org; SDK từ 0.11.0): chỉ đọc, có trong script, route, trigger (cả before-change), job và Development Session. Lấy người gọi bằng org.me() hoặc invocation.user.membership.personnelId, không nhận personnelId người duyệt hay cấp quản lý do client gửi; ID nhận từ request thì kiểm tra định dạng (ví dụ ^[A-Za-z0-9_-]{1,64}$) trước khi gọi. Dùng isManagerOf(managerId, personnelId, { departmentId }) cho kiểm tra quyền duyệt và truyền phòng ban của record khi có (không có departmentId thì mọi phòng ban của người tạo đều được xét, nên người quản lý họ ở phòng ban khác cũng nhận true); directOnly: true khi chỉ quản lý trực tiếp được duyệt. Chọn người duyệt bằng personnel.managerChain(id, { departmentId, accountOnly: true }): tiers[0] là quản lý gần nhất, nhiều người cùng cấp chung một tier, mỗi người chỉ xuất hiện ở tier gần nhất; xử lý rõ chuỗi rỗng (người đứng đầu, nhân sự không thuộc phòng ban nào hoặc không tồn tại), vacantDepartmentIds và truncated theo contract thay vì mặc định cho qua. level của membership là cấp quản lý (0 nhân viên, 1 cao nhất), level của phòng ban là độ sâu cây (1 là gốc). Chỉ thêm withDisplay: true khi cần tên, email, avatar (thêm tối đa một lần đọc cho mỗi loại dữ liệu và mỗi 200 ID, tính capability call thay vì lời gọi cục bộ; giá trị hiển thị có thể là null); tên theo ngôn ngữ người gọi trừ khi truyền language. Đọc nhiều người bằng getMany hoặc một trang members (mặc định 500, tối đa 2.000, tối đa 200 khi có withDisplay; phân trang bằng nextCursor), không gọi get trong vòng lặp; mọi lời gọi trong một execution đọc cùng một phiên bản cơ cấu. Field nhân sự khác (số điện thoại, custom field) đọc qua data.object("personnel") với quyền record. Lỗi: get trả null, personnel.managerChain trả mảng rỗng, isManagerOf/isInDepartment trả false với ID không tồn tại; members, managers, managerChain của phòng ban, ancestors, tree({ rootId }) ném NotFoundError (chuyển thành 404); ORGANIZATION_UNAVAILABLE là lỗi tạm thời (HTTP 503 nếu không bắt, ném RetryableError trong job/after-change khi cần thử lại). Tách logic chọn người duyệt thành hàm thuần nhận kết quả org để unit test các ca nhân viên, phó phòng, trưởng phòng, nhiều phòng ban, chuỗi rỗng và phòng trống.
    • Notification (notifications.send; SDK từ 0.12.0): có trong script, route, trigger after-change, job, lời gọi inbound webhook và Development Session ghi được. to là 1–200 personnel ID (chia lô khi nhiều hơn), exclude: ["actor"] khi không báo cho chính người gây ra execution; kiểm tra notified và skippedPersonnelIds (personnel chưa có tài khoản) trong kết quả thay vì coi mọi người nhận đều đã được báo. channel là record ID notification_channel đã xác minh, đặt thành hằng số hoặc cấu hình có tên rõ ràng; không truyền channel thì mọi hình thức gửi đều bật; email: false khi không muốn bản sao email. categories (SDK từ 0.17.0) chỉ nhóm notification trong danh sách để người nhận lọc, không đổi cách gửi như channel: truyền code của notification_category (ví dụ process, sales, system; ổn định giữa các Workspace, khác channel là record ID riêng từng Workspace), tối đa 10, mỗi phần tử khớp ^[A-Za-z0-9_-]{1,64}$; Cogover không báo lỗi khi danh mục không tồn tại hoặc không hoạt động mà bỏ qua, không còn danh mục hợp lệ thì vào system; không truyền thì Workspace tự gán theo loại notification. title ≤ 200 ký tự, content ≤ 16.384 ký tự; contentType: "html" chỉ giữ định dạng cơ bản, escape dữ liệu người dùng trước khi ghép HTML; subtitle cần display: "FULL" (mặc định khi có subtitle); link.url là path web app bắt đầu bằng / hoặc URL http(s) tuyệt đối; sender: "actor" ném ValidationError khi execution không có người dùng. Không đưa secret hoặc dữ liệu người nhận không được xem vào nội dung.
    • Email (email.send, email.senders; SDK từ 0.12.0): có trong script, route, trigger after-change, job, lời gọi inbound webhook và Development Session. Chọn hộp thư theo địa chỉ từ email.senders() hoặc ID cấu hình có tên rõ ràng, không nhận mailbox ID từ client; from: "actor" chỉ khi có người dùng và policy cho phép. Người nhận là địa chỉ, { email, name } hoặc { personnelId } (tổng tối đa 50 trong to/cc/bcc, to bắt buộc trừ khi có recordEmailFields); cung cấp đúng một trong html và text (tối đa 192 KB), subject một dòng ≤ 255 ký tự; escape dữ liệu người dùng trong HTML. Cần email trên timeline record (theo dõi lượt mở/bấm, gom thư trả lời) thì truyền record; recordEmailFields (≤ 20 field email), attachments (file field của record, ≤ 10 file, tổng 20 MB) và appendSignature (chỉ với "actor") cần record, và record phải đọc được bằng danh tính mặc định. Không có record thì delivery: "direct". Lời gọi xong khi Cogover tiếp nhận: lưu trạng thái "đã tiếp nhận để gửi" cùng requestId, không coi là đã tới hộp thư. Lỗi riêng: EMAIL_SENDER_NOT_GRANTED (grant ở bước 5), NotFoundError mailbox/personnel/record, ValidationError.
    • Chung cho notification và email: mỗi lời gọi là một capability call, không thu hồi được; việc gửi lặp khi execution chạy lại chỉ tránh được bằng idempotencyKey (chữ, số, ., _, :, -, tối đa 128 ký tự, giữ 7 ngày, key của notification và email tách biệt): trigger after-change dùng ${trigger.changeId}:${record.id}, job dùng job.id kèm ID nghiệp vụ, route dùng khoá nghiệp vụ để bấm lặp không gửi hai lần; duplicate: true là kết quả hợp lệ. DUPLICATE_IN_PROGRESS nghĩa là lời gọi cùng key đang gửi: thử lại sau giây lát hoặc ném RetryableError trong job. Hạn mức mặc định mỗi project 5.000 người nhận notification và 500 email mỗi giờ (RateLimitError, không gửi gì): chia đợt lớn qua nhiều run job. NOTIFICATIONS_DISABLED/EMAIL_DISABLED là dependency của deployment, báo lại thay vì sửa code. Gửi sau khi các thao tác ghi record liên quan đã thành công, không gửi trong vòng lặp đọc/ghi từng record khi gom được một lần gửi cho nhiều người nhận. Tách hàm dựng message (người nhận, tiêu đề, nội dung, key) thành hàm thuần để unit test.
    • Background job (defineJob, named export jobs, jobs.enqueue; SDK từ 0.8.0): khai báo trong module riêng, liệt kê đủ trong jobs (tối đa 50), key ổn định giữa các version (key mới là job khác). Tự kiểm tra payload như input request (JSON tối đa 64 KiB; truyền ID record thay vì dữ liệu lớn). Handler idempotent và chạy lại an toàn (ít nhất một lần, có thể chạy đồng thời với run khác): kiểm tra trạng thái/record trước khi ghi, dùng job.id làm idempotency key khi gọi dịch vụ ngoài, ném RetryableError chỉ cho lỗi tạm thời (lỗi khác kết thúc FAILED không retry), giá trị trả về bị bỏ qua nên lưu kết quả vào record hoặc state. Mỗi lần thử trong timeoutMs (tối đa 60 giây): việc dài xử lý một trang (tối đa 200 record, sắp xếp ổn định, ghi bằng batch) rồi jobs.enqueue chính nó với cursor và idempotency key ${job.id}:next, có điều kiện dừng. Enqueue từ route/trigger/webhook với idempotencyKey theo khoá nghiệp vụ (tối đa 128 ký tự, key cũ trả run cũ với duplicate: true kể cả đã kết thúc), delayMs hoặc runAt khi cần chạy trễ; enqueue sau các thao tác ghi record mà job phụ thuộc và không cùng transaction; tối đa 50 lần enqueue mỗi invocation; run đã enqueue không hủy riêng được nên handler đọc lại dữ liệu và bỏ qua việc không còn cần. Lịch: cron năm trường (không tên MON, không macro @daily), múi giờ IANA mặc định UTC, defineJob validate cron khi module được nạp (typecheck không phát hiện), mốc bị lỡ không chạy bù, run lịch có thể chồng nhau nên bọc locks.withLock (waitMs: 0) cho đoạn không được chạy song song.
    • Custom Module Action (defineAction, s; SDK từ 0.15.0, mục Custom Module Action): khai báo trong module riêng, liệt kê đủ trong named export actions (tối đa 50); defineAction validate ngay khi module được nạp (key lạ, handler không phải function, description thiếu khi mở cho agent ném ValidationError) nên chạy npm test hoặc máy chủ local trước publish. key (chữ thường, chữ số, _, tối đa 64 ký tự) giữ ổn định giữa các version: node Process và tool agent tham chiếu projectSlug + key và luôn gọi version active, đổi key là action khác. label 1–100 ký tự; description tối đa 1.000 ký tự, viết cho AI Agent đọc (khi nào dùng, cần gì, trả gì); .describe() cho từng property (1–500 ký tự) để builder hiển thị và agent điền đúng. Chọn effect: "read" khi action không ghi: host từ chối ghi record, locks, push, notifications.send, email.send, jobs.enqueue, secrets.get (và key { secret } của crypto), fetch khác GET/HEAD hoặc có credential, processes.start, agents.start với PermissionDeniedError (This action is declared read-only); "write" khi có ghi, và tool AI Agent của action này mặc định cần phê duyệt. Schema: gốc input/output là s.object; builder s.string({ minLength, maxLength }) (mặc định maxLength 10.000, không có pattern: kiểm tra định dạng trong handler rồi trả kết quả hoặc ném ValidationError), s.enum (1–100 giá trị), s.number/s.integer (minimum/maximum), s.boolean, s.date (YYYY-MM-DD), s.dateTime (Unix ms), s.recordId(objectSlug) (kiểu CogoverRecordId, truyền thẳng vào records API; Cogover không kiểm tra record tồn tại), s.array(item, { maxItems }) (mặc định 1.000), s.object (tối đa 50 property, tên [A-Za-z][A-Za-z0-9_]{0,63}, giữ thứ tự khai báo); lồng tối đa 3 cấp, JSON Schema input + output ≤ 16 KiB; schema viết tay không được nhận. Property Process có thể để trống thì khai báo .optional() (node không gửi property rỗng). Cogover kiểm tra input trước handler (INPUT_INVALID, handler không chạy) và output sau handler (OUTPUT_INVALID, thao tác ghi đã làm không hoàn tác): trả object khớp schema, không undefined. Bên gọi chỉ nhận mã lỗi và thông báo cố định (SCRIPT_ERROR kèm details.scriptErrorCode, không có message của handler): kết quả dự kiến như không tìm thấy, không đủ điều kiện, vượt hạn mức trả trong output (ví dụ found, reason kiểu s.enum) để gateway phía sau rẽ nhánh; chỉ ném lỗi cho trường hợp thật sự thất bại. Mọi lời gọi chạy trong ngân sách route và timeoutMs (1.000–8.000 ms, vượt là TIMEOUT, handler không bắt được): việc dài hơn jobs.enqueue rồi trả ngay (ví dụ status: "queued"). Idempotent: bên gọi gửi lại cùng action.runId khi không nhận được trả lời, Cogover trả kết quả đã lưu trong 7 ngày nhưng có thể chạy lại handler nếu bị gián đoạn giữa chừng, nên dùng action.runId làm idempotency key cho jobs.enqueue, processes.start, notifications.send, email.send và lời gọi ra ngoài; lần chạy mới của node/tool có runId mới. Danh tính: đọc invocation.executionIdentity/invocation.user (null khi system) và invocation.source (type: "process" với processId, processInfoId, instanceId, nodeId, runAs PROCESS_STARTER/PERSONNEL/SYSTEM; type: "agent" với agentId, sessionId, runId, interactive, initiatorPersonnelId); xử lý rõ nhánh user: null; action do agent gọi chạy dưới danh tính của agent (nhân sự run-as của agent, agent nội bộ thì người đang chat, lượt chạy nền theo runAs của lượt chạy) nên quy tắc theo người dùng thật dựa vào initiatorPersonnelId, không tin personnelId nằm trong input do LLM hay dữ liệu Process sinh ra để nâng quyền. Input từ agent là dữ liệu không tin cậy như request của client. Ghi record của action tiêu thụ ngân sách tự động hoá của lượt chạy gọi nó (MAX_HOP_EXCEEDED khi hết), nên action ghi Object có trigger hoặc Process tự kích hoạt lại không tạo vòng lặp vô hạn nhưng vẫn phải có điều kiện dừng. Tách logic thành hàm thuần nhận input đã định kiểu và dữ liệu đã đọc để unit test; handler chỉ đọc, gọi hàm thuần, ghi và trả output.
    • Khởi chạy Process và AI Agent (processes, agents; SDK từ 0.15.0, mục Process và AI Agent): có trong script, route, trigger after-change, job và action "write"; không có trong before-change, action "read" (chỉ get), preview/session chỉ đọc. processes.start(processInfoId, …) dùng Process ID (PI...) đã được policy cho phép, khởi chạy version đã kích hoạt và xuất bản của Normal Flow: input là object theo tên biến Process được đánh dấu input (tối đa 64 KiB), instanceName 1–255 ký tự. agents.start(agentId, …): instruction 1–20.000 ký tự, variables tối đa 50 giá trị chuỗi (≤ 4.000 ký tự)/số/boolean, records tối đa 20 { objectSlug, recordId, withActivities }, resultSchema bằng s.object (agent trả kết quả không khớp thì lượt chạy thất bại RESULT_SCHEMA_MISMATCH), runAs: "caller" (mặc định) hoặc "agent" (bắt buộc khi không có người dùng), approvalPolicy: "deny" (mặc định: tool cần duyệt không chạy) hoặc "autoApprove" (cần allowAutoApprove và người dùng), timeoutSeconds 10–1.800 (mặc định 300). deliverTo: { personnelId, conversation, title } (SDK từ 0.16.0, mục Gửi tin nhắn chat cho một nhân sự) cho agent gửi tin nhắn chat tới một nhân sự trong AI chat của agent với người đó: personnelId bắt buộc; conversation: "new" (mặc định) luôn mở hội thoại mới, "latest" nối hội thoại gần nhất của agent với người nhận và mở mới khi chưa có, hội thoại đang bận hoặc đã bị xoá; title một dòng 1–80 ký tự (mặc định lấy phần đầu tin nhắn). Agent tự soạn tin và tự quyết định có gửi hay không nên instruction nêu người nhận cần biết gì và khi nào đáng gửi; text/result chỉ trả về code, không gửi cho người nhận; khi người nhận trả lời, agent chạy với danh tính và quyền của người nhận. Người nhận phải là nhân sự của Workspace, được dùng agent và có seat AI Agent: không đạt thì start ném CogoverApiError RECIPIENT_NOT_FOUND, RECIPIENT_ACCESS_DENIED hoặc RECIPIENT_AI_SEAT_REQUIRED (cần đổi cấu hình Workspace: báo người dùng, không thử lại ngay), hoặc DELIVERY_UNAVAILABLE (tạm thời, thử lại sau); không lượt chạy nào được tạo, mã có ở code và details.upstreamCode, và nói về người nhận, không về người gọi (lỗi của người gọi là AI_SEAT_REQUIRED, AGENT_RUN_DENIED). Kết quả gửi nằm ở delivery của agents.get và agentRun.delivery của job onResult (kiểu AgentDelivery; null khi không có deliverTo, đọc khi lượt chạy đã ở trạng thái cuối): status SENT (kèm chatSessionId, conversationId, newConversation), NOT_SENT (agent không gửi), BLOCKED/FAILED kèm errorCode (ví dụ RECIPIENT_RATE_LIMITED khi vượt số tin một lượt chạy hoặc một ngày được gửi cho người đó); lượt chạy có thể COMPLETED mà tin không được gửi nên job kiểm tra delivery.status riêng với agentRun.status. Không chờ kết quả trong route/action: onComplete: { job, payload }/onResult: { job, payload } chỉ định job của version active (payload ≤ 8 KiB; job không được khai báo thì start ném ValidationError), job nhận ProcessCompletionJobPayload (payload, process.state COMPLETED/CANCELED/DELETED/FAILED, process.output là biến output hoặc null khi lớn hơn 48 KiB) hoặc AgentResultJobPayload (agentRun.status, text ≤ 32 KiB, result hoặc null khi lớn hơn 16 KiB, error.code trong danh sách cố định, usage, delivery), chạy dưới danh tính của lần thực thi đã gọi start, enqueuedBy là process:<instanceId>/agent:<runId>; kiểm tra state/status trước khi dùng kết quả, job có thể chạy lặp nên idempotent, và kết quả không được giao khi version active không còn khai báo job, hết ngân sách tự động hoá hoặc chuỗi job quá dài. Luôn truyền idempotencyKey theo khoá nghiệp vụ (trong job suy ra từ payload/job.id, trong action từ action.runId; nhiều người dùng chung khoá thì đưa người dùng vào key; một việc nhắn nhiều người thì đưa người nhận của deliverTo vào key, vì cùng key với deliverTo khác trả lượt chạy đầu và không gửi cho người kia): cùng key trả lượt chạy cũ với duplicate: true, key thuộc danh tính khác ném CogoverApiError IDEMPOTENCY_CONFLICT. RetryableError với details.writesMayHaveCompleted: true nghĩa là lượt chạy có thể đã được tạo: thử lại sau với cùng key (trong job ném lại RetryableError), không đổi key. Giới hạn: 20 processes.start và 5 agents.start mỗi lần thực thi (RateLimitError không có details.budget), 1.000 Process mỗi ngày UTC mỗi project, maxRunsPerDay của agent; mỗi lần start tốn một bước ngân sách tự động hoá (MAX_HOP_EXCEEDED) và agent lồng nhau tối đa 3 cấp (AGENT_DEPTH_EXCEEDED): không thiết kế chuỗi Process/agent → action/job → start lại chính nó. Lỗi khác: PermissionDeniedError (PROCESSES_NOT_ALLOWED, AGENTS_NOT_ALLOWED, AGENT_AUTO_APPROVE_NOT_ALLOWED, PROCESS_START_DENIED, AGENT_RUN_DENIED), NotFoundError (Process không tồn tại/không active, agent không có, get lượt chạy không do project khởi tạo), ValidationError với details.upstreamCode (ví dụ biến input không tồn tại), CogoverApiError với mã tính phí credit AI của agents.start (INSUFFICIENT_CREDITS, AI_SUBSCRIPTION_REQUIRED, AI_SEAT_REQUIRED, AGENT_CREDIT_LIMIT_EXCEEDED, MODEL_NOT_PRICED: không lượt chạy nào được tạo, báo người dùng việc cần làm thay vì thử lại ngay), PROCESSES_DISABLED/AGENTS_DISABLED là dependency của môi trường. processes.get/agents.get chỉ để hiển thị trạng thái; không poll trong vòng lặp. Tách hàm dựng tham số start và hàm xử lý payload kết quả thành hàm thuần để unit test.
    • Secret/credential (fetch({ credential }), secrets.get, key { secret } của crypto; SDK từ 0.8.0): ưu tiên credential khi token nằm trong HTTP header (không tự đặt authorization hoặc header trùng với header đã cấu hình); chỉ dùng secrets.get/{ secret } với secret OPAQUE khi cần chính giá trị, đọc một lần trước vòng lặp (tối đa 20 thao tác secret mỗi invocation, giá trị cache trong invocation); không ghi giá trị vào log, record, state, response hay thông báo lỗi; chỉ trả field cần thiết từ response của dịch vụ ngoài vì response có thể chứa token. 401/500 từ dịch vụ ngoài là response fetch bình thường: kiểm tra ok/status; timeout không chứng minh thao tác ghi đã thất bại.
    • Crypto hash/HMAC/random (sha256, hmacSha256, randomBytes, randomUUID, timingSafeEqual; SDK từ 0.8.0): lấy từ context hoặc import { crypto } from "@cogover/sdk", mọi thao tác trừ timingSafeEqual cần await và tính một lời gọi cục bộ (localCalls), không tính capability call; so sánh chữ ký/token luôn bằng timingSafeEqual; tạo body gửi đi một lần rồi ký đúng chuỗi đó, kiểm tra chữ ký inbound trên request.rawBody (không JSON.stringify(request.body)), chuẩn hoá encoding/prefix của bên gửi trước khi so; tối đa 256 KiB mỗi lần gọi; chỉ SHA-256 hash/HMAC, không băm mật khẩu; token truy cập chỉ lưu hash kèm hạn; sandbox không có Buffer hay node:crypto.
    • Mã hoá và chữ ký (aesEncrypt, aesDecrypt, rsaEncrypt, rsaDecrypt, sign, verify; SDK từ 0.9.0): key qua { secret: name } (AES key thêm encoding: "base64" hoặc "hex" khi secret lưu dạng đó), không đưa private key/AES key thật vào source, fixture hay log (test chỉ dùng key sinh riêng cho test). AES: mặc định GCM và để Cogover sinh IV ngẫu nhiên, chỉ truyền iv khi bên kia quy định và không bao giờ dùng lại IV với cùng key GCM; gửi iv cùng ciphertext; tag mặc định nối cuối ciphertext (Java, .NET, Web Crypto), separateTag: true khi bên kia cần tag riêng (Node.js, OpenSSL); aad phải khớp khi giải mã; CBC chỉ khi bên kia bắt buộc và xác thực thêm bằng HMAC. RSA: mặc định OAEP-SHA256 với MGF1 SHA-256, xác nhận digest MGF1 của bên kia (code Java OAEPWithSHA-256AndMGF1Padding thường để MGF1 SHA-1, không khớp cả OAEP-SHA256 lẫn OAEP-SHA1), PKCS1 chỉ khi bắt buộc; dữ liệu vượt giới hạn RSA thì mã hoá bằng AES với key randomBytes(32) rồi chỉ mã hoá key đó bằng RSA. Chữ ký: chọn algorithm/encoding/signatureFormat theo bảng JWT alg của SDK API reference (ES* cần base64url và ieee-p1363), ký và xác minh đúng chuỗi byte (request.rawBody, header.payload của JWT), không dùng RSA-SHA1 cho chữ ký mới; verify trả false với chữ ký sai hoặc sai định dạng nên kiểm tra giá trị trả về, không dựa vào exception; JWT có chữ ký hợp lệ vẫn phải kiểm exp, iss, aud trước khi tin. Giải mã thất bại vì bất kỳ lý do gì ném CogoverApiError code: "DECRYPTION_FAILED": bắt riêng để trả lỗi nghiệp vụ tiếng Anh, không log ciphertext/plaintext nhạy cảm; plaintext không phải UTF-8 thì dùng output: "bytes" hoặc encoding nhị phân. Key/thuật toán/tuỳ chọn sai ném ValidationError; data, aad và ciphertext tối đa 256 KiB, văn bản key tối đa 16.384 ký tự; mỗi lời gọi tính một lời gọi cục bộ (localCalls), key { secret } còn tính vào giới hạn 20 thao tác secret mỗi invocation.
    • Inbound webhook (SDK từ 0.8.0): đăng ký route dưới /hooks/ (bên gửi gọi /hooks/{inboundId}/..., handler thấy request.path không có inbound ID), kiểm tra invocation.identity === "inbound" và invocation.inbound.mode khi bắt buộc HMAC (lời gọi qua URL thường hoặc local có danh tính người dùng thì trả 403), validate request.body (JSON object; body dạng khác cho {} và tự parse từ rawBody/contentType). Handler ngắn: kiểm tra, ghi dấu đã nhận bằng state expectedVersion: 0 (xử lý StateConflictError khi trùng, thêm định danh bên gửi vào khoá nếu nhiều bên có thể dùng cùng event ID) hoặc jobs.enqueue với event ID làm idempotencyKey, rồi trả 200/202; nhà cung cấp gửi lại khi không nhận 2xx nên phải chịu được sự kiện lặp, và chặn chữ ký đã dùng của Cogover không bảo đảm tuyệt đối. request.headers không chứa Authorization, cookie, inbound key hay header chữ ký đã cấu hình. Cần thêm chữ ký riêng của nhà cung cấp thì kiểm tra bằng crypto trên rawBody; cách ký của họ khớp chế độ HMAC có sẵn thì cấu hình ở inbound access (bước 6A) để Cogover kiểm tra trước handler.
  2. Test logic, input không hợp lệ, thiếu record, quyền được phép/bị từ chối và ghi lặp khi liên quan; route, trigger hoặc job xử lý nhiều record thêm ca khối lượng lớn nhất dự kiến và ca usage() sắp hết để chứng minh bàn giao sang job. Custom Module Action: test hàm thuần với giá trị biên của schema (property optional vắng mặt, chuỗi dài nhất, số ở minimum/maximum, mảng rỗng), mọi nhánh kết quả dự kiến trong output, danh tính user/system và source process/agent, gửi lại cùng runId không tạo hiệu ứng lần hai; kiểm tra JSON.stringify(schema) của input/output đúng ý (thứ tự property, required, description). Khởi chạy Process/AI Agent: test hàm dựng tham số start (key idempotency, input theo tên biến) và hàm xử lý payload kết quả với từng state/status và result: null. Decimal, preview/apply hoặc ghi hàng loạt: đọc Batch writes và checkpoint, kiểm tra làm tròn, request đồng thời, preview cũ và lỗi từng phần. Chạy typecheck/build và các test phù hợp dự án.
  3. Backend có dữ liệu thật chạy qua COGOVER_LOCAL_PORT=<PORT> cogover-dev run --profile <PROFILE> --allow-writes=false -- npm run dev cho test đọc; test ghi trong phạm vi yêu cầu dùng --allow-writes=true với fixture có marker/ID theo dõi. CLI mặc định cho phép writes; chạy local không đồng nghĩa mock hay dry-run. CLI từ 0.19.0 với SDK từ 0.14.0 áp cùng ngân sách theo loại lần thực thi của từng handler (route, before-change, after-change, job) và từ chối như trên Cogover, trừ record liên kết của expandLookups và giới hạn thời gian (chỉ báo trong usage()); --no-enforce-limits chỉ đếm để đo mức dùng, chạy lại không có cờ trước khi kết luận PASS. Record trigger chạy trên cùng server local theo Record trigger quick start: GET /__cogover/triggers liệt kê manifest đã chuẩn hoá; POST /__cogover/triggers/<key> với operation và changes (create) hoặc recordId kèm changes (update/delete), hoặc records: [...] cho một lô; đối chiếu input.records, results (errors, changes) và warnings. Before-change chạy với --allow-writes=false; after-change có ghi hoặc push cần server không có cờ này và ghi/gửi thật qua Development Session (Development Session chỉ đọc từ chối push). Push trên local gửi tới người dùng thật của Workspace: khi thử, giới hạn recipients là personnel test hoặc record fixture chỉ người test đang mở. Notification và email trên local cũng tới người thật, kể cả địa chỉ ngoài Workspace: chỉ gửi tới personnel test và địa chỉ test đã được người dùng xác nhận, từ hộp thư test hoặc hộp thư được phép thử, gắn idempotencyKey và ghi lại requestId; session --allow-writes=false từ chối notifications.send/email.send nhưng vẫn gọi được email.senders() để xác minh hộp thư khả dụng. records.aggregate trên local chạy dưới danh tính người dùng của Project key nên có giới hạn của data.asUser(): groupBy hoặc metric ngoài count trên "id"/field số bị từ chối (ValidationError) dù route production chạy bằng người gọi nhóm được; kiểm thử phần nhóm của route trên version đã publish ở bước 9, hoặc qua data.asSystem() khi code thực sự dùng danh tính đó. Đọc org chạy được với session chỉ đọc và dùng cơ cấu thật của Workspace dưới danh tính nhân sự được cấp Project key (khác người dùng của Workspace API key khi gọi production): đối chiếu kết quả /me, thành viên phòng ban và chuỗi quản lý với dữ liệu Department/Personnel/Position đọc qua $user-permission, thay đổi cơ cấu hiện ra sau vài giây. Runner không đánh giá when/changedFields/runWhen và bỏ field ngoài fields; route /__cogover/ chỉ có trên local. Cogover chỉ gửi sự kiện thật tới version đã activate, nên kiểm thử bằng thay đổi bản ghi thật ở bước 9. Máy chủ local không chạy job, lịch cron hay xác thực inbound: jobs.enqueue từ route local tạo run thật trên Cogover chạy code job của version đang active (publish/activate handler job trước khi thử theo cách này; session --allow-writes=false từ chối enqueue); route /hooks/... gọi local mang danh tính người dùng nên điều kiện inbound trả 403 theo thiết kế; secret, credential và { secret } chỉ dùng được local khi quản trị viên cho phép Development Session, còn hash, random và crypto với key truyền trực tiếp chạy được local (chỉ dùng cặp key test sinh riêng, không dùng key thật). Mã hoá/chữ ký: kiểm tra round-trip (aesEncrypt → aesDecrypt, rsaEncrypt → rsaDecrypt, sign → verify), ca ciphertext bị sửa trả DECRYPTION_FAILED, chữ ký bị sửa trả false, và giải mã/xác minh mẫu do bên kia cung cấp để chứng minh tương thích. Tách logic job/webhook thành hàm thuần để unit test bằng fixture; kiểm chứng retry, lịch, danh tính và chữ ký trên Cogover ở bước 9. Custom Module Action: Process và AI Agent chỉ gọi version đã activate trên Cogover; starter dùng cho SDK 0.15.0 trở lên có thể có runner action local, dùng theo README của bản đã clone và không tự gọi __cogoverActionHandler (dành riêng cho Cogover). Không có runner thì kiểm thử handler bằng unit test và, khi cần dữ liệu thật, gọi cùng hàm thuần từ một route chỉ dùng khi phát triển rồi gỡ trước publish; effect, kiểm tra input/output theo schema, runId, danh tính và lỗi bên gọi nhận được kiểm chứng trên Cogover ở bước 9. processes.start/agents.start chạy trên local với Dev CLI từ 0.20.0 và session ghi được: tạo lượt chạy Process/agent thật dưới danh tính nhân sự của Project key (chỉ dùng Process/agent test, idempotencyKey có marker), onComplete/onResult bị từ chối NOT_SUPPORTED nên thử start không kèm hai tuỳ chọn, đọc kết quả bằng processes.get/agents.get, rồi kiểm thử job nhận kết quả sau publish. Lượt chạy có deliverTo trên local gửi tin nhắn chat thật: người nhận là personnel test đã biết trước việc này.
  4. Frontend test trên browser: sub-agent Frontend tự kiểm tra khi phát triển các trạng thái loading, empty, error, thao tác chính, khả năng dùng bàn phím và kích thước màn hình liên quan; sub-agent E2E độc lập viết E2E test tự động và chạy trên trình duyệt theo Kiểm thử E2E frontend, chạy các ca chạy được trên local ngay khi frontend báo sẵn sàng. Full-stack phải test xuyên frontend → backend → dữ liệu. Đăng nhập trình duyệt test bằng tài khoản người dùng cung cấp; không có thì sub-agent E2E dùng $cogover-api-auth đổi Workspace API key thành phiên Web App (POST /bapi/v1/auth-token) rồi nạp cookie phiên vào browser context. Phiên mang danh tính chủ key nên persona khác lấy theo $user-permission; agent không gõ mật khẩu vào form đăng nhập. Custom component: chạy npm run build-watch và npm run preview, dán URL http://localhost:5101/#./Components/<Tên> do preview in ra vào item Federation component của layout test, kiểm tra trên form thật các ca thiếu formBuilder, cập nhật field/ô Related List, lỗi giữa chừng và không tự chạy lặp; slug item lấy từ layout qua $object-layout, không dùng slug Object. Federation Page: npm run dev với .env.local trỏ Workspace test, đăng nhập theo cách trên hoặc người dùng tự đăng nhập Cogover trên trình duyệt (agent không nhập mật khẩu), kiểm tra từng route, Link nội bộ và API qua HTTP client của template. URL localhost trên layout chỉ dùng khi debug và phải thay bằng đường dẫn đã deploy ở bước 9.
  5. Ghi có thể đã thành công trước khi lỗi: đọc lại state trước retry; nhiều lệnh ghi không mặc nhiên là transaction. Lưu kết quả test và fixture đã chạm tới, không lưu credential.

Điều kiện bắt buộc cho từng project trước khi kết thúc bước 8:

  • Toàn bộ unit tests PASS, số thất bại bằng 0, có test thực sự được chạy; không dùng suite rỗng hoặc skip/todo thay các ca cần kiểm chứng. Typecheck/build và các test tích hợp/browser thuộc phạm vi cũng phải thành công.
  • Frontend: sub-agent E2E đã viết E2E test cho mọi tiêu chí nghiệm thu và các ca chạy được trên local đã PASS; ca chỉ chạy được trên Workspace (phiên thật, trang record, URL đã deploy) được ghi rõ để chạy ở bước 9. Thiếu sub-agent E2E độc lập hoặc chưa có script E2E là chưa đạt.
  • Backend có record trigger, background job, action ghi hoặc route ghi dữ liệu: sub-agent Review backend đã review code tại commit sẽ publish; phát hiện mức Cao đã sửa và review lại, phát hiện còn mở được agent chính ghi là rủi ro chấp nhận kèm lý do hoặc chuyển thành ca kiểm thử ở bước 9, phát hiện phụ thuộc hành vi nền tảng chưa đo được đo ở bước 9 hoặc bằng spike. Thiếu review độc lập là chưa đạt.
  • Review bảo mật: sub-agent Review bảo mật đã review commit sẽ publish của từng project theo Review bảo mật. Không còn phát hiện Nghiêm trọng/Cao đã xác định: không giới hạn số vòng sửa, sau 2 vòng chưa đóng thì báo người dùng và không publish. Phát hiện Trung bình/Thấp sửa tối đa 2 vòng, phần còn lại agent chính ghi rủi ro chấp nhận kèm lý do và báo người dùng trước khi publish. Phát hiện Nghiêm trọng/Cao PLAUSIBLE cần chạy thử thì chỉ publish để preview, chưa activate. Commit publish ở các vòng sửa sau qua review phần chênh lệch trước khi publish. Thiếu review bảo mật độc lập là chưa đạt.
  • Line coverage >= 70% riêng backend và >= 70% riêng frontend. Không lấy trung bình hoặc gộp coverage hai project; không dùng branch/function/statement coverage thay line coverage; không cộng E2E/integration coverage vào kết quả unit tests.
  • Đo coverage từ unit-test suite trên toàn bộ source ứng dụng của project, kể cả file chưa được test import. Chỉ loại trừ dependency, test/fixture, build output, code sinh tự động, declaration và cấu hình/local runner không thuộc code ứng dụng; ghi rõ cấu hình include/exclude. Không loại code nghiệp vụ, entrypoint hay UI cần chạy để đạt ngưỡng. Backend test logic/validation/error/side effect bằng dependency được kiểm soát; frontend test logic và component/hành vi UI phù hợp stack.
  • Lưu cho mỗi project: lệnh test, số test pass/fail, số dòng covered/total, phần trăm lines và đường dẫn báo cáo coverage, khớp source cuối sẽ publish. Sau khi sửa code, chạy lại unit tests với coverage của project bị ảnh hưởng.
  • Agent chính kiểm tra báo cáo và ngưỡng, yêu cầu sub-agent sửa nếu chưa đạt. Coverage chưa đo hoặc thiếu báo cáo là chưa đạt, không tự coi là 70%; không chuyển project chưa đạt sang publish.

9. Publish, activate và test trên Workspace thật

  1. Ghi nhận active version hiện tại của từng project và các thay đổi schema liên quan. Kiểm tra archive không có secret, .env, session file, source map hoặc dependency/local runner ngoài phạm vi. Publish, approve policy và activate thay đổi môi trường thật: trước mỗi thao tác xin người dùng xác nhận cho đúng project và version (version ID hoặc commit, thay đổi so với version đang active, trigger/job/lịch/action bị ảnh hưởng, dữ liệu thật có thể bị ghi); xác nhận cho version trước, cho riêng bước publish hay cho kế hoạch chung không thay xác nhận activate version mới. Chạy publish, approve policy, activate và đọc lại thành các lệnh riêng, không gộp thao tác thay đổi môi trường với nhau hay với thao tác đọc lại. Môi trường của agent từ chối hoặc đòi phê duyệt một thao tác thì dừng, báo người dùng thao tác bị chặn và lý do, chờ quyết định; không đổi sang đường khác cho cùng hiệu ứng (management API thay CLI, gộp hoặc tách lệnh, đổi tên script) để vượt qua.
  2. cogover-dev publish tại đúng thư mục project: backend ZIP chứa src/main.ts; frontend phải build trước, ZIP chứa dist/index.html (giữ thư mục dist/), CLI không build frontend. Publish qua API trực tiếp: upload ZIP private, dùng file metadata thật cùng idempotencyKey theo reference; không đoán upload endpoint.
  3. Chờ version READY hoặc FAILED có thời hạn; HTTP 202 chưa phải hoàn tất. Timeout: đọc version đã tạo trước khi publish lại. FAILED: sửa nguyên nhân rồi tạo version mới. Ghi version ID và build error an toàn nếu có. Backend có trigger: build evaluate export triggers thành trigger manifest và validate Object/field slug tồn tại, writableFields không có field chỉ đọc, key không trùng; TRIGGER_MANIFEST_INVALID nêu key/slug sai, sửa code rồi publish lại. Backend có job: build evaluate export jobs thành job manifest (key, timeoutMs, maxAttempts, schedule đã chuẩn hoá); defineJob ném ValidationError với cron/múi giờ/key sai khi module được nạp nên chạy máy chủ local hoặc npm test trước publish, không chỉ typecheck. Backend có action: build đọc export actions thành action manifest (key, label, mô tả, exposeTo, effect, timeoutMs, JSON Schema input/output); ACTION_MANIFEST_INVALID (key trùng, schema dùng keyword không hỗ trợ hoặc lớn hơn 16 KiB) sửa code rồi publish lại.
  4. Backend có policy: approve/read snapshot đúng version theo bước 5. Preview service 6 phải chỉ rõ version ID và READ_ONLY/READ_WRITE; preview mặc định READ_WRITE, ghi thật và không rollback theo nhóm.
  5. Activate đúng version bằng CLI, đọc lại project xác nhận ACTIVE và activeVersionId. Full-stack: backend sẵn sàng trước frontend phụ thuộc nó; hoàn tất Process còn chờ dependency. Version có trigger: activate áp dụng manifest ngay cho mọi thao tác create/update/delete trên Object từ mọi nguồn; activate version khác thay bằng manifest của version đó, deactivate gỡ toàn bộ. Nêu phạm vi ảnh hưởng trong yêu cầu xác nhận ở mục 1, đặc biệt lần activate đầu tiên trên Object đang có dữ liệu thật; gỡ một trigger bằng cách bỏ khỏi export triggers rồi publish/activate version mới. Version có trigger after-change hoặc job ghi dữ liệu: trước khi activate, xác nhận không có kiểm thử thủ công, spike hay sub-agent khác đang sửa cùng record hoặc Object mà trigger/job sẽ tính lại (trigger/job sẽ ghi đè số liệu và làm sai kết luận của kiểm thử đó); có thì hoãn activate hoặc tách dữ liệu test. Sau activate, đọc Record trigger đã đăng ký (POST /api/v1/ts-projects/triggers/list với projectId, không nằm dưới /ts-projects/{projectId}/) để xác nhận trigger của version mới ACTIVE khớp manifest; chưa khớp thì đọc lại sau vài giây trước khi kết luận. Version có job: activate làm jobs.enqueue nhận các key của version đó và bật lịch ngay (xem bằng cogover-dev jobs schedules); activate version khác thay lịch, deactivate gỡ lịch; run chạy trễ/retry dùng code của version active tại lúc chạy nên giữ key và định dạng payload tương thích. Lịch ghi dữ liệu thật nêu trong yêu cầu xác nhận ở mục 1; dừng một lịch bằng cách bỏ schedule rồi publish/activate version mới. Version có action: activate đưa action vào danh mục của Process Builder/AI Agent Builder và mọi node/tool đang tham chiếu cùng projectSlug + key gọi version mới ngay; bỏ action, đổi key, bỏ "process"/"agent" khỏi exposeTo hoặc deactivate Project làm node/tool đó lỗi (ACTION_NOT_FOUND, ACTION_NOT_EXPOSED; xuất bản lại Process bị chặn với mã 71/72), đổi schema không tương thích (thêm input bắt buộc, đổi kiểu, bỏ field output mà bước sau đọc) làm node cũ lỗi INPUT_INVALID hoặc tham chiếu RESOURCE_NOT_FOUND. Trước khi activate, liệt kê Process/tool đang dùng action và báo người dùng thay đổi cần làm trên chúng; đổi schema thì nhờ sub-agent Process/AI Agent cập nhật node/tool theo schema mới. Trong lúc đổi version active, lời gọi có thể thất bại chốc lát với PROJECT_POLICY_NOT_APPROVED và bên gọi tự gửi lại. Có Git: gắn tag cogover-<VERSION_ID> vào commit đã publish và push tag.
  6. Backend: gọi production service 3 bằng session do CLI tạo theo mẫu cURL; kiểm tra HTTP/body và đọc lại dữ liệu nghiệp vụ. Preview hoặc READY không phải bằng chứng production PASS. Route xử lý nhiều dữ liệu: gọi preview service 6 với showDebugData trên dữ liệu đại diện, so usage, operations (đếm cả lời gọi bị từ chối, chỉ ra lời gọi chạm giới hạn) và durationMs với ngân sách route; sát ngân sách thì đổi thiết kế trước khi bàn giao. Route dùng query string: test filter có kết quả/không có kết quả và cursor trên production; local khác Workspace thì áp dụng mục tương thích trong Full-stack integration. Record trigger: kiểm thử bằng thay đổi bản ghi thật qua $object-record hoặc giao diện theo mục 8 của Record trigger quick start, đủ các ca của từng trigger: bị từ chối trả HTTP 400, r: 70, msg: BEFORE_CHANGE_TRIGGER_REJECTED, data.trigger.key, meta theo field slug ($record cho lỗi cả bản ghi) và messages; điều chỉnh giá trị thì đọc lại record thấy field đã điền; after-change đọc lại sau vài giây và kiểm tra runWhen: "onEnter" không chạy lại khi record đã khớp when. HTTP 503 với BEFORE_CHANGE_TRIGGER_FAILED là handler ném lỗi, hết giờ hoặc ghi trong before-change: mọi thao tác ghi liên quan trên Object bị chặn cho tới khi activate version đã sửa hoặc version cũ hợp lệ. Dọn fixture theo đúng quy tắc trigger (ví dụ đổi trạng thái trước khi xoá). Push message: xác minh trên trình duyệt đã đăng nhập đang mở record đích (record tải lại sau refreshRecords, toast hiển thị đúng nội dung/vị trí/thời lượng, người trong exclude không nhận) và log Project không có PUSH_DISABLED; push best-effort nên không nhận được một lần chưa chứng minh lỗi code, lặp lại với record khác trước khi kết luận. Backend có trigger, job, action ghi hoặc route ghi dữ liệu: sub-agent Review backend chạy tiêu chí nghiệm thu trên version đã activate theo Kiểm thử end-to-end trên Workspace; lỗi giao lại sub-agent Backend, sau mỗi version sửa chạy lại ca lỗi cùng ca hồi quy liên quan.
  7. Background job, Custom Module Action, khởi chạy Process/AI Agent, secret/credential, crypto, inbound webhook, notification, email và cơ cấu tổ chức trên Workspace thật:
    • Background job: cogover-dev jobs schedules xác nhận lịch sau activate; enqueue thử bằng cogover-dev jobs enqueue <key> --payload-file payload.json --idempotency-key <key> (có --delay-ms khi cần; run này mang danh tính Super Admin của Workspace API key, nên nhánh danh tính system kiểm thử bằng run theo lịch hoặc từ webhook) hoặc qua route/trigger, rồi theo dõi cogover-dev jobs runs --job <key> và cogover-dev jobs run {RUN_ID} tới SUCCEEDED với số lần thử mong đợi, đọc durationMs/usage của lần thử gần nhất (CLI từ 0.19.0) để biết mức dùng ngân sách job và chỉnh kích thước trang; đọc lại record/state để chứng minh hiệu ứng nghiệp vụ. Kiểm thử ca lỗi tạm thời (PENDING với lỗi RETRYABLE rồi SUCCEEDED ở lần thử sau, FAILED khi hết maxAttempts), enqueue lặp cùng idempotency key trả duplicate: true, chạy trễ, job tự enqueue trang kế dừng đúng lúc. Run theo lịch chờ mốc kế tiếp; cần thử nhanh thì dùng cron ngắn trên Workspace test rồi publish lại lịch thật. CLI/management API không trả payload, chỉ payloadBytes; lịch sử run giữ bảy ngày. JOBS_DISABLED, JOB_NOT_DEFINED, IDENTITY_NOT_GRANTED, JOB_ACTOR_UNAVAILABLE, JOB_TIMEOUT, RateLimitError xử lý theo bảng sự cố của Background job quick start.
    • Custom Module Action: đọc danh mục GET /api/v1/ts-projects/actions?consumer=process (và consumer=agent) với x-req-type: 9, x-req-service: 4 bằng phiên Workspace, xác nhận action của version active có đúng exposeTo, effect, timeoutMs, schema và thứ tự property. Process: sub-agent Process tạo hoặc cập nhật node Custom Module Action theo Custom Module Action Task (schema snapshot lấy từ route chi tiết của danh mục), kích hoạt, chạy lượt chạy và đọc $action.<slug>.output (status, result.*, errorCode, errorMessage, runId, durationMs) cùng hiệu ứng nghiệp vụ bằng $object-record; đủ các ca của contract: thành công và bước sau dùng result.*, kết quả dự kiến trong output, INPUT_INVALID (thiếu hoặc sai kiểu), lỗi handler (SCRIPT_ERROR) với continueOnFailure bật và tắt, từng danh tính dùng thật (PROCESS_STARTER, PERSONNEL, SYSTEM có và không có allowInternalSystem), effect: "read" từ chối ghi. AI Agent: sub-agent AI Agent tạo tool CUSTOM_MODULE theo mục CUSTOM_MODULE, gắn vào skill/agent, chat bằng câu hỏi cần action và đối chiếu agent gọi đúng tool với input hợp lệ, câu trả lời dựa trên output; tool "write" sinh yêu cầu phê duyệt, từ chối không tạo hiệu ứng; agent CSKH chạy bằng nhân sự cố định thì kiểm thử quy tắc theo initiatorPersonnelId bằng hai người chat khác quyền. Log Project không được chứa input/output nhạy cảm.
    • Khởi chạy Process/AI Agent: approve policy có mục processes/agents cho đúng version; gọi route/job/action khởi chạy với Process hoặc agent test, ghi instanceId/runId, theo dõi lượt chạy Process theo $process-creator (chi tiết lượt chạy, giá trị resource) hoặc agents.get tới trạng thái cuối, rồi xác nhận job nhận kết quả chạy (cogover-dev jobs runs --job <key>; nguồn enqueuedBy là process:<instanceId> hoặc agent:<runId> đọc qua mục Liệt kê hoặc đọc lần chạy job của Backend API Reference) và hiệu ứng đã đọc lại; gọi lặp cùng idempotencyKey trả duplicate: true; ca bị từ chối theo contract: mục policy thiếu (PROCESSES_NOT_ALLOWED/AGENTS_NOT_ALLOWED), người dùng không có quyền khởi chạy (PROCESS_START_DENIED/AGENT_RUN_DENIED, cần persona theo $user-permission), execution không có người dùng thiếu allowSystem, autoApprove khi chưa bật allowAutoApprove. Có deliverTo: người nhận là personnel test đã biết trước (tin là thật); đọc delivery qua agents.get hoặc job onResult khi lượt chạy ở trạng thái cuối, SENT thì đối chiếu conversationId và người nhận xác nhận thấy tin trong AI chat của agent (conversation: "latest" nối đúng hội thoại gần nhất); thử ca người nhận không tồn tại, không được dùng agent hoặc chưa có seat AI Agent (RECIPIENT_NOT_FOUND/RECIPIENT_ACCESS_DENIED/RECIPIENT_AI_SEAT_REQUIRED, không tạo lượt chạy) và ca instruction cho phép agent không gửi (NOT_SENT). Huỷ lượt chạy test còn dang dở theo quy tắc an toàn của $process-creator.
    • Secret/credential: cogover-dev secrets list có mục ACTIVE đúng tên, loại, host trước khi gọi route; gọi route production và xác minh response không chứa, log không ghi giá trị; trong phạm vi được phép, thử host ngoài danh sách nhận FETCH_BLOCKED rồi khôi phục; xoay giá trị bằng secrets set cùng tên và gọi lại không cần publish. Xoá mục thử nghiệm bằng secrets delete (có xác nhận), giữ mục thật.
    • Crypto với key { secret }: gọi route production với dữ liệu thử, kiểm tra round-trip và ca lỗi như bước 8 trên version đã activate; trao đổi mẫu với bên kia (họ giải mã/xác minh được dữ liệu Cogover gửi, Cogover giải mã/xác minh được mẫu của họ) trước khi dùng dữ liệu thật; response và log không chứa key hay plaintext nhạy cảm. SECRETS_NOT_ALLOWED, TRIGGER_READ_ONLY, ValidationError về key xử lý theo Crypto quick start và mục Key của SDK API reference.
    • Inbound webhook: tạo inbound access theo bước 6A sau activate; gửi request thử tới {URL gốc}/{route} với key (Authorization: Bearer hoặc X-Cogover-Inbound-Key) hoặc chữ ký HMAC đúng cách ký đã cấu hình (có timestamp header thì ký <timestamp>.<raw body>, gửi --data-binary đúng chuỗi đã ký) mong đợi 200/202 và hiệu ứng (state/job/record); ca sai key, sai chữ ký, body đổi sau khi ký, gửi lại cùng chữ ký/timestamp, route ngoài routePrefix, access hết hạn mong đợi 401 trước handler; gửi lại cùng event ID để chứng minh chống trùng; 429 khi vượt 600 request/phút. Bên gửi không cần Workspace session hay header x-req-*; response không có envelope. Thu hồi access thử và unset biến shell chứa key/secret sau khi xong; access thật giữ lại và bàn giao metadata.
    • Notification: kích hoạt qua route, trigger hoặc job trên version active với người nhận là personnel test đã xác nhận (notification và email không thu hồi được); kiểm tra kết quả notified/skippedPersonnelIds, rồi trên trình duyệt đăng nhập đúng người nhận xác minh notification trong danh sách thông báo (tiêu đề, subtitle, nội dung, người gửi, link mở đúng trang), hình thức gửi theo kênh và cài đặt riêng của người nhận, notification nằm đúng chip danh mục khi truyền categories, người trong exclude không nhận; gọi lại cùng idempotencyKey trả duplicate: true và không tạo notification mới. NotFoundError với resource notificationChannel là sai channel ID, với resourceId notification_channel là Workspace chưa thiết lập notification.
    • Email: approve policy có mục email cho đúng version trước khi thử; xác nhận email.senders() trả đúng hộp thư mong đợi; gửi tới địa chỉ test, người dùng xác nhận thư tới nơi (gửi bất đồng bộ nên chờ và kiểm tra lại trước khi kết luận lỗi) và, khi có record, hoạt động email xuất hiện trên timeline record (delivery: "activity") với file đính kèm đúng; ca hộp thư chưa grant nhận EMAIL_SENDER_NOT_GRANTED, execution không có người dùng thiếu allowInternalSystem bị từ chối; gọi lặp cùng key trả duplicate: true.
    • Cơ cấu tổ chức: gọi route production bằng session người dùng thật; org.me() là nhân sự của người dùng sở hữu session, có thể khác nhân sự của Project key dùng ở local. Kiểm tra đủ các ca quyết định của contract: quản lý và người không phải quản lý (isManagerOf true/false), nhân viên/phó phòng/trưởng phòng, nhân sự nhiều phòng ban có và không có departmentId, nhân sự chưa có tài khoản với accountOnly, chuỗi rỗng hoặc có phòng trống; job, webhook và trigger do hệ thống ghi đọc được org khi policy có allowInternalSystem: true. Cần đổi phòng ban hoặc cấp quản lý của nhân sự test để có fixture thì làm qua $user-permission với xác nhận, snapshot và phục hồi sau test; chờ vài giây để thay đổi có hiệu lực.
  8. Frontend theo dạng đã xác nhận, trong browser đã đăng nhập Workspace (CLI publish không đăng nhập browser; link frontend yêu cầu session Workspace, không phải trang công khai vô danh). Sub-agent E2E chạy lại toàn bộ E2E trên URL đã deploy bằng phiên test theo Kiểm thử E2E frontend cho từng persona; E2E PASS trên Workspace là điều kiện nghiệm thu frontend, lỗi giao lại sub-agent phụ trách rồi chạy lại sau mỗi lần publish. Số liệu từ báo cáo: bằng phiên từng persona, giá trị hiển thị khớp service 200 chạy trực tiếp cùng tham số, người ngoài ACL thấy trạng thái không có quyền, người có phạm vi record hẹp chỉ thấy số liệu của phạm vi đó, ghi thời gian tải với dữ liệu đại diện:
    • Single page app: mở https://{WORKSPACE_DOMAIN}/{slugSlot}/index.html, xác minh asset/load/refresh và luồng nghiệp vụ với API thật.
    • Custom component: thêm hoặc cập nhật item Federation component trên layout đích với đường dẫn {slugSlot}/Components/<Tên> khớp chữ hoa/thường với khóa expose; đặt qua $object-layout (mục "Đưa Federation component vào layout": component fieldType: "federation_component", trường federationUrl) hoặc trình sửa layout, rồi view lại layout để xác minh. Mở form thật, chạy lại các ca ở bước 8, lưu và đọc lại record bằng $object-record; thay mọi URL localhost còn sót.
    • Federation Page: mở https://{WORKSPACE_DOMAIN}/{APP_SLUG}/c{N}/{PATH} với N từ slugSlot và App do người dùng chỉ định, đã xác minh qua $app-menu-manager; kiểm tra từng route, refresh, deep-link và quyền. Gắn vào menu ứng dụng bằng mục menu điều hướng tới trang trong Workspace với đường dẫn /{APP_SLUG}/c{N}/{PATH}; dùng API $app-menu-manager chỉ khi contract của skill đó mô tả loại hành động này, nếu không thì cấu hình trên giao diện rồi đọc lại menu.
    • Custom component và Federation Page: sau activate version mới, tải lại cứng, xác nhận không còn lỗi không tìm thấy remote module và asset tải từ /{slugSlot}/assets/.
  9. Xác minh quyền bằng caller phù hợp: session từ Workspace API key không bảo đảm cùng user với Project key và không giả lập được user tùy ý; chưa test user thường thì báo đúng giới hạn. Quy tắc theo Super Admin/role: kiểm thử người gọi là Super Admin, người có role được phép và người không có role (persona test của $user-permission hoặc session của đúng user đó; session tạo từ Workspace API key mang role của user sở hữu key, user đó là Super Admin thì không chứng minh được ca bị từ chối); sau khi gán/gỡ role, HTTP invocation phản ánh ngay còn record trigger và background job có thể chậm khoảng một phút; phục hồi role test tạm. Object do backend quản lý: phối hợp sub-agent Security kiểm chứng action trực tiếp bị từ chối, action được phép (ví dụ View) vẫn đúng phạm vi, đường backend hợp lệ thực hiện được thao tác hệ thống; kiểm thử cả caller không được phép gọi nghiệp vụ. Thành công dưới data.asSystem() không chứng minh caller có quyền CRUD trực tiếp. Quy tắc theo cơ cấu tổ chức (chỉ quản lý của người tạo được duyệt, chỉ thành viên phòng ban được thao tác): kiểm thử bằng session của người thoả điều kiện và của người không thoả, không chỉ bằng user sở hữu Workspace API key. Hoàn tất các ca runtime còn thiếu từ bước 2A trước khi tuyên bố quyền đã được kiểm thử end-to-end.
  10. Hiệu năng (khi thuộc điều kiện của sub-agent Hiệu năng): sau khi kiểm thử chức năng ở mục 6–8 PASS, sub-agent Hiệu năng đo trên version đang active với dữ liệu đại diện và so với ngưỡng trong contract theo Kiểm thử hiệu năng; không chạy load test. Không đạt: sub-agent phụ trách sửa, publish/activate version mới với xác nhận theo mục 1 (commit mới qua review bảo mật phần chênh lệch), chạy lại ca chức năng bị ảnh hưởng rồi đo lại; tối đa 2 vòng sửa, hết 2 vòng vẫn không đạt thì dừng, báo người dùng số đo trước/sau và phương án, không tự nới ngưỡng. Code đổi sau khi đo thì đo lại ca liên quan.
  11. Process: kiểm chứng instance và hiệu ứng nghiệp vụ theo $process-creator, phục hồi lịch và quyền test tạm. Module lỗi sau activate: ưu tiên activate lại version cũ còn hợp lệ trong phạm vi phục hồi và đọc lại xác nhận; rollback version không hoàn tác schema/record writes hay side effect.
  12. Phục hồi cấu hình test tạm, xóa file session export do tác vụ tạo khi dùng xong; fixture Workspace xử lý theo skill dữ liệu và phạm vi đã được phép; báo rõ phần còn lại. Không tự xóa Project, schema hoặc Process để “cleanup”. Kiểm thử, spike hoặc backfill chạm số liệu do module hoặc backend khác duy trì: snapshot record tổng hợp bị ảnh hưởng trước và sau; không sửa tay số liệu đó để đưa về snapshot (record tổng sẽ lệch với tổng record chi tiết còn lại); chứng từ đang giữ số liệu, marker và phần dư không hoàn tác theo Dữ liệu kiểm thử của $object-record.

10. Bàn giao

  • Frontend: project/version đang active, slugSlot, dạng đã xác nhận ở 1A, thư mục source và kết quả browser test. Single page app kèm link /{slugSlot}/index.html. Custom component kèm danh sách expose key, Object/layout đã gắn item Federation component và đường dẫn {slugSlot}/Components/<Tên>. Federation Page kèm URL từng route theo App, mục menu đã gắn và quyền hiển thị. Kèm kết quả E2E của sub-agent E2E: thư mục và lệnh chạy, môi trường/version, persona và nguồn phiên (không kèm credential), số test pass/fail/skip, bảng tiêu chí nghiệm thu → test → kết quả, ca chưa chạy được và lý do.
  • Backend: lệnh cogover-dev auth session --format curl --output ... và cURL hoàn chỉnh tới production theo reference bàn giao, điền domain, slug, method, route, payload đã kiểm thử; không đưa cookie/token thật vào câu trả lời. Kèm response mong đợi và cách tạo lại session khi hết hạn. Route, trigger hoặc job xử lý nhiều dữ liệu: mức dùng ngân sách đo với dữ liệu đại diện (usage của preview hoặc job run) và điểm bàn giao sang job. Có record trigger: bảng trigger (key, Object, timing, operations, fields/changedFields/when/runWhen/writableFields, mã lỗi và message), version đang áp dụng, bằng chứng kiểm thử bằng thay đổi bản ghi thật cho từng ca và cách gỡ/rollback (activate version khác hoặc deactivate). Có push message: liệt kê nơi gọi, loại message, người nhận/exclude và bằng chứng quan sát trên trình duyệt. Có background job: bảng job (key, tên, timeoutMs, maxAttempts, lịch cron/múi giờ hoặc nơi enqueue, quy tắc idempotency key, định dạng payload), version áp dụng, bằng chứng run (run ID, trạng thái, số lần thử, hiệu ứng đã đọc lại) và cách dừng/rollback (bỏ schedule hoặc activate version khác, deactivate). Có Custom Module Action: bảng action (key, label, exposeTo, effect, timeoutMs, input/output chính, danh tính chạy dự kiến, kết quả dự kiến trong output và mã lỗi), version đang áp dụng, Process/node và agent/tool đang dùng (ID, link), bằng chứng lượt chạy hoặc hội thoại cho từng ca, và cách gỡ an toàn (cập nhật node/tool trước, rồi bỏ khỏi actions hoặc deactivate). Có khởi chạy Process/AI Agent: nơi gọi, Process ID/agent ID, mục processes/agents của policy đã duyệt cho version, runAs/approvalPolicy, job nhận kết quả, quy tắc idempotencyKey, bằng chứng lượt chạy và job; có deliverTo thì thêm cách xác định người nhận, conversation, điều kiện gửi nêu trong instruction và bằng chứng delivery. Có secret/credential: tên, loại, header/host được phép, valueVersion, ai giữ giá trị và cách xoay; không kèm giá trị. Có inbound access: inbound ID, name, mode, route prefix, URL gốc, hạn dùng, cách ký của bên gửi và cách rotate/revoke; không kèm inbound key hay HMAC secret. Có crypto: nơi gọi và thao tác (hash/HMAC, AES, RSA, sign/verify), thuật toán/mode/padding/định dạng chữ ký và encoding đã thống nhất với bên kia, tên secret chứa từng key, bên giữ private key/AES key và cách xoay (secrets set cùng tên, phối hợp thời điểm với bên kia); không kèm giá trị key. Có notification: nơi gọi, cách xác định người nhận (org, field record, danh sách cố định) và exclude, channel (record ID, tên kênh), link, người gửi, quy tắc idempotencyKey và bằng chứng quan sát trên danh sách thông báo. Có email: nơi gọi, hộp thư gửi (ID, địa chỉ, dùng chung hay "actor") cùng mục email của policy đã duyệt cho version, người nhận hoặc field email, record ghi nhận timeline, file field đính kèm, quy tắc idempotencyKey, bằng chứng gửi (requestId, delivery, thư nhận hoặc hoạt động trên timeline). Có cơ cấu tổ chức: các hàm org dùng và quyết định nghiệp vụ dựa trên chúng (ai được duyệt, phòng ban nào được xét, accountOnly, directOnly), cách xử lý chuỗi rỗng/phòng trống/truncated, policy allowInternalSystem cho execution không có người dùng và các ca đã kiểm thử.
  • Cả hai: hai mục trên cùng mapping frontend → backend, thư mục source và lệnh build/test/publish để bảo trì.
  • Git: link repository (webUrl trong field git) và commit/tag cuối đã push của từng Project, hoặc lý do chưa lưu được; repository chứa tài liệu chung, nội dung docs/ và e2e/ của từng repository cùng lệnh chạy E2E; xác nhận không còn file dự án nằm ngoài thư mục Project.
  • Báo cáo: khi module dùng số liệu báo cáo, bàn giao theo mục Kiểm thử của Tận dụng báo cáo: saved report (ID, slug, link), Report Type, ACL, payload, nơi gọi và lý do, kết quả theo persona, thời gian tải, chỉ số vẫn quét record kèm lý do.
  • Kiểm thử: báo cáo unit tests và line coverage của từng project theo bước 8, không gộp hai project thành một chỉ số; E2E báo riêng, không cộng vào coverage; nêu rõ sub-agent phụ trách backend/frontend/E2E/Review backend/Review bảo mật/Hiệu năng/Báo cáo/Process/AI Agent đã thực hiện phần nào. Kèm báo cáo review bảo mật theo mục Báo cáo của reference (phát hiện đã sửa, rủi ro được chấp nhận và người quyết định) và, khi thuộc điều kiện, báo cáo hiệu năng theo mục Báo cáo (ngưỡng, số đo, các vòng sửa). Backend ghi dữ liệu kèm báo cáo review và kết quả kiểm thử end-to-end theo Báo cáo của reference.
  • Security: ma trận quyền, mapping bốn slot rule của từng Object mới và rules bổ sung nếu có, action chỉ backend được thực hiện, quy tắc nghiệp vụ theo Super Admin/role trong code (role ID, tên và thao tác áp dụng) và theo cơ cấu tổ chức (quản lý, phòng ban được xét), bằng chứng đọc lại/nghiệm thu của sub-agent Security, kết quả runtime cùng giới hạn còn lại. Phân biệt cấu hình đã xác minh với quyền đã kiểm thử end-to-end.
  • Kèm phần schema đã tạo/tái sử dụng, workbook đã duyệt nếu có, Process/link/instance nếu có, policy/key ở mức metadata và bằng chứng kiểm thử. Báo PASS, PARTIAL, FAIL hoặc BLOCKED theo kết quả quan sát; liệt kê thiếu quyền/dependency/fixture còn lại. Chỉ gọi hoàn tất khi local và Workspace test đáp ứng tiêu chí của bài toán.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.