Files
solution-erp/.claude/workflows/hmw.js
pqhuy1987 11db5efdf8
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m39s
[CLAUDE] Docs: S153 closeout — adopt TRỌN Ctx soft-memory + OWNER-VETO 3-vai + RENAME hub-form (squash 22 wal:)
Pipeline anh lệnh: /check-email + /adap-apply -> /fable-real invest (spec 9-task/24-checklist)
-> hmw Opus-5-MAX 2 đợt + lead-solo T6-T8 -> checklist verify.

Thư ① 9c909007 (model-default Opus 5 MAX): TRỌN — filing 2-tuyến + probe-sau-restart
2-nguồn claude-opus-5[1m] + thư hub d432f5eab5cd + caveat (e) đóng.

Thư ② 0df10df4 (pause-tiep Ctx soft-memory, sàn 7-khoản): EXECUTED
- máy mind-check: scripts/session_ctx.py +542 dòng (7 phép + 2 phụ, 4 mức, draft/closed/--root/--session PIN, exit 0/1/3)
- template .claude/templates/session-mind-template.md + wire 4 lệnh (pause C-bis+2bis+§1-carve-out, tiep §2.6+§3-ter, session-start Phase 3.5, session-end §L.b(l)+6.3-bis)
- memory-budget: mind_ctx_kb=32 + tiep_reload + ĐÓNG ghost-wire session_ctx_kb (H18 class-2 sống S146->S153)
- fault-inject 18/18 + 2 fix hậu-T4 (FI 6/6): GAP-#9 closed-mode E-0-con-trỏ CỜ->TRƯỢT (chặn gradient
  thưởng xoá-con-trỏ-trung-thực, họ bẫy F-1 của chính thư hub) · secrets-sweep +--root parity
- số ĐO THẬT pause +117ms thay ước lệch 2.2-5.4x

OWNER 2 quyết trong-phiên:
- (38) VETO: 1-vai -> hub-form 3-vai (ctx-curator @pause + carve-out pause §1 · ctx-verifier @tiep · ctx-audit @2-bookend)
- RENAME theo chuẩn hub/sisters để audit chéo: ctx-pause-check->ctx-curator, ctx-reload->ctx-verifier
  (đo đĩa 3 nhà; hub≠sister => SE theo HUB-form, tiền lệ S149). Roster 20->23, VALID_ROLES 23.
  Gap còn lại: ring5-audit (V5 truth-floor) hub CÓ, SE+VIPIX chưa -> slot (41).

Bookend 2 đầu (12 spawn) + trio auto 1 vòng:
- @open: H1 7f · H2 4f · H24 5+3 FLAG · ring1 28Đ/4T · ring2 8/8Đ + M-1 MISS thật · trio eval-MIXED-14/refine-3A-7B-4N/audit-25Đ-3T (re-chạy 7 instrument, 24 số byte-exact)
- @close: H1 6f · H2 8f (baseline S154 = 16/10, tự sửa bản ~23/11) · H24 4+2 FLAG · ring1 47Đ/5T/1KC/53 · ring2 6/6Đ + 21 phép phụ
- lead xử 11 mục: 9 FLAG H24 + 3 RESIDUAL ring2 + F-r2 (plugin 3 hard-site) + M-1 cả 2 persona + M9 14 sổ diary + F-3/F-5/F-6 + nit Sàn-5 + BÁC F-4 (fan-out không vào _index)

#53 x14-cận-dưới, 0 mất dữ liệu (WRITE-lane derive-from-body · Agent-resume · SendMessage-resume x6).
Datum: trio return-only hết miễn nhiễm (3/3 CLEAN -> 1/3). 4-A suýt xảy ra thật (bản committed của 2 file H24 là bản cắt 24%/48%).
completeness-gate: 5 vòng đủ · phép ĐẠT 4 / TRƯỢT 0 / vacuous 0.
State: Mig 67 · 89 bảng · 532 test · gotcha 83 · Sub-agents 23 · counter 27 · bundle FROZEN · governance-only 0 prod-code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 16:10:54 +07:00

291 lines
28 KiB
JavaScript
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.

