[CLAUDE] Docs: closeout OUTWARD wave adap đợt-9/10 + vá drift skill-matrix H24
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m43s

Closeout 4 mục OUTWARD của wave S141-S142 (anh gật @S143):
- adap-report 7/7 thư, đủ 5 trường REPORT-FORMAT LOCK, evidence đo thật
- email hub báo-nấc (sha 6c94873f72e0, selftest_verify exit 0, log _index cùng lượt)
- STAGE-2: 7 thư -> inbox/ai_infra/, _index 0 pending, cross-check 7/7
- squash K=8 wal: -> commit chốt

Ngoài wave: agents/README skill-matrix thiếu 2 row H24 (drift S121) -> 15/17 thành 17/17.

Nấc cao nhất khai được = executed-file/verified-pending-restart (trio CHƯA spawn).
2 phát hiện khai thẳng theo G-015 (chi tiết trong report + email):
- whitelist `tools:` KHÔNG chặn ghi ở runtime: 6 vai read-only bị append Write+Edit
- pull-lag do "watch broadcasts/inbox" canh nhầm chỗ

TICK H24: counter 16->17 (S143), 3-điều-kiện OK-reachable, không fail-loud.
Detector TOTAL 46 == baseline 46, 0 flag mới.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-22 12:32:27 +07:00
parent b146e2bb4b
commit 2757e418a1
56 changed files with 2318 additions and 58 deletions

View File

