<#
.SYNOPSIS
  spawn-model-audit.ps1 - H23 PA-2b: spawn-model audit, resolved-vs-expected. INFORMATIONAL.

.DESCRIPTION
  Owner-decision S119 PA-2 (runs/2026-07-15-S119-adap-6-broadcast/owner-decisions-15-07-2026.md:59-60)
  splits "anchor the model version" in two, because anchoring AT SPAWN is NOT possible at SE
  (the spawn param takes an enum alias only; SE has 0 full-id in any definition file - H8 all-inherit):
    PA-2a  .claude/workflows/hmw.js declares  TIER2_EXPECTED_FULL_ID (value lives ONLY there -
           single-source, do NOT copy it here; stale copy 'claude-opus-4-8' fixed S150).
    PA-2b  THIS script compares the model actually RESOLVED at spawn against that constant.
           Drift => print FLAG => owner decides re-pin. NO enforcement (H23 section 2(4)).

  Design rules (mirror scripts/governance-detectors.ps1):
    (1) NO-API    - reads files only. NEVER calls a model/API.
    (2) FLAG-only - prints FLAGs, NEVER edits files.
    (3) PowerShell 5.1, offline. ASCII-only script body (gotcha #30); target files read -Encoding UTF8.
    (4) DETECT-only. Exit code ALWAYS 0 (audit/inform, never a build gate).

  ---------------------------------------------------------------------------
  MEASURABLE vs NOT-MEASURABLE - read before trusting any line of this report
  ---------------------------------------------------------------------------
  MEASURED (this script really reads it, offline, from disk):
    * expected  = the TIER2_EXPECTED_FULL_ID constant in hmw.js               [section A]
    * resolved  = "message":{"model":"..."} in the per-lane spawn transcripts
                  <TranscriptRoot>/<slug>/<session>/subagents/workflows/wf_*/agent-*.jsonl
                  Verified present S119: 9 lanes 'claude-opus-4-8' + 2 lanes 'claude-fable-5'. [section B]

  NOT MEASURABLE HERE - declared, never silently passed:
    (i)  FORWARD drift. This audit is RETROSPECTIVE: it reads what ALREADY resolved in recorded
         runs. It cannot pre-verify what the NEXT spawn will resolve to. A clean report does NOT
         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 all 14 agents THEN-EXISTING to 'model: opus' (ALIAS pin; roster is 20 as of
         S145 - measurement taken when N was 14; NOT re-run for the 6 added since = 3 trio S141
         + 3 ring S145. Count fixed S151 per H1 F-5 - the old note said "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
         hub form pins FULL-ID 'claude-opus-4-8' 17/17 - NOT covered by this measurement);
         retrospective - says nothing about future spawns. Older 'inherit' evidence: below.
    (iii)Whether the spawn param REJECTS a full-id (fix #8a). Per harness docs the param is an enum
         alias (sonnet|opus|haiku|fable); passing a full-id has NEVER been tried. Not tried here.
    (iv) Documentation coverage. This measures EXPLICITNESS IN TRANSCRIPTS only. A clean report must
         NOT be read as "the floor is fully applied" (owner-decisions:65, honesty declaration 3).

  PRECEDENCE - spawn-param vs frontmatter 'inherit': MEASURED 2026-07-15, no longer "never tested".
    Spec v2:271 recorded this as CHUA TUNG DUOC TEST, reasoning that a discriminating experiment
    needs lead=Fable. That reasoning only covered the tier:'opus' direction. The discriminating
    condition is simply  spawn-param family != lead family  - and the tier:'fable' direction
    satisfies it under lead=Opus. It ALREADY RAN, unplanned, in run wf_cb964f83-331:
        lead model      = claude-opus-4-8   (290/290 records, session 3d9bec56, zero Fable)
        agentType       = reviewer          (.claude/agents/reviewer.md:5 = 'model: inherit')
        spawn param     = fable             (/fable-real escape-hatch, hmw.js:44 returns the alias)
        RESOLVED        = claude-fable-5    (2/2 lanes, 39 + 42 assistant records)
    If frontmatter 'inherit' had won, resolved would be the lead's claude-opus-4-8. It was not.
    => the spawn param TAKES EFFECT on a frontmatter-'inherit' agent => the GAP#6 fix is NOT a no-op.
    HONEST LIMIT: this does not prove 'inherit' LOSES a precedence contest. 'inherit' may simply mean
    "defer to the caller", in which case there is no contest at all. Both readings give the same
    operative answer (the param takes effect); this data cannot separate them, and does not try.
    UPDATE 2026-07-16 (S126): the hard-pin contest HAS now been measured - see NOT-MEASURABLE (ii)
    above, which flipped to MEASURED. Param 'fable' beat frontmatter 'model: opus' on a real lane.

.PARAMETER RepoRoot
  Repo root. Default = 2 levels up from this script (scripts/ -> repo root).
  Every path derives from this, so -RepoRoot <temp tree> makes fault-injection possible.

.PARAMETER TranscriptRoot
  Root of the CLI project transcripts. Default derives from $env:USERPROFILE (NOT a hardcoded
  absolute path). The per-project folder name is derived from $RepoRoot, so a fault-inject tree
  resolves to a slug with no transcripts and section B reports NO-SCOPE instead of faking a number.

.PARAMETER SessionId
  Audit one specific session folder. Default = most recent session that HAS spawn transcripts.
  Default-to-current is deliberate: scanning old history surfaces pre-convention runs, which is
  noise, not a bug storm (owner-decisions:64, honesty declaration 2).

.EXAMPLE
  powershell.exe -ExecutionPolicy Bypass -File scripts/spawn-model-audit.ps1
#>
param(
    [string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path,
    [string]$TranscriptRoot = (Join-Path $env:USERPROFILE '.claude\projects'),
    [string]$SessionId = ''
)

$ErrorActionPreference = 'Continue'

# ---------------------------------------------------------------------------
# Owner-ratified expectation (S119 PA-2a, owner-decisions-15-07-2026.md:59).
# Deliberate 2-key design: the expected version is declared TWICE - once in hmw.js (PA-2a) and
# once here - so that a unilateral edit of ONE of them is FLAGGED instead of passing silently.
# Cost, stated plainly: an owner re-pin must touch BOTH files. That cost IS the check.
# NOT a fallback: if hmw.js has no constant, section A fails loud - it never borrows this value.
# ---------------------------------------------------------------------------
# 🔄 VALUE-update 2026-07-25 (broadcast model-default-opus-5-max sha published 9c909007 - re-stamp G-024a 07-25, pre 59ce1d0f + SE-owner "dam Opus 5 MAX"):
#   'claude-opus-4-8' -> 'claude-opus-5'. Khoan 1.4: records cu mang 'claude-opus-4-8*' = LEGACY bucket
#   trong giai doan chuyen tiep — dem rieng, KHONG co drift (tranh bao canh bao gia tren ban ghi cu).
#   2-key: doi DONG THOI voi TIER2_EXPECTED_FULL_ID trong .claude/workflows/hmw.js (cung commit).
$OWNER_RATIFIED_TIER2_FULL_ID = 'claude-opus-5'
$TIER2_LEGACY_FULL_ID = 'claude-opus-4-8'

# Family that the 'opus' alias maps to. Alias locks the FAMILY, not the version (H23 section 2(2)):
# same family + different version = the drift this audit exists to catch.
$TIER2_FAMILY_PREFIX = 'claude-opus-'

# ---------------------------------------------------------------------------
# Helpers (shape mirrors scripts/governance-detectors.ps1:43-67)
# ---------------------------------------------------------------------------
$script:FlagCount = 0

function Write-Flag {
    param(
        [ValidateSet('HIGH', 'MED', 'LOW')] [string]$Severity,
        [string]$Where,
        [string]$Desc,
        [string]$Resolve
    )
    $color = switch ($Severity) { 'HIGH' { 'Red' } 'MED' { 'Yellow' } default { 'Gray' } }
    Write-Host ("[SPAWN-AUDIT] {0,-4} | {1} | {2} | resolve: {3}" -f $Severity, $Where, $Desc, $Resolve) -ForegroundColor $color
    $script:FlagCount++
}

function Write-Section($title) {
    Write-Host ''
    Write-Host ("===== $title =====") -ForegroundColor Cyan
}

function Rel($full) {
    $r = $full
    if ($full.StartsWith($RepoRoot, [StringComparison]::OrdinalIgnoreCase)) {
        $r = $full.Substring($RepoRoot.Length).TrimStart('\', '/')
    }
    return ($r -replace '\\', '/')
}

Write-Host ''
Write-Host '########## spawn-model-audit.ps1 - H23 PA-2b (INFORMATIONAL, exit 0 always) ##########' -ForegroundColor Cyan
Write-Host ("RepoRoot: {0}" -f $RepoRoot) -ForegroundColor DarkGray

# ===========================================================================
# SECTION A - PA-2a: the EXPECTED constant in hmw.js  (STATIC, measurable)
# ===========================================================================
Write-Section 'A - expected constant (PA-2a, hmw.js)'

$hmwPath = Join-Path $RepoRoot '.claude\workflows\hmw.js'
$expected = $null

if (-not (Test-Path $hmwPath)) {
    Write-Flag 'HIGH' (Rel $hmwPath) 'hmw.js not found - cannot resolve the expected model version' `
        'run from a repo root that contains .claude/workflows/hmw.js'
}
else {
    # STRUCTURAL anchor, NOT a loose string grep (W0.4 lesson 1 - self-reference).
    # Capture the VALUE of the assignment. A bare grep for the version string is NOT acceptable
    # evidence here: hmw.js also carries the expected literal inside COMMENT text (measured S150:
    # naive grep = 2 hits, both comments), so a naive grep returns >=1 hit and passes VACUOUSLY
    # even when the constant is absent or holds a wrong value. (Old line-pinned example
    # 'hmw.js:36 demote-pin' died in an edit - keep this description line-number-free, S150.)
    # The naive count is printed below as a counter-example only - it is never used as evidence.
    $RX_CONST = 'TIER2_EXPECTED_FULL_ID\s*=\s*[''"]([^''"]+)[''"]'
    $constHits = @(Select-String -Path $hmwPath -Pattern $RX_CONST -Encoding UTF8 -AllMatches)

    $naiveHits = @(Select-String -Path $hmwPath -Pattern ([regex]::Escape($OWNER_RATIFIED_TIER2_FULL_ID)) -Encoding UTF8)
    Write-Host ("  anti-vacuous note: naive grep '{0}' in hmw.js = {1} hit(s) [comment text] -> NOT used as evidence" -f `
            $OWNER_RATIFIED_TIER2_FULL_ID, $naiveHits.Count) -ForegroundColor DarkGray

    if ($constHits.Count -eq 0) {
        # FAIL-LOUD. No hardcoded default is substituted: an audit that invents the value it is
        # meant to verify would report a PASS about itself.
        Write-Flag 'HIGH' (Rel $hmwPath) `
            'expected constant missing (W2 chua land): TIER2_EXPECTED_FULL_ID not declared in hmw.js' `
            ("land PA-2a: add TIER2_EXPECTED_FULL_ID = '{0}' next to resolveModel() in hmw.js" -f $OWNER_RATIFIED_TIER2_FULL_ID)
        Write-Host '  -> section B comparison will report SKIPPED (no expected value to compare against)' -ForegroundColor DarkGray
    }
    else {
        $values = @()
        foreach ($h in $constHits) {
            foreach ($m in $h.Matches) { $values += $m.Groups[1].Value }
        }
        $distinctValues = @($values | Sort-Object -Unique)

        Write-Host ("  found {0} definition(s) of TIER2_EXPECTED_FULL_ID; distinct value(s): {1}" -f `
                $values.Count, ($distinctValues -join ', '))
        foreach ($h in $constHits) {
            Write-Host ("    {0}:{1}" -f (Rel $hmwPath), $h.LineNumber) -ForegroundColor DarkGray
        }

        if ($distinctValues.Count -gt 1) {
            Write-Flag 'HIGH' (Rel $hmwPath) `
                ("conflicting definitions: TIER2_EXPECTED_FULL_ID declared with {0} different values [{1}]" -f `
                    $distinctValues.Count, ($distinctValues -join ' vs ')) `
                'keep exactly ONE authoritative definition of TIER2_EXPECTED_FULL_ID'
        }

        $expected = $distinctValues[0]

        # PASS/FAIL is COMPUTED from the measured value (W0.4 lesson 2 - never a label pasted
        # next to a number that says otherwise).
        if ($expected -cne $OWNER_RATIFIED_TIER2_FULL_ID) {
            Write-Flag 'HIGH' (Rel $hmwPath) `
                ("mismatch vs owner-ratified expectation: hmw.js declares '{0}' but S119 PA-2a ratified '{1}'" -f `
                    $expected, $OWNER_RATIFIED_TIER2_FULL_ID) `
                ("set TIER2_EXPECTED_FULL_ID = '{0}', or have the owner re-ratify and update this script" -f $OWNER_RATIFIED_TIER2_FULL_ID)
        }
        else {
            Write-Host ("  [OK] hmw.js expected = '{0}' = owner-ratified (S119 PA-2a)" -f $expected) -ForegroundColor Green
        }

        if ($expected -match '\[1m\]') {
            Write-Flag 'MED' (Rel $hmwPath) `
                ("expected value carries a '[1m]' context-window suffix: '{0}'" -f $expected) `
                'drop the [1m] suffix - it is not part of the model id (gotcha #37)'
        }
    }
}

# ===========================================================================
# SECTION B - PA-2b: the RESOLVED model, from spawn transcripts  (MEASURED)
# ===========================================================================
Write-Section 'B - resolved model (PA-2b, spawn transcripts)'

# Project folder name derives from $RepoRoot (path separators -> '-'), so a fault-inject tree
# resolves to a slug with no transcripts -> NO-SCOPE, never a faked measurement.
$slug = $RepoRoot -replace '[:\\/_]', '-'
$projDir = Join-Path $TranscriptRoot $slug

# (b) W5/S181 - GOM LANE TU CA HAI DUONG. Mot lane = mot tep .jsonl.
#     duong 1 PHANG  : <session>/subagents/*.jsonl              <- spawn bang Agent-tool (Task)
#     duong 2 WORKFLOW: <session>/subagents/workflows/**/agent-*.jsonl
#     Ban cu CHI quet duong 2 => mu hoan toan duong 1. Chieu mu dung la
#     'THAY Workflow, MU Task/phang' (errata E-5: co luc bi viet NGUOC chieu).
#     Tra ve mang rong khi khong co gi - de goi ben ngoai tu quyet, khong tu in.
function Get-LaneFiles {
    param([string]$SessionPath)
    $out = @()
    $flatRoot = Join-Path $SessionPath 'subagents'
    if (Test-Path -LiteralPath $flatRoot) {
        $out += @(Get-ChildItem -Path $flatRoot -Filter '*.jsonl' -File -ErrorAction SilentlyContinue)
        $wfRoot = Join-Path $flatRoot 'workflows'
        if (Test-Path -LiteralPath $wfRoot) {
            $out += @(Get-ChildItem -Path $wfRoot -Recurse -Filter 'agent-*.jsonl' -File -ErrorAction SilentlyContinue)
        }
    }
    return @($out)
}

$dispatch = 0   # spawn lanes found (one agent-*.jsonl = one lane)
$explicit = 0   # lanes where a resolved model is actually recorded
$mismatch = 0   # lanes resolved to the tier-2 FAMILY but NOT the expected version = alias drift
$otherFam = 0   # lanes resolved outside the tier-2 family (escape-hatch / inherited lead)
$legacy   = 0   # lanes resolved the PRE-FLIP default (transition bucket, Khoan 1.4 - counted, not flagged)
$modelTally = @{}

Write-Host ("  transcript root: {0}" -f $TranscriptRoot) -ForegroundColor DarkGray
Write-Host ("  project slug   : {0} (derived from RepoRoot)" -f $slug) -ForegroundColor DarkGray

if (-not (Test-Path $projDir)) {
    Write-Host '  [NO-SCOPE] no transcript folder for this repo - nothing to measure, nothing claimed.' -ForegroundColor DarkGray
    Write-Host '             (expected when running against a fault-inject / temp tree)' -ForegroundColor DarkGray
}
else {
    $sessionDir = $null
    if ($SessionId -ne '') {
        $cand = Join-Path $projDir $SessionId
        if (Test-Path $cand) { $sessionDir = Get-Item $cand }
        else { Write-Flag 'MED' (Rel $cand) 'requested -SessionId not found' 'pass an existing session id, or omit to use the most recent' }
    }
    else {
        # (a) W5/S181 - CHON PHIEN KHONG LOC THEO HINH DANG THU MUC.
        #     Ban cu loc 'Test-Path subagents\workflows' => phien chi co lane PHANG (Task)
        #     la VO HINH. Do that @S181: 5 phien gan nhat deu wf=0, flat=2/24/5/1/1
        #     => may bo qua ca 5, roi ve mot phien cu, ma van in 'TOTAL FLAGS: 0'.
        #     Do la den-xanh-gia tren pham vi 0 - dung lop loi TC-09.
        #     Tieu chi dung = 'co BAT KY vet spawn nao', khong phai 'co thu muc workflows'.
        $sessionDir = Get-ChildItem -Path $projDir -Directory -ErrorAction SilentlyContinue |
            Where-Object { (Get-LaneFiles $_.FullName).Count -gt 0 } |
            Sort-Object LastWriteTime -Descending | Select-Object -First 1
    }

    # (c) phien duoc chon co phai phien MOI NHAT khong? Neu khong, PHAI khai chenh lech.
    $newestAny = Get-ChildItem -Path $projDir -Directory -ErrorAction SilentlyContinue |
        Sort-Object LastWriteTime -Descending | Select-Object -First 1

    if ($null -eq $sessionDir) {
        Write-Host '  [NO-SCOPE] no session with spawn transcripts under this project - nothing measured.' -ForegroundColor DarkGray
    }
    else {
        Write-Host ("  session scope  : {0} (last write {1:yyyy-MM-dd HH:mm})" -f $sessionDir.Name, $sessionDir.LastWriteTime)
        # (c) W5/S181 - nhan ':262' cu duoc in VO DIEU KIEN, ke ca khi phien duoc chon
        #     KHONG phai phien moi nhat => cau 'CURRENT session' co the SAI ma van in ra.
        #     Nay: dung thi in nhu cu; khac thi KHAI CHENH LECH, khong doi ten cho de nghe.
        if ($null -ne $newestAny -and $newestAny.Name -ne $sessionDir.Name) {
            # DO, dung khang dinh. Ban dau tien cua dong nay viet cung 'nhung KHONG co vet
            # spawn nao' - mot cau CHUA DO, va no SAI ngay lan chay dau (phien moi nhat co 2 lane).
            # In con so that; ly do vi sao khong chon thi de nguoi doc tu ket luan.
            $newestLanes = (Get-LaneFiles $newestAny.FullName).Count
            Write-Host ("  [KHAI] phien do KHONG phai phien moi nhat. Moi nhat = {0} (last write {1:yyyy-MM-dd HH:mm}, {2} lane)." -f `
                    $newestAny.Name, $newestAny.LastWriteTime, $newestLanes) -ForegroundColor Yellow
            Write-Host '         => con so duoi day KHONG mo ta phien moi nhat. Doc kem cau nay.' -ForegroundColor Yellow
        }
        else {
            Write-Host '  scope = CURRENT session only, by design (old runs predate the convention; owner-decisions:64)' -ForegroundColor DarkGray
        }

        # STRUCTURAL anchor: the model key of an assistant record sits immediately inside
        # "message":{ ... . Anchoring on the record shape (not a bare "model": grep) keeps quoted
        # JSON inside message CONTENT - agents routinely paste transcript snippets - from being
        # counted as if it were a real dispatch.
        $RX_MODEL = '"message"\s*:\s*\{\s*"model"\s*:\s*"([^"]+)"'

        $laneFiles = @(Get-LaneFiles $sessionDir.FullName)

        # (d) W5/S181 - IN MAU SO. Khong co mau so thi 'do duoc 0 lech' va 'khong do lane nao'
        #     nhin y het nhau tren man hinh - dung bay ma hop dong may soi ten goi ten o muc (3).
        $flatN = @(Get-ChildItem -Path (Join-Path $sessionDir.FullName 'subagents') -Filter '*.jsonl' -File -ErrorAction SilentlyContinue).Count
        $wfN   = $laneFiles.Count - $flatN
        Write-Host ("  mau so lane    : {0} lane do / {1} lane ton tai  (phang/Task {2} + workflow {3})" -f `
                $laneFiles.Count, $laneFiles.Count, $flatN, $wfN)
        if ($laneFiles.Count -eq 0) {
            Write-Host '  [KHAI] 0 lane - moi con so duoi day la tren pham vi RONG, KHONG phai bang chung sach.' -ForegroundColor Yellow
        }

        foreach ($lane in $laneFiles) {
            $dispatch++
            $hits = @(Select-String -Path $lane.FullName -Pattern $RX_MODEL -Encoding UTF8 -AllMatches)
            $models = @()
            foreach ($h in $hits) {
                foreach ($m in $h.Matches) { $models += $m.Groups[1].Value }
            }
            $distinct = @($models | Sort-Object -Unique)

            # Lane PHANG nam thang trong 'subagents' => parent-leaf ra dung chu 'subagents',
            # doc len khong biet la gi. Dat nhan ro rang de phan biet 2 duong.
            $parentLeaf = Split-Path (Split-Path $lane.FullName -Parent) -Leaf
            $wfName = if ($parentLeaf -eq 'subagents') { 'flat/Task' } else { $parentLeaf }
            $agentType = '?'
            $metaPath = ($lane.FullName -replace '\.jsonl$', '.meta.json')
            if (Test-Path $metaPath) {
                $metaTxt = Get-Content -Path $metaPath -Raw -Encoding UTF8
                $mm = [regex]::Match($metaTxt, '"agentType"\s*:\s*"([^"]+)"')
                if ($mm.Success) { $agentType = $mm.Groups[1].Value }
            }

            if ($distinct.Count -eq 0) {
                Write-Host ("    {0}/{1} [{2}] -> no model recorded (lane produced no assistant record)" -f `
                        $wfName, $lane.Name, $agentType) -ForegroundColor DarkGray
                continue
            }
            $explicit++

            foreach ($d in $distinct) {
                if ($modelTally.ContainsKey($d)) { $modelTally[$d] = $modelTally[$d] + 1 }
                else { $modelTally[$d] = 1 }
            }

            $verdict = 'other-family'
            $inFamily = @($distinct | Where-Object { $_.StartsWith($TIER2_FAMILY_PREFIX) })
            if ($null -eq $expected) {
                $verdict = 'SKIPPED (no expected constant)'
            }
            elseif ($inFamily.Count -eq 0) {
                $verdict = 'other-family (escape-hatch / inherited lead - not a drift signal)'
                $otherFam++
            }
            else {
                $bad = @($inFamily | Where-Object { $_ -cne $expected })
                # Khoan 1.4 broadcast model-default-opus-5-max (2026-07-25): records that resolved the
                # PREVIOUS default are a LEGACY bucket during the transition, counted separately,
                # NOT flagged as drift (a value-update flips the expected mid-history; old records
                # predate the flip - flagging them is a false alarm storm, exactly what 1.4 forbids).
                # A record that is neither expected NOR legacy is still a REAL mismatch.
                $legacyOnly = @($bad | Where-Object { $_ -cne $TIER2_LEGACY_FULL_ID })
                if ($bad.Count -gt 0 -and $legacyOnly.Count -eq 0) {
                    $legacy++
                    $verdict = ("legacy ({0} = pre-flip default; transition bucket Khoan 1.4, not drift)" -f ($bad -join ', '))
                }
                elseif ($legacyOnly.Count -gt 0) {
                    $mismatch++
                    $verdict = 'MISMATCH'
                    Write-Flag 'HIGH' ("{0}/{1}" -f $wfName, $lane.Name) `
                        ("alias-drift: resolved '{0}' is in the tier-2 family but expected '{1}' (and not the legacy bucket)" -f ($legacyOnly -join ', '), $expected) `
                        'owner decides re-pin (H23 section 2(2)); update TIER2_EXPECTED_FULL_ID once ratified'
                }
                else {
                    $verdict = 'match'
                }
            }
            Write-Host ("    {0}/{1} [{2}] -> {3} ({4} record(s)) : {5}" -f `
                    $wfName, $lane.Name, $agentType, ($distinct -join ', '), $models.Count, $verdict)
        }

        Write-Host ''
        Write-Host ("  COUNTS: {0} dispatch | {1} explicit | {2} mismatch" -f $dispatch, $explicit, $mismatch)
        Write-Host  '  (dispatch = lanes found | explicit = lanes with a model actually recorded | mismatch = tier-2-family lanes off the expected version)' -ForegroundColor DarkGray
        if ($modelTally.Count -gt 0) {
            Write-Host '  resolved model tally (lanes per model):'
            foreach ($k in ($modelTally.Keys | Sort-Object)) {
                Write-Host ("    {0,-24} {1} lane(s)" -f $k, $modelTally[$k])
            }
        }
        if ($null -eq $expected) {
            Write-Host '  [SKIPPED] resolved-vs-expected NOT evaluated: section A found no expected constant.' -ForegroundColor Yellow
            Write-Host '            This is NOT a pass. The lanes above are reported raw, uncompared.' -ForegroundColor Yellow
        }
        elseif ($dispatch -eq 0) {
            Write-Host '  [NO-SCOPE] 0 lanes in scope - a 0-mismatch count here would be VACUOUS, not a pass.' -ForegroundColor Yellow
        }
        elseif ($mismatch -eq 0) {
            Write-Host ("  [OK] 0/{0} lane(s) drifted off expected '{1}' (retrospective only - says nothing about the NEXT spawn)" -f `
                    $explicit, $expected) -ForegroundColor Green
        }
    }
}

