[CLAUDE] Docs: bookend @close S185 ONE-SHOT (YC-018) — trio+H24deep+ring1/2+ctx 9-vai mot-mach, harvest 698KB->11 so, 16-FLAG patched, Tests canonical 668

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-08-10 02:35:20 +07:00
parent 42f5f3b316
commit f69af273cf
34 changed files with 2004 additions and 72 deletions

View File

@ -347,10 +347,55 @@ if (Test-Path $gotchasPath) {
ForEach-Object { $gotchaAnchors[[int]$_.Matches[0].Groups[1].Value] = $true }
}
# --- L7-BOLT (C-2 @S185, medicine of broadcast 2026-08-05 "Lop 7 - neo vao hinh dang
# chu do nguoi go", Ro C) ---------------------------------------------------------
# '### N.' is a HUMAN-TYPED heading glyph. Let the writer file the two newest gotchas as
# "### 87 -" or "#### 87." and they drop OUT of $gotchaAnchors in silence -- after which
# this detector keeps emitting CONFIDENT per-ref lines ("'gotcha #87' has no anchor")
# that are pure artefacts of a dead parse, not findings. That is the exact failure the
# broadcast reports: the machine slid past the newest lines, re-anchored on an older
# block and printed 13 overdue items to the session when the true number was 0.
#
# The medicine has TWO layers and layer 2 is the load-bearing one:
# (1) do not anchor on the glyph -> not fixable here without owning gotchas.md's
# heading convention (= owner ground), so it is NOT attempted; declared instead.
# (2) add a live/dead BOLT read from an INDEPENDENT source: the newest anchor parsed
# must not sit more than ONE step behind canonical. Canonical here is the
# docs/STATUS.md Gotchas row -- a different FILE reached by a different parse
# (Get-StatusValue table-row regex), so a heading-shape change cannot move both.
# Off by more than one step => print "chua do duoc" and emit NO anchor-derived
# number. Without the bolt a "0" and a "13" are equally meaningless.
# Tolerance is ONE step by construction (a gotcha counted in STATUS whose section is
# written a beat later is normal drift, not a dead parse) - that is the broadcast's own
# wording ("khong cu hon moc moi nhat qua mot bac"), not a threshold invented here.
# SCOPE of the suppression: only the ANCHOR-EXISTENCE verdict is withheld. The
# range check (cites #N > canonical max) does NOT read $gotchaAnchors at all, so it
# keeps firing -- withholding it would hide a real witness behind a parse problem.
$anchorMaxGotcha = 0
foreach ($ak in $gotchaAnchors.Keys) { if ($ak -gt $anchorMaxGotcha) { $anchorMaxGotcha = $ak } }
$gotchaAnchorLive = $true
$gotchaAnchorGap = 0
if ($null -ne $maxGotcha -and $gotchaAnchors.Count -gt 0) {
$gotchaAnchorGap = $maxGotcha - $anchorMaxGotcha
if ($gotchaAnchorGap -gt 1) { $gotchaAnchorLive = $false }
}
$gotchaAnchorSuppressed = 0
if ($null -eq $maxGotcha -or $gotchaAnchors.Count -eq 0) {
Write-Host ' [skip] no canonical max-gotcha or no anchors parsed - cannot validate gotcha refs' -ForegroundColor DarkGray
}
else {
if (-not $gotchaAnchorLive) {
Write-Host (" CHUA DO DUOC (L7-bolt): newest '### N.' anchor parsed = {0} but canonical (docs/STATUS.md Gotchas) = {1} -> the heading anchor is {2} steps behind, i.e. the parse is DEAD, not the docs. Anchor-existence verdict WITHHELD - no number is emitted from a dead anchor." -f `
$anchorMaxGotcha, $maxGotcha, $gotchaAnchorGap) -ForegroundColor Yellow
Write-InformFlag ("docs/gotchas.md") `
("L7-bolt: gotcha anchor parse is DEAD (newest '### N.' anchor = {0}, canonical = {1}, gap {2} > 1) - anchor-existence checking measured NOTHING this run (chua do duoc, NOT clean)" -f $anchorMaxGotcha, $maxGotcha, $gotchaAnchorGap) `
"re-file the newest gotcha section(s) as '### <N>. <title>' so the anchor parse reaches canonical, OR re-ground the docs/STATUS.md Gotchas row if IT is the stale side"
}
else {
Write-Host (" [ok] L7-bolt: newest anchor {0} vs canonical {1} (gap {2} <= 1) - anchor parse is LIVE, numbers below are measured" -f `
$anchorMaxGotcha, $maxGotcha, $gotchaAnchorGap) -ForegroundColor DarkGray
}
# Match "gotcha #N", "gotcha N", and bare "#N" tokens.
# S151 fix (FP #111, H1 F-6 + ring1 confirm): the word-branch used to swallow
# "gotchas 111.616B" (a BYTE-SIZE, thousands-separator) and flag a phantom #111.
@ -382,6 +427,10 @@ else {
'fix the number or add the gotcha to docs/gotchas.md'
}
elseif ($isGotchaWord -and -not $gotchaAnchors.ContainsKey($num)) {
# L7-bolt (C-2): a dead anchor parse makes EVERY ref look anchorless.
# Count what is withheld and print the count, so the drop in emitted
# lines is told by its COMPONENTS and can never read as "got cleaner".
if (-not $gotchaAnchorLive) { $gotchaAnchorSuppressed++; continue }
Write-Flag 'LOW' ("{0}:{1}" -f (Rel $f.FullName), ($i + 1)) `
("broken-gotcha-ref: 'gotcha #{0}' has no '### {0}.' anchor in gotchas.md" -f $num) `
'fix ref or create the missing gotcha anchor'
@ -389,6 +438,9 @@ else {
}
}
}
if (-not $gotchaAnchorLive) {
Write-Host (" L7-bolt withheld {0} anchor-existence line(s) this run (they would all be artefacts of the dead parse, not findings)" -f $gotchaAnchorSuppressed) -ForegroundColor Yellow
}
}
# ---------------------------------------------------------------------------
@ -908,18 +960,70 @@ else {
# that flags the sentence DEFINING its own pattern is the self-reference trap.
$carryRx = '\[carry:([a-z0-9][a-z0-9._-]*)\]'
$carryLines = @()
foreach ($s in $segs) {
$carrySegIdx = @() # L7-bolt: which segment each carry-line came from
for ($si = 0; $si -lt $segs.Count; $si++) {
$ks = @()
foreach ($cm in [regex]::Matches($s, $carryRx)) { $ks += $cm.Groups[1].Value }
if ($ks.Count -gt 0) { $carryLines += , (@($ks | Select-Object -Unique)) }
foreach ($cm in [regex]::Matches($segs[$si], $carryRx)) { $ks += $cm.Groups[1].Value }
if ($ks.Count -gt 0) { $carryLines += , (@($ks | Select-Object -Unique)); $carrySegIdx += $si }
}
Write-Host (" HANDOFF logic-segments (pre-marker + NEXT anh/em) = {0} ; of those, carry-lines = {1}" -f `
$segs.Count, $carryLines.Count)
# --- L7-BOLT (C-2 @S185, medicine of broadcast 2026-08-05 "Lop 7", Ro C) ---------
# This net anchors on TWO human-typed shapes: the '**NEXT anh/em' segment header and
# the '[carry:slug]' stamp. Only the NEWEST carry-bearing segment can hold a live
# streak, so if the newest block is typed differently (bold dropped, stamp cased
# '[Carry:'), the anchor SLIDES DOWN to an older block and every streak is then
# measured against history the repo already replaced -- while the output keeps its
# confident per-key shape ("streak=3 < M=6"). That is the broadcast's ca verbatim:
# the machine slid past the newest lines and reported precise, wrong numbers.
#
# BOLT, read from an INDEPENDENT source: the session milestone of the anchored
# segment vs the newest milestone in the SAME document parsed WITHOUT any of the two
# glyphs (plain S<N> scan over the raw text). Both readings come off the same file
# but through disjoint parse paths, so no single glyph change can move both. Off by
# more than ONE step => print "chua do duoc" and emit NO streak number.
# WHY the doc's own newest S and not the session counter: the question this net asks
# is "is my anchor at the FRONT of this document", which is a property of the
# document. The counter would answer a different question (is the document current),
# and picking a tolerance for THAT is an owner number -- not invented here.
$SMilestoneRx = '(?<![A-Za-z0-9])S(\d+)(?![\d])'
$docMaxS = 0
foreach ($sm in [regex]::Matches($raw, $SMilestoneRx)) {
$sn = [int]$sm.Groups[1].Value; if ($sn -gt $docMaxS) { $docMaxS = $sn }
}
$anchorMaxS = 0
if ($carrySegIdx.Count -gt 0) {
foreach ($sm in [regex]::Matches($segs[$carrySegIdx[0]], $SMilestoneRx)) {
$sn = [int]$sm.Groups[1].Value; if ($sn -gt $anchorMaxS) { $anchorMaxS = $sn }
}
}
$carryAnchorLive = $true
$carryAnchorGap = 0
if ($carryLines.Count -gt 0 -and $docMaxS -gt 0 -and $anchorMaxS -gt 0) {
$carryAnchorGap = $docMaxS - $anchorMaxS
if ($carryAnchorGap -gt 1) { $carryAnchorLive = $false }
}
if ($carryLines.Count -eq 0) {
Write-Host ' (0 carry-line - no [carry:<slug>] stamped yet, nothing to age)' -ForegroundColor DarkGray
}
elseif ($docMaxS -eq 0 -or $anchorMaxS -eq 0) {
Write-Host (" CHUA DO DUOC (L7-bolt): no S<N> milestone readable ({0} in doc / {1} in anchored segment) - cannot tell whether the anchor is at the front of the file, so NO streak number is emitted" -f $docMaxS, $anchorMaxS) -ForegroundColor Yellow
Write-InformFlag 'docs/HANDOFF.md' `
'L7-bolt: carry-age anchor liveness UNMEASURABLE (no S<N> milestone in the doc and/or in the anchored segment) - carry-age measured NOTHING this run (chua do duoc, NOT clean)' `
'keep an S<N> session milestone in the newest carry block so the anchor can be proven to sit at the front of the file'
}
elseif (-not $carryAnchorLive) {
Write-Host (" CHUA DO DUOC (L7-bolt): carry anchor sits at S{0} while the document's newest milestone is S{1} ({2} steps behind) -> the '**NEXT' / '[carry:' shape anchor has SLID off the newest block. Streak numbers WITHHELD ({3} key(s) would have been scored against replaced history)." -f `
$anchorMaxS, $docMaxS, $carryAnchorGap, $carryLines[0].Count) -ForegroundColor Yellow
Write-InformFlag 'docs/HANDOFF.md' `
("L7-bolt: carry-age anchor DEAD - anchored segment is S{0}, doc newest is S{1} (gap {2} > 1); {3} key(s) NOT scored, carry-age measured NOTHING this run (chua do duoc, NOT clean)" -f $anchorMaxS, $docMaxS, $carryAnchorGap, $carryLines[0].Count) `
"restore the newest block's '**NEXT anh/em' header and lower-case '[carry:<slug>]' stamps so the newest segment is the anchored one again"
}
else {
Write-Host (" [ok] L7-bolt: carry anchor S{0} vs doc newest S{1} (gap {2} <= 1) - anchor is at the front of the file, streaks below are measured" -f `
$anchorMaxS, $docMaxS, $carryAnchorGap) -ForegroundColor DarkGray
foreach ($k in $carryLines[0]) {
$n = 0
for ($i = 0; $i -lt $carryLines.Count; $i++) {
@ -971,6 +1075,12 @@ else {
# ---------------------------------------------------------------------------
Write-Section 'H25-closeout-ritual (GAP-2)'
# C13 hook (carve-out liveness, C-8 @S185): counts close-groups this section ACTUALLY
# scored. 0 = the grid ran but measured nothing (skip branch) = "chet lang". Read by
# the C13 section far below; kept here so the number is produced by the grid itself and
# not re-derived (a second derivation would be a second definition).
$script:H25Scored = 0
# Subject shape of a session-close commit. Held in a variable (used by -match on git
# SUBJECTS, never on any scanned file) so no prose copy exists to self-trip a matcher.
$CloseoutSubjectRx = '^\[CLAUDE\] Docs: S\d+.*(?:closeout|session-end)'
@ -1041,6 +1151,7 @@ else {
continue
}
$prev = $closeGroups[$i + 1].Tip
$script:H25Scored++ # C13 hook: this group IS scored (not skipped)
$range = ("{0}..{1}" -f $prev.Hash, $cur.Hash)
$union = @()
@ -1942,6 +2053,325 @@ else {
$c12Note = if ($c12Measured) { '' } else { ' -- MEASURED NOTHING (no session log at/after the land date); a 0 outside the window is not a green' }
Write-Host (" C12 raw-engine-bypass flags = {0} [INFORM-only, LOW, NOT folded into TOTAL baseline]{1}" -f $c12Flags, $c12Note) -ForegroundColor DarkGray
# ---------------------------------------------------------------------------
# C13 - carve-out liveness (INFORM-only, NEW @S185; count SEPARATE, NOT in TOTAL)
# ---------------------------------------------------------------------------
# NAMING: this is the script's OWN C-numbering (C1..C12 already exist above). It is a
# DIFFERENT namespace from the S185 run-checklist codes; this detector implements
# checklist item C-8. Do not read "C13" as "checklist C-13".
#
# WHY: docs/governance/error-ledger.md "Carve-out o3 - dieu-kien SONG" pins the
# force-fire carve-out at rank `PASS-voi-carve-out`, VALID **only while BOTH grids are
# alive at the same time** (error-ledger:52-54, verbatim targets):
# (a) the read-only debt line still PRINTS at every stop point - scripts/nhip-no-probe.ps1
# (b) the "closeout-missing-ritual" probe is still WIRED - section H25-closeout-ritual
# (GAP-2) in THIS script
# One grid dead => the carve-out loses its self-transparency layer => RE-EVAL the rank,
# and fall back to unconditional running until the grid is alive again (error-ledger:58).
#
# THE GAP THIS CLOSES - self-declared, not hypothetical. The SE adap-report for the
# 2026-07-17 hub reply says, in its own honest-caveat (section 5):
# "Carve-out dang duoc canh bang NGUOI, khong bang MAY ... khong co detector nao canh
# chinh dieu-kien do. Neu mot luoi chet lang, nac PASS-voi-carve-out se TIEP TUC duoc
# khai ma khong ai biet"
# and section 4 files the liveness detector as "follow-up, chua build". This section is that
# follow-up. Until now the condition holds by DISCIPLINE, which is exactly the
# `cam-bang-tri-nho` class the ledger forbids.
#
# HOW EACH GRID IS MEASURED (existence is NOT liveness):
# (a) is measured in TWO parts, because a guard can fail in two independent ways:
# (a1) EMITS - the probe is RUN and must print a line starting `NHIP-NO:`.
# Existence alone proves nothing: nhip-no-probe.ps1 exits 0 ALWAYS and
# degrades to `probe-loi (khong chan)` on any exception, i.e. its failure
# mode is precisely "alive-looking and silent". Only running it separates
# the two. Cost measured before wiring: ~512 ms, and this script is an
# audit tool, not a stop point (the stop-point cheapness predicate C7 is
# about /pause and /tiep, not about the detector suite).
# (a2) IS CALLED - at least one stop-point command file must carry a real
# INVOCATION. use vs mention matters here as a measured fact, not a theory:
# a prior count of "6 call sites" included .claude/commands/check-email.md,
# where both hits are the script's NAME in prose and neither is a call; the
# true set is 5. The predicate therefore requires `-File <...>nhip-no-probe.ps1`
# on the line, which prose mentions do not carry. A machine that emits
# perfectly but that nothing calls is dead in the only sense that matters.
# (b) is measured by whether the H25 section SCORED anything this run
# ($script:H25Scored), not by whether its code is present. A section that runs and
# takes the [skip] branch prints reassuring output while measuring nothing.
#
# SEVERITY: INFORM. A dead grid is a governance fact of some weight, but folding a
# brand-new net into the audited TOTAL (and raising its severity) is a post-triage,
# owner-gated decision per the sink contract at the top of this file - never the net
# author's call. The line shape is identical to Write-Flag, so `comm before/after`
# isolates it and a fault-inject greps it exactly like any other flag.
Write-Section 'C13 - carve-out liveness (INFORM-only)'
$c13Flags = 0
$c13Measured = $false
$c13LedgerPath = Join-Path $RepoRoot 'docs\governance\error-ledger.md'
$c13Resolve = 'restore the dead grid, OR re-eval the carve-out rank in docs/governance/error-ledger.md (Carve-out o3) and fall back to unconditional running until it is alive again'
if (-not (Test-Path $c13LedgerPath)) {
Write-Host ' [skip] docs/governance/error-ledger.md not found - no carve-out rank is being claimed here, nothing to guard' -ForegroundColor DarkGray
}
else {
$c13Measured = $true
# ---- grid (a1): the probe RUNS and EMITS ----
$probePath = Join-Path $RepoRoot 'scripts\nhip-no-probe.ps1'
$aEmits = $false
$aWhy = ''
if (-not (Test-Path $probePath)) {
$aWhy = 'scripts/nhip-no-probe.ps1 is GONE from disk'
}
else {
$pOut = ''
try { $pOut = (& powershell.exe -ExecutionPolicy Bypass -File $probePath -RepoRoot $RepoRoot 2>&1 | Out-String) }
catch { $pOut = '' }
if ($pOut -match '(?m)^NHIP-NO:') { $aEmits = $true }
else {
$first = (($pOut -split "`r?`n") | Where-Object { $_.Trim().Length -gt 0 } | Select-Object -First 1)
if ($null -eq $first) { $first = '(no output at all)' }
$aWhy = ("the probe ran but printed no 'NHIP-NO:' line (first line: {0})" -f $first.Trim())
}
}
# ---- grid (a2): at least one stop point CALLS it ----
$callRx = '-File\s+\S*nhip-no-probe\.ps1'
$callSites = @()
$cmdDir = Join-Path $RepoRoot '.claude\commands'
if (Test-Path $cmdDir) {
foreach ($cf in (Get-ChildItem -Path $cmdDir -Filter *.md -File -ErrorAction SilentlyContinue)) {
$cl = Get-Content -Path $cf.FullName -Encoding UTF8 -ErrorAction SilentlyContinue
for ($i = 0; $i -lt $cl.Count; $i++) {
if ($cl[$i] -match $callRx) { $callSites += ("{0}:{1}" -f $cf.Name, ($i + 1)) }
}
}
}
# ---- verdicts ----
if (-not $aEmits) {
Write-InformFlag 'scripts/nhip-no-probe.ps1' `
("carve-out grid (a) DEAD - the debt line no longer prints: {0}. error-ledger 'Carve-out o3' requires BOTH grids alive; rank PASS-voi-carve-out is NOT valid while this is dead" -f $aWhy) `
$c13Resolve
$c13Flags++
}
elseif ($callSites.Count -eq 0) {
Write-InformFlag 'scripts/nhip-no-probe.ps1' `
("carve-out grid (a) DEAD-BY-DISCONNECT - the probe emits, but NO stop-point command actually calls it (0 lines matching '-File ...nhip-no-probe.ps1' under .claude/commands). A debt line nobody triggers is not printed 'at every stop point'") `
$c13Resolve
$c13Flags++
}
else {
Write-Host (" [ok] grid (a) ALIVE: nhip-no-probe emits NHIP-NO: and is CALLED from {0} stop point(s) - {1}" -f `
$callSites.Count, ($callSites -join ', ')) -ForegroundColor DarkGray
}
if ($script:H25Scored -le 0) {
Write-InformFlag 'scripts/governance-detectors.ps1 (H25-closeout-ritual)' `
'carve-out grid (b) DEAD - the closeout-ritual probe took its [skip] branch and scored 0 close-group this run, i.e. it is wired but measuring NOTHING. error-ledger Carve-out o3 requires BOTH grids alive' `
$c13Resolve
$c13Flags++
}
else {
Write-Host (" [ok] grid (b) ALIVE: H25-closeout-ritual scored {0} close-group(s) this run" -f $script:H25Scored) -ForegroundColor DarkGray
}
}
$c13Note = if ($c13Measured) { '' } else { ' -- MEASURED NOTHING (skipped above); a 0 after a skip is not a green' }
Write-Host (" C13 carve-out-liveness flags = {0} [INFORM-only, LOW, NOT folded into TOTAL baseline]{1}" -f $c13Flags, $c13Note) -ForegroundColor DarkGray
# ---------------------------------------------------------------------------
# C14 - stamp_verify mirror-drift (INFORM-only, NEW @S185; count SEPARATE, NOT in TOTAL)
# ---------------------------------------------------------------------------
# NAMING: script-local C-numbering (see C13 note). This implements S185 checklist C-19.
#
# WHY: scripts/stamp_verify.py is a MIRROR, not a source. Its own header says so:
# "mirror-of AI_INFRA/scripts/stamp_verify.py (ported S141 ...) - re-pull khi hub doi
# section-N canon. KHONG sua logic local."
# The SE adap-report for 2026-07-17-Governance-chuan-hoa-stamp-decode says it plainly
# (section 5 honest-caveat): "hub doi section-N canon ma SE quen re-pull thi 2 ben lech IM LANG - hien
# KHONG CO MAY NAO canh drift giua 2 ban, chi co header-note nhac NGUOI." Measured @S185:
# a note is the only guard, and a note guards nothing once nobody re-reads it. Two copies
# of one canon = the drift class B1 exists to prevent (same shape as C9 above).
#
# WHAT IS COMPARED - and why not the raw bytes. Measured today: the two files differ by
# exactly ONE line, the local mirror-of note itself (`diff` = 1 added line, 2378 B vs
# 2247 B). A raw-hash watcher would therefore INFORM on EVERY run forever - a permanently
# red lamp is read as noise within a week and is just a dead grid pointing the other way.
# So the drift verdict is taken on the LOGIC hash: the declared local-only note line is
# excluded (it is the file's own statement that only the note may differ), CRLF is
# normalized to LF (a checkout EOL setting is not a canon change), and the RAW hashes are
# printed alongside anyway so nothing is hidden by the normalization.
#
# HUB PATH IS DERIVED, NEVER HARDCODED: repo-root -> up 2 -> AI_INFRA. An absolute path
# with a machine-specific prefix silently degrades to the else-branch on any other
# machine, and a silent degrade reads exactly like "clean" - the S122 W5 lesson already
# paid for once in this file. Hub unreachable => "CHUA DO DUOC", never [ok], never silence.
Write-Section 'C14 - stamp_verify mirror-drift (INFORM-only)'
$c14Flags = 0
$c14Measured = $false
function Get-MirrorHashes {
param([string]$Path)
$txt = [System.IO.File]::ReadAllText($Path)
$raw = $txt -replace "`r`n", "`n"
$logic = (($raw -split "`n") | Where-Object { $_ -notmatch '^\s*#.*mirror-of' }) -join "`n"
$sha = [System.Security.Cryptography.SHA256]::Create()
$enc = New-Object System.Text.UTF8Encoding($false)
$h1 = ($sha.ComputeHash($enc.GetBytes($raw)) | ForEach-Object { $_.ToString('x2') }) -join ''
$h2 = ($sha.ComputeHash($enc.GetBytes($logic)) | ForEach-Object { $_.ToString('x2') }) -join ''
return [pscustomobject]@{ Raw = $h1; Logic = $h2; Lines = (($raw -split "`n").Count) }
}
$mirLocal = Join-Path $RepoRoot 'scripts\stamp_verify.py'
$hubRoot = $null
$p1 = Split-Path $RepoRoot -Parent
if ($null -ne $p1) { $p2 = Split-Path $p1 -Parent; if ($null -ne $p2) { $hubRoot = Join-Path $p2 'AI_INFRA' } }
$mirHub = if ($null -eq $hubRoot) { $null } else { Join-Path $hubRoot 'scripts\stamp_verify.py' }
if (-not (Test-Path $mirLocal)) {
Write-InformFlag 'scripts/stamp_verify.py' `
'stamp mirror MISSING locally - the ported verifier is gone, so /check-email and /send-email fall back to the regex-pin path with nothing checking the canon' `
're-pull scripts/stamp_verify.py from AI_INFRA, or declare the regex-pin path canonical and retire the mirror in the SAME change'
$c14Flags++; $c14Measured = $true
}
elseif ($null -eq $mirHub -or -not (Test-Path $mirHub)) {
$shown = if ($null -eq $mirHub) { '(hub root underivable)' } else { $mirHub }
Write-Host (" CHUA DO DUOC: hub copy not reachable at {0} - drift is UNMEASURED on this machine (this is NOT a green; the mirror may be stale and nothing here can tell)" -f $shown) -ForegroundColor Yellow
}
else {
$c14Measured = $true
$hl = Get-MirrorHashes $mirLocal
$hh = Get-MirrorHashes $mirHub
if ($hl.Logic -ne $hh.Logic) {
Write-InformFlag 'scripts/stamp_verify.py' `
("stamp mirror DRIFT: local logic-sha {0} != hub logic-sha {1} (local {2} lines / hub {3} lines; raw-sha local {4} hub {5}) - the mirror no longer carries the hub canon, so a stamp verified here can pass while the hub would fail it" -f `
$hl.Logic.Substring(0, 12), $hh.Logic.Substring(0, 12), $hl.Lines, $hh.Lines, $hl.Raw.Substring(0, 8), $hh.Raw.Substring(0, 8)) `
'BLOCKING NOTHING (INFORM): re-pull the hub copy over scripts/stamp_verify.py keeping ONLY the mirror-of header note, then re-run this detector'
$c14Flags++
}
else {
$rawNote = if ($hl.Raw -eq $hh.Raw) { 'raw identical too' } else { ('raw differs only by the declared mirror-of note: local {0} / hub {1}' -f $hl.Raw.Substring(0, 8), $hh.Raw.Substring(0, 8)) }
Write-Host (" [ok] stamp mirror in sync: logic-sha {0} on both sides ({1})" -f $hl.Logic.Substring(0, 12), $rawNote) -ForegroundColor DarkGray
}
}
$c14Note = if ($c14Measured) { '' } else { ' -- MEASURED NOTHING (hub unreachable); a 0 here is "chua do duoc", not a green' }
Write-Host (" C14 stamp-mirror-drift flags = {0} [INFORM-only, LOW, NOT folded into TOTAL baseline]{1}" -f $c14Flags, $c14Note) -ForegroundColor DarkGray
# ---------------------------------------------------------------------------
# C15 - V-5 encoding predicate (INFORM-only, NEW @S185; count SEPARATE, NOT in TOTAL)
# ---------------------------------------------------------------------------
# NAMING: script-local C-numbering (see C13 note). This implements S185 checklist C-11.
#
# WHY: the artifact roll-call machine (scripts/artifact-integrity.ps1) carries FOUR
# predicates - absent / empty / truncated / over-compressed - and is BLIND at the
# encoding level; the sub-w3 case proved it. The debt is on the books as "V-5 vi-tu
# ma-hoa CHUA CAM" in docs/governance/adap-reports/upgrade-pack-phased-bao-cao-cuoi.md:79
# (section 9, "No co moc"), where the V5 line is FILLED BY HAND with no machine behind
# it. The spec already existed - one grep line, TWO ranks - and was only waiting for a
# home. This section is that home, i.e. the hand-filled line becomes a measured count.
#
# TWO RANKS, kept apart on purpose (they need different actions):
# rank-1 DATA LOSS - raw U+FFFD present (UTF-8 bytes EF BF BD). The original bytes
# are GONE; re-decoding cannot bring them back, only the source
# artifact can. Detected on BYTES, so it is decode-independent.
# rank-2 MOJIBAKE-ONLY - no U+FFFD, but double-encoding signatures present. Nothing is
# lost; a correct re-decode repairs it.
# rank-1 dominates when a file shows both: "lost" is the actionable fact.
#
# USE vs MENTION is enforced at the BYTE level and that is not a detail: a previous
# baseline counted a file that writes the ASCII token "<FFFD>" seven times as if it held
# seven damaged characters, then a later reader saw the count "drop" and went hunting a
# phantom file. Matching raw EF BF BD only cannot make that mistake - a doc that TALKS
# about U+FFFD in ASCII is invisible to it (proved by the boundary case in the harness).
#
# CORPUS: $GovMd PLUS .claude/workflows/runs/**/*.md. The run folders are excluded from
# $GovMd by the C4 self-line rule (they describe patterns), but that rule is about
# CONTENT patterns and this predicate reads BYTES, so the exclusion buys nothing here and
# costs everything: measured @S185, 7 of the 12 damaged files live exactly there. An
# auditor that skipped runs/ has already reported a false 0-hit once.
#
# The mojibake markers are built from CODE POINTS (same U helper as the VN tokens) so this
# .ps1 stays pure-ASCII on disk (gotcha #30) and so the detector never carries a literal
# specimen of the thing it hunts.
Write-Section 'C15 - V-5 encoding predicate (INFORM-only)'
$c15Flags = 0
$c15Measured = $false
# Double-encoding signatures. Chosen for precision over recall: each is a Latin-1 lead
# byte followed by a continuation-range char, a pair that legitimate Vietnamese/English
# prose does not produce. Recall is deliberately partial - this is a lowering net.
# NOTE ON THE PARENTHESES, which are load-bearing: each element must be wrapped so the
# '+' concatenation cannot run ACROSS the commas. Written without them, PowerShell folds
# the three patterns into ONE string and the array silently becomes Count=1 - a net that
# then matches nothing and reports a confident 0. That is not hypothetical: the first
# measurement taken while building this section was exactly that shape and returned
# "mojibake: 0 files" on a corpus that really holds 13. Verified by printing
# ($MojiRx.Count = 3) and each element's code points before trusting any number.
$MojiRx = @(
((U @(0x00C3)) + '[' + (U @(0x00A0)) + '-' + (U @(0x00BF)) + ']'),
((U @(0x00E2, 0x20AC))),
((U @(0x00C4, 0x0091)))
)
# U+FFFD as its three UTF-8 bytes, seen through the Latin-1 view (EF BF BD).
$FFFD_BYTES = U @(0x00EF, 0x00BF, 0x00BD)
$v5Files = @($GovMd)
$runsDirV5 = Join-Path $RepoRoot '.claude\workflows\runs'
if (Test-Path $runsDirV5) {
$v5Files += @(Get-ChildItem -Path $runsDirV5 -Recurse -Filter *.md -File -ErrorAction SilentlyContinue)
}
$v5Files = $v5Files | Where-Object { $_.FullName -notmatch '[\\/](bin|obj|node_modules)[\\/]' }
if ($v5Files.Count -eq 0) {
Write-Host ' [skip] no .md in scope - encoding predicate measured NOTHING (not a green)' -ForegroundColor DarkGray
}
else {
$c15Measured = $true
$lossFiles = @(); $lossHits = 0
$mojiFiles = @(); $mojiHits = 0
foreach ($vf in $v5Files) {
$bytes = $null
try { $bytes = [System.IO.File]::ReadAllBytes($vf.FullName) } catch { $bytes = $null }
if ($null -eq $bytes) { continue }
# rank-1 counted on RAW BYTES via a Latin-1 view (codepage 28591 maps byte n ->
# char U+00n, 1:1 and lossless), so the needle EF BF BD is matched exactly as
# bytes while still using the fast .NET regex engine. A per-byte PowerShell loop
# measured the same thing but took ~18 s longer over this corpus.
$lat = [System.Text.Encoding]::GetEncoding(28591).GetString($bytes)
$n = ([regex]::Matches($lat, $FFFD_BYTES)).Count
if ($n -gt 0) {
$lossFiles += ("{0} x{1}" -f (Rel $vf.FullName), $n); $lossHits += $n
continue # rank-1 dominates: a lost-byte file is not re-classified as merely mojibake
}
$txt = [System.Text.Encoding]::UTF8.GetString($bytes)
$mm = 0
foreach ($rx in $MojiRx) { $mm += ([regex]::Matches($txt, $rx)).Count }
if ($mm -gt 0) { $mojiFiles += ("{0} x{1}" -f (Rel $vf.FullName), $mm); $mojiHits += $mm }
}
Write-Host (" V-5 measured over {0} .md (docs + .claude + workflows/runs) ; rank-1 data-loss: {1} file(s) / {2} hit(s) ; rank-2 mojibake-only: {3} file(s) / {4} hit(s)" -f `
$v5Files.Count, $lossFiles.Count, $lossHits, $mojiFiles.Count, $mojiHits)
if ($lossFiles.Count -gt 0) {
Write-InformFlag 'V-5 rank-1' `
("encoding DATA LOSS: {0} file(s) / {1} raw U+FFFD - the original bytes are gone and no re-decode recovers them; only the source artifact can" -f $lossFiles.Count, $lossHits) `
're-fetch each file from its source artifact (a re-save will NOT restore the lost characters); if a file is a frozen record, note the loss in it explicitly so it is not read as content'
$c15Flags++
foreach ($lf in $lossFiles) { Write-Host (" [rank-1] {0}" -f $lf) -ForegroundColor DarkGray }
}
if ($mojiFiles.Count -gt 0) {
Write-InformFlag 'V-5 rank-2' `
("encoding MOJIBAKE-ONLY: {0} file(s) / {1} double-encoding signature(s) - nothing is lost, a correct re-decode repairs it" -f $mojiFiles.Count, $mojiHits) `
're-decode the file as UTF-8 and re-save; verify by re-running this detector (rank-2 must go to 0 without rank-1 rising)'
$c15Flags++
foreach ($mf in $mojiFiles) { Write-Host (" [rank-2] {0}" -f $mf) -ForegroundColor DarkGray }
}
if ($lossFiles.Count -eq 0 -and $mojiFiles.Count -eq 0) {
Write-Host ' [ok] no encoding damage found in scope' -ForegroundColor DarkGray
}
}
$c15Note = if ($c15Measured) { '' } else { ' -- MEASURED NOTHING (skipped above); a 0 after a skip is not a green' }
Write-Host (" C15 V-5 encoding flags = {0} [INFORM-only, LOW, NOT folded into TOTAL baseline]{1}" -f $c15Flags, $c15Note) -ForegroundColor DarkGray
# ---------------------------------------------------------------------------
# Summary + C4 self-exclusion audit (RUNTIME proof)
# ---------------------------------------------------------------------------
@ -1965,7 +2395,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 + H24-5 counter-ancestor-classify + C9 hmw-width-drift + C11 h24-flag-form + C12 raw-engine-bypass): {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 + C9 hmw-width-drift + C11 h24-flag-form + C12 raw-engine-bypass + C13 carve-out-liveness + C14 stamp-mirror-drift + C15 V-5-encoding + the L7-bolt 'chua do duoc' lines): {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

@ -0,0 +1,190 @@
{
"note": "DENOMINATOR (mau-so) - liet ke TUONG MINH tung dong, KHONG phai output regex. Xem scripts/loi-hua-may-scan.ps1 .DESCRIPTION rang buoc (b). Entry disposition=TODO-classify VAN BI FLAG.",
"law": "2026-07-19-Governance-goi-chot-owner-nam-khoan khoan 4 (goi-chot:52)",
"scopeNote": "C6 corpus = docs/governance/*.md. Cham tay @S185 2026-08-09 tren cay snapshot dong bang.",
"dispositions": "history | caveat-honest | debt-open | design-note | draft-proposal | proof-elsewhere | violation-open | TODO-classify",
"violationOpenCount": 0,
"generatedAt": "2026-08-09",
"entries": [
{
"file": "docs/governance/adap-upgrade-pack-tracking.md",
"fragment": "🔸 \"được cửa gọi\" ≠ \"fire vô điều kiện\". nhiều máy nằm sau cổng nhịp hoặc cổng overdue. nấc này",
"disposition": "caveat-honest",
"note": "tu thu hep claim: chi khai co duong ghi tu dong, khong khai chay moi phien"
},
{
"file": "docs/governance/ctx-soft-memory-se-draft.md",
"fragment": "- 3 rào đầu tệp: (i) không chép lời anh (chỉ trỏ pause-\u003ck của context ) · (ii) cấm mọi biến-thể",
"disposition": "draft-proposal",
"note": "tep DRAFT chua wire; claim \"may doc ts quet _*\" chua neu path may"
},
{
"file": "docs/governance/ctx-soft-memory-se-draft.md",
"fragment": "- chỗ cắm: /session-start (ghi mind block-0 @cuối bootstrap) · /pause §2.6 (+refresh) · /tiep §",
"disposition": "draft-proposal",
"note": "tep DRAFT chua wire; mo ta cho cam tuong lai"
},
{
"file": "docs/governance/error-ledger.md",
"fragment": "điểm-đóng-thật ⇒ tự-hỏi \"diary monitor có delta? dòng counter có in?\" (chờ hub canonical detect",
"disposition": "history",
"note": "dong so loi (ledger) + tu khai cho hub canonical detector"
},
{
"file": "docs/governance/error-ledger.md",
"fragment": "- 5-why: (1) người viết sổ vừa làm xong việc nên \"biết\" nó đã xong → (2) viết câu tổng-kết theo",
"disposition": "history",
"note": "tuong thuat 5-why cua mot su co da xay ra"
},
{
"file": "docs/governance/error-ledger.md",
"fragment": "- guard hiện có đã giữ: h2 5-trục fidelity-flag bắt được cả 3 ca trong cùng phiên chúng sinh ra",
"disposition": "history",
"note": "ghi ca DA quan sat duoc (3 ca cung phien) - past-tense, co bang chung"
},
{
"file": "docs/governance/error-ledger.md",
"fragment": "- what: 3 closeout liên-tiếp — s128 289ba96 20:57 · s129 295c70c 23:16 · s130 71757fc 00:26 — c",
"disposition": "history",
"note": "bien ban su co closeout S128-S130"
},
{
"file": "docs/governance/error-ledger.md",
"fragment": "- fix (không prod-bug — process): (đợt này) s131 mở session-end đúng nghi-thức đầu-tiên sau 3 l",
"disposition": "history",
"note": "bien ban fix cua chinh su co do"
},
{
"file": "docs/governance/error-ledger.md",
"fragment": "5-why → gốc: luật c1 tồn tại từ s138, viết bởi người đã cân nhắc từng tệp (wal được nêu làm ngo",
"disposition": "caveat-honest",
"note": "tu khai KHONG co may: \"0 cong may nao chan ghi trong cua-so do\""
},
{
"file": "docs/governance/error-ledger.md",
"fragment": "fix (2 vế, lỗi kép): (a) vá hành-vi — closeout sau: mọi ghi vào bề-mặt-đo xếp hàng sau verdict ",
"disposition": "debt-open",
"note": "no duoc ghi ro dang du-dinh: \"CHUA LAM, ghi thanh no: can mot may chan/canh bao\""
},
{
"file": "docs/governance/harness-11-engine.md",
"fragment": "- c5 resolve-condition 🔴 — mỗi flag kèm resolve: \u003cđiều-kiện-gỡ-cờ . 2-strike anti-repeat = con",
"disposition": "design-note",
"note": "spec detector C5 + cau chong over-claim mechanized-2-strike"
},
{
"file": "docs/governance/harness-11-engine.md",
"fragment": "d6 — dò + nêu-cờ mọi thứ chạm prose/luật/con-trỏ-mục/thẩm-quyền/copy-chéo (3 detector c + monit",
"disposition": "design-note",
"note": "bang D6 mo ta ranh DO-only cua detector"
},
{
"file": "docs/governance/harness-11-engine.md",
"fragment": "honest nấc: h1 deterministic = executed-file + runtime-smoke s93 (chạy thật, 3-tier ok). h2 jud",
"disposition": "caveat-honest",
"note": "Honest nac: H2 judge = SCAFFOLD-only, tu tu choi nhan \"da do do-nho\""
},
{
"file": "docs/governance/harness-11-engine.md",
"fragment": "honest nấc tổng §j (checklist 12 sàn 🔴): a 3/3 đạt (mechanized + runtime fault-inject 4/4) · b",
"disposition": "caveat-honest",
"note": "Honest nac tong §J - liet tung san dat/khong dat"
},
{
"file": "docs/governance/harness-11-engine.md",
"fragment": "- (v) honest nấc k.e: codify s110 + dogfood run-1 = chính lượt adopt này (spec-file 2026-07-11-",
"disposition": "caveat-honest",
"note": "Honest nac K.E: mechanized = 0, convention"
},
{
"file": "docs/governance/harness-11-engine.md",
"fragment": "l.c — ánh-xạ se (🟡 form + honest): se = convention (grep + phân-loại = kỷ-luật người, không cơ",
"disposition": "caveat-honest",
"note": "L.C tu khai SE = convention, KHONG co-che-hoa auto-fix"
},
{
"file": "docs/governance/harness-11-engine.md",
"fragment": "honest nấc §l: convention (không mechanized auto-fix — nhánh chỉ-dò per caveat \"auto-write luật",
"disposition": "caveat-honest",
"note": "Honest nac §L: mechanized THAT = CHI grep; phan-loai + sua = nguoi"
},
{
"file": "docs/governance/harness-11-engine.md",
"fragment": "m.c — run-loop 2-tầng (🔴 4-upgrade2 — 🟡 largely already-met honest): tầng-1 đo mỗi session-en",
"disposition": "proof-elsewhere",
"note": "neu 5 may bang STEM khong duoi .ps1 (memory-selfimprove-audit, mfe-eval, ...) - deu ton tai"
},
{
"file": "docs/governance/harness-11-engine.md",
"fragment": "- detector = lưới giảm-sót, không khoá-cứng — bắt @đầu/đóng-phiên (theo nhịp); giữa 2 nhịp có k",
"disposition": "caveat-honest",
"note": "tu khai detector = luoi giam-sot, co khoang-mu giua 2 nhip"
},
{
"file": "docs/governance/harness-11-engine.md",
"fragment": "- nấc dogfood: a2/a3/d4/d9/d11 = se runtime-mechanized sẵn (h11 = chuẩn-hoá). c1-c3 + b3 + memo",
"disposition": "caveat-honest",
"note": "phan-dinh executed-file KHAC da-chay-quan-sat"
},
{
"file": "docs/governance/harness-11-engine.md",
"fragment": "vì sao ranh nằm ở đây chứ không ở mức nghiêm trọng: nghiêm trọng đo hậu quả , còn mã thoát quyế",
"disposition": "design-note",
"note": "van ly-le ve ranh ma thoat (chu de W3b), khong phai claim nang-luc"
},
{
"file": "docs/governance/harness-11-engine.md",
"fragment": "cám dỗ ở đây là \"chặn có 1 cái thì cổng gần như vô dụng\" rồi nới tiêu chí cho nhiều cờ chặn hơn",
"disposition": "design-note",
"note": "canh bao Goodhart ve viec noi tieu chi cho nhieu co chan hon"
},
{
"file": "docs/governance/harness-11-engine.md",
"fragment": "⇒ phân loại ở trên đã xong và có thể cắm , nhưng mã thoát chưa được đổi ở bất kỳ máy nào . mọi ",
"disposition": "caveat-honest",
"note": "tu khai ma thoat CHUA doi o bat ky may nao - moi may van exit 0"
},
{
"file": "docs/governance/naming-standard.md",
"fragment": "- 🔴 điểm mù có chủ đích, phải biết: 112/719 tệp (15,6%) không thuộc bất kỳ họ nào — ghi chú, b",
"disposition": "caveat-honest",
"note": "diem mu CO CHU DICH 112/719 tep - tu khai gioi han"
},
{
"file": "docs/governance/naming-standard.md",
"fragment": "⇒ chưa cần chủ dự án phán. nếu về sau ai muốn cho detectors:1109 kêu thật, lúc đó mới là xung đ",
"disposition": "design-note",
"note": "dieu kien tuong lai: neu ai muon cho detectors:1109 keu that"
},
{
"file": "docs/governance/ritual-labels.md",
"fragment": "🔸 khai thiếu, không giấu: vế \"đánh giá lại điều kiện trên đĩa\" của nhãn tự ý bỏ hiện chưa có m",
"disposition": "caveat-honest",
"note": "tu khai CHUA CO MAY cho ve danh-gia-lai-dieu-kien"
},
{
"file": "docs/governance/session-model-se-draft.md",
"fragment": "governance-detectors + h17 + m.b mỗi /session-start (skip light) per-l mở (giữa phiên chỉ khi n",
"disposition": "draft-proposal",
"note": "tep DRAFT chua wire; bang nhip do"
},
{
"file": "docs/governance/session-model-se-draft.md",
"fragment": "hub §2 mô tả điểm dừng ghi delta vào \"một tệp ngữ-cảnh cộng-dồn của phiên\" . wal se là sổ mạch-",
"disposition": "design-note",
"note": "mo ta detector C5 cap 40 dong (neu bang ID trong engine doc)"
},
{
"file": "docs/governance/vocab-alias-map.md",
"fragment": "một session-log viết \"two-tier\" hồi s60 là đúng sự thật lịch sử — nó phản ánh mô hình còn hiệu ",
"disposition": "design-note",
"note": "mo ta hanh vi detector C3 voi file dong bang"
},
{
"file": "docs/governance/vocab-alias-map.md",
"fragment": "về việc detector có \"im\" sau khi tạo file này không",
"disposition": "design-note",
"note": "tieu de muc ban ve viec detector co im khong"
}
]
}

View File

@ -0,0 +1,591 @@
<#
.SYNOPSIS
loi-hua-may-scan.ps1 - C-12 machine-promise scanner (khoan 4.4, broadcast
2026-07-19-Governance-goi-chot-owner-nam-khoan).
.DESCRIPTION
Class scanned: sentences that ASSERT a machine capability ("X chan/quet/bat/
dam-nhiem Y") in governance docs. Law (goi-chot:52): such a sentence MUST carry
existence-proof AT THE POINT OF CLAIM = {machine path + runnable command +
test/positive-control}. Missing any piece => the sentence MUST be written in the
intent form ("se build - CHUA co may"). Writing it in the have-it form is banned.
Until today this gate was HUMAN-ONLY: both hub (goi-chot:54) and SE
(adap-report 4.4 + runs/2026-07-22-S144-adap-dot-11/C6-cua-loi-hua-may.md:60)
declared the automatic scanner as INTENT, not existing. This script is that
scanner. It is DETECT-and-FLAG-only: it never edits a file, exit code is
always 0 in scan mode (teeth live in -SelfTest, which exits 1 on failure).
FOUR design constraints inherited from C6 (violating any = the machine is worse
than nothing, because a green scan would then mean "not measured"):
(b) REGEX IS NOT THE DENOMINATOR. C6 measured: literal "may se" -> 0 hits
(blind: a fault-injected fake promise stayed green), loose regex -> 19 hits
with 4/19 = 21% false positives. Regex here is a CANDIDATE SIEVE only; the
denominator is the ENUMERATED baseline file (loi-hua-may-baseline.json).
(c) use ORTHOGONAL-TO mention, split by ENCLOSURE (not by shape, not by
charset). A line that DEFINES the rule or QUOTES an anti-pattern is not a
violation. Enclosure = fenced code block, blockquote, or quote-pair
around the verb. Same class SE paid for 3 generations in one wave (S123
citation-trap).
(d) MEASURE THE GATE, NOT THE SWEEP. goi-chot:53 - "chan tu luc VIET".
This script reports lines NOT in the baseline (i.e. written after the gate
went in). It NEVER claims the corpus is clean.
(e) BACKFILL IS TIME-BOXED. goi-chot:55 floor covers governance docs that are
NEW. Legacy lines live in the baseline with an explicit disposition.
FP-guard, tested: Vietnamese "bat buoc" = MANDATORY, not CATCH. The catch verb
carries a negative lookahead so it can never swallow it. This exact substring
bug produced the 21% inflation in C6 and is control #2 of -SelfTest.
ASCII-only script body (gotcha #30): PowerShell 5.1 decodes a BOM-less .ps1 with
the system ANSI codepage under -File, which mojibakes any inline Vietnamese
literal so it stops matching correctly-decoded UTF-8 content. Every Vietnamese
token is therefore built from Unicode code points at RUNTIME.
.PARAMETER RepoRoot
Repo root. Default = one level up from scripts/. Point it at a COPY of the tree
to fault-inject without touching the real repo.
.PARAMETER BaselinePath
Enumerated denominator. Default = scripts/loi-hua-may-baseline.json.
.PARAMETER All
Widen scope from the C6 corpus (docs/governance/*.md) to the wider governance
surface (rules/CLAUDE/commands/skills/gotchas).
.PARAMETER EmitBaseline
Print the current candidate set as baseline JSON with disposition
"TODO-classify". Entries left at TODO-classify are STILL FLAGGED (LOW) - so
emitting a baseline can never silence anything on its own.
.PARAMETER SelfTest
Run the 7 controls on a throwaway tree under $env:TEMP. Exits 1 if any control
fails. Nothing is written inside the repo.
.EXAMPLE
powershell.exe -ExecutionPolicy Bypass -File scripts/loi-hua-may-scan.ps1
.EXAMPLE
powershell.exe -ExecutionPolicy Bypass -File scripts/loi-hua-may-scan.ps1 -SelfTest
#>
param(
[string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path,
[string]$BaselinePath,
[switch]$All,
[switch]$EmitBaseline,
[switch]$SelfTest,
[switch]$NoProximity # audit hatch: run the pre-proximity sieve so anyone can
# diff the two flag sets and check what the rule removed
)
$ErrorActionPreference = 'Continue'
# ---------------------------------------------------------------------------
# Unicode-token builder (gotcha #30 mojibake guard) - see .DESCRIPTION.
# ---------------------------------------------------------------------------
function U { param([int[]]$cp) -join ($cp | ForEach-Object { [char]$_ }) }
$T_CHAN = U @(0x63, 0x68, 0x1EB7, 0x6E) # chan (block)
$T_QUET = U @(0x71, 0x75, 0xE9, 0x74) # quet (scan)
$T_BAT = U @(0x62, 0x1EAF, 0x74) # bat (catch)
$T_BUOC = U @(0x62, 0x75, 0x1ED9, 0x63) # buoc (in "bat buoc" = mandatory)
$T_DAM = U @(0x111, 0x1EA3, 0x6D) # dam (dam-nhiem / dam bao)
$T_CANH = U @(0x63, 0x61, 0x6E, 0x68) # canh (watch)
$T_TUDONG= U @(0x74, 0x1EF1, 0x20, 0x111, 0x1ED9, 0x6E, 0x67) # tu dong (automatic)
$T_MAY = U @(0x6D, 0xE1, 0x79) # may (machine)
$T_LUOI = U @(0x6C, 0x1B0, 0x1EDB, 0x69) # luoi (net)
$T_SE = U @(0x73, 0x1EBD) # se (will) - fixtures only
# ---------------------------------------------------------------------------
# Stage 1 - CANDIDATE SIEVE (explicitly NOT the denominator; see constraint b)
# A candidate = line carrying a MACHINE NOUN and a CAPABILITY VERB.
# ---------------------------------------------------------------------------
$NounPattern = '(?i)(detector|guard|hook|script|checker|scanner|\.ps1|\.py\b|' +
[regex]::Escape($T_MAY) + '|' + [regex]::Escape($T_LUOI) + ')'
# "bat" carries a negative lookahead so "bat buoc" (mandatory) can never be read
# as "bat" (catch). This is the exact 21% inflation bug from C6, control #2.
# The separator class is [\s-]* , NOT \s* : this repo writes the word BOTH ways
# ("bat buoc" and "bat-buoc"). A \s*-only guard leaks on every hyphenated
# occurrence - found live at fable-real-runbook.md:571, where only the proximity
# rule was accidentally masking it. Controls #2 and #9 pin both spellings.
$VerbPattern = '(?i)(' +
[regex]::Escape($T_CHAN) + '|' +
[regex]::Escape($T_QUET) + '|' +
[regex]::Escape($T_BAT) + '(?![\s-]*' + [regex]::Escape($T_BUOC) + ')|' +
[regex]::Escape($T_DAM) + '|' +
[regex]::Escape($T_CANH) + '|' +
[regex]::Escape($T_TUDONG) + '|enforce|detect)'
# Stage 1b - PROXIMITY. A capability assertion keeps its subject next to its
# verb ("detector X chan Y"). Without this, any long table row or narrative
# paragraph that happens to contain a machine noun in one cell and a verb 300
# chars later in another cell is swept in - which is how the C6 loose regex got
# to 21% false positives. The line is therefore split on the markdown cell
# separator and the pair must co-occur inside ONE chunk, within MaxGap chars.
# Verified against the real corpus by diffing the flag set before/after and
# reading every line that dropped out (see run artifact) - the rule removes
# unrelated co-occurrences, not witnesses.
$ProxMaxGap = 80
# ---------------------------------------------------------------------------
# Stage 3 - EXISTENCE-PROOF tokens ("ma nao lam viec do?" answered inline).
# ---------------------------------------------------------------------------
$PathPattern = '(?i)([\w./-]+\.(?:ps1|py|js|sh|yml|yaml))'
$CmdPattern = '(?i)(powershell\.exe|pwsh|dotnet\s|node\s|python\s|npm\s|git\s)'
# ---------------------------------------------------------------------------
# C4-style self-line exclusion: files that DESCRIBE the pattern must never be
# scanned for it, or the detector flags itself (and, via quotation, its own
# children - the S123 citation-trap went 3 generations in one wave).
# ---------------------------------------------------------------------------
$SelfExclude = @(
'scripts/loi-hua-may-scan.ps1',
'scripts/loi-hua-may-baseline.json',
'.claude/workflows/runs/'
)
function Rel($full) {
$r = $full
if ($full.StartsWith($RepoRoot, [StringComparison]::OrdinalIgnoreCase)) {
$r = $full.Substring($RepoRoot.Length).TrimStart('\', '/')
}
return ($r -replace '\\', '/')
}
function Test-SelfExcluded($relPath) {
foreach ($x in $SelfExclude) { if ($relPath -like ('*' + $x + '*')) { return $true } }
return $false
}
# ---------------------------------------------------------------------------
# Scope. Default = the C6 corpus (docs/governance top level) so the number this
# script prints is comparable with the C6 measurement. -All widens it.
# adap-reports/ and adap-requests/ stay out by default: they are reports ABOUT
# hub broadcasts and are quotation-heavy by construction (mention, not use).
# ---------------------------------------------------------------------------
function Get-ScopeFiles {
param([string]$Root, [switch]$Wide)
$files = @()
$gov = Join-Path $Root 'docs/governance'
if (Test-Path $gov) {
$files += Get-ChildItem -LiteralPath $gov -Filter '*.md' -File -ErrorAction SilentlyContinue
}
if ($Wide) {
foreach ($p in @('docs/rules.md', 'docs/gotchas.md', 'CLAUDE.md', 'docs/CLAUDE.md')) {
$f = Join-Path $Root $p
if (Test-Path $f) { $files += Get-Item -LiteralPath $f }
}
$cmd = Join-Path $Root '.claude/commands'
if (Test-Path $cmd) {
$files += Get-ChildItem -LiteralPath $cmd -Filter '*.md' -File -Recurse -ErrorAction SilentlyContinue
}
$sk = Join-Path $Root '.claude/skills'
if (Test-Path $sk) {
$files += Get-ChildItem -LiteralPath $sk -Filter 'SKILL.md' -File -Recurse -ErrorAction SilentlyContinue
}
}
return ($files | Where-Object { -not (Test-SelfExcluded (Rel $_.FullName)) })
}
# ---------------------------------------------------------------------------
# Stage 2 - use ORTHOGONAL-TO mention, by ENCLOSURE (constraint c).
# Returns the mention reason, or $null when the line is a genuine ASSERTION.
# m1 fenced code block m2 blockquote m3 verb sits inside a quote pair
# ---------------------------------------------------------------------------
function Get-EnclosureSpans {
param([string]$Line)
$spans = @()
# Straight double quote, curly quotes, guillemets, backtick code span.
$pairs = @(
@{ open = [char]0x0022; close = [char]0x0022 },
@{ open = [char]0x201C; close = [char]0x201D },
@{ open = [char]0x00AB; close = [char]0x00BB },
@{ open = [char]0x2018; close = [char]0x2019 },
@{ open = [char]0x0060; close = [char]0x0060 }
)
foreach ($p in $pairs) {
$i = 0
while ($true) {
$s = $Line.IndexOf([string]$p.open, $i)
if ($s -lt 0) { break }
$e = $Line.IndexOf([string]$p.close, $s + 1)
if ($e -lt 0) { break }
$spans += , @($s, $e)
$i = $e + 1
}
}
return $spans
}
function Get-MentionReason {
param([string]$Line, [bool]$InFence)
if ($InFence) { return 'm1-code-fence' }
if ($Line -match '^\s{0,3}>') { return 'm2-blockquote' }
$m = [regex]::Match($Line, $VerbPattern)
if ($m.Success) {
foreach ($sp in (Get-EnclosureSpans $Line)) {
if ($m.Index -gt $sp[0] -and $m.Index -lt $sp[1]) { return 'm3-quoted' }
}
}
return $null
}
# ---------------------------------------------------------------------------
# Stage 3 - proof AT THE POINT OF CLAIM ("tai cho", goi-chot:52).
# Returns: 'proof-ok' | 'proof-dead:<path>' | $null (no proof at all).
#
# Window rule, and why it is this narrow: the first cut used +/-2 lines and
# -SelfTest failed 2/8 - adjacent claims stole each other's proof (a promise
# inherited the proof of the bullet above it, and a clean line inherited the
# DEAD path of the bullet above it, i.e. contamination in both directions).
# "Tai cho" therefore means: the claim's own line, the line directly under it
# (sub-bullet / wrapped continuation), and the line directly above ONLY when
# that line is not itself a claim - proof already spoken for by another
# assertion is not proof of this one.
#
# 'proof-dead' (HIGH) is raised ONLY from the claim's OWN line: citing a machine
# that does not exist on disk is the sharpest failure of this class, but a dead
# path on a NEIGHBOURING line belongs to the neighbour, not here.
# ---------------------------------------------------------------------------
function Test-IsCandidate {
param([string]$Line)
if ($Line -notmatch $NounPattern) { return $false }
if ($Line -notmatch $VerbPattern) { return $false }
if ($NoProximity) { return $true }
foreach ($chunk in ($Line -split '\|')) {
$n = [regex]::Match($chunk, $NounPattern)
if (-not $n.Success) { continue }
foreach ($v in [regex]::Matches($chunk, $VerbPattern)) {
foreach ($nn in [regex]::Matches($chunk, $NounPattern)) {
if ([Math]::Abs($v.Index - $nn.Index) -le $ProxMaxGap) { return $true }
}
}
}
return $false
}
# Machine index: every runnable file under scripts/ and .claude/. This repo
# routinely names a machine WITHOUT its directory (`governance-detectors.ps1`,
# `hmw.js:171-186`), and such a citation does answer the reviewer's one question
# ("ma nao lam viec do?"). A bare name that RESOLVES therefore counts as proof;
# a bare name that resolves nowhere is NOT escalated to HIGH (it may be a generic
# word), it simply fails to prove anything.
function New-MachineIndex {
param([string]$Root)
$set = @{}
foreach ($d in @('scripts', '.claude')) {
$p = Join-Path $Root $d
if (-not (Test-Path $p)) { continue }
Get-ChildItem -Path $p -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -notmatch '[\\/]node_modules[\\/]' -and
@('.ps1', '.py', '.js', '.sh') -contains $_.Extension.ToLowerInvariant() } |
ForEach-Object { $set[$_.Name.ToLowerInvariant()] = $true }
}
return $set
}
function Get-PathVerdict {
param([string]$Line, [string]$Root, $Index)
$dead = @()
foreach ($pm in [regex]::Matches($Line, $PathPattern)) {
# Trim wrappers only. NEVER TrimStart('.') - it eats the leading dot of
# ".claude/hooks/..." and turns a live path into a phantom dead one.
# That exact bug produced this scanner's first HIGH flag, on a path that
# exists (naming-standard.md:50 -> .claude/hooks/wal-flush.ps1).
$cand = $pm.Groups[1].Value.Trim('`', '(', ')').TrimEnd(',', '.', ';', ':')
if ($cand -notmatch '[\\/]') {
# bare filename: proof only if it resolves to a real machine
if ($Index -and $Index.ContainsKey($cand.ToLowerInvariant())) { return 'ok' }
continue
}
$full = Join-Path $Root ($cand -replace '/', '\')
if (Test-Path -LiteralPath $full) { return 'ok' }
if ($Index -and $Index.ContainsKey(([System.IO.Path]::GetFileName($cand)).ToLowerInvariant())) {
return 'ok' # path stale but the machine exists elsewhere -> not a phantom
}
$dead += $cand
}
if ($dead.Count -gt 0) { return ('dead:' + $dead[0]) }
return $null
}
function Get-ProofState {
param([string[]]$Lines, [int]$Idx, [string]$Root, $Index)
# (1) own line
if ($Lines[$Idx] -match $CmdPattern) { return 'proof-ok' }
$own = Get-PathVerdict -Line $Lines[$Idx] -Root $Root -Index $Index
if ($own -eq 'ok') { return 'proof-ok' }
# (2) neighbours - proof only, never dead-path escalation
$neighbours = @()
if ($Idx + 1 -le $Lines.Count - 1) { $neighbours += ($Idx + 1) }
if ($Idx - 1 -ge 0 -and -not (Test-IsCandidate $Lines[$Idx - 1])) { $neighbours += ($Idx - 1) }
foreach ($n in $neighbours) {
if ($Lines[$n] -match $CmdPattern) { return 'proof-ok' }
if ((Get-PathVerdict -Line $Lines[$n] -Root $Root -Index $Index) -eq 'ok') { return 'proof-ok' }
}
if ($own) { return ('proof-' + $own) } # dead:<path> on the claim's own line
return $null
}
# ---------------------------------------------------------------------------
# Baseline (THE DENOMINATOR - enumerated by hand, constraint b).
# Matching is by NORMALISED TEXT FRAGMENT, never by line number: line numbers
# drift on every edit above them, and a drifting anchor would silently re-flag
# (or silently silence) the wrong line.
# ---------------------------------------------------------------------------
function Get-NormText {
param([string]$s)
$t = $s -replace '[`*_>#|]', ' '
$t = $t -replace '\s+', ' '
return $t.Trim().ToLowerInvariant()
}
function Load-Baseline {
param([string]$Path)
$result = @{ entries = @(); loaded = $false; rejected = @() }
if (-not $Path -or -not (Test-Path -LiteralPath $Path)) { return $result }
try {
$raw = Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json
} catch {
Write-Host ("[LOI-HUA-MAY] WARN | baseline unreadable: " + $Path) -ForegroundColor Yellow
return $result
}
$result.loaded = $true
foreach ($e in $raw.entries) {
# Teeth: a too-short fragment would match half the corpus and silence it.
if (-not $e.fragment -or $e.fragment.Length -lt 25) {
$result.rejected += $e
continue
}
$result.entries += [pscustomobject]@{
file = $e.file
fragment = (Get-NormText $e.fragment)
disposition = $e.disposition
note = $e.note
}
}
return $result
}
function Find-BaselineEntry {
param($Baseline, [string]$RelFile, [string]$NormLine)
foreach ($e in $Baseline.entries) {
if ($e.file -ne $RelFile) { continue }
if ($NormLine.Contains($e.fragment)) { return $e }
}
return $null
}
# ---------------------------------------------------------------------------
# Core scan.
# ---------------------------------------------------------------------------
function Invoke-Scan {
param([string]$Root, $Files, $Baseline)
$out = @()
$index = New-MachineIndex -Root $Root
foreach ($f in $Files) {
$rel = Rel $f.FullName
$lines = @(Get-Content -LiteralPath $f.FullName -Encoding UTF8 -ErrorAction SilentlyContinue)
$inFence = $false
for ($i = 0; $i -lt $lines.Count; $i++) {
$line = $lines[$i]
if ($line -match '^\s*```') { $inFence = -not $inFence; continue }
if (-not (Test-IsCandidate $line)) { continue }
$mention = Get-MentionReason -Line $line -InFence $inFence
if ($mention) {
$out += [pscustomobject]@{ File = $rel; Line = ($i + 1); Text = $line
Verdict = 'mention'; Severity = 'INFO'; Reason = $mention }
continue
}
$proof = Get-ProofState -Lines $lines -Idx $i -Root $Root -Index $index
if ($proof -eq 'proof-ok') {
$out += [pscustomobject]@{ File = $rel; Line = ($i + 1); Text = $line
Verdict = 'proved'; Severity = 'INFO'; Reason = 'proof-ok' }
continue
}
if ($proof -and $proof.StartsWith('proof-dead')) {
$out += [pscustomobject]@{ File = $rel; Line = ($i + 1); Text = $line
Verdict = 'flag'; Severity = 'HIGH'; Reason = $proof }
continue
}
$known = Find-BaselineEntry -Baseline $Baseline -RelFile $rel -NormLine (Get-NormText $line)
if ($known -and $known.disposition -ne 'TODO-classify') {
$out += [pscustomobject]@{ File = $rel; Line = ($i + 1); Text = $line
Verdict = 'baseline'; Severity = 'INFO'; Reason = ('baseline:' + $known.disposition) }
continue
}
if ($known) {
$out += [pscustomobject]@{ File = $rel; Line = ($i + 1); Text = $line
Verdict = 'flag'; Severity = 'LOW'; Reason = 'baseline-TODO-classify' }
continue
}
$out += [pscustomobject]@{ File = $rel; Line = ($i + 1); Text = $line
Verdict = 'flag'; Severity = 'MED'; Reason = 'no-proof-not-in-baseline' }
}
}
return $out
}
# ---------------------------------------------------------------------------
# -SelfTest : 7 controls on a throwaway tree. Positive controls prove the net
# catches; negative controls prove it does not catch what it must not. A net
# that only ever passes happy-path proves nothing (house rule:
# feedback_faultinjection_proves_teeth).
# ---------------------------------------------------------------------------
function Invoke-SelfTest {
$tmp = Join-Path $env:TEMP ('loi-hua-may-selftest-' + [guid]::NewGuid().ToString('N').Substring(0, 8))
$gov = Join-Path $tmp 'docs\governance'
$scr = Join-Path $tmp 'scripts'
New-Item -ItemType Directory -Path $gov -Force | Out-Null
New-Item -ItemType Directory -Path $scr -Force | Out-Null
Set-Content -LiteralPath (Join-Path $scr 'real-machine.ps1') -Value '# fixture' -Encoding UTF8
$q = [char]0x0022
$fx = @()
$fx += '# fixture'
$fx += ('C1 detector ' + $T_CHAN + ' moi cau hua sai trong doc governance.') # 1 must flag MED
$fx += ('C2 3 GAP-guard ' + $T_BAT + ' ' + $T_BUOC + ' CA 2 mode - pin de stamp co nghia.') # 2 must NOT flag
$fx += ('> C3 luat: cau dang ' + $q + $T_MAY + ' ' + $T_SE + ' ' + $T_CHAN + ' X' + $q + ' phai co existence-proof.') # 3 must NOT
$fx += ('C4 detector ' + $T_QUET + ' drift: `scripts/real-machine.ps1` chay duoc.') # 4 must NOT flag
$fx += ('C5 detector ' + $T_QUET + ' drift: `scripts/khong-ton-tai.ps1` chay duoc.') # 5 must flag HIGH
$fx += 'C6 doan van thuong khong nhac may moc gi ca, chi la mo ta quy trinh.' # 6 must NOT flag
$fx += ('C7 legacy detector ' + $T_CHAN + ' cau hua cu tu truoc khi cong nay duoc cai dat.') # 7 baseline-suppressed
$fx += ('C9 RETURN guard ' + $T_BAT + '-' + $T_BUOC + ' theo SCHEMA - dau noi la gach ngang.') # 9 must NOT flag
$fx += ('C10 detector ' + $T_QUET + ' drift: `real-machine.ps1` - ten tran khong kem thu muc.') # 10 must NOT flag
$fx += ('C11 detector ' + $T_QUET + ' drift: `khong-he-ton-tai.ps1` - ten tran khong resolve.') # 11 must flag MED
Set-Content -LiteralPath (Join-Path $gov 'selftest.md') -Value $fx -Encoding UTF8
$blPath = Join-Path $tmp 'baseline.json'
$frag = ('legacy detector ' + $T_CHAN + ' cau hua cu tu truoc khi cong nay')
$bl = [pscustomobject]@{
note = 'selftest fixture baseline'
entries = @([pscustomobject]@{ file = 'docs/governance/selftest.md'; fragment = $frag
disposition = 'legacy-pre-gate'; note = 'fixture' })
}
Set-Content -LiteralPath $blPath -Value ($bl | ConvertTo-Json -Depth 5) -Encoding UTF8
$script:RepoRoot = $tmp
$files = Get-ScopeFiles -Root $tmp
$res = Invoke-Scan -Root $tmp -Files $files -Baseline (Load-Baseline $blPath)
function Get-Row($tag) { return ($res | Where-Object { $_.Text -match ('^' + $tag + '\b') } | Select-Object -First 1) }
function Get-QuotedRow($tag) { return ($res | Where-Object { $_.Text -match $tag } | Select-Object -First 1) }
$checks = @()
$r1 = Get-Row 'C1'; $checks += @{ n = 'C1 positive-control: cau hua khong proof'
ok = ($r1 -and $r1.Verdict -eq 'flag' -and $r1.Severity -eq 'MED'); got = $r1 }
$r2 = Get-Row 'C2'; $checks += @{ n = 'C2 FP-guard: bat-buoc KHONG duoc doc thanh bat'
ok = (-not $r2); got = $r2 }
$r3 = Get-QuotedRow 'C3'; $checks += @{ n = 'C3 use/mention: dong dinh-nghia-luat trong blockquote'
ok = ($r3 -and $r3.Verdict -eq 'mention'); got = $r3 }
$r4 = Get-Row 'C4'; $checks += @{ n = 'C4 proof-ok: script duoc cite CO tren dia'
ok = ($r4 -and $r4.Verdict -eq 'proved'); got = $r4 }
$r5 = Get-Row 'C5'; $checks += @{ n = 'C5 positive-control: cite script KHONG ton tai'
ok = ($r5 -and $r5.Verdict -eq 'flag' -and $r5.Severity -eq 'HIGH'); got = $r5 }
$r6 = Get-Row 'C6'; $checks += @{ n = 'C6 negative-control: van thuong khong bi hut vao'
ok = (-not $r6); got = $r6 }
$r7 = Get-Row 'C7'; $checks += @{ n = 'C7 baseline: dong legacy da liet ke -> khong flag'
ok = ($r7 -and $r7.Verdict -eq 'baseline'); got = $r7 }
# C8: anti-Goodhart - the same line with disposition TODO-classify must STILL flag.
$bl2 = [pscustomobject]@{ note = 'todo'; entries = @([pscustomobject]@{
file = 'docs/governance/selftest.md'; fragment = $frag; disposition = 'TODO-classify'; note = 'x' }) }
$bl2Path = Join-Path $tmp 'baseline-todo.json'
Set-Content -LiteralPath $bl2Path -Value ($bl2 | ConvertTo-Json -Depth 5) -Encoding UTF8
$res2 = Invoke-Scan -Root $tmp -Files $files -Baseline (Load-Baseline $bl2Path)
$r8 = $res2 | Where-Object { $_.Text -match '^C7\b' } | Select-Object -First 1
$checks += @{ n = 'C8 anti-Goodhart: TODO-classify KHONG duoc lam tat den'
ok = ($r8 -and $r8.Verdict -eq 'flag'); got = $r8 }
$r9 = Get-Row 'C9'; $checks += @{ n = 'C9 FP-guard: bat-buoc CO GACH NGANG cung khong duoc doc thanh bat'
ok = (-not $r9); got = $r9 }
$r10 = Get-Row 'C10'; $checks += @{ n = 'C10 proof: ten may TRAN resolve duoc = da tra loi "ma nao lam viec do"'
ok = ($r10 -and $r10.Verdict -eq 'proved'); got = $r10 }
$r11 = Get-Row 'C11'; $checks += @{ n = 'C11 positive-control: ten may TRAN khong resolve -> van flag'
ok = ($r11 -and $r11.Verdict -eq 'flag'); got = $r11 }
Write-Host ''
Write-Host '===== SELF-TEST (tree: ' -NoNewline -ForegroundColor Cyan
Write-Host $tmp -NoNewline; Write-Host ') =====' -ForegroundColor Cyan
$fail = 0
foreach ($c in $checks) {
if ($c.ok) {
Write-Host (' PASS ' + $c.n) -ForegroundColor Green
} else {
$fail++
$g = 'ABSENT'
if ($c.got) { $g = ($c.got.Verdict + '/' + $c.got.Severity + '/' + $c.got.Reason) }
Write-Host (' FAIL ' + $c.n + ' [got: ' + $g + ']') -ForegroundColor Red
}
}
Write-Host ''
Write-Host ('SELF-TEST: ' + ($checks.Count - $fail) + '/' + $checks.Count + ' PASS') -ForegroundColor (& { if ($fail) { 'Red' } else { 'Green' } })
Remove-Item -LiteralPath $tmp -Recurse -Force -ErrorAction SilentlyContinue
if ($fail) { exit 1 }
exit 0
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
if ($SelfTest) { Invoke-SelfTest }
if (-not $BaselinePath) { $BaselinePath = Join-Path $PSScriptRoot 'loi-hua-may-baseline.json' }
$baseline = Load-Baseline $BaselinePath
$files = Get-ScopeFiles -Root $RepoRoot -Wide:$All
$res = Invoke-Scan -Root $RepoRoot -Files $files -Baseline $baseline
if ($EmitBaseline) {
$entries = @()
foreach ($r in ($res | Where-Object { $_.Verdict -eq 'flag' })) {
$norm = (Get-NormText $r.Text)
$len = [Math]::Min(90, $norm.Length)
$entries += [pscustomobject]@{ file = $r.File; fragment = $norm.Substring(0, $len)
disposition = 'TODO-classify'; note = ('auto-emitted line ' + $r.Line) }
}
([pscustomobject]@{
note = 'DENOMINATOR (enumerated by hand). TODO-classify still flags - emitting cannot silence.'
scope = ($files | ForEach-Object { Rel $_.FullName })
generatedAt = (Get-Date -Format 'yyyy-MM-dd')
entries = $entries
} | ConvertTo-Json -Depth 6)
exit 0
}
$flags = @($res | Where-Object { $_.Verdict -eq 'flag' })
$mention = @($res | Where-Object { $_.Verdict -eq 'mention' })
$proved = @($res | Where-Object { $_.Verdict -eq 'proved' })
$known = @($res | Where-Object { $_.Verdict -eq 'baseline' })
Write-Host ''
Write-Host '===== LOI-HUA-MAY (khoan 4.4) =====' -ForegroundColor Cyan
Write-Host ('scope : ' + $files.Count + ' file' + $(if ($All) { ' (-All wide)' } else { ' (C6 corpus: docs/governance/*.md)' }))
Write-Host ('baseline : ' + $(if ($baseline.loaded) { ($baseline.entries.Count.ToString() + ' entry - ' + (Rel $BaselinePath)) } else { 'KHONG LOAD DUOC - moi dong deu se bi flag' }))
if ($baseline.rejected.Count -gt 0) {
Write-Host (' WARN : ' + $baseline.rejected.Count + ' entry bi loai (fragment < 25 ky tu, qua ngan -> se lam tat den nham)') -ForegroundColor Yellow
}
Write-Host ('candidate : ' + $res.Count + ' -> mention ' + $mention.Count + ' | proved ' + $proved.Count + ' | baseline ' + $known.Count + ' | FLAG ' + $flags.Count)
# The baseline total is printed BROKEN DOWN, never as one number: "30 known" would
# read as 30 settled lines, while "debt-open 1 / draft-proposal 3" keeps the debt
# visible. Same reason the emitter cannot self-classify.
if ($known.Count -gt 0) {
$bd = $known | Group-Object Reason | Sort-Object Count -Descending |
ForEach-Object { ($_.Name -replace '^baseline:', '') + ' ' + $_.Count }
Write-Host (' breakdown: ' + ($bd -join ' | '))
}
Write-Host ''
foreach ($r in ($flags | Sort-Object Severity, File, Line)) {
$color = 'Yellow'; if ($r.Severity -eq 'HIGH') { $color = 'Red' }; if ($r.Severity -eq 'LOW') { $color = 'Gray' }
$txt = $r.Text.Trim(); if ($txt.Length -gt 120) { $txt = $txt.Substring(0, 120) + '...' }
Write-Host ("[LOI-HUA-MAY] {0,-4} | {1}:{2} | {3} | {4}" -f $r.Severity, $r.File, $r.Line, $r.Reason, $txt) -ForegroundColor $color
Write-Host (" resolve: them existence-proof tai cho {path may + lenh + test}, HOAC viet lai dang du-dinh, HOAC ghi vao baseline kem disposition") -ForegroundColor DarkGray
}
Write-Host ''
Write-Host 'DOC DUNG CON SO NAY: day la so dong CHUA-CO-PROOF va CHUA-CO-TRONG-BASELINE.' -ForegroundColor DarkCyan
Write-Host 'FLAG 0 != corpus sach. Cua nay do GATE (dong viet moi), khong do SWEEP (goi-chot:53).' -ForegroundColor DarkCyan
exit 0

View File

@ -18,8 +18,9 @@
IN-LINE (no assumed default, not swallowed into the generic probe-loi) - keeps
the no-default signal H24-2 depends on.
Output contract - EXACTLY one line:
NHIP-NO: kiem light a/e deep b/f <ok|OVERDUE:light|deep> | tran-bo-nho x over-cap | run-chua-gom y | pull-cach N ngay
Output contract - EXACTLY one line (5 ve - ve-5 so-yc them @S181; doc-fix S185 theo harness-audit §8:
comment nay tung khai 4 ve trong khi code :264 in 5 - doc-stale lop "chuoi trang-thai noi sai su that"):
NHIP-NO: kiem light a/e deep b/f <ok|OVERDUE:light|deep> | tran-bo-nho x over-cap | run-chua-gom y | pull-cach N ngay | so-yc treo T / nhac2+ K
* kiem = audit cadence: a = counter - last_audit.light_at_counter (light debt),
e = h24_cadence.light_every ; b/f = deep pair. OVERDUE when a>=e or b>=f.
* tran-bo-nho = x agent-memory/*/MEMORY.md files whose (Get-Item).Length exceeds

View File

@ -1,8 +1,26 @@
# tien-kiem-cua.ps1 - GD-3 khoan (iv): tien kiem chi phi ~0 o DAU MOI CUA.
#
# Chi DOC. KHONG ghi. LUON exit 0 - day la lop INFORM, KHONG phai cong chan.
# Cong chan bang ma loi = W3b (khoan C3-04 + thang 4 trang thai), dang cho owner (P1).
# Ai sua file nay de tra ma != 0 la da doi lop, phai qua W3b truoc.
# Cong chan bang ma loi = W3b (khoan C3-04 + thang 4 trang thai).
#
# [1] P1 DA CHOT = PA-2 (hai ma thoat trong MOT may) - owner @S180, land @S181.
# Nguon: .claude/sessions/session-12/_context-s-12.md:55
# docs/governance/harness-11-engine.md PHAN Q (phan loai 64/64)
# => DUNG hoi lai owner ve P1. Cau cu o dong nay noi P1 con treo - da GO @S185 vi STALE.
# (co y KHONG trich lai nguyen van cau cu: trich = de mot hit grep song mai trong tep,
# bay citation-trap use-vs-mention da xay ra that 3 doi/1 wave o repo nay)
#
# [2] VAN CHUA CAM. Dieu-kien-vao W3b co HAI ve; ve thu hai CHUA DAT
# (harness-11-engine.md:587): "rider-jump W2 da soak INFORM >=1 phien".
# Do @S185: rider-jump CHUA TON TAI tren dia => soak thuc te = 0 phien.
# Tai-lap: grep -c "OVERDUE:jump" scripts/nhip-no-probe.ps1 -> 0
# Control: grep -c "OVERDUE" scripts/nhip-no-probe.ps1 -> 6 (thuoc con song)
# Truc lich da du (4 phien tu 2026-08-07) nhung chu-ngu cua phep soak chua duoc dung.
#
# Mo khoa khi: grep -c "OVERDUE:jump" scripts/nhip-no-probe.ps1 >= 1 VA no da chay
# INFORM >= 1 phien (>=1 tick moi trong .session-counter.json SAU commit land cua no).
#
# Ai sua file nay de tra ma != 0 la da doi lop, phai qua W3b truoc (DU CA HAI ve).
#
# Vi sao KHONG tin mtime cua he dieu hanh:
# hook 'wal:' cham tep o bien moi luot, va 'git checkout' viet lai mtime hang loat.