Skip to content

Commit 041a120

Browse files
os-elon-muskclaude
andauthored
feat(pm): the queue guard refuses a project-thread Tier S record reviewed from the PR's own account (#25436)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DCP4SZZMWf3q3mM4d5VgXFh)_ Fixes #25415 ## What changes - `scripts/pm/record-recognisers.mjs`: two new readers. `readReviewedByAccount` reads a `Reviewed-by-account:` key line (in the file's one key-line convention), and it accepts exactly one GitHub login. A leading `@` is allowed; a bot login, prose, or two logins read `unreadable`. `readAttributionLogin` reads the `claude-projects-attribution` `github_login` from **any** line of a PR body. A marker with no readable login reads `unreadable`, never `absent`. `locateReviewOfRecord` now also returns `account`, read off the same comment it chose. - `scripts/pm/check-governed-queue-guard.mjs`: the Tier S record leg reads the attribution from the PR body it already fetches (no new API call). A new pure function, `accountIndependence`, does the comparison. When the body carries the attribution line, a record that otherwise stands becomes `not-independent` (refused, exit 3) if the reviewer account equals the attribution login (case-insensitive), or if the account line is missing or unreadable, or if the attribution itself is unreadable. The refusal names both logins and the rule. The clear prints both logins it compared. A body **without** the line (Routine seats, humans) is `not-owed`, so today's reading is unchanged. The approval limb is untouched, so the leg stays monotone. - Self-tests: there is a new guard battery (10 cases; floor 27 → 28) and a new recogniser battery (7 cases; floor 12 → 13). Guard: 344 cases pass. Recognisers: 208 cases pass. A mutation that turns the check off reds all 10 guard cases. ## Key name This PR uses `Reviewed-by-account:`, as the card says. The paired skills card #25414 (draft PR #25422) writes the rule in prose ("记录独立性段并书两账户") and settles no key of its own. If it settles a different key, only `keyLineRegex('Reviewed-by-account')` and the refusal text change. ## Deviations from the card (premise drift) - **`--pair` no longer exists.** Ruling record 5770886272 (#19061, letter B) retired the `--pair` landing pre-check. Its job of printing the two logins is now done by the guard's own clear and refusal lines. - **The guard has no `--explain` flag.** In its place, here is an offline replay of #25363 at head `a6b929fa3`. The attribution reads `os-bill`. Record 6357150459 names the other account only in prose, so the reading is `account-missing` and the record **would be refused**. With `Reviewed-by-account: os-elon-musk` added, the reading is `holds`. (#25363 touched no governed path, so the guard never consulted its record in real life.) - **`--template` does not print the new line yet.** Its output is pinned byte-for-byte across several batteries. The refusal text tells the seat exactly what to add. ## Gates `dispatch-gates --commands --repo objectstack-ai/objectstack` derived 35 families. All 35 ran with exit 0, and `--ran` reports `35 run, 0 NOT-MEASURED`. `check-governed-merges --test` on the two paths: NOT governed, so this is an ordinary queue landing. The workflow that runs the guard is unchanged. --- _Generated by [Claude Code](https://claude.ai/code/session_0186iLxsmzoYpA9qHMo7Stku)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 03243fc commit 041a120

2 files changed

Lines changed: 258 additions & 6 deletions

File tree

‎scripts/pm/check-governed-queue-guard.mjs‎

Lines changed: 178 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,15 @@
266266
* record IS one (#18060). Every governed path outside Tier S is Tier H, the
267267
* rules layer, and keeps the predicate above byte-for-byte.
268268
*
269+
* ⚖️ AND, ON A PULL REQUEST BUILT IN A PROJECT THREAD, FROM ANOTHER ACCOUNT
270+
* (#25415, maintainer 2026-10-23). The relay authors every PR and comment as
271+
* `objectstack-fleet[bot]`, so the only place a project-thread PR names its
272+
* account is its body's `claude-projects-attribution` line. When that line is
273+
* there, the record must also carry `Reviewed-by-account:` naming a DIFFERENT
274+
* login (`accountIndependence`); same, missing or unreadable refuses, and the
275+
* refusal and the clear both print the two logins compared. A body without the
276+
* line (a Routine seat, a human) owes nothing here — today's reading unchanged.
277+
*
269278
* ⭐ THE TWO CARRIERS ARE ONE DELIVERY, and the location is the governed text's
270279
* to decide rather than this file's — quoted, untranslated, because it IS the
271280
* criterion: 「复核记录 = 一条评论落 PR 或卡」
@@ -560,6 +569,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
560569
'the per-row repository scope (ruling B2, 2026-10-13)': 14,
561570
'⭐ B′ with C: the record stands at the tier its FACE maps to, read off the committed paths': 8,
562571
'the CODEOWNERS mirror: every Tier H register row routes to every GOVERNED_APPROVERS account, nothing else is routed, and the file says so': 12,
572+
'⭐ #25415: an attributed pull request lands on a review of record from ANOTHER account': 10,
563573
});
564574

