Skip to content

docs(agents,pm-dispatch): a draft conversion drops only an unqueued PR's auto-merge; a queued governed pull-back goes to the maintainer - #20854

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20847-agents-draft-dequeue
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20847-agents-draft-dequeue

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20847

Clause-②: no

What changes

Four sentences stop saying that a draft conversion, or draft plus an auto-merge disable, removes a PR from the merge queue. Each keeps the half that holds: on an unqueued PR, the conversion drops auto-merge. Prime Directive 14 now names the one removal act that is measured, the maintainer's manual removal, and says why a seat cannot do it.

site before after
AGENTS.md Prime Directive 14, "Already armed or queued" (was :285-:287, now :285-:288) Convert it back to draft AND disable auto-merge — draft is what removes queue membership, disabling alone drops only the arming — then confirm from the remote that it is in neither the queue nor origin/main. Disable auto-merge AND convert it back to draft, which disarms an unqueued PR; no seat act, those two included, is measured to dequeue one (platform-readings.md, queue membership), so ask the maintainer at once to remove a queued PR by hand — then confirm from the remote that it is in neither the queue nor origin/main.
AGENTS.md "State on your PR" (:428-:429 → :429-:430) the draft flag, which flipped back destroys auto-merge and queue membership at once the draft flag, which flipped back destroys an unqueued PR's auto-merge at once
AGENTS.md multi-agent §7 (:532-:533 → :533-:534) flipping back to draft drops auto-merge and queue membership at once, and neither returns by itself flipping back to draft drops an unqueued PR's auto-merge at once, and it does not return by itself
.claude/skills/pm-dispatch/SKILL.md:202 已入队才读到本条 ⇒ 转 draft 与 disable 都做;出队以阳性探针答,ref 缺席只旁证。 已入队 ⇒ 转 draft 并 disable,即请维护者手动出队(席位无实测手段);出队以阳性探针答。

Every clause that still holds is kept: "Draft is no barrier by itself — the barrier is this directive", "confirm from the remote that it is in neither the queue nor origin/main", "ready first, arm second", and the positive-probe reading of a dequeue. The rule text cites platform-readings.md and restates no reading. The readings themselves are in PR #20845 (issue #20764).