# ===========================================================================
# SECTION C - canonical cross-check: docs/STATUS.md, the Sub-agents row
# ===========================================================================
Write-Section 'C - canonical cross-check (STATUS.md Sub-agents row)'

# Row label read from the file, not guessed: docs/STATUS.md:24 = "| Sub-agents | **12** | ...".
# That row is the single-owner flip-chain canonical; hmw.js:34 and agents/README point at it.
$SUBAGENT_ROW_LABEL = 'Sub-agents'
$FLIPCHAIN_MARKER = 'CANONICAL single-owner flip-chain'

$statusPath = Join-Path $RepoRoot 'docs\STATUS.md'
if (-not (Test-Path $statusPath)) {
    Write-Flag 'MED' (Rel $statusPath) 'docs/STATUS.md not found - cannot cross-check the roster canonical' `
        'run against a repo root that contains docs/STATUS.md'
}
else {
    $rowPat = '^\|\s*' + [regex]::Escape($SUBAGENT_ROW_LABEL) + '\s*\|\s*\*\*(\d+)'
    $rowHit = Select-String -Path $statusPath -Pattern $rowPat -Encoding UTF8 | Select-Object -First 1

    if ($null -eq $rowHit) {
        Write-Flag 'MED' (Rel $statusPath) `
            ("canonical row '| {0} | **N** |' not found in STATUS.md CURRENT STATE table" -f $SUBAGENT_ROW_LABEL) `
            'restore the Sub-agents row, or update this script if the canonical owner moved'
    }
    else {
        $canonSub = [int]$rowHit.Matches[0].Groups[1].Value
        Write-Host ("  STATUS.md:{0} canonical {1} = {2}" -f $rowHit.LineNumber, $SUBAGENT_ROW_LABEL, $canonSub)

        if ($rowHit.Line -notmatch [regex]::Escape($FLIPCHAIN_MARKER)) {
            Write-Flag 'LOW' ("{0}:{1}" -f (Rel $statusPath), $rowHit.LineNumber) `
                ("the {0} row no longer carries the '{1}' marker" -f $SUBAGENT_ROW_LABEL, $FLIPCHAIN_MARKER) `
                'restore the marker, or re-point this cross-check at the new canonical owner'
        }
        else {
            Write-Host ("  [OK] row carries the flip-chain canonical marker") -ForegroundColor Green
        }

        # disk cross-check: the canonical count must not itself be stale.
        $agentDir = Join-Path $RepoRoot '.claude\agents'
        if (Test-Path $agentDir) {
            $diskAgents = @(Get-ChildItem -Path $agentDir -Filter *.md -File -ErrorAction SilentlyContinue |
                    Where-Object { $_.Name -ne 'README.md' })
            Write-Host ("  disk cross-check: .claude/agents/*.md minus README = {0}" -f $diskAgents.Count)
            if ($diskAgents.Count -ne $canonSub) {
                Write-Flag 'MED' (Rel $statusPath) `
                    ("canonical-itself-stale: STATUS {0}=**{1}** but disk has {2} agent .md" -f `
                        $SUBAGENT_ROW_LABEL, $canonSub, $diskAgents.Count) `
                    ("re-ground the STATUS.md {0} row to {1}" -f $SUBAGENT_ROW_LABEL, $diskAgents.Count)
            }
            else {
                Write-Host ("  [OK] canonical {0} matches disk ({1})" -f $canonSub, $diskAgents.Count) -ForegroundColor Green
            }
        }
    }
}

