Skip to content

gate/AGENTS: the Documentation Guardrails table has no row for packages/*/CHANGELOG.md — a released, consumer-shipped, generated file that is neither .changeset/ input nor content/docs/releases/ #16849

Description

@os-zhuang

Filed by the triage seat while answering the pm:retriage objections on #15058 and #15026. Both blew through the same unwritten boundary, so the gap is measured rather than anticipated.

⛔ Filed under SKILL.md's conflict rule — 「两条细则冲突 ⇒ 按更严的一条行动并立卡;⛔ 不当场改文本了结」. I acted on the stricter reading for those two cards and am filing the rule gap here rather than settling it by editing AGENTS.md on the spot.

The gap

AGENTS.md's Documentation Guardrails table (:672 onward) has exactly four rows:

row disposition
content/docs/references/ AUTO-GEN — ❌ never hand-edit
content/docs/releases/ RELEASE-OWNED — ❌ never edit in a code PR; factual error → dedicated docs-only PR or an issue
**/translations/*.generated.ts AUTO-GEN structure
content/docs/<tree>/ (all others) hand-written

packages/*/CHANGELOG.md is in none of them, and it is not a corner case:

  • It is generated — changeset version compiles it from .changeset/*.md, the same way content/docs/releases/ is compiled. Structurally it belongs with the auto-gen / release-owned rows.
  • It is published to consumers. AGENTS.md :1039 says so in its own words, in the breaking-changeset rule: "this text ships to consumers as CHANGELOG.md inside the npm package and is what an upgrading agent greps after the tombstone error."
  • ⭐ So the one place AGENTS.md does describe this file establishes that it is consumer-facing and agent-read, while the table that says what may be edited does not mention it at all.

Why the gap has teeth: the two cards that fell into it

Both #15058 and #15026 were filed as "correct one line in a pending .changeset/*.md" — which the guardrail explicitly permits (#16671 states the permission cleanly: "⛔ Not a content/docs/releases/ edit — this is a .changeset/ input, which the documentation guardrail explicitly permits a PR to touch").

Then a release consumed both changesets, and each false sentence moved from an editable input to a shipped CHANGELOG. Verified on origin/main c5ea982d, using the line-count form (⚠️ git ls-tree <ref> -- <path> exits 0 on no match, so an && echo PRESENT idiom reports PRESENT for a file that does not exist):

.changeset/field-rows-and-option-description-declared.md -> 0 line(s)   (#15058's target)
.changeset/react-tier-vocab-converge.md                  -> 0 line(s)   (#15026's target)
control  .changeset/README.md                            -> 1 line
control  files in .changeset/                            -> 369

⇒ a real absence, not an empty read. The sentences now live at packages/spec/CHANGELOG.md:1497 (#15058) and at both packages/lint/CHANGELOG.md:436 and packages/spec/CHANGELOG.md:3641 (#15026).

⇒ The route each card prescribed no longer exists, and the successor route is unwritten. That is this card.

What to decide and write

One row in the Documentation Guardrails table for packages/*/CHANGELOG.md, stating:

  1. May a code PR edit it? The stricter reading — the one I applied to finding(changeset): the pending field-rows/option-description changeset still says the canonical field-level spelling is depends_on — false for this package, and it is release-notes input #15058/finding(changeset): the pending changeset still tells authors the lint accepts the canonical ListView spelling — the step-1 tightening makes every clause of that sentence false #15026 — is no: it is generated, released, consumer-shipped text, so it takes the content/docs/releases/ disposition. ⚠️ But that is my reading of an unwritten rule, not a quotation of one.
  2. What is the correction route for a factual error in a released entry? The content/docs/releases/ row's answer is "dedicated docs-only PR or an issue, never a rider on code changes", and .changeset/stack-refusal-envelopes.md still says "None of the six is registered in ERROR_CODE_LEDGER" — PR #16652 registers all six, and both changesets compile into the same release #16671 independently calls the post-release form "an erratum". Say which: amend the historical entry in a dedicated docs-only PR, or add an erratum in a later entry and leave the record of what shipped intact.
  3. What is a PR's input, and until when? The .changeset/ file is the input — and unlike content/docs/releases/, that input has a hard, unwatched deadline: it stops being editable the moment a release consumes it.

