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

11 KiB
Raw Blame History

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):

"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.85floor(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 (thesubstring:""` 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.