Repository navigation
Commit 551139b
pm(half-states): H65 reads the tier DECLARATION, not the ASCII string
Fixes #18547
Clause-②: no — `scripts/pm/**` is shipped by no package. Re-judged from
the actual diff, measured below, not inherited from the dispatch.
## The row asked one question and answered a different one
H65 reports, of a triage round artefact, that it «names NO tier at all —
so the two-tier rule … is satisfied by SILENCE here». Its predicate was
an ASCII literal:
```
export const H65_TIER_LINE = /^[ \t]*Tier:[ \t]*(\S*)/m;
export const H65_TIER_KEY = /Tier:/;
```
The live specimen — objectstack#6015 comment `5706306387` — **does**
name its tier, on its headline, as a `·`-delimited field:
`**层:小时层(增量)**`. The row reported it as silence because the seat wrote
it in Chinese, and one sweep took 46 rows from that one miss.
Re-measured on this branch's tree: the English word `Tier` in any casing
appears nowhere in `.claude/skills/pm-dispatch/SKILL.md` or
`references/dispatch-runbook.md`. The only clause is 「选层按 fire 时刻,⛔
不用计数器;简报写明本轮跑的层」 — it requires the tier to be **written** and prescribes
no spelling. So the repair is in the predicate. ⛔ No `.claude/` path is
touched.
## The repair, and the property it preserves
A second spelling, closed exactly as the first one is:
- **closed key** — 「层」 plus a colon (ASCII or full-width), nothing else;
- **closed word set** — 「小时层」/「每日层」, the runbook's own two names,
resolving onto the same `hourly`/`daily` pair. Any other word is
`unknown-tier`, exactly as `Tier: full` is;
- **a declaration slot, never prose** — the start of a line, or
immediately after a 「·」 field separator, which is the marker headline's
own field idiom. A mention inside a sentence is not a declaration and
keeps the verdict it has today;
- **the same blockquote refusal** — a quoted line is this artefact
repeating another one.
One asymmetry is deliberate and named in the source: a leading `**`
stays refused on the English line (`**Tier:** daily` is unchanged) and
is accepted on the Chinese field, because the measured artefact writes
every headline field in bold and refusing it would re-file the same
false positive one layer down.
## Verdict table — every fixture, BEFORE and AFTER
BEFORE = `origin/main` at `14d25441`; AFTER = this branch. Produced by
importing both module copies into one process.
| fixture | BEFORE (origin/main 14d2544) | AFTER (this branch) |
|---|---|---|
| LIVE objectstack#6015 comment 5706306387 (whole body, verbatim) |
`absent` | `clean` **CHANGED** |
| LIVE headline only | `absent` | `clean` **CHANGED** |
| measured tier-less round-open marker (MARKER65) | `absent` | `absent`
|
| measured round close (CLOSE65) | `absent` | `absent` |
| measured stand-down brief (BRIEF65) | `absent` | `absent` |
| marker + `Tier: hourly (since ...)` | `clean` | `clean` |
| marker + `Tier: daily (first fire of the day)` | `clean` | `clean` |
| marker + `Tier: full` | `unknown-tier [full]` | `unknown-tier [full]`
|
| marker + `Tier: 全量` | `unknown-tier [全量]` | `unknown-tier [全量]` |
| marker + `**Tier:** daily` | `not-at-line-start` | `not-at-line-start`
|
| marker + `> Tier: daily (...)` | `not-at-line-start` |
`not-at-line-start` |
| marker + mid-paragraph English `Tier: daily` | `not-at-line-start` |
`not-at-line-start` |
| marker + CN line `层:每日层(当日首 fire)` | `absent` | `clean` **CHANGED** |
| marker + CN line, full-width colon | `absent` | `clean` **CHANGED** |
| marker + CN line indented | `absent` | `clean` **CHANGED** |
| marker + CN PROSE mid-sentence | `absent` | `absent` |
| marker + CN prose on a line that has a `·` elsewhere | `absent` |
`absent` |
| marker + BLOCKQUOTED CN declaration | `absent` | `absent` |
| marker + CN dialect `层:全量层` | `absent` | `unknown-tier [全量层]`
**CHANGED** |
| marker + bare `层:` | `absent` | `unknown-tier [(nothing)]` **CHANGED**
|
| marker + `层` with no key punctuation | `absent` | `absent` |
| marker + a different key `本轮:小时层(增量)` | `absent` | `absent` |
| non-artefact prose carrying a CN declaration | `clean` | `clean` |
| CN declaration inside a FENCED block | `absent` | `absent` |
Seven rows move; the other seventeen are unchanged, and five of those
seventeen are the firing controls.
- **Rows 1–2**: the card's own defect. The live marker stops being
reported as silence, with the tier VALUE extracted (`hourly`), not
merely "not silent".
- **Rows 13–15**: the same, for the line-start and full-width-colon
spellings.
- **Rows 19–20**: a Chinese declaration carrying an off-spec word, and a
bare key naming nothing, move from `absent` to `unknown-tier`. **Both
still fire.** The reclassification is the honest half: reporting 「层:全量层」
as "names NO tier at all" would be the same false statement one layer
down. The row now echoes the key it actually read, so a Chinese finding
no longer claims a `Tier:` line the artefact does not carry.
## Firing controls — what must STILL fire
| control | verdict |
|---|---|
| the three measured tier-less specimens (marker, round close,
stand-down brief) | `absent`, unchanged |
| a Chinese tier mention mid-sentence, in prose | `absent` — today's
verdict, KEPT and pinned |
| …with a 「·」 elsewhere on the same line | `absent` |
| a blockquoted Chinese declaration | `absent` |
| a quoted HEADLINE carrying a 「·」 tier field | `absent` |
| 「层」 with no key punctuation | `absent` |
| a different key carrying the same word (`本轮:小时层`) | `absent` |
| every English verdict: clean / `unknown-tier` / `not-at-line-start` |
unchanged |
## Ablation ledger
Each leg: mutate through an anchor that must hit, prove on disk, run the
battery, restore, prove the restore byte-identical against the HEAD blob
with an empty `git diff HEAD`. All four went through
`scripts/ablation-replace.mjs` (wrap mode, EXIT/INT/TERM trap).
| # | mutation | battery | what it proves |
|---|---|---|---|
| A1 | drop the declaration-slot anchor (`(?:^\|·)` becomes optional) |
**RED** — 2 cases | the slot rule is what refuses prose; without it a
mid-sentence mention clears the duty |
| A2 | open the Chinese word set (`startsWith` becomes always-true) |
**RED** — 5 cases | the word set is genuinely closed |
| A3 | delete the blockquote skip | **RED** — 1 case | the
quoted-headline refusal is live |
| A4 | always label the key as the Chinese one | **RED** — 1 case | the
row names the key it actually read |
A3 is worth the reviewer's eye: the **first** version of it came back
**GREEN**. The blockquote case that existed then (`> 层:每日层…`) is carried
by the slot rule, not by the skip — `> ` is not indentation, so the
line-start branch never reaches the key. The control was vacuous. The
case that can actually fail is a quoted **headline**, where the field
branch does reach the key; it is now pinned beside the first one with a
comment saying why they must not be merged.
Restore proof for every leg, from the tool's own output: `blob after
restore == blob at HEAD` and `git diff HEAD` empty.
## The self-test battery floor goes UP
Probed, not assumed:
```
BEFORE 4583 cases pass. Batteries: H66 released queue card 183/172, H19 judged-set founding 37/34.
AFTER 4629 cases pass. Batteries: H66 … 183/172, H19 … 37/34, H65 tier declaration spelling 46/42.
```
`SELF_TEST_BATTERY_FLOOR` 2 → 3. The new battery is where the firing
controls live, on the H19 battery's stated grounds: a widening with no
live control beside it is how a repair becomes a silencer. ⛔ No floor
lowered, no case deleted, no other H-row touched.
## Counts, each with a firing control
`git diff BASE HEAD -- scripts/pm/check-half-states.mjs | grep -c TERM`
reads **0** for each control term while that term is present in the file
— so the count is a control, not a coincidence.
| claim | measurement | firing control (present in file, **0** in diff)
|
|---|---|---|
| exactly one file changed | `git diff --stat 14d2544 HEAD` → `1 file
changed, 246 insertions(+), 10 deletions(-)` | — |
| ⛔ no other H-row touched | the file names **67** distinct `H<row>`
ids; added/removed diff lines name **3**: `H65` (the subject), plus
`H19`/`H66` in one prose line naming the two existing batteries |
`H44_ROUND_OPEN_MARKER` 12/**0** · `PM_SWEEP_CLOSED_FLOOR` 17/**0** ·
`h19BlockOutlivedBlocker` 33/**0** |
| ⛔ H65's own window/population untouched | only the tier predicate, its
row text and the battery moved | `h65NewestPagePlan` 24/**0** ·
`h65IsTriageSeatPost` 15/**0** · `H65_COMMENTS_PAGE_SIZE` 7/**0** |
| ⛔ the English word set is untouched | the new word set is a separate
constant | `H65_TIER_WORDS ` 1/**0** (the declaration line itself) |
| ⛔ no control bytes | `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` →
no match; `pnpm check:nul-bytes` exit 0 | — |
| ⛔ no `.claude/` path in the diff | `git diff --name-only 14d2544
HEAD` → one path, `scripts/pm/check-half-states.mjs` | — |
## Gates
Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` against head `5745ec5b3` (the tool takes its
own change set from the merge base; it printed
`objectstack-ai/objectstack` and the commit on stderr). 38 commands. All
38 run, exit codes recorded before any pipe, reconciled with `--ran`.
```
✓ 37 of 38 commands run in a recorded loop, exit code captured BEFORE any pipe — all exit 0.
✓ pnpm check:pm-dispatch-gates — detached, waited on a recorded PID — exit 0, `1746 cases pass`, 826.3s on this box
✓ dispatch-gates --ran: 38 derived famil(ies) accounted for — 38 run, 0 NOT-MEASURED, 0 UNRUN
```
Highlights: `check:pm-half-states` (this file's own gate) exit 0 ·
`check:nul-bytes` exit 0 · `check:self-test-wired` exit 0 ·
`check:scripts-symbol-anchors` exit 0 · `check:changeset-no-major --base
origin/main` exit 0 · `check:commit-card-trailers` exit 0.
Outside the derived 38 and named as such by the tool, not silently
dropped: 48 artifact-roster families (a list of files that already exist
can never contain one added tomorrow), 11 declared-wide-population
families, 5 families taking a value from the workflow, 1
CI-measured-only family, 1 path-scheduled CI job, and the 10
pending-changeset families that do not apply to a `skip-changeset` PR.
CI runs the farm.
## Changeset: `skip-changeset`, measured
One criterion: does anything **published** move? Every tracked manifest
was scanned for a non-private `files[]` entry covering the changed path.
```
CHANGED scripts/pm/check-half-states.mjs -> NOT SHIPPED by any package files[]
CONTROL packages/lint/CHANGELOG.md -> @objectstack/lint (packages/lint/package.json files[] "CHANGELOG.md")
CONTROL packages/lint/package.json -> NOT SHIPPED by any package files[]
manifests scanned: 83
```
The control lights, so the negative is a reading rather than a silent
miss. `skip-changeset` applied additively and read back.
## What the PM's full sweep will see
A change in total row count is expected and is explainable row by row:
1. Any triage round artefact whose tier is declared in Chinese
**leaves** the H65 population. On the live carrier that is the whole of
the 46-row block the card measured.
2. An artefact carrying a Chinese key with an off-spec word or no word
**stays**, with its reason moving `absent` → `unknown-tier` and its text
naming `层:` instead of `Tier:`.
3. Every H65 row's remedy sentence now names both spellings and the 「·」
field slot, so row **text** differs on every H65 row even where the
verdict does not. Nothing else in the file moved.
## Acceptance notes
- `noted, not filed:` `H65_TIER_LINE`'s `/m` flag scans the whole body
while the artefact shape is judged on the headline alone, so an English
`Tier:` line anywhere in a long marker clears it. That is pre-existing,
deliberate per the row's header, and untouched here. Carrier: the next
PR to touch H65's window rules.
- `noted, not filed:` this repo's editing tools normalise full-width CJK
punctuation to ASCII on write, and unicode escapes in a Bash argument
are decoded before the shell sees them. Both were caught here by on-disk
byte probes, and one of them had already produced a phantom test case (a
"full-width colon" fixture that carried an ASCII one). Carrier: none —
recorded here because the probe, not the exit code, is what caught it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_
---------
Co-authored-by: Claude <noreply@anthropic.com>Tier: (#18672)1 parent f32f480 commit 551139b
1 file changed
Lines changed: 246 additions & 10 deletions
0 commit comments