565575
// DELETING an entry silences that battery's floor exactly as effectively as
@@ -569,7 +579,8 @@ const SELF_TEST_BATTERIES = Object.freeze({
569579
// Raised 23 → 24 with the queued pull-back battery: one battery, one row.
570580
// Raised 25 → 26 with the B′-with-C face battery (ruling record 6159870311): one battery, one row.
571581
// Raised 26 → 27 with the CODEOWNERS mirror battery: one battery, one row.
572-
const SELF_TEST_BATTERY_FLOOR = 27;
582+
// Raised 27 → 28 with the #25415 account-independence battery: one battery, one row.
583+
const SELF_TEST_BATTERY_FLOOR = 28;
573584

574585
// The key an assertion is filed under when no battery is open. It is not a
575586
// declared battery, so it reds by the same set difference rather than silently
@@ -815,11 +826,12 @@ export async function loadRecordRecognisers(load = (specifier) => import(specifi
815826
servedTierStands: tier?.servedTierStands,
816827
isModelIdentifierToken: tier?.isModelIdentifierToken,
817828
reviewFace: tier?.reviewFace,
829+
attributionLogin: tier?.readAttributionLogin,
818830
threads: tier?.REVIEW_OF_RECORD_THREADS,
819831
location: tier?.REVIEW_OF_RECORD_LOCATION,
820832
};
821833
const missing = [
822-
...['locate', 'deliveredCard', 'servedTierStands', 'isModelIdentifierToken', 'reviewFace'].filter((key) => typeof found[key] !== 'function'),
834+
...['locate', 'deliveredCard', 'servedTierStands', 'isModelIdentifierToken', 'reviewFace', 'attributionLogin'].filter((key) => typeof found[key] !== 'function'),
823835
...(Array.isArray(found.threads) && found.threads.length > 0 ? [] : ['threads']),
824836
...(typeof found.location === 'string' && found.location.length > 0 ? [] : ['location']),
825837
];
@@ -884,7 +896,7 @@ export async function loadRecordRecognisers(load = (specifier) => import(specifi
884896
* and the rule cannot drift. ⛔ A pair that carries no path list cannot say
885897
* which face it is on: UNREADABLE, never the cheaper tier.
886898
*/
887-
export function recordVerdict({ pair, recognisers, cardNote = null }) {
899+
export function recordVerdict({ pair, recognisers, cardNote = null, attribution = { state: 'absent' } }) {
888900
if (recognisers?.available !== true) {
889901
return { state: 'unreadable', reason: recognisers?.reason ?? 'the contract-review recognisers were not loaded', cardNote };
890902
}
@@ -944,7 +956,41 @@ export function recordVerdict({ pair, recognisers, cardNote = null }) {
944956
};
945957
if (located.state === 'unsigned') return { state: 'unsigned', ...found };
946958
if (!recognisers.servedTierStands(located.served, face.tierName)) return { state: 'below-tier', ...found };
947-
return { state: 'stands', ...found };
959+
if (attribution?.state === 'unreadable' && attribution.reason) {
960+
return { state: 'unreadable', cardNote, reason: attribution.reason };
961+
}
962+
const independence = accountIndependence(attribution, located.account);
963+
if (independence.state !== 'holds' && independence.state !== 'not-owed') return { state: 'not-independent', ...found, independence };
964+
return { state: 'stands', ...found, independence };
965+
}
966+
967+
/**
968+
* Was the review of record written from a DIFFERENT ACCOUNT than the one the
969+
* pull request was built under? (#25415, maintainer 2026-10-23.)
970+
*
971+
* A pull request landed from a Claude project thread carries its authoring
972+
* account in the body's attribution line and nowhere else — the relay authors
973+
* every PR and comment as `objectstack-fleet[bot]`, so neither `Reviewed-by:`
974+
* (a session) nor the comment author can tell two accounts apart. So when the
975+
* body names an account, the record must name its reviewer's account on a
976+
* `Reviewed-by-account:` line, and the two must differ; a same-account
977+
* subagent's review is a review, not an independent one.
978+
*
979+
* ⭐ A pull request with NO attribution line (a Routine seat, a human) owes
980+
* nothing here: `not-owed`, today's behaviour byte for byte. ⛔ Everything else
981+
* that is not a readable, different login is a refusal — an unreadable
982+
* attribution, a missing or unreadable account line, or the same login (GitHub
983+
* logins compare case-insensitively).
984+
*/
985+
export function accountIndependence(attribution, account) {
986+
if (!attribution || attribution.state === 'absent') return { state: 'not-owed' };
987+
if (attribution.state !== 'read') return { state: 'attribution-unreadable', line: attribution.line ?? null };
988+
const author = attribution.login;
989+
if (account?.state === 'unreadable') return { state: 'account-unreadable', author, line: account.line };
990+
if (account?.state !== 'read') return { state: 'account-missing', author };
991+
const reviewer = account.login;
992+
if (author.toLowerCase() === reviewer.toLowerCase()) return { state: 'same-account', author, reviewer };
993+
return { state: 'holds', author, reviewer };
948994
}
949995

950996
/**
@@ -1263,6 +1309,41 @@ export function guardVerdict({ event, repo, governed = [], unattributed = [], ap
12631309
return { ...base, conclusion: 'cleared', exitCode: EXIT_CLEAR, refusalKind: null };
12641310
}
12651311

1312+
/**
1313+
* The refusal for a record that stands on tier but not on account (#25415):
1314+
* it names both logins it compared (or the one it could read) and the rule.
1315+
*/
1316+
function independenceRefusalLines(record) {
1317+
const ind = record.independence ?? {};
1318+
const at = `comment ${record.id ?? '(no readable id)'}, ${record.at ?? 'undated'}`;
1319+
const rule = [
1320+
' Rule (#25415, maintainer 2026-10-23): a pull request landed from a project thread lands on a review of record',
1321+
' from ANOTHER account — add `Reviewed-by-account: LOGIN` to the record, LOGIN being the reviewing session\'s',
1322+
' account and different from the PR body\'s `claude-projects-attribution` github_login.',
1323+
];
1324+
if (ind.state === 'same-account') {
1325+
return [
1326+
` ⛔ the review of record on this head (${at}) was written from the SAME account that built the pull request:`,
1327+
` attribution \`${ind.author}\` = Reviewed-by-account \`${ind.reviewer}\`. A same-account review is a review, not an independent one.`,
1328+
...rule,
1329+
];
1330+
}
1331+
if (ind.state === 'account-missing' || ind.state === 'account-unreadable') {
1332+
return [
1333+
` ⛔ the review of record on this head (${at}) names no reviewer ACCOUNT the guard can compare against the attribution \`${ind.author}\`:`,
1334+
ind.state === 'account-missing'
1335+
? ' it carries NO `Reviewed-by-account:` line.'
1336+
: ` its \`Reviewed-by-account:\` line carries no single GitHub login (${ind.line}).`,
1337+
...rule,
1338+
];
1339+
}
1340+
return [
1341+
` ⛔ the pull request body carries a \`claude-projects-attribution\` line whose github_login cannot be read (${ind.line ?? 'no line quoted'}),`,
1342+
' so the account the record must differ from is unknown.',
1343+
...rule,
1344+
];
1345+
}
1346+
12661347
/**
12671348
* The words a reader gets. Requirement (e) of the card lives here: every
12681349
* rendering names the exact paths that matched and states what would satisfy
@@ -1395,6 +1476,12 @@ export function renderGuardVerdict(verdict) {
13951476
(entry.record.served?.stamps
13961477
? ` on a stamp control of ${entry.record.served.stamps.atTier}/${entry.record.served.stamps.total}.`
13971478
: ' (no stamp control declared, which the rule permits).'),
1479+
...(entry.record.independence?.state === 'holds'
1480+
? [
1481+
` ⭐ accounts compared (#25415): built under \`${entry.record.independence.author}\` (the PR body's attribution line), ` +
1482+
`reviewed under \`${entry.record.independence.reviewer}\` (\`Reviewed-by-account:\`) — different accounts, so it is independent.`,
1483+
]
1484+
: []),
13981485
...(entry.record.carriedFrom
13991486
? [
14001487
` ⭐ that record names head \`${String(entry.record.carriedFrom).slice(0, 12)}\`, carried forward over ` +
@@ -1434,6 +1521,8 @@ export function renderGuardVerdict(verdict) {
14341521
]
14351522
: entry.record.state === 'unreadable'
14361523
? [` ⛔ the review of record could NOT be read — ${entry.record.reason}`]
1524+
: entry.record.state === 'not-independent'
1525+
? independenceRefusalLines(entry.record)
14371526
: entry.record.state === 'unsigned'
14381527
? [
14391528
` ⛔ a \`## Contract review\` comment names this head (comment ${entry.record.id ?? '(no readable id)'}, ` +
@@ -1765,7 +1854,12 @@ export async function runGuard({ event, repo, rows, fetchReviews, fetchPull, fet
17651854
records.set(entry.pr, { state: 'unreadable', reason: unreadable, cardNote });
17661855
continue;
17671856
}
1768-
records.set(entry.pr, recordVerdict({ pair, recognisers, cardNote }));
1857+
// ⭐ The authoring account, off the body this guard already read (#25415).
1858+
// A row that could not be read cannot say whether one is owed: unreadable.
1859+
const attribution = pull
1860+
? recognisers.attributionLogin(pull.body)
1861+
: { state: 'unreadable', reason: `PR #${entry.pr}'s body could not be read, so whether its record owes a second account is unknown` };
1862+
records.set(entry.pr, recordVerdict({ pair, recognisers, cardNote, attribution }));
17691863
}
17701864
}
17711865
return guardVerdict({ event, repo, governed, unattributed, approvals, records, apiCalls, headNotes, lifted });
@@ -4234,6 +4328,85 @@ export async function selfTest() {
42344328
// live leg is pinned at the contract-review tier; `recordVerdict` is pure, so
42354329
// the declaration face — which no governed entry can be on — is driven on it
42364330
// directly, with the real recognisers.
4331+
battery('⭐ #25415: an attributed pull request lands on a review of record from ANOTHER account');
4332+
{
4333+
const ATTRIBUTED = (login) => `<!-- claude-projects-attribution: {"github_login":"${login}"} -->\n_Requested by **Elon**_\n\nFixes #${REF_CARD}`;
4334+
const withAccount = (login) => {
4335+
const c = recordComment();
4336+
return { ...c, body: `${c.body.replace('Reviewed-by: `session_01SEAT`', `Reviewed-by: \`session_01SEAT\`\nReviewed-by-account: \`${login}\``)}` };
4337+
};
4338+
const plain = await tierRun({ comments: [recordComment()] });
4339+
assert(
4340+
'an-UNATTRIBUTED-PR-keeps-todays-behaviour-the-record-stands-and-no-account-is-owed',
4341+
plain.exitCode === EXIT_CLEAR && plain.entries[0].record.state === 'stands' && plain.entries[0].record.independence.state === 'not-owed' &&
4342+
!/accounts compared/.test(renderGuardVerdict(plain)),
4343+
JSON.stringify(plain.entries[0].record.independence),
4344+
);
4345+
const differ = await tierRun({ prBody: ATTRIBUTED('lane-account-a'), comments: [withAccount('lane-account-b')] });
4346+
const differText = renderGuardVerdict(differ);
4347+
assert(
4348+
'an-attributed-PR-whose-record-names-a-DIFFERENT-account-PASSES-and-the-clear-prints-both-logins',
4349+
differ.exitCode === EXIT_CLEAR && differ.entries[0].record.state === 'stands' && differ.entries[0].record.independence.state === 'holds' &&
4350+
/accounts compared/.test(differText) && differText.includes('`lane-account-a`') && differText.includes('`lane-account-b`'),
4351+
JSON.stringify(differ.entries[0].record.independence),
4352+
);
4353+
const same = await tierRun({ prBody: ATTRIBUTED('lane-account-a'), comments: [withAccount('@Lane-Account-A')] });
4354+
const sameText = renderGuardVerdict(same);
4355+
assert(
4356+
'⛔ the-SAME-account-is-REFUSED-logins-compare-case-insensitively-and-the-refusal-names-both-and-the-rule',
4357+
same.exitCode === EXIT_REFUSED_UNAPPROVED && same.entries[0].record.state === 'not-independent' &&
4358+
same.entries[0].record.independence.state === 'same-account' &&
4359+
sameText.includes('`lane-account-a`') && sameText.includes('`Lane-Account-A`') && /Reviewed-by-account: LOGIN/.test(sameText),
4360+
JSON.stringify(same.entries[0].record.independence),
4361+
);
4362+
const missing = await tierRun({ prBody: ATTRIBUTED('lane-account-a'), comments: [recordComment()] });
4363+
assert(
4364+
'⛔ an-attributed-PR-whose-record-carries-NO-Reviewed-by-account-line-is-REFUSED',
4365+
missing.exitCode === EXIT_REFUSED_UNAPPROVED && missing.entries[0].record.independence.state === 'account-missing' &&
4366+
/carries NO `Reviewed-by-account:` line/.test(renderGuardVerdict(missing)),
4367+
JSON.stringify(missing.entries[0].record.independence),
4368+
);
4369+
const prose = await tierRun({ prBody: ATTRIBUTED('lane-account-a'), comments: [withAccount('the other account')] });
4370+
assert(
4371+
'⛔ a-Reviewed-by-account-line-that-is-not-ONE-login-is-REFUSED',
4372+
prose.exitCode === EXIT_REFUSED_UNAPPROVED && prose.entries[0].record.independence.state === 'account-unreadable',
4373+
JSON.stringify(prose.entries[0].record.independence),
4374+
);
4375+
const botAccount = await tierRun({ prBody: ATTRIBUTED('lane-account-a'), comments: [withAccount('objectstack-fleet[bot]')] });
4376+
assert(
4377+
'⛔ the-relay-BOT-is-no-reviewer-account-REFUSED',
4378+
botAccount.exitCode === EXIT_REFUSED_UNAPPROVED && botAccount.entries[0].record.independence.state === 'account-unreadable',
4379+
);
4380+
const badAttr = await tierRun({ prBody: `<!-- claude-projects-attribution: {"github_login":""} -->\n\nFixes #${REF_CARD}`, comments: [withAccount('lane-account-b')] });
4381+
assert(
4382+
'⛔ an-attribution-line-naming-NO-readable-login-is-REFUSED-never-read-as-unattributed',
4383+
badAttr.exitCode === EXIT_REFUSED_UNAPPROVED && badAttr.entries[0].record.independence.state === 'attribution-unreadable',
4384+
JSON.stringify(badAttr.entries[0].record.independence),
4385+
);
4386+
const lower = await tierRun({ prBody: `Fixes #${REF_CARD}\n\n${ATTRIBUTED('lane-account-a')}`, comments: [withAccount('lane-account-a')] });
4387+
assert(
4388+
'⛔ the-attribution-line-is-read-on-ANY-line-an-edit-above-it-does-not-switch-the-check-off',
4389+
lower.exitCode === EXIT_REFUSED_UNAPPROVED && lower.entries[0].record.independence.state === 'same-account',
4390+
);
4391+
const approvedSame = await tierRun({
4392+
prBody: ATTRIBUTED('lane-account-a'),
4393+
reviews: [{ state: 'APPROVED', user: { login: GOVERNED_APPROVERS[0] }, commit_id: REF_HEAD }],
4394+
comments: [withAccount('lane-account-a')],
4395+
});
4396+
assert(
4397+
'MONOTONE: an-authorized-approval-still-clears-an-attributed-PR-the-account-leg-is-never-consulted',
4398+
approvedSame.exitCode === EXIT_CLEAR && approvedSame.entries[0].approval.state === 'approved' && tierThreadsRead.length === 0,
4399+
JSON.stringify({ code: approvedSame.exitCode, threads: tierThreadsRead }),
4400+
);
4401+
assert(
4402+
'the-pure-reading-attribution-absent-is-not-owed-and-nothing-else-is',
4403+
accountIndependence({ state: 'absent' }, { state: 'missing' }).state === 'not-owed' &&
4404+
accountIndependence(undefined, { state: 'missing' }).state === 'not-owed' &&
4405+
accountIndependence({ state: 'read', login: 'a' }, { state: 'read', login: 'b' }).state === 'holds' &&
4406+
accountIndependence({ state: 'read', login: 'a' }, { state: 'read', login: 'A' }).state === 'same-account',
4407+
);
4408+
}
4409+
42374410
battery('⭐ B′ with C: the record stands at the tier its FACE maps to, read off the committed paths');
42384411
const { TIER_DEFAULT_NAME: DEFAULT_TOKEN, TIER_DEFAULT: DEFAULT_VALUE } = await import('./dispatch-gates.mjs');
42394412
const tierDefaultOnS = await tierRun({ comments: [recordComment({ tierLine: `Served-tier: 75/75 \`${DEFAULT_TOKEN}\`` })] });

0 commit comments

Comments
 (0)