Files
solution-erp/.claude/governance/reinject-ledger.md
pqhuy1987 55a01643ae
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 10m0s
[CLAUDE] Contract: chặn mọi đường tạo HĐ ngoài KHKK — YC-040 (106)=(b), chuỗi NCC→KHKK→HĐ khép kín
Owner chốt: "(b) Chặn HẾT 7 loại — mọi HĐ đều phải từ KHKK -> Tao nghĩ nên như vậy trước đã."

BE: ContractCreationGate (1 chỗ, AllowedDirectTypes rỗng — nới = thêm loại, không sửa handler)
chặn 409 ở 2/3 write-site: POST /contracts (tạo tự do) + POST /purchase-evaluations/{id}/
create-contract (PE→HĐ 1-click nhảy cóc KHKK, di tích Phase 6). Bridge KHKK giữ nguyên
(write-site duy nhất). Gate đứng TRƯỚC mọi load — không lộ tồn tại dữ liệu. Seeder demo
là write-site miễn-trừ có khai (flag-gated).

FE ×2 app (8 lối): menu Create per-loại ẩn (Layout, policy Ct_*_Create giữ DB) · nút
"Tạo HĐ mới"/"Thêm mới" → "Tạo từ KHKK" trỏ /khkk/list (MyContracts + Dashboard +
ContractCreatePage) · ?mode=new → EmptyState chỉ đường KHKK · nút PE→HĐ ẩn (PeDetailTabs).
ContractCreatePage + PeDetailTabs giữ SHA-mirror 2 app.

Tests 697 pass + 6 skip / 0 fail (total 703): +4 gate mới (quét-trọn-enum fail-closed +
chặn-trước-NotFound + đổi message ApplicableType); 6 test multi-winner PE→HĐ chuyển
Skip-có-khai (spec di sản Mig 58, mở lại khi owner nới gate). Reviewer PASS_WITH_FIXES,
6/6 issue vá trong lượt (kể cả 3 chuỗi EmptyState/ListPanel còn mời tạo trực tiếp).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 19:39:28 +07:00