@ -1356,6 +1356,164 @@ else {
Write-Host (" H24-4 pending-flip flags = {0} [INFORM-only, LOW, NOT folded; 0 = designed first-run]" -f $h244Flags) -ForegroundColor DarkGray
}
# ---------------------------------------------------------------------------
# H24-5 - counter-ancestor-classify (INFORM-only, NEW; count SEPARATE, NOT in TOTAL)
#
# WHAT: a static, read-only replay of the .session-counter.json contract v2 clause
# "fail_loud_on_regress" (adopted S141, hub template 58e28bae C-1). The RUNTIME owner of
# that clause is the /session-start 2.1.8(b) ritual -- this detector does NOT replace it
# and could not: the ritual is the half with teeth (it FAIL-LOUDs to the owner mid-run).
# What a detector adds is a between-sessions eye that can be pointed at ANY tree.
#
# THE CLASSIFICATION (contract order, verbatim shape):
# 1. last_ticked_head reachable from HEAD (git merge-base --is-ancestor, exit 0)
# -> OK-reachable. No flag.
# 2. NOT reachable BUT the object still EXISTS (git cat-file -t = 'commit')
# -> SQUASH-BENIGN: a trace line, ZERO flags. This is the expected steady state --
# every closeout squashes the session's wal: commits, so the head captured at the
# previous tick routinely drops out of history. The 3 historical FAIL-LOUDs recorded
# in history[] (S127/S133/S139) all reclassify here; that IS contract v2's purpose.
# 3. object MISSING (cat-file fails) -> flag object-missing. The anti-tamper half: a
# head that no longer exists AT ALL was not squashed out, it was rewritten away.
# [M1] counter-regress, an INDEPENDENT axis and DEFAULT-ON: the stored counter must
# never sit BELOW a counter at which an audit already ran (last_audit.light_at_counter
# / deep_at_counter). Below = the file was rolled back or hand-edited, and a silent
# reset would hide the cadence gap -- the exact thing H24 exists to catch.
#
# [M2] DAY-ONE = INFORM-SINK. Every H24-5 hit goes through Write-InformFlag: its own
# [DETECTOR] line, counted in $script:InformCount, NEVER folded into TOTAL. Same
# reasoning as C6/H24-4 (see the Write-InformFlag banner): a brand-new net must not move
# the audited baseline on day one, in EITHER direction. No teeth are lost by this --
# 2.1.8(b) still FAIL-LOUDs at runtime, independently of this script. Fold + severity
# raise is a post-triage, owner-gated call.
#
# git-based => honours -RepoRoot, so fault-injection on a temp tree isolates completely
# (mirror of H25-closeout). Every probe is exit-code checked and never throws: a RepoRoot
# that is not a work-tree, or a file that was never ticked, SKIPs WITH A REASON and the
# section summary says MEASURED NOTHING -- a 0 printed after a skip is not a green.
#
# READ-ENCODING (bug E-010/S130): the JSON is read via ReadAllBytes + UTF8.GetString and
# NOT Get-Content without -Encoding. A BOM-less UTF-8 file decoded through the ANSI
# codepage yields a DIFFERENT document than the one on disk, and a classifier that reads
# a different document is worse than no classifier.
# ---------------------------------------------------------------------------
Write-Section 'H24-5 - counter-ancestor-classify (INFORM-only)'
$h245Flags = 0
$h245Measured = $false
$counterP = Join-Path $RepoRoot '.claude\governance\.session-counter.json'
if (-not (Test-Path $counterP)) {
Write-Host ' [skip] no .claude/governance/.session-counter.json - no tick state to classify' -ForegroundColor DarkGray
}
elseif (-not (Test-Path (Join-Path $RepoRoot '.git'))) {
Write-Host ' [skip] RepoRoot is not a git work-tree - cannot classify head reachability' -ForegroundColor DarkGray
}
else {
# BOM-STRIP (measured S142, NOT theoretical): the file on disk starts EF BB BF -- it is
# written by PS 5.1 ConvertTo-Json + Out-File -Encoding utf8, which always emits a BOM.
# [Text.Encoding]::UTF8.GetString does NOT consume that BOM (unlike Get-Content or a
# StreamReader with detectEncodingFromByteOrderMarks), so the string begins with a
# literal U+FEFF and ConvertFrom-Json dies with "Invalid JSON primitive: .".
# First run of this detector on the REAL repo raised counter-unparseable on a file that
# parses fine -- i.e. the encoding-safe read prescribed to dodge bug E-010 opened a
# SECOND failure mode of its own. Both must be handled: read bytes as UTF-8 (never let
# the ANSI codepage decode it) AND drop a leading BOM. Written as a code point so this
# .ps1 stays ASCII-only (gotcha #30).
$ctr = $null
try {
$ctrJson = [Text.Encoding]::UTF8.GetString([IO.File]::ReadAllBytes($counterP))
$ctrJson = $ctrJson.TrimStart([char]0xFEFF)
$ctr = ($ctrJson | ConvertFrom-Json)
}
catch { $ctr = $null }
if ($null -eq $ctr) {
Write-InformFlag (Rel $counterP) `
'counter-unparseable: .session-counter.json is not valid JSON - tick state UNREADABLE, classifying NOTHING' `
'fix the JSON syntax (the tick contract cannot be replayed against an unparseable file)'
$h245Flags++
$h245Measured = $true
}
else {
# ---- axis 1: reachability of last_ticked_head ----
$head = [string]$ctr.last_ticked_head
if ([string]::IsNullOrWhiteSpace($head)) {
Write-Host ' [skip-axis1] last_ticked_head is null - never ticked (seed-honesty state), nothing to classify' -ForegroundColor DarkGray
}
else {
$h245Measured = $true
$shortHead = $head.Substring(0, [Math]::Min(8, $head.Length))
$reachable = $false
try {
$null = & git -C $RepoRoot merge-base --is-ancestor $head HEAD 2>$null
$reachable = ($LASTEXITCODE -eq 0)
}
catch { $reachable = $false }
if ($reachable) {
Write-Host (" [OK] OK-reachable: last_ticked_head {0} (session {1}) is an ancestor of HEAD" -f `
$shortHead, $ctr.last_ticked_session) -ForegroundColor Green
}
else {
$objType = ''
$catOk = $false
try {
$objType = & git -C $RepoRoot cat-file -t $head 2>$null
$catOk = ($LASTEXITCODE -eq 0)
}
catch { $catOk = $false }
if ($catOk -and (("$objType").Trim() -eq 'commit')) {
Write-Host (" [trace] SQUASH-BENIGN: last_ticked_head {0} (session {1}) is NOT reachable from HEAD but the object EXISTS (cat-file -t = commit) - expected closeout-squash drift, 0 flag (contract v2)" -f `
$shortHead, $ctr.last_ticked_session) -ForegroundColor DarkGray
}
else {
Write-InformFlag (Rel $counterP) `
("object-missing: last_ticked_head {0} (session {1}) is neither reachable from HEAD nor present as an object (cat-file -t failed) - NOT the benign squash class" -f `
$head, $ctr.last_ticked_session) `
'confirm history was not rewritten; re-tick from a real HEAD or record why the object vanished (contract: object-missing => FAIL-LOUD at runtime)'
$h245Flags++
}
}
}
# ---- axis 2 [M1]: counter-regress vs the already-audited floor (DEFAULT-ON) ----
$curCounter = $null
if ($null -ne $ctr.counter) { $curCounter = [int]$ctr.counter }
$audit = $ctr.last_audit
if ($null -eq $curCounter) {
Write-InformFlag (Rel $counterP) `
'counter-key-missing: no "counter" key - cadence state unreadable, regress-check measuring NOTHING' `
'restore the counter key (H24 tick state lives in this file only)'
$h245Flags++
$h245Measured = $true
}
elseif ($null -eq $audit) {
Write-Host ' [note] no last_audit block - regress-check has no audited floor to compare against' -ForegroundColor DarkGray
}
else {
foreach ($floorKey in @('light_at_counter', 'deep_at_counter')) {
$floorVal = $audit.$floorKey
if ($null -eq $floorVal) { continue }
$h245Measured = $true
$floorN = [int]$floorVal
if ($curCounter -lt $floorN) {
Write-InformFlag (Rel $counterP) `
("counter-regress: counter={0} sits BELOW last_audit.{1}={2} - an audit already ran at a HIGHER counter, so this file was rolled back or hand-edited" -f `
$curCounter, $floorKey, $floorN) `
'restore the counter to its true value; never silently reset (contract: regress => FAIL-LOUD at runtime, cadence gap must stay visible)'
$h245Flags++
}
else {
Write-Host (" [ok] counter={0} >= last_audit.{1}={2}" -f $curCounter, $floorKey, $floorN) -ForegroundColor DarkGray
}
}
}
}
}
$h245Note = if ($h245Measured) { '' } else { ' -- MEASURED NOTHING (skipped above); a 0 after a skip is not a green' }
Write-Host (" H24-5 counter-ancestor flags = {0} [INFORM-only, LOW, NOT folded into TOTAL baseline]{1}" -f $h245Flags, $h245Note) -ForegroundColor DarkGray
# ---------------------------------------------------------------------------
# Summary + C4 self-exclusion audit (RUNTIME proof)
# ---------------------------------------------------------------------------
@ -1379,7 +1537,7 @@ if ($selfInScan -eq 0 -and $leaked -eq 0) {
Write-Host ''
Write-Host ("TOTAL FLAGS: {0}" -f $script:FlagCount) -ForegroundColor Cyan
Write-Host ("INFORM-ONLY (new nets C6 cite-2-tier + H24-4 pending-flip): {0} - counted SEPARATELY, NOT in TOTAL above (anti-Goodhart, owner-set; fold+sev-raise is post-triage)" -f $script:InformCount) -ForegroundColor DarkGray
Write-Host ("INFORM-ONLY (new nets C6 cite-2-tier + H24-4 pending-flip + H24-5 counter-ancestor-classify): {0} - counted SEPARATELY, NOT in TOTAL above (anti-Goodhart, owner-set; fold+sev-raise is post-triage)" -f $script:InformCount) -ForegroundColor DarkGray
Write-Host 'NOTE: DETECT-only lowering net. Exit 0 always (never fails build). FLAGs are advisory.' -ForegroundColor DarkGray
exit 0

View File

@ -31,7 +31,9 @@
promise the next spawn stays on the expected version. This is exactly why H23 section 2(4)
makes it informational, not enforcement.
(ii) PRECEDENCE vs a frontmatter HARD-PIN - MEASURED at SE 2026-07-16 (S126), after S124
re-pinned 14/14 agents to 'model: opus' (ALIAS pin): spawn param 'fable' on agentType
re-pinned all 14 agents THEN-EXISTING to 'model: opus' (ALIAS pin; roster is 17 as of
S141 - this measurement was taken when N was 14 and was NOT re-run for the 3 new ones):
spawn param 'fable' on agentType
reviewer (pin 'opus') RESOLVED claude-fable-5 (run wf_f960dae2-fa0, 3 records); a
control lane with NO override resolved claude-opus-4-8 under lead=Fable => the pin
itself works at runtime AND the spawn param BEATS it. Caveats: alias-pin only (the

55
scripts/stamp_verify.py Normal file
View File

@ -0,0 +1,55 @@
# stamp_verify.py — content_sha256 selftest (G-024(b) / E-024: hash via script-file ONLY)
# Recompute canonical body-hash of broadcast/email .md and compare vs declared frontmatter.
# Canonical (Harness-3 §N / README): strip \r -> split at 2nd '---' line -> strip ONE leading newline -> sha256 utf-8.
# Also prints the NO-STRIP variant to diagnose wrong-method stamps (BVAAU s71 finding class).
# Usage: python scripts/stamp_verify.py <file.md> [...] exit 1 if any MISMATCH.
# mirror-of AI_INFRA/scripts/stamp_verify.py (ported S141 2026-07-20) - re-pull khi hub đổi §N canon. KHONG sửa logic local.
import hashlib
import re
import sys
from pathlib import Path
def analyze(p: Path):
raw = p.read_bytes().decode("utf-8", errors="replace")
m = re.search(r'content_sha256:\s*"?([0-9a-fA-F]{8,64})"?', raw)
declared = m.group(1).lower() if m else None
text = raw.replace("\r", "")
parts = re.split(r"(?m)^---\s*$", text, maxsplit=2)
if len(parts) < 3:
return declared, None, None
body_nostrip = parts[2]
body_canon = re.sub(r"^\n", "", body_nostrip, count=1)
h_canon = hashlib.sha256(body_canon.encode("utf-8")).hexdigest()
h_nostrip = hashlib.sha256(body_nostrip.encode("utf-8")).hexdigest()
return declared, h_canon, h_nostrip
def main():
if len(sys.argv) < 2:
print("usage: python scripts/stamp_verify.py <file.md> [...]")
sys.exit(2)
bad = 0
for arg in sys.argv[1:]:
p = Path(arg)
declared, h_canon, h_nostrip = analyze(p)
print(f"\n== {p.name}")
print(f" declared : {declared}")
print(f" canonical: {h_canon}")
print(f" no-strip : {h_nostrip}")
if declared is None:
print(" verdict : NO-DECLARED-SHA (n/a)")
elif h_canon and (h_canon.startswith(declared) or declared.startswith(h_canon[: len(declared)])):
print(" verdict : OK (canonical match)")
elif h_nostrip and (h_nostrip.startswith(declared) or declared.startswith(h_nostrip[: len(declared)])):
print(" verdict : WRONG-METHOD (declared = NO-STRIP variant -> re-stamp canonical)")
bad += 1
else:
print(" verdict : MISMATCH (neither variant -> investigate: edit-after-stamp?)")
bad += 1
sys.exit(1 if bad else 0)
if __name__ == "__main__":
main()