The grep. AGENTS.md and every file under .claude/** were searched for other copies of the claim: queue membership, draft next to queue or auto-merge, 转 draft, 出队, 踢队, dequeue, evict. The four sites above are all of them. references/platform-readings.md:41-:43 is PR #20845's, and this PR leaves it alone. lanes/cli.md:17 only points at that file.

Is there a seat act that removes a queued PR? (the claim's measurement)

Every step was read-only. ⛔ No mutation was called and no PR was dequeued.

  1. Live GraphQL introspection was refused. The request was POST https://api.github.com/graphql with query { __type(name: "Mutation") { fields { name description } } }. It returned 403 from this session's egress, with the message "GitHub GraphQL is not available from Claude Code sessions". The same message lists the pull-request routes the egress does serve: ccr/auto_merge (PUT/DELETE), ccr/ready_for_review and ccr/convert_to_draft. None of them dequeues.
  2. GitHub's published GraphQL schema does declare the mutation. This is a document reading, not a live introspection. The source is octokit/graphql-schema schema.graphql on main, fetched 2026-09-30T12:18Z (1,223,842 bytes, sha256 prefix 3c62d0526d133cee). It declares dequeuePullRequest(input: DequeuePullRequestInput!) with the description "Remove a pull request from the merge queue." Its input is id: ID!, the pull request id.
  3. REST has no dequeue endpoint. In github/rest-api-description, api.github.com.json (info version 1.1.4, main), no path contains queue. The word "dequeue" appears only in the pull_request.dequeued webhook schemas.
  4. GitHub's own docs list the ways a PR leaves the queue. The source is github/docs on main (data/reusables/pull_requests/merge-queue-removal-reasons.md). The list has four entries: CI failure, timeout, "User requesting a removal via the API or merge queue interface", and a branch-protection failure. Neither a draft conversion nor a close is on it. The how-to page (merging-a-pull-request-with-a-merge-queue.md) removes a PR through the web page's Remove from queue button and says the CLI cannot do it.
  5. The relay cannot carry it. scripts/pm/fleet-write/ops.mjs at 05a7547c9 has 15 ops: comment, comment_edit, labels_add, labels_remove, assign, unassign, issue_patch, issue_create, pr_create, pr_request_reviewers, pr_ready, pr_draft, automerge_enable, automerge_disable and transfer. ALLOWED_MUTATIONS has five entries, and dequeuePullRequest is not one of them.

⇒ No dequeue act is measured to be available to a seat. The platform documents one (the GraphQL mutation), but neither this session's egress nor the relay can reach it. So the rule sends a queued pull-back to the maintainer and says why. The triage direction on the card reserves the next step for the triage seat: whether to file a tooling child for a relay dequeue op. That would be a new relay capability.

Coordination with PR #20845

The new rule cites the queue-membership section of platform-readings.md. At origin/main (05a7547c9), line :42 of that section still carries the old reading, and PR #20845 rewrites :41-:43. Until #20845 lands, the rule and line :42 disagree. #20845 should land first, or with this PR. The citation names a file section, not a PR, so it is correct once both have landed, whatever the order.

Line budget

  • AGENTS.md: 1107 → 1108 lines, against a ceiling of 1116 (headroom 9 → 8). The +1 is all in Prime Directive 14. The new sentence adds the maintainer route and the reason for it, and it needs four lines where the old one took three. The other two AGENTS.md sentences are net 0.
  • pm-dispatch/SKILL.md: 319 → 319 lines (ceiling 319, headroom 0). Line :202 is edited in place and goes from 107 to 116 bytes.
  • Every added line is at most 120 bytes: 118 / 110 / 120 / 118 (Prime Directive 14), 83 / 87 ("State on your PR"), 88 / 88 (§7) and 116 (SKILL.md). check:pm-skill-ratchet reports no over-length line and leaves the table-row pins unchanged (768 and 342).

Deviations from the dispatch

  1. SKILL.md:202 drops two parts. They are the clause 「ref 缺席只旁证」 and the 「才读到本条」 of the trigger. The 120-byte cap left 13 bytes of room on the line, and the maintainer route with its reason needs about 50. The dropped clause is kept in platform-readings.md:44 (「ref 缺席只作旁证,⛔ 永不承载结论」), and the positive-probe half of the line stays.
  2. AGENTS.md is net +1, not ≤ 0. None of the three sentences frees a whole line by deleting content. Freeing one would need a re-wrap of text this PR does not otherwise change. The pin is not exceeded.
  3. The introspection is a schema-document reading. The live endpoint refused this session (item 1 above).

Acceptance notes

  • Same family, outside this PR's scope (the dispatch rules out any script edit). scripts/pm/check-governed-queue-guard.mjs makes the same claim in both of its refusal texts, at :1451-:1452 and :1911-:1912 (at 05a7547c9): "take the pull request out of the queue: convert it back to DRAFT (disarming auto-merge alone does NOT dequeue it)". Parking the PR in draft after the guard's red is still useful: an armed PR re-enters the queue, and a draft cannot be armed. The part in parentheses is the falsified premise. This is reported to the seat so it can join the card's family. No card is filed.
  • An unmeasured candidate, for triage. Closing the PR is an act a seat has, through the relay's issue_patch with state: closed. A code comment in .github/workflows/cross-repo-issue-closer.yml:399-:401 says that closing drops queue membership. GitHub's list of removal reasons does not name it, and I know of no reading of it in this repo. It was not attempted.
  • Changeset. None. .claude/** and AGENTS.md are not in any package's files[], so this PR publishes nothing. skip-changeset is for the seat to apply.
  • Landing. This is a Tier H governed surface (AGENTS.md). The PR stays a draft until an authorized approval.

Verification (head b095ba02e)

  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, with no paths, derived 22 commands for this change set (2 paths against merge base 05a7547c9). All 22 exited 0:
    • check-closing-keyword-parity, plus its --self-test;
    • check-comment-mask-corpus;
    • check-governed-queue-guard --self-test and check-harness-current --self-test;
    • @objectstack/lint check:doc-formula-expressions, run after building @objectstack/lint and its dependency closure under os-verify-lock.sh (VERDICT command-exit 0);
    • the check: family: agent-test-spelling, cross-package-test-inputs, doc-authoring, docs-audit-scope, driver-memory-census, gitlink-declared, nul-bytes, pm-expected-skips, pm-governed-merges, pm-governed-prose, pm-skill-id-lint, pm-skill-ratchet, refd-timer-probe, required-contexts, skill-frame-sync and watch-hint-literal.
  • pnpm check:pm-settings-deny-roster was also run, because its roster lives under .claude. It exited 0.
  • The --ran reconciliation read "22 derived, 22 run, 0 NOT-MEASURED, 0 UNRUN". All 22 commands recorded an exit code.
  • Readings from the gates:
    • check:pm-skill-ratchet: "AGENTS.md is 1108 lines (ceiling 1116; headroom 8)" and "pm-dispatch/SKILL.md is 319 lines (ceiling 319; headroom 0)".
    • check:pm-skill-id-lint: "34 file(s) clean".
    • check:pm-governed-prose: "2 instruction surface(s) name all 6 registered governed surfaces".
  • NOT MEASURED:
    • check-required-contexts --verify-required-set, which reads the workflow event and so runs only in CI;
    • the four CI type-check lanes. This diff touches no TypeScript.

维护者速读(草稿)

  • 改了什么:AGENTS.md 三句、pm-dispatch/SKILL.md 一行。旧文说「转 draft 会把 PR 踢出合并队列」,现在都不再这么说。改后:转 draft 只在未入队时掉 auto-merge。已入队的受管 PR 要撤回时,两手照做,并立即请你手动出队。
  • 为什么改:实测里 4 个已入队后转 draft 的 PR 都没有因此出队,其中 2 个带着 draft 被队列直接合入,读数在 PR docs(pm-dispatch): the draft-conversion reading splits by queue state — no queued PR left the queue on a draft conversion #20845。「转 draft 能出队」的说法写在第 14 条指令里,那是防止坏合并的撤回手段,而这个手段实际不起作用。出队手段问题:目前席位没有经实测可用的出队手段。GitHub 公开 schema 里有 GraphQL dequeuePullRequest,但本会话出口拒绝 GraphQL(403),中继也没有这个 op。所以规则暂时把已入队的撤回交给你手动出队。要不要给中继加出队 op,属于新能力,由分诊决定是否立子卡。子卡立了,你会看到裁决请求。
  • 风险与代价(含回滚):只改规则文字,不改代码。代价是已入队的撤回要等你点「Remove from queue」。队列落地约需 15–30 分钟,所以需要你及时响应。PR docs(pm-dispatch): the draft-conversion reading splits by queue state — no queued PR left the queue on a draft conversion #20845 应先合或同合,否则被引用的读数第 42 行暂时和本规则说法相反。回滚就是 revert 这一个 PR。
  • 席位意见:
  • 你要做的:审阅后批准本 PR(或直接合并)。

Generated by Claude Code

…nd routes a queued PR to the maintainer

WIP: first of the three AGENTS.md sentences rewritten.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg
…to-merge; a queued pull-back goes to the maintainer

The "State on your PR" paragraph and multi-agent section 7 stop saying a
draft conversion drops queue membership; pm-dispatch SKILL.md's queued
pull-back line asks the maintainer for a manual removal at once, since no
seat act is measured to dequeue a PR.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b095ba02eee411e2db30b8336aa3cb8a7d7c9575
Local-runs: none

Reviewed read-only: card #20847 (body + comments 5910732998 / 5911100852 / 5911443452), PR #20854 body, file list and net diff, PR #20845 diff, the #20695 / #6732 / #20797 timelines, the head's check-runs, and the repository at 05a7547c9 (merge base), b095ba02e (head), origin/main (72f8c3820) and 02ea176a1 (#20845's tip) via git show / git grep / git diff only. The record template was printed with record-recognisers.mjs --template as the brief directs; no gate, build or test was run. Not read: the dispatch order or the dispatching seat's conclusions.

Net diff at the head. origin/main moved from the PR's snapshot base to 72f8c3820 after the branch forked at 05a7547c9; none of AGENTS.md, pm-dispatch/SKILL.md, platform-readings.md, fleet-write/ops.mjs or check-governed-queue-guard.mjs changed on main in that span, so the three-dot diff IS the net diff: 2 files, +9 / -8 (AGENTS.md +8 / -7 across three hunks; SKILL.md +1 / -1 at :202).

Check-runs on the head (filter=latest, 30 runs): success 18, skipped 12, no other conclusion; every run is completed. Every non-success run is a skipped: Auto Label (dup name), Build Core, Build Docs, Check Changeset (dup name), Check PR Size (dup name), Console Pin Gate, Dogfood Regression Gate (matrix), Dogfood Verify CLI, Packed-tarball smoke ×2, Temporal Conformance, Test Core (matrix). Of note among the greens: Governed Surface Queue Guard, Lint & Repo Gates, Check Changeset, the four Type Check · lanes, TypeScript Type Check, Test Core, Dogfood Regression Gate, and the three card/branch parity checks. PR state read: draft, auto-merge off, mergeable_state: clean, labels documentation · size/s · skip-changeset. Untouched.

① Derived judgments

Prime Directive #14, AGENTS.md:285-:288 at head — right. Clause by clause against the timelines and the code:

"State on your PR", AGENTS.md:429-:430 — right. "flipped back destroys an unqueued PR's auto-merge at once … invisibly" is the measured unqueued reading; the paragraph's rule (do not "correct" a flag another actor set) is unchanged; the pointer "(§7's draft-flip re-arm note)" still resolves to the rewritten §7 sentence at :533. Nothing true lost.

Multi-agent §7, AGENTS.md:533-:534 — right. "flipping back to draft drops an unqueued PR's auto-merge at once, and it does not return by itself — ready first, arm second": #4745 and #6727 were re-armed only by a fresh act after ready (01:30:52Z, 13:46:02Z). "neither returns" → "it does not return" is the correct follow-through once queue membership leaves the sentence. Nothing true lost.

.claude/skills/pm-dispatch/SKILL.md:202 — right, with one named residual.
Base: 「已入队才读到本条 ⇒ 转 draft 与 disable 都做;出队以阳性探针答,ref 缺席只旁证。」 (107 bytes)
Head: 「已入队 ⇒ 转 draft 并 disable,即请维护者手动出队(席位无实测手段);出队以阳性探针答。」 (116 bytes)

  • The dropped 「ref 缺席只旁证」 does live at platform-readings.md:44, verified at both origin/main and the docs(pm-dispatch): the draft-conversion reading splits by queue state — no queued PR left the queue on a draft conversion #20845 tip, in its stronger form: 「出队按队列 ref 阳性探针答不在队向,加未落地;ref 缺席只作旁证,⛔ 永不承载结论。」 — :44 is a context line in docs(pm-dispatch): the draft-conversion reading splits by queue state — no queued PR left the queue on a draft conversion #20845's diff, untouched. lanes/cli.md:17 routes queue readers to that file. The line's positive-probe half 「出队以阳性探针答」 is kept. No rule is lost.
  • Dropping 「才读到本条」 widens the literal trigger from "queued by the time you first read this clause" to "queued". In its block (## 路径分叉, :180) the bullet sits after :182 (take the path surface BEFORE ready / arm / queue) and :186 (the Tier H fork) and its ①-④ items, and the legitimate queue entries carry their own preconditions (:196 批准后, :204 PASS 后), so in context "已入队" still names the same case: a governed PR found queued ahead of its landing key. AGENTS.md PD14 keeps the exact trigger "Already armed or queued when you read this?", and AGENTS.md wins on conflict (:6). Residual: read in isolation the new line is literally satisfied by a Tier S PR the seat itself just queued after its PASS record, where the cost of a misread is one unnecessary manual-dequeue request to the maintainer. Restoring 「才读到本条」 costs 15 bytes (116 → 131, over the 120 cap); the only way to fit it is to drop the reason 「(席位无实测手段)」, which the triage direction requires ("says why"). The trade is forced by the pins and the direction; judged acceptable, flagged in ③ for the maintainer's eye.

The dequeue-act measurement — the relay half verified at the code; the sentence is an honest statement. scripts/pm/fleet-write/ops.mjs at 05a7547c9 (unchanged on main since): OPS (:253) has exactly the 15 keys the dev lists — comment, comment_edit, labels_add, labels_remove, assign, unassign, issue_patch, issue_create, pr_create, pr_request_reviewers, pr_ready, pr_draft, automerge_enable, automerge_disable, transfer; PR_MUTATIONS (:209-:213) carries only ready / draft / auto-merge enable / disable; ALLOWED_MUTATIONS (:393) is exactly markPullRequestReadyForReview, convertPullRequestToDraft, enablePullRequestAutoMerge, disablePullRequestAutoMerge, transferIssue; the strings dequeue, queue, enqueue do not occur in the file. The three external legs (published GraphQL schema declares dequeuePullRequest; live introspection 403 from the session egress; REST OpenAPI has no queue path; github/docs removal reasons) are the dev's readings of sources outside this checkout and were not re-fetched here. The rule text does not depend on them: "no seat act … is measured to dequeue one" rests on the in-repo evidence alone (four queued drafts never dequeued by the conversion; the relay carries no dequeue). The wording claims "not measured", never "does not exist", which is exactly what the evidence supports, and the PR body discloses that the platform's schema does declare one. Honest.

The cross-file citation and sequencing. At origin/main (72f8c3820, file identical to 05a7547c9), platform-readings.md:42 still reads 「本仓转 draft 同时掉 auto-merge 与队列成员资格,不自动恢复,转正后重挂;姊妹仓曾保位照合。」, while :41 already says 「转 draft 不是可靠的踢队手段」 and :43 already prescribes both acts. PR #20845 (head 02ea176a1, base 30839063b; the file is byte-identical between that base and current main, so it applies cleanly) rewrites :41-:43 to the split-by-queue-state reading and leaves :44 alone. So: this PR's sentence is true on its own; its citation is consistent with the cited section only once #20845 has landed — until then the rule and :42 disagree, with AGENTS.md winning in the window. The order that keeps the two texts consistent at every moment is #20845 first (or in the same batch), then #20854. #20845 is Tier S and still a draft (needs its own PASS record, then the queue); #20854 is Tier H and waits on the maintainer's word anyway, so the owning seat can hold this order.

Pins — right, measured at the head. AGENTS.md 1107 → 1108 lines against ceiling 1116 (check-skill-line-ratchet.mjs:1531), headroom 9 → 8; pm-dispatch/SKILL.md 319 → 319 against ceiling 319 (:374), :202 edited in place; every added line measured 116 / 118 / 110 / 120 / 118 / 83 / 87 / 88 / 88 bytes, max 120 against the 120-byte cap. Lint & Repo Gates is green on the head.

Survivor grep at the head (AGENTS.md + .claude/**: queue membership, dequeue, evict, 转 draft, 出队, 踢队, 踢出, and draft beside queue/auto-merge): no copy of the falsified claim remains except platform-readings.md:41-:43, which is #20845's. The four sites the PR body names are all of them.

② Semver level

skip-changeset is right. The diff touches AGENTS.md and .claude/skills/pm-dispatch/SKILL.md only; neither is in any released package's files[], so nothing publishes (AGENTS.md:1072-:1076: the label is for a diff that publishes nothing from any released package). Clause-②: no is on the PR body's second line and on the claim (5911100852). Check Changeset is success on the head.

③ Boundary flags

  1. Dev deviation — SKILL.md:202 drops 「ref 缺席只旁证」 and 「才读到本条」. Answered above: the first has its home at platform-readings.md:44 (verified at main and at docs(pm-dispatch): the draft-conversion reading splits by queue state — no queued PR left the queue on a draft conversion #20845's tip); the second widens the literal trigger but not the practical one, bounded by the block's :182 / :186 / :196 / :204 and by AGENTS.md precedence. Acceptable; the maintainer may weigh whether 「已入队才读到本条」 should return in a later shrink of the line.
  2. Dev deviation — AGENTS.md net +1, not at most 0. The claim stated an aim, not a pin; 1108 / 1116 holds. Acceptable.
  3. Dev deviation — the introspection is a published-schema reading, live endpoint 403. Answered: the rule's wording is "not measured", which the in-repo evidence alone establishes; the external readings are disclosed as such in the PR body. Acceptable.
  4. Dev deviation — wording departs from the PM's suggested route. The suggestion is not in this review's inputs; judged against the card's triage direction (5910732998) instead, every requirement is met: the falsified claim removed at all sites, the unqueued half kept, only the measured removal act named, a queued pull-back routed to the maintainer at once with the reason, readings cited not restated, skill copies grep-found and corrected in the same PR. Acceptable.
  5. The dequeue measurement and the tooling-child decision — escalated to the triage seat, as the direction reserves it. The direction's condition for a tooling child ("the claim measures that the platform exposes one") is met by the dev's schema-document reading, not a live one. One observation for that decision, from the code: the relay already executes GraphQL mutations server-side (PR_MUTATIONS, run by the fleet-write dispatch workflow), so the session egress's GraphQL refusal is not by itself the constraint on a relay dequeuePullRequest op; that op would be a new capability and a maintainer ruling, exactly as the card says. Not this PR's scope; no card filed by this review.
  6. The queue-guard refusal text — out of this PR's scope; filed as [finding] the Governed Surface Queue Guard's refusal text tells a seat to dequeue a governed PR by converting it to DRAFT — measured not to dequeue in this repo (same family as #20847) #20855. Verified at 05a7547c9 (unchanged on main since): check-governed-queue-guard.mjs:1451-:1452 and :1911-:1912 both print "convert it back to DRAFT (disarming auto-merge alone does NOT dequeue it)". The claim excluded scripts and scripts/pm/** is not a governed surface; the guard's --self-test is green on this head, so nothing in this PR breaks it. [finding] the Governed Surface Queue Guard's refusal text tells a seat to dequeue a governed PR by converting it to DRAFT — measured not to dequeue in this repo (same family as #20847) #20855 (open, unlabelled, bare for triage) carries it and should land with or after docs(agents,pm-dispatch): a draft conversion drops only an unqueued PR's auto-merge; a queued governed pull-back goes to the maintainer #20854 so the printed remedy and the rule agree. Escalated via [finding] the Governed Surface Queue Guard's refusal text tells a seat to dequeue a governed PR by converting it to DRAFT — measured not to dequeue in this repo (same family as #20847) #20855, already filed.
  7. The close-PR candidate act — escalated to the triage seat, correctly not named in the rule. Verified: ops.mjs:292-:302 issue_patch accepts state, so a seat can close a PR through the relay; .github/workflows/cross-repo-issue-closer.yml:399-:401 asserts closing "drops its merge-queue membership and any armed auto-merge in the same step". No timeline reading of that on a queued PR exists in the inputs, and the dev's reading of GitHub's removal-reason list does not name it. The dev rightly did not attempt it (a mutation). The rule names only measured acts, so its silence on closing is correct. Caution for any future measurement: closing a governed PR is not a reversible pull-back (review state, head-ref handling), so it needs a maintainer ruling before any seat measures it.
  8. Sequencing with docs(pm-dispatch): the draft-conversion reading splits by queue state — no queued PR left the queue on a draft conversion #20845 — escalated to the owning seat as a landing-order constraint: land docs(pm-dispatch): the draft-conversion reading splits by queue state — no queued PR left the queue on a draft conversion #20845 (Tier S, draft, needs its own PASS record) first or in the same batch as docs(agents,pm-dispatch): a draft conversion drops only an unqueued PR's auto-merge; a queued governed pull-back goes to the maintainer #20854 (Tier H, maintainer's word). Until docs(pm-dispatch): the draft-conversion reading splits by queue state — no queued PR left the queue on a draft conversion #20845 lands, platform-readings.md:42 contradicts the rule that cites its section, with AGENTS.md winning in the window.

This PR is Tier H (AGENTS.md, :272-:275): this record is not its landing key. The maintainer's authorized APPROVED review by an account in GOVERNED_APPROVERS remains the key, and the PR stays a draft until then.

Implemented-by: claude/issue-20847-agents-draft-dequeue
Reviewed-by: session_01KTZmMfzVzjNvyaLyQ8mHvg

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 14:03
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 2d5fe76 Sep 30, 2026
34 of 35 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20847-agents-draft-dequeue branch September 30, 2026 14:44
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…that decided it (objectstack-ai#20866)

Part of objectstack-ai#20596
Clause-②: no

## What changed

This is the seventeenth stage of the `domain:services` lane of the
dead-citation sweep. It covers `packages/services/service-job/src/**`
and nothing else. By the seat's claim (`5911377566`), it is the next of
the two single-site packages left in the lane, taken one package per
stage. Later stages cover the other packages, so this PR says `Part of`
and the card stays open.

Every comment or docblock site in scope that cited a tracker number
answering 404 has been rewritten in ruling C+D's form C (comment
5749154545 on objectstack-ai#19123), by the method of stages 1 to 16 (the latest is PR
objectstack-ai#20850, landed as `40d6c5fbd`, the base here). That is **2 sites on 2
lines in 2 files, covering 1 number**:

- `src/never-fires.fixture.ts:30`, the census's one site in this package
at the base (a docblock);
- `src/db-job-adapter.timeout.test.ts:55`, a test-comment site the
census defers, found by the supplementary reading below and rewritten
too, as stages 1 to 15 did.

No test string, source string or gate-invisible spelling in this package
carries a dead number, so these two sites are the whole population.

Both rewritten lines now cite the commit in this repository that decided
what they describe: **1 commit sha**. No ADR or ruling record records
that decision (see the per-number table), so ruling C's commit rung
applies. No number was dropped.

Only comments changed. Each file keeps its line count (2 lines out, 2
in), so no line citation into either file moves. No code token moves
(see the guard below). **No citation number is added**: the only tracker
number on the added lines is the live `objectstack-ai#8628`, which already stood on
its line.

**No changeset.** Neither rewritten line reaches `dist`, and `dist` is
byte-identical with and without the rewrite (see Changeset below), so
this PR takes `skip-changeset`.

## Census: `service-job`, before and after

**Instrument (A1).** The gate's own `node
scripts/check-issue-citations.mjs --census --json`, read-only and
unchanged. The count below is its `allocated-but-absent` findings under
`packages/services/service-job/`. Each run counts as a reading only
because its board frontier equals the newest issue or pull-request
number, read by a separate request just before and just after the run.

| reading | tree | board | whole-repo `allocated-but-absent` |
service-job sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `40d6c5fbd`, run 2026-09-30T12:36:32Z to 12:40:56Z |
enumerated, 187 pages, frontier objectstack-ai#20855 (newest objectstack-ai#20854 before; objectstack-ai#20855
after, an issue opened 12:40:06Z inside the run, so the frontier equals
the newest at the run's end) | 745 | **1** | 1 | 1 | 1 |
| after | head `6ac866f86`, run 12:46:10Z to 12:49:55Z | enumerated, 187
pages, frontier objectstack-ai#20856 (newest objectstack-ai#20856 before and after) | 744 | **0** |
0 | 0 | 0 |

The whole-repo drop is 1, and the finding sets of the two runs differ by
exactly one row, `never-fires.fixture.ts:30 objectstack-ai#8748`, removed; none was
added. The `resolves` tally is 33,221 in both runs, and
`resolves-as-pull-request` (1,985) and `cross-repo-unjudged` (1,018) did
not move either. Neither run was truncated or discarded: both
enumerations read 187 pages at the newest frontier. The seat's census
counted 1 here at `6bff748b`, and the base agrees: `6bff748b` is an
ancestor of the base, and no commit between them touches this package's
`src`.

**Supplementary instrument, the whole scope.** The census does not read
test files or strings. So a second reading runs the gate's own exported
`extractCitations` (whole-file and comment-prose projections) and
`namesThisRepository` over every `.ts` file under `service-job/src` (18
files). It takes its verdicts from the before census's own board reading
rather than from a second enumeration: a number is dead when that census
reported it `allocated-but-absent`, and alive when the gate's own
census-scope extraction (`collectCitations`) judged it and the census
did not report it. 4 numbers are covered by neither, because they stand
only in test files. Each was read on its own through the read-only
tools, and each answers as an issue: `objectstack-ai#8628`, `objectstack-ai#9611`, `objectstack-ai#9631` and
`objectstack-ai#9633`. The probe's control: `objectstack-ai#8748` answers 404 on the issues endpoint
and on the pull-request endpoint.

| reading | citations | dead | src comment | test comment | src string |
test string |
|---|---|---|---|---|---|---|
| before, `40d6c5fbd` | 95 | **2** | 1 | 1 | 0 | 0 |
| after, `6ac866f86` | 93 | **0** | 0 | 0 | 0 | 0 |

Its src-comment column equals the census's 1, which is the control on
the second instrument. The 93 live citations are the same in both
readings (81 by the census's judgement, 12 by the single reads), none is
cross-repo, none is unjudged, and the drop of 2 citations is exactly the
two rewritten sites. A third, raw reading (every `#` followed by 2 to 6
digits, whatever surrounds it) finds 95 occurrences before and 93 after,
the same drop of 2, and no token beyond the gate's grammar.

## Per-number table

Sites and files count every dead occurrence in scope at the base
(comments and strings, tests included).

| number | sites / files | rewritten / left | anchor: what it decided |
|---|---|---|---|
| `objectstack-ai#8748` | 2/2 | 2/0 | `226cb71d5` (PR objectstack-ai#8774): every exact-count case
in this package moves onto the inert `NEVER_FIRES` cron fixture (`'0 0
30 2 *'`, February 30th, which never occurs), and the fixture moves out
of the timeout suite into the shared `never-fires.fixture.ts`. Its
message records the measurement `:30` describes: only `Date` faked, the
registration placed 2/5/10/15 ms before the expression's instant, one
extra handler run per case under the firing spellings, and two of the
three no longer reproducing at a 2 ms lead. That is what `:30` says, and
what `:55`'s 「now shared with the sibling job suites rather than spelled
a second time here」 says. Its subject names `objectstack-ai#8748`, and its own diff
names it on 2 added lines; `git blame` puts both rewritten lines in this
commit |

The sha matches exactly one commit (`git rev-parse --disambiguate`,
count 1) and is an ancestor of the base (`merge-base --is-ancestor`,
exit 0; reverse leg, base against the anchor, exit 1; control legs exit
0: `7246d631d`, 5,451 commits behind the base, and the repository's root
commit `1598cabe4`, 15,209 behind, against the anchor's 5,436; the
history is complete, `--is-shallow-repository` false, 15,210 commits at
the base). `objectstack-ai#8748` answers 404 on the issues endpoint and on the
pull-request endpoint, read one by one.

No ADR, `scripts/adr-anchors/` file or ruling record names `objectstack-ai#8748`, PR
objectstack-ai#8774, `NEVER_FIRES`, `never-fires`, February 30th or an inert cron
(`git grep` over `docs/adr` and `scripts/adr-anchors`: 0 hits), so
ruling C's first rung is empty. The repository's other `objectstack-ai#8748` tokens
are `objectui#8748`, a cross-repo reference the gate never judges.

## Wordings to check

- **`never-fires.fixture.ts:30`, tag swap in place.** 「Measured (objectstack-ai#8748)
by faking ONLY `Date`」 became 「Measured (commit 226cb71) by faking
ONLY `Date`」. The form is the one
`packages/lint/src/lint-flow-patterns.ts:1468` already carries on `main`
(「Measured (commit 8ed9c54)」).
- **`db-job-adapter.timeout.test.ts:55`, tag swap in place.** 「(objectstack-ai#8628,
objectstack-ai#8748)」 became 「(objectstack-ai#8628, commit 226cb71)」. The live `objectstack-ai#8628` stays: it
is the report of the wall-clock flake, whose own change, `7246d631d` (PR
objectstack-ai#8750), first made this suite's cron unable to self-fire. The form is
the one stages 7 and 9 landed (for example `(objectstack-ai#5928, commit 69787f0)`
in `plugin-audit`).
- Neither line was reflowed, so each is 11 characters longer than before
(70 to 81, and 72 to 83), a little past its block's wrap.
`eslint.config.mjs` declares no line-length rule, and reflowing would
have moved neighbouring lines.

## Sites left

- **In `src`: none.** No test title, assertion message, operator log
string, runtime refusal, quoted maintainer ruling or generated file in
this package carries a dead number.
- **Outside `src`, listed and left, not edited in this stage:**
- The release-owned `CHANGELOG.md` names the dead `objectstack-ai#13112` on 1 line
(`:705`; 404 on the issue and pull-request endpoints). Its other 27
numbers are live: 19 by the census's judgement, and by single reads
`objectstack-ai#18715`, `objectstack-ai#14619`, `objectstack-ai#9517`, `objectstack-ai#1893`, `objectstack-ai#9611`, `objectstack-ai#9631` and `objectstack-ai#9633` as
issues and `objectstack-ai#9546` as a pull request.
  - `vitest.config.ts` names the live `objectstack-ai#10374`.
  - `README.md`, `package.json` and `tsconfig.json` name none.

## Mechanical guard: no code token moves

The guard compares, base `40d6c5fbd` against head, over both touched
`.ts` files:

- **Reading 1**, the TypeScript parser's leaf nodes (a `forEachChild`
walk, so comments are trivia and JSDoc nodes are never visited). String
and template literals are therefore read in full.
- **Reading 2**, the full token stream in parser context (a
`getChildren` walk, so punctuation and keywords are included; JSDoc
nodes skipped).

Results:

- Real run at the head `6ac866f86`: 755 base leaf tokens, **0 files with
a token change** on either reading (exit 0).
- Comment control (「croner resolves Feb 29 forward」 to 「croner rolls Feb
29 forward」): 0 files changed, as expected (exit 0).
- Positive control, a code identifier renamed (`expression: NEVER_FIRES
}` to `expression: NEVER_FIRESX }`): DIFFER on the identifier in both
readings (exit 1).
- Positive control, a string literal changed (`'0 0 30 2 *'` to `'0 0 31
2 *'`): DIFFER on the string literal in both readings (exit 1).
- Positive control in the second file, a numeric literal changed
(`TIMEOUT_MS = 20` to `21`): DIFFER on the literal in both readings
(exit 1).

Every mutation went through `scripts/ablation-replace.mjs` (wrap mode)
under a shell trap that restores by absolute path, and each landed
(anchor 1 to 0, blob changed). Each restore was proven byte-identical to
the HEAD blobs (`1fd5e1b3f612`, `ce37b1050c78`), with `git diff HEAD`
empty and a clean tree afterwards.

## Changeset: none (`skip-changeset`)

`files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the package is
not private. `never-fires.fixture.ts` is a test-only support module
(nothing in `src/index.ts` re-exports it, and the tsup entry is
`src/index.ts` alone), and the other touched file is a test. Measured on
the built package (A3), after `pnpm --filter
'@objectstack/service-job...' build` at `6ac866f86`, where the package's
own `tsup` ran and cleaned its output folder:

- Neither rewritten line appears in any `dist` file (0 in all 6, source
maps included). Nor do the old number, `objectstack-ai#8628`, the anchor sha, the
neighbouring lines `:28`, `:31`, `:54` and `:56`, `NEVER_FIRES`, the `'0
0 30 2 *'` expression, or a never-written negative phrase.
- Positive controls, text that does ship: `CronJobAdapter` (45 hits),
`DbJobAdapter` (73) and `sys_job_run` (52) across the entries, and two
docblock lines of the `DbJobAdapter` class JSDoc in `db-job-adapter.ts`
(「`{@link DbJobAdapterOptions.recordRuns}` is `false`, the on/off switch
for」 and the `recordRuns` gating line below it), each in `index.d.ts`
and `index.d.cts`.
- **Byte identity.** The package was rebuilt with the base text put back
in both files (through nested `scripts/ablation-replace.mjs` legs, with
each file proven equal to its base blob, `919a2e444f23` and
`eb00f6eb1978`, during the leg), and again after the proven restore. The
sha256 of all 6 `dist` files is identical in all three builds: JS
entries, declaration files and source maps.

So nothing this PR rewrites is published, and a changeset would announce
a change no consumer can observe. No `.changeset/*` file is added or
touched.

## Gates (head `6ac866f86`)

- **Citation judging, as CI runs it:** `node
scripts/check-issue-citations.mjs` exits 0 (「no issue citations added
against 40d6c5f」). `pnpm check:issue-citations` exits 0 (self-test,
114 cases, 8 batteries).
- **Doc authoring:** `pnpm check:doc-authoring` exits 0 (the
sibling-package prose-id baseline holds, no growth).
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `6ac866f86` (change set derived
from git: 2 paths against the merge base `40d6c5fbd`) derived 54
commands, all among the dispatch list's 63.
- All 63 of the dispatch list ran, each with its exit code captured
before any pipe, and all 63 exit 0; none exited 3.
- `--ran`, fed each command with its exit code, reports 54 derived, 54
run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. Of the other
9, it classifies 7 as pending-changeset families and 2 as outside this
derivation.
- A full `turbo run build` over `./packages/*` and `./packages/*/*` ran
first under the shared verify lock (71 of 71 tasks), so no gate hit an
unbuilt workspace.
- **Roster families the derivation lists outside its commands** (their
rosters sit in a directory this diff touches): `pnpm
check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm
check:filter-alias-parity`, plus `node
scripts/check-changeset-fixed.mjs`, which the dispatch named. Each exits
0.
- **Tests and typecheck, under the verify lock, at `6ac866f86`:**
- `pnpm --filter @objectstack/service-job test`: 11 files pass and 117
tests pass, which is every tracked test file under `src/`.
- `pnpm --filter @objectstack/service-job typecheck` exits 0, and `tsc
--noEmit --listFiles` puts both touched files in the program (the config
includes `src` and excludes no test file).
- **Lint, as a proven narrowing:** eslint with inline config disabled,
over the 2 touched `.ts` files, gives 2 files, 0 errors and 0 warnings
(its `--format json` output). Both files are in eslint's own population
(not reported ignored; `dist/index.js`, the control, reads ignored).
`eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, as its own lines 327-328 state), so a comment
edit here cannot move the verdict on any untouched file. The repo-wide
`pnpm lint` is CI's run.
- **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of
the changed files for control bytes finds none.

