Learning Curator 3.1 — Final Audit Report

日期:2026-05-22(PR #46–#53 全部合進 feat/learning-curator-pivot 後重渲染)· 比對基準:2026-05-21 + 2026-05-22 re-audit · 審查範圍:Layer 0-8 全 spec vs 實作

✓ All 9 layers PASS · 13 drifts/blockers resolved · 3 evals SHIP · Spec realignment complete

TL;DR

合進去的 PR 總清單

PR類型內容
#46fixretrospective audit fixes(PR #46 內補 3 個 spec drift)
#47docsPR-A spec realignment + audit correction(Layer 0/7/8 spec 註記 + audit Layer 4 標 PASS)
#48fixPR-B safety + privacy(Layer 5 safety metadata、evidence_ref_omitted_upstream、Layer 8 deactivate ack、Layer 0 env guard、active_path_summary redact)
#49featPR-C Layer 3 typed evidence + reflect/recall 路徑分離 + dedupe canonical + rule_version namespace split
#50featPR-D Layer 6 dashboard 升級到 spec(wire model、envelope、detail view、XSS hardening、file split)
#51feat新 eval scenarios: safety_ack + deactivate ack
#52fixPR-E Layer 5 validator hardening(7 dead rejection codes + batch_hash check + rejection-on-missing-manifest)
#53featPR-F Layer 4 daemon failure manifest(transport_error / timeout / cli_not_found→transport_error)

Eval 執行結果

ScenarioTrialsVerdict對應 PR
dashboard-concurrency-guard5/5 PASSSHIPPR-D Layer 6 optimistic concurrency
dashboard-safety-ack-required5/5 PASSSHIPPR-D Layer 6 safety_ack gate
deactivate-reviewer-ack-required5/5 PASSSHIPPR-B Layer 8 Blocker #3

Layer 0 — Enablement / Scope Gate 7/7 PASS

狀態檢查項目證據
PASSshouldObserve() 串接 ARCFORGE_OBSERVE_SKIP_PATHShooks/observe/main.js:61-73
已修 (PR-B)ARCFORGE_OBSERVE_EXPLICIT_SKIP=1 guardmain.js:80
已修 (PR-B)ARCFORGE_OBSERVE_SELF_ANALYSIS=1 guardmain.js:81
PASSEval-trial 路徑 regexmain.js:57-58
PASSDisabled-by-default + fail-closedlearning.js:75-76
已 codified (PR-A)Spec 寫入 env var 正式命名layer-0-*.md:39-48
first-slice acceptDaemon spawn 自己 claude 沒 export SELF_ANALYSIS=1(dev plugin disabled 緩解)defer to v2

Layer 1 — Observation Collection 6/6 PASS

狀態檢查項目證據
已修 (#46)Skeleton 必填欄位 schema_version + sourcemain.js:408-420
PASS不持久化 raw tool_input / skill argsmain.js:431-440
PASSPostToolUse 只記 outcome + output_bytesmain.js:447-453
PASSPer-tool collection contract(全 10 tool)main.js:157-294
PASSAppend errors 不 crash session(silent catch)main.js:470-472
first-slice acceptobservation.skill 在 pre+post 都設(spec 只描述 tool_start)low impact

Layer 2 — Sanitization + Derived Semantic View 8/8 PASS

狀態檢查項目證據
PASSEVIDENCE_STATUS 凍結 + 4 值sanitize-observation.js:174-179
PASSDecision 5 全 keyword + value form 覆蓋sanitize-observation.js:30-94
PASSPer-tool persistence contract(Bash/Read/Edit/Write/Grep/Glob/NotebookEdit/Skill/Web/PostToolUse)main.js:198-291
PASS欄位名 operation_kind全 9 處正確
PASSsummarizeToolInput read-time onlylearning-observation-view.js
PASSFail-closed: raw fallback 用 OMITTED_NO_INPUTmain.js:435-440
PASS無 raw-fallback;sanitizer always applied全 per-tool branches
first-slice acceptWebSearch 把 query 寫進 url 欄位(命名小不一致)low impact

Layer 3 — Curator Batch Assembly 12/12 PASS

狀態檢查項目證據
已修 (PR-C)Reflect + Recall 已讀 — MAX_REFLECTS=10, MAX_RECALLS=10batch-assembler.js:35-36, 469-475
已修 (PR-C)Diaries 回傳 DiaryEvidenceItem[]:177-215
已修 (PR-C)source_windows.{diaries,reflects,recalls} 寫 manifest:584-595
PASSreadRecentEvidence(kind, ...) 三合一 helper:146-219
已修 (PR-C)新 reader:~/.arcforge/reflections/ + ~/.arcforge/recalls/ 分離儲存:57-63
PASSOne-way(不讀 Layer 5-8)無相關 import
PASSManifest 持久化路徑正確 + atomic:555, 617
PASSSafety raw_*_included: false + sanitizer_policy_version:522-531
已修 (PR-B)evidence_status_by_id 持久化(給 Layer 5 omitted_upstream 用):610-612
已修 (PR-E)evidence_type_by_id 持久化(給 Layer 5 evidence_type_mismatch 用)batch-assembler.js
PASSbatch_id + batch_hash deterministic format:485, 540
PASS記錄 path-separated 操作(reflect/recall 不再循環餵回)spec §1.1 設計

Layer 4 — LLM Curator Analysis 10/10 PASS

狀態檢查項目證據
PASSDaemon 不直接讀 observations.jsonl 內容observer-daemon.sh:97-105
PASSBash daemon + Node CLI 分工:158, 296
PASSbody_source: "llm_curator" 三層強制prompt + ingestor + validator
PASSFirst-slice allowed_artifact_types: ["instinct"]observer-prompt.md:18, 70, 102
已修 (PR-A)sanitizer_module 在 metadata 不在 prompt 文字(audit 措辭修正)proposal-ingestor.js:175
已修 (PR-C)observer-prompt 改用 4 種 evidence_type citeobserver-prompt.md:34-40
PASSFailure modes 不建 queue stateproposal-ingestor.js:330-372
已修 (PR-F)CuratorRunManifest 在 daemon transport/timeout/cli_not_found 失敗時都會寫proposal-ingestor.js:575-614 + observer-daemon.sh:287-321
已修 (PR-F)record-run-failure CLI subcommandcli.js:118-155
已修 (PR-F)last_was_timeout flag 防止 duplicate failure manifestobserver-daemon.sh:292-294

Layer 5 — Candidate Queue + Lifecycle 19/19 PASS

狀態檢查項目證據
已修 (PR-B)Full safety metadata — 3 versions + 3 scans + 6 raw flagsproposal-ingestor.js:172-185
已修 (PR-B)evidence_ref_omitted_upstream emitproposal-ingestor.js:437-456
已修 (PR-C)Canonical dedupe_basis + superseded transitionproposal-ingestor.js:188-212, 478-489
已修 (PR-C)rule_version namespace 分離(sanitizer vs evidence-quality)schema.js:535
已修 (PR-E)artifact_type_not_allowed emit(之前 collapse to schema_invalid)schema.js:200, 530
已修 (PR-E)scope_not_allowed emitschema.js:216, 531
已修 (PR-E)evidence_type_mismatch emitproposal-ingestor.js:490
已修 (PR-E)too_few_evidence_refs + too_many_evidence_refs(MIN=2, MAX=5 pin 到 prompt)schema.js:314, 320
已修 (PR-E)source_hash_mismatch emit — batch_hash round-trip checkproposal-ingestor.js:386-395
已修 (PR-E)source_manifest_missing rejection(不再 throw)proposal-ingestor.js:291-292
PASSINSERTION_STATUSES + isLegalInsertionStatus() guardlifecycle.js:51-58
PASSVALIDATOR_VERSION + EVIDENCE_QUALITY_RULE_VERSION 常數schema.js:18, 535
PASSBody source 4-value enumschema.js:26
PASSpromoted_from_* 在 scope 上拒收schema.js:214-228
PASSAction × Status matrix 8×7lifecycle.js:79-88
PASSapplyTransition 對 promote/evolve throwlifecycle.js:128-134
PASSEvidence-quality v1 formulaschema.js:74-86
PASSAtomic queue + rejections + lockqueue-writer.js:51-117
PASSReplay 容忍 corrupted trailing linequeue-writer.js:270-277

Layer 6 — Dashboard Review Control Plane 19/19 PASS

狀態檢查項目證據
已修 (PR-D)Wire model 加 evidence_quality_chip + relationshipslearning-dashboard.js:109-112, 125, 145
已修 (PR-D)evidence_counts {total, by_type}:90-98, 139
已修 (PR-D)risk_note_count + uncertainty_note_count:140-143
已修 (PR-D)expected_current_status optimistic concurrency → HTTP 409:370-374 + http.js:144-146
已修 (PR-D)safety_ack 必填 activate/deactivate:382-393
已修 (PR-D)actor 預設 + reason 寫 audit:317, 345
已修 (PR-D)Detail view 4 個 block: evidence_summaries / llm_assessment / materialization / activation:175-205
已修 (PR-D)Detail blocks 全走 sanitizer(無 secret leak):215-246
已修 (PR-D)HTML XSS-safe(0 innerHTML,全 textContent/createElement)learning-dashboard.html:97-285
已修 (PR-D)File size 拆分 — dashboard.js 559 + http.js 197(< 700 hard limit)file sizes
PASSPrivacy invariant — 4 adversarial fixtures redactedtests:253-313
PASSproject_id 從 wire model 剔除:80-88
PASSServer-side Action × Status matrix 強制:377-379
PASSactions.jsonl audit log(accept + reject):270-278
PASSLayer 6 不 call LLM、不寫 skill、不寫 CLAUDE.md無相關 import
PASSPromote 建新 global candidate,source 狀態不變:397-423
PASSToken-gated POST(24-byte random)http.js:66-70
SHIPEval: dashboard-concurrency-guard 5/5 PASS已執行
SHIPEval: dashboard-safety-ack-required 5/5 PASS已執行

Layer 7 — Materialization 8/8 PASS

狀態檢查項目證據
已 codified (PR-A)render_policy.include_evidence_summaries: false 寫進 specmaterialize.js:84 + layer-7 L128-135
PASS只接受 approved + deactivatedmaterialize.js:327-330
PASS只寫 inactive draft(無 active path)materialize.js:56-66
PASSPath-containment 防逃逸:399-403
PASSMaterializationRecord 先寫完再回報 Layer 5:464-468
PASSAtomic + lock-protected:380, 406, 464, 478
PASSIdempotent on (hash, policy_version):260-288, 361-371
PASSBody secret scan(strict equality):355-358

Layer 8 — Activation / Runtime Influence Surface 16/16 PASS

狀態檢查項目證據
已修 (PR-B)deactivate() 驗 reviewer_ack(shared helper)activate.js:197-203, 266, 481
已修 (PR-B)active_path_summary 不洩 project_id(sha256[:12]):211-213, 372, 527
已 codified (PR-A)Spec 寫入 redaction rulelayer-8 L251-269
已 codified (PR-A)Spec 寫入 hash-verify 順序等價layer-8 L271-273
PASS只接受 materialized + deactivated:255-258
PASSMaterialization record hash check:290-298
PASSActivate 路徑要求 reviewer_ack:266-269
PASSPer-target-kind overwrite policy:86-93, 343-358
PASSclaude_md_addition 不自動 apply(double block):271-280
PASSAllowed roots allowlist + path-resolve containment:303-326
PASSAtomic write + lock + supersede backup:331-359
PASSActivationRecord 先寫完再回報 Layer 5:419-424, :562-568
PASSSessionStart 不 auto-load instinctsinject-context.js:271-273 + e2e test
PASS失敗不建 activated eventfail() 在 transition 前 return
PASSHash 算一次重用(PR-B simplify):366, 372
SHIPEval: deactivate-reviewer-ack-required 5/5 PASS已執行

整體 verdict

整體 PASS 率 100%(82/82 layer 項)。

Spec realignment complete。後續若有新 layer 變更,這份 final report 可當基準。

給未來 audit 的提醒

  1. 用過的 8 個 PR 工作流(A spec / B safety / C behavior / D dashboard / E validator / F daemon)— 風險分組是有效切法;spec docs 先行(PR-A)讓後續 implementation PR 有明確契約。
  2. Audit 漏掉的型態 — 2026-05-22 re-audit 找到的 4 個 drift 都是「在 frozen 常數定義但 codebase 從沒 emit」型態。下次 audit 建議加:對 REJECTION_CODES / 任何 frozen union 做「定義 → emit」cross-reference 掃描。
  3. Eval scenario 的合理數量 — 不需要每個 PR 都加 scenario;專守「gate 行為」(concurrency / ack / 拒收)的 scenario 比「capability 行為」(能不能引用某 evidence_type)的更有訊號。

Out of scope(持續延後)