Skip to content

pm: land low-risk skills wording on the seat's review (Tier S arm on the catalog rows) - #33531

Draft
objectstack-fleet[bot] wants to merge 11 commits into
mainfrom
claude/project-thread-dhp24e
Draft

objectstack-fleet[bot] wants to merge 11 commits into
mainfrom
claude/project-thread-dhp24e

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #33501

domain:skills#1 · os-project-manager · session_01FEkQ5pF46HJnpBrhgVWtwV

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:登记表里 skills/** 与 ui/skills/** 两行仍是 Tier H,但多了一个「低风险技能臂」读数。以下全部成立才读成 Tier S:受管路径只在这两行;这两行下只有 .md 与 evals/*.json;其余路径只有 changeset 与 docs/、content/docs/ 下的 Markdown 散文;正文恰好一行裸写的 Clause-②: no 和一行 Metadata-protocol: none(引用、代码块、尾随文字、多行声明都算 H);至少一个 changeset,在 head 读出,只发布 '@objectstack/skills': patch,也带这两行;不带 security 标签,时间线上也没被摘掉过;不是 fork。合并队列守卫在 merge_group 腿读这些,再要求同 head 的达档复核记录写的是 PASS,席位入队才放行;--pr 预检给同一判定。AGENTS.md 第 14 条与 pm-dispatch 两处落地行同步。
  • 为什么改:你 2026-11-14 同意把低风险 Tier H 放权给座位(「同意 把低风险的 Tier H 放权给座位」「把低风险的 Tier H 放权给座位,立 p1 卡优化」);现在座位入队技能措辞 PR 会被必需检查 Governed Surface Queue Guard 弹出。
  • 风险与回滚:上面任一条不成立或读不到(正文、标签、时间线、changeset、fork 判定),一律仍是 Tier H(失败即关);复核记录是 FAIL 或没写结论也拒。CODEOWNERS、--test、--branch 与中继请审照旧按路径读 H。docs/adr/** 不动,仍 Tier H,ADR 那一半由 pm: let a seat land an add-only ADR note under the low-risk Tier H ruling (docs/adr/** stays Tier H until then) #33539 承接。回滚即还原本 PR。
  • 先看:scripts/pm/check-governed-merges.mjs 的 lowRiskArmVerdict 与 readBareDeclaration;scripts/pm/check-governed-queue-guard.mjs 的 readLowRiskTier 与 lowRiskRecordVerdict;AGENTS.md 第 14 条 Tier S 括号。
  • 两分钟验证:node scripts/pm/check-governed-merges.mjs --pr 33394,预期 exit 3、Tier S(席内达档复核落地) 与 low-risk skills arm ... Tier S 一行。
  • 需要你做的:本 PR 改 AGENTS.md,本身是 Tier H:请合并(或批准后由席位落地)。

What changes

  • The register (scripts/pm/check-governed-merges.mjs). The skills-catalog and ui-skills-catalog rows keep tier: H and gain lowRisk (scoped to objectstack), modelled on the workflows split: a READING moves them, the row does not. lowRiskArmVerdict(matched, repo, reading) reads Tier S only when ALL of these hold, each a printed reason when it does not:

    • every governed row hit is a catalog row;
    • every changed path lies under a hit catalog row or matches LOW_RISK_RIDER_PATTERNS (a .changeset/*.md, or Markdown prose under docs/ or content/docs/), and every path under a catalog row is .md or evals/*.json (LOW_RISK_ARM_FILE_PATTERNS);
    • the body carries exactly one bare Clause-②: no and one bare Metadata-protocol: none. readBareDeclaration runs the fleet's own readers (readClause2Line, readMetadataProtocolLine) line by line, never a second parser: a second declaration, trailing words, an arm, or the line quoted or inside a fence is H;
    • at least one changeset, each read at the head, whose frontmatter releases nothing but '@objectstack/skills': patch (an empty frontmatter is allowed) and whose body carries the same two bare lines (lowRiskChangesetReasons);
    • the labels were read and none is security, and the timeline was read and has no unlabeled event for security;
    • forkVerdict of the pull object reads a same-repository head.

    No reading, or an unmeasured one, is Tier H with its reason. applyLowRiskArm runs after applyWorkflowSplit, so a split workflow row beside a catalog path counts as a governed row outside the arm. governedTierFor and testVerdict take the reading as one more optional argument; every caller that passes none answers exactly as before (catalog paths Tier H). renderLowRiskLines prints the arm's verdict. --pr builds the reading from the pull object and file walk it already fetches (pullLowRiskReading), plus the timeline over the same channel (fetchTimelineOver, only when a catalog path is hit, every page or NOT MEASURED) and each changeset at the PR's head from this checkout (pullChangesetReading, NOT MEASURED with the fetch that cures it when the head is not here). --test and --branch have no body and say NOT MEASURED. The header section "The low-risk skills arm" lists the conditions.

  • The queue guard (scripts/pm/check-governed-queue-guard.mjs). decomposeGovernedWork keeps allPaths (every path of every attributed commit of the PR, collected before the governed filter drops a row) and changesetRefs (each changeset with the commit that touched it, ungoverned commits included). The entry reads the arm NOT MEASURED there; the pull_request leg prints Tier H with that note and never reddens. On merge_group, readLowRiskTier re-reads each catalog entry's tier through the register's governedTierFor with: the body, label names and forkVerdict of the pull object the leg already fetched (makePullReader now returns labels and fork); the timeline the enqueue leg already read (kept, not re-fetched; an unread or failed timeline is NOT MEASURED); and each changeset by git show <commit>:<path>. Then the Tier S record leg runs, and for an entry the arm put at Tier S the same-head record must also READ PASS: lowRiskRecordVerdict reads the comment the locator chose with the fleet's contractReviewVerdictOf. FAIL, no verdict word, or no reader loaded is a refusal (not-pass) with its own line. A record carried from an older head (the regeneration and prose-only carry) stands for the arm only when the hops, diffed with the leg's git reader (--no-renames, so a rename lists both paths), moved no .changeset/ path and no path under a catalog row (the carry admits both as prose, yet the arm reads its tier from them); an unreadable diff refuses. The other Tier S legs are unchanged and still read no verdict (pm: the queue guard's Tier S record leg checks a same-head record exists but never reads its verdict #33541).

  • scripts/pm/record-recognisers.mjs. Re-exports contractReviewVerdictOf from check-half-states.mjs (already its import), so the guard's lazy loader reaches the fleet's one verdict reader. One import name and one export line.

  • AGENTS.md Prime Directive 14. The surface list drops "all Tier H"; Tier H is "every register row outside Tier S"; Tier S names .claude/** and the objectstack workflow edits and skills-only wording the register reads as S (readWorkflowDiff, lowRiskArmVerdict). The detail lives in the script header, per AGENTS.md's own rule. Net content is 13 bytes shorter; the paragraph is re-wrapped at the 120-byte budget and keeps its 16 lines (AGENTS.md 1050 to 1050).

  • pm-dispatch. SKILL.md: "路径面命中 Tier H" becomes "定档 Tier H", and the workflows line names both readings and keeps the workflow Tier H triggers (readWorkflowDiff(发布面/C2/拒读⇒H),技能 lowRiskArmVerdict,未读⇒H), within the 120-byte line budget. references/lanes/skills.md: the pre-arm check is check-governed-merges --pr N (only --pr can read the arm). The other places the card and comment 6885437809 listed were read and still hold as written: contract-review.md 16 and 51 and landing-operations.md 12 and 89 speak of Tier S and Tier H generically, execution-duties.md 17's soft cap already counts only PRs that stay Tier H, and coordinator.md names no tier.

Why

Ruling-ref: Leehom, project chat 2026-11-14, verbatim 「同意 把低风险的 Tier H 放权给座位」, then 「把低风险的 Tier H 放权给座位,立 p1 卡优化」. Measured on origin/main f068cde: GOVERNED_SURFACES put skills/** at Tier H, and the guard's merge_group leg passes a Tier H entry only on an authorized approval or enqueue (GOVERNED_APPROVERS), so a seat enqueue of a skills wording PR is refused. Comment 6885437809 measured that the ruleset requires no review (PRs merged with zero reviews), so the guard is the only stop and no settings change is owed.

Acceptance notes

Verification

Head e2b1cabc (after the at-tier reviews 6886787995, 6887175399 and 6887454202: the carried-record fix, --no-renames on its diff, then the real-git case detached from the caller's GIT_ environment; branch merged with origin/main).

Self-tests of the edited scripts
  • node scripts/pm/check-governed-merges.mjs --self-test: exit 0, 670 assertions. Battery "⚖️ the low-risk skills arm" now 54 cases (floor pinned at 54), pinning each review item: M1 (no changeset; changesets unread; another package, minor/major, or a second release line; changeset missing or annotating either declaration; no frontmatter; not readable at the head; a changed changeset the reading holds no file for; body without a bare Metadata-protocol: none; the catalog at patch in either quote or an empty frontmatter is S), S1 (unlabeled security in any case; another label removed stays S; timeline unread or not a list; --pr reads the timeline only when a catalog path is hit), S2 (trailing words; two declarations, even two no; a quoted or fenced no before a real yes, backtick and tilde fences; a lone fenced or quoted no; a closed fence before the real line stays S), S3 (fork; deleted head repo; unread fork predicate), S4 (.js, .json outside evals/, nested evals/sub/*.json, .yaml, .png, .sh are H; evals/*.json is S), plus pullChangesetReading and lowRiskRowHit.
  • node scripts/pm/check-governed-queue-guard.mjs --self-test: exit 0, 477 cases. Battery "⚖️ the low-risk skills arm at the queue" now 44 cases (floor 44), end to end through runGuard on merge_group: the PASS clear; a PASS record carried over a docs-only hop clears, while one carried over a hop that rewrote the changeset or moved a skill's CHANGELOG.md is refused, as is a carried record whose hops cannot be diffed; the carry check's diff carries --no-renames (argument pinned), and on a real temporary repository (every git call under gitFreeEnv() with -c commit.gpgsign=false; the self-test also passes with GIT_DIR set to a bogus path) a skill renamed out of the catalog into content/docs/ refuses the carried record while the plain diff names only the destination; a same-head FAIL record, a record with no verdict word, a newer FAIL behind an older PASS, and a recogniser set without the verdict reader are each refused (not-pass); **VERDICT: PASS** clears; a non-arm Tier S entry with a FAIL record still stands (unchanged, pm: the queue guard's Tier S record leg checks a same-head record exists but never reads its verdict #33541); M1, S1, S2, S3, S4 each H with a valid PASS record (changeset in another commit of the PR included; no git reader; a timeline that threw or was never read); makePullReader carries the fork predicate.
  • node scripts/pm/record-recognisers.mjs --self-test: exit 0, 383 cases.
  • Mutation checks (not kept, each restored): dropping the PASS check, ignoring the guard's timeline, ignoring the guard's fork reading, ignoring unlabeled, allowing a second declaration, allowing a fenced line, switching S4 off, allowing no changeset, skipping the carried-record check, and dropping --no-renames each turn the self-test red.
Derived gates: 47 derived, 47 run, all exit 0

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 47 families on head e2b1cabc (check:pm-record-recognisers joins for the re-export). Each ran with its exit code captured before any pipe, after building @objectstack/lint... under the verify lock (VERDICT command-exit 0). All 47 exit 0. --ran: "47 derived famil(ies) accounted for — 47 run, 0 NOT-MEASURED (a DERIVED zero)". CI families are read from this PR's check-runs, not re-run here.

Tier, governed reading, line budget
  • dispatch-gates --tier: MANDATORY contract-review tier, from .claude/skills/pm-dispatch/SKILL.md.
  • node scripts/pm/check-skill-line-ratchet.mjs: exit 0. SKILL.md stays 319 lines; its workflows line is exactly 120 bytes and the tier line says the skills arm is exclusive. AGENTS.md stays 1050 lines; PD 14's last eight lines are re-wrapped at the byte budget for "skills-only".
  • Live on head 4a459236: --pr 33394 and --pr 33482 read Tier S through the arm with the timeline, fork and changesets read (heads fetched locally).

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No schema touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (CI runs it.)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (Publishes nothing: scripts/pm/**, .claude/**, AGENTS.md; skip-changeset.)
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (Not touched.)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FEkQ5pF46HJnpBrhgVWtwV

…the catalog rows)

The skills-catalog and ui-skills-catalog register rows keep tier H for every
path-only reader and carry a low-risk reading: a diff whose only governed
paths are catalog paths, whose other paths are changesets or docs prose,
whose body declares a bare `Clause-②: no` and which carries no `security`
label reads Tier S. The queue guard reads it on the merge_group leg off the
pull object it already fetches, over every commit of the PR; `--pr` reads it
off the same GET. Everything else, and any unread input, stays Tier H.
docs/adr stays Tier H. AGENTS.md Prime Directive 14 and the pm-dispatch
landing lines name the arm.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5pF46HJnpBrhgVWtwV
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 14, 2026
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Nov 14, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 69addfe985db2a23e1754db183382010ed3a1377
Local-runs: probe — one node script calling governedTierFor and readLowRiskTier from a git show copy of this head's scripts/ tree, to measure the break cases the card names (trailing text, duplicated declarations, the Metadata-protocol: line, an unread changeset, non-wording shapes under skills/, a fork pull); no self-test, build or gate re-run
Scope: .claude/skills/pm-dispatch/SKILL.md, .claude/skills/pm-dispatch/references/lanes/skills.md, AGENTS.md + scripts/pm/check-governed-merges.mjs, scripts/pm/check-governed-queue-guard.mjs

Inputs: card #33501 (body, claim, grading comment 6885437809, os-dev-report 6886128572), PR #33531 (body, five files, net diff against main from merge base 811b976), and the check-runs on this head. The ruling quoted on the card: 「同意 把低风险的 Tier H 放权给座位」 and 「把低风险的 Tier H 放权给座位,立 p1 卡优化」. Its scope, in the coordinator's words on the card: pure docs, ADR notes and skills wording land on the seat's own contract-review PASS; anything touching a contract (spec or published types, Clause ②, narrowing) or security stays with the maintainer. Fail closed everywhere.

Check-runs on 69addfe9 at render time: 25 success, 26 skipped, 1 in progress (Test Core (1/8)), 0 failed. Governed Surface Queue Guard (pull_request leg), Lint · PM dispatch-gates, Lint · scoped · tooling self-tests, Check Changeset and PR-body guards are green. The record does not say every check is green, because one shard is still running.

① Derived judgments

  • GOVERNED_SURFACES: the skills-catalog and ui-skills-catalog rows keep tier: H and gain lowRisk (objectstack only). This is right: every path-only reader (CODEOWNERS mirror, --test, --branch, the prose gate, the relay's review plan) answers as before, as comment 6885437809 point 2 asked.
  • governedTierFor and testVerdict take an optional lowRisk reading, and a caller that passes none gets the old answer. Right.
  • New exports in the register: LOW_RISK_ARM_RULING, LOW_RISK_RIDER_PATTERNS, lowRiskArmVerdict, applyLowRiskArm, renderLowRiskLines, pullLowRiskReading. The register now imports readClause2Line from clause2-line.mjs, which is the fleet's one reader, not a second parser. Right.
  • Queue guard: decomposeGovernedWork entries gain allPaths, every path of every commit attributed to the PR, collected before the governed filter, and workflowDiff. makePullReader now returns label names. readLowRiskTier is new. Right in shape.
  • The accept set that widens: on merge_group, a PR whose only governed paths are under the catalog rows now clears on a seat enqueue plus a same-head CONTRACT_REVIEW_TIER record, with no approval. The widening is in line with the ruling for skills wording. It is wrong where the reading admits things the ruling keeps with the maintainer (M1, S1 to S4 below).

② Semver level

Nothing in the diff publishes. The changed files are scripts/pm/**, .claude/** and AGENTS.md. skip-changeset is correct, and Check Changeset is green.

Clause-②: no

No published accept set or public surface moves. The changed exports belong to unpublished fleet tooling.

③ Boundary flags

  • Open question (the docs/adr/** arm): leaving docs/adr/** at Tier H is the right call. The card body says "Do not decide it in code without the seat's word", and a path list cannot tell an ADR note from a changed decision. Option B (add-only, no Status line, nothing under a Decision heading) is the right next step, as its own card. The ADR half of the ruling is therefore still not delivered: ADR notes keep waiting on a maintainer click until that card lands. Option C, a self-declared line on the seat's record, is rightly rejected. Escalated to the seat: file the B card before pm: land low-risk governed PRs on the seat review (Tier S arm for skills wording and docs), per the 2026-11-14 delegation ruling #33501 closes on Fixes.
  • Out-of-scope finding (the relay's governedReviewPlan still summons the approvers): agreed that it is a summons, not a block. It needs a follow-up card, and the seat files it.
  • Observation, code outside the diff that this record reads but does not review: the Tier S record leg checks only that a record exists and where it came from. It never reads the verdict, and its own self-test pins that a FAIL record still clears. AGENTS.md PD 14 and this PR's header both say "a PASS verdict", but at the queue the arm actually rests on "a same-head record exists". This was true before for .claude/**, and the arm now extends it to the published catalog. Escalated to the seat to decide whether a follow-up card should make the queue leg read the verdict.
  • Dev deviation (1), the model tier: met by this record at CONTRACT_REVIEW_TIER.

④ Design judgment

M1 — must-fix. The arm reads contract declarations from the one carrier a seat can still change after the PASS, and leaves the committed carriers unread. lowRiskArmVerdict in check-governed-merges.mjs decides "touches a contract" only from the PR body's Clause-②: value:

  • (a) LOW_RISK_RIDER_PATTERNS admits any .changeset/*.md without reading it. The docblock says "a changeset (the one the published catalog owes)", but the code takes any package at any level. The probe read a skills diff plus an unread changeset as S. Under .changeset/config.json the catalog sits in one fixed train of about 120 packages, so a changeset that names another package, or grades a level above patch, is a release decision. It is not skills wording. The card lists "a .changeset that bumps a package other than @objectstack/skills" as a case that must fall back to Tier H.
  • (b) The arm never reads the Metadata-protocol: line. The probe read Clause-②: no with Metadata-protocol: semantic as S. According to its own header, check-changeset-no-major.mjs refuses added under no and narrowed without the (narrowing) arm, but not semantic. A declared change of protocol meaning is a contract change under ruling A, and on this path it lands on the seat.
  • (c) The body is not bound to the record. On merge_group the guard reads the live body, while the record pins only the head sha, and the recognisers never read a Clause-②: line from the record. A body edited from yes to no after a PASS therefore moves the PR from H to S, and the record still stands. The card's note asks exactly this: nothing a seat writes after the PASS should count without a fresh review.

Fix: in both --pr and the queue leg, read the PR's committed changesets at the head sha. The queue leg already reads committed trees for the workflow split. Require at least one changeset; each one must grade only '@objectstack/skills': patch and carry a bare no on its own Clause-② line and none on its Metadata-protocol: line. Keep the body check as an additional AND, and add a body Metadata-protocol: none check through readMetadataProtocolLine, the sibling reader in clause2-line.mjs. Pin each case in both batteries.

S1 — should-fix. Removing the security label after the record moves the PR to S. The arm reads only the labels the PR carries at queue time. Adding the label after the record correctly falls back to H. Removing it before the enqueue gives S, and no fresh review is needed for that. Fix: read the timeline, which the enqueue leg already fetches for every unapproved entry, and treat any unlabeled event for security on the PR as H.

S2 — should-fix. Clause-② lines with trailing text, or more than one Clause-② line, read S. readClause2Line takes the first line that matches and ignores the rest. The probe read all of these as S: a no followed by an em-dash and a sentence saying it narrows the spec; a no followed by a parenthesis that does not start with an arm word; a no line followed later by a yes line; a no inside a blockquote followed by a real yes; and a no inside a code fence followed by a real yes. The card names "duplicated" and "carries trailing text" as cases that must be H. Fix: without adding a second parser, run the one reader once per line, refuse more than one declared or malformed line, and require that nothing but decoration follows the value.

S3 — should-fix. A fork PR passes the arm. pullLowRiskReading and readLowRiskTier never look at the head repository. The probe gave S for a pull whose head.repo is null, which is a deleted fork. PD 14 says a fork PR is never a delivery. The arm opens the published catalog, the surface an outside contributor is most likely to send, and nothing in the record leg checks who wrote the record. Fix: makePullReader and pullLowRiskReading should take head.repo.full_name and base.repo.full_name from the pull object they already fetch, and a fork, or an unread head repo, should be H. forkVerdict already exists in the register.

S4 — should-fix. Inside the arm rows, anything under skills/ counts as wording. The rider list is an allowlist of path shapes, but the arm rows are matched by prefix alone. The probe read skills/objectstack-data/src/x.ts and skills/package.json as S. The card names "any src/". Skills shipped by npx skills add can carry scripts that a customer's agent runs, and the first PR that adds one would land on the seat. Fix: allow only .md files and evals/*.json under the arm rows, which covers every file under skills/ on main today. Anything else should be H.

N1 — nit. PD 14 names lowRiskArmVerdict but does not say that the arm reads Clause-②, labels and paths, and does not cite the ruling. Card item 3 asked for the ruling to be cited. The phrase "objectstack workflow edits ... and skills wording" also leaves it ambiguous whether "objectstack" covers the skills arm, though the code scopes it to objectstack. This is prose only, so it can be fixed on AGENTS.md or left as an accepted deviation.

N2 — nit. SKILL.md line 202 drops the H triggers for workflows (published path, C2 trigger, refused read) and keeps only "未读 ⇒ H", pointing to the script header. That is acceptable under the pointer rule, but a seat reading only the skill now sees less of the rule than before.

Holds, read against the code:

  • The guard takes the body and labels from the pull object it already fetches, with no read of its own.
  • An unreadable pull, unread labels, an empty body, a missing Clause-②: line, yes, (narrowing) and (widening) are all H.
  • A record on an older head is refused.
  • Each other governed row beside the catalog is H: docs/adr/**, docs/NORTH-STAR.md, .claude/**, AGENTS.md, and a split workflow row, because the arm runs after applyWorkflowSplit.
  • packages/**, ui/packages/**, gate scripts, lockfiles and non-Markdown docs are H.
  • A published path in another commit of the same PR counts through allPaths.
  • The pull_request leg never reddens.
  • The CODEOWNERS mirror and every path-only caller answer exactly as before.
  • check:pm-governed-prose agrees with PD 14 on the surface list (green in CI). It deliberately does not parse tier words, so the tier sentence is judged here: it does not go beyond the ruling. It is narrower, since it covers skills wording only and leaves docs/ADR out.
  • The post-merge sweep still lists every catalog merge, as it does every .claude/** merge. Card item 4 is met to the same standard: in both cases a merge without a PASS record is a finding for the human audit.

Implemented-by: claude/project-thread-dhp24e
Reviewed-by: session_01FEkQ5pjSPT7ui2EUcunaVb

VERDICT: FAIL

Adopted by the seat 2026-11-14T23:59Z · isolated sub-agent at CONTRACT_REVIEW_TIER · Ruling-ref: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb, option A, tapped 2026-11-14T15:37Z

…and a PASS record

The arm now holds a skills PR at Tier H unless its body carries exactly one bare
`Clause-②: no` and `Metadata-protocol: none`, every changeset read at the head
releases only `'@objectstack/skills': patch` with the same two lines, the
timeline never removed a `security` label, the head is no fork, and every file
under a catalog row is Markdown or an `evals/*.json`. At the queue the arm clears
only on a same-head record that reads PASS, read with the fleet's verdict reader
(re-exported by the recognisers). SKILL.md names the workflow Tier H triggers again.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5pF46HJnpBrhgVWtwV
@github-actions github-actions Bot added size/l and removed size/m labels Nov 15, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Correction: contract-review tier

The contract-review record(s) 6886243839 on this PR say Served-tier: CONTRACT_REVIEW_TIER, and the seat adopted them as "isolated sub-agent at CONTRACT_REVIEW_TIER". That is wrong. The seat launched the reviewing sub-agent without the tier, so it ran at the seat's own default tier, not at CONTRACT_REVIEW_TIER. The seat found this on its own audit. The review was still isolated and adversarial, and its findings stand as an off-tier review. They are not the at-tier record AGENTS.md and the in-seat review ruling require.

The seat re-runs the review at CONTRACT_REVIEW_TIER on this PR's head and posts that record here. Until it does, this PR has no at-tier record.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 4a459236f421a0add59431b816de104de054eb89
Local-runs: none
Scope: .claude/skills/pm-dispatch/SKILL.md, .claude/skills/pm-dispatch/references/lanes/skills.md, AGENTS.md + scripts/pm/check-governed-merges.mjs, scripts/pm/check-governed-queue-guard.mjs, scripts/pm/record-recognisers.mjs

Inputs: card #33501 (body, claim 6885432460, grading comment 6885437809, os-dev-report 6886128572), PR #33531 (the r2 body as the dev's file reads, six files, net diff against main from merge base e3f0cfd), and the check-runs on this head. This is a full review of the reworked head. The earlier record 6886243839 is not used as evidence; ③ says which of its findings this review confirms as fixed.

The ruling quoted on the card: 「同意 把低风险的 Tier H 放权给座位」 and 「把低风险的 Tier H 放权给座位,立 p1 卡优化」. Its scope, in the coordinator's words on the card: pure docs, ADR notes and skills wording land on the seat's own contract-review PASS; anything touching a contract (spec or published types, Clause ②, narrowing) or security stays with the maintainer.

Check-runs on 4a459236 at render time: 22 success, 22 skipped, 2 in progress (Test Core (1/8), Type Check · consumer gates), 0 failed. Every Lint job is green, among them Lint · PM dispatch-gates, Lint · scoped · tooling self-tests, Lint · repo & tooling gates and Lint · CI, release & engine gates; Governed Surface Queue Guard (pull_request leg) and PR-body guards are green; Check Changeset is an expected skip under skip-changeset. This record does not say every check is green: two shards were still running, neither of them a gate over the files in scope.

① Derived judgments

  • GOVERNED_SURFACES: the skills-catalog and ui-skills-catalog rows keep tier: H and gain lowRisk, scoped to objectstack. Right. Every path-only reader (--test, --branch, the CODEOWNERS mirror, the prose gate, the relay's review plan, the post-merge sweep) answers as before, which is what comment 6885437809 point 2 asked for.
  • governedTierFor and testVerdict take one more optional argument. A caller that hands no reading gets Tier H on a catalog path. Right, and it is the fail-closed default.
  • New register exports: LOW_RISK_ARM_RULING, LOW_RISK_RIDER_PATTERNS, LOW_RISK_ARM_FILE_PATTERNS, LOW_RISK_CHANGESET_PATH, LOW_RISK_CHANGESET_RELEASE, readBareDeclaration, lowRiskChangesetReasons, lowRiskArmVerdict, applyLowRiskArm, renderLowRiskLines, pullLowRiskReading, lowRiskRowHit, fetchTimelineOver, pullChangesetReading. The declarations are read with readClause2Line and readMetadataProtocolLine from clause2-line.mjs, run once per line. Right: no second parser.
  • Queue guard: decomposeGovernedWork entries gain allPaths, changesetRefs and workflowDiff; makePullReader returns label names and forkVerdict; new readLowRiskTier, landsOnLowRiskArm, lowRiskRecordVerdict; a new record state not-pass, which every clearing predicate (state === 'stands') refuses. Right.
  • record-recognisers.mjs re-exports contractReviewVerdictOf, which it already imported. Right: one verdict reader, reached by the guard's lazy loader as verdictOf.
  • The accept set that widens: on merge_group, a PR whose only governed paths are catalog paths clears on a seat enqueue with zero approvals when the arm reads Tier S and the same-head record reads PASS. That is the ruling's skills-wording arm and nothing wider, with the one gap in SF1. Nothing narrows for any other entry: the other Tier S legs and every Tier H leg read as on main.

② Semver level

Nothing in the diff publishes: scripts/pm/**, .claude/** and AGENTS.md. skip-changeset is correct.

Clause-②: no

No published accept set or public surface moves; the changed exports belong to unpublished fleet tooling.

③ Boundary flags

  • Open question, the docs/adr/** arm: leaving it Tier H is the right call under the ruling. The card says not to decide it in code without the seat's word, a path cannot tell a note from a changed decision, and fail closed is the ruling's own default. The ADR half of the ruling is therefore not delivered by this PR; pm: let a seat land an add-only ADR note under the low-risk Tier H ruling (docs/adr/** stays Tier H until then) #33539 (open) carries it, so Fixes #33501 does not lose it. Option C, a self-declared line on the seat's record, stays rejected.
  • Out-of-scope finding, the relay's governedReviewPlan: a summons, not a block. pm: the relay's governedReviewPlan still summons the approvers on a low-risk Tier H PR the seat may land #33540 (open) carries it. Agreed.
  • The other Tier S legs still read no verdict: pm: the queue guard's Tier S record leg checks a same-head record exists but never reads its verdict #33541 (open) carries it. Agreed that it is outside this card.
  • Dev deviation (1), the model tier: met by this record.
  • Findings of record 6886243839, judged on this tree:
    • M1 (a), changesets unread: fixed. Both --pr and the queue leg read each changed changeset from a committed tree; at least one is required; the frontmatter may hold only the catalog at patch; an unread, deleted or frontmatter-less changeset is H.
    • M1 (b), Metadata-protocol: unread: fixed, on the body and on each changeset.
    • M1 (c), the body not bound to the record: fixed in part. The body is now a redundant AND over the committed changeset, so a body edit alone moves nothing from H to S. The committed carrier is itself not bound to the record: see SF1.
    • S1, a removed security label: fixed. The guard keeps the timeline the enqueue leg already read (every page or it throws); --pr reads every page or says NOT MEASURED.
    • S2, trailing text and duplicated declarations: fixed. Exactly one declaration line, outside a fence and a quote, equal to the bare spelling after decoration is stripped.
    • S3, a fork head: fixed, through forkVerdict on the pull object both readers already fetch; a deleted head repo or an unread predicate is H.
    • S4, any file under the catalog rows: fixed. Only .md and evals/*.json, which covers all 97 files under skills/ on this head.
    • Its observation that the record leg read no verdict: fixed for the arm (lowRiskRecordVerdict), newest same-head record governs.
    • N1 not taken, with a reason that holds (AGENTS.md takes no ruling quotation; the conditions live in the script header PD 14 names). Accepted deviation. N2 fixed: the SKILL.md line keeps the workflow triggers.
  • Escalated to the seat, not a finding on this diff: the arm's only security reading is the security label, as the card specifies, and no workflow applies that label by path. A skills PR that rewrites security teaching (for example skills/objectstack-data/rules/security.md, the file docs(skills): the security rules teach a position rename through renamedFrom #33480 changed) reads Tier S unless someone hangs the label. Whether the ruling's "security stays with the maintainer" means the label or the content is the seat's question to put to the maintainer; until it is answered the at-tier reviewer of such a PR should hang the label.

④ Design judgment

SF1 — should-fix. On the arm, a carried record lets the declarations and catalog files move after the PASS. The M1 fix makes the committed changeset the carrier of Clause-②: no, Metadata-protocol: none and the release line. The queue guard's record leg still accepts a record on an OLDER head when the head moved only on prose faces outside the review surface (regenCarry and isOutsideReviewSurface in record-recognisers.mjs: .changeset/**, content/docs/**, apps/docs/**, and any CHANGELOG.md at any depth), pointed at by a Regen-provenance: comment a seat writes. lowRiskRecordVerdict reads the verdict of that carried record and does not look at carriedHops. Two consequences, both read off the code, neither pinned by a self-test:

  • (a) Head A carries Clause-②: yes (or a non-none protocol kind, or a release line beyond the catalog at patch) on the body and the changeset, so it is Tier H, and the record on A is written against that declaration. The seat then rewrites the changeset to the clean spelling at head B, edits the body to match, and posts the provenance line. At the queue the arm reads the body and the changeset at B, answers Tier S, the record carries, reads PASS, and the seat's enqueue clears. Both declaration carriers were written after the PASS and no review saw them. This is the card note's "an edited body after the record" case, and it does not fall back to Tier H.
  • (b) skills/**/CHANGELOG.md is a .md under a catalog row, so the arm admits it, and it is also on the carry's excluded list by basename, so catalog content added or changed after the PASS lands on the carried record.
    The exposure is bounded: the path allowlist and the release line are still read at the queued commit, so no published source, schema or package bump can ride through. What is lost is the tripwire the ruling names (Clause ②) and the same-head guarantee for one file name under the catalog. Fix, in lowRiskRecordVerdict or beside it and through the readers already imported: on an arm entry refuse a carried record whose hops moved any .changeset/ path or any path under a catalog row (or refuse every carried record on the arm, which costs a fresh review after a merge-forward), and pin both cases in the queue battery. Reading the record's own Clause-② line with readClause2Line and requiring bare no is a second, independent way to bind (a).