## Acceptance notes

- **The gate-invisible spellings, grepped as the claim asked** (objectstack-ai#20636,
including the `clause #N` position). At the base and at the head,
`#N-word`, `#A/#B`, `option #N`, `clause #N` and URL-spelled links are
each on 0 lines, so the claim's 0 / 0 / 0 / 0 / 0 hold. The one
prefix-joined token, `pre-objectstack-ai#6617` in a test title of
`db-job-adapter.degraded-outcome.test.ts`, is inside the gate's grammar
and names a live number.
- **Wording only:** 「the card」 / 「this card」 stands on 4 comment lines
in 3 files under `service-job/src`. It carries no number, neither
instrument sees it, and this diff removes no antecedent.
- **Outside-`src` residue.** `CHANGELOG.md:705` names the dead `objectstack-ai#13112`;
the file is release-owned and is left.
- **The census instrument did not truncate in this stage.** Both
enumerations read 187 pages at the newest frontier.
- **Base.** The branch is on `main` at `40d6c5fbd`. `main` has since
moved two commits (to `660a9b247`). Neither touches
`packages/services/service-job`, `scripts/check-issue-citations.mjs`,
`scripts/pm/dispatch-gates.mjs` or `.changeset/config.json`, so no merge
was taken; the merge queue rebuilds on the merged generation. The one
derivation input that moved is
`scripts/doc-authoring-prose-id.baseline.json`, where the second commit
removed only `packages/objectql` entries; this package has no entry
there before or after, so the merged tree cannot move that gate's
verdict on this diff.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants