Verdict sản phẩm: `998ea55` (KHKK 3-panel mirror Duyệt NCC) = PASS. Test-gate #447 644/644 Failed 0 khớp baseline tách-phần Δ0 · bundle 4/4 rotate khớp log CI tới byte · 2 endpoint MỚI 401 · mig repo 71 = prod 71 set-diff 0/0 hai chiều, tables 97 · smoke 8/8. Không rollback, prod khoẻ. Chân #44/#85 đóng bằng đo — nấc cũ "credential UAT chết" SAI (tra nhầm account đời cũ; bộ sống ở HANDOFF slot 64). Phạm vi = 2 tổ-hợp vai (Drafter+Procurement · CostControl+DeptManager); CHƯA loại nhánh vai thường khác vẫn 403 — siết 5 site/3 file sau ring2 ESCALATE-1. Bookend-close 5 vòng / 11 sub → runs/2026-08-05-S172-bookend-close/: H1 DRIFT 7 · H2 GATE-HOLD 6 · lead-stale 9 FLAG(SÀN)+1 ESCALATE · lead-gap 8 FLAG(3 HIGH) · ring1 67/69 · ring2 ĐẠT 17/17 · trio MIXED → 2 action/15 bác → MIXED-PASS 50/59 · ctx-audit TRUOT 3 FLAG. Vá stale @closeout: - STATUS:479 bundle hash (stale 2 phiên, lần 2 cùng ô) → Ajv-MaCz/YsXRkBSR - STATUS:6 counter 42→46, deep 2/15→6/15; Recently Done S171-S172 - HANDOFF segment @S172: E-7 + 5 acceptance có nhà (trước đó 0 hit/6 sổ bền), carry re-stamp sau 4 phiên bỏ, 4 site neo tuyệt đối (1 site sai DẤU), slot 67-72 - skills/README ×2 ổ số cứng nằm cạnh chính con trỏ B1 - gotcha #87 (mã pre-auth 411/415 trả lời sai câu hỏi authz) - run.md S171 hết mồ côi (0→4 hit) · 8 dir rỗng mis-land đã dọn - MIND-3 neo xuất xứ sai 2 trường (ts tương lai + HEAD stale) — ctx-audit F-1 §L.c completeness-gate: vòng 4/5 (V4 không-nhịp) | phép ĐẠT 2 / TRƯỢT 2 / vacuous 0. 2 TRƯỢT cùng một bệnh: S169·S170·S171 chạy xong mà 0 session-log durable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
26 KiB
cicd-verify — S171 KHKK 3-panel mirror Duyệt NCC (998ea55)
Lane: cicd-monitor (READ-ONLY / propose-only) · Spawn: 2026-08-04 Nguyên tắc ghi: khung rỗng lượt 1, append sau MỖI phép đo (bài học ngay trong phiên này: lane
implementer-frontendghi khung mà không append ⇒ chết ở lượt trả về ⇒ nhật-ký 642 B rỗng).
0. Đề bài PIN
| Mục | Giá trị |
|---|---|
| Commit đích | 998ea55 [CLAUDE] Contract: KHKK 3-panel mirror Duyet NCC… (CODE — không docs-only) |
| HEAD sau push | bf0200b ([CLAUDE] Docs: … — docs-only) |
| Range đã đẩy | 4b1905d..bf0200b |
| Baseline Tests (STATUS.md B1) | 644 PASS = 45 Domain + 599 Infra |
| Baseline Migrations (STATUS.md B1) | 71 — đợt này KHÔNG có mig mới (enum +ApprovalAttachment=3 không đổi schema) |
Flake đã biết (lead đo local TRƯỚC push): Domain 45/45 · Infra 598/599 — 1 FAIL =
SolutionErp.Infrastructure.Tests.Application.CreatePeWorkflowDefinitionCommandHandlerTests.Create_DifferentEvaluationType_DoesNotAffect_Other.
Bất-định đã chứng: full-suite 2→1→1, chạy riêng 0/0/0. Nghi hạ-tầng: PeWorkflowAdminTests.cs tạo new SqliteDbFixture() riêng ×6 test ⇒ flake dưới song-song xUnit.
🔴 Việc phải chốt: lead CHƯA đo được baseline trên HEAD sạch (git worktree fail vì đường-dẫn Dropbox) ⇒ "flake có TRƯỚC S171" hiện là SUY-LUẬN. CI runner sạch = đúng baseline đó ⇒ soi log ≥2 run TRƯỚC 998ea55.
1. Stage 1 — Push + path-filter ✅ ĐO 19:50
git log -1 → bf0200bc730bc40596500b6c01058b1ec6a63f5a [CLAUDE] Docs: va 4 diem stale…
git log origin/main..HEAD → (rỗng) ⇒ đã push đủ, không còn commit treo
git diff --name-only 998ea55^..998ea55 → 21 file
21 file của 998ea55 — 18 FE (fe-admin ×9 + fe-user ×9: components/khkk/*, components/pipeline/PipelineTreePanel.tsx, hooks/usePipelineStages.ts, pages/khkk/*, types/khkk.ts) + 3 BE (ContractSigningPlansController.cs, ContractSigningPlanFeatures.cs, ContractSigningPlanAttachment.cs).
Path-filter (gotcha #41) — 7-glob live trong .gitea/workflows/deploy.yml:19-26:
docs/** · **/*.md · .claude/skills/** · .claude/agent-memory/** · .claude/workflows/runs/** · .gitignore · scripts/**.md.
⇒ 998ea55 KHÔNG khớp glob nào (toàn .tsx/.ts/.cs) ⇒ CI PHẢI chạy. ✅ đúng như đề bài PIN.
⇒ bf0200b (4 file: 2 .claude/agents/*.md + docs/HANDOFF.md + docs/STATUS.md) khớp **/*.md + docs/** ⇒ tự nó docs-only.
🔴 Nhưng CI keyed theo TIP của range, không theo từng commit (AS-6): Gitea đánh giá cả push-range, ≥1 commit có file non-ignored ⇒ toàn range build, và run mang head_sha = tip = bf0200b. ⇒ KHÔNG có run riêng mang sha 998ea55 — tra theo 998ea55 sẽ ra 0 hit và dễ kết luận nhầm "CI không chạy". Run phủ 998ea55 = run của bf0200b.
2. Stage 2 — Gitea Actions poll
Lượt 1 — 19:50:56. GET /api/v1/repos/vietreport-admin/solution-erp/actions/tasks?limit=6 (anon OK, repo public):
| id | run_number | head_sha | status | created_at |
|---|---|---|---|---|
| 560 | #447 | bf0200bc |
running | 2026-08-04T19:49:08+07:00 |
| 559 | #446 | 4b1905d5 |
success | 2026-08-01T20:41:12 |
| 558 | #445 | ff7f6559 |
cancelled | 2026-08-01T20:37:54 |
| 557 | #444 | 0bfadc20 |
success | 2026-08-01T17:08:56 |
| 556 | #443 | a2b37dcc |
success | 2026-08-01T16:12:23 |
| 555 | #442 | 628132cf |
success | 2026-08-01T13:08:26 |
⇒ Run đích = #447 / id 560, head bf0200b, phủ 998ea55. Bắt đầu 19:49:08, đang chạy.
Link: https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/447
2b. 🔑 Chụp PRE kịp giờ (spawn 19:50 / deploy land ~19:55)
Đo TRƯỚC khi deploy đáp ⇒ có differential THẬT thay vì chỉ ảnh chụp cuối.
| Đo PRE @19:50-19:51 | Giá trị |
|---|---|
| admin bundle | index-DLRf9TeA.js + index-IuRRquXK.css |
| eoffice bundle | index-C7TGA4Kn.js + index-DesmJ6-_.css |
GET …/{guid0}/changelogs (no token) |
404 |
POST …/{guid0}/approval-attachments (bodyless, no token) |
411 ⚠️ (IIS Length-Required, pre-auth — KHÔNG phân biệt được) |
POST …/{guid0}/approval-attachments (có body {}, no token) |
404 ✅ (thoát 411, đo được thật) |
control ÂM POST …/{guid0}/khong-ton-tai-xyz (có body) |
404 |
control DƯƠNG GET /api/contract-signing-plans (route KHKK cũ) |
401 |
🔑 4 điểm mà bảng này khoá lại:
- Bundle PRE trùng khít baseline #444 (
DLRf9TeA/C7TGA4Kn) ⇒ prod đứng im từ 08-01, chưa có gì của S171 lên. - 2 route mới 404 PRE ⇒ chưa tồn tại ⇒ nếu POST-deploy thành 401 thì đó là wire-proof (route mới thật sự lên sóng), mạnh hơn chỉ nhìn "CI xanh".
- Control DƯƠNG 401 chứng phép-đo 401-vs-404 có răng — không phải mọi thứ ở host này đều trả 404.
- Bẫy 411 đã né: POST bodyless bị IIS chặn TRƯỚC routing ⇒ 411 vô nghĩa; phải gửi body mới đọc được 404/401.
3. Stage 3 — Test gate + phán quyết flaky vs hồi-quy ✅ ĐO 2026-08-05 (phiên nối)
Run #447 / id 560 — status=success, 19:49:08 → 19:56:33 = 7 phút 25 giây (conclusion=None là bình thường với endpoint tasks — tin status, xem MEMORY Stage 2).
Log web-UI /actions/runs/447/jobs/0/logs = 18 666 B, 2 dòng tổng kết (đối chiếu TÁCH-PHẦN, không chỉ khớp tổng):
12:49:38 Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45 — SolutionErp.Domain.Tests.dll (net10.0) [167 ms]
12:52:51 Passed! - Failed: 0, Passed: 599, Skipped: 0, Total: 599 — SolutionErp.Infrastructure.Tests.dll [1 m 50 s]
| Vế | Baseline STATUS.md (B1) | Run #447 | Δ |
|---|---|---|---|
| Domain | 45 | 45 | 0 |
| Infrastructure | 599 | 599 | 0 |
| Tổng | 644 | 644 | 0 |
| Failed | 0 | 0 | 0 |
| Skipped | 0 | 0 | 0 |
Δ = 0 đúng kỳ vọng: 998ea55 chạm 0 file test (§4.5) ⇒ không được phép có test mới. Số khớp tách-phần cả 2 project.
Grep phủ định trên log #447 (đếm thật, không suy):
Failed! = 0 · [FAIL] = 0 · error CS = 0 · Create_DifferentEvaluationType_DoesNotAffect_Other = 0 hit.
3.1 🔴 Phán quyết flaky vs hồi-quy — GIỮ 2 VẾ TÁCH BẠCH, cấm nén thành 1 câu
- ✅ Vế A — không có hồi-quy do S171. Đây là vế được chứng. Điều kiện "đủ" mà §4.3 treo lại chờ #447 nay đã đo: cây có S171, trên runner sạch, Release, 2 step tách ⇒ 599/599 Failed 0. Cộng với #446 (cây không S171 ⇒ cũng 599/599) ⇒ cặp đối-chứng khép kín, delta đúng bằng 21 file của
998ea55. Không FAIL nào khác flaky đã biết ⇒ không có hồi-quy THẬT để báo động. - ❌ Vế B — vẫn KHÔNG chứng được "flake pre-exist". #447 là log CI thứ 4 liên tiếp có 0 hit tên test đó ⇒ tổng cộng 0/4 log CI từng tái hiện. Runner tiếp tục không nổ. Đây vẫn là vắng mặt bằng chứng, không phải bằng chứng vắng mặt. Nguyên trạng §4.3/§4.4: flake chỉ nổ ở local (2 project cùng lượt + máy nhiều tiến-trình), CI xanh không rửa sạch được nó và cũng không xác nhận được nó.
⇒ Test gate: PASS. Nợ kỹ-thuật PeWorkflowAdminTests.cs new SqliteDbFixture() ×6 vẫn treo, chưa ai chứng được nguồn gốc bằng CI.
4. Stage 3b — Baseline lịch-sử: flake có TRƯỚC S171 không? ✅ ĐO 19:52-19:54
4.1 Cô-lập delta: run #446 XANH ⟶ run #447, ở giữa đúng bằng 998ea55
git diff --name-only 4b1905d..bf0200b | (loại 7-glob paths-ignore) → 21 file
21 file đó trùng khít 21 file của 998ea55. ⇒ Run #446 (4b1905d) và run #447 (bf0200b) khác nhau đúng một commit code. Đây là cặp đối-chứng sạch nhất có thể có, miễn phí — không cần git worktree (thứ đã fail vì đường-dẫn Dropbox).
4.2 Log CI các run TRƯỚC 998ea55 (web-UI /actions/runs/{n}/jobs/0/logs)
| Run | head_sha | Domain | Infra | Dòng Failed!/[FAIL] |
Nhắc tên test flaky |
|---|---|---|---|---|---|
| #446 | 4b1905d ← base ngay trước S171 |
45/45 | 599/599 | 0 | 0 |
| #444 | 0bfadc2 |
45/45 | 599/599 | 0 | 0 |
| #443 | a2b37dc |
45/45 | 594/594 | 0 | 0 |
Passed! - Failed: 0, Passed: 599, Skipped: 0 (log #446, 18 657 B).
4.3 🔴 Kết luận trung thực — CI KHÔNG chứng minh được "flake có sẵn"
Mệnh đề lead cần chốt: "flake này có TRƯỚC S171". CI trả lời được một nửa, và tao nói rõ nửa nào:
- ✅ Bác được "hồi-quy tất định": run #446 chạy chính cái cây tiền-S171 trên runner sạch ⇒ Infra 599/599, Failed 0. Nếu S171 gây lỗi thì #446 (không có S171) phải xanh — nó xanh — nhưng đó là điều kiện cần, chưa đủ. Phần "đủ" nằm ở #447 (§3).
- ❌ KHÔNG chứng được "flake pre-exist": chưa run CI nào từng tái hiện test này fail (0 hit tên test trong cả 3 log). Một flake không nổ trên runner thì log xanh không phải bằng chứng nó có sẵn — nó chỉ là vắng mặt bằng chứng. Ghi đúng như vậy, không nâng cấp thành "đã chốt".
4.4 Vì sao runner khó tái hiện — 3 khác biệt đo được (không suy đoán)
| Trục | Local (lead) | CI (deploy.yml:64-80) |
|---|---|---|
| Phạm vi lệnh | dotnet test SolutionErp.slnx (2 project cùng lượt) |
2 step TÁCH: dotnet test tests/…Domain.Tests.csproj rồi …Infrastructure.Tests.csproj |
| Cấu hình | mặc định (Debug) | --configuration Release |
| Máy | workstation, nhiều tiến-trình agent song song | runner riêng |
Mẫu-số chung của flake vẫn giữ nguyên 2 phía: PeWorkflowAdminTests.cs new SqliteDbFixture() × 6 (đếm grep -c = 6, khớp chẩn đoán của lead) ⇒ 6 SQLite riêng dựng/huỷ dưới song-song-collection của xUnit. Chạy 2 project cùng lượt (local) làm tăng tranh-chấp CPU/IO ⇒ hợp lý là local dễ nổ hơn CI, và đó cũng là lý do CI xanh không rửa sạch được flake.
4.5 Trục nhân-quả: S171 có chạm được vào test đó không?
git diff --name-only 4b1905d..bf0200b | grep -iE 'tests?/'⇒ 0 file test bị chạm.- 3 file BE của
998ea55đều thuộcContractSigningPlans(KHKK); test flaky thuộcCreatePeWorkflowDefinitionCommandHandler(PE workflow) — hai module rời. - File chứa flake
PeWorkflowAdminTests.cssửa lần cuối 2026-05-08 (dbb0089), tức ~3 tháng trước S171.
⇒ Không có đường nhân-quả nào từ 998ea55 tới test đó.
4.6 Quét TOÀN BỘ 446 task lịch sử: CI đã bao giờ đỏ vì test này chưa?
API trả về 446 task (limit=30 nhưng server trả hết). Lọc status != success|running:
- Kỷ nguyên gần (từ 2026-06-16 tới nay): DUY NHẤT 1 run
failure= #291 (8c8179cd, 06-16). Còn lại chỉ làcancelled(7 lần — push-đè, gotcha #86). - Các
failurekhác đều nằm ở 04-2026/05-2026 (giai đoạn dựng pipeline: #1-#15, #29-#45, #85, #108-#111, #125-#127, #196/#197, #215).
Loại trừ #291 bằng ĐO, không bằng trí nhớ: log #291 (9 326 B) → grep -c tên test flaky = 0; Domain Passed! - Failed: 0, Passed: 45; nguyên nhân thật = error CS7036 ⇒ lỗi biên-dịch, chưa từng chạy tới test Infra. Không liên quan.
⇒ Chốt §4: trong toàn bộ lịch sử CI, Create_DifferentEvaluationType_DoesNotAffect_Other chưa từng làm đỏ một run nào.
5. Stage 4 — Ship thật? (FE bundle byte-level ×2 app) ✅ ĐO 2026-08-05 (phiên nối)
5.1 Hash PRE → POST: rotate cả 2 app, và khớp log CI tới từng byte
deploy.yml:111-122 build fe-admin trước, fe-user sau ⇒ ánh xạ 2 khối build trong log là xác định, không phải đoán.
| App | PRE (§2b, = baseline #444) | POST (live index.html) |
Log CI #447 | Content-Length |
Log CI (kB) |
|---|---|---|---|---|---|
admin .js |
index-DLRf9TeA.js |
index-Ajv-MaCz.js |
dist/assets/index-Ajv-MaCz.js @12:54:41 |
1 783 465 | 1,783.46 kB ✅ |
admin .css |
index-IuRRquXK.css |
index-Cd15r15R.css |
dist/assets/index-Cd15r15R.css |
86 551 | 86.55 kB ✅ |
eoffice .js |
index-C7TGA4Kn.js |
index-YsXRkBSR.js |
dist/assets/index-YsXRkBSR.js @12:55:53 |
1 701 224 | 1,701.22 kB ✅ |
eoffice .css |
index-DesmJ6-_.css |
index-BpzM6R0V.css |
dist/assets/index-BpzM6R0V.css |
92 289 | 92.28 kB ✅ |
4/4 rotate. Kích-thước live khớp con số Vite in trong log CI tới từng byte ⇒ file trên đĩa prod chính là artifact run #447 sinh ra, không phải bản nào khác.
5.2 Last-Modified nằm TRONG cửa-sổ deploy (ship-proof theo #69, không dựa hash-delta)
Run #447: 19:49:08 → 19:56:33 (+07). Last-Modified (UTC) = 12:54:41 (admin ×2 file) và 12:55:53 (eoffice ×2 file) = 19:54:41 / 19:55:53 +07 ⇒ đúng trong cửa-sổ, và trùng khít giây với dòng build tương ứng trong log. Không có FROZEN, không có LM cũ ⇒ loại sạch nhánh "bundle cũ kẹt".
5.3 Control ÂM tầng-2 — bundle CŨ đã biến mất khỏi đĩa
| Hash cũ | HTTP | Content-Type |
Size |
|---|---|---|---|
admin/assets/index-DLRf9TeA.js |
200 | text/html |
900 B |
eoffice/assets/index-C7TGA4Kn.js |
200 | text/html |
876 B |
200 ở đây là bẫy SPA-fallback, đã né đúng cách: discriminator là Content-Type chứ không phải mã HTTP. text/html ~876-900 B ⇒ file cũ không còn trên đĩa, IIS rewrite /*→index.html ⇒ chứng minh ghi đè thật, không phải cộng thêm.
5.4 Byte-marker (gotcha #77 — phân biệt deploy vs cache), có control 2 chiều
Marker chọn theo luật "0-hit PRE phải chứng được", đo bằng git grep -F trên cả 2 cây rồi python str.find trên bundle tải về (không grep -P — chết locale Git-Bash):
| Marker | 998ea55^ (admin/user) |
998ea55 (admin/user) |
Bundle admin | Bundle eoffice |
|---|---|---|---|---|
M1 Bước này chưa cấu hình cấp duyệt. |
0 / 0 | 1 / 1 | 1 ✅ | 1 ✅ |
CTRL-DƯƠNG Chưa khai căn cứ nào. (có TRƯỚC S171) |
1 / 1 | 1 / 1 | 1 ✅ | 1 ✅ |
| CTRL-ÂM chuỗi bịa | — | — | 0 ✅ | 0 ✅ |
M3 Bước không gắn phòng. |
0 / 0 | 1 / 1 | 0 ⚠️ | 0 ⚠️ |
Ngữ cảnh trích thật quanh M1 (giống hệt 2 app, minify ra backtick):
text-[11px] italic text-slate-400`,children:`Bước này chưa cấu hình cấp duyệt.`}),e.levels.ma…
- M1 = wire-proof mã FE mới: vắng mặt ở cây tiền-commit ⇒ nó chỉ có thể tới từ
998ea55. - CTRL-DƯƠNG khoá tiền-đề "tiếng Việt đọc được trong bundle" ⇒ 0-hit của M3 không thể đổ cho unicode-escape.
- ⚠️ M3 0-hit là ĐÚNG LUẬT, không phải thiếu ship: M3 nằm trong comment
//tạife-admin|fe-user/src/types/khkk.ts:288⇒ Vite strip. Ghi lại để không ai đọc nhầm thành cờ đỏ (đúng cơ-chế đã gặp @S168).
⇒ Stage 4: PASS — 4 chân độc lập (hash rotate · size khớp-byte với log CI · LM trong cửa-sổ · marker mới có mặt + control 2 chiều) + control-âm tầng-2.
6. Stage 4b — Smoke prod + 2 endpoint MỚI (gotcha #82) ✅ ĐO 2026-08-05 (phiên nối)
6.1 Negative-control 2 endpoint MỚI: PRE 404 → POST 401 (cả hai)
Attribute thật trong ContractSigningPlansController.cs (đọc từ mã, không suy từ cây menu — luật gotcha #85):
[HttpGet("{id:guid}/changelogs")] + [Authorize(Policy = "KeHoachKyKet.Read")] · [HttpPost("{id:guid}/approval-attachments")] + [Authorize(Policy = "KeHoachKyKet.Update")] — policy per-method, đúng key của endpoint, không OR key con.
| Probe (không token) | PRE §2b | POST | Ý nghĩa |
|---|---|---|---|
GET …/{guid0}/changelogs |
404 | 401 ✅ | route MỚI đã lên sóng + authz gác |
POST …/{guid0}/approval-attachments (json {}) |
404 | 415 ⚠️ | pre-auth, xem 6.2 |
POST …/{guid0}/approval-attachments (multipart) |
— | 401 ✅ | chạm được authz ⇒ 401 thật |
CTRL-ÂM POST …/{guid0}/khong-ton-tai-xyz (multipart) |
404 | 404 ✅ | 401 KHÔNG phải phản-xạ chung của host |
🔴 Cả 2 đều 401 — không 200 (không lỗ hổng authz), không 500 (không hỏng deploy). Đúng yêu-cầu negative-control.
6.2 Bẫy 415 (họ hàng bẫy 411 ở §2b) — ghi lại để lần sau khỏi mất công
POST với Content-Type: application/json trả 415, KHÔNG phải 401. Nguyên nhân: endpoint nhận multipart (upload file) ⇒ mismatch bị loại ở tầng action-selection, tức TRƯỚC authorization filter — y hệt cơ-chế 411 của IIS. 415 chỉ chứng "route tồn tại", không chứng authz. Muốn đọc được authz phải gửi đúng multipart. Sau khi sửa: 401. ⇒ Luật: mã lỗi pre-auth (411/415) là vô nghĩa cho câu hỏi authz, phải re-probe đúng dạng.
6.3 🔑 Wire-proof MẠNH NHẤT — thông-điệp-lỗi gọi tên field (mạnh hơn 401)
POST …/{guid0}/approval-attachments + bearer admin + multipart ⇒ 400 với thân:
{"status":400,"errors":{
"ContractSigningPlanId":["'Contract Signing Plan Id' must not be empty."],
"ContentType":["Định dạng file không được hỗ trợ. Cho phép: pdf, doc(x), xls(x), png, jpg, webp."]}}
Chuỗi này chứng end-to-end Api → Application: qua routing, qua authz, model-binding đọc được file multipart, và validator MỚI của command MỚI đã chạy (nó gọi đúng 2 tên field và bung whitelist đuôi file). 401 chỉ chứng route tồn tại; 400-liệt-kê-field chứng binary mới đang phục vụ — mạnh hơn cả đo w3wp StartTime.
Đối xứng cho endpoint kia: GET …/{guid0}/changelogs + bearer admin ⇒ 404 (phiếu GUID-toàn-0 không có thật) — cùng route mà 401 khi vắng token / 404 khi có token ⇒ 401 kia đúng là authz, không phải routing.
6.4 Smoke prod (bearer admin) — 8/8 xanh
| Endpoint | Mã |
|---|---|
GET /health/ready |
200 |
GET /api/contract-signing-plans (KHKK) |
200 |
GET /api/contracts |
200 |
GET /api/purchase-evaluations |
200 |
GET /api/menus |
200 |
GET /api/suppliers |
200 |
GET /api/catalogs/contract-catalog |
200 |
POST /hubs/notifications/negotiate (có body) |
200 + connectionId |
(SignalR: bodyless trả 411 — lại đúng bẫy IIS Length-Required ở §2b; gửi kèm body thì 200 ⇒ WebSocket/negotiate lành, gotcha #25 không tái phát.)
6.5 ✅ ĐÃ ĐÓNG — chân non-admin (gotcha #44) — lead đo 2026-08-05, phiên nối S172
🧊 Nấc cũ (giữ làm vết): nv.test@solutions.com.vn / TestUser@123456 ⇒ 401 ×2 ⇒ kết luận "credential UAT chết trên prod, chân này KHÔNG ĐO ĐƯỢC". Kết luận đó SAI — không phải credential chết, mà là tra nhầm sổ: nv.test@ là account đời cũ. Bộ user test đang sống nằm ở docs/HANDOFF.md slot (64): test.drafter@ · test.approver@, password chung Test@12345678 (đổi theo policy prod ≥12 ký tự). ⇒ Bài: "KHÔNG ĐO ĐƯỢC" phải kèm câu hỏi "đã tra hết sổ có sẵn chưa?" — cùng lớp feedback_mechanism_right_data_absent (cơ-chế đúng, dữ-liệu tưởng không có nhưng thật ra có, chỉ nằm chỗ khác).
Login 200/200 cả hai. Probe bằng bearer NON-ADMIN (multipart đúng dạng theo bẫy §6.2):
| Probe (bearer non-admin) | test.drafter@ |
test.approver@ |
Ý nghĩa |
|---|---|---|---|
GET /api/contract-signing-plans |
200 | 200 | qua KeHoachKyKet.Read |
GET …/{guid0}/changelogs |
404 | 404 | 404 chứ KHÔNG 403 ⇒ qua authz, chỉ là phiếu GUID-0 không có thật |
POST …/{guid0}/approval-attachments (multipart pdf) |
400 | 400 | qua KeHoachKyKet.Update VÀ validator MỚI chạy — thân lỗi gọi đúng tên field ContractSigningPlanId |
CTRL-ÂM POST …/{guid0}/khong-ton-tai-xyz |
404 | 404 | 400/404 KHÔNG phải phản-xạ chung của host |
🔴 Kết luận — phạm-vi ĐÚNG BẰNG MẪU ĐÃ ĐO (siết @S172 sau ring2-audit ESCALATE-1): 2 tổ-hợp vai đã đo — Drafter+Procurement (test.drafter@) và CostControl+DeptManager (test.approver@) — có đủ KeHoachKyKet.Read+Update (4/4 probe, CTRL-ÂM 404). 🔴 CHƯA LOẠI nhánh "một vai thường KHÁC (vd Employee trần) vẫn 403" ⇒ #85 không tái phát TRÊN 2 TỔ-HỢP NÀY, chưa phải kết-luận toàn-cục.
🧊 Bản S172 đầu viết: "role thường có đủ … ⇒ #85 KHÔNG tái phát" + "chứng wire cho đúng role người dùng thật". Cả hai vế đều rộng hơn mẫu — mẫu là 2 tài-khoản, và HANDOFF slot (64) khai 0/2 là user trần (cả hai mang vai cấp riêng; test.approver@ còn "KHÔNG nằm trong trạm nào" nên không đại-diện được "người dùng thật"). Giữ vết vì đây là ca mẫu của class suy-luận vượt mẫu — thứ enum ĐÓNG hiện chưa có ô để đếm (đề-nghị owner mở view-claim-broader-than-sample).
⚠️ Câu hỏi MỚI đẻ ra từ phép đo này (KHÔNG phải bug — là quyết-định thiết-kế, để owner phán): test.drafter@ (Drafter+Procurement) cũng qua KeHoachKyKet.Update ⇒ người soạn phiếu upload được file-khi-duyệt. Spec 3-QĐ-owner đặt upload ở bước duyệt. Nếu chủ-ý là chỉ người duyệt mới upload thì policy đang rộng hơn ý-định. Đo được, chưa phán — xem WAL mục chờ-anh.
7. Stage 5 — Migration (kỳ vọng: KHÔNG có mig mới) ✅ ĐO 2026-08-05 (phiên nối)
NO-Mig proof = 2 vế, cả 2 đều đo:
git diff --name-only 998ea55^..998ea55 -- '*Migrations*'⇒ 0 file. Commit không đẻ mig — khớp đề bài PIN (enum+ApprovalAttachment=3là int, không đổi schema).- Lịch sử prod không nhích: vẫn 71, top vẫn
20260731085624_…(của S164/S165), không có entry nào mang dấu 04-08.
| Phép đo | Repo | Prod (ssh vietreport-vps + sqlcmd -E) |
Khớp |
|---|---|---|---|
| Số migration | 71 | 71 | ✅ |
| Mig mới nhất | 20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime |
(y hệt) | ✅ |
sys.tables (is_ms_shipped=0) |
— | 97 | ✅ = STATUS.md B1 |
Mạnh hơn khớp-tổng — set-diff 2 CHIỀU (comm -23 / comm -13 trên danh sách đầy đủ; đếm repo neo-đuôi grep -vE 'Designer\.cs$|ModelSnapshot\.cs$' để không ăn nhầm …Designer.cs):
repo \ prod → 0 dòng
prod \ repo → 0 dòng
Cả hai rỗng ⇒ hai tập trùng khít, không phải "tình cờ bằng số lượng". Baseline Mig 71 + tables 97 giữ nguyên sau đợt này.
(Chân "pool đã recycle & phục vụ binary mới" không cần đo riêng: §6.3 đã chứng bằng validator MỚI chạy thật trên prod — mạnh hơn w3wp StartTime.)
8. Verdict ✅ 2026-08-05
PASS — 🔄 7/7 chân xanh (cập-nhật S172: chân phụ non-admin ĐÃ ĐÓNG, xem §6.5)
| # | Chân | Kết quả | Bằng chứng gọn |
|---|---|---|---|
| 1 | Push + path-filter | ✅ | 21 file code, 0 khớp 7-glob; run keyed theo tip bf0200b (AS-6) |
| 2 | CI run | ✅ | #447 / id 560 status=success, 19:49:08→19:56:33 (7m25s) |
| 3 | Test gate | ✅ | 45/45 + 599/599 = 644, Failed 0, Skipped 0 — khớp B1 tách-phần, Δ0 đúng kỳ vọng |
| 4 | Ship FE ×2 app | ✅ | 4/4 hash rotate · size khớp log CI tới byte · LM trong cửa-sổ · marker M1 mới có mặt ×2 + control 2 chiều · hash cũ rơi text/html |
| 5 | Endpoint MỚI (negative-control) | ✅ | cả 2 = 401 (không 200, không 500); +400-gọi-tên-field khi có bearer = wire-proof end-to-end |
| 6 | Migration | ✅ | repo 71 = prod 71, set-diff 0/0 hai chiều, tables 97, commit chạm 0 file mig |
| — | Smoke prod | ✅ | 8/8 200 (kèm SignalR negotiate 200 + connectionId) |
| 7 | Non-admin (gotcha #44/#85) — phạm-vi = 2 tổ-hợp vai | ✅ 🔄 ĐÓNG @S172 (siết phạm-vi cùng ngày) | Drafter+Procurement + CostControl+DeptManager: login 200 · list 200 · changelogs 404 (không 403) · attach 400 gọi tên field · CTRL-ÂM 404 ⇒ #85 không tái phát TRÊN 2 TỔ-HỢP NÀY; 🔴 chưa loại nhánh vai thường khác (vd Employee trần) vẫn 403 |
Việc cần em main xử (theo thứ tự)
- ✅
Cấp lại tài-khoản non-admin để đóng chân #44— XONG @S172: không cần cấp mới, account đang sống nằm ở HANDOFF slot (64); nấc cũ tra nhầmnv.test@đời cũ. Xem §6.5. - Flake
Create_DifferentEvaluationType_DoesNotAffect_Othervẫn treo — 0/4 log CI tái hiện. Đừng nén "CI xanh" thành "flake đã hết"; muốn chốt thì phải tấn côngPeWorkflowAdminTests.cs(new SqliteDbFixture()×6) chứ CI không trả lời hộ được. Đây nay là việc treo DUY NHẤT của đợt. - ⚠️ MỚI (đẻ từ §6.5, chờ owner phán):
test.drafter@cũng quaKeHoachKyKet.Update⇒ người soạn upload được file-khi-duyệt. Policy có thể rộng hơn ý-định spec. Đo được, chưa phán. - Không có gì để rollback. Prod khoẻ.
END cicd-verify 998ea55 — VERDICT=PASS (🔄 S172: 7/7 chân xanh; chân non-admin đóng bằng đo, không còn "KHÔNG ĐO ĐƯỢC").