// hmw.js — HMW P2 (Execute) workflow cho SOLUTION_ERP. CHẠY bởi Workflow runtime (body wrap async →
// top-level await/return hợp lệ); KHÔNG node-runnable trực tiếp (`node hmw.js` sẽ lỗi await).
// Em main lo P0/P1/P3/P4 NGOÀI workflow; script này CHỈ lo P2 fan-out.
// Invoke bằng {scriptPath} (no hot-reload — restart/re-invoke sau khi sửa). Scope = repo SOLUTION_ERP ONLY (S1).
// ⚠️ Script chạy JS-sandbox KHÔNG filesystem → KHÔNG tự tạo folder/ghi file. Scaffold run-folder runs/<run-id>/ (TRACKED) = EM MAIN @P1 (Harness-10, supersedes Harness 2 B3 wave-folder).
export const meta = {
name: 'hmw',
description: 'HMW P2 execute (SOLUTION_ERP) — fan-out roster ĐẦY-ĐỦ (số vai canonical → docs/STATUS.md §Sub-agents; KHÔNG hardcode số ở đây — lesson S108 comment stale làm worker đọc sai runtime) có MEMORY-PACK slice (qua args vì script không đọc file) + return findings + checklistEvidence + memoryDelta (spawn-record 4-field). 2 MODE (Harness 2, 06-07): (A) DEFAULT return-delta-only — fan-out NHỎ (<3 task VÀ ≤5 phút), sub KHÔNG ghi file, git-diff verify. (B) RUN-TRACE mode (args.run, Harness-10) — 🔴 BẮT BUỘC khi run ≥3 task HOẶC ước >5 phút (sàn H22 S111; script no-fs KHÔNG tự ép — enforcement = em-main scaffold @P1 + set args.run) — workflow DÀI, em main scaffold .claude/workflows/runs/<run-id>/ TRACKED FLAT (run.md + sub-<role>-<i>.md + <stage>-synthesis.md phẳng cùng cấp) @P1, sub ghi full-detail vào CHỈ sub-<role>-<i>.md mình (B4/B6), harvest per-turn primary (C4) + H2 gom @session-end = backstop verify-idempotent. taskList thoải mái (queue theo slot, không cap cứng). memoryDelta KHÔNG tự ghi — em main VERIFY + APPEND-only @P3 (no-overwrite-unverified, B3). Model worker-tier-pin (owner-decision S124): role-có-frontmatter = opus+effort:max (worker-tier, KHÔNG inherit) · role-less inherit lead · per-task tier:\'fable\'|\'opus\' escape-hatch (fable qua /fable-real; H23-precedence ĐO S126 2026-07-16: spawn-param THẮNG frontmatter hard-pin ⇒ /fable-real SỐNG). Scope = repo SOLUTION_ERP ONLY (S1 — KHÔNG fan-out repo/corpus khác).',
phases: [{ title: 'Execute', detail: 'fan-out memory-pack-injected agents, structured return' }],
}
// ─── args (em main bơm @P2 sau khi P1 chốt) ──────────────────────────────────
// args = {
// memoryPack: { 'investigator-codebase':'<slice>', 'implementer-backend':'...', reviewer:'...', ... }, // SLICE liên quan, KHÔNG full
// spec: '<acceptance-criteria / context chung>',
// checkpointApproved: true, // em main set SAU khi BÁO {số agent·vai·task} @inform (S2)
// taskList: [ { role:<VALID_ROLES|null>, label:'..', prompt:'..', tier:'fable'|'opus'? }, ... ] // tier = per-task model override (H4.5)
// run: { name:'<run-id>', dir:'.claude/workflows/runs/<run-id>' } // 🔴 BẮT BUỘC khi run ≥3 task HOẶC ước >5 phút (sàn H22 S111 — chết giữa run còn vật-liệu đĩa cho /tiep; dưới ngưỡng = tùy chọn) — bật RUN-TRACE mode (B, Harness-10 TRACKED). Folder + run.md em main ĐÃ scaffold @P1 (script no-fs). [legacy alias: args.wave]
// }
const VALID_ROLES = [
'investigator-codebase', 'investigator-api',
'implementer-backend', 'implementer-frontend',
'test-specialist', 'reviewer', 'cicd-monitor', 'frontend-designer',
'database-agent', // +S57 — S56 dùng 3× qua fail-soft WARN; read-advisory DB lens (DB1-DB11)
'office-document', // +S107 — Office document WRITE specialist (OD1-OD10; docx/xlsx/pptx/pdf + form-engine)
'tooling-auditor', 'harvest-curator', // +S110 anh-directed (H21 roster-parity lệnh-B ensemble; supersede
// điểm H6-design "monitor ngoài workflow" — monitor VẪN INFORM-only, lane RETURN-only nên không xung đột)
'lead-stale-auditor', 'lead-gap-auditor', // +S121 W2 (H24 lead-self-audit) — 2 monitor MỚI, INFORM-only propose-only.
// 🔴 KHÔNG gộp vào tooling-auditor(H1)/harvest-curator(H2): H24 §2(1) soi LEAD (view-* = lead nhìn sai / gap-* =
// lead bỏ sót), khác trục H1 (tooling-freshness) và H2 (harvest-integrity). Class-flag = enum ĐÓNG ở
// memory-budget.json → lead_self_audit.flag_classes (11 class: 5 view-* + 6 gap-*) — vai KHÔNG tự chế class.
// Từ-vựng "view"/"omission" chọn CÓ CHỦ-ĐÍCH tránh va-chạm: governance-detectors.ps1:15 đã sở-hữu
// "derived-staleness" ⇒ cấm tên *-stale-role; "GAP" quá-tải (session-end build-gap · memory-selfimprove CRITERIA+GAP).
'harness-eval', 'harness-refine', 'harness-audit', // +S141 bộ-ba đồng-kiểm memory-system (adopt AI_INFRA 2026-07-18 khuôn 5-câu) read-only INFORM-only propose-only;
// trục = CHÍNH HỆ MEMORY (độ-giữ·hành-động·đúng-spec) — KHÁC H1 tooling / H2 harvest / H24 lead / reviewer diff;
// TUẦN TỰ eval→refine→audit (CẤM song song — audit đọc bản cũ = verdict vô-nghĩa), lead scribe return từng con vào run-folder.
'ring1-audit', // +S145 C4/C4b (adap dot-11, owner-decision [2] TÁCH) — vai-KIỂM ĐỘC-LẬP vòng tooling/harvest: đọc OUTPUT H1(tooling-auditor)+H2(harvest-curator), no-self-exempt ≠ H1/H2. AUTO fire-after-measure @session-end §L.b(g-bis) (KHÁC trio/H24 consent-gated). Vai #1/3 round-KIỂM (ring2-audit + ring4-audit land dần incremental).
'ring2-audit', 'ring4-audit', // +S145 C4/C4b vai #2/3 + #3/3 round-KIỂM (owner-decision [2] TÁCH mỗi-vòng-1-con). ring2-audit = KIỂM vòng soi-lead (đọc OUTPUT lead-stale+lead-gap, no-self-exempt ≠ chúng, KHÔNG soi lead trực-tiếp) · ring4-audit = KIỂM vòng nén-ngủ (đọc /sleep-recovery gist+Fidelity, tự đo byte). AUTO fire-after-measure, propose-only, NO-OP nếu vòng không đo.
'ctx-audit', // +S153 ctx soft-memory 0df10df4 — vai-3 vòng Ctx: SOI-CHUỖI 2 đầu phiên, INFORM-only (🔄 OWNER-VETO @S153: (38)(a) 1-vai → hub-form 3-vai, anh chốt trực tiếp)
'ctx-curator', 'ctx-verifier', // +S153 OWNER-VETO hub-form 3-vai — vai-1 KIỂM nháp @mỗi-pause (carve-out pause §1) · vai-2 ĐỐI-CHIẾU+NẠP @mỗi-tiep; cả 2 INFORM-only trả-văn-bản
]
// ─── PA-2a — hằng-số kỳ-vọng model tier-2 (H23 model-at-spawn, owner-chốt O1 @S121 W2) ──
// 🔴 ĐÂY LÀ HẰNG-SỐ KHAI-BÁO, KHÔNG PHẢI CƠ-CHẾ NEO. Nó KHÔNG ép model lúc spawn.
// Vì sao chỉ là hằng-số: neo-model-tại-spawn BẤT-KHẢ-THI ở SE — param `model:` chỉ nhận ALIAS
// ('opus'/'fable'/...), không nhận full-id; SE nay pin ALIAS `opus` (KHÔNG full-id) ở TOÀN-BỘ frontmatter
// roster (KHÔNG chép số — lesson S108, tái @S151: "17/17" viết cứng đã stale khi roster lên 20).
// ⇒ owner chốt PA-2 = PA-2a (hằng-số này, để audit có thứ ĐỐI-CHIẾU) + PA-2b (scripts/spawn-model-audit.ps1
// so resolved-vs-expected). Đọc bởi: scripts/spawn-model-audit.ps1 (thiếu hằng-số ⇒ script FAIL-LOUD).
// 🔸 H23 precedence — ĐÃ ĐO S126 (2026-07-16, phiên đầu sau restart; run wf_f960dae2-fa0, đọc bằng
// spawn-model-audit.ps1, KHÔNG tin lane tự khai): khi CẢ HAI cùng hiện-diện ⇒ SPAWN-PARAM THẮNG hard-pin.
// Lane reviewer (frontmatter `opus`) + tier:'fable' → resolved `claude-fable-5` ⇒ /fable-real·/fable-clone
// SỐNG, KHÔNG NO-OP, không cần owner re-pin. Control: lane investigator-codebase KHÔNG tier → resolved
// `claude-opus-4-8` DÙ lead=Fable ⇒ pin frontmatter ăn runtime (loại giả-thuyết inherit-lead).
// Caveat: retrospective (không hứa spawn TƯƠNG-LAI — đúng PA-2b) · đo trên ALIAS-pin, KHÔNG generalize
// sang full-id-pin (hub form). Evidence: runs/2026-07-16-S126-adap-spec-execute/runtime-verify-synthesis.md.
// 🔸 CHƯA THỬ (fix#8a): param `model:` có TỪ-CHỐI full-id không — doc nói enum-alias; chưa ai thử thật.
// 🔄 VALUE-update @S149 2026-07-25 (broadcast `model-default-opus-5-max` sha published 9c909007 — re-stamp G-024a 07-25, pre 59ce1d0f, hub-owner "làm gấp"
// + SE-owner cùng ngày "Giao cho đám Opus 5 MAX apply"): 'claude-opus-4-8' → 'claude-opus-5'.
// Khoản 1.2: SE pin ALIAS `opus` ⇒ CODE spawn-site GIỮ NGUYÊN, alias family tự resolve Opus 5.
// Khoản 1.4: 'claude-opus-4-8*' = bucket LEGACY giai đoạn chuyển tiếp (record cũ KHÔNG cờ drift).
// 2-key: đổi ĐỒNG THỜI với $OWNER_RATIFIED_TIER2_FULL_ID trong scripts/spawn-model-audit.ps1.
const TIER2_EXPECTED_FULL_ID = 'claude-opus-5'
// ─── Worker-tier-pin opus (owner-decision S124 — thay thế H8 all-inherit; adopt erratum-h8 floor) ──
// MỌI sub-agent (TOÀN roster — số canonical docs/STATUS.md §Sub-agents, KHÔNG chép số ở comment)
// → frontmatter `model: opus` + `effort: max` = worker-tier gán-cứng (KHÔNG còn
// inherit lead). Lead = DEFAULT Opus 5 MAX (thư 9c909007 @S149, re-stamp 07-25 pre 59ce1d0f; BƯỚC 0.6 probe runtime -
// idiom cũ "owner-choice per-session" THÔI DÙNG @S149, sweep @S151); Fable/cao-hơn = owner gõ /fable-real
// HOẶC /model tay (owner "còn Fable tao sẽ chỉ định qua Fable-real hoặc tự chọn").
// KHÔNG hardcode model hiện-hành ở comment — lesson S108: comment stale làm 2/4 worker đọc sai runtime.
// (🔴 restart-CLI để frontmatter mới ăn runtime — EDIT-agent-tồn-tại no-hot-reload.)
// hmw KHÔNG set model cho role-có-frontmatter → return undefined, frontmatter (opus) tự lo (= worker-tier).
// Role-less (H6.2 governed-ultracode): default = inherit lead-model (top-tier) — KHÔNG pin rẻ làm default.
// H8.2 "workflow nhanh nhất" = song song tối đa + xuất nhanh, KHÔNG hạ model làm rẻ.
// Escape-hatch per-task tier:'opus' GIỮ cho sweep/cost (ngoại lệ có chủ đích); tier:'fable' CHỈ hợp lệ
// trong lượt lệnh engine-đắt anh gõ kèm vai (/fable-real|/fable-clone <vai> <đề-bài> — H21 S110);
// lead KHÔNG tự ý elevate theo phán-đoán riêng (MTv3 §1.3: đường DUY NHẤT nâng 1 vai lên hạng-nhất = lệnh owner).
function resolveModel(role, rawRole, tier, i) {
if (tier === 'fable' || tier === 'opus') return tier
if (tier) log(`⚠️ hmw: tier "${tier}" lạ (task #${i}) → bỏ qua, dùng mặc định (frontmatter worker-tier opus)`)
// Role-less (null) → inherit lead; invalid non-empty role đã THROW up-front (STOP-HARD, DIRECTED REC-3 2026-07-13)
// → nhánh này giờ CHỈ đạt bởi role-less thật (rawRole null/''); giữ làm defensive no-op.
if (!role && rawRole) return undefined
if (!role) { log(`hmw: task #${i} role-less → inherit lead-model (H6.2 governed-ultracode; per-task tier:'opus' = escape-hatch sweep/cost)`); return undefined }
return undefined // role có frontmatter: tất cả `opus`+`effort:max` (worker-tier-pin S124) — KHÔNG override
}
const SCHEMA = {
type: 'object',
required: ['findings', 'memoryDelta'],
properties: {
findings: { type: 'string', description: 'Kết quả chính. MỌI claim kèm evidence file:line. KHÔNG narrative suông.' },
checklistEvidence: { type: 'string', description: 'Bằng chứng cho acceptance-checklist P1 (số đo / PASS-FAIL / verdict).' },
subMdPath: { type: 'string', description: 'RUN-TRACE mode FLAT: đường-dẫn sub-<role>-<i>.md agent đã ghi (em main/H2 đọc on-demand). DEFAULT-mode: bỏ trống.' },
memoryDelta: {
type: 'object',
description: 'Spawn-record 4-field — RETURN-only để EM MAIN harvest @P3. Agent KHÔNG tự ghi ký ức (KHÔNG file MEMORY.md, KHÔNG store_memory/RAG). Em main VERIFY + APPEND-only (KHÔNG overwrite entry cũ nếu chưa kiểm tra — B3).',
required: ['task', 'verdict', 'learned', 'surprise'],
properties: {
task: { type: 'string', description: 'việc gì (1 dòng)' },
verdict: { type: 'string', description: 'kết luận / PASS-FAIL / root-cause' },
learned: { type: 'string', description: 'pattern / anti-pattern rút ra' },
surprise: { type: 'string', description: 'edge-case / bất ngờ (chống mất discovery)' },
},
},
},
}
// S4b — args có thể tới OBJECT hoặc JSON-STRING (harness đôi khi stringify object args) → normalize defensive
// 🔴 LUẬT (per 6c2eb2b1 — B6 non-JSON-args): args LUÔN là object JSON thật — dòng normalize NGAY DƯỚI là
// LƯỚI-CUỐI cho ca tầng-máy tự stringify, KHÔNG phải giấy phép để em-main tự stringify args khi gọi.
const A = (typeof args === 'string') ? JSON.parse(args) : (args || {})
if (!A || !Array.isArray(A.taskList) || A.taskList.length === 0) {
throw new Error('hmw: cần args.taskList (mảng {role,label,prompt}) — em main bơm @P1. (đã thử JSON.parse nếu args bị stringify.)')
}
// S2 mechanize: checkpoint TRƯỚC P2 = gate CỨNG, không chỉ prompt. Em main BÁO {số agent·vai·task} @inform
// (KHÔNG chờ confirm) → set args.checkpointApproved=true → fan-out. Flag GIỮ = anti-accidental-fire
// (call quên-set-flag → throw, chặn 515K-class accidental).
if (A.checkpointApproved !== true) {
throw new Error('hmw: checkpointApproved chưa set — em-main BÁO {số agent·vai·task} @inform rồi set args.checkpointApproved=true (guard = anti-accidental-fire).')
}
// ─── C8b LOUD-skip trần WIDTH (S144, adap đợt-11 · 🔴 CHƯA RATIFY — xem ĐÍNH-CHÍNH dưới) ────
// 🔴 ĐÍNH-CHÍNH ATTRIBUTION @S149 (2026-07-24) — bản trước ghi "owner chốt O-7 2026-07-22" ×2.
// ĐÓ LÀ SAI. Đo 4 nguồn: `docs/HANDOFF.md` mục (19) ghi "chưa ai duyệt" + "số = owner-SỐ";
// `docs/STATUS.md` ghi O-7 "câm 5 nhãn"; grep `O-7.{0,60}(anh chốt|owner chốt|đã duyệt|ratif)`
// toàn `docs/` + `.claude/governance/` = **0 dòng**; `ring2-audit` @S149 chấm FLAG "bảng owner-gated
// O-2/O-3/O-7 câm" = ĐẠT/HELD (falsify F2+F3 đều HELD).
// ⇒ Nguồn thật của cơ-chế = **ADOPT-theo-floor** của spec-R2 + C14 3.4 theo `depth-axis:43`,
// KHÔNG phải owner ký. Code tự nâng adopt-theo-floor thành owner-decision = cùng họ "lời-hứa-máy"
// (`goi-chot:52`) và họ "SHA bịa" — chữ ký của owner KHÔNG được suy ra từ việc đã implement.
// 🔸 Chưa loại trừ owner chốt MIỆNG @S144 không ghi sổ ⇒ đang HỎI owner @S149; nếu đúng thì
// đây chỉ là ghi-vết-lại, và dòng này đổi thành "owner ratified <date>".
// LINEAGE (giữ vết, KHÔNG xoá — khuôn `auto-toan-vong:35` "đảo có chủ đích, chủ-ký, ngày, lý do"):
// BẢN CŨ: `if (len > 16) log(...)` + comment "số task THOẢI MÁI … KHÔNG cap cứng (soft notice only)".
// VÌ SAO ĐẢO: broadcast `depth-axis:40` đòi "clamp trần cứng + assert tổng-lượt ≤ trần";
// soft-notice = chạm trần vẫn chạy hết ⇒ không phải clamp. Cơ-chế LOUD-skip = adopt theo floor.
// VÌ SAO KHÔNG THROW: `depth-axis:43` nguyên văn "BỎ đào + báo RÕ (KHÔNG văng lỗi, không
// lặng-thầm bỏ)"; và :124-125 dưới đã tự khai throw per-lane có thể bị nuốt thành null =
// STOP-SILENT, tệ hơn. Reviewer R2 bắt bản spec v1 đòi THROW = tự mâu thuẫn → đã sửa.
// 🔴 CANONICAL của số này = `.claude/agent-memory/memory-budget.json` → `hmw_width.cap` (B1).
// Dòng dưới là **MIRROR HARDCODE**, KHÔNG đọc runtime — sandbox workflow no-fs (xem :5).
// Lệch giữa 2 nơi ⇒ drift-check `governance-detectors.ps1` phải FLAG (K8, chưa land — grep
// `width` toàn `scripts/` @S149 = 0 hit).
// 🔸 16 → 20 @S149 2026-07-24 (owner-DELEGATED → lead-decided; xem `hmw_width._ratified_by`).
// VÌ SAO: trần này chặn ĐỐT TOKEN NHẦM, không chặn concurrency (engine đã cap min(16,cpu-2),
// >16 chỉ xếp hàng). Sweep TOÀN-ROSTER là use-case đã chạy thật (S125/S126 = 14/14 thời roster
// 14); roster nay **20** ⇒ cap 16 cắt 4 lane, ép chia 2 đợt vô cớ. Đánh đổi khai thẳng:
// 8 lane @S149 ≈ 1,06M token ⇒ 20 lane ước ~2,5M.
const WIDTH_CAP = 20
if (A.taskList.length > WIDTH_CAP) {
const dropped = A.taskList.slice(WIDTH_CAP)
const keptN = WIDTH_CAP
log(`hmw: 🔴 VƯỢT TRẦN WIDTH — taskList=${A.taskList.length} > trần=${WIDTH_CAP}.`)
log(`hmw: 🔴 CHẠY ${keptN} lane ĐẦU, BỎ ${dropped.length} lane CUỐI (LOUD-skip, KHÔNG throw — depth-axis:43).`)
log(`hmw: 🔴 LANE BỊ BỎ: ${dropped.map((t, k) => `[${WIDTH_CAP + k}]${(t && t.label) || (t && t.role) || '(no-label)'}`).join(' · ')}`)
log(`hmw: 🔴 Đây KHÔNG phải "đã chạy hết" — em-main PHẢI khai số bỏ trong report @P4.`)
A.taskList = A.taskList.slice(0, WIDTH_CAP)
}
// ─── STOP-HARD role-whitelist (DIRECTED 6c32df89 REC-3, 2026-07-13 · S115) ────
// Vai ∉ VALID_ROLES (typo / rename-drift) = DỪNG HẲN + báo owner — KHÔNG fail-soft
// về default subagent (fail-soft = "đứt âm-thầm" đã gây lỗi thật hub-side; sim 5-ca).
// PHÂN-BIỆT (backward-compat H6.2): role null/omitted/'' = HỢP-LỆ role-less (inherit
// lead) → KHÔNG throw. CHỈ non-empty-string ∉ whitelist mới throw. Validate UP-FRONT
// (trước parallel) — halt KHÔNG phụ-thuộc semantics parallel()/.filter(Boolean) (per-lane
// throw có thể bị nuốt thành null = STOP-SILENT, tệ hơn). Mirror checkpointApproved tripwire.
const badRoles = A.taskList
.map((t, i) => ({ i, raw: t && t.role }))
.filter(x => x.raw != null && x.raw !== '' && !VALID_ROLES.includes(x.raw))
if (badRoles.length > 0) {
const detail = badRoles.map(x => `#${x.i}="${x.raw}"`).join(', ')
throw new Error(
`hmw: vai ∉ VALID_ROLES (${detail}) — DỪNG HẲN (STOP-HARD). ` +
`Sửa typo HOẶC báo owner thêm vai vào VALID_ROLES [${VALID_ROLES.join(', ')}]. ` +
`KHÔNG fail-soft về default subagent (DIRECTED 6c32df89 REC-3 2026-07-13).`)
}
const memoryPack = A.memoryPack || {}
const spec = A.spec || ''
// ─── RUN-TRACE mode (Harness-10, supersedes Harness-2 wave-mode B) ────────────
// run = { name, dir }. Folder runs/<run-id>/ (TRACKED) + run.md em main ĐÃ scaffold @P1 (script no-fs). Bật → sub ghi full-detail
// vào CHỈ sub-<role>-<i>.md phẳng mình + return memoryDelta. Containment: tracked-change NGOÀI run-folder + code-disjoint = vi-phạm
// (run-folder TRACKED → HIỆN trong git-diff = audit trực-tiếp; em main git-diff post-P2 + chunk-count RAG). [legacy alias args.wave]
const wave = (A.run && A.run.dir) ? A.run : ((A.wave && A.wave.dir) ? A.wave : null)
if (wave) log(`hmw: RUN-TRACE mode on → dir=${wave.dir} (TRACKED FLAT; sub ghi sub-<role>-<i>.md phẳng isolated; em main scaffold @P1; harvest per-turn primary C4, H2 gom @session-end = backstop).`)
// ─── SÀN-1 GATE — RUN-TRACE BẮT BUỘC khi ≥3 task (H22 wal-defect-fix, adopt S121 W2) ─────
// TRƯỚC S121 sàn này chỉ là CHỮ trong doc ("script no-fs KHÔNG tự ép") ⇒ phụ-thuộc em-main NHỚ set
// args.run. Đo thật @S119: `git grep args.run .claude/commands/` = 0 hit ⇒ 4/4 wrapper KHÔNG cờ
// ⇒ sàn = danh-nghĩa. Nay CƠ-KHÍ-HOÁ (mirror tripwire checkpointApproved).
// Vì sao ≥3: chết giữa run DÀI mà không có run-folder ⇒ /tiep §4 KHÔNG có vật-liệu trên đĩa để cắt-gọt
// ⇒ phải chạy lại TỪ ĐẦU (per-turn returns mất theo phiên; chỉ file trên đĩa sống sót).
// Bằng-chứng: S118 raw-Agent 7-spawn ⇒ 0 run-folder ⇒ mất trace.
// 🔴 THROW chứ KHÔNG fail-soft: fail-soft = "đứt âm-thầm" (cùng lý-lẽ STOP-HARD role-whitelist bên trên).
if (!wave && A.taskList.length >= 3) {
throw new Error(
`hmw: SÀN-1 — ${A.taskList.length} task (≥3) mà THIẾU args.run ⇒ DỪNG HẲN. ` +
`RUN-TRACE là BẮT BUỘC ở ngưỡng này (sàn H22 S111): chết giữa run mà không có runs/<run-id>/ ` +
`thì /tiep §4 không có vật-liệu đĩa để relaunch cắt-gọt, phải chạy lại từ đầu. ` +
`SỬA: em-main scaffold .claude/workflows/runs/<run-id>/ (run.md + taskList snapshot) @P1 rồi truyền ` +
`args.run = { name:'<run-id>', dir:'.claude/workflows/runs/<run-id>' }. ` +
`(Script chạy JS-sandbox KHÔNG filesystem ⇒ KHÔNG tự tạo folder được — vì thế mới gate ở đây.) ` +
`Dưới 3 task = MODE A return-delta-only, args.run tùy chọn.`)
}
// 🔸 KHAI THẬT — gate này CHỈ phủ đường `hmw.js`. Ngưỡng "hoặc ước >5 phút" KHÔNG cơ-khí-hoá được
// (script không đo trước được thời-lượng) ⇒ phần đó vẫn là kỷ-luật người, không phải cổng.
// 🔸 KẼ CÒN-LẠI (F2, KHÔNG gate được từ đây): raw `Workflow` tool và raw `Agent` tool đi THẲNG, không
// qua file này ⇒ mất luôn writeGuard + run-trace. S72 raw-Workflow ⇒ reviewer ghi MEMORY +2850B over-cap;
// S118 raw-Agent 7-spawn ⇒ 0 run-folder; và chính run S119 cũng đi raw Workflow. SE KHÔNG có CLI-launcher
// để whitelist ⇒ 3 nguyên-tắc detector của broadcast là N/A **vì không có launcher**, KHÔNG phải vì "hết kẽ".
phase('Execute')
log(`HMW P2: fan-out ${A.taskList.length} task (${wave ? 'RUN-TRACE' : 'return-delta-only'}, worker-tier-pin opus, memory-pack-injected, scope=SOLUTION_ERP repo only)`)
const results = await parallel(A.taskList.map((t, i) => () => {
const raw = t && t.role
// STOP-HARD validate up-front (DIRECTED REC-3 2026-07-13): raw ở đây CHỈ có thể ∈ VALID_ROLES HOẶC null/'' (role-less H6.2).
const role = VALID_ROLES.includes(raw) ? raw : undefined // role-less (null/'') → undefined → inherit lead
const mem = role && memoryPack[role] ? memoryPack[role] : ''
const subMd = wave ? `${wave.dir}/sub-${role || 'task'}-${i}.md` : null // Harness-10 FLAT (h10-refine 2026-06-18): sub-<role>-<i>.md phẳng cùng cấp dưới runs/<run-id>/ — KHÔNG sub-md/ subdir
// Write-guard TOOL-AWARE theo MODE (B6 isolation). SE read-only sub (KHÔNG Write tool): investigator-codebase/api,
// reviewer, cicd-monitor (+ monitor tooling-auditor/harvest-curator) (+S153 bộ-ba Ctx: ctx-audit, ctx-curator, ctx-verifier — lớp mềm _mind, tools: KHÔNG Write/Edit).
// Write sub: implementer-backend/frontend, test-specialist, frontend-designer.
const writeGuard = wave
? [
`## ✍️ RUN-TRACE ghi sub-<role>-<i>.md FLAT (Harness-10 h10-refine, supersedes subfolder sub-md/) — TOOL-AWARE (chống mojibake G-009):`,
`- Full-detail công-việc của mày → ĐÚNG 1 file: \`${subMd}\` (folder đã scaffold sẵn — KHÔNG tạo folder).`,
` • NẾU mày CÓ Write/Edit tool (implementer-backend/frontend, test-specialist, frontend-designer): GHI TRỰC TIẾP via Write/Edit. 🔴 KHÔNG Bash-write MD ($-expansion/mojibake).`,
` • NẾU mày CHỈ có Bash (read-only sub: investigator-codebase/api, reviewer, cicd-monitor, tooling-auditor, harvest-curator, lead-stale-auditor, lead-gap-auditor, harness-eval, harness-refine, harness-audit, ctx-audit, ctx-curator, ctx-verifier — KHÔNG Write tool): 🔴 TUYỆT ĐỐI KHÔNG Bash-write MD → để full-detail trong "findings" + đặt subMdPath="${subMd}"; EM MAIN scribe @P3 (single-writer Write-tool, no-corruption).`,
`- 🔴 ISOLATION (B6→Harness-10, AUDIT): CHỈ ghi \`${subMd}\` (+ code-file-disjoint nếu task giao). TUYỆT ĐỐI KHÔNG ghi/sửa: agent-memory/* (MEMORY.md BẤT KỲ sub) · MD canonical (CLAUDE/README/STATUS/agents) · sub-MD agent khác. Em main git-status/diff audit sau P2 — tracked-file đổi NGOÀI run-folder (runs/<run-id>/) + code-disjoint = vi-phạm (run-folder TRACKED → HIỆN trong diff).`,
`- LUÔN return: findings (FULL) + checklistEvidence + memoryDelta (4-field) + subMdPath="${subMd}". Harvest per-turn primary (C4); H2 gom @session-end = backstop verify-idempotent → agent-memory/${role || 'sub'}.`,
`- 🔴 KHÔNG store_memory/RAG-write · KHÔNG Bash curl/HTTP Qdrant (:6333 = git-diff MÙ, chỉ chunk-count bắt) · KHÔNG ghi file NGOÀI repo/run-folder. RAG single-writer=em main; containment = git-diff(in-repo)+chunk-count(RAG) [G-015].`,
].join('\n')
: [
`## OUTPUT write-guard (DEFAULT return-delta-only):`,
`- ⚠️ KHÔNG ghi ký ức kênh nào: KHÔNG file MEMORY.md, KHÔNG store_memory/RAG-write (Qdrant). CHỈ return memoryDelta → em main VERIFY + APPEND-only @P3 (B3). KHÔNG overwrite file/chunk của sub khác.`,
`- ⚠️ Nếu task có WRITE file: CHỈ file-disjoint được giao (em main git-diff verify sau P2). KHÔNG đụng file ngoài phạm vi. Scope = repo SOLUTION_ERP only (S1).`,
].join('\n')
const prompt = [
mem ? `## MEMORY-PACK (${role}) — SLICE ký ức tích lũy của sub này, ĐỌC trước khi làm (KHÔNG phải full memory):\n${mem}\n` : '',
spec ? `## SPEC / acceptance-criteria (chung):\n${spec}\n` : '',
`## TASK:\n${(t && t.prompt) || '(thiếu prompt — lỗi taskList @P0)'}`,
[
'## OUTPUT (structured bắt buộc):',
'- findings: kết quả + evidence file:line (KHÔNG narrative suông).',
'- checklistEvidence: số đo / verdict cho acceptance-checklist.',
'- memoryDelta {task·verdict·learned·surprise}: để EM MAIN harvest @P3.',
wave ? '- subMdPath: đường-dẫn sub-MD mày đã ghi.' : '',
].filter(Boolean).join('\n'),
writeGuard,
].filter(Boolean).join('\n')
return agent(prompt, {
agentType: role || undefined,
schema: SCHEMA,
label: (t && t.label) || `hmw:${role || 'task'}-${i}`,
model: resolveModel(role, raw, t && t.tier, i), // worker-tier-pin (undefined = theo frontmatter = opus S124)
})
}))
// ─── C1 LANE-CHẾT-PHẢI-KÊU (S144, adap đợt-11 · repro NGAY trong run sinh ra nó) ───────────
// LINEAGE (giữ vết): BẢN CŨ = `return results.filter(Boolean)` + comment "lọc null nếu agent
// lỗi/null … .filter(Boolean) chỉ lọc null-lane do agent lỗi runtime" — tức code TỰ KHAI nó
// nuốt lane chết, mà KHÔNG phát tín hiệu nào ra ngoài.
// VÌ SAO SỬA — bằng chứng ngôi-thứ-nhất `wf_491c05a8-6dc` (2026-07-22): 3/5 lane chết vì cháy
// maxTurns; em-main nhận mảng 2 phần tử KHÔNG tín hiệu "chết", chỉ biết nhờ khối <failures> của
// HARNESS chứ không nhờ file này. Đúng `depth-cost:53` "Thử-rồi-chết ≠ chạy-rồi-sạch … tầng
// tổng-hợp sẽ viết 'tầng-2 không thêm gì' = củng-cố verdict bằng một tầng-2 không tồn tại".
// Falsifier hub `depth-cost:59` hỏi "tầng chết thì tầng sau thấy 'chết' hay 'sạch'?" — trước
// bản vá này SE trả lời: thấy SẠCH.
// 🔴 CỐ Ý KHÔNG throw: :124-125 đã khai per-lane throw bị nuốt thành null = STOP-SILENT.
// Fix = THÊM TÍN HIỆU, KHÔNG đổi shape trả về (P3 downstream giữ nguyên hợp đồng).
const deadIdx = results.map((r, i) => (r ? -1 : i)).filter(i => i >= 0)
if (deadIdx.length > 0) {
const nameOf = i => (A.taskList[i] && (A.taskList[i].label || A.taskList[i].role)) || '(no-label)'
log(`hmw: 🔴 ${deadIdx.length}/${results.length} lane CHẾT (null) — KHÔNG phải "sạch".`)
log(`hmw: 🔴 LANE CHẾT: ${deadIdx.map(i => `[${i}]${nameOf(i)}`).join(' · ')}`)
log(`hmw: 🔴 Lane chết = 0 findings, KHÔNG phải "không tìm thấy gì". Em-main @P3 PHẢI khai số chết trong report, và CẤM đọc mảng ngắn thành "đã phủ đủ".`)
log(`hmw: 🔴 Recovery: đọc ĐĨA trước (runs/<id>/sub-*.md) — lane có thể đã ghi xong mà return hỏng.`)
}
// về em main → P3 VERIFY + harvest + P4 checklist (shape GIỮ NGUYÊN: chỉ lane sống)
return results.filter(Boolean)