[CLAUDE] Docs: adopt Harness-11 engine tự-bảo-trì (3-workflow audit→implement→review)

- engine-doc canonical docs/governance/harness-11-engine.md (PHẦN A/B/C/D + 3-tier D5/D6/D7 + one-direction-lock D8 + CAVEAT honest)
- scripts/governance-detectors.ps1 (C1 broken-pointer + C2/B3 staleness + C3 vocab-fork + C4 self-exclusion + C5 resolve, NO-API DÒ+FLAG-only, runtime-proven, FP-refined 59→27)
- scripts/memory-archive-gate.ps1 (PHẦN A: hysteresis 0.85 + keep-floor 5 + 2-strike + A7 NO-API L1-eval) + budget.json archive_gate
- B1 ×11 count→canonical-pointer (root CLAUDE.md, ef-core/dep-audit SKILL, skills/README, docs/CLAUDE.md) — drift mig53→55/test306→339/gotcha68→69 RESOLVED + ef-core +Mig 54/55 rows
- cadence-wire D1 session-start §2.1.3 + D2 session-end §L.b(c) + agents/README Upgrade S75
- run-trace TRACKED: audit wf_7fdc3bd5-930 / implement wf_c5e5844e-7c1 / review wf_d7ca1ff8-942 (REVIEW PASS, completeness-gate ĐẠT)
- check-email AI_INFRA harness-11 (verify whole-file 318ff9f6 + body b2a2fc1c) + adap-report + outbox report (body 7fa1b53a)
- 0 production code; state THẬT giữ nguyên (Mig 55 · 88 bảng · 339 test · gotcha 69 · menu 54 · bundle BYF5vIMJ/CB-tiRxd)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-18 20:44:26 +07:00
parent 462bfbc854
commit e70c0462d7
25 changed files with 1452 additions and 19 deletions

View File

@ -0,0 +1,156 @@
# sub-task-1 — Harness-11 PART-A — memory-archive-gate.ps1 (hot-mem auto-archive standing-gate, NO-API)
**Role:** general-purpose (implementer, has Write/Edit) · **Run:** 2026-06-18-h11-implement
**Files owned (single-writer, file-disjoint):**
- `scripts/memory-archive-gate.ps1` (NEW, 266 lines)
- `.claude/agent-memory/memory-budget.json` (ADD `archive_gate` block only)
- this sub-MD
**Verdict:** DONE + RUNTIME-PROVEN. DRY-RUN clean (A7 GATE PASS 186/186 pointers, exit 0). A6 2-strike lifecycle proven across 2 `-Apply` runs (WATCH@strike1 → PROPOSE@strike2). NO-API + FLAG-ONLY audited clean.
---
## 1. budget.json — additive params (acceptance item 1)
Added `archive_gate` block **before** `measured` (did NOT touch `measured` / `tiers` / `last_sleep_at` / `seeded_date`):
```json
"archive_gate": {
"_note": "Harness-11 PART-A ... ADDITIVE ...",
"autoinject_cap_bytes": 25600,
"low_watermark_ratio": 0.85,
"keep_floor_entries": 5,
"strike_threshold": 2
}
```
- `autoinject_cap_bytes: 25600` = A1 over-cap line (matches existing `tiers.l1_hot.autoinject_cap_bytes`).
- `low_watermark_ratio: 0.85``floor(0.85 * 25600) = 21760` = A4 hysteresis drain target.
- `keep_floor_entries: 5` = A5.
- `strike_threshold: 2` = A6.
**Untouched-block proof (runtime):**
```
JSON parse OK
archive_gate: cap=25600 low_ratio=0.85 keep_floor=5 strike=2
measured block still present? cicd l1_hot=23653
last_sleep_at preserved? 2026-06-18
```
## 2. memory-archive-gate.ps1 — structure
Two independent passes, DRY-RUN by default (`-Apply` only advances the strike counter; **never** moves/edits a `.md`).
| Pass | Maps to | What it does |
|---|---|---|
| PASS 1 PLANNER | A1 / A4 / A5 / A6 | per `<sub>/MEMORY.md`: measure bytes (A1); if > cap, plan #oldest-entries to MOVE to get **below** low-watermark (A4 hysteresis), keeping ≥ keep_floor newest (A5), gated behind 2-strike (A6). Prints `sub bytes over? entries strike after-est resolve`. |
| PASS 2 A7-GATE | A7 NO-API | per `<sub>/archive/_INDEX.md`: extract every `substring:"..."`; literal `.Contains()` search across `archive/*.md` (UTF-8); byte-sanity (file exists + size>0). Prints PASS/FAIL per pointer. |
Key design points:
- **Entry boundary** (A5 counting) = first of `^##` / `^###` / `^---` (regex `^(#{2,3}\s|---\s*$)`). MEMORY.md files here are h2-only today (verified) so marker-count == entry-count; regex also tolerates h3/HR files.
- **after-est** = `total sum(line.Length+2)` for the moved prefix (CRLF-aware estimate; prefixed `~` to flag it as approximate — the gate never performs a real cut).
- **A6 strike state** persisted to `.claude/agent-memory/.archive-strikes.json` (flat `{sub:int}`, ASCII). Reset-to-0 on any clean run ⇒ *consecutive*-over-cap semantics. Mutated **only under `-Apply`** so DRY-RUN is side-effect-free.
- **A7 robustness**: resolves the substring against ALL `archive/*.md` for the sub (not just the arrow-named file) because the 3 `_INDEX` formats name the target differently (reviewer `→ \`file\``; cicd `\`file\` · substring:`; inv-codebase q-shorthand table). A unique substring landing anywhere in the sub's frozen archive == resolved. Skips `^\s*>` blockquote legend lines (the `substring:"<unique-string>"` template is documentation, not a record).
- **Exit code**: `2` on A7 integrity failure (broken pointer / 0-byte archive); `0` otherwise. Over-cap is a FLAG, not an error (gate reports, human curates).
## 3. RUNTIME — DRY-RUN (canonical evidence)
`powershell.exe -ExecutionPolicy Bypass -File scripts\memory-archive-gate.ps1` → EXIT 0:
```
============================================================
memory-archive-gate.ps1 - Harness-11 PART-A
mode : DRY-RUN (no writes at all)
cap : 25600 bytes (autoinject_cap)
low-water : 21760 bytes (A4 hysteresis drain target = ratio 0.85)
keep-floor : 5 newest entries (A5)
strike-need : 2 consecutive over-cap runs to PROPOSE (A6)
============================================================
### PASS 1 - hot-tier over-cap planner (FLAG ONLY, no moves)
sub bytes over? entries strike after-est resolve
------------------------ --------- ----- ---------- ------- ------------ -------
cicd-monitor 26798 YES 18 1 ~21180 WATCH (strike 1<2): re-run; propose only after 2 consecutive over-cap
database-agent 5917 no 6 0 - ok
frontend-designer 24004 no 6 0 - ok
harvest-curator 18952 no 6 0 - ok
implementer-backend 17692 no 23 0 - ok
implementer-frontend 13394 no 16 0 - ok
investigator-api 8510 no 9 0 - ok
investigator-codebase 31502 YES 20 1 ~27069 WARN keep-floor hit (5); cannot auto-drain - SPLIT/condense entries by hand
reviewer 38755 YES 14 1 ~33738 WARN keep-floor hit (5); cannot auto-drain - SPLIT/condense entries by hand
test-specialist 24663 no 17 0 - ok
tooling-auditor 18431 no 6 0 - ok
[A6] DRY-RUN: strike counters NOT persisted (run with -Apply to advance strikes)
### PASS 2 - A7 archive-integrity gate (NO-API: grep + measure only)
[cicd-monitor] _INDEX.md + 7 archive file(s)
-> PASS pointers 76 resolved 76 failed 0
[implementer-backend] _INDEX.md + 7 archive file(s)
-> PASS pointers 41 resolved 41 failed 0
[investigator-codebase] _INDEX.md + 7 archive file(s)
-> PASS pointers 40 resolved 40 failed 0
[reviewer] _INDEX.md + 5 archive file(s)
-> PASS pointers 29 resolved 29 failed 0
------------------------------------------------------------
A7 GATE PASS - total pointers 186, resolved 186, failed 0
------------------------------------------------------------
EXITCODE=0
```
**Reads the spec's expected over-cap subs:** reviewer 38755 (~37.8KB, over), investigator-codebase 31502 (~30.8KB, over). cicd-monitor 26798 also over (current, > spec's stale ~note). All 8 under-cap subs print `ok`.
## 4. RUNTIME — A6 2-strike lifecycle (`-Apply` ×2)
The strike-counter is an **executed-file** mechanism; it needs 2 runs to demonstrate the PROPOSE gate (honest n"executed-file vs runtime" tier).
**APPLY run 1** → strikes file written `{cicd:1, inv:1, reviewer:1, all-clean:0}`; over-cap subs show `WATCH (strike 1<2)`:
```
cicd-monitor 26798 YES 18 1 ~21180 WATCH (strike 1<2): re-run; propose only after 2 consecutive over-cap
investigator-codebase 31502 YES 20 1 ~27069 WARN keep-floor hit (5); cannot auto-drain - SPLIT/condense entries by hand
reviewer 38755 YES 14 1 ~33738 WARN keep-floor hit (5); cannot auto-drain - SPLIT/condense entries by hand
[A6] strikes persisted -> ...\.archive-strikes.json
```
**APPLY run 2** → strikes advance to 2; cicd-monitor flips WATCH → **PROPOSE** (it has drainable headroom: move 6 oldest → ~21180 < low-water 21760); inv/reviewer stay WARN (A5 keep-floor priority newest 5 entries alone exceed cap):
```
cicd-monitor 26798 YES 18 2 ~21180 PROPOSE archive (strike 2>=2): move 6 oldest -> curate L1->L2 by hand
investigator-codebase 31502 YES 20 2 ~27069 WARN keep-floor hit (5); cannot auto-drain - SPLIT/condense entries by hand
reviewer 38755 YES 14 2 ~33738 WARN keep-floor hit (5); cannot auto-drain - SPLIT/condense entries by hand
```
strikes file after run 2: `{cicd:2, inv:2, reviewer:2, all-clean:0}`.
**Strike artifact cleaned up** post-test (`rm .archive-strikes.json`) DRY-RUN default never creates it; shipped repo carries no stale strike state. Cold-start DRY-RUN re-verified: EXIT 0, A7 GATE PASS 186/186.
## 5. Bugs hit + fixed during build (honesty log)
1. **PS 5.1 parser cascade (mid-build, self-fixed):** line 94 had a typo `'resolve")` a `"` where a `'` belonged which left a single-quoted string unterminated and consumed forward, throwing 4 misleading errors anchored at *later* valid lines (102/222/233/266). Root-caused via per-line `0x22`-byte hexdump (`xxd | grep 22`) the closing `'` of the last `-f` arg was a `"`. Fixed `'resolve')`. Lesson: PS 5.1 mis-locates the error site of an unterminated string; bisect by counting quotes, not by trusting the reported line.
2. **gotcha #30 mojibake (A7 false-FAIL):** first A7 run reported 55 PTR-FAILs, all on pointers containing Vietnamese diacritics / em-dash / arrows (`â€"`, `×`, `â†'`). Cause: PS 5.1 `Get-Content` / `Select-String` default to ANSI codepage and mangle UTF-8. Fix: read BOTH `_INDEX.md` and archive files via `[System.IO.File]::ReadAllText($path, UTF8)` + `String.Contains()` 189/190 resolve. This **is the gotcha #30 trap the precedent script's "ASCII-only" header warns about**, hit from the read side.
3. **legend-line false-positive (true-positive catch):** cicd `_INDEX.md:4` blockquote documents the format with literal `substring:"<unique-string>"`; my regex matched it (77 vs the index's self-declared 76 real pointers). Added `^\s*>` skip clean 76/76. The gate correctly distinguished a non-record from a broken pointer.
## 6. Acceptance checklist evidence
| Item | Verdict | Evidence |
|---|---|---|
| (1) NO-API | PASS | grep audit: only `Set-Content` is the strikes int-map (line 177); zero http/curl/api/model/qdrant calls. `.md` files are read-only. |
| (2) FLAG-ONLY / no auto-write of rules | PASS | DRY-RUN header "no writes at all"; over-cap = printed FLAG; no Move/Edit of any MEMORY.md or archive. `-Apply` writes only the counter. |
| (3) PowerShell .ps1, `-ExecutionPolicy Bypass` | PASS | runs via `powershell.exe -ExecutionPolicy Bypass -File`; ASCII-only output; PS 5.1 parse OK. |
| (4) RUNTIME-prove | PASS | DRY-RUN output §3 (exit 0) + 2× `-Apply` strike lifecycle §4 pasted from real runs. |
| (5) sub writes only file-disjoint + sub-MD | PASS | `git status --porcelain` after cleanup = only `M memory-budget.json` + `?? memory-archive-gate.ps1` (+ this sub-MD in run-folder). No canonical MD touched. |
| A1 measure | PASS | bytes column real (reviewer 38755 etc.). |
| A4 hysteresis | PASS | drains to BELOW low-water (cicd after-est ~21180 < 21760), not to the line. |
| A5 keep-floor | PASS | inv/reviewer WARN "keep-floor hit (5); cannot auto-drain" refuses to vét-sạch. |
| A6 2-strike | PASS | run1 WATCH(1<2) run2 PROPOSE(2>=2); strikes file 1→2; reset-on-clean. |
| A7 pointer-resolve + byte-sanity | PASS | 186/186 across 4 subs w/ archive; UTF-8 round-trip; legend-line skipped. |
## 7. Tailoring declared (PART-A = 🟡 tailorable)
Documented inline in the script `[TAILOR]` footer block:
- **after-est is an ESTIMATE** (`line.Length+2` CRLF heuristic, `~` prefixed) — real bytes only known post-move, which the gate deliberately never does.
- **Entry boundary** = first of `^##`/`^###`/`^---` (today's files are h2-only → exact; tolerant of other styles).
- **A6 strike** = flat `{sub:int}` JSON, consecutive semantics via reset-on-clean, requires 2 real `-Apply` runs to reach PROPOSE (matches spec "runtime needs 2 runs").
- **A7** resolves substring against ALL `archive/*.md` for the sub (the 3 index formats name targets differently) + skips `>` legend lines.