Files
solution-erp/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-cicd-verify-k7.md
2026-08-01 13:36:33 +07:00

23 KiB
Raw Blame History

CICD-K7: PARTIAL — K7 ĐÃ LIVE & verify sạch (gate 639=45+594 · Mig GIỮ 71 · bundle rotate ×2 marker 8/8 · seeder 0 added + 2 upgraded + COUNT 1→3 · endpoint mới 400-không-403). 2 lệch: run aaed699 bị CANCELLED (ship qua #442, đề-xuất gotcha #86) + restart×2 không quan-sát-được trong cửa-sổ.

sub-cicd-verify-k7 — deploy verify K7 (bridge KHKK→HĐ), S167 2026-08-01

Artifact chống-#53: ghi TỪNG MỤC ngay khi có bằng chứng. Return chỉ là tóm tắt. Baseline PRE đọc từ sub-cicd-verify-k6.md (post-#440): admin js Ig1R5tAh · eoffice js DFOCQ2ks · gate 622=45+577 · Mig 71 · sys.tables 97.


M0 — Push + path-filter (ĐÃ ĐO)

  • HEAD = aaed699228f418fa31ecba07414709de465502fc subject: [CLAUDE] Contract: K7 SP-002 bridge KHKK->HD hoi-2-khoa + Line.ContractId + grant seeder (gate PASS-WITH-FLAGS 10, F-8 fixed) committed: 2026-08-01 13:04:49 +0700
  • git log origin/main..HEAD = RỖNG ⇒ đã push thật.
  • Range 1c6d77a..aaed699 = 7 commit (6 × wal: flush + 1 code).
  • File code trong range (13/13 khớp đề bài):
    • BE (4): Api/Controllers/ContractSigningPlansController.cs · Application/ContractSigningPlans/ContractSigningPlanFeatures.cs · Application/ContractSigningPlans/CreateContractFromSigningPlanFeatures.cs · Infrastructure/Persistence/DbInitializer.cs
    • FE mirror ×2 app (6): fe-{admin,user}/src/hooks/usePipelineStages.ts · .../pages/khkk/KhkkDetailPage.tsx · .../types/khkk.ts
    • Test (3): Api/AuthorizePolicyRegressionTests.cs · Api/KhkkBridgeAuthzTests.cs · Application/CreateContractFromSigningPlanTests.cs
    • (còn lại 14 file đều .claude/** = paths-ignore, KHÔNG trigger)
  • M3-phần-1 (git): git diff --name-only 1c6d77a..aaed699 -- '*Migrations*' = 0 file ⇒ 0-mig chứng-minh-được từ git.
  • Path-filter: có file src/** + fe-*/** ⇒ CI PHẢI chạy (không skip).

M1 — Run Gitea 🔴 PHÁT HIỆN LỚN: run của aaed699 bị CANCELLED, KHÔNG success

Đo lúc 13:09-13:10 qua actions/tasks?limit=3 (khớp theo head_sha, KHÔNG theo run_number):

run head_sha status started updated ghi chú
#441 (id 554) aaed699 🔴 cancelled 13:04:57 13:07:59 sống ~3m02s rồi bị huỷ giữa chừng
#442 (id 555) 628132c running → (đo tiếp) 13:08:26 run-2 lead báo
#440 (id 553) 1c6d77a success 11:54:38 12:01:07 mốc PRE (K6)

Cơ chế: push 628132c lúc 13:08:26 ⇒ Gitea cancel-in-progress giết run #441 đang chạy. ⇒ Đề bài mục 1 ("run aaed699 success") KHÔNG THOẢ THEO NGHĨA ĐENaaed699 chưa bao giờ tự deploy xong.

Nhưng K7 VẪN ship, chứng minh bằng ancestry (không phải suy đoán):

  • git merge-base --is-ancestor aaed699 628132c = YES ⇒ cây của run #442 CHỨA trọn 13 file K7.
  • git diff --name-only aaed699..628132c = 6 file (docs/STATUS.md · fe-{admin,user}/…/KhkkWorkflowPanel.tsx · 3 scripts/*.ps1) — không đụng file K7 nào. ⇒ run #442 = phương-tiện ship DUY NHẤT của K7. Mọi phép đo POST vì thế quy về #442.

🔴 GOTCHA MỚI đề-xuất (#86) — "push-đè nuốt deploy im lặng"

Push code A rồi push B ngay sau (<run-time của A) ⇒ run A cancelled. Nếu B rơi trọn vào paths-ignore (docs/memory-only) thì run B skipcode A KHÔNG BAO GIỜ deploy, mà bảng Actions trông vẫn "lành" (1 cancelled + 0 run) và git log thì đã push. Lần này thoát NHỜ MAY: 628132c có chạm fe-*/src/** nên mới đẻ run. Luật rút ra: sau 2 push liên tiếp, PHẢI xác minh run cuối cùng có head_sha là TIP và tip ⊇ commit code, chứ không được đọc "có run xanh" là xong.

Hệ quả cho mục 5 (restart-proof): giả định "run-1 + run-2 = restart ×2 miễn phí" của lead KHÔNG còn đúng — run #441 bị giết TRƯỚC bước deploy nên không hề restart pool. Trong cửa sổ này chỉ có 1 lần restart thật (từ #442). Idempotent-qua-2-restart do đó phải hạ cấp thành "chưa chứng-minh-được trong cửa-sổ" (xem M5) — tao KHÔNG tự restart pool prod (READ-only, cấm #6).


M4-PRE — mốc bundle + tập marker (ĐO TRƯỚC khi deploy đè, 13:06-13:07)

Bundle PRE (live, khớp đúng baseline K6 post-#440):

app js css Last-Modified (GMT) size js
admin index-Ig1R5tAh.js index-DqXm5Hpn.css Sat 01 Aug 2026 04:59:40 (=11:59:40 +07) 1.758.853 B
eoffice index-DFOCQ2ks.js index-DlN3yZ5L.css Sat 01 Aug 2026 05:00:41 (=12:00:41 +07) 1.676.752 B

Kiểm-tra-tiền-đề cho marker (bài K6 nâng cấp — kiểm ĐƠN-VỊ-MÃ-HOÁ + ĐỊA-CHỈ trước khi tin 0-hit):

  1. file báo bundle "ASCII text" ⇒ nghi non-ASCII bị \u-escape. Bác bỏ bằng đo: grep -c -F 'Hợp đồng' = 4, 'Kế hoạch' = 3 trên bundle PRE ⇒ tiếng Việt nằm thô (UTF-8), grep -F đọc được.
  2. Nghi code-split (nếu KhkkDetailPage nằm chunk lazy riêng thì marker 0-hit là vô nghĩa): index.html chỉ trỏ đúng 2 asset (index-*.js + index-*.css), và bundle 0 ref dạng "./xxx-<hash>.js"single-bundle, không lazy-chunk.
  3. CONTROL DƯƠNG (chứng KhkkDetailPage THẬT nằm trong index-*.js + grep có răng) — 4 chuỗi bản tiền-K7 của chính file đó, đo trên cả 2 bundle PRE: Phiếu nguồn chưa có NCC trúng thầu.=1/1 · Chưa khai căn cứ nào.=1/1 · Giá tham chiếu (phiếu NCC)=1/1 · Tên TVGS xử lý=1/1 ⇒ 4/4 hiện diện.

CONTROL ÂM / tập marker K7 — 0-hit PRE trên CẢ 2 bundle (8/8, grep -c -F):

marker (chuỗi UI nút bridge) adm PRE eof PRE
Đưa dòng kế hoạch vào Hợp đồng 0 0
Chọn dòng đưa vào hợp đồng 0 0
Loại hợp đồng 0 0
Quy trình duyệt hợp đồng 0 0
Phiếu chưa có dòng nhà cung cấp nào. 0 0
khkk-bridge-line 0 0
Tạo hợp đồng 0 0
Chưa có giá chốt 0 0

⇒ marker có tín hiệu thật (0→≥1 mới là bằng chứng ship). Diff FE đối xứng: fe-adminfe-user mỗi bên +308 dòng KhkkDetailPage.tsx, +61 usePipelineStages.ts, +49 types/khkk.ts (796 insert/40 delete tổng) ⇒ kỳ vọng element-wise 8/8 ở CẢ 2 app.


M3 — Migration (phần git, ĐÃ CHỐT) + prod PRE

  • git: 1c6d77a..aaed699 0 file dưới *Migrations*0-mig. (Range mở rộng tới 628132c cũng 0 — 6 file kia không có Migrations.)
  • prod PRE (sqlcmd 13:06): MIG_TOP = 20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime = Mig 71 · sys.tables(is_ms_shipped=0) = 97 · MenuItems=200 · Permissions=2221 (khớp anchor #438/#440).

M5-PRE — trạng thái quyền Contracts TRƯỚC deploy (mấu chốt K7)

SELECT COUNT(*) FROM Permissions WHERE MenuKey='Contracts' AND CanCreate=1PRE = 1 (đúng dự-đăng-ký của gate-K7) SELECT COUNT(*) … WHERE MenuKey='Contracts' (mọi cờ) → PRE = 13 row (13 role, mỗi role 1 row)

Bảng đầy đủ 13 role PRE — CanRead=1 toàn bộ, CanCreate=1 duy nhất Admin:

role create read role create read
Accounting 0 1 Equipment 0 1
Admin 1 1 Finance 0 1
AuthorizedSigner 0 1 HrAdmin 0 1
CatalogManager 0 1 Procurement 0 1
CostControl 0 1 ProjectManager 0 1
DeptManager 0 1
Director 0 1
Drafter 0 1

Drafter + Procurement ĐÃ CÓ row sẵn (read=1, create=0) ⇒ seeder phải UPGRADE 2 row, KHÔNG add row nào ⇒ acceptance 0 added + 2 upgraded nhất-quán với trạng thái prod đo được, và kỳ vọng POST: CanCreate=1 → 3 đồng thời tổng row Contracts GIỮ NGUYÊN 13 (nếu thành 15 ⇒ seeder add trùng ⇒ FAIL). Đọc code seeder xác nhận khuôn UPGRADE-ONLY: SeedContractCreateAccessAsync gọi sau revoker, chỉ nâng cờ, không hạ.


M1-bis — run #442 (628132c) = phương-tiện ship K7: SUCCESS

  • start 13:08:26 → settle 13:14:51 = 6m25s (nằm dải bình thường 5m3x-6m3x, không escalate).
  • status success (field conclusion = None như thường lệ với tasks — tin status, đã biết từ trước).
  • URL: https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/442

M2 — Test gate: 639 = 45 Domain + 594 Infra KHỚP TUYỆT ĐỐI

Đo LIVE ở web-UI log (/actions/runs/442/jobs/0/logs, 18.695 B) — tách phần, không chỉ khớp tổng:

Passed!  - Failed: 0, Passed:  45, Skipped: 0, Total:  45, Duration: 169 ms  - SolutionErp.Domain.Tests.dll (net10.0)
Passed!  - Failed: 0, Passed: 594, Skipped: 0, Total: 594, Duration: 1 m 47 s - SolutionErp.Infrastructure.Tests.dll (net10.0)
PRE (#440) POST (#442) Δ
Domain 45 45 0
Infrastructure 577 594 +17
tổng 622 639 +17
Failed / Skipped 0 / 0 0 / 0

Δ+17 phân rã khớp TỪNG ĐỒNG với nguồn (đếm attr trong repo — an toàn vì 0 [Theory]/[InlineData], hết rủi ro undercount):

  • CreateContractFromSigningPlanTests.cs = 12 [Fact]
  • KhkkBridgeAuthzTests.cs = 5 [Fact]
  • AuthorizePolicyRegressionTests.cs (file sửa, không thêm test) = 0 attr test mới trong diff
  • ⇒ 12 + 5 + 0 = 17 = đúng Δ Infra đo được.

Flaky đã khai: DeleteContractSigningPlan_SoftDeletes_KeepsChangelog_ThenCreateAgainSucceeds (ContractSigningPlanCrudTests.cs:482) — gate báo Skipped 0 / Failed 0XANH lần thứ 4 liên tiếp (#439, #440, local, #442). Chưa có cớ hô hồi-quy.


M3 — Migration GIỮ 71 + sys.tables 97 (2 nguồn ⟂)

nguồn kết quả
git 1c6d77a..aaed699 -- '*Migrations*' 0 file ⇒ 0-mig
git mở rộng tới 628132c 0 file (6 file của run-2 không có Migrations)
sqlcmd __EFMigrationsHistory TOP 20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime = Mig 71 (PRE == POST)
sqlcmd COUNT(*) __EFMigrationsHistory 71 (khớp tên-đỉnh — 2 cách đọc độc lập cùng chốt)
sqlcmd sys.tables (is_ms_shipped=0) 97 (PRE == POST)

⇒ NO-Mig proof đủ 2 chân: history KHÔNG tiến + range git rỗng file Migrations. Anchor phụ giữ nguyên: MenuItems 200 · Permissions 2221 · control revoker Hrm*/Off* CanRead=1 = 29.


M4 — Bundle rotate ×2 app + marker 8/8 element-wise

Hash + LM + size (curl LIVE sau status=success):

app PRE js POST js LM POST (GMT / +07) size PRE→POST Δ
admin Ig1R5tAh RgvHZkPH 06:13:22 / 13:13:22 1.758.853 → 1.765.720 +6.867 B
eoffice DFOCQ2ks D65FJPGd 06:14:22 / 13:14:22 1.676.752 → 1.683.623 +6.871 B
  • LM cả 2 nằm trong cửa-sổ deploy (13:08:26 → 13:14:51) ⇒ ship THẬT, không phải FROZEN-kẹt.
  • Δ size 2 app gần bằng nhau (+6.867 / +6.871) — nhất quán với diff FE mirror đối xứng (+308/+61/+49 dòng mỗi app). Nhân chứng ⟂ grep.
  • CSS giữ nguyên cả 2 (DqXm5Hpn / DlN3yZ5L) — hợp lý: class mới đều là utility Tailwind đã tồn tại, không sinh CSS mới. Không phải cờ đỏ.
  • NHÂN CHỨNG THỨ 4 — log CI in thẳng hash+size, khớp curl tới từng byte: dist/assets/index-RgvHZkPH.js 1,765.72 kB ↔ curl Content-Length: 1765720 dist/assets/index-D65FJPGd.js 1,683.62 kB ↔ curl Content-Length: 1683623
  • Chống SPA-fallback trap (#S72): Content-Type: application/javascript cho cả 2 hash mới; hash BỊA index-ZZZZfake9.jstext/html 900 B discriminator còn răng; hash Ig1R5tAhtext/html 900 B ⇒ bundle cũ đã biến khỏi đĩa = chứng ghi-đè (control âm tầng-2)

Marker (đo bằng grep -c -F — KHÔNG regex-neo-quote, đúng recipe K6):

marker ADM pre→post EOF pre→post
Đưa dòng kế hoạch vào Hợp đồng 0 → 1 0 → 1
Chọn dòng đưa vào hợp đồng 0 → 1 0 → 1
Loại hợp đồng 0 → 1 0 → 1
Quy trình duyệt hợp đồng 0 → 1 0 → 1
Phiếu chưa có dòng nhà cung cấp nào. 0 → 1 0 → 1
khkk-bridge-line 0 → 1 0 → 1
Tạo hợp đồng 0 → 1 0 → 1
Chưa có giá chốt 0 → 1 0 → 1
CONTROL DƯƠNG Phiếu nguồn chưa có NCC trúng thầu. 1 → 1 1 → 1
CONTROL DƯƠNG Chưa khai căn cứ nào. 1 → 1 1 → 1
CONTROL ÂM ĐỘC-QUYỀN …vào Hợp đồng XYZ (bịa) 0 → 0 0 → 0
CONTROL ÂM ĐỘC-QUYỀN khkk-bridge-line-nonexistent 0 → 0 0 → 0

8/8 × 2 app, control dương giữ, control âm câm ⇒ tín hiệu sạch cả 2 chiều.

TRÍCH BYTE THẬT (bắt buộc trước khi kết luận — bài K6):

ADMIN   idx=1667835 : …title:`Đưa dòng kế hoạch vào Hợp đồng`,footer:(0,N.jsxs)(N.Fragment,…
ADMIN   idx=1669865 : …jsx)(`input`,{type:`radio`,name:`khkk-bridge-line`,checked:a===e.id,…
EOFFICE idx=1501164 : …title:`Đưa dòng kế hoạch vào Hợp đồng`,footer:(0,F.jsxs)(F.Fragment,…
EOFFICE idx=1503194 : …jsx)(`input`,{type:`radio`,name:`khkk-bridge-line`,checked:a===e.id,…

🔑 Xác nhận lại bài K6: rolldown/Vite-8 minify literal thành BACKTICK (title:`…`) — regex neo ["'] sẽ 0-hit GIẢ. grep -F là đúng công cụ. 2 app chỉ khác tên biến module (N vs F) = minifier, code y hệt.

Kiểm-tra-tiền-đề đã làm TRƯỚC khi tin marker (ghi lại vì suýt sập bẫy mới): file báo bundle "ASCII text" ⇒ nghi \u-escape ⇒ bác bỏ bằng đo (grep -c -F 'Hợp đồng'=4 trên bundle PRE); và nghi code-split ⇒ bác bỏ bằng đo (index.html chỉ 2 asset, 0 ref "./x-<hash>.js"). Cả 2 nghi ngờ đều SAI, nhưng nếu không đo mà kết luận "0-hit = chưa ship" thì đã FAIL-OAN như K6.


M5 — 🎯 K7-đặc-thù: seeder grant restart-proof (#75/#76)

(a) Log API — dòng acceptance CHỐT bởi gate-K7: CÓ, verbatim

2026-08-01 13:14:40.592 +07:00 [INF] Seeded Contracts.Create access: 0 added + 2 upgraded
   (K7 cầu KHKK→HĐ - Drafter/Procurement/Admin; câu III default chờ ratify)

(C:\inetpub\solution-erp\api\logs\solution-erp-20260801.log; ký tự Việt hiển thị lỗi do console codepage, không phải log hỏng.)

Chứng restart THẬT (3 chân kiềng, không đọc số lúc running):

  1. Dòng ĐẦU TIÊN của log = 13:14:33.867 [INF] Applying migrations... ⇒ tiến trình khởi động mới.
  2. w3wp PID 7008, StartTime = 13:14:27 — và map PID→pool bằng appcmd list wp xác nhận WP "7008" (applicationPool:SolutionErp-Api). ⚠️ Map này KHÔNG thừa: PID 7680 cũng start 13:13:09 nhưng là vietreport-admin (dự án khác) — đọc vội sẽ tưởng pool ta restart 2 lần.
  3. Cả 3 mốc (13:14:27 / :33 / :40) nằm gọn trong cửa-sổ run #442 (13:08:26 → 13:14:51).

(b) sqlcmd đếm row — PRE 1 → POST 3

phép đo PRE (13:06) POST (13:16) phán
COUNT(*) Permissions WHERE MenuKey='Contracts' AND CanCreate=1 1 3 đúng kỳ vọng
COUNT(*) Permissions WHERE MenuKey='Contracts' (mọi cờ) 13 13 KHÔNG đẻ row ⇒ chứng "0 added"
COUNT(*) Permissions (toàn hệ) 2221 2221 hệ quả tất yếu của 0-added

3 role có CanCreate=1 POST + hình-dạng cờ (không chỉ đếm):

Admin       | create=1 read=1 upd=1 del=1   ← vốn đã đủ từ trước (không bị đụng)
Drafter     | create=1 read=1 upd=0 del=0   ← NÂNG, và KHÔNG tràn sang Update/Delete
Procurement | create=1 read=1 upd=0 del=0   ← NÂNG, và KHÔNG tràn sang Update/Delete

⇒ vừa đúng số, vừa đúng hình dạng (không over-grant) — thứ mà đếm-tổng đơn thuần không thấy được.

(c) Idempotent — ⚠️ PARTIAL: chứng ở tầng-row (ĐO ĐƯỢC), chưa chứng ở tầng restart×2 (không quan-sát-được)

Đo được (mạnh): STARTUP_COUNT('Applying migrations...') trong log = 1 ⇒ trong cửa-sổ này pool SolutionErp-Api restart đúng 1 lần. Giả định của lead "run-1 + run-2 = restart ×2 miễn phí" KHÔNG thành vì run #441 bị giết trước bước deploy (M1) ⇒ không có lần restart thứ hai để đo. Tao KHÔNG tự recycle pool prod (READ-only, anti-pattern #6/#8).

Nhưng nhánh no-op ĐÃ CHẠY THẬT và đo được — nhờ Admin đóng vai control sẵn có: code đếm (DbInitializer.cs, diff K7):

if (existing.TryGetValue(role.Id, out var row)) {
    var changed = false;
    if (!row.CanRead)   { row.CanRead   = true; changed = true; }
    if (!row.CanCreate) { row.CanCreate = true; changed = true; }
    if (changed) upgraded++;      // ← CHỈ tăng khi cờ THỰC SỰ lật
    continue;                     // ← row có sẵn ⇒ KHÔNG BAO GIỜ Add
}

Seeder chạy trên 3 role nhưng báo 2 upgraded (không phải 3) ⇒ Admin bị bỏ qua vì đã đúng sẵnđó chính là nhánh idempotent, đã thực thi trên prod và đo được, chứ không phải suy từ code.

Suy (chưa đo — khai rõ): restart lần 2, cả 3 row đã ở trạng thái đích ⇒ added=0, upgraded=0 ⇒ do guard if (added > 0 || upgraded > 0) bọc câu log, sẽ KHÔNG in gì cả (IM LẶNG) — chứ không in 0 added + 0 upgraded. 🔎 Ai verify lần sau: đừng đọc "log không có dòng seeder" thành hồi-quy — đó là hình-dạng ĐÚNG của lần 2. Phép đo rẻ nhất cho (c) ở deploy kế: đếm lại COUNT (phải vẫn 3) + STARTUP_COUNT (phải ≥1 ở log mới).


M6 — Smoke (kể cả endpoint MỚI)

Bearer admin OK (accessToken len=468, roles=['Admin']).

phép thử kỳ vọng thực đo
POST /api/auth/login (admin) 200 + token 200, token 468 ký tự
GET /api/contracts 200 200
GET /api/purchase-evaluations 200 200
GET /api/menus 200 200
GET /api/contract-signing-plans 200 200
control âm GET /api/contracts KHÔNG token 401 401
control âm GET /api/bogus-route-xyz + admin 404 404
🎯 MỚI POST /api/contract-signing-plans/{0-guid}/create-contract + admin 404/400, KHÔNG 403 400
🎯 MỚI cùng endpoint, không token 401 401

Wire-proof mạnh hơn cả mã số — thân 400 là lỗi bind/validate của chính command K7, tức request đã đi xuyên authz vào tới FluentValidation:

{"status":400,"errors":{"PlanId":["'Plan Id' must not be empty."],
                        "ApprovalWorkflowId":["'Approval …"]}}

⇒ endpoint đã wire thật, policy không chặn nhầm Admin. (403 mới là FAIL — không xảy ra.)

⚠️ Hạn chế trung thực — tầng-2 (#82) của Drafter CHƯA đo LIVE được

nv.test@solutions.com.vn / TestUser@123456401 "Email hoặc mật khẩu không đúng" (tài khoản UAT không đăng nhập được ở thời điểm đo). Vì vậy:

  • Đã đo: row Contracts của Drafter/Procurement có CanCreate=1 (tầng dữ-liệu/hiển-thị).
  • CHƯA đo: một principal Drafter thật gọi endpoint và KHÔNG bị 403 (tầng API-authz).
  • Bằng chứng thay thế (CI, không phải prod): KhkkBridgeAuthzTests 5/5 PASS trong gate #442.
  • ⚠️ Admin qua được không tách bạch được công của row vs admin-bypass (chính comment seeder cũng nói vậy) ⇒ không dùng kết quả Admin để tuyên bố grant Drafter đã ăn ở tầng API. Đề nghị: UAT đăng nhập 1 tài khoản Drafter thật bấm nút, hoặc cấp lại mật khẩu nv.test rồi đo lại 1 lệnh curl.

📌 UAT — NÓI TRƯỚC (ghi theo yêu cầu lead)

Nút "Đưa vào Hợp đồng" chỉ sáng cho Drafter / Procurement SAU KHI USER ĐĂNG XUẤT & ĐĂNG NHẬP LẠI. Lý do: menu/permission được client cache sau lần login trước; quyền Contracts vừa được nâng ở phía server lúc 13:14:40 hôm nay nên phiên đang mở vẫn mang bộ quyền CŨ. Không phải lỗi deploy, không cần báo bug — chỉ cần re-login. (Admin không thấy khác biệt vì vốn đã có quyền.)


Tổng kết 6 mục

# Mục Phán Bằng chứng gọn
1 Run aaed699 success 🔴 LỆCH #441 cancelled 13:07:59 (bị push 628132c giết); K7 ship qua #442 success 6m25s, ancestry aaed699 ⊂ 628132c đã verify
2 Gate 639 = 45+594 PASS log web-UI tách phần; Δ+17 = 12+5 [Fact] khớp từng đồng; 0 fail/0 skip; flaky xanh lần 4
3 Mig 71 + tables 97 PASS git 0-file Migrations ⟂ sqlcmd TOP + COUNT=71 + sys.tables=97
4 Bundle rotate ×2 + marker PASS Ig1R5tAh→RgvHZkPH, DFOCQ2ks→D65FJPGd; LM in-window; hash+size khớp log CI; marker 8/8 × 2 app + 2 control dương + 2 control âm
5 Seeder restart-proof 🟡 PARTIAL (a) log verbatim 0 added + 2 upgraded (b) COUNT 1→3, row 13→13 (c) restart×2 không quan-sát-được (chỉ 1 startup) — nhánh no-op vẫn chứng được qua Admin
6 Smoke + endpoint mới PASS 4 GET 200 · 401/404 control · endpoint mới 400 (không 403) + 401 khi thiếu token; ⚠️ Drafter-live chưa đo (login UAT hỏng)

Việc cần lead xử:

  1. Gotcha #86 (đề xuất mới) — push-đè nuốt deploy: nếu commit thứ 2 rơi trọn paths-ignore thì code commit thứ 1 im lặng không bao giờ lên prod. Lần này thoát nhờ may. Luật: sau 2 push liên tiếp phải kiểm "run cuối có head_sha = TIP" "TIP ⊇ commit code".
  2. Mở lại tài khoản UAT nv.test (hoặc cấp 1 Drafter khác) để đóng nốt tầng-2 #82 cho K7.
  3. Deploy kế: đếm lại Contracts/CanCreate=1 (phải vẫn 3) để khép mục 5(c).

END cicd-k7 — VERDICT=PARTIAL (K7 ĐÃ LIVE, 4/6 PASS sạch + 1 LỆCH-cơ-chế run bị huỷ + 1 PARTIAL restart×2 không quan-sát-được; 0 defect trong code K7)