Files
solution-erp/.claude/workflows/runs/2026-06-18-h11-implement/sub-task-1.md
pqhuy1987 e70c0462d7 [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>
2026-06-18 20:44:26 +07:00

157 lines
11 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.

# 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.