149 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# REINJECT-LEDGER — Harness-17 CG-1 termination ledger (SOLUTION_ERP)
> **Sổ-ghi chốt-dừng (termination) cho vòng tự-cải-thiện bộ-nhớ (Harness-17 §I).** Ghi mỗi lần một **mục-sàn floor-rot** (đã-từng-ở-hot-mem nay RỚT) được **reinject verbatim** trở lại L1 — để **CG-1** chặn reinject-loop vô-hạn.
>
> 🔴 **Vì sao file RIÊNG (không nhét vào `memory-budget.json`):** `memory-budget.json` = **quyền cấp-ngân-sách của anh (project-owner)** — MFE + audit chỉ **đọc** `token_governor`, KHÔNG ghi (ranh-giới §G.4(4), mark `RC-…21-06…01-58-01`). Trộn reinject-state vào đó = AI lấn quyền cấp-ngân-sách. Ledger này = **git-tracked · append-only · single-writer em-main (D9)** — mirror provenance-model của [`ACTIVE-MARKS.md`](ACTIVE-MARKS.md). NEVER overwrite dòng cũ.
## CG-1 rule (chốt-dừng — H17 mục B2)
- Mỗi `item-id` được **reinject TỐI-ĐA 1 lần trong N phiên** (SE solo-dev: **N = 3**). N nhỏ hơn multi-dev vì solo = 1 người-điều-phối, feedback-loop nhanh (đủ 3 phiên để work-state block bơm lại "ngấm").
- Sau reinject, phiên MFE kế **đo-lại**: nếu mục ĐÃ hiện diện lại trong L1 → `status=resolved`, **đóng chu-kỳ** (KHÔNG reinject nữa).
- Nếu đo-lại **VẪN thiếu****KHÔNG** tự-reinject lần-2 (KHÔNG ghi `attempt=2`). Thay vào → `status=escalated` + **báo anh (project-owner)**: có thể **build-gap** (cơ-chế bơm chưa đủ — cần extend) HOẶC cần anh quyết **tăng-budget / đổi-placement** (mirror MFE H3 2-ca: thiếu-CHỖ→tăng-budget-anh-quyết vs rot→sắp-xếp-lại). AI **KHÔNG** tự-loop.
- **Chỉ floor-rot mới vào ledger này** (phân-loại qua phép-thử B3 §I): item **ĐÃ-từng-tồn-tại ∩ CÒN-giá-trị ∩ RỚT-khỏi-L1**. Item chưa-từng-dựng = **build-gap** (đi dựng cơ-chế, KHÔNG reinject). Item hết-giá-trị = để **cold-archive** (rớt đúng-đắn).
## Schema (mỗi dòng = 1 chu-kỳ reinject; append newest xuống cuối)
| Cột | Nghĩa |
|---|---|
| `item-id` | id ổn-định mục-sàn rớt (RC-sig · `AS-N` · `gotcha #N` · guard-name) |
| `source` | file canonical chứa verbatim (`ACTIVE-MARKS.md` · `error-ledger.md` · `gotchas.md`) |
| `detected` | phiên MFE/audit phát-hiện floor-rot (`S<NN>`) |
| `reinjected` | phiên em-main reinject verbatim (`S<NN>`) |
| `attempt` | lần thứ mấy (rule ≤1 trong N=3; nếu cần lần-2 → **escalate**, KHÔNG reinject) |
| `remeasured` | phiên MFE đo-lại sau reinject (`S<NN>`) |
| `status` | `reinjected``resolved` (đo-lại đủ) HOẶC `escalated` (đo-lại vẫn thiếu → anh quyết) |
> **Cách đọc (ví-dụ minh-hoạ — KHÔNG phải dòng thật):**
> `| gotcha #57 | docs/gotchas.md | S96 | S96 | 1 | S97 | resolved |` = chu-kỳ đóng đúng: S96 audit FLAG gotcha #57 rớt khỏi L1 → em-main APPEND verbatim từ `gotchas.md` → S97 đo-lại thấy lại → `resolved`, dừng.
> Chu-kỳ **escalate**: reinject `attempt=1` @S96 → S97 đo-lại VẪN thiếu → `status=escalated`, báo anh (build-gap? tăng-budget? đổi-placement?) — KHÔNG ghi `attempt=2`.
## Ledger (append-only — single-writer em-main)
| item-id | source | detected | reinjected | attempt | remeasured | status |
|---|---|---|---|---|---|---|
| CG-1 `front-end-reviewer-style` (floor-rot sub-class **clobber-rot** — mất H1 + LUẬT LÕI owner) | `.claude/agent-memory/front-end-reviewer-style/MEMORY.md` | S195 | S195 (UNION `e470bddf^`, commit `24566a50`) | 1 | **S197** (lead đo TƯƠI cùng lượt: `wc -c` = **5.913 B ≥ ngưỡng 5.800** + H1 dòng-1 còn; khớp `harness-eval` I1 bản PIN) | **resolved** |
> 🧊 *Dòng trên = dòng THẬT đầu tiên của bảng (@S197, thi hành `harness-refine` A1). Placeholder cũ "(chưa có floor-rot nào được reinject — ledger khởi-tạo trống S95)" đứng ở đây S95→S197 và từ @S195 đã TỰ MÂU THUẪN với prose `:100` (attempt=1) — `harness-audit` N2 @S197 bắt; lật trong CÙNG lượt ghi để lượt đóng nợ không đẻ neo thối mới. CHỐT-DỪNG giữ nguyên: tick sau nếu file <5.800 B hoặc mất H1 ⇒ ESCALATE, CẤM attempt-2.*
---
> **Nấc honest:** ledger = **convention** (em-main append tay, git-tracked audit-trail — KHÔNG OS-hook auto-write, nhất-quán CAVEAT engine "no-OS-hook"). Tín-hiệu floor-rot feed = **mechanized** (`mfe-eval.ps1` age-band + `memory-selfimprove-audit.ps1`). Detection per-item-L1-presence hiện **partial** (MFE age-band chỉ đo marks-có-date; AS/guard/gotcha mang session-ref chưa có pass so-từng-item-trong-L1 — xem §I honest nấc + CAVEAT C4 self-blind-spot).
---
## Re-verify: presence-not-age selector (broadcast `ab6c387e`, adopt S115 · 2026-07-13)
> AI_INFRA broadcast `ab6c387e` (type=**update**) — re-verify the memory refine-step **selector** decides by PRESENCE/COVERAGE, not AGE. Verdict per floor:
- **(i) Reinject = MET (presence-based).** Trigger = **floor-rot** `"ĐÃ-từng-ở-L1 ∩ CÒN-giá-trị ∩ RỚT-khỏi-L1"` (coverage-gap, this file `:12`) = the broadcast (i) condition "should be present but is missing." Age plays no part in *what-to-reinject*. The CG-1 `reinjected` session column is a **reinject-event rate-limiter / loop-breaker (≤1 per N=3 sessions), NOT an age-rank** → OK per broadcast (iii) carve-out ("date used only for last-seen/loop-breaking = OK"). Drop-date test: dropping the column changes only the rate-limit/termination count, never the selection.
- **(iii) MFE age-band = FLAG-only (COMPLIANT).** `mfe-eval.ps1` `$oldN` is computed then `Write-Host`-printed ("KEPT status-driven age-blind") with **no downstream consumer**; the denominator is gated by STATUS, not date. Drop-date test: changes one diagnostic count, not the denominator / FIT / Goodhart / any selection.
- **(ii)+(iii) Archive-gate = RED-FLAG surfaced → HARDENED (D3, S115).** The planner's oldest-by-position base ordering tripped the (iii) self-check; fixed in `scripts/memory-archive-gate.ps1` by promoting `value_protect` from an advisory post-hoc flag to a **pre-selection HARD-SKIP** (value-primary; position = within-low-value tiebreak) + a value-floor WARN. Fault-injection-verified (protected-not-drained + permutation-invariant + value-floor). See `memory-budget.json:value_protect._note`.
**BUILD-GAP (honest, disclosed):** mechanized **per-item-L1-presence** detection is still PARTIAL — the MFE age-band counts *present* marks (by date), not per-item *drops*; AS/guard/gotcha carrying session-refs have no per-item-in-L1 pass yet. So reinject-detection stays **convention + em-main judgement**, not full mechanization (do NOT overclaim). Consistent with §I honest nấc + CAVEAT C4.
**3 honest-notes (broadcast §5 — MANDATORY):**
1. Basis = **ONE** occurrence already fixed elsewhere → **proactive-prevention**, NOT a spreading SE incident.
2. **SPECIFIC-APPLICATION**, not a new rule — SE reinject + MFE age-band were already presence/flag-based (quick re-verify, no change); only the archive-gate needed hardening.
3. Floor = **FUNCTION not FORM** — implemented in SE's own script shape; no hub structure/filenames copied.
> **Provenance:** `/fable-clone reviewer` 5-lane ensemble (`wf_b621aac4-f0b`) → spec `runs/2026-07-13-presence-not-age-adopt/spec-presence-not-age-adopt-13-07-2026.md` → `/fable-real reviewer` deep-pass (PASS-WITH-FIXES; M1+M2 applied) → HMW execute D1/D2/D3. Applies existing mark `RC-…10-29-11` (age=false-proxy) to the selector layer; codify-only (no new mark).
---
## S185 (2026-08-10) — 2 mục MFE MISS lần đầu CÓ TÊN ⇒ phân loại B3 = **BUILD-GAP, KHÔNG reinject**
**Bối cảnh:** `-Detail` được cắm vào `session-end §L.b(c)` @YC-019(6) (switch tồn tại từ đầu, **0 call-site suốt 12 phiên**). Lần chạy đầu tiên có cờ này, 2 mục MISS thoát khỏi trạng thái vô danh:
| Vai | Điểm | Mục MISS (nguyên văn máy in) | Nhà trong role-file |
|---|---|---|---|
| `implementer-backend` | 17/18 = 94% | `❌ Integration testing multiple components` | `.claude/agents/implementer-backend.md:34` (auto-refuse #5) |
| `implementer-frontend` | 12/13 = 92% | `❌ < 30 min trivial` | `.claude/agents/implementer-frontend.md:31` (auto-refuse #6) |
**Phân loại B3 (bắt buộc TRƯỚC reinject) = BUILD-GAP, không phải floor-rot.** Bằng chứng đo: `grep -ci` trên diary 2 vai = **0/0** — 2 mục này **chưa bao giờ xuất hiện rồi rơi**; chúng chưa bao giờ được **kích hoạt**. Lý do cơ chế: "integration test nhiều component" ở SE luôn về `test-specialist` (ranh vai), còn "< 30 min trivial" thì lead tự làm chứ không spawn sub 2 vai **chưa từng gặp ca để từ chối**.
🔴 **Quyết định: KHÔNG reinject-verbatim.** Nhồi 2 dòng này vào diary sẽ đẩy 94%→100% / 92%→100% **không thêm một giá trị nào** đúng hình dạng Goodhart `feedback_goodhart_leave_measurement_set` tả: đổi hình-dạng thứ đang bị đo thay đổi thực-chất. CG-1 (≤1/N=3) **không tiêu quota** không sự kiện reinject nào.
**Hệ quả phải khai khi đọc số MFE về sau:** trần thực tế của 2 vai này **< 100% cho tới khi ca thật** 94%/92% đây đọc *"chưa gặp ca"*, KHÔNG phải *"trí nhớ rot"*. Ai thấy số này rồi "" cho tròn 100% đang xoá **chứng nhân**, không phải xoá lỗi.
**Điều-kiện lật (làm cho phán quyết này falsifiable):** nếu về sau một trong 2 vai **thực sự từ chối** một task bằng đúng tiêu chí đó diary vẫn 0-hit lúc ấy mới **floor-rot thật** reinject theo CG-1.
---
## CG-1 attempt #1 — `front-end-reviewer-style` · REINJECT THI HÀNH (2026-08-14, S195)
**Phân loại B3 = FLOOR-ROT**, sub-class MỚI: **`clobber-rot`** (rơi do bị ĐÈ, không do drain). Đây **lần đầu tiêu quota CG-1** sổ trước đó 0 dòng reinject.
**Trigger:** `harness-eval` §3 trục #14 + `harness-refine` A1 `.claude/agent-memory/front-end-reviewer-style/MEMORY.md` **4.721 B → 1.192 B = 3.529 B (74,8%)** tại commit `e470bddf`.
**4 bằng chứng độc lập chốt là CLOBBER, không phải curate:**
1. Cùng commit `e470bddf` chạm **9 diary — 8/9 là APPEND THUẦN** (`+7 0`, `+10 0`; +1.154…+1.690 B). Riêng 1/9 **`+5 17`**. Cùng một thao tác seed, 8 con làm đúng **ý định là append**.
2. Commit đó **KHÔNG tạo file archive nào** (`--name-status`: 2 file `A` đều thuộc `.claude/sessions/`) 3.529 B **không đi đâu cả**.
3. **Mất luôn dòng H1.** Quét 29 diary: **đúng 1/29** dòng đầu không phải H1 chính con này. **28/29 sạch = control dương**, thước phân giải.
4. **Blob còn nguyên** tại `e470bddf^` = 4.721 B **hồi phục được, KHÔNG mất trắng**.
**Cái mất là loại đắt nhất, không phải log:** LUẬT LÕI nguyên văn của owner (*"tính năng đã deploy production chuẩn, trừ khi tao chỉnh lại"*) · toàn bộ **địa phận/ranh vai** (`fe-admin/src/**` + `fe-user/src/**`; soi style không soi logic) · bản thước dispatch **SỬA-1** (thứ quyết định vai **có được gọi hay không**) · 2 bài citation-trap. Vai vẫn sống (`.claude/agents/front-end-reviewer-style.md` 12.960 B) lần spawn tới vào việc **không còn định nghĩa địa phận lẫn luật owner**, diary chỉ còn *một ghi chú rằng nó đã chết*.
**Thi hành:** **UNION trên đúng 1 path** `git show 'e470bddf^:<path>'` + append nội dung hiện .
🔴 **BÁC `git revert e470bddf`** (`harness-refine` BÁC-14): revert sẽ **xoá entry S194** (ghi nhận một lần vai chết = dữ liệu thật), **lật ngược 8 file anh em đã append ĐÚNG**, đụng 2 file `.claude/sessions/`.
**Verify sau thi hành (4 phép, đo tươi):** `wc -c` = **5.913 B** 🔴 **khớp BYTE-EXACT dự đoán 5.913 B của `harness-refine`** · H1 `# front-end-reviewer-style Agent — Persistent Memory` đã về · `grep -c '^## S194'` = **1** (không nhân đôi, không mất) · `grep -c 'SINH SỔ … SỬA-5a'` = **1** (nội dung không lặp) · 3 hit các mục load-bearing kể trên.
Bản chứa **0 hit `S194`** UNION an toàn by-construction, không đếm đôi.
**CHỐT-DỪNG (theo `harness-refine`):** attempt = **1**, lần đầu trên vật này. Tick sau nếu file **< 5.800 B** HOẶC vẫn thiếu H1 **ESCALATE lead, CẤM attempt-2**.
**Nợ KÈM — thuộc lead, KHÔNG phải corpus (ES-3):** **đường ghi** đẻ ra clobber này (Write đè thay append) lọt qua nghi thức thước hiện tại *"byte > 0"* **1.192 > 0** **guard có, nhưng mù đúng sub-class mới**. Luật phân biệt đã ghi: *L1 co lại mà **0 file L2 sinh trong cùng commit** + **mất H1** = clobber, KHÔNG phải curate*.
---
## 🔴 ĐÍNH CHÍNH TIỀN-ĐỀ SAI của dòng `:70` — phán bởi `harness-audit` @S195, lead nhận
**Dòng `:70` (mục MFE @S185) khai:** *"grep -ci trên diary 2 vai = 0/0 — 2 mục này **chưa bao giờ xuất hiện rồi rơi**"* phân loại **BUILD-GAP** cho CẢ HAI vai. **Tiền-đề này SAI cho CẢ HAI**, mỗi vai sai một kiểu.
### (a) `implementer-backend` — SAI vì mục ĐÃ BỊ CHÍNH LANE REFINE DỜI ĐI
`implementer-backend/archive/2026-07.md:18` nguyên văn:
```
## @S152 R1 retention-note [moved-not-cut @S185 refine-A1, verbatim]
```
`:19` = mục `Integration testing multiple components` **chính mục MFE báo MISS**. Chuỗi nhân quả đóng kín: **@S152** vai ghi vào L1 **@S185 `refine-A1`** (hành động của chính lane refine) dời L1L2 **@S185 ledger** phán build-gap bằng grep **chỉ quét L1, và L1 rỗng VÌ A1 vừa dọn** **@S192** MFE 18/1817/18.
Control-dương đóng vòng: L1 = **0** · `archive/2026-07.md` = **1** · 14 file archive khác = **0**.
Phân loại đúng = **FLOOR-ROT**. `mfe-eval.ps1:243` (nguồn mục = role-file) + **`:264`** (diary = **CHỈ** `MEMORY.md`) **archive tàng hình với MFE by construction**.
### (b) `implementer-frontend` — SAI vì MATCHER MÙ, không phải vì mục không tồn tại
🔴 **`harness-refine` ES-4 định GIỮ NGUYÊN phán quyết build-gap cho vai này. `harness-audit` bác bằng đĩa:**
```
.claude/agents/implementer-frontend.md:31 6. ❌ < 30 min trivial <- mục denominator
implementer-frontend/MEMORY.md:54 "2 ranh auto-refuse: việc <30min/trivial = em-main solo"
implementer-frontend/archive/2026-H2.md:80 "(a) Việc < 30 min / trivial: … REFUSE nhận đúng
role-file; nhận việc trivial = đốt spawn vô ích."
```
Mục này **ĐANG SỐNG Ở L1 HÔM NAY**, còn bản đầy đủ L2. *"Chưa từng sống"* **SAI**. cơ-chế biện minh kèm theo (*"lead tự làm chứ không spawn sub vai chưa từng gặp ca để từ chối"*) cũng đổ: `2026-H2.md:80` **đúng một ca từ chối có lý do**.
**Vì sao máy vẫn báo MISS — cơ-chế, không phải bí ẩn:** matcher `mfe-eval.ps1:281-287` đòi **2 content-word alnum-run RỜI** (`(?<![a-z0-9])…(?![a-z0-9])`). L1 viết **DÍNH**: `<30min/trivial` `30min` **MỘT** run `30` `min` **đều trượt lookaround** chỉ còn `trivial` = 1 từ < `minWords 2` **MISS**. Đây đúng cơ-chế **DÍNH-vs-TÁCH** đã ghi @S173 **tái phát nguyên vẹn**, lần này bị đọc nhầm thành *"chưa từng tồn tại"*.
### 🔴 Hệ quả — HÀNH ĐỘNG giữ nguyên, LÝ DO phải đổi
**KHÔNG reinject cho cả hai** nhưng nền lẽ đổi hẳn:
- ~~"chưa từng sống build-gap"~~ **SAI**.
- **Nền đúng:** nội dung **ĐÃ được mang rồi** (backend L2 sau A1 copy-về; frontend cả L1 lẫn L2) nhồi thêm **Goodhart thuần**: đẩy 92%→100% với **0 thông tin mới**. Đúng cảnh báo `harness-audit` đã ghi @S173: *"cách RẺ NHẤT đẩy 92%→100% là viết chuỗi `30` đứng riêng vào diary"*.
- Đây hình dạng **quen thuộc S189/S192: ĐÚNG KẾT LUẬN, SAI LÝ DO**. Nếu ES-4 thi hành y văn thì sổ governance sẽ mang **một mệnh đề sự thật SAI, tồn tại lâu dài**.
### Vai tự nghi trước khi kết tội (giữ vì đây là chuẩn)
`harness-audit` chạy **F16** (*"probe `30 min` của tao lỏng, bắt oan refine không?"*) **F17** (*"`<30min/trivial` khác nghĩa mục 6 không?"*) dùng control-dương trên backend (1 hit đúng file, 0 chỗ khác) **và đọc NGUYÊN VĂN** cả 2 dòng thay tin số đếm. **Cả 2 HELD.**
### Khuyết tật thước KÈM (chưa vá, thuộc owner/tooling)
1. **`git ls-tree`/`git archive` KHÔNG dùng được cho trục byte trên kho này** `measure-agent-memory.ps1:14` dùng `(Get-Item).Length` = **ĐĨA**, git normalize CRLFLF nên blob **luôn hụt đúng số dòng**. Đo: `reviewer` disk **28.804** blob **28.673**, delta **131** = **CRLF pairs 131**, khớp tuyệt đối. **8/29 diary là `w/crlf`.** Công thức đúng: `disk = blob + LF_count nếu w/crlf`.
2. **`mfe-eval.ps1` KHÔNG IN dòng tổng nào** `grep 'roles_scored\|numerator\|denominator'` trong script = **0 hit**; `163/165 = 98,79%` số **gộp tay**, **chưa bao giờ là output máy**. signal #7 của con-ĐO (*"MFE dòng tổng vắng khỏi bề mặt"*) sẽ đẩy lane refine đi đòi lead dán **một dòng không bao giờ tồn tại**. Verdict *"không tự kiểm được dưới C1"* ĐÚNG, **nguyên nhân SAI**.
3. **`-Tier sub` đường READ-ONLY** của `mfe-eval.ps1` (dispatch `:329` `Measure-Lead` chứa write `:330` `Measure-Sub`) chứng bằng **md5 trước = sau** (`58ebfd05…`, mtime bất biến), không bằng đọc code. Mở lại đường chạy @S192 vai từng **từ chối**.