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+ ! / a c c o u n t s c o m p a r e d / . 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+ / a c c o u n t s c o m p a r e d / . 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`' ) && / R e v i e w e d - b y - a c c o u n t : L O G I N / . 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+ / c a r r i e s N O ` R e v i e w e d - b y - a c c o u n t : ` l i n e / . 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