- 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>
11 KiB
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(ADDarchive_gateblock 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 existingtiers.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-Applyso DRY-RUN is side-effect-free. - A7 robustness: resolves the substring against ALL
archive/*.mdfor the sub (not just the arrow-named file) because the 3_INDEXformats 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:
2on A7 integrity failure (broken pointer / 0-byte archive);0otherwise. 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)
- 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-line0x22-byte hexdump (xxd | grep 22) → the closing'of the last-farg 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. - 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.1Get-Content/Select-Stringdefault to ANSI codepage and mangle UTF-8. Fix: read BOTH_INDEX.mdand 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. - legend-line false-positive (true-positive catch): cicd
_INDEX.md:4blockquote documents the format with literalsubstring:"<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+2CRLF 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-Applyruns to reach PROPOSE (matches spec "runtime needs 2 runs"). - A7 resolves substring against ALL
archive/*.mdfor the sub (the 3 index formats name targets differently) + skips>legend lines.