N1 — nit. PD 14 and SKILL.md line 202 name the arm beside .claude/** under Tier S, and neither says the arm is exclusive: skills wording beside a .claude/** path is Tier H in the code (a governed path outside the arm), where a reader of the prose would expect S plus S. The error direction is safe, since --pr answers H. Prose only; fix on the prose faces or leave as an accepted deviation.

N2 — nit. The ui-skills-catalog row carries the arm, but the arm's one allowed release line is '@objectstack/skills': patch in a root .changeset/*.md, and ui/.changeset/** is not a rider. No ui/skills/ tree exists on this head, so nothing is reachable today; when one appears the arm either never fires or fires on a changeset that names the wrong package. A follow-up card when ui/skills/** gets its first file.

N3 — nit. The post-merge sweep classifies by path alone, so a seat-landed arm merge is listed as a Tier H row with no approval, and the auditor has to re-read the arm by hand to tell it from a violation. Card item 4 is met at parity with .claude/** (the merge is on the list, and a missing PASS record is the human audit's finding), no better. Follow-up card material.

N4 — nit. Comments in both scripts cite 6886243839 as "contract review"; that record was served off-tier and is being replaced. The pointers describe history accurately, but they should name the record that stands. Accepted deviation on this head, since a comment edit moves it.

Holds, read against the code and each tried as a break:

  • Published source or any path outside the allowlist: packages/**, ui/packages/**, a gate script, a lockfile, .changeset/config.json, a nested .changeset/a/b.md, a non-Markdown file under docs/ or content/docs/, ui/docs/** are each H. The list is an allowlist of shapes, so the next published tree is H without an edit.
  • A rename out of published source into skills/ is H: --pr reads previous_filename, and the guard's rows are diff-tree --no-renames, so both paths reach the allowlist. A published path in another commit of the same PR counts through allPaths, collected before the governed filter.
  • Under the catalog rows: .js, .mjs, .sh, .yaml, .png, package.json, and evals/sub/*.json are H.
  • The body: no line, yes, any arm, no with trailing words, two declaration lines (two no included), a quoted or fenced no beside a real yes, a lone quoted or fenced no, a missing or non-none Metadata-protocol: line, and a body that is not a string are each H. A hidden declaration cannot be undercounted: every line the fleet reader reads as declared or malformed is a hit, fenced or not, and the final test is equality with the bare spelling.
  • The changeset: none, another package, the catalog above patch, a second release line, an indented or commented frontmatter line, no frontmatter, a deleted or unreadable one, a second changeset the reading holds no file for, and a missing or annotated declaration are each H. The frontmatter bounds agree with the changesets parser's own.
  • Labels and timeline: a security label in any case, labels not read, an unlabeled event for it, a timeline that threw, was cut, or was never read are each H. A label added after the record is read live at the queue and is H.
  • A fork head, a deleted head repository and an unread fork predicate are H. An unreadable pull is NOT MEASURED and H.
  • Any other governed row beside the catalog is H: docs/adr/**, docs/NORTH-STAR.md, AGENTS.md, .claude/**, and a split workflow row, because the arm runs after applyWorkflowSplit.
  • A record on an older head with no provenance line is refused; a same-head FAIL, a record with no verdict word, a newer FAIL behind an older PASS and a missing verdict reader are each refused as not-pass.
  • The guard reads the body, labels and fork predicate from the pull object it already fetches and the timeline from the read the enqueue leg already makes; it adds no API call of its own. The merge_group job checks out with full history and blobs, so git show of a changeset at its commit reads in production. The pull_request leg says Tier H, NOT MEASURED, and never reddens.
  • check:pm-governed-prose is green in lint.yml on this head and PD 14's surface list is unchanged. The gate does not parse tier words, so the tier sentence is judged here: "every register row outside Tier S" and "skills wording (lowRiskArmVerdict) read as S" do not widen beyond the ruling. They are narrower: docs prose was never governed and ADR notes stay H.
  • SKILL.md: the two edited lines agree with the register on this head (the tier is a reading, not a path fact; both readers named, the workflow H triggers kept, unread is H), within the line budget. references/lanes/skills.md: --pr N is the right pre-arm command, since --test and --branch can never read the arm; it names the fetch that cures an unread head. The other pm-dispatch lines the card listed speak of the tiers generically and still hold.
  • The r2 body's account matches the tree where checked: battery floors 54 and 38, AGENTS.md 1050 lines on both sides, SKILL.md 319 lines, the three follow-up cards exist and are open.

Implemented-by: claude/project-thread-dhp24e
Reviewed-by: session_01FEkQ5pjSPT7ui2EUcunaVb

VERDICT: PASS

Adopted by the seat 2026-11-15T00:43Z · isolated sub-agent at CONTRACT_REVIEW_TIER (served model verified by the seat) · Ruling-ref: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb, option A, tapped 2026-11-14T15:37Z

…r catalog path moved

The regeneration and prose-only carry admits `.changeset/**` and catalog prose
such as a skill's CHANGELOG.md, yet those are what the arm reads its tier from.
For an arm entry, a record carried from an older head now stands only when the
hops (diffed with the leg's git reader) moved no changeset and no path under a
catalog row; an unreadable diff refuses. PD 14 and SKILL.md say the skills arm
is exclusive (skills-only wording), and the code comments drop the off-tier
review citation.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5pF46HJnpBrhgVWtwV
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 98d301976e04e0619557a292c90304ae1c627e1c
Local-runs: probe — one git diff -z --name-only with and without --no-renames on a two-commit throwaway repository in the scratch folder, to measure how git lists a rename out of skills/ (finding SF2); nothing of this repository was built, run or re-run
Scope: .claude/skills/pm-dispatch/SKILL.md, .claude/skills/pm-dispatch/references/lanes/skills.md, AGENTS.md + scripts/pm/check-governed-merges.mjs, scripts/pm/check-governed-queue-guard.mjs, scripts/pm/record-recognisers.mjs

This is a delta review at tier. Base: head 4a459236, which holds the at-tier record 6886787995 (PASS, with SF1 and nits N1 to N4). Between the two heads the branch merged main (merge 3ec8102e, merge base now 6e23180f) and added one commit, 98d30197. The PR's net diff against its merge base was compared at both heads: it differs by exactly the 79 changed lines of that commit (59 added, 20 removed, four files), and the merge from main touched none of the six PR files. Everything record 6886787995 judged outside those lines is byte-identical, so its judgments carry.

Inputs: card #33501 (body and all three comments), PR #33531 (body, six files, the net diff at both heads, the commit 98d30197), the records 6886243839 and 6886787995 and the correction 6886699168 on the PR thread, and the check-runs on this head.

Check-runs on 98d30197 at render time: 18 success, 0 failed, 5 in progress (Lint · PM dispatch-gates, Lint · CI, release & engine gates, Test Core (1/8), Type Check · consumer gates, Type Check · debt ledger), the rest expected skips. Green among them: Lint · scoped · tooling self-tests (the guard self-test and its battery floors), Lint · repo & tooling gates, Lint · docs & code guards, Lint · ESLint, Governed Surface Queue Guard (pull_request leg), PR-body guards. This record does not say every check is green: five jobs were still running, Lint · PM dispatch-gates among them, and the seat reads them before it acts.

① Derived judgments

  • lowRiskRecordVerdict gains one refusal: for an arm entry, a record with carriedFrom set is not-pass when the leg has no git reader or no head sha, when the diff throws, or when the diff from the reviewed head to the PR head names a .changeset/ path or a path under a lowRisk row's prefix (skills/, ui/skills/, read off GOVERNED_SURFACES, no second list). Right in direction: the accept set only narrows, and only for arm entries with a carried record. A same-head record and every non-arm entry read as on the base head.
  • Production wiring, read in runGuard: pair carries runGit (the git helper, execFileSync, which throws on a non-zero exit) and headSha (the PR head off the pull object), so the new branch is live at the queue and a failed diff is a throw, never an empty answer. Right.
  • No export is added or removed. The battery floor moves from 38 to 42, matching the four new assertions.
  • The accept set that is still too wide: a rename out of the catalog, see SF2.

② Semver level

Nothing in the diff publishes: scripts/pm/**, .claude/** and AGENTS.md. skip-changeset is on the PR and is correct.

Clause-②: no

No published accept set or public surface moves; the changed code is unpublished fleet tooling.

③ Boundary flags

  • SF1 of record 6886787995: fixed for every case it named, with one hole left (SF2). A hop that rewrites the changeset is refused, so the declarations and the release line can no longer be rewritten after the PASS and ride a carried record; a hop that adds or edits a catalog file, a skill's CHANGELOG.md included, is refused; no git reader, no head sha and an unreadable diff each refuse as not-pass.
  • N1 of that record: fixed. PD 14 says "skills-only wording" and SKILL.md line 202 says the skills arm is exclusive.
  • N4 of that record: fixed. No comment in the three scripts cites 6886243839 any more.
  • N2 and N3 of that record are unchanged on this head and stay follow-up material.
  • The docs/adr/** question: unchanged. Leaving it Tier H is the right call under the ruling, and pm: let a seat land an add-only ADR note under the low-risk Tier H ruling (docs/adr/** stays Tier H until then) #33539 carries the ADR half.
  • The open security-scope question (which skills paths count as security-touching) is with the maintainer and is not decided here. This diff contradicts neither option: it reads no new path as security-touching and removes no label reading. One note for whoever implements the answer: SF2's rename works on any catalog .md, a rules/security.md included, so a path-based answer needs SF2 closed to hold on a carried record.
  • The dev's gate report (47 derived gates, guard self-test 475, queue battery 42) is the dev's account. The platform verdict that covers the self-tests, Lint · scoped · tooling self-tests, is green on this head.

④ Design judgment

SF2 — should-fix. A rename OUT of a catalog row is invisible to the new check, so a catalog file can leave the published catalog after the PASS on a carried record. lowRiskRecordVerdict in scripts/pm/check-governed-queue-guard.mjs reads the hops with git diff -z --name-only FROM TO. git diff detects renames by default (diff.renames is unset in the queue checkout and in this repository's workflows), and with --name-only a rename is listed by its destination alone. Measured on git 2.43 in a throwaway repository: moving skills/a/rules/security.md to content/docs/security.md lists only content/docs/security.md; with --no-renames both paths are listed. The chain, each step read in the code:

  • The carry admits the hop. unexplainedPathsBetween in record-recognisers.mjs (on main, read not reviewed) uses the same diff, sees only the content/docs/ destination, and that is a prose face.
  • The new check sees only the destination, which is under no catalog prefix and not under .changeset/, so the record stands and reads PASS.
  • The arm still reads Tier S: the guard's own rows are diff-tree --no-renames, so allPaths holds both paths, and both are on the allowlist (.md under a catalog row, Markdown under content/docs/). The changeset is untouched.
    So after a PASS on head A, a seat can move any skill file (a SKILL.md, a rules file, an eval) out of the catalog into content/docs/, post a Regen-provenance: line, and enqueue on the carried record. Git pairs files at 50 percent similarity, so the destination may also be rewritten. The docblock's sentence "may touch no path under a catalog row" is not true for this case. The exposure is bounded: nothing new enters the catalog, a rename INTO it or within it is listed by its destination and refused, and a changeset renamed away is caught by the arm itself (a deleted changeset reads as unreadable at its commit, Tier H). What is lost is the same-head guarantee for removals from the published catalog, which is the thing SF1 was fixed to restore. Fix: add --no-renames to that diff, as the guard's row enumeration two functions away already does, and pin the flag in the queue battery (the fake git reader destructures the argument positions, so it needs the same edit). The carry's own diff in record-recognisers.mjs has the same blind spot for any reviewed file renamed onto a prose face; that code is outside this diff and is follow-up card material.

N5 — nit. The check diffs the two trees whole, so a merge-forward from main that brings any changeset or any skills edit refuses the carried record on the arm even when the PR's own delta did not move. The merge on this very branch brought ten changesets. The direction is safe and record 6886787995 named refusing every carried record as an acceptable fix, but the PR body's "carried over a docs-only hop clears" will rarely hold after a sync, and the cost is a fresh at-tier review per sync. A follow-up could subtract what the base brought, as unexplainedPathsBetween does with the PR's own delta at each head.

N6 — nit. The four new cases drive a fake git reader that answers one fixed list, so they pin the argument order and the path filter but cannot measure what git itself lists. That is why SF2 passed the battery. The pin SF2 asks for is on the flag.

N7 — nit. Dropping the citations left three docblock lines unwrapped or short (the readLowRiskTier docblock line that now runs past the block's width, "must also READ PASS, read off", "may change: skill"). No effect on meaning.

Holds, each read against the code and tried as a break:

  • A hop through a merge commit that brings a changeset from main: the tree diff lists it and the record is refused (N5 is the cost side of this).
  • A carriedFrom sha that is not an ancestor of the head: the check is a two-tree diff, not a range, so ancestry does not matter. An abbreviated or unknown sha that git cannot resolve throws and refuses.
  • Several hops: carriedFrom is the head the record judged (the first hop's start, expanded with rev-parse), so one diff spans the whole chain.
  • Path quoting: -z and a NUL split, so a space, a quote or a newline in a path cannot hide it. An empty list is not mistaken for a failure, since a failure is a throw.
  • A rename into the catalog, within it, or into .changeset/ is listed by its destination and refused.
  • The branch runs only for a record that stands and is carried; a same-head record skips it and goes to the verdict read, which is unchanged.
  • The four new cases and the floor: a docs-only hop clears with carriedHops 1 and verdict PASS (control); a hop that rewrote the changeset is refused with exit EXIT_REFUSED_UNAPPROVED and the path in the rendered words; a hop that moved a skill's CHANGELOG.md is refused; a diff that throws and a pair with no git reader are both not-pass, with a clean diff beside them as the control. Floor 42 equals 38 plus these four.
  • PD 14: the only content change is "skills wording" to "skills-only wording"; the other seven lines are the same words re-wrapped, each within 120 bytes, and AGENTS.md stays 1050 lines. The sentence does not widen beyond the ruling. Lint · repo & tooling gates and Lint · docs & code guards are green on this head.
  • SKILL.md line 202 is 119 bytes and line 203 is 120; the file stays 319 lines. The new words agree with the register: skills wording beside a .claude/** path is Tier H.
  • scripts/pm/check-governed-merges.mjs: two comment lines changed, no code.
  • No regression found in the rest of the scope: outside the 79 lines the PR's net diff is identical at both heads.

Implemented-by: claude/project-thread-dhp24e
Reviewed-by: session_01FEkQ5pjSPT7ui2EUcunaVb

VERDICT: PASS

Adopted by the seat 2026-11-15T01:16Z · isolated sub-agent at CONTRACT_REVIEW_TIER (served model verified by the seat) · Ruling-ref: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb, option A, tapped 2026-11-14T15:37Z

`git diff --name-only` names a rename by its destination alone, so a hop that
moved a skill out of the catalog into the docs tree cleared on a carried record.
The check now diffs with `--no-renames`; the self-test pins the argument and
runs the rename on a real repository.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5pF46HJnpBrhgVWtwV
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 087d8be30986dd80b680450cb86c99ac301a26dd
Local-runs: none
Scope: .claude/skills/pm-dispatch/SKILL.md, .claude/skills/pm-dispatch/references/lanes/skills.md, AGENTS.md + scripts/pm/check-governed-merges.mjs, scripts/pm/check-governed-queue-guard.mjs, scripts/pm/record-recognisers.mjs

This is a delta review at tier. Base: head 98d30197, which holds the at-tier record 6887175399 (PASS, with SF2 and nits N5 to N7). Between the two heads the branch added one commit, 6e6e3fe2, and merged main (merge 087d8be3, merge base now a9da5474). The PR's net diff against its merge base was compared at both heads: it differs by exactly the 61 changed lines of that commit (48 added, 13 removed, one file, scripts/pm/check-governed-queue-guard.mjs). The merge brought 58 paths from main (changesets, content/docs/permissions/**, packages/**, ui/**) and touched none of the six PR files. Everything record 6887175399 judged outside those 61 lines is byte-identical, so its judgments carry.

Inputs: card #33501 (body and comments), PR #33531 (body, six files, the net diff at both heads, the commit 6e6e3fe2), the records 6886243839, 6886787995 and 6887175399 and the correction 6886699168 on the PR thread, and the check-runs on this head. Read and not reviewed: scripts/git-env.mjs and the sibling fixtures in scripts/pm/check-governed-merges.mjs, to judge the new real-git case.

Check-runs on 087d8be3 at render time: 11 success, 0 failed, 12 in progress, the rest expected skips. Green: Governed Surface Queue Guard (pull_request leg, whose first step is this guard's --self-test, so the two new cases and the floor of 44 ran green on a runner), Lint · scoped · tooling self-tests, PR-body guards, Check PR Size, Dogfood Regression Gate, Test UI, Plan Test Core shards. Still running: Lint · PM dispatch-gates, Lint · repo & tooling gates, Lint · docs & code guards, Lint · ESLint, Lint · CI, release & engine gates, Lint · scoped · slot lookup & corpus, Test Core (1/8) and (2/8), and the four Type Check jobs. This record does not say every check is green; the seat reads the twelve before it acts.

① Derived judgments

  • lowRiskRecordVerdict now diffs the hops with git diff -z --name-only --no-renames. The list it reads can only grow (a rename now names its source as well as its destination), so the accept set only narrows, and only for an arm entry whose record is carried. Right.
  • No export is added or removed. New imports are mkdirSync, mkdtempSync, rmSync, writeFileSync and tmpdir, used by the self-test alone.
  • The queue battery floor moves from 42 to 44, matching the two new assertions; the guard total moves from 475 to 477 by the same two.
  • The rest of the commit is comment re-wrapping with no change of words apart from the docblock sentence that now names the flag.

② Semver level

Nothing in the diff publishes: scripts/pm/**, .claude/** and AGENTS.md. skip-changeset is on the PR and is correct.

Clause-②: no

No published accept set or public surface moves; the changed code is unpublished fleet tooling.

③ Boundary flags

④ Design judgment

SF2 — fixed. Read in the tree at this head:

  • The call is pair.runGit(['diff', '-z', '--name-only', '--no-renames', record.carriedFrom, pair.headSha]). --no-renames on the command line outranks diff.renames in any config, and -z makes core.quotePath irrelevant, so the production read does not depend on the runner's git config.
  • The chain of the last record now closes: a hop that moves skills/a/rules/security.md to content/docs/security.md lists the catalog path as a delete, the prefix filter catches it, and the carried record is not-pass with the path in the reason. Renames into the catalog, within it, and of a changeset were already listed and still are.
  • The argument pin: the fake reader records every call carrying the flag and the assertion requires at least one and every one to equal diff -z --name-only --no-renames OLD HEAD exactly. Dropping the flag leaves the list empty and reddens; reordering or adding an argument reddens. The fake strips the flag before handing the call to the positional fixture, so the four older carry cases still run through the same reader.
  • The real-git case: a temporary repository, one commit holding skills/a/rules/security.md, one commit after git mv to content/docs/security.md. It asserts three things: the plain diff with -c diff.renames=true names the destination alone (the blind spot is shown, not assumed, and does not depend on the user's diff.renames), lowRiskRecordVerdict with real git as runGit returns not-pass, and the reason names skills/a/rules/security.md. The file is twenty identical lines moved unchanged, so the rename is detected at any threshold. This answers N6 for the rename case.
  • Other name-only reads in the scope: the guard's own row enumeration (diff-tree ... --no-renames, two places) and the register's --branch and sweep reads already carry the flag. The one left without it is unexplainedPathsBetween in scripts/pm/record-recognisers.mjs, which this PR does not touch (it adds one import name and one export line there) and which is pm: the carried-record regeneration test lists a renamed path by its destination only, so a hop that moves a reviewed file out of its face is not seen #33578.

SF3 — should-fix. The real-git case is not hermetic against the caller's git environment, and the repository states a rule for exactly this. scripts/git-env.mjs (on main, read not reviewed) records the measured incident and exports the rule as GIT_ENV_ISOLATION_RULE: a git child that operates on a throwaway repository must be spawned with a GIT_-stripped environment (gitFreeEnv()), because GIT_DIR, GIT_WORK_TREE and GIT_INDEX_FILE outrank cwd. The new helper is execFileSync('git', args, { cwd: dir, ... }) with no env, and it runs init, config user.email, config user.name, add -A, commit and mv. Run where a parent git exports its location (a hook, a rebase --exec), those land on the real checkout: add -A stages the tree as deleted, commit commits it, and the two config writes go to the shared config. Around twenty scripts under scripts/ already use gitFreeEnv() for the same fixture shape. Two smaller holes in the same helper:

  • It does not pass commit.gpgsign=false. The sibling fixtures in scripts/pm/check-governed-merges.mjs do. On the fleet's own containers the global config sets commit.gpgsign true with an ssh signing program, so the two fixture commits call that program; where it cannot sign, the commit throws.
  • A throw during setup happens while the assertion's argument is evaluated, so it escapes as an uncaught error of the whole self-test instead of one named failing case. It is loud and red, never a false pass.
    Why should-fix and not must-fix: CI is unaffected (a runner exports no git location and signs nothing; Governed Surface Queue Guard ran this case green on this head), the self-test is not wired into .githooks, the temporary directory is removed in finally, and the paths are ASCII. The contract under review (the arm, the carry check) is correct. Why not a nit: this step is the first step of a required merge gate and a derived gate agents run by hand, and the failure mode the rule names is destructive and silent. Fix: spawn the helper with env: gitFreeEnv() (scripts/git-env.mjs is inside the guard job's sparse checkout of scripts) and pass -c commit.gpgsign=false, as the sibling does. The same strip is missing from the older fixtures in scripts/pm/check-governed-merges.mjs; those lines are outside this delta and are follow-up material.

N7 — nit, partly taken. As in ③: "may change: skill" is still a short line, and one new docblock line is 88 columns. No effect on meaning.

N8 — nit. armDiffArgs is declared after the closure that pushes to it. It works because the first call comes later; declaring it first would read better.

Holds, each read against the code and tried as a break:

  • A git that cannot resolve carriedFrom still throws and refuses; the flag changes nothing there.
  • A hop that renames a file inside content/docs/ or docs/ now lists two prose paths instead of one; neither is a changeset or a catalog path, so a docs-only hop still clears (the control case is green).
  • A copy out of the catalog was never hidden: --name-only without -C lists a copy as an add, and the source is unchanged.
  • The real-git case cannot pass for the wrong reason: the reason string is matched on the catalog path, which only a --no-renames list contains.
  • Regression: outside the 61 lines the PR's net diff is identical at both heads; AGENTS.md, both pm-dispatch files, scripts/pm/check-governed-merges.mjs and scripts/pm/record-recognisers.mjs are byte-identical to the base head, so PD 14, the 120-byte lines and the 1050 and 319 line counts stand as judged.

Implemented-by: claude/project-thread-dhp24e
Reviewed-by: session_01FEkQ5pjSPT7ui2EUcunaVb

VERDICT: PASS

Adopted by the seat 2026-11-15T01:41Z · isolated sub-agent at CONTRACT_REVIEW_TIER (served model verified by the seat) · Ruling-ref: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb, option A, tapped 2026-11-14T15:37Z

…the caller's GIT_ env

The temporary-repository case now runs every git call with `gitFreeEnv()` and
`-c commit.gpgsign=false`, as the sibling fixtures do, so an inherited GIT_DIR
or GIT_INDEX_FILE never reaches the real checkout. Docblock lines rewrapped.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5pF46HJnpBrhgVWtwV

This branch has not been deployed

No deployments
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/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pm: land low-risk governed PRs on the seat review (Tier S arm for skills wording and docs), per the 2026-11-14 delegation ruling

1 participant