⚠️ Point 2 is the one with a real trade-off and it should not be waved through: rewriting a shipped entry makes the CHANGELOG accurate but no longer a record of what was actually published; an erratum keeps the history honest at the cost of a reader having to find it. ⛔ Pick one and say why, rather than leaving both available.

⛔ Scope

Refs

#15058 · #15026 (the two that fell through) · #16671 (a live instance of the same class whose window is still open — its .changeset/stack-refusal-envelopes.md is present on origin/main, sentence at :24; ⚠️ it is editable now and will not be after the next release) · AGENTS.md:672 (the table) · AGENTS.md:1039 (the consumer-shipped statement).

Activity

  1. added theissue type on Sep 8, 2026
  2. self-assigned this
    on Sep 9, 2026
  3. yinlianghui commented on Sep 9, 2026

    @yinlianghui
    Collaborator

    Claim: PM loop round 1 — flight L: one row in AGENTS.md's Documentation Guardrails table for packages/*/CHANGELOG.md — generated by changeset version, consumer-shipped, so it takes a stated disposition (may a code PR edit it; the correction route for a released entry, amend vs erratum, ONE picked with the reason; the .changeset/ input and its release-consumption deadline) — paid for by a deletion elsewhere in the file because the ratchet sits at 1068/1068
    Session: session_01HxLw5aKDPR5RJgyUR7Exkd
    Branch: claude/issue-16849-agents-changelog-guardrail-row
    Worktree: objectstack-issue-16849
    Domain: domain:skills
    File surface: AGENTS.md — the Documentation Guardrails table (heading at line 674, rows 678–680 on origin/main 2e8e1185) plus the one line paid within the same section or its immediate neighbourhood; ⛔ lines 430–460 untouched (flight D's PR #17023 region at 442–444, at the human terminal); ⛔ no .changeset/* and no CHANGELOG.md edited (stop on breach; explain in the report)
    Container & model: S (one table row + one paid line), mode:subagent, model: opus — --tier at 03:25Z on 2e8e1185 for AGENTS.md: "no path-derived mandate … floor sonnet · default opus · ceiling fable"; PM judgment: default tier — the trade-off is framed on the card and the maintainer sees the pick in the 速读; reviewed in the seat at claude-fable-5-1
    Clause-②: no
    Thread-read: (no comments on the card at 03:20Z; body read in full)
    Serial constraints cleared: AGENTS.md newest touch a814bdb8 (#16843) on origin/main 2e8e1185 read 03:25Z; open PRs on the file: PR #17023 (flight D, lines 442–444, at the human terminal) — answered PARALLEL by region (table at 674–690, the paid line kept ≥ 30 lines away from 442–444; the dev merges main before opening and again after any sibling lands); fold gate ① fails against #16814 (a different rule). H17 trigger-file index: no hit. Verify lock free at 03:25Z. Governed (AGENTS.md) ⇒ draft at the human terminal, four-piece; skip-changeset. Grading note written in the same label write: finding dropped — triage had already graded the card (priority:p3, Task, pm:queue), the label was a leftover.


    Generated by Claude Code

  4. yinlianghui commented on Sep 9, 2026

    @yinlianghui
    Collaborator

    os-dev-report

    {
    "issue": 16849,
    "status": "done",
    "branch": "claude/issue-16849-agents-changelog-guardrail-row",
    "pr": "#17078",
    "premise_still_valid": true,
    "summary": "Added the fifth Documentation Guardrails row for packages/*/CHANGELOG.md, placed directly under the content/docs/releases/ row whose disposition it takes. It states all three things the card asked for: a code PR never edits it; the PR's input is its changeset on a hard, unwatched deadline that the consuming release closes; and a factual error in a released entry is AMENDED IN PLACE in a dedicated docs-only PR (option A), not corrected by an erratum in a later entry (option B). The four-axis reasoning for A over B is in the PR body, together with B's cost stated plainly so the maintainer can flip it. Draft PR, skip-changeset, human merge — no ready flip, no queue, no auto-merge, no seat approval.",
    "premise_checks": {
    "AGENTS.md_CHANGELOG_grep": "git show origin/main:AGENTS.md | grep -n CHANGELOG -> exactly one hit, :1040 (the consumer-shipped statement in the breaking-changeset rule). Zero hits inside the guardrails table. Control word content/docs/releases/ -> hit at :679. Premise holds.",
    "packages_spec_CHANGELOG_exists": "git ls-tree origin/main -- packages/spec/CHANGELOG.md -> blob d94ff3b4. File head is changeset-generated output (## 17.4.0 / ### Minor Changes / - fe0d9a4: ...). 23 files match packages/*/CHANGELOG.md on origin/main.",
    "PM_fence_430_460": "Verified moot (PR #17023 landed) and respected anyway — the diff touches only line 680 and the 700-702 paragraph."
    },
    "line_budget": {
    "file": "AGENTS.md",
    "lines_before": 1068,
    "lines_after": 1068,
    "ceiling": 1068,
    "headroom_after": 0,
    "gate_verdict_lines": [
    "check-skill-line-ratchet: AGENTS.md: widest table row is 768 bytes (pin 768; headroom 0).",
    "check-skill-line-ratchet: AGENTS.md is 1068 lines (ceiling 1068; headroom 0)."
    ],
    "new_row_bytes": 751,
    "new_row_line": 680,
    "widest_row_pin": 768,
    "widest_row_after": "768, still the **/translations/*.generated.ts row (now line 681) — the pin did not move",
    "paid_by": "Deleted 119 bytes of prose from the paragraph that follows the packages/spec regeneration table (three lines to two): the sentences A .describe() string counts — it lands in content/docs/references/. and Adding one export counts — it lands in api-surface/. Both are strict restatements of that table's own first two rows, three lines above them, and the surviving sentence is the one that tells you NOT to hand-match those rows. Each deleted fact has a home in the same section: the two counts facts in regeneration-table rows 1 and 2; content/docs/references/ in guardrail row 1 of the very table this PR extends; api-surface/ in the check:exported-any bullet further down the section. Not a re-wrap: 119 bytes were removed at the paragraph's existing ~82-column wrap width, and the surviving 96-byte sentence still occupies two lines."
    },
    "files_changed": ["AGENTS.md"],
    "gates": {
    "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the final tree 3f72809; change set = 1 path (AGENTS.md), committed 1 / working tree 0 / untracked 0, three-dot vs merge base 854639b",
    "reconciliation": "dispatch-gates --ran: 14 derived famil(ies) accounted for — 14 run, 0 NOT-MEASURED, 0 UNRUN. Plus 1 recorded outside the derivation: pnpm check:doc-authoring.",
    "results": [
    {"cmd": "node scripts/check-closing-keyword-parity.mjs", "exit": 0},
    {"cmd": "node scripts/check-closing-keyword-parity.mjs --self-test", "exit": 0},
    {"cmd": "node scripts/check-comment-mask-corpus.mjs", "exit": 0},
    {"cmd": "pnpm check:agent-test-spelling", "exit": 0},
    {"cmd": "pnpm check:docs-audit-scope", "exit": 0},
    {"cmd": "pnpm check:driver-memory-census", "exit": 0},
    {"cmd": "pnpm check:nul-bytes", "exit": 0},
    {"cmd": "pnpm check:pm-governed-merges", "exit": 0},
    {"cmd": "pnpm check:pm-governed-prose", "exit": 0},
    {"cmd": "pnpm check:pm-skill-id-lint", "exit": 0},
    {"cmd": "pnpm check:pm-skill-ratchet", "exit": 0},
    {"cmd": "pnpm check:refd-timer-probe", "exit": 0},
    {"cmd": "pnpm check:required-contexts", "exit": 0},
    {"cmd": "pnpm check:watch-hint-literal", "exit": 0},
    {"cmd": "pnpm check:doc-authoring (beyond the derivation; PM clue)", "exit": 0}
    ],
    "governed_probe": "node scripts/pm/check-governed-merges.mjs --test AGENTS.md -> exit 3, GOVERNED — a human merge is the review record for this PR (#9495 regime), AGENTS.md ×1 — the repo-root agent instruction file. Quoted in the PR body.",
    "ci": "in_progress — reported at draft-PR time per the dispatch contract; no waiting, no polling."
    },
    "tests": "No package build or test is owed: the diff touches no package (so no dependency closure and no affected package), and edits no gate or tool script (so no checker suite). Evidence taken on the final commit 3f72809. (1) Ratchet: the two green verdict lines quoted under line_budget; wc -l AGENTS.md = 1068 before the change, after the change, and after merging origin/main 854639b. (2) Repo-wide lint, narrowed with the narrowing proved: universe read from eslint's own config — all seven blocks of eslint.config.mjs scope to {ts,tsx,mts,cts,js,jsx,mjs,cjs}, Markdown in none; file count read from npx eslint --no-inline-config --format json AGENTS.md — one result, zero files actually linted, File ignored because no matching configuration was supplied., errorCount 0; invariance — the diff's single path is outside every files glob, so it cannot move any untouched file's verdict. A repo-wide pnpm lint is CI's run. (3) Control bytes: pnpm check:nul-bytes green plus a self-scan, grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' AGENTS.md exits 1 (no match). (4) skip-changeset: AGENTS.md is a repo-root instruction file; no package files[] can reach outside its own directory, so it ships in no tarball. No ablation or reverse-verification artifact applies — this diff adds no gate and changes no code path, so neither would be measuring anything.",
    "mcp_calls": "0 — every GitHub read and write went through the container REST channel (probe GET /repos/objectstack-ai/objectstack = 200, core limit 15000/hr): issue body, comment thread, PR creation (POST /pulls, draft=true), label add (POST /issues/17078/labels), and the read-backs. No MCP GitHub tool was invoked.",
    "deviations": [
    "origin/main moved edf59e3 -> 854639b (06:11Z) between dispatch and work. Merged it into the branch before opening the PR; the merge is clean and that commit touches no AGENTS.md (git diff --name-only edf59e35 origin/main has no AGENTS.md). wc -l stayed 1068 across the merge.",
    "PM gate clue named pnpm check:doc-authoring. It is NOT in this diff's derived family set — dispatch-gates places it in the population it declares too wide to place, so no path narrows it. Ran it anyway, exit 0; --ran records it as Outside this card's derivation (1).",
    "PM claim comment (5595335532) cited the table as rows 678-680 on 2e8e118; on edf59e3 it is rows 678-681, four rows. Line drift only, the anchor is unambiguous.",
    "PR body read back in full after creation: stored body is 14844 bytes vs 14845 sent — the single byte is the trailing newline GitHub strips. The signature footer survived whole (rule line plus the session-URL line). Every marker checked present."
    ],
    "open_questions": [
    {
    "question": "Correction route for a factual error in an ALREADY RELEASED packages/*/CHANGELOG.md entry. The card required one to be picked rather than left open, so the row ships with A; the governed human merge is where the maintainer confirms or flips it, and the PR body ends by asking for exactly one letter.",
    "options": [
    "A — amend the historical entry in place, in a dedicated docs-only PR (SHIPPED IN THIS PR)",
    "B — add an erratum in a later entry and leave the record of what shipped intact (the form #16671 independently reached for)"
    ],
    "recommendation": "A. Axis 1 (real, measured need): AGENTS.md:1040 names the reader in its own words — an upgrading agent that greps after a tombstone error. It arrives at ONE entry by symbol and does not read forward. Both routes have identical delivery latency, since a published tarball is immutable and each reaches only the next install; they differ only in where the correction sits relative to where the reader lands, and under B the reader lands on the false sentence with nothing pointing it onward. Axis 2 (long-term soundness): CHANGELOG.md is a generated artifact whose generator appends and never reconciles hand-written matter, so an erratum is an un-generated second layer nothing keeps in step, while amending is the one deliberate out-of-band hand edit the file already tolerates; contract-first, the breaking-changeset rule requires the entry that removes a symbol to carry its FROM-TO, which B leaves broken exactly there. Axis 3 (hardest for an AI to get wrong): the failure being corrected IS an AI acting on a false migration sentence — B keeps it and adds a second live reading for the reader to reconcile, the tolerant shape this project rules against, and it declares a route the runtime does not honour since nothing back-links an entry to a later erratum (that detection question is filed separately and out of scope here); A is mechanical where B asks an author to choose which later entry and phrase a back-reference. Axis 4 (startup stage, no proliferation): B invents a document form that does not exist today and pulls in a placement convention, a back-link and eventually a gate, with two sentences on two cards as its whole demand; A adds no new form, reusing the route already written one row above; and the no-gradual-transition ruling refuses precisely B's both-readings-stay-live shape. Conflict stated honestly: B's case is real — under A the file on main stops being byte-identical to what each version shipped. It does not carry, because that record is held by npm's immutable per-version tarballs and by git history, while the working file's one measured reader is a greper B cannot reach."
    }
    ],
    "out_of_scope_findings": [
    "noted, not filed: AGENTS.md:689 introduces the packages/spec regeneration table with Match the change to the gate and regenerate up front: while the paragraph after the table says Don't match by hand. A readability nit, not an error, a contract breach, or a metadata trap — outside the three filing classes. This PR reduces it (the two hand-matching examples are gone). 承接者: the maintainer reviewing this PR, who reads that exact paragraph in the diff."
    ]
    }


    Generated by Claude Code

  5. yinlianghui commented on Sep 9, 2026

    @yinlianghui
    Collaborator

    ACCEPT — flight L, #16849 (skills seat, session session_01HxLw5aKDPR5RJgyUR7Exkd, 2026-09-09T06:52Z)

    PR #17078 (Fixes #16849, head 3f72809c = origin/main 854639b3 merged onto the content commit 0410c22d), draft, targets main, one file AGENTS.md (+3/−3). Verified against GitHub and the fetched head, not the report (5597433499):

    • Path face (node scripts/pm/check-governed-merges.mjs --test AGENTS.md): exit 3 — GOVERNED ⇒ draft at the human terminal — the maintainer merges, or an authorised approval (os-zhuang / hotlong) lets this seat enqueue. ⛔ No ready flip, no auto-merge, no queue from this seat.
    • The row landed at line 680, directly under the content/docs/releases/ row whose disposition it takes, and states the card's three items: a code PR never edits it; the PR's input is its changeset on a deadline the consuming release closes; a factual error in a released entry is amended in place in a dedicated docs-only PR (A), never an erratum in a later entry (B), with the reason (the reader greps the tombstoned symbol and lands on the old entry) and where the shipped record survives (tarballs, git history). The cross-reference it makes (§ Post-Task Checklist step 3) resolves in the file.
    • Paid for inside the same section: the two sentences removed from the paragraph after the packages/spec regeneration table (「A .describe() string counts — it lands in content/docs/references/. Adding one export counts — it lands in api-surface/.」) restate rows 1–2 of that table three lines above them and guardrail row 1; each fact keeps a home in the section (re-read on the head), so no rule is lost; the surviving sentence is the one that says not to match by hand. Not a re-wrap.
    • Re-run on the head in the seat's scratch worktree: ratchet exit 0 — AGENTS.md 1068/1068, widest table row 768/768 unmoved (the new row is 752 B); check:pm-skill-id-lint 26 files clean; control-byte scan 0. Labels read back: documentation, size/xs, skip-changeset. Closing keyword: exactly Fixes #16849. mergeable: true.
    • CI on 3f72809c at 06:51Z: 12 success / 12 skipped / 4 in progress (Lint & Repo Gates and the three Type Check jobs) — a draft-time reading; the terminal is human either way, and the seat re-reads before any landing step.
    • The A-vs-B choice, reviewed at the contract tier: A holds. The one measured reader (AGENTS.md's own :1040 — an upgrading agent grepping after a tombstone) reaches exactly one entry; B leaves the false sentence where that reader lands and adds a hand-written layer the generator never reconciles; A reuses the route the row above already prescribes. B's real cost (the working file stops being byte-identical to what shipped) is carried by the immutable tarballs and git history. It is the maintainer's to flip at merge — the 速读 asks for one letter.
    • Deviations accepted: base moved edf59e35 → 854639b3 (merged cleanly, no AGENTS.md in between); check:doc-authoring is outside the derived family set and was run anyway (exit 0); the claim's row numbers drifted by one (four rows, not three).

    Governed four-piece: needs-user-decision hung on the PR and the 「维护者速读」 final comment posted there; reviews requested from os-zhuang and hotlong; listed under awaiting a human merge in the round report. The card stays pm:dispatched with this seat as assignee until the merge lands.


    Generated by Claude Code

  6. removed their assignment
    on Sep 9, 2026
  7. yinlianghui commented on Sep 9, 2026

    @yinlianghui
    Collaborator

    Landing record (skills seat, session session_01HxLw5aKDPR5RJgyUR7Exkd, 2026-09-09T08:08Z): PR #17078 (flight L) MERGED 08:07Z as 9cb6f51f on main via the merge queue (approved and readied by os-zhuang, the authorised approver, at 07:4xZ; auto-merge armed by this seat); this card closes by its Fixes line. pm:dispatched and the assignee cleared in this same write; domain:skills and the type labels stay.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions