Mức hoàn thiện — tính năng, bằng chứng và độ phủ Tầng 1
Tài liệu này trả lời một câu hỏi: tính năng nào của Signet đã xong thật, căn cứ vào đâu. Mỗi tính năng trong đặc tả
(dac-ta-san-pham.md §4) và kế hoạch
(du-thi/ke-hoach-phat-trien.md) được xếp một mức, kèm bằng chứng kiểm được trong
repo: tệp test và số hàm test, bước CI, lệnh trình diễn, hạn chế đã biết. Mục 3 đối chiếu với 7 cụm dịch vụ Tầng 1
mà đề liệt kê (du-thi/olp-2026-de-thi.md §2.1), kể cả những năng lực chưa có.
Việc sắp làm nằm ở ROADMAP.md.
Tài liệu lập ngày 23/10/2026 trên nhánh develop, các dòng sửa theo thay đổi về sau; số đếm tĩnh ở mục 1 đếm lại
ngày 31/10/2026 trên develop 23bfadb. Số hàm test của từng tệp ở mục 2 luôn khớp mã, có test kiểm. Tổng ở mục 1 đổi
theo khi thêm, bớt test — mục 4 có lệnh để đếm lại.
Đường dẫn trong bảng tính từ gốc repo. Cột CI ghi job, bước có trong .github/workflows/ci.yml; lượt chạy gần nhất
xanh hay đỏ thì xem trang Actions, tài liệu này không chép lại.
Các mức
| Mức | Điều kiện |
|---|---|
| Hoàn thiện | Đủ cả bốn: (1) có kiểm tự động khẳng định đúng sai — test đơn vị, test trên CSDL thật, hoặc kiểm đầu-cuối có điều kiện dừng (make smoke, make e2e, make demo-*); (2) kiểm đó chạy trong CI trên mọi pull request; (3) trình diễn được bằng một lệnh: make demo-*, kịch bản e2e trên giao diện, hoặc — với phần lõi không có giao diện — lệnh make của chính tính năng hay bước smoke gọi qua cổng APISIX; (4) không còn hạn chế đã ghi nào chặn luồng chính |
| Dùng được | Chạy đầu-cuối và có kiểm tự động trong CI, nhưng thiếu một trong (1)–(3), hoặc còn hạn chế đã ghi buộc người dùng làm ngoài giao diện (gọi API, chạy lệnh, sửa mã) |
| Minh họa | Có mã và có kiểm trong CI, nhưng chủ đích chỉ để chứng minh năng lực: thiết bị hay dữ liệu giả lập, không có giá trị pháp lý, hoặc bản rút gọn so với kế hoạch. Dùng thật phải làm thêm |
| Một phần | Mới có một phần phạm vi kế hoạch mô tả; phần còn lại chưa có mã |
| Chưa làm | Chưa có mã trong repo |
Cột "Test tự động" ghi số hàm test đếm tĩnh của từng tệp (cách đếm ở mục 1). Thêm hay bớt hàm test thì sửa số trong
bảng ở cùng pull request: scripts/tests/test_muc_hoan_thien.py (chạy trong make test) đối mọi "<thư mục> tệp N"
với tệp thật, sai thì báo dòng, số ghi và số thật (#285). "test_x_*.py N" là tổng mọi tệp khớp mẫu, trừ tệp nêu
trong "(trừ …)" ngay sau số. Số chỉ một phần tệp viết "tệp (N hàm về …)" để test bỏ qua.
Một tệp có thể phục vụ nhiều tính năng; bảng gán tệp cho tính năng chính của nó, chỗ dùng lại ghi "đã tính ở …". Viết
tắt thư mục test: svc-files = services/svc-files/tests/, svc-workflow = services/svc-workflow/tests/, db =
db/tests/, scripts = scripts/tests/, office = apps/office/tests/, ca-nhan = apps/ca-nhan/tests/, bang-tin =
apps/bang-tin/tests/, nghi-phep = apps/nghi-phep/tests/, sinh = apps/sinh/tests/, e2e = tests/e2e/; mã
nguồn svc_files/… = services/svc-files/src/svc_files/…, svc_workflow/… =
services/svc-workflow/src/svc_workflow/…. "smoke" là một kiểm của scripts/smoke.py.
1. Số test
Đếm tĩnh ngày 31/10/2026 trên develop 23bfadb (sau bản v1.4.0), bằng các lệnh ở mục 4. Số chạy vẫn lấy từ log của
lần CI 40552387498 trên e32dd1d (27/10/2026, xanh
hết), chưa đếm lại: cây mã lúc đó ít test hơn và chưa có Đổi, bù lịch giảng.
| Phần | Thư mục | Cách đếm tĩnh | Đếm tĩnh (23bfadb) | Chạy trong CI (lần 40552387498, 27/10) | Job CI |
|---|---|---|---|---|---|
| svc-files | services/svc-files/tests/ | grep -rhoE '^\s*(async )?def test_' | 1 043 hàm | 1 691 đạt, 9 bỏ qua | unit (make test) |
| svc-workflow | services/svc-workflow/tests/ | như trên | 506 hàm | 963 đạt | unit (make test) |
| Lệnh vận hành, demo | scripts/tests/ | như trên (phương thức unittest) | 754 hàm | 596 chạy, 594 đạt, 2 bỏ qua | unit (make test) |
| Giao diện Văn phòng điện tử | apps/office/tests/ | grep -hE '^\s*(test|it)\(' | 259 lời gọi | 262 đạt | unit (make test) |
| Không gian cá nhân P.A.R.A | apps/ca-nhan/tests/ | như trên | 20 lời gọi | 20 đạt | unit (make test) |
| Bảng tin đơn vị | apps/bang-tin/tests/ | như trên; def test_ cho tệp Python | 14 lời gọi + 15 hàm | 14 + 15 đạt | unit (make test) |
| Đơn nghỉ phép (#301) | apps/nghi-phep/tests/ | như trên | 33 lời gọi + 12 hàm | 111 + 12 đạt | unit (make test) |
| Đổi, bù lịch giảng (#482) | apps/lich-giang/tests/ | như trên | 32 lời gọi + 12 hàm | chưa có, ra sau lần chạy này | unit (make test) |
| Trang tài liệu API (#357) | apps/tai-lieu-api/tests/ | def test_ | 15 hàm | 15 đạt | unit (make test) |
| CSDL + Hasura thật | db/tests/ | grep -rhoE '^\s*(async )?def test_' | 349 hàm | 583 đạt | db (MOC_IT=1) |
| Trình duyệt thật (Playwright) | tests/e2e/ | grep -hE '^test\(' | 43 test trong 28 tệp *.spec.ts | 49 đạt | chay-thu, phần e2e, bước make e2e |
| Smoke qua cổng APISIX | scripts/smoke.py | grep -cE '^\s*ok\(' | 103 kiểm | 99 kiểm đạt | chay-thu, phần luong-chinh, bước make smoke |
Tổng đếm tĩnh (23bfadb, 31/10): 3 107 test (1 043 + 506 + 754 + 259 + 20 + 14 + 15 + 33 + 12 + 32 + 12 + 15 + 349 +
43), cộng 103 kiểm của smoke; lúc chạy trong CI (lần 40552387498, 27/10): 4 329 test đạt (1 691 + 963 + 594 + 262 +
20 + 14 + 15 + 111 + 12 + 15 + 583 + 49 — scripts tính 594 vì 2 test bỏ qua), cộng 99 kiểm smoke. Trên cùng cây mã, số
lúc chạy lớn hơn số đếm tĩnh vì pytest.mark.parametrize và test sinh trong vòng lặp nhân một hàm thành nhiều ca; smoke
đạt ít hơn số ok( vì vài kiểm nằm trong nhánh if, chỉ chạy khi đủ điều kiện. Test của scripts chỉ bỏ qua có điều
kiện (skipIf, skipUnless: thiếu công cụ, thư viện hay thư mục). 9 test bỏ qua của svc-files là
integration/test_live.py (chỉ chạy khi đặt MOC_IT=1 và khóa JWT thử — job unit không đặt, nên 9 test này không
chạy ở đâu tự động). Không gán cho tính năng nào: svc-files test_hasura.py 20 (máy khách Hasura dùng chung),
integration/test_live.py 9, office format.test.ts 6, paging.test.ts 4.
2. Ma trận tính năng
2.1. Lõi Tầng 1 (Phase 1)
| Mã | Chức năng | Mức | Test tự động | Đầu-cuối trong CI · trình diễn | Hạn chế đã biết |
|---|---|---|---|---|---|
| F1.1 | SSO + MFA: Keycloak, vai trò, tenant_id và department_id trong token | Hoàn thiện | svc-files test_auth_config.py 26 (kiểm token Keycloak, biến môi trường của dịch vụ); scripts test_dang_nhap.py 4 | smoke: 5 tài khoản đăng nhập qua cổng, văn thư phải nhập TOTP, luồng mật khẩu của moc-cli và admin-cli cũng đòi TOTP; e2e: mọi kịch bản đăng nhập qua Keycloak kèm TOTP (tests/e2e/helpers.ts) | Realm chỉ nạp ở lần khởi động đầu: sửa platform/keycloak/realm/moc-realm.json sau đó phải make reset hoặc sửa trong trang quản trị Keycloak. Xác thực hai bước chỉ có TOTP |
| F1.2 | Cổng API zero-trust: APISIX, danh sách trắng đường dẫn, kiểm JWT ở biên, giới hạn tốc độ, X-Request-Id | Hoàn thiện | scripts test_mang_ung_dung.py 8 (container ứng dụng chỉ ở mạng apps cùng APISIX, không service lõi nào khác ở đó; cổng hook 9081 có trong node_listen, không publish, route hook chỉ khớp ở cổng đó); route khai báo trong platform/apisix/apisix.yaml, kiểm bằng smoke | smoke: GraphQL không token → 401; đường quản trị Hasura, Keycloak, Flowable bị chặn; khóa quản trị Hasura gửi từ ngoài bị gỡ; đăng nhập quá giới hạn theo IP, GraphQL quá giới hạn theo phiên → 429; tải tấn công bị WAF chặn ở cổng → 403 (F3.15); bước CI make demo-cai-ung-dung bước 5: container app-bang-tin chỉ mở được kết nối tới apisix:9080, POST /su-kien/bang-tin ở cổng công khai → 404 | WAF ở cổng (F3.15) mới ở mức Dùng được: chữ lạ ở các ô chữ ngắn (Cơ quan ban hành, Nơi nhận…) có thể bị chặn nhầm, khi đó người quản trị thêm loại trừ trong apisix.yaml |
| F1.3 | Dữ liệu có cấu trúc: PostgreSQL 16 + Hasura v2 CE, GraphQL tự sinh, quyền theo vai trò và đơn vị tới từng dòng, migrations và metadata tự áp | Hoàn thiện | db test_schema.py 15, test_permissions.py 23 | job db; smoke: văn thư Trường Kinh tế không thấy văn bản Trường CNTT & TT, không tải được tệp (404); văn thư không ghi thẳng được bảng văn bản | svc-files, svc-workflow giữ khóa quản trị Hasura (lấy từ két): quyền cấp dòng không giới hạn chính hai dịch vụ này |
| F1.4 | Quy trình BPMN: Flowable REST 8, Hasura Actions → svc-workflow, callback, job đối soát | Hoàn thiện | svc-workflow test_workflow.py 32, test_app.py 8, test_so_do.py 9; office processDiagram.test.ts 8 | smoke: vào sổ khởi chạy quy trình → chỉ đạo → phân công → báo xong, dòng thời gian đủ 5 bước từ tiếp nhận; e2e van-ban-den.spec.ts (cả mục Sơ đồ quy trình: bước chỉ đạo đã qua, bước phân công đang chờ), van-ban-di.spec.ts | Đơn vị gắn vào quy trình bằng biến tenantId và nhóm ứng viên t:<đơn vị>:…, không dùng tenant của Flowable. Lệch giữa Flowable và CSDL chỉ được sửa ở lượt đối soát kế tiếp (MOC_RECONCILE_INTERVAL_S) |
| F1.5 | Dữ liệu phi cấu trúc: SeaweedFS, tải lên và tải xuống chỉ qua svc-files | Hoàn thiện | svc-files test_upload.py 18, test_download.py 10 | smoke: văn thư tải công văn lên, quét sạch; đơn vị khác tải tệp → 404 | Một bucket chung, đơn vị tách bằng tiền tố tenant/<id>/ — có chủ ý, lý do ở docs/mo-hinh-du-lieu.md |
| F1.6 | Trạm kiểm dịch: ClamAV quét trước khi ghi | Hoàn thiện | svc-files test_clamav.py 8 | smoke: EICAR → 422, không lưu, lần chặn vào sổ kiểm toán; e2e kiem-dich.spec.ts; make demo-kiem-dich | ClamAV chiếm khoảng 0,9 GiB RAM (docker stats trên bản 1.3.0; README, mục Yêu cầu hệ thống), lên 1,9 GiB trong khoảng 10 giây lúc nạp lại chữ ký (#288) |
| F1.7 | Tìm kiếm toàn văn: Meilisearch qua /api/search, lọc đơn vị ở máy chủ | Hoàn thiện | svc-files test_search.py 34 | e2e van-ban-den.spec.ts: tìm lại bằng chữ không dấu | Xem F2.7 |
| F1.8 | Cài ra ngoài thư mục mã nguồn: make install PREFIX=… | Hoàn thiện | — (không có test đơn vị cho scripts/install.sh) | bước CI "Cài ra ngoài thư mục mã nguồn và chạy từ đó (make install)": cài sang thư mục tạm, dựng lại từ đó, chạy smoke | — |
| F1.9 | Lên mạng: HTTPS sau proxy (MOC_TRUSTED_PROXY), Cloudflare Tunnel tùy chọn | Hoàn thiện | scripts test_duong_ham.py 13 (kiểm chữ cấu hình tunnel, route Keycloak sau proxy, MOC_TRUSTED_PROXY=auto chỉ tin mạng moc), test_kiem_ban_online.py 24 (site giả) | Job .github/workflows/ban-online.yml kiểm bản online https://signet.htechcs.com hằng ngày và ngay sau mỗi lần phát hành, bản dev theo develop https://signet-dev.htechcs.com hằng ngày (đúng commit đầu develop) (scripts/kiem_ban_online.py: chứng chỉ, HTTP sang HTTPS, /api/status đúng thẻ phát hành mới nhất, URL của Keycloak sau proxy, header bảo mật); diễn tập tay 22/10/2026 trên máy phát triển (CHANGELOG [1.0.0]); phần CI https trên mọi pull request (#261): .env như docs/trien-khai.md bước 3, proxy TLS nginx với chứng thư tự ký như bước 5, rồi kiem_ban_online.py, smoke trọn qua https://, nhật ký APISIX ghi IP của máy gọi; với MOC_TRUSTED_PROXY=auto, X-Forwarded-For giả từ container ở mạng apps bị bỏ (#211); hướng dẫn docs/trien-khai.md | Máy chạy bản online tắt, hoặc bản online chưa nâng lên thẻ mới (quá 60 phút sau khi phát hành, hay tới lượt mỗi sáng), thì job đỏ. CI dùng nginx với chứng thư tự ký thay Caddy và Let's Encrypt, không kiểm Cloudflare Tunnel |
| F1.10 | Tài liệu API xuất tự động (docs/api/), kèm trang API REST đọc được sinh từ OpenAPI (rest-*.md) | Hoàn thiện | scripts test_api_docs.py 8 (trang REST trong git khớp OpenAPI trong git; gói dòng, ô bảng, kiểu dữ liệu) | bước CI "Tài liệu API trong docs/api/ khớp mã (make api-docs)": scripts/api_docs.py --check so tài liệu với hệ thống vừa dựng | — |
| F1.11 | Ứng dụng sinh từ bảng: Studio, postgres-meta của Supabase và PostgREST trên schema sinh, khung SQL (cột chuẩn lấy từ token, RLS theo đơn vị, sổ kiểm toán, chặn lệnh gỡ bảo vệ), trang /sinh dựng biểu mẫu từ OpenAPI (QĐ-014); báo cáo khai báo bằng dữ liệu (sinh.bao_cao, hàm gộp của PostgREST, #587); mẫu in lưu thành dữ liệu, svc-files in ra PDF (sinh.mau_in, #586) | Dùng được | db test_sinh.py 15; scripts test_cong_rest.py 3; sinh openapi.test.js 7, i18n.test.js 7, test_server.py 6, bao-cao.test.js 9, in.test.js 3; svc-files test_sinh_print.py 11 | phần e2e của chay-thu chạy scripts/kiem_sinh.py (#568) trên hệ thống dựng thật, WAF bật: bảng tạo qua postgres-meta có ngay trên /rest/v1, không token 401, cột chuẩn theo token, hai đơn vị không thấy, không sửa, xóa được dòng của nhau, lệnh gỡ bảo vệ bị hủy, PATCH, DELETE không bộ lọc bị cổng chặn 403, không dòng nào đổi (#588), đếm, tổng, trung bình theo nhóm chỉ gồm dòng của đơn vị (#589), in ra PDF, đơn vị khác 404 (#594); trình diễn: màn "Ngoài 4 nguyên lý" của demo-script.md, làm tay, chưa có make demo-* | Profile studio tắt mặc định; chỉ người vận hành nền tảng dùng Studio, qua đường hầm SSH (Studio là chủ bảng, thấy dòng mọi đơn vị); bảng tạo trong Studio không nằm trong git; chưa có quy trình duyệt, tệp đính kèm, thông báo; trang /sinh chưa xóa dòng, chưa phân trang, lọc, tìm; RLS là cơ chế mới, chỉ bảo đảm trên /rest/v1; chặn PATCH, DELETE không bộ lọc chỉ có khi MOC_WAF=On; báo cáo một cột nhóm, một phép tính, chưa xuất tệp; mẫu in là văn bản thuần, chỉ bảng có khóa chính id số nguyên (them-ung-dung.md mục 8.5–8.7) |
2.2. Văn phòng điện tử — Must (Phase 2)
| Mã | Chức năng | Mức | Test tự động | Đầu-cuối trong CI · trình diễn | Hạn chế đã biết |
|---|---|---|---|---|---|
| F2.1 | Đăng nhập SSO; tài khoản dùng thử giamkhao (chuyên viên, không TOTP) | Hoàn thiện | scripts test_seed_mau.py 4 (giám khảo có sẵn việc); svc-workflow test_directory.py 6 (danh bạ đồng bộ từ Keycloak) | e2e giam-khao.spec.ts; smoke: chuyên viên đúng mật khẩu là vào | apps/office không có trang hồ sơ cá nhân riêng |
| F2.2 | Tiếp nhận văn bản đến: Poka-yoke 3 lớp theo schemas/van-ban-den.json, chặn vào sổ trùng | Hoàn thiện | office forms.test.ts 6; svc-workflow test_intake.py 17; db test_intake.py 5 — ba lớp chạy chung bộ ca thử schemas/van-ban-den.cases.json | smoke: lỗi trả về từng trường, chặn tiếp nhận trùng, ràng buộc CHECK chặn cả ghi bằng quyền quản trị; e2e van-ban-den.spec.ts: form sai bị chặn trước khi gửi | JSON Schema dùng chung mới có cho form tiếp nhận văn bản đến |
| F2.3 | Vào sổ, chỉ đạo (chủ trì / phối hợp), phân công, xử lý — BPMN van-ban-den; nhắc hạn, báo lãnh đạo | Hoàn thiện | svc-workflow test_sla.py 15 (luồng: test_workflow.py, đã tính ở F1.4); db test_registration.py 11 | smoke: đủ các bước; CI đặt MOC_SLA_DURATION=PT20S → nhắc người làm rồi báo lãnh đạo; e2e van-ban-den.spec.ts | Thời hạn mỗi bước đếm theo đồng hồ (PT24H), không trừ ngày nghỉ |
| F2.4 | Văn bản đi: soạn, duyệt, ký, cấp số, phát hành PDF có mã QR và SHA-256 — BPMN van-ban-di | Hoàn thiện | svc-files test_outgoing.py 28; svc-workflow test_outgoing.py 28; db test_outgoing.py 15 | smoke: soạn → trình → duyệt → ký → phát hành, mã QR, SHA-256 khớp CSDL; e2e van-ban-di.spec.ts (cả trả lại, trình lại) | Một khuôn thể thức viết trong mã (services/svc-files/src/svc_files/render.py); quản trị đơn vị chưa tự sửa mẫu được |
| F2.5 | Hộp việc + thông báo realtime từng người (Centrifugo) | Hoàn thiện | svc-workflow test_notify.py 14, test_subscriptions.py 16; db test_notifications.py 6 | smoke: token giả bị đóng kết nối, tin việc mới tới qua WebSocket, thông báo lưu đúng người; e2e thong-bao.spec.ts (hai trình duyệt cùng lúc) | Kênh ngoài (email, webhook, SMS, Zalo) tắt mặc định, xem ⑦ ở mục 3; SMS, Zalo chỉ tới người tự bật cuối chuông |
| F2.6 | Dòng thời gian văn bản (office.document_events) | Hoàn thiện | Các test CSDL đọc office.document_events: db test_registration.py, test_outgoing.py, test_dispatch.py… (đã tính ở F2.3, F2.4, F3.12) | smoke: dòng thời gian đủ 5 bước từ tiếp nhận (sự kiện office.document.received, #381); e2e lien-thong.spec.ts kiểm dòng thời gian bên nhận | — |
| F2.7 | Tìm theo số ký hiệu, trích yếu, cơ quan ban hành và nội dung PDF: lớp chữ, bản scan qua OCR (#253) | Hoàn thiện | svc-files test_pdftext.py 22, test_texts.py 17 (test_search.py đã tính ở F1.7); db test_file_texts.py 7 | smoke: tìm ra công văn theo chữ chỉ có trong tệp PDF; bản scan 3 trang tìm ra theo chữ chỉ có trong ảnh, chữ OCR giữ ranh giới trang; tệp văn bản hạn chế, kể cả bản scan, không bị đọc chữ; e2e van-ban-den.spec.ts | Bản scan chỉ vào chỉ mục chữ OCR của MOC_TEXT_OCR_PAGES trang đầu (mặc định 3), có thể sai chữ, sai dấu; OCR chạy lần lượt từng tệp, nhiều bản scan cùng lúc thì chữ PDF mới phải chờ; OCR hỏng vì máy (quá giờ lúc máy bận) 5 lượt liền thì bản scan đó giữ empty, không tự đọc lại. Lớp chữ của văn bản đã nộp lưu rời chỉ mục (F3.8) |
| F2.8 | Sổ đăng ký văn bản đến / đi theo năm, in PDF theo Phụ lục IV NĐ 30/2020 | Hoàn thiện | svc-files test_registers.py 39 | smoke: sổ liền số, lần in vào sổ kiểm toán, chuyên viên → 403; e2e so-dang-ky.spec.ts | — |
| F2.9 | Giao diện tiếng Việt / tiếng Anh | Dùng được | office: một test trong answer.test.ts kiểm vi.json, en.json cùng khóa, cùng tham số (1573 khóa mỗi bên); i18n.test.ts 4 (quét mọi tệp app/: khóa t('…') có thật, phần viết thẳng của khóa ghép có thật, không chữ tiếng Việt viết cứng trong template và script); ca-nhan i18n.test.js 7, bang-tin i18n.test.js 7 (#306: vi và en cùng khóa, cùng tham số; không chữ tiếng Việt viết cứng trong public/*.js; chữ tĩnh của index.html khớp bảng dịch; ngôn ngữ lưu chung khóa moc.locale với Văn phòng điện tử); nghi-phep i18n.test.js 9 (#398: cùng các kiểm đó, cộng form dựng từ forms/*.json hiện nhãn, lựa chọn, gợi ý, câu báo lỗi bằng tiếng Anh, dựng lại khi đổi ngôn ngữ vẫn giữ chữ đang gõ); svc-files test_registers.py (6 hàm về sổ đăng ký tiếng Anh: hai bảng chữ cùng khóa, cùng số cột; chỉ chữ cố định được dịch, chữ người dùng nhập giữ nguyên; mọi loại văn bản nạp sẵn có tên tiếng Anh; ?lang=en qua API, ngôn ngữ khác → 422); svc-files test_outgoing.py (6 hàm về văn bản đi tiếng Anh: hai bảng chữ cùng khóa; ngày kiểu Anh, địa danh giữ nguyên; quốc hiệu, số, nơi nhận, khối xác thực bằng tiếng Anh, chữ người soạn gõ giữ nguyên; công văn "Re:", "To:"; dự thảo "DRAFT"; đọc cột language, giá trị lạ về tiếng Việt); db test_outgoing.py (1 hàm: mặc định vi, người soạn đổi được tới lúc trình, fr bị chặn) | job unit; e2e tieng-anh.spec.ts (3 test): bấm nút đổi ngôn ngữ trong apps/office/app/layouts/ → menu, danh sách văn bản đến và đi, soạn và lưu dự thảo (ngôn ngữ văn bản English, tải được bản xem trước PDF), trang văn bản (dòng thời gian, tab Lịch sử) bằng tiếng Anh, tải lại vẫn giữ (cả thẻ <html lang>), bấm "Tiếng Việt" thì về lại; trang công khai (trạng thái hệ thống, cổng xác thực) đổi được không cần đăng nhập; Không gian cá nhân bấm "English" → thư mục, tiêu đề bằng tiếng Anh, tải lại vẫn giữ, Bảng tin và Văn phòng điện tử mở ra cũng tiếng Anh, về lại tiếng Việt ở Văn phòng điện tử thì Không gian cá nhân về theo | Văn bản đi tiếng Anh (#398) chỉ dịch phần cố định: tên đơn vị, cơ quan chủ quản, địa danh, chức danh người ký in như đã khai (thường là tiếng Việt), tên loại do đơn vị tự thêm giữ tên đã khai; cổng xác thực vẫn hiện tên loại tiếng Việt |
| F2.10 | Dữ liệu mẫu make seed, tạo qua API như người dùng thật; dữ liệu công khai (#424): bản chụp thông tư Bộ GD&ĐT từ Công báo trong seed/du-lieu-mo/, nạp qua API nạp lô (?open_data=: tệp đối chiếu manifest trên máy chủ, lượt nạp có phả hệ OpenLineage, #445), /api/status báo tuổi dữ liệu | Hoàn thiện | scripts test_seed_mau.py (đã tính ở F2.1), test_du_lieu_mo.py 18 (bản chụp khớp manifest và khuôn hợp đồng; sai băm, thiếu tệp, tên tệp lạ là hỏng; quá hạn là cũ; lấy lỗi giữ bản cũ; đọc trang Công báo; lô sinh ra qua bộ đọc của svc-files); svc-files test_open_data.py 9 | bước CI "Nạp dữ liệu mẫu (make seed)" trước smoke và e2e (cả bốn thông tư qua API nạp lô); e2e giam-khao.spec.ts dựa vào dữ liệu seed | 2 đơn vị, 18 văn bản đến và 7 văn bản đi giả lập (danh sách CNTT, KT, OUT_CNTT trong scripts/seed_documents.py) cộng 4 thông tư thật; kế hoạch là ~50 văn bản lấy từ công văn đã công bố. Danh sách thông tư chọn tay, lấy mới bằng tay ở máy của đội |
| F2.11 | Cộng đồng | Một phần | — | README.en.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md, mẫu issue .github/ISSUE_TEMPLATE/ | Repo chưa ghi đơn vị chạy thử nào; bài giới thiệu không kiểm được từ mã nguồn |
| F2.12 | Sổ kiểm toán chuỗi băm, neo đầu chuỗi ra bucket khóa ghi, make audit | Hoàn thiện | db test_audit.py 23; svc-files test_anchors.py 28; scripts test_moc_neo.py 34 | smoke: moc audit verify khớp mốc neo, kiểm chữ ký mốc chép ra tệp, mốc neo không xóa được (S3 Object Lock); bước CI sao lưu → xóa → khôi phục → make audit; e2e so-kiem-toan.spec.ts (2 test) | Mốc neo nằm trên cùng máy chủ (bucket lạnh của SeaweedFS); bản ngoài máy chủ có chữ ký của két: moc audit anchor --publish đẩy lên kho git công khai (timer mẫu moc-anchor-publish), nhưng kho công khai, khóa triển khai do đơn vị tự tạo |
| F2.13 | Cổng xác thực công khai: tra mã hoặc quét QR; thả PDF, trình duyệt tự tính SHA-256 | Hoàn thiện | svc-files test_outgoing.py (đã tính ở F2.4); office verify.test.ts 7 | smoke: tra mã in cạnh QR ra đúng văn bản, giới hạn theo IP; tệp nguyên vẹn khớp, sửa một byte không khớp; e2e van-ban-di.spec.ts (người ngoài, không đăng nhập) | Là trang /xac-thuc trong apps/office (layout public), không phải ứng dụng apps/verify riêng như đặc tả §5 |
2.3. Should (Phase 3)
| Mã | Chức năng | Mức | Test tự động | Đầu-cuối trong CI · trình diễn | Hạn chế đã biết |
|---|---|---|---|---|---|
| F3.1 | (iv) Màn kiểm dịch | Hoàn thiện | db test_audit.py (dòng file.rejected), svc-files test_clamav.py (đã tính ở F2.12, F1.6) | smoke: EICAR bị chặn, lãnh đạo thấy lần chặn trong sổ kiểm toán; e2e kiem-dich.spec.ts; make demo-kiem-dich | make demo-kiem-dich không phải bước CI — smoke và e2e đã phủ cùng luồng |
| F3.2 | (iv) Két bí mật OpenBao: static seal, AppRole, Transit | Hoàn thiện | svc-files test_openbao.py 9; svc-workflow test_openbao.py 9 | bước CI make demo-ket-bi-mat; smoke: két đã mở, dịch vụ không có bí mật trong biến môi trường, AppRole không đọc chéo | .env vẫn là nguồn bí mật, khóa mở két nằm trong .env; PostgreSQL, Keycloak, Flowable, Hasura, SeaweedFS, Meilisearch, Centrifugo (và Parseable, Scorpio, restic khi bật profile) vẫn đọc bí mật từ biến môi trường; két chưa có TLS (docs/van-hanh.md, mục Két bí mật) |
| F3.3 | (iv) GitOps + tự phục hồi: scripts/reconcile.py, systemd timer hoặc cron | Hoàn thiện | scripts test_gitops_ky.py 4 (chỉ áp commit đã ký), test_gitops_profile.py 3 (profile thêm của bản cài) | bước CI make demo-tu-phuc-hoi và "GitOps — reconciler áp commit mới, sửa lệch cấu hình"; smoke: một lượt reconciler không có gì phải sửa | Compose + reconciler, không Argo CD; áp một commit là cài lại toàn bộ, Signet dừng khoảng một phút; image cũ không tự xóa; .env không do git quản |
| F3.4 | Sao lưu, khôi phục (restic) | Hoàn thiện | db test_backup.py 3 | bước CI: hai lượt sao lưu, xóa volume CSDL, tệp và két, khôi phục, smoke, make audit | Kho sao lưu nằm trên cùng máy chủ, chép ra ngoài là việc của người vận hành (docs/trien-khai.md); lịch sử đo NGSI-LD không vào bản sao lưu |
| F3.5 | Tab "Lịch sử" trên trang văn bản, đọc từ sổ kiểm toán | Hoàn thiện | db test_document_history.py 3 | e2e van-ban-den.spec.ts (tab Lịch sử), giao-viec.spec.ts | Trường nội dung chỉ báo "đã đổi", không hiện chữ trước và sau |
| F3.7 | Văn bản hạn chế: mã hóa từng tệp, giải mã trong bộ nhớ, dấu tên người xem | Hoàn thiện | svc-files test_sealed.py 34; svc-workflow test_restricted.py 16; db test_restricted.py 14 | bước CI make demo-han-che; smoke 8 kiểm; e2e han-che.spec.ts | Chỉ nhận PDF; không dùng cho bí mật nhà nước; ai có quyền Docker trên máy chủ đọc được két (docs/van-hanh.md) |
| F3.8 | (i) Lập hồ sơ, nộp lưu nóng → lạnh: Parquet + tệp + manifest có băm, khóa ghi; phòng đọc xem tệp; phân tích trên dữ liệu lạnh: DuckDB đếm văn bản đã nộp lưu theo năm, loại, phòng trên các gói Parquet, hiện ở trang Điều hành (#304) | Hoàn thiện | svc-files test_archive.py 19, test_reading_room.py 22, test_archive_stats.py 17; db test_case_files.py 8; office caseFiles.test.ts 10, archiveStats.test.ts 5 | bước CI make demo-nong-lanh (có xem tệp trong phòng đọc; bước 5 đọc lại gói vừa nộp lưu qua GET /api/case-files/archive-stats: số đếm tăng đúng số văn bản của hồ sơ, chuyên viên bị 403, phả hệ ghi lần đọc); smoke: nộp lưu qua POST /api/case-files/{id}/archive (chuyên viên bị 403), gói trên bucket lạnh, cổng xác thực vẫn "khớp", xem tệp có dấu người xem + dòng sổ kiểm toán; e2e ho-so.spec.ts (chuyên viên lập, đóng; tổ trưởng nộp lưu; văn thư xem tệp; hết văn bản thì đỏ, không bỏ qua) | Nộp lưu trên giao diện chạy đồng bộ trong một yêu cầu (cổng chờ 60 giây), chưa đo với hồ sơ lớn; quá 60 giây thì cổng trả 504 trong khi svc-files có thể vẫn nộp lưu xong — tải lại trang để xem trạng thái hồ sơ — job nền mỗi giờ vẫn là đường chính cho hồ sơ đóng quá hạn. Phòng đọc chỉ xem được tệp PDF; tệp khác chỉ thấy tên, băm. Bản xem có dấu người xem nhưng người xem vẫn lưu lại được (như F3.7). Phân tích kho lạnh đọc lại mọi gói mỗi khi có hồ sơ mới nộp lưu (giữa hai lần thì dùng kết quả lưu đệm), chưa đo với hàng nghìn gói; chỉ lãnh đạo đơn vị xem, như các biểu đồ nóng |
| F3.9 | (iii) Phả hệ OpenLineage + kiểm chất lượng bảng Parquet; phả hệ báo cáo tổng hợp (biểu đồ ← khung nhìn ← bảng, tính từ catalog PostgreSQL) và trang xem đồ thị; cờ chất lượng số liệu (Đạt / Cảnh báo / Trượt): 5 luật tính trong CSDL trên khung nhìn báo cáo, cờ nộp lưu theo assertion đã ghi | Hoàn thiện | svc-files test_archive.py (6 test phả hệ, đã tính ở F3.8); db test_lineage.py 2, test_report_lineage.py 6; office lineage.test.ts 14 (khai báo biểu đồ → khung nhìn khớp truy vấn thật của trang Điều hành, cả hai chiều, dựng và xếp đồ thị, lượt nạp nguồn công khai, xuất OpenLineage); scripts test_pha_he.py 4; db test_report_checks.py 7 (từng luật với số biết trước, cảnh báo, giả lập view hỏng, cột cá nhân qua catalog kể cả view lồng, quyền, khớp khai báo của office); office quality.test.ts 8 (cờ của biểu đồ, assertion nộp lưu lấy nguyên văn, cờ cạnh đúng từng nút Nguồn số liệu, i18n) | bước CI make demo-pha-he (bước 5: phả hệ báo cáo); smoke 5 kiểm (2 của cờ chất lượng: không luật nào Trượt trên dữ liệu luồng chính, văn thư không đọc được); e2e pha-he.spec.ts (nút "Nguồn số liệu" → trang Phả hệ; cờ cạnh từng biểu đồ, mục kiểm chất lượng) | Chỉ lượt nộp lưu và lượt nạp nguồn công khai (#445) phát sự kiện phả hệ, trang chỉ vẽ lượt gần nhất (lượt nạp: gần nhất mỗi nguồn); chưa dựng Marquez — tệp OpenLineage xuất ở trang (JobEvent cho khung nhìn, biểu đồ) chưa thử nạp vào Marquez; phả hệ báo cáo mức bảng, chưa mức cột; view gọi hàm SQL đọc bảng thì catalog không ghi cạnh đó (hiện không view v_ nào làm vậy, test chặn); cạnh biểu đồ → khung nhìn vẫn khai trong mã giao diện; cờ chất lượng: luật cột cá nhân theo tên cột (cột đặt tên lạ không bắt được, khóa người dùng user_id, created_by không tính), status_total, unique_keys hôm nay đạt theo cấu trúc (ràng buộc, GROUP BY — luật chặn hồi quy), ngưỡng 0 cố định, không lưu lịch sử cờ, không báo chủ động khi một luật chuyển Trượt; nộp lưu chỉ có cờ của lượt gần nhất và lượt bị dừng gần nhất |
| F3.10 | (ii) IT/OT rút gọn: cảm biến → NGSI-LD (Scorpio) → dự thảo "Báo cáo sự cố"; sơ đồ cảm biến kho (/cam-bien, SVG) với bốn cờ chất lượng dữ liệu OT | Minh họa | svc-workflow test_ot.py 23, test_ot_map.py 24 (cờ mất tín hiệu, ngoài dải, trùng observedAt, observedAt ở tương lai; chỉ đọc tenant của người gọi; broker tắt, lỗi → unavailable không 5xx; quyền vai trò khớp actions.yaml; kết quả khớp kiểu GraphQL; sensor.py --room, --late, --frozen-clock, --clock-ahead); office otMap.test.ts 9 | bước CI make demo-it-ot (profile iot; bước 7: năm cảm biến kho ra đúng cờ, đơn vị khác không thấy, quản trị đơn vị không gọi được Action) | Cảm biến giả lập (services/ot-sensor/sensor.py); broker không xác thực thiết bị; broker gửi mỗi thông báo một lần; lịch sử đo không vào bản sao lưu (docs/van-hanh.md, mục Hội tụ IT/OT). Sơ đồ mặt bằng mẫu cố định trong mã giao diện, chung cho mọi đơn vị; dải đo −55…125 °C cố định, không theo từng cảm biến; cờ chỉ trên sơ đồ — số đo vượt ngưỡng vẫn dựng dự thảo kể cả khi ngoài dải; tối đa 100 cảm biến mỗi đơn vị |
| F3.11 | [D] Dashboard điều hành (ECharts, bốn view số đếm qua Hasura; thêm phần văn bản đã nộp lưu đọc từ kho lạnh) | Hoàn thiện | db test_dashboard.py 6; phả hệ và cờ chất lượng từng biểu đồ đã tính ở F3.9; phần kho lạnh đã tính ở F3.8 | smoke 2 kiểm (số chia khớp văn bản lãnh đạo đọc được, văn thư không đọc được); e2e dieu-hanh.spec.ts, pha-he.spec.ts (nguồn số liệu, cờ chất lượng của biểu đồ) | Bốn view cố định (office.v_*); muốn báo cáo mới phải viết view mới, và khai biểu đồ mới ở apps/office/app/utils/lineage.ts (test đỏ nếu quên); cờ chất lượng là một truy vấn thứ hai đọc lại bốn view, hỏi sau khi biểu đồ có số (không làm chậm biểu đồ, cờ hiện sau): đo trên PostgreSQL 16 thử 2 CPU, đơn vị 2 000 văn bản đến — số liệu 19 ms, cờ 36 ms; 20 000 — số liệu 158–520 ms, cờ 167–313 ms (dao động giữa các lần; phần lớn là v_processing_time có sẵn) |
| F3.12 | Liên thông văn bản giữa các đơn vị; thêm đơn vị bằng một lệnh | Hoàn thiện | svc-files test_dispatch.py 12; svc-workflow test_dispatch.py 28; db test_dispatch.py 16; scripts test_tenants.py 32; office dispatch.test.ts 4 | bước CI make demo-lien-thong và "Thêm đơn vị bằng một lệnh (moc tenant create, check)"; smoke: mỗi đơn vị một Organization, công văn tới đơn vị nhận; e2e lien-thong.spec.ts | Chưa gửi lại được dòng liên thông failed; danh sách đơn vị nhận hiện cho mọi người dùng; văn bản hạn chế không liên thông (chủ ý) |
| F3.13 | Giám sát: Prometheus + Grafana, cảnh báo (#255) gửi ra chuông qua Alertmanager (#284), bảng trạng thái thành phần (#274) | Dùng được | luật cảnh báo platform/monitoring/kiem-tra/canh-bao.test.yml 8 ca (promtool test rules, job build), cấu hình Alertmanager (amtool check-config, job build); svc-files test_status.py (4 hàm cho GET /metrics); svc-workflow test_alerts.py 10; office notifications.test.ts 5 (câu, đường dẫn của cảnh báo, của flow Node-RED #366; chuông hiện cho quản trị đơn vị) | smoke 4 kiểm: Prometheus đọc được chỉ số cổng API, Grafana có bảng điều khiển nạp sẵn; Prometheus nạp và tính được 8 luật cảnh báo; Prometheus nối Alertmanager; mỗi thành phần đang chạy của /api/status có đúng một dòng moc_component_up = 1, /metrics qua cổng → 404. Bước CI "Cảnh báo vận hành ra chuông (make demo-canh-bao)" (chay-thu, phần luong-chinh): cảnh báo giả tới chuông của quản trị cả ba đơn vị, hết cảnh báo thêm một thông báo, token sai → 401, svc-files không tới được API của Alertmanager | Thu chỉ số của APISIX và trạng thái từng thành phần (platform/monitoring/prometheus.yml), chưa có chỉ số bên trong từng dịch vụ (số yêu cầu, độ trễ của svc-files, svc-workflow). Lên Hoàn thiện khi chọn người nhận, tắt tạm một cảnh báo (silence) làm được trên giao diện và lỗi gửi cảnh báo có cảnh báo riêng. Hiện cảnh báo tới mọi quản trị đơn vị; silence phải gọi API của Alertmanager (amtool trong container); PostgreSQL, Hasura, svc-workflow gián đoạn thì cảnh báo về chính sự cố đó chưa vào chuông được (Alertmanager gửi lại khi chúng chạy lại); gửi lỗi (token sai) chỉ thấy trong log của Alertmanager. Trợ lý AI không có cảnh báo (Ollama không trả lời thì báo Tắt, như chưa bật) |
| F3.14 | Log tập trung (Vector, Parseable) | Dùng được | svc-workflow test_request_id.py 10; scripts test_log_tap_trung.py 35; platform/logs/vector.test.yaml 43 ca (vector test) | bước CI make demo-log-tap-trung: một mã qua APISIX → svc-workflow → Hasura, svc-files; câu SQL lỗi, lần đăng nhập sai, câu tìm gọi thẳng Meilisearch vào kho đã che; Vector chạy lại thì dòng đầu tiên của dịch vụ đang im vẫn tới kho (#226) | Profile logs tắt mặc định; thu 9 dịch vụ (APISIX, hai ứng dụng web, svc-files, svc-workflow tự che; Hasura, Keycloak, PostgreSQL, Meilisearch che bằng luật riêng của Vector); cổng fluent không xác thực (chỉ 127.0.0.1); Vector dùng tài khoản quản trị Parseable; Parseable chạy root (bỏ mọi capability), luôn mở CORS cho cdn.parseable.com; che lần hai không bắt số viết tách nhóm không có từ khóa đứng trước, họ tên, lời báo lỗi của hàm SQL tự viết, giá trị Meilisearch trích không trong dấu `; bật, tắt phải tạo lại 9 container (CSDL, Hasura, Keycloak, tìm kiếm gián đoạn vài giây); log không vào bản sao lưu (docs/van-hanh.md, mục Log tập trung) |
| F3.15 | WAF ở cổng: Coraza + OWASP CRS (plugin proxy-wasm của APISIX) | Dùng được | scripts test_waf.py 27 (cấu hình: image ghim digest và băm, pha rewrite, WAF thấy thân yêu cầu, chặn mặc định, MOC_WAF sai bị từ chối ở make up, ở reconciler GitOps trước khi dừng Signet và lúc khởi động, loại trừ gỡ theo id có lý do và chỉ trên khóa giao diện còn gửi, token Keycloak chỉ mất đúng các luật bắt nhầm, chặn JSON lồng sâu); svc-files test_upload.py, test_download.py (tên tệp có ' " ; = : giữ nguyên, tải về an toàn) | smoke 8 kiểm: SQLi, XSS, duyệt thư mục, SQLi trong biến GraphQL không token, tải lên .php → 403 có X-Request-Id, không lộ luật; dòng chặn của Coraza mang đúng request_id; JSON lồng 100 tầng → 400, cổng vẫn trả lời; chữ thật qua: GraphQL có nháy, gạch, _is_null; tải lên tên có ' ; = :; dự thảo có đề mục I./II./III.; khối ký, "File …?", http://<IP>, "03 file (…)" ở nội dung và ô Tìm kiếm; token ngẫu nhiên của Keycloak (cookie phiên, mã PKCE, code). Smoke và e2e chạy qua WAF (MOC_WAF=On từ .env.example) trên mọi pull request | Lên Hoàn thiện khi chữ nghiệp vụ ở các ô nhập liệu (Cơ quan ban hành, Nơi nhận…) không còn cần người quản trị thêm loại trừ trong apisix.yaml: loại trừ theo ô, có test. Dò chặn nhầm trên 7 389 yêu cầu dựng từ dữ liệu mẫu: 0 với chữ của dữ liệu mẫu, 28 với câu tự viết đặt vào ô không dành cho chúng (Cơ quan ban hành, Nơi nhận) — gặp chữ thật bị chặn thì người quản trị thêm loại trừ trong apisix.yaml. Token ngẫu nhiên của Keycloak, OIDC (#140): trước khi loại trừ khoảng 1/2 500 giá trị mỗi loại bị chặn, sau 0/50 968 trong lượt đo. Coraza 3.3.3 — bản mới nhất có dạng WebAssembly — còn bốn cách lẩn WAF mức medium; hai lỗ mức high có biện pháp ở cổng (platform/apisix/README.md, mục WAF). Nội dung tệp không qua WAF (ClamAV soi). Cổng tốn thêm ~0,4–0,8 GB RAM |
F3.6 bỏ trống: cổng xác thực đã lên Must thành F2.13.
2.4. Could (Phase 4)
| Mã | Chức năng | Mức | Test tự động | Đầu-cuối trong CI · trình diễn | Hạn chế đã biết |
|---|---|---|---|---|---|
| F4.1 | Đọc văn bản đến: lớp chữ PDF hoặc OCR Tesseract, quy tắc theo bố cục NĐ 30, điền sẵn form | Hoàn thiện | svc-files test_docread_*.py 142 (api 43, rules 35, ocr 22, summarize 16, pdflayout 13, layout 10, samples 3); db test_intake_suggestions.py 20; office suggest.test.ts 13 | bước CI make demo-doc-van-ban, make do-doc-van-ban (đỏ khi PDF có chữ dưới ngưỡng); smoke 3 kiểm; e2e doc-van-ban.spec.ts | Đo trên 22 văn bản mẫu hư cấu (số ký hiệu đúng 20/22, ngày ban hành 22/22 — CHANGELOG [1.0.0]), chưa đo trên công văn thật; CI không bật mô hình ngôn ngữ, trích yếu theo quy tắc |
| F4.2 | Trợ lý hỏi đáp: câu trích nguyên văn kèm "số ký hiệu · tr. n", lọc quyền ở bước truy xuất | Hoàn thiện | svc-files test_assist_*.py 298 (trừ test_assist_similar.py); db test_assistant.py 15; scripts test_hoi_dap.py 28; office answer.test.ts 31, chatHistory.test.ts 6 | bước CI make demo-hoi-dap, make do-hoi-dap (đỏ khi recall@3 dưới 80 %, ra nguồn bị cấm, câu đi sai đường hoặc câu âm được trả lời, #448); smoke 3 kiểm; e2e hoi-dap.spec.ts | CI chỉ chạy chế độ extract (câu trích), không bật mô hình viết câu trả lời; bộ đo 54 câu hỏi (19 câu tra cứu văn bản, 3 câu trên thông tư nguồn A, 22 câu theo loại của bộ định tuyến, 10 câu đường dữ liệu) trên 6 PDF mẫu hư cấu và 4 thông tư nguồn A |
| F4.3 | Tác tử gợi ý giao việc: nhánh song song từ lúc vào sổ, lãnh đạo quyết | Hoàn thiện | svc-workflow test_advisor.py 33, test_suggest.py 22, test_ngay_nghi.py 8; svc-files test_assist_similar.py 26; db test_assignment_suggestions.py 17; scripts test_giao_viec.py 24, test_nguon_ngay_nghi_le.py 10; office assignSuggest.test.ts 13 | bước CI make demo-giao-viec, make do-giao-viec (đỏ khi top-1 dưới 60 %); smoke 5 kiểm; e2e giao-viec.spec.ts | Top-1 29/29 đo trên lịch sử giả lập chia cụm gọn, chỉ 2 đơn vị — cao hơn thực tế (CHANGELOG [1.0.0]); hạn gợi ý tránh ngày lễ của MOC_HOLIDAYS và lịch nghỉ chính thức của nguồn công khai B (Tết, nghỉ bù, làm bù; tới hết 2027, #427) — năm chưa công bố thì người vận hành lấy mới hoặc thêm vào MOC_HOLIDAYS (services/svc-workflow/README.md) |
| F4.4 | Che thông tin cá nhân trong nhật ký và trong chữ gửi mô hình | Hoàn thiện | svc-files test_pii*.py 40 (gồm đo trên kho mẫu và 18 đột biến mã); svc-workflow test_pii_copy.py 3, test_pii_logging.py 2; scripts test_che_pii.py 23 | bước CI make demo-che-pii; make do-che-pii chạy sẵn trong make test (job unit) | Chưa che họ tên, địa chỉ, số dính chữ (trừ di động 10 số sau ĐT, SĐT, DĐ, Tel, Phone: #58), CMND và số tài khoản không có từ khóa đi trước; nhật ký Hasura, PostgreSQL, Keycloak, Meilisearch không qua bộ che (CHANGELOG [1.0.0], mục Bảo mật) |
| F4.5 | Ký số PAdES bản phát hành bằng PKI nội bộ trong két — chỉ minh họa | Minh họa | svc-files test_signing.py 29; db test_file_signatures.py 14; scripts test_ky_so.py 22; office verify.test.ts (đã tính ở F2.13) | bước CI make demo-ky-so; smoke 5 kiểm (bỏ qua khi két chưa có PKI); e2e van-ban-di.spec.ts kiểm khối chữ ký ở cổng xác thực | Không có giá trị pháp lý như chữ ký số công cộng; không HSM; ai chiếm được svc-files ký được bằng khóa của đơn vị; két dựng trước F4.5 phải nâng cấp tay để ký |
| F4.6 | Thu góp ý, đo số liệu "sau" theo đặc tả §8 | Dùng được | db test_feedback.py 25, test_success_metrics.py 8; office feedback.test.ts 11, metrics.test.ts 10; scripts test_so_lieu_sau.py 16 | job db; bước CI make do-so-lieu-sau; trang Số đo /so-do của lãnh đạo đơn vị, nút Góp ý và trang /gop-y | Cột "trước" của cả bốn chỉ số chưa đo — chưa có đơn vị chạy thử; số trên bản demo tính trên dữ liệu mẫu (seed đi hết các bước trong vài giây); thời gian tìm lại văn bản cũ là độ trễ hệ thống, không phải thời gian người tìm; số bước chuyển tay chỉ so sánh định tính; chưa có ≥ 5 người ngoài đội góp ý như kế hoạch; góp ý không hiện người gửi nhưng không ẩn danh (giờ gửi chính xác, khuôn màn hình có thể lộ vai trò); nội dung chỉ được che email, dãy số dài bằng luật SQL theo mẫu (#265: không che họ tên, địa chỉ, số dính chữ; đơn giản hơn bộ che của F4.4) (docs/so-lieu-sau.md) |
| F4.7 | Tự chẩn đoán mức trưởng thành H-P-D-I cho lãnh đạo: 12 câu × 5 mức, radar ECharts, lệch nền, không gian nên làm trước, tỷ trọng và hình dạng theo giáo trình DX-OS mục 2.4 (bốn nguyên mẫu), cảnh báo "rác vào, rác ra" dẫn tới cờ chất lượng số liệu, bước tiếp theo trỏ trang Signet có thật mà người xem mở được, bằng chứng đo được từ các view đã có, lưu lịch sử theo đơn vị | Dùng được | office hpdi.test.ts 21; db test_maturity.py 18 | job db; e2e tu-chan-doan.spec.ts (chay-thu, phần e2e); trang /tu-chan-doan của lãnh đạo đơn vị, lãnh đạo phòng | Bộ câu do đội tự viết, chưa thử với đơn vị thật, không phải bộ chỉ số chính thức, không quy đổi sang DTI; tỷ trọng H-P-D-I chỉ ước lượng từ mức tự đánh giá, hệ số thực chứng lấy bằng 1; một người trả lời cho cả đơn vị; tỷ lệ văn bản đi ký số và các số đo khác của [H] chưa có nguồn lãnh đạo đọc được nên không hiện; lãnh đạo phòng không lưu, không thấy bằng chứng (docs/tu-chan-doan.md §6) |
2.5. Ngoài danh sách F — kiểm chất lượng của dự án
| Việc | Bằng chứng |
|---|---|
| Giấy phép | job license "Giấy phép (PoF #2)": scripts/license-header.sh check, có LICENSE, NOTICE, không bundle mã dự án khác; khai REUSE ở REUSE.toml |
| Dựng từ mã nguồn | job build "Dựng từ mã nguồn (PoF #4)": docker compose config cho từng profile, cú pháp và quyền chạy của script, actionlint |
| Tài liệu bắt buộc | job docs "Tài liệu (PoF #6)"; scripts test_tai_lieu_ci.py 7 (job unit): job, phần chạy thử, bước CI mà tài liệu trỏ tới làm bằng chứng có thật trong .github/workflows/; test_muc_hoan_thien.py 9: số hàm test của từng tệp trong bảng này khớp mã |
| Bảo mật chuỗi cung ứng | job security: gitleaks trên lịch sử commit, OSV-Scanner trên tệp khóa phụ thuộc |
| Quy trình pull request | .github/workflows/pr.yml "Quy trình nhánh và tiêu đề PR"; scripts test_quy_trinh.py 23; ruleset .github/rulesets/develop.json đòi đủ 8 kiểm tra (7 job của CI + kiểm quy trình PR) |
| Phát hành | .github/workflows/release.yml "Phát hành gói nguồn"; scripts test_phat_hanh.py 32 |
Quy trình của ứng dụng là dịch vụ của lõi (#301, mục workflow của manifest, them-ung-dung.md mục 1.10) — mức Dùng được | Kiểm BPMN theo danh sách trắng, form JSON Schema, triển khai một deployment theo băm nội dung, gỡ = tạm dừng; API /api/workflow/<ứng dụng>/… chỉ nhận token của client app-<ứng dụng>. svc-workflow test_app_*.py 107 (api 37, deploy 22, bpmn 13, forms 27 — chạy mọi ca của apps/*/forms/*.cases.json, luật form lúc cài app_form_rules.cases.json và tệp mẫu của trình dựng form, core 8 — hộp việc Văn phòng không lẫn việc ứng dụng); scripts test_ung_dung_quy_trinh.py 25. Trên Flowable thật: bước CI "Quy trình của ứng dụng qua lõi (make demo-quy-trinh)" (chay-thu, phần luong-chinh; ứng dụng mẫu Đơn nghỉ phép), xanh ở lần CI 40369675895 trên develop fe0a37c (26/10/2026). Dùng được, chưa Hoàn thiện: quy trình chỉ cài, gỡ cùng ứng dụng bằng dòng lệnh moc app install, moc app remove. Chỉ quy trình duyệt: không timer, bước tự động, quy trình con; điều kiện chỉ so chuỗi; form chưa có tệp đính kèm (#325); gỡ không xóa lịch sử khỏi Flowable |
Cắm ứng dụng bằng một lệnh (moc app install, them-ung-dung.md mục 1) — mức Dùng được | Manifest apps/<thư mục>/app.yaml theo scripts/app-manifest.schema.json; client Keycloak, route APISIX, container, schema + migration bằng vai trò CSDL riêng của ứng dụng (không quyền gì trên lõi; đối tượng đọc lõi hay chạy bằng quyền của Hasura bị từ chối trước khi commit), bảng và quyền Hasura (điều kiện trong quyền không đọc được dữ liệu lõi), Event Trigger — không sửa tệp lõi nào, trạng thái trong .moc-apps/; gỡ giữ dữ liệu. Ứng dụng thứ ba apps/bang-tin (Bảng tin đơn vị). Trang Danh mục ứng dụng /ung-dung của Văn phòng điện tử (mọi vai trò; băm manifest, ứng dụng đã gỡ chỉ lãnh đạo, quản trị đơn vị; khung nhìn hẹp core.app_catalog). scripts test_ung_dung.py 49, test_mang_ung_dung.py 8, test_yaml_nho.py 6; bang-tin node --test 5, unittest 15; db test_app_registry.py 13 (44 ca), test_app_catalog.py 6; office apps.test.ts 10; e2e ung-dung.spec.ts; bước CI make demo-cai-ung-dung (chay-thu, phần luong-chinh; bước 4, 6, 7 kiểm cả danh mục). Một container một trang mỗi ứng dụng, không route API riêng, không Action; cài = tin mã của ứng dụng (mã không bị kiểm; container chỉ ở mạng apps, tới lõi duy nhất qua APISIX, các ứng dụng tới được nhau); .moc-apps/ không vào moc backup; cài, gỡ chỉ bằng dòng lệnh (trang danh mục chỉ để xem, ghi "Đã cài" — không dò sức khỏe container của ứng dụng) |
Tách lõi khỏi Văn phòng điện tử (architecture.md §4.7, bước 1–2, #302) — mức Dùng được | MOC_OFFICE_ENABLED=false (mặc định bật): svc-files, svc-workflow vẫn lên, không gắn route, Action, BPMN, vòng nền nào của Văn phòng; svc-files test_office_flag.py 10, svc-workflow test_office_toggle.py 3. Trên hệ thống chạy: make demo-tat-van-phong, bước CI "Tắt Văn phòng điện tử, lõi vẫn chạy (make demo-tat-van-phong)" — dựng lại hai dịch vụ với cờ tắt, kiểm /api/verify 404 và Action myTasks lỗi, smoke.py --chi-loi và make demo-ca-nhan đạt, rồi dựng lại như cũ. Chưa làm: bước 3–5 (CSDL, vai trò, gói riêng) |
Ứng dụng mẫu có quy trình riêng: Đơn nghỉ phép (apps/nghi-phep, #301, them-ung-dung.md mục 7) — mức Minh họa | BPMN (chỉ sự kiện, việc người làm, cổng rẽ) và hai form JSON Schema khai trong manifest, chạy trên động cơ quy trình của lõi qua API chung; container chỉ phục vụ trang tĩnh. Trang dựng form từ chính hai tệp JSON Schema đó (apps/nghi-phep/public/schema-form.js, không thư viện, #312), kiểm theo cùng schema mà svc-workflow kiểm lại. nghi-phep schema-form.test.js 14, don.test.js 11, test_server.py 6, test_bpmn.py 6; scripts test_nghi_phep.py 10 (phần thuần của kịch bản); job chay-thu, phần luong-chinh, bước "Quy trình của ứng dụng qua lõi (make demo-quy-trinh)", xanh ở lần CI 40369675895 nêu ở hàng trên. Minh họa: ứng dụng viết để chứng minh lõi chạy được quy trình của ứng dụng khác, bản rút gọn của một đơn nghỉ phép thật. Cần phần lõi của #301 (mục workflow của manifest, API /api/workflow/<ứng dụng>/…); một bước duyệt, không hủy đơn đã gửi |
Wiki nội bộ DokuWiki (#367, profile wiki tắt mặc định, van-hanh.md mục Wiki nội bộ) — mức Dùng được | Mỗi đơn vị một không gian don-vi:<mã> chỉ người của đơn vị đọc, sửa; đăng nhập chỉ qua Keycloak (client bí mật wiki, nhóm = tenant_id), không tài khoản quản trị; volume vào moc backup. scripts test_wiki.py 25 (danh sách quyền xét như DokuWiki: hai đơn vị tách nhau, không gian bỏ lại không ai đọc, nhóm mặc định không ghi; client Keycloak; cấu hình compose, APISIX, WAF, Makefile, sao lưu); office apps.test.ts (thẻ Wiki chỉ khi wiki trả lời). Bước CI "Wiki nội bộ theo đơn vị (make wiki-up, make demo-wiki)" (chay-thu, phần sao-luu): người phòng CNTT viết trang, văn thư phòng KT không đọc, không sửa được, sơ đồ trang không lộ trang, người chưa đăng nhập về Keycloak; sau khôi phục bản sao lưu trang vẫn còn (demo_wiki.py --sau-khoi-phuc). Chưa có không gian chung cả trường; đơn vị mới cần chạy lại moc wiki sync; trang wiki không vào tìm kiếm của Văn phòng |
2.6. Tóm tắt
45 tính năng (F1.1–F4.7, không tính F3.6):
| Mức | Số | Tính năng |
|---|---|---|
| Hoàn thiện | 35 | F1.1–F1.10; F2.1–F2.8, F2.10, F2.12, F2.13; F3.1–F3.5, F3.7–F3.9, F3.11, F3.12; F4.1–F4.4 |
| Dùng được | 7 | F1.11; F2.9, F3.13, F3.14, F3.15, F4.6, F4.7 |
| Minh họa | 2 | F3.10, F4.5 |
| Một phần | 1 | F2.11 |
3. Độ phủ 7 cụm dịch vụ Tầng 1
Đề liệt kê năng lực của từng cụm (du-thi/olp-2026-de-thi.md §2.1). Chỉ 4 dịch vụ là
bắt buộc (định danh, cổng API, dữ liệu, quy trình); phủ thêm là điểm cộng. Bảng tách những năng lực đề viết chung
một ô ("log truy vết phân tán + kiểm toán bất biến", "chữ ký số / mã hóa") thành hai dòng. Mức đánh trên đúng năng lực
đề nêu, nên có thể thấp hơn mức của tính năng tương ứng ở mục 2: tính năng có thể xong đúng phạm vi kế hoạch mà vẫn
chưa phủ hết năng lực của cụm.
| Cụm | Năng lực đề nêu | Thành phần | Mức | Ghi chú |
|---|---|---|---|---|
| ① Định danh, bảo mật | SSO, MFA | Keycloak, platform/keycloak/realm/moc-realm.json | Hoàn thiện | F1.1 |
| ① | Két quản lý vòng đời khóa và bí mật | OpenBao, platform/openbao/; svc_files/openbao.py, svc_workflow/openbao.py | Dùng được | F3.2. secret_id của dịch vụ cấp mới mỗi lần khởi động; chưa có quy trình xoay khóa transit, xoay bí mật; .env vẫn là nguồn bí mật |
| ① | Trạm kiểm dịch tệp | ClamAV, services/svc-files/src/svc_files/clamav.py | Hoàn thiện | F1.6, F3.1 |
| ① | Mã hóa | Transit của OpenBao + AES-GCM, services/svc-files/src/svc_files/sealed.py | Hoàn thiện | F3.7 |
| ① | Chữ ký số (PKI, HSM) | PKI nội bộ trong OpenBao + pyHanko, services/svc-files/src/svc_files/signing.py | Minh họa | F4.5. Không HSM — đặc tả §4.4 xếp HSM, chữ ký số công cộng vào "Không làm" |
| ② Đa thuê ước, biên mạng | API Gateway zero-trust | APISIX, platform/apisix/ | Hoàn thiện | F1.2; WAF Coraza + OWASP CRS ở cổng (F3.15, Dùng được); ứng dụng tới lõi duy nhất qua cổng: container ứng dụng chỉ ở mạng apps cùng APISIX (test_mang_ung_dung.py, make demo-cai-ung-dung bước 5) |
| ② | Rà soát và che PII | services/svc-files/src/svc_files/pii.py (bản chép ở svc-workflow) | Hoàn thiện | F4.4; các giới hạn ở mục 2.4 |
| ② | Đa thuê ước cách ly dữ liệu | tenant_id + quyền Hasura (db/metadata/), Keycloak Organizations, scripts/tenants.py | Hoàn thiện | F1.3, F3.12; một realm, một bucket chia theo tiền tố |
| ② | Dịch thuật, bản địa hóa đa ngôn ngữ | apps/office/app/locales/ | Một phần | F2.9: bản địa hóa giao diện Việt / Anh; lõi không có dịch vụ dịch thuật nào cho ứng dụng dùng chung |
| ③ Dữ liệu có cấu trúc | OLTP toàn vẹn | PostgreSQL 16, db/migrations/default/ (CHECK, khóa ngoại kèm tenant_id, trigger) | Hoàn thiện | F1.3 |
| ③ | Tự sinh API từ CSDL, phân quyền tới cấp dòng | Hasura v2 CE, db/metadata/; PostgREST cho bảng tạo trong Studio (schema sinh, RLS của khung, QĐ-014) | Hoàn thiện | F1.3; db test_permissions.py; F1.11 |
| ③ | Phiên bản và lịch sử biến động | core.audit_log, view office.document_history | Hoàn thiện | F2.12, F3.5; mức dòng thay đổi, trường nội dung dài chỉ báo "đã đổi" |
| ③ | Đóng băng số liệu chuỗi thời gian | Lịch sử đo trong Scorpio (NGSI-LD temporal API, đọc ở make demo-it-ot); bảng office.archived_document_facts | Một phần | Chưa có kho chuỗi thời gian riêng, chưa chụp số liệu theo kỳ; archived_document_facts chỉ giữ số liệu dashboard của văn bản đã nộp lưu |
| ③ | Lớp ngữ nghĩa báo cáo | Bảng sinh: báo cáo khai báo bằng dữ liệu (sinh.bao_cao: bảng nguồn, cột nhóm, phép tính, kiểu hiện), trang /sinh gọi hàm gộp của PostgREST (#587); Văn phòng: bốn view office.v_* qua Hasura (db/migrations/default/1792281601200_dashboard/) | Một phần | F1.11, F3.11. Bảng sinh: chiều là một cột enum hay ngày, chỉ số là đếm, tổng, trung bình; thêm báo cáo không viết mã, RLS lọc theo đơn vị trước khi gộp. Chưa có định nghĩa chỉ số dùng chung giữa các báo cáo; báo cáo của Văn phòng vẫn là view viết tay |
| ④ Dữ liệu phi cấu trúc | Lưu trữ đối tượng | SeaweedFS, services/svc-files/src/svc_files/storage.py | Hoàn thiện | F1.5 |
| ④ | Quản lý hồ sơ, văn bản cho quy trình phê duyệt | apps/office + svc-workflow + Flowable | Hoàn thiện | F2.2–F2.4, F3.8 (lập hồ sơ, nộp lưu, phòng đọc trên giao diện) |
| ④ | Chỉ mục toàn văn | Meilisearch, svc_files/search.py, svc_files/texts.py | Hoàn thiện | F2.7; bản scan vào chỉ mục bằng chữ OCR của 3 trang đầu (#253) |
| ④ | Trộn dữ liệu sinh biểu mẫu, báo cáo động | Bảng sinh: mẫu in lưu thành dữ liệu (sinh.mau_in), svc-files trộn một dòng vào mẫu rồi dựng PDF bằng WeasyPrint (svc_files/sinh_print.py, GET /api/sinh/<bảng>/<id>/pdf, #586); báo cáo khai báo bằng dữ liệu (sinh.bao_cao, #587). Văn phòng: WeasyPrint với svc_files/render.py (văn bản đi), svc_files/registers.py (sổ đăng ký); dự thảo "Báo cáo sự cố" (svc_workflow/ot.py) | Dùng được | F1.11. Bảng sinh: đổi mẫu, thêm báo cáo không sửa mã; mẫu là văn bản thuần (chỗ trống {{cột}}, tiêu đề, đoạn), giá trị thoát HTML, WeasyPrint chỉ tải data:. Khuôn văn bản đi, sổ đăng ký của Văn phòng vẫn viết trong mã |
| ⑤ Quy trình, tự động hóa | BPMN quản lý trạng thái luồng phê duyệt | Flowable 8, services/svc-workflow/processes/*.bpmn20.xml (có DI); sơ đồ trên trang văn bản: svc_workflow/so_do.py, Action documentProcess, apps/office/app/components/ProcessDiagram.vue | Hoàn thiện | F1.4, F2.3, F2.4. Người đọc được văn bản xem được quy trình BPMN của chính văn bản đó ở mục Sơ đồ quy trình: bước đã qua, bước đang chờ, nhánh đã đi, kèm danh sách bước dạng chữ; SVG tự vẽ từ DI, không bpmn-js (#603; QĐ-009). Ứng dụng cắm thêm dùng chung động cơ này qua API /api/workflow/<ứng dụng>/… (#301; mục 2.5, mức Dùng được), chưa có sơ đồ |
| ⑤ | Thực thi kịch bản serverless | Node-RED 5.0.8 (profile tu-dong, tắt mặc định), platform/tu-dong/, scripts/tu_dong.py | Một phần | Người vận hành nền tảng (vai trò operator của client Keycloak tu-dong) soạn, triển khai flow ở /tu-dong/ không sửa mã dịch vụ; không node exec, không cài node từ trình soạn, chỉ tới lõi qua APISIX (#366, van-hanh.md mục Tự động hóa bằng Node-RED, bước CI make demo-tu-dong). Flow gọi API của lõi bằng robot của đơn vị (moc tu-dong robot add: chỉ đọc văn bản không hạn chế, báo người trong đơn vị qua Action notifyFromFlow); hai flow mẫu: văn bản sắp hạn lúc 07:00, văn bản Hỏa tốc vào sổ. Chưa có endpoint webhook và bước BPMN gọi flow; việc nền của lõi vẫn là vòng lặp trong svc-files, svc-workflow |
| ⑤ | API nạp dữ liệu khối lớn theo lô | POST /api/imports/incoming của svc-files (svc_files/imports/), bảng core.import_jobs, core.import_rows | Dùng được | Văn bản đến: tệp NDJSON/CSV + zip tệp đính kèm, xử lý nền theo mẻ; mỗi dòng qua đúng ClamAV và Action receiveIncoming thay mặt văn thư, lỗi từng dòng, gửi lại không nhân đôi, chạy tiếp sau khi svc-files khởi động lại. svc-files test_imports_*.py 111; db test_import_jobs.py 18; bước CI make demo-nap-lo. Chưa có màn "Nhập lô" trên giao diện — gọi API; mới có một loại lô (văn bản đến); zip tệp đính kèm mất khi svc-files khởi động lại (dòng còn cần tệp báo archive_lost) |
| ⑥ Vận hành, giám sát, kiểm toán | Log truy vết phân tán | APISIX gắn X-Request-Id (platform/apisix/apisix.yaml) và ghi nó ở cuối dòng log truy cập; svc-files, svc-workflow ghi mã này vào nhật ký, chuyển tiếp sang Hasura và vào core.audit_log.request_id; log tập trung (F3.14, profile logs) tra theo mã: make logs-tra ID=…. Truy vết (#364, profile trace): APISIX lấy mẫu MOC_TRACE_RATIO yêu cầu, svc-files, svc-workflow ghi span tiếp theo traceparent (tracing.py), Jaeger 2.22 lưu trong RAM và hiện thời gian từng chặng; tìm theo thẻ x-request-id | Dùng được | Tắt mặc định (cả log tập trung và truy vết). Span che hai lớp: dịch vụ gửi theo danh sách trắng, bỏ chuỗi truy vấn, URL ngoài chỉ còn tên máy, lỗi chỉ còn tên loại; Jaeger bỏ http.target của APISIX. Chưa có span của Hasura (bản CE), Flowable, Keycloak, PostgreSQL; trace mất khi Jaeger khởi động lại; chưa xem được trong Grafana (Jaeger 2.22 bỏ API v1). Bằng chứng: svc-files test_tracing.py, svc-workflow test_tracing_copy.py, scripts test_truy_vet.py; bước CI jaeger validate. Giám sát: Prometheus + Grafana chỉ có chỉ số APISIX (F3.13) |
| ⑥ | Kiểm toán bất biến | Sổ kiểm toán chuỗi băm (db/migrations/default/1792281600100_core_schema/), neo ra bucket khóa ghi, scripts/audit_chain.py | Hoàn thiện | F2.12 |
| ⑥ | Trục streaming sự kiện | Luồng core.app_events (migration 1792281603200) + Hasura Event Triggers do moc app install sinh (scripts/ung_dung.py) | Một phần | Lõi chép sự kiện văn bản trong danh sách trắng (đến, vào sổ, phát hành; không văn bản hạn chế, 5 cột) sang core.app_events; mỗi ứng dụng khai events nhận qua webhook — Hasura gọi cổng hook nội bộ 9081 của APISIX (không publish), APISIX chuyển tới container ứng dụng ở mạng apps —, header bí mật, Hasura gửi lại khi lỗi (ít nhất một lần). Không phải broker streaming (không Kafka hay tương đương): không đọc lại từ một vị trí, không nhóm người nghe, không bảo đảm thứ tự, chỉ 3 loại sự kiện; sự kiện khác vẫn chỉ nằm trong bảng (office.document_events, core.lineage_events, core.notifications), Centrifugo chỉ đẩy tới trình duyệt. Bằng chứng: db test_app_registry.py (PostgreSQL, Hasura thật khi MOC_IT=1); bước CI make demo-cai-ung-dung (chay-thu, phần luong-chinh) |
| ⑥ | GitOps | scripts/reconcile.py, platform/systemd/ | Hoàn thiện | F3.3; Compose + reconciler, không Argo CD |
| ⑥ | Sao lưu, khôi phục thảm họa | restic, scripts/backup.py | Hoàn thiện | F3.4; kho sao lưu trên cùng máy chủ, chép ra ngoài bằng tay |
| ⑦ Truyền thông, giao vận | Định tuyến thông báo đa kênh | svc_workflow/channels.py, hộp thư đi core.notification_deliveries; Mailpit (profile mail) để thử | Một phần | Chuông trên web (Centrifugo) cộng email (SMTP), webhook chat (Mattermost, Slack, ntfy), SMS (SMS Gateway for Android) và Zalo (bot, liên kết bằng mã), tắt mặc định; trigger tạo dòng chờ gửi trong cùng giao dịch ghi thông báo, lỗi tạm thử lại giãn dần. SMS, Zalo do từng người tự bật (#365). Email chưa cho từng người tắt; webhook một URL cho cả bản cài; SMS, Zalo mới thử với cổng, bot giả trong test, chưa với tài khoản thật |
| ⑦ | WebSocket thời gian thực | Centrifugo, platform/centrifugo/config.yaml, route /realtime | Hoàn thiện | F2.5 |
| ⑦ | Hàng đợi bất đồng bộ chống chối bỏ | Hộp thư đi office.dispatches (svc_workflow/dispatch.py); việc giao ra ngoài của Flowable (topic moc-goi-y, moc-sla) | Một phần | Liên thông (F3.12) có thử lại lùi dần, so SHA-256, ghi sổ kiểm toán cả hai đơn vị trong cùng giao dịch. Chưa có hàng đợi dùng chung cho ứng dụng khác, chưa có biên nhận ký số |
Tổng: 29 năng lực — Hoàn thiện 17, Dùng được 4, Minh họa 1, Một phần 7, Chưa có 0.
- Một phần: kịch bản serverless (⑤: Node-RED có robot, flow mẫu; chưa có webhook, bước BPMN gọi flow), dịch thuật trong lõi (②), đóng băng số liệu chuỗi thời gian (③), lớp ngữ nghĩa báo cáo (③: bảng sinh khai báo báo cáo bằng dữ liệu, Văn phòng vẫn view viết tay), trục sự kiện (⑥: Event Triggers giao tới webhook của ứng dụng, chưa phải broker streaming), định tuyến thông báo đa kênh (⑦: email chưa cho từng người tắt; SMS, Zalo chưa thử với tài khoản thật), hàng đợi chống chối bỏ (⑦).
- Bốn dịch vụ bắt buộc (SSO, cổng API, dữ liệu có cấu trúc và phi cấu trúc, quy trình) đều ở mức Hoàn thiện.
4. Kiểm lại
Chạy ở gốc repo. Đếm tĩnh như mục 1:
for d in services/svc-files/tests services/svc-workflow/tests scripts/tests db/tests; do
printf '%s %s\n' "$d" "$(grep -rhoE '^\s*(async )?def test_' --include='*.py' "$d" | wc -l)"
done
grep -hE '^\s*(test|it)\(' apps/office/tests/*.test.ts | wc -l # giao diện (node:test)
grep -hE '^\s*(test|it)\(' apps/ca-nhan/tests/*.test.js apps/bang-tin/tests/*.test.js | wc -l # hai ứng dụng
grep -rhoE '^\s*(async )?def test_' apps/bang-tin/tests | wc -l # Bảng tin, phần Python
grep -hE '^\s*(test|it)\(' apps/nghi-phep/tests/*.test.js | wc -l # Đơn nghỉ phép (#301)
grep -rhoE '^\s*(async )?def test_' apps/nghi-phep/tests | wc -l # Đơn nghỉ phép, phần Python
grep -hE '^\s*(test|it)\(' apps/lich-giang/tests/*.test.js | wc -l # Đổi, bù lịch giảng (#482)
grep -rhoE '^\s*(async )?def test_' apps/lich-giang/tests | wc -l # Đổi, bù lịch giảng, phần Python
grep -rhoE '^\s*(async )?def test_' apps/tai-lieu-api/tests | wc -l # trang tài liệu API (#357)
grep -hE '^test\(' tests/e2e/*.spec.ts | wc -l # kịch bản Playwright
grep -cE '^\s*ok\(' scripts/smoke.py # kiểm của make smoke
grep -cE '^\s*(async )?def test_' services/svc-files/tests/test_*.py # từng tệp (đổi thư mục cho phần khác)
Chạy thật (cần gì cho từng lệnh: README, mục Kiểm thử):
make test # hai dịch vụ, scripts, apps/office và năm ứng dụng còn lại ở apps/ — như job unit
db/tests/stack.sh up # PostgreSQL + Hasura cho db/tests — như job db (lệnh đủ ở db/README.md)
make up && make seed && make smoke && make e2e # như job chay-thu (phần luong-chinh, e2e)
make demo-nong-lanh # từng màn ở mục 2; make help liệt kê tất cả
Các ô "Chưa có", "Chưa làm" ở mục 2, mục 3:
git grep -n event_triggers -- db/metadata scripts/ung_dung.py # chỉ ung_dung.py: trigger sinh lúc cài ứng dụng
git grep -niE 'kafka|nats|rabbitmq|kestra|openfaas|marquez' -- docker-compose.yml # rỗng
grep -n job_name platform/monitoring/prometheus.yml # apisix, svc-files (trạng thái thành phần), prometheus
grep -nE '^ [a-z]+:$|^ name:|^ - name:' .github/workflows/ci.yml # job và bước CI
Tính năng đổi mức thì sửa bảng ở mục 2 và mục 3 trong cùng pull request với thay đổi đó.