# ===========================================================================
# SECTION D - S124 worker-tier-pin: agent frontmatter (model: opus + effort: max)
# (was: H8 all-inherit - superseded by owner-decision S124 2026-07-15, adap erratum-h8)
# ===========================================================================
Write-Section 'D - frontmatter worker-tier-pin (S124)'

# GLOB, never a hardcoded roster size: the roster moves 12 -> 14 at W2, and a hardcoded count
# would self-age into a false FAIL the moment it lands (fix #9(h) / R2-M3).
$agentDir = Join-Path $RepoRoot '.claude\agents'
if (-not (Test-Path $agentDir)) {
    Write-Flag 'MED' (Rel $agentDir) '.claude/agents not found - cannot check frontmatter' `
        'run against a repo root that contains .claude/agents'
}
else {
    $agentFiles = @(Get-ChildItem -Path $agentDir -Filter *.md -File -ErrorAction SilentlyContinue |
            Where-Object { $_.Name -ne 'README.md' })
    $pinCount = 0
    $effortOkCount = 0

    foreach ($a in $agentFiles) {
        $lines = @(Get-Content -Path $a.FullName -Encoding UTF8)
        # frontmatter = the block between the first '---' and the next '---'
        $modelVal = $null
        $modelLine = 0
        $effortVal = $null
        $effortLine = 0
        $inFm = $false
        for ($i = 0; $i -lt $lines.Count; $i++) {
            $t = $lines[$i].Trim()
            if ($t -eq '---') {
                if (-not $inFm) { $inFm = $true; continue }
                break
            }
            if ($inFm -and $t -match '^model\s*:\s*(.+?)\s*$') {
                $modelVal = $Matches[1].Trim()
                $modelLine = $i + 1
            }
            if ($inFm -and $t -match '^effort\s*:\s*(.+?)\s*$') {
                $effortVal = $Matches[1].Trim()
                $effortLine = $i + 1
            }
        }

        if ($null -eq $modelVal) {
            Write-Flag 'MED' (Rel $a.FullName) `
                'no "model:" key in frontmatter - the lane falls back to a harness default, NOT the worker-tier pin' `
                'add "model: opus" to the agent frontmatter (S124 worker-tier-pin)'
        }
        elseif ($modelVal -cne 'opus') {
            Write-Flag 'MED' ("{0}:{1}" -f (Rel $a.FullName), $modelLine) `
                ("frontmatter has 'model: {0}' - S124 worker-tier-pin expects every agent pinned 'model: opus'" -f $modelVal) `
                'set "model: opus", or have the owner ratify the change and record it in STATUS.md Sub-agents'
        }
        else {
            $pinCount++
        }

        if ($null -eq $effortVal) {
            Write-Flag 'MED' (Rel $a.FullName) `
                'no "effort:" key in frontmatter - S124 worker-tier-pin expects "effort: max" alongside the model pin' `
                'add "effort: max" to the agent frontmatter (S124 worker-tier-pin)'
        }
        elseif ($effortVal -cne 'max') {
            Write-Flag 'MED' ("{0}:{1}" -f (Rel $a.FullName), $effortLine) `
                ("frontmatter has 'effort: {0}' - S124 worker-tier-pin expects 'effort: max'" -f $effortVal) `
                'set "effort: max", or have the owner ratify the change and record it in STATUS.md Sub-agents'
        }
        else {
            $effortOkCount++
        }
    }

    # verdict COMPUTED from the counts, never a pasted label (W0.4 lesson 2)
    Write-Host ("  frontmatter: {0}/{1} agent .md carry 'model: opus' and {2}/{1} carry 'effort: max' (S124 worker-tier-pin)" -f `
            $pinCount, $agentFiles.Count, $effortOkCount)
    if ($agentFiles.Count -eq 0) {
        Write-Host '  [NO-SCOPE] 0 agent .md found - a clean result here would be VACUOUS.' -ForegroundColor Yellow
    }
    elseif (($pinCount -eq $agentFiles.Count) -and ($effortOkCount -eq $agentFiles.Count)) {
        Write-Host ("  [OK] worker-tier-pin holds ({0}/{0} model + {0}/{0} effort)" -f $agentFiles.Count) -ForegroundColor Green
    }
}

# ===========================================================================
# SECTION E - declared limits (printed EVERY run, so no report can over-read)
# ===========================================================================
Write-Section 'E - what this report does NOT say'

Write-Host '  1. SPOT-CHECK BY CONVENTION, NOT ENFORCEMENT. Nothing here blocks a spawn (H23 section 2(4)).'
Write-Host '  2. CURRENT SESSION ONLY. Older runs predate the convention; scanning them yields noise, not a bug storm.'
Write-Host '  3. NOT "the floor is applied". This measures explicitness in transcripts, never documentation coverage.'
Write-Host '  4. RETROSPECTIVE ONLY. It reads models that ALREADY resolved; it cannot pre-verify the NEXT spawn.'
Write-Host '  5. TESTED 2026-07-16 (S126) - spawn param vs frontmatter HARD-PIN: the param WON (reviewer pinned opus +'
Write-Host '     spawn fable -> claude-fable-5; no-override control -> claude-opus-4-8 under lead=Fable). ALIAS-pin only;'
Write-Host '     the full-id-pin form (hub) is still untested here. Inherit-case evidence: header, wf_cb964f83-331.'
Write-Host '  6. NOT TRIED - whether the spawn param rejects a full-id (fix #8a). Docs say enum alias; never attempted.'

# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
Write-Section 'Summary'
Write-Host ("TOTAL FLAGS: {0}" -f $script:FlagCount) -ForegroundColor Cyan
Write-Host 'NOTE: informational audit. Exit 0 always (never fails a build). FLAGs are advisory - owner decides re-pin.' -ForegroundColor DarkGray

exit 0
