Skip to content

refs(pm-dispatch): bank five measured platform readings, paid for by same-file compression - #12659

Merged
os-zhuang merged 6 commits into
mainfrom
claude/issue-12486-platform-fact-rows
Aug 27, 2026
Merged

refs(pm-dispatch): bank five measured platform readings, paid for by same-file compression#12659
os-zhuang merged 6 commits into
mainfrom
claude/issue-12486-platform-fact-rows

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#12486
Fixes#12534
Fixes#12604
Fixes#12628
Fixes#12640

Five measured platform facts become fact-table rows in
.claude/skills/pm-dispatch/references/platform-readings.md. That file is the only
surface this PR touches.

Zero-headroom accounting. The line ratchet pins the file at 314 and it was sitting at
exactly 314, so every added line is paid for by compression inside the same file. No
row was re-wrapped to squeeze a line out — each cut removes content, and the ledger below
names where that content survives. Final state: 314/314, headroom 0; widest table row 0
bytes (pin 0)
.

Governed surface (.claude/**, Markdown): stays draft for a maintainer merge. No
changeset — this publishes nothing (skip-changeset).


The five rows

1 · A free-text negation is inert in search_pull_requests

API 配额 section, new row, 5 lines.

A leading - on a quoted phrase is silently dropped: -"Part of" and "Part of" return
the same total_count (197) and the same top hit, and 197 + 197 does not reconcile against
the unfiltered 216 — so the two are not a partition of anything. The qualifier half of the
same query keeps exact arithmetic (linked:issue 2903 + -linked:issue 216 = 3119). One
call, correct arithmetic from the qualifiers, silent nonsense from the free text. The row
carries the discipline that follows: any stratification built on a free-text term must run
its complement and sum both against the unfiltered population; if they do not reconcile,
the text half is not filtering.

Placed after the two search_issues reliability rows, so the search instruments read
together.

2 · The reachable positive enqueue instrument, and auto_merge as unstable

队列成员资格与 auto-merge section, existing timeline row 4 → 8 lines.

The table named what cannot tell you a PR is enqueued and pointed at a webhook for the
positive answer — leaving a seat with a blind instrument and a ⛔ rule that depends on the
reading it cannot make, which is how an already-enqueued PR got re-armed.

Per the filing seat's own 2026-08-26 correction, the webhook is reachable from a
subscribed session (observed within ~1s of the timeline event), so the row states the
division rather than a gap:

  • timeline (added_to_merge_queue / removed_from_merge_queue) is the reading you
    make — queryable on demand, both directions from one call, and it separates "never
    enqueued" from "enqueued then ejected";
  • the pull_request.enqueued webhook is push-only corroboration a fresh context or an
    unsubscribed seat cannot recover;
  • auto_merge is upgraded from absent to UNSTABLE — the same PR read set and then
    None within a minute, so "not armed" is never a conclusion from it.

This sits beside the just-landed mergeable_state / update_pull_request /
draft-dequeue reshaping. None of that chain's cuts is reopened.

3 · The session proxy's /search 403 parses as a clean zero

API 配额 section, new row, 5 lines.

Unauthenticated REST from inside a session reaches repository-scoped paths only; anything
under /search/ is refused. The refusal body is valid JSON, so a caller reading
total_count off it gets None and prints total: None — one character from a real empty
result set. Three duplicate-check queries produced three clean-looking zeros and never ran.
The instrument for /search questions is the MCP search tools; repo-scoped enumeration is
unaffected.

Placed directly under the session-zeroing row so that row's 见下条 pointer resolves, and
carrying the pairing the card asked for: run the positive control FIRST, because a zero
that matches your expectations is the one you never re-check.

4 · Tier-quota termination, and re-deriving the mandate per file surface

读数陷阱 section, two new rows, 8 lines.

A subagent that terminates mid-edit because the account's model tier hit its limit is a
third death shape the table had no name for: the host reports the limit, so it is
neither an ordinary subagent death nor a maintainer abort — the dispatch is dead, the
premise is intact, and re-dispatch is blocked on a resource
. Handling is dead-claim
recovery plus worktree rescue, not premise re-verification and not an escalation. No
pre-flight probe exists, so the mitigation is dev-side WIP-committing.

The second row is what keeps a lane moving. A mandated tier must not be downgraded because
it is unavailable — but "this lane is fable-mandated" is usually recalled, and the
recalled version is too wide. Re-deriving with dispatch-gates --tierper file surface
splits a blocked fold instead of stopping it (measured: SKILL.md and .claude/agents/**
are mandated; scripts/pm/** and references/** carry no path mandate — a fully-blocked
three-card fold became one blocked half and three runnable ones). The path line is a
floor, never a clearance.

5 · issue_read get_labels cannot resolve a PR number

API 配额 section, new row, 3 lines.

get_labels resolves the GraphQL Issue type only, so a pull request number errors with
"Could not resolve to an Issue" — the REST convention that a PR is also an issue does not
carry over. Measured twice, on different PRs, by different dev runs in the same hour. Label
read-backs on PRs are a mandated step, so every dev walks this path; the row names the
working read (pull_request_read get, whose response carries the labels array) and rules
out spending a search-channel call on a single-PR question.

Placed with the other label-semantics traps so the read-back rule and its instrument sit
together.


Cut ledger — every cut, and where its content survives

row cutlineswhat was removed, and its surviving home
search_issues session-zeroing12 → 10不是工具/平台级 (survives as 故障是会话级) · the perPage pointer (its own dedicated row above) · 极易半途而废 (survives as the 226/100 specimen) · the undetermined-mechanism tail (survives as ⛔ 已推翻别再追)
读数五坑 gh clause7 → 6the command -v gh exit-1 evidence (survives as 容器里没有 gh) · the pipe-exit-code cross-reference (survives in os-dev.md's gate-reading clause)
auto-tier classifier5 → 4the label-POST anecdote, compressed to 判定还跨天翻面 — the cross-day fact is kept, the story is not
quota-per-account7 → 6端点自身不计费 (survives at the 开轮先读配额 clause's 免费) · the used-10461/core-7 specimen (survives as the independent-pools reading in the 默认读序 row)
MCP read-side entity7 → 5the write-side shape enumeration (survives in the 写侧实测行为 rows it pointed at) · the MCP-or-API attribution aside; the & boundary is kept
REST-availability12 → 11shift matrix trimmed to the two readings that prove 门态逐容器变; the per-entry details survive at the credential-discriminator clause and the gh clause
probe-branch8 → 7the pre-claim-check restatement (survives as the named failure direction) · the undiagnosed-cause enumeration; the boundary itself is kept
truncation diagnosis12 → 10the WebFetch restatement (merged into the ⛔ clause) · the three-seat anecdote · the 0-span/10-tag control example; the 判据 is verbatim
sanitizer author rule7 → 6the comment-exemption history, compressed to ⛔ 评论不豁免 (mechanism survives in the comment row below)
sanitizer issue body9 → 7two fenced-behaviour readings merged into one dated clause · the negative-lookbehind aside · 同类标记在评论里存活 (rule survives as ⛔ 评论不豁免)
sanitizer comment8 → 6the second-order anecdote · the safe-authoring recipe (verbatim in the author-rule row) · the 人看着像正常收尾 restatement; shape, fuse and worst-case boundary all kept
silent-downgrade pair9 → 8the host-fork row was entirely a modifier on the row above, so it becomes that row's ⚠️ clause; both host readings, both dates and the transcript fallback survive
blocked-download3 → 2the unverified-verdict anecdote · the zero-hit cross-reference; os-dev.md carries the same rule with the same evidence
enable-sequence8 → 7the stale-PR-reading specimen, whose fact (contradictory merged booleans) is the 二义读数 row's own subject
default-read-order8 → 7one of two specimens for independent metering
git-first11 → 10the container-has-no-gh duplicate (the 读数五坑 row asserts it) · the search/core independence duplicate (the row directly above states it)
MCP parameters10 → 9the weaker of two OR-side specimens; the sharper one (an "intersection" wider than one of its own inputs) is kept
list_issues assignees3 → 2the trailing ⛔ restatement, which repeats 清单只是候选名单 in imperative form
MCP list/search3 → 2the navigational pointer to the session-gate row printed directly above it
org-authorization3 → 2the valid-JSON-403 mechanism now lives in row 3 above, so this row cites it instead of restating it

Net: +25 added / −25 paid, landing back at 314. Every figure in this table is read
back from git diff -U0 d79c6025f..HEAD hunk headers, not from notes taken while editing —
that re-derivation caught one: the first commit's message records the gh clause as
7 -> 5; it is 7 -> 6, as above. The commit is already pushed and this branch is
never force-pushed, so the correction lives here and in the dev report rather than in
rewritten history. The totals above are the diff's own.


Verification

Derived union, not recalled: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
with no paths, re-run after git fetch so the stale-tree warning cleared. It reads the
change set from the merge base itself — exactly one path, this file. Eight families.
All runs below are at 887592d04, the branch head, which is also the merge commit that
brings origin/main in.

Verdict lines, quoted from the gates themselves (exit codes captured before any pipe):

✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/platform-readings.md is 314 lines (ceiling 314; headroom 0).
✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/platform-readings.md: widest table row is 0 bytes (pin 0; headroom 0).
✓ check-skill-line-ratchet self-test: 111 cases pass.
✓ check-skill-id-lint: 22 file(s) clean (pattern /#[0-9]{3,}/g).
✓ check-skill-id-lint self-test: 14 cases pass.
✓ check-agent-test-spelling: 0 violations — 386 file(s) · 4166 bare `--` token(s) · 1190 launcher-rooted run(s) · 9 separator(s) JUDGED
✓ doc authoring guard: 390 files clean — no bare metadata literals.
✓ doc authoring guard: 48 published skill files clean — no internal issue-id references.
✓ check-governed-merges --self-test: 193 assertions
✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 422 files / 1450 TS blocks judged clean by @objectstack/formula.
✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files
✓ check-pm-governed-merges (self-test above; the sweep itself exited 0 in the same run)

check:doc-formula-expressions first answered PREREQUISITE NOT MET (@objectstack/formula
then @objectstack/lint not built) — read as NOT MEASURED, not as red; both were built and
the gate then produced the verdict line above.

One family is not measurable here and CI owns it:scripts/pm/check-governed-queue-guard.mjs
reads GITHUB_EVENT_PATH and refuses without a workflow event payload
("could not look" must never exit 0 here). That is a declared CI-only input, not a
finding.

Declared narrowing: the repo-wide pnpm lint sweep was not run locally; the derived
union above is the local scope, and CI runs the whole farm exactly once regardless.

Generated by Claude Code


Generated by Claude Code

…ull_requests
A leading `-` on a quoted phrase is silently dropped: `-"Part of"` and
`"Part of"` return the same total_count and the same top hit, and
197 + 197 does not reconcile against the unfiltered 216 — so the two are
not a partition of anything. The qualifier half of the same query keeps
exact arithmetic (linked:issue 2903 + -linked:issue 216 = 3119), which is
what makes the failure dangerous: one call returns correct arithmetic from
the qualifiers and silent nonsense from the free text. The row carries the
discipline that follows — any stratification built on a free-text term must
run its complement and sum both against the unfiltered population.
Paid for in the same file (line ratchet 314/314, headroom 0):
- search_issues session-zeroing row 12 -> 10: dropped "不是工具/平台级"
(survives as 故障是**会话级**), the perPage pointer (own row above), the
"极易半途而废" aside (survives as the 226/100 specimen) and the
undetermined-mechanism tail (survives as ⛔ 已推翻别再追).
- 读数五坑 `gh` clause 7 -> 5: dropped the `command -v gh` exit-1 evidence
(survives as **容器里没有 `gh`**) and the pipe-exit-code cross-reference
(survives in os-dev.md's 门禁结果读法 clause).
- auto-tier classifier row 5 -> 4: label-POST specimen compressed to
判定还跨天翻面, keeping the cross-day fact and dropping the anecdote.
- quota-per-account row 7 -> 6: dropped 端点自身不计费 (survives at the
开轮先读配额 clause's "免费") and the used-10461/core-7 specimen
(survives as the independent-pools reading in the 默认读序 row).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnijPVVDakqK2J335JoJtq
…hat auto_merge is unstable
The merge-queue rows recorded what cannot tell you a PR is enqueued
(`auto_merge` reads back off, `merge_group` is a false negative off the
queue front) and named the authoritative confirmation as a webhook — so a
seat with a blind instrument and a ⛔ rule that depends on the reading it
cannot make did the predictable thing and re-armed an already-enqueued PR.
Per the filing seat's own correction, the webhook IS reachable from a
subscribed session (measured within ~1s of the timeline event), so the row
states the division rather than a gap: the timeline is the reading you make
— queryable on demand, both directions from one call, and it separates
"never enqueued" from "enqueued then ejected" — while the webhook is
push-only corroboration that a fresh context or an unsubscribed seat cannot
recover. `auto_merge` is upgraded from absent to UNSTABLE: the same PR read
`set` and then `None` within a minute, so "not armed" is never a conclusion.
Sits beside the just-landed mergeable_state / update_pull_request /
draft-dequeue reshaping; none of those cuts is reopened.
Paid for in the same file (line ratchet 314/314, headroom 0):
- MCP read-side entity row 7 -> 5: dropped the write-side shape enumeration
(survives in the 写侧实测行为 rows it pointed at) and the MCP-or-API
attribution aside, keeping the &amp; boundary.
- REST-availability row 12 -> 11: shift matrix trimmed to the two readings
that prove 门态逐容器变; the per-entry gh/rate_limit details survive at
the 单调用凭据判别 clause and the 读数五坑 gh clause.
- probe-branch row 8 -> 7: dropped the pre-claim-check restatement
(survives as 失效方向是活卡被永久读成已被认领) and the undiagnosed-cause
enumeration, keeping the boundary itself.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnijPVVDakqK2J335JoJtq
… a clean zero
Unauthenticated REST from inside a session reaches repository-scoped paths
only; anything under /search/ is refused by the proxy. The refusal body is
valid JSON, so a caller reading total_count off it gets None and formats it
as `total: None` — one character away from a real empty result set. A
duplicate check that ran three such queries produced three clean-looking
zeros, and the request never ran at all. The instrument for /search
questions is the MCP search tools; repo-scoped enumeration is unaffected.
Placed directly under the session-zeroing row so that row's "见下条" pointer
resolves, and it carries the pairing the card asked for: run the positive
control FIRST, because a zero that matches your expectations is the one you
never re-check — here the control zeroing alongside the others is the only
reason the artefact was caught.
Paid for in the same file (line ratchet 314/314, headroom 0):
- truncation-diagnosis row 12 -> 10: dropped the WebFetch restatement (the
same instruction survives in the merged ⛔ 永不单凭 MCP 读判截断 clause),
the three-seat observation anecdote, and the 0-span/10-tag control
example (the 判据 itself survives verbatim).
- sanitizer author-rule row 7 -> 6: the comment-exemption history compressed
to ⛔ 评论不豁免 (the mechanism survives in the 评论 row below it).
- sanitizer issue-body row 9 -> 7: merged the two fenced-behaviour readings
into one dated clause, dropped the negative-lookbehind aside and the
"同类标记在评论里存活" note whose rule survives as ⛔ 评论不豁免.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnijPVVDakqK2J335JoJtq
… number
`issue_read` with method `get_labels` and a PULL REQUEST number fails with a
GraphQL resolution error — its query resolves the Issue type only, so the
REST convention that a PR is also an issue does not carry over. Measured
twice on different PRs by different dev runs in the same hour. Label
read-backs on PRs are a mandated step, so every dev walks this path and
re-derives the workaround alone; the row names the working read
(`pull_request_read get`, whose response carries the labels array) and rules
out the obvious fallback of spending a search-channel call to answer a
single-PR question. The failure is loud, which is the good direction — the
row exists to stop the re-derivation, not to prevent a wrong answer.
Placed with the other label-semantics traps (`labels` OR-vs-AND, the
whole-set replace) so the read-back rule and its instrument sit together.
Paid for in the same file (line ratchet 314/314, headroom 0):
- MCP-parameter row 10 -> 9: the OR-side evidence keeps the sharper of the
two specimens (a two-label "intersection" wider than one of its own
inputs) and drops the duplicate one.
- list_issues-assignees row 3 -> 2: dropped the trailing ⛔ restatement,
which repeats 清单只是候选名单 verbatim in imperative form.
- MCP list/search row 3 -> 2: dropped the navigational pointer to the
session-gate row printed directly above it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnijPVVDakqK2J335JoJtq
…ndate re-derivation
Two rows, both measured 2026-08-27.
A dispatched subagent that terminates mid-edit because the account's model
tier hit its limit is a THIRD death shape, and the fact table had no name
for it: the host reports the limit, so it is neither an ordinary subagent
death nor a maintainer abort — the dispatch is dead, the premise is intact,
and re-dispatch is blocked on a resource rather than on anything about the
card. Handling is dead-claim recovery plus worktree rescue, not premise
re-verification and not an escalation. No pre-flight probe exists (quota
state is observable only once a dev dies inside it), so the mitigation is
dev-side WIP-committing, which the row says rather than implying.
The second row is what keeps the lane moving. A mandated tier must not be
downgraded because it is unavailable — that is the substitution the
downgrade fuse refuses — but "this lane is fable-mandated" is usually
recalled rather than derived, and the recalled version is too wide.
Re-deriving with dispatch-gates --tier PER FILE SURFACE splits a blocked
fold instead of stopping it (measured: SKILL.md and .claude/agents/** are
mandated; scripts/pm/** and references/** carry no path mandate, turning a
fully-blocked three-card fold into one blocked half and three runnable
ones). The path line is a floor, never a clearance — content clauses are
still judged separately.
Paid for in the same file (line ratchet 314/314, headroom 0):
- sanitizer comment row 8 -> 6: dropped the second-order anecdote (the card
recording the incident truncating at the same point), the safe-authoring
recipe (verbatim in the author-rule row above) and the "人看着像正常收尾"
restatement; the truncation shape, the fuse and the worst-case boundary
all stay.
- silent-downgrade rows 9 -> 8: the host-fork row was entirely a modifier on
the row above it, so it becomes that row's ⚠️ clause; both host readings,
both dates and the transcript fallback survive.
- blocked-download row 3 -> 2: dropped the unverified-verdict anecdote and
the zero-hit cross-reference; os-dev.md carries the same rule with the
same evidence.
- enable-sequence row 8 -> 7: dropped the stale-PR-reading specimen, whose
fact (contradictory merged booleans) is the 二义读数 row's subject.
- default-read-order row 8 -> 7: kept one of two specimens for independent
metering.
- git-first row 11 -> 10: dropped the container-has-no-gh duplicate (the
读数五坑 row asserts it) and the search/core independence duplicate (the
default-read-order row above states it).
- org-authorization row 3 -> 2: the valid-JSON-403 mechanism now lives in
the session-proxy row, so this row cites it instead of restating it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnijPVVDakqK2J335JoJtq
@os-litantos-litant added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/m labels Aug 27, 2026 — with Claude
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Aug 27, 2026
@os-litant
os-litant marked this pull request as ready for review August 27, 2026 04:16
@os-zhuang
os-zhuang added this pull request to the merge queueAug 27, 2026
Merged via the queue into main with commit 9acb9edAug 27, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-12486-platform-fact-rows branch August 27, 2026 04:49
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment