Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 128 additions & 25 deletions .github/workflows/merge-queue-triage.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -550,13 +550,118 @@ jobs:
};

const ANCHOR_LABEL = 'finding';
// The anchor's IDENTITY label, and the only label the lookup keys on.
// `finding` is a TRIAGE-STATE label: first-touch grading takes it off by
// definition, so a lookup keyed on `finding` stops seeing an anchor the
// moment a human triages it and files a fresh one on the next ejection.
// This label is the machine's, is never graded off, and is what keeps
// the anchor findable for the rest of its life, open or closed.
const ANCHOR_IDENTITY_LABEL = 'queue-flake-anchor';
const MAX_ANCHOR_PAGES = 3;
const anchorNotes = [];
for (const a of aggregated.slice(0, 3)) {
const anchorMarker = `<!-- queue-signature-anchor:${a.key} -->`;
// Stable across refreshes — the victim count lives in the body, so
// a growing count cannot make the anchor unfindable by title.
const title = `Queue-flake anchor: ${a.key}`;

// ── Anchor lookup ─────────────────────────────────────────────
// The anchor's identity is the FILE KEY, carried by the body marker
// with the exact title as a second way in (a body is the one channel
// GitHub is known to rewrite). It is NOT the `finding` label and NOT
// `open`:
// - `finding` comes off the moment the anchor is graded, which is
// what first-touch grading MEANS. Keying on it made a properly
// triaged anchor invisible and turned every further ejection into
// a new anchor.
// - a CLOSED anchor is still evidence about this key: closed as a
// duplicate says the conversation moved; closed on its own merits
// says the next ejection is a REGRESSION, not a continuation.
const isThisAnchor = (i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title);
const labelNames = (i) => (i.labels ?? []).map((l) => (typeof l === 'string' ? l : l?.name));

// The passes, in order. `firstMatchWins` says whether a pass may stop
// at its first hit: the identity pass may NOT, because the resolution
// order below needs this key's CLOSED anchors as well as its open one.
// `adopts` marks the transitional pass — a bounded scan of OPEN issues
// that finds anchors filed before the identity label existed (a graded
// one carries neither label) and puts the identity label on them, so
// the first pass owns them from the next ejection on.
const ANCHOR_QUERIES = [
{ state: 'all', labels: ANCHOR_IDENTITY_LABEL, firstMatchWins: false, adopts: false },
{ state: 'open', firstMatchWins: true, adopts: true },
];

const candidates = [];
let adopted = null;
let scanComplete = true;
let refusal = null;
for (const [qi, q] of ANCHOR_QUERIES.entries()) {
// A later pass is skipped only once an OPEN anchor is in hand.
// Closed candidates are not enough to stop: a key can have a
// labelled closed duplicate and an UNLABELLED open survivor at the
// same time — that is exactly what a half-migrated key looks like
// — and stopping there would read the survivor as absent.
if (candidates.some((i) => i.state !== 'closed')) break;
const { firstMatchWins = false, adopts = false, ...params } = q;
const found = [];
let complete = true;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, ...params,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
found.push(...res.data.filter(isThisAnchor));
if ((firstMatchWins && found.length > 0) || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) complete = false;
}
} catch (error) {
complete = false;
refusal = describe(error);
}
// Completeness is the IDENTITY pass's argument — it is the bounded,
// label-keyed one. A truncated adoption pass is a missed migration,
// not an unestablished absence, and must not block a first anchor.
if (qi === 0) scanComplete = complete;
if (adopts && found.length > 0) adopted = found[0];
for (const i of found) {
if (!candidates.some((c) => c.number === i.number)) candidates.push(i);
}
}

if (adopted && !labelNames(adopted).includes(ANCHOR_IDENTITY_LABEL)) {
try {
await github.rest.issues.addLabels({
owner, repo, issue_number: adopted.number, labels: [ANCHOR_IDENTITY_LABEL],
});
} catch (error) {
core.warning(`Could not put the identity label on the existing anchor #${adopted.number} for ${a.key} (${describe(error)}); it is refreshed anyway and adoption retries next run.`,
{ title: 'Queue-signature anchor not adopted' });
}
}

// Resolution order. OPEN beats closed, and the OLDEST open anchor
// beats a newer one: that is the issue the duplicates were closed
// against.
const openAnchors = candidates.filter((i) => i.state !== 'closed')
.sort((x, y) => x.number - y.number);
const closedAnchors = candidates.filter((i) => i.state === 'closed')
.sort((x, y) => y.number - x.number);
const existing = openAnchors[0] ?? null;
const closedAsDuplicate = !existing && closedAnchors[0]?.state_reason === 'duplicate'
? closedAnchors[0]
: null;
// A closed anchor that was NOT closed as a duplicate is a RESOLVED
// one: the flake was answered once and this key is ejecting PRs
// again. A new anchor is legitimate there, but it has to say which
// issue it regressed from or the previous answer is lost.
const resolvedBefore = closedAnchors.find((i) => i.state_reason !== 'duplicate') ?? null;
const priorAnchor = resolvedBefore && (!existing || existing.number > resolvedBefore.number)
? resolvedBefore
: null;

const prRows = [...a.prs.entries()].sort((x, y) => x[0] - y[0]).map(([pr, runs]) => {
const s = a.stackOf.get(pr);
const cell = !s || s.size < 2
Expand All@@ -572,6 +677,13 @@ jobs:
'that conversation; it is refreshed by the merge-queue-triage workflow on every',
'further ejection.',
'',
...(priorAnchor
? [
`⚠️ 同签名的上一个汇总 issue #${priorAnchor.number} 已经关闭(不是作为重复关闭的),`,
'之后这个文件又开始弹出 PR ⇒ 这是一次**回归**,上一轮的结论在那张 issue 里。',
'',
]
: []),
'| PR | stack | queue build |',
'|---|---|---|',
...prRows,
Expand DownExpand Up@@ -616,29 +728,6 @@ jobs:
anchorMarker,
].join('\n');

let existing = null;
let scanComplete = true;
let refusal = null;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, state: 'open', labels: ANCHOR_LABEL,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
// Identity is the body marker; the exact title is a second
// way in, because a body is the one channel GitHub is known
// to rewrite and an anchor that cannot be found is an anchor
// that gets duplicated.
existing = res.data.find((i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title)) ?? null;
if (existing || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) scanComplete = false;
}
} catch (error) {
scanComplete = false;
refusal = describe(error);
}

if (existing) {
try {
await github.rest.issues.update({
Expand All@@ -664,11 +753,25 @@ jobs:
continue;
}

if (closedAsDuplicate) {
// The newest anchor for this key was closed AS A DUPLICATE and no
// open anchor is left. Filing a fresh one here is exactly what
// feeds the loop — it would be closed as a duplicate in turn.
// GitHub's issue payload does not carry a duplicate's TARGET (only
// `state_reason: duplicate`), so the canonical cannot be followed
// from here; this says where the conversation was sent instead of
// guessing at a new home for it.
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 同签名的汇总 issue #${closedAsDuplicate.number} 已作为**重复**关闭,且没有仍然打开的同签名汇总 issue ⇒ 本次不新建(新建只会再被判重)。请到它指向的那张 issue 上谈,或重开 #${closedAsDuplicate.number}。`);
core.warning(`The newest anchor for ${a.key} (#${closedAsDuplicate.number}) was closed as a duplicate and no open anchor is left; not filing another one.`,
{ title: 'Queue-signature anchor not created' });
continue;
}

try {
const created = await github.rest.issues.create({
owner, repo, title, body, labels: [ANCHOR_LABEL],
owner, repo, title, body, labels: [ANCHOR_LABEL, ANCHOR_IDENTITY_LABEL],
});
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(新建)`);
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(${priorAnchor ? `新建,回归自已关闭的 #${priorAnchor.number}` : '新建'})`);
} catch (error) {
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 汇总 issue 新建失败(${describe(error)}),上面的名单就是全部事实。`);
core.warning(`Could not file the anchor issue for ${a.key} (${describe(error)}).`,
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 128 additions & 25 deletions .github/workflows/merge-queue-triage.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -550,13 +550,118 @@ jobs:
};

const ANCHOR_LABEL = 'finding';
// The anchor's IDENTITY label, and the only label the lookup keys on.
// `finding` is a TRIAGE-STATE label: first-touch grading takes it off by
// definition, so a lookup keyed on `finding` stops seeing an anchor the
// moment a human triages it and files a fresh one on the next ejection.
// This label is the machine's, is never graded off, and is what keeps
// the anchor findable for the rest of its life, open or closed.
const ANCHOR_IDENTITY_LABEL = 'queue-flake-anchor';
const MAX_ANCHOR_PAGES = 3;
const anchorNotes = [];
for (const a of aggregated.slice(0, 3)) {
const anchorMarker = `<!-- queue-signature-anchor:${a.key} -->`;
// Stable across refreshes — the victim count lives in the body, so
// a growing count cannot make the anchor unfindable by title.
const title = `Queue-flake anchor: ${a.key}`;

// ── Anchor lookup ─────────────────────────────────────────────
// The anchor's identity is the FILE KEY, carried by the body marker
// with the exact title as a second way in (a body is the one channel
// GitHub is known to rewrite). It is NOT the `finding` label and NOT
// `open`:
// - `finding` comes off the moment the anchor is graded, which is
// what first-touch grading MEANS. Keying on it made a properly
// triaged anchor invisible and turned every further ejection into
// a new anchor.
// - a CLOSED anchor is still evidence about this key: closed as a
// duplicate says the conversation moved; closed on its own merits
// says the next ejection is a REGRESSION, not a continuation.
const isThisAnchor = (i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title);
const labelNames = (i) => (i.labels ?? []).map((l) => (typeof l === 'string' ? l : l?.name));

// The passes, in order. `firstMatchWins` says whether a pass may stop
// at its first hit: the identity pass may NOT, because the resolution
// order below needs this key's CLOSED anchors as well as its open one.
// `adopts` marks the transitional pass — a bounded scan of OPEN issues
// that finds anchors filed before the identity label existed (a graded
// one carries neither label) and puts the identity label on them, so
// the first pass owns them from the next ejection on.
const ANCHOR_QUERIES = [
{ state: 'all', labels: ANCHOR_IDENTITY_LABEL, firstMatchWins: false, adopts: false },
{ state: 'open', firstMatchWins: true, adopts: true },
];

const candidates = [];
let adopted = null;
let scanComplete = true;
let refusal = null;
for (const [qi, q] of ANCHOR_QUERIES.entries()) {
// A later pass is skipped only once an OPEN anchor is in hand.
// Closed candidates are not enough to stop: a key can have a
// labelled closed duplicate and an UNLABELLED open survivor at the
// same time — that is exactly what a half-migrated key looks like
// — and stopping there would read the survivor as absent.
if (candidates.some((i) => i.state !== 'closed')) break;
const { firstMatchWins = false, adopts = false, ...params } = q;
const found = [];
let complete = true;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, ...params,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
found.push(...res.data.filter(isThisAnchor));
if ((firstMatchWins && found.length > 0) || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) complete = false;
}
} catch (error) {
complete = false;
refusal = describe(error);
}
// Completeness is the IDENTITY pass's argument — it is the bounded,
// label-keyed one. A truncated adoption pass is a missed migration,
// not an unestablished absence, and must not block a first anchor.
if (qi === 0) scanComplete = complete;
if (adopts && found.length > 0) adopted = found[0];
for (const i of found) {
if (!candidates.some((c) => c.number === i.number)) candidates.push(i);
}
}

if (adopted && !labelNames(adopted).includes(ANCHOR_IDENTITY_LABEL)) {
try {
await github.rest.issues.addLabels({
owner, repo, issue_number: adopted.number, labels: [ANCHOR_IDENTITY_LABEL],
});
} catch (error) {
core.warning(`Could not put the identity label on the existing anchor #${adopted.number} for ${a.key} (${describe(error)}); it is refreshed anyway and adoption retries next run.`,
{ title: 'Queue-signature anchor not adopted' });
}
}

// Resolution order. OPEN beats closed, and the OLDEST open anchor
// beats a newer one: that is the issue the duplicates were closed
// against.
const openAnchors = candidates.filter((i) => i.state !== 'closed')
.sort((x, y) => x.number - y.number);
const closedAnchors = candidates.filter((i) => i.state === 'closed')
.sort((x, y) => y.number - x.number);
const existing = openAnchors[0] ?? null;
const closedAsDuplicate = !existing && closedAnchors[0]?.state_reason === 'duplicate'
? closedAnchors[0]
: null;
// A closed anchor that was NOT closed as a duplicate is a RESOLVED
// one: the flake was answered once and this key is ejecting PRs
// again. A new anchor is legitimate there, but it has to say which
// issue it regressed from or the previous answer is lost.
const resolvedBefore = closedAnchors.find((i) => i.state_reason !== 'duplicate') ?? null;
const priorAnchor = resolvedBefore && (!existing || existing.number > resolvedBefore.number)
? resolvedBefore
: null;

const prRows = [...a.prs.entries()].sort((x, y) => x[0] - y[0]).map(([pr, runs]) => {
const s = a.stackOf.get(pr);
const cell = !s || s.size < 2
Expand All@@ -572,6 +677,13 @@ jobs:
'that conversation; it is refreshed by the merge-queue-triage workflow on every',
'further ejection.',
'',
...(priorAnchor
? [
`⚠️ 同签名的上一个汇总 issue #${priorAnchor.number} 已经关闭(不是作为重复关闭的),`,
'之后这个文件又开始弹出 PR ⇒ 这是一次**回归**,上一轮的结论在那张 issue 里。',
'',
]
: []),
'| PR | stack | queue build |',
'|---|---|---|',
...prRows,
Expand DownExpand Up@@ -616,29 +728,6 @@ jobs:
anchorMarker,
].join('\n');

let existing = null;
let scanComplete = true;
let refusal = null;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, state: 'open', labels: ANCHOR_LABEL,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
// Identity is the body marker; the exact title is a second
// way in, because a body is the one channel GitHub is known
// to rewrite and an anchor that cannot be found is an anchor
// that gets duplicated.
existing = res.data.find((i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title)) ?? null;
if (existing || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) scanComplete = false;
}
} catch (error) {
scanComplete = false;
refusal = describe(error);
}

if (existing) {
try {
await github.rest.issues.update({
Expand All@@ -664,11 +753,25 @@ jobs:
continue;
}

if (closedAsDuplicate) {
// The newest anchor for this key was closed AS A DUPLICATE and no
// open anchor is left. Filing a fresh one here is exactly what
// feeds the loop — it would be closed as a duplicate in turn.
// GitHub's issue payload does not carry a duplicate's TARGET (only
// `state_reason: duplicate`), so the canonical cannot be followed
// from here; this says where the conversation was sent instead of
// guessing at a new home for it.
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 同签名的汇总 issue #${closedAsDuplicate.number} 已作为**重复**关闭,且没有仍然打开的同签名汇总 issue ⇒ 本次不新建(新建只会再被判重)。请到它指向的那张 issue 上谈,或重开 #${closedAsDuplicate.number}。`);
core.warning(`The newest anchor for ${a.key} (#${closedAsDuplicate.number}) was closed as a duplicate and no open anchor is left; not filing another one.`,
{ title: 'Queue-signature anchor not created' });
continue;
}

try {
const created = await github.rest.issues.create({
owner, repo, title, body, labels: [ANCHOR_LABEL],
owner, repo, title, body, labels: [ANCHOR_LABEL, ANCHOR_IDENTITY_LABEL],
});
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(新建)`);
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(${priorAnchor ? `新建,回归自已关闭的 #${priorAnchor.number}` : '新建'})`);
} catch (error) {
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 汇总 issue 新建失败(${describe(error)}),上面的名单就是全部事实。`);
core.warning(`Could not file the anchor issue for ${a.key} (${describe(error)}).`,
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 128 additions & 25 deletions .github/workflows/merge-queue-triage.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -550,13 +550,118 @@ jobs:
};

const ANCHOR_LABEL = 'finding';
// The anchor's IDENTITY label, and the only label the lookup keys on.
// `finding` is a TRIAGE-STATE label: first-touch grading takes it off by
// definition, so a lookup keyed on `finding` stops seeing an anchor the
// moment a human triages it and files a fresh one on the next ejection.
// This label is the machine's, is never graded off, and is what keeps
// the anchor findable for the rest of its life, open or closed.
const ANCHOR_IDENTITY_LABEL = 'queue-flake-anchor';
const MAX_ANCHOR_PAGES = 3;
const anchorNotes = [];
for (const a of aggregated.slice(0, 3)) {
const anchorMarker = `<!-- queue-signature-anchor:${a.key} -->`;
// Stable across refreshes — the victim count lives in the body, so
// a growing count cannot make the anchor unfindable by title.
const title = `Queue-flake anchor: ${a.key}`;

// ── Anchor lookup ─────────────────────────────────────────────
// The anchor's identity is the FILE KEY, carried by the body marker
// with the exact title as a second way in (a body is the one channel
// GitHub is known to rewrite). It is NOT the `finding` label and NOT
// `open`:
// - `finding` comes off the moment the anchor is graded, which is
// what first-touch grading MEANS. Keying on it made a properly
// triaged anchor invisible and turned every further ejection into
// a new anchor.
// - a CLOSED anchor is still evidence about this key: closed as a
// duplicate says the conversation moved; closed on its own merits
// says the next ejection is a REGRESSION, not a continuation.
const isThisAnchor = (i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title);
const labelNames = (i) => (i.labels ?? []).map((l) => (typeof l === 'string' ? l : l?.name));

// The passes, in order. `firstMatchWins` says whether a pass may stop
// at its first hit: the identity pass may NOT, because the resolution
// order below needs this key's CLOSED anchors as well as its open one.
// `adopts` marks the transitional pass — a bounded scan of OPEN issues
// that finds anchors filed before the identity label existed (a graded
// one carries neither label) and puts the identity label on them, so
// the first pass owns them from the next ejection on.
const ANCHOR_QUERIES = [
{ state: 'all', labels: ANCHOR_IDENTITY_LABEL, firstMatchWins: false, adopts: false },
{ state: 'open', firstMatchWins: true, adopts: true },
];

const candidates = [];
let adopted = null;
let scanComplete = true;
let refusal = null;
for (const [qi, q] of ANCHOR_QUERIES.entries()) {
// A later pass is skipped only once an OPEN anchor is in hand.
// Closed candidates are not enough to stop: a key can have a
// labelled closed duplicate and an UNLABELLED open survivor at the
// same time — that is exactly what a half-migrated key looks like
// — and stopping there would read the survivor as absent.
if (candidates.some((i) => i.state !== 'closed')) break;
const { firstMatchWins = false, adopts = false, ...params } = q;
const found = [];
let complete = true;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, ...params,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
found.push(...res.data.filter(isThisAnchor));
if ((firstMatchWins && found.length > 0) || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) complete = false;
}
} catch (error) {
complete = false;
refusal = describe(error);
}
// Completeness is the IDENTITY pass's argument — it is the bounded,
// label-keyed one. A truncated adoption pass is a missed migration,
// not an unestablished absence, and must not block a first anchor.
if (qi === 0) scanComplete = complete;
if (adopts && found.length > 0) adopted = found[0];
for (const i of found) {
if (!candidates.some((c) => c.number === i.number)) candidates.push(i);
}
}

if (adopted && !labelNames(adopted).includes(ANCHOR_IDENTITY_LABEL)) {
try {
await github.rest.issues.addLabels({
owner, repo, issue_number: adopted.number, labels: [ANCHOR_IDENTITY_LABEL],
});
} catch (error) {
core.warning(`Could not put the identity label on the existing anchor #${adopted.number} for ${a.key} (${describe(error)}); it is refreshed anyway and adoption retries next run.`,
{ title: 'Queue-signature anchor not adopted' });
}
}

// Resolution order. OPEN beats closed, and the OLDEST open anchor
// beats a newer one: that is the issue the duplicates were closed
// against.
const openAnchors = candidates.filter((i) => i.state !== 'closed')
.sort((x, y) => x.number - y.number);
const closedAnchors = candidates.filter((i) => i.state === 'closed')
.sort((x, y) => y.number - x.number);
const existing = openAnchors[0] ?? null;
const closedAsDuplicate = !existing && closedAnchors[0]?.state_reason === 'duplicate'
? closedAnchors[0]
: null;
// A closed anchor that was NOT closed as a duplicate is a RESOLVED
// one: the flake was answered once and this key is ejecting PRs
// again. A new anchor is legitimate there, but it has to say which
// issue it regressed from or the previous answer is lost.
const resolvedBefore = closedAnchors.find((i) => i.state_reason !== 'duplicate') ?? null;
const priorAnchor = resolvedBefore && (!existing || existing.number > resolvedBefore.number)
? resolvedBefore
: null;

const prRows = [...a.prs.entries()].sort((x, y) => x[0] - y[0]).map(([pr, runs]) => {
const s = a.stackOf.get(pr);
const cell = !s || s.size < 2
Expand All@@ -572,6 +677,13 @@ jobs:
'that conversation; it is refreshed by the merge-queue-triage workflow on every',
'further ejection.',
'',
...(priorAnchor
? [
`⚠️ 同签名的上一个汇总 issue #${priorAnchor.number} 已经关闭(不是作为重复关闭的),`,
'之后这个文件又开始弹出 PR ⇒ 这是一次**回归**,上一轮的结论在那张 issue 里。',
'',
]
: []),
'| PR | stack | queue build |',
'|---|---|---|',
...prRows,
Expand DownExpand Up@@ -616,29 +728,6 @@ jobs:
anchorMarker,
].join('\n');

let existing = null;
let scanComplete = true;
let refusal = null;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, state: 'open', labels: ANCHOR_LABEL,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
// Identity is the body marker; the exact title is a second
// way in, because a body is the one channel GitHub is known
// to rewrite and an anchor that cannot be found is an anchor
// that gets duplicated.
existing = res.data.find((i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title)) ?? null;
if (existing || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) scanComplete = false;
}
} catch (error) {
scanComplete = false;
refusal = describe(error);
}

if (existing) {
try {
await github.rest.issues.update({
Expand All@@ -664,11 +753,25 @@ jobs:
continue;
}

if (closedAsDuplicate) {
// The newest anchor for this key was closed AS A DUPLICATE and no
// open anchor is left. Filing a fresh one here is exactly what
// feeds the loop — it would be closed as a duplicate in turn.
// GitHub's issue payload does not carry a duplicate's TARGET (only
// `state_reason: duplicate`), so the canonical cannot be followed
// from here; this says where the conversation was sent instead of
// guessing at a new home for it.
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 同签名的汇总 issue #${closedAsDuplicate.number} 已作为**重复**关闭,且没有仍然打开的同签名汇总 issue ⇒ 本次不新建(新建只会再被判重)。请到它指向的那张 issue 上谈,或重开 #${closedAsDuplicate.number}。`);
core.warning(`The newest anchor for ${a.key} (#${closedAsDuplicate.number}) was closed as a duplicate and no open anchor is left; not filing another one.`,
{ title: 'Queue-signature anchor not created' });
continue;
}

try {
const created = await github.rest.issues.create({
owner, repo, title, body, labels: [ANCHOR_LABEL],
owner, repo, title, body, labels: [ANCHOR_LABEL, ANCHOR_IDENTITY_LABEL],
});
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(新建)`);
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(${priorAnchor ? `新建,回归自已关闭的 #${priorAnchor.number}` : '新建'})`);
} catch (error) {
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 汇总 issue 新建失败(${describe(error)}),上面的名单就是全部事实。`);
core.warning(`Could not file the anchor issue for ${a.key} (${describe(error)}).`,
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 128 additions & 25 deletions .github/workflows/merge-queue-triage.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -550,13 +550,118 @@ jobs:
};

const ANCHOR_LABEL = 'finding';
// The anchor's IDENTITY label, and the only label the lookup keys on.
// `finding` is a TRIAGE-STATE label: first-touch grading takes it off by
// definition, so a lookup keyed on `finding` stops seeing an anchor the
// moment a human triages it and files a fresh one on the next ejection.
// This label is the machine's, is never graded off, and is what keeps
// the anchor findable for the rest of its life, open or closed.
const ANCHOR_IDENTITY_LABEL = 'queue-flake-anchor';
const MAX_ANCHOR_PAGES = 3;
const anchorNotes = [];
for (const a of aggregated.slice(0, 3)) {
const anchorMarker = `<!-- queue-signature-anchor:${a.key} -->`;
// Stable across refreshes — the victim count lives in the body, so
// a growing count cannot make the anchor unfindable by title.
const title = `Queue-flake anchor: ${a.key}`;

// ── Anchor lookup ─────────────────────────────────────────────
// The anchor's identity is the FILE KEY, carried by the body marker
// with the exact title as a second way in (a body is the one channel
// GitHub is known to rewrite). It is NOT the `finding` label and NOT
// `open`:
// - `finding` comes off the moment the anchor is graded, which is
// what first-touch grading MEANS. Keying on it made a properly
// triaged anchor invisible and turned every further ejection into
// a new anchor.
// - a CLOSED anchor is still evidence about this key: closed as a
// duplicate says the conversation moved; closed on its own merits
// says the next ejection is a REGRESSION, not a continuation.
const isThisAnchor = (i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title);
const labelNames = (i) => (i.labels ?? []).map((l) => (typeof l === 'string' ? l : l?.name));

// The passes, in order. `firstMatchWins` says whether a pass may stop
// at its first hit: the identity pass may NOT, because the resolution
// order below needs this key's CLOSED anchors as well as its open one.
// `adopts` marks the transitional pass — a bounded scan of OPEN issues
// that finds anchors filed before the identity label existed (a graded
// one carries neither label) and puts the identity label on them, so
// the first pass owns them from the next ejection on.
const ANCHOR_QUERIES = [
{ state: 'all', labels: ANCHOR_IDENTITY_LABEL, firstMatchWins: false, adopts: false },
{ state: 'open', firstMatchWins: true, adopts: true },
];

const candidates = [];
let adopted = null;
let scanComplete = true;
let refusal = null;
for (const [qi, q] of ANCHOR_QUERIES.entries()) {
// A later pass is skipped only once an OPEN anchor is in hand.
// Closed candidates are not enough to stop: a key can have a
// labelled closed duplicate and an UNLABELLED open survivor at the
// same time — that is exactly what a half-migrated key looks like
// — and stopping there would read the survivor as absent.
if (candidates.some((i) => i.state !== 'closed')) break;
const { firstMatchWins = false, adopts = false, ...params } = q;
const found = [];
let complete = true;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, ...params,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
found.push(...res.data.filter(isThisAnchor));
if ((firstMatchWins && found.length > 0) || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) complete = false;
}
} catch (error) {
complete = false;
refusal = describe(error);
}
// Completeness is the IDENTITY pass's argument — it is the bounded,
// label-keyed one. A truncated adoption pass is a missed migration,
// not an unestablished absence, and must not block a first anchor.
if (qi === 0) scanComplete = complete;
if (adopts && found.length > 0) adopted = found[0];
for (const i of found) {
if (!candidates.some((c) => c.number === i.number)) candidates.push(i);
}
}

if (adopted && !labelNames(adopted).includes(ANCHOR_IDENTITY_LABEL)) {
try {
await github.rest.issues.addLabels({
owner, repo, issue_number: adopted.number, labels: [ANCHOR_IDENTITY_LABEL],
});
} catch (error) {
core.warning(`Could not put the identity label on the existing anchor #${adopted.number} for ${a.key} (${describe(error)}); it is refreshed anyway and adoption retries next run.`,
{ title: 'Queue-signature anchor not adopted' });
}
}

// Resolution order. OPEN beats closed, and the OLDEST open anchor
// beats a newer one: that is the issue the duplicates were closed
// against.
const openAnchors = candidates.filter((i) => i.state !== 'closed')
.sort((x, y) => x.number - y.number);
const closedAnchors = candidates.filter((i) => i.state === 'closed')
.sort((x, y) => y.number - x.number);
const existing = openAnchors[0] ?? null;
const closedAsDuplicate = !existing && closedAnchors[0]?.state_reason === 'duplicate'
? closedAnchors[0]
: null;
// A closed anchor that was NOT closed as a duplicate is a RESOLVED
// one: the flake was answered once and this key is ejecting PRs
// again. A new anchor is legitimate there, but it has to say which
// issue it regressed from or the previous answer is lost.
const resolvedBefore = closedAnchors.find((i) => i.state_reason !== 'duplicate') ?? null;
const priorAnchor = resolvedBefore && (!existing || existing.number > resolvedBefore.number)
? resolvedBefore
: null;

const prRows = [...a.prs.entries()].sort((x, y) => x[0] - y[0]).map(([pr, runs]) => {
const s = a.stackOf.get(pr);
const cell = !s || s.size < 2
Expand All@@ -572,6 +677,13 @@ jobs:
'that conversation; it is refreshed by the merge-queue-triage workflow on every',
'further ejection.',
'',
...(priorAnchor
? [
`⚠️ 同签名的上一个汇总 issue #${priorAnchor.number} 已经关闭(不是作为重复关闭的),`,
'之后这个文件又开始弹出 PR ⇒ 这是一次**回归**,上一轮的结论在那张 issue 里。',
'',
]
: []),
'| PR | stack | queue build |',
'|---|---|---|',
...prRows,
Expand DownExpand Up@@ -616,29 +728,6 @@ jobs:
anchorMarker,
].join('\n');

let existing = null;
let scanComplete = true;
let refusal = null;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, state: 'open', labels: ANCHOR_LABEL,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
// Identity is the body marker; the exact title is a second
// way in, because a body is the one channel GitHub is known
// to rewrite and an anchor that cannot be found is an anchor
// that gets duplicated.
existing = res.data.find((i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title)) ?? null;
if (existing || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) scanComplete = false;
}
} catch (error) {
scanComplete = false;
refusal = describe(error);
}

if (existing) {
try {
await github.rest.issues.update({
Expand All@@ -664,11 +753,25 @@ jobs:
continue;
}

if (closedAsDuplicate) {
// The newest anchor for this key was closed AS A DUPLICATE and no
// open anchor is left. Filing a fresh one here is exactly what
// feeds the loop — it would be closed as a duplicate in turn.
// GitHub's issue payload does not carry a duplicate's TARGET (only
// `state_reason: duplicate`), so the canonical cannot be followed
// from here; this says where the conversation was sent instead of
// guessing at a new home for it.
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 同签名的汇总 issue #${closedAsDuplicate.number} 已作为**重复**关闭,且没有仍然打开的同签名汇总 issue ⇒ 本次不新建(新建只会再被判重)。请到它指向的那张 issue 上谈,或重开 #${closedAsDuplicate.number}。`);
core.warning(`The newest anchor for ${a.key} (#${closedAsDuplicate.number}) was closed as a duplicate and no open anchor is left; not filing another one.`,
{ title: 'Queue-signature anchor not created' });
continue;
}

try {
const created = await github.rest.issues.create({
owner, repo, title, body, labels: [ANCHOR_LABEL],
owner, repo, title, body, labels: [ANCHOR_LABEL, ANCHOR_IDENTITY_LABEL],
});
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(新建)`);
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(${priorAnchor ? `新建,回归自已关闭的 #${priorAnchor.number}` : '新建'})`);
} catch (error) {
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 汇总 issue 新建失败(${describe(error)}),上面的名单就是全部事实。`);
core.warning(`Could not file the anchor issue for ${a.key} (${describe(error)}).`,
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 128 additions & 25 deletions .github/workflows/merge-queue-triage.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -550,13 +550,118 @@ jobs:
};

const ANCHOR_LABEL = 'finding';
// The anchor's IDENTITY label, and the only label the lookup keys on.
// `finding` is a TRIAGE-STATE label: first-touch grading takes it off by
// definition, so a lookup keyed on `finding` stops seeing an anchor the
// moment a human triages it and files a fresh one on the next ejection.
// This label is the machine's, is never graded off, and is what keeps
// the anchor findable for the rest of its life, open or closed.
const ANCHOR_IDENTITY_LABEL = 'queue-flake-anchor';
const MAX_ANCHOR_PAGES = 3;
const anchorNotes = [];
for (const a of aggregated.slice(0, 3)) {
const anchorMarker = `<!-- queue-signature-anchor:${a.key} -->`;
// Stable across refreshes — the victim count lives in the body, so
// a growing count cannot make the anchor unfindable by title.
const title = `Queue-flake anchor: ${a.key}`;

// ── Anchor lookup ─────────────────────────────────────────────
// The anchor's identity is the FILE KEY, carried by the body marker
// with the exact title as a second way in (a body is the one channel
// GitHub is known to rewrite). It is NOT the `finding` label and NOT
// `open`:
// - `finding` comes off the moment the anchor is graded, which is
// what first-touch grading MEANS. Keying on it made a properly
// triaged anchor invisible and turned every further ejection into
// a new anchor.
// - a CLOSED anchor is still evidence about this key: closed as a
// duplicate says the conversation moved; closed on its own merits
// says the next ejection is a REGRESSION, not a continuation.
const isThisAnchor = (i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title);
const labelNames = (i) => (i.labels ?? []).map((l) => (typeof l === 'string' ? l : l?.name));

// The passes, in order. `firstMatchWins` says whether a pass may stop
// at its first hit: the identity pass may NOT, because the resolution
// order below needs this key's CLOSED anchors as well as its open one.
// `adopts` marks the transitional pass — a bounded scan of OPEN issues
// that finds anchors filed before the identity label existed (a graded
// one carries neither label) and puts the identity label on them, so
// the first pass owns them from the next ejection on.
const ANCHOR_QUERIES = [
{ state: 'all', labels: ANCHOR_IDENTITY_LABEL, firstMatchWins: false, adopts: false },
{ state: 'open', firstMatchWins: true, adopts: true },
];

const candidates = [];
let adopted = null;
let scanComplete = true;
let refusal = null;
for (const [qi, q] of ANCHOR_QUERIES.entries()) {
// A later pass is skipped only once an OPEN anchor is in hand.
// Closed candidates are not enough to stop: a key can have a
// labelled closed duplicate and an UNLABELLED open survivor at the
// same time — that is exactly what a half-migrated key looks like
// — and stopping there would read the survivor as absent.
if (candidates.some((i) => i.state !== 'closed')) break;
const { firstMatchWins = false, adopts = false, ...params } = q;
const found = [];
let complete = true;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, ...params,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
found.push(...res.data.filter(isThisAnchor));
if ((firstMatchWins && found.length > 0) || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) complete = false;
}
} catch (error) {
complete = false;
refusal = describe(error);
}
// Completeness is the IDENTITY pass's argument — it is the bounded,
// label-keyed one. A truncated adoption pass is a missed migration,
// not an unestablished absence, and must not block a first anchor.
if (qi === 0) scanComplete = complete;
if (adopts && found.length > 0) adopted = found[0];
for (const i of found) {
if (!candidates.some((c) => c.number === i.number)) candidates.push(i);
}
}

if (adopted && !labelNames(adopted).includes(ANCHOR_IDENTITY_LABEL)) {
try {
await github.rest.issues.addLabels({
owner, repo, issue_number: adopted.number, labels: [ANCHOR_IDENTITY_LABEL],
});
} catch (error) {
core.warning(`Could not put the identity label on the existing anchor #${adopted.number} for ${a.key} (${describe(error)}); it is refreshed anyway and adoption retries next run.`,
{ title: 'Queue-signature anchor not adopted' });
}
}

// Resolution order. OPEN beats closed, and the OLDEST open anchor
// beats a newer one: that is the issue the duplicates were closed
// against.
const openAnchors = candidates.filter((i) => i.state !== 'closed')
.sort((x, y) => x.number - y.number);
const closedAnchors = candidates.filter((i) => i.state === 'closed')
.sort((x, y) => y.number - x.number);
const existing = openAnchors[0] ?? null;
const closedAsDuplicate = !existing && closedAnchors[0]?.state_reason === 'duplicate'
? closedAnchors[0]
: null;
// A closed anchor that was NOT closed as a duplicate is a RESOLVED
// one: the flake was answered once and this key is ejecting PRs
// again. A new anchor is legitimate there, but it has to say which
// issue it regressed from or the previous answer is lost.
const resolvedBefore = closedAnchors.find((i) => i.state_reason !== 'duplicate') ?? null;
const priorAnchor = resolvedBefore && (!existing || existing.number > resolvedBefore.number)
? resolvedBefore
: null;

const prRows = [...a.prs.entries()].sort((x, y) => x[0] - y[0]).map(([pr, runs]) => {
const s = a.stackOf.get(pr);
const cell = !s || s.size < 2
Expand All@@ -572,6 +677,13 @@ jobs:
'that conversation; it is refreshed by the merge-queue-triage workflow on every',
'further ejection.',
'',
...(priorAnchor
? [
`⚠️ 同签名的上一个汇总 issue #${priorAnchor.number} 已经关闭(不是作为重复关闭的),`,
'之后这个文件又开始弹出 PR ⇒ 这是一次**回归**,上一轮的结论在那张 issue 里。',
'',
]
: []),
'| PR | stack | queue build |',
'|---|---|---|',
...prRows,
Expand DownExpand Up@@ -616,29 +728,6 @@ jobs:
anchorMarker,
].join('\n');

let existing = null;
let scanComplete = true;
let refusal = null;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, state: 'open', labels: ANCHOR_LABEL,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
// Identity is the body marker; the exact title is a second
// way in, because a body is the one channel GitHub is known
// to rewrite and an anchor that cannot be found is an anchor
// that gets duplicated.
existing = res.data.find((i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title)) ?? null;
if (existing || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) scanComplete = false;
}
} catch (error) {
scanComplete = false;
refusal = describe(error);
}

if (existing) {
try {
await github.rest.issues.update({
Expand All@@ -664,11 +753,25 @@ jobs:
continue;
}

if (closedAsDuplicate) {
// The newest anchor for this key was closed AS A DUPLICATE and no
// open anchor is left. Filing a fresh one here is exactly what
// feeds the loop — it would be closed as a duplicate in turn.
// GitHub's issue payload does not carry a duplicate's TARGET (only
// `state_reason: duplicate`), so the canonical cannot be followed
// from here; this says where the conversation was sent instead of
// guessing at a new home for it.
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 同签名的汇总 issue #${closedAsDuplicate.number} 已作为**重复**关闭,且没有仍然打开的同签名汇总 issue ⇒ 本次不新建(新建只会再被判重)。请到它指向的那张 issue 上谈,或重开 #${closedAsDuplicate.number}。`);
core.warning(`The newest anchor for ${a.key} (#${closedAsDuplicate.number}) was closed as a duplicate and no open anchor is left; not filing another one.`,
{ title: 'Queue-signature anchor not created' });
continue;
}

try {
const created = await github.rest.issues.create({
owner, repo, title, body, labels: [ANCHOR_LABEL],
owner, repo, title, body, labels: [ANCHOR_LABEL, ANCHOR_IDENTITY_LABEL],
});
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(新建)`);
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(${priorAnchor ? `新建,回归自已关闭的 #${priorAnchor.number}` : '新建'})`);
} catch (error) {
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 汇总 issue 新建失败(${describe(error)}),上面的名单就是全部事实。`);
core.warning(`Could not file the anchor issue for ${a.key} (${describe(error)}).`,
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 128 additions & 25 deletions .github/workflows/merge-queue-triage.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -550,13 +550,118 @@ jobs:
};

const ANCHOR_LABEL = 'finding';
// The anchor's IDENTITY label, and the only label the lookup keys on.
// `finding` is a TRIAGE-STATE label: first-touch grading takes it off by
// definition, so a lookup keyed on `finding` stops seeing an anchor the
// moment a human triages it and files a fresh one on the next ejection.
// This label is the machine's, is never graded off, and is what keeps
// the anchor findable for the rest of its life, open or closed.
const ANCHOR_IDENTITY_LABEL = 'queue-flake-anchor';
const MAX_ANCHOR_PAGES = 3;
const anchorNotes = [];
for (const a of aggregated.slice(0, 3)) {
const anchorMarker = `<!-- queue-signature-anchor:${a.key} -->`;
// Stable across refreshes — the victim count lives in the body, so
// a growing count cannot make the anchor unfindable by title.
const title = `Queue-flake anchor: ${a.key}`;

// ── Anchor lookup ─────────────────────────────────────────────
// The anchor's identity is the FILE KEY, carried by the body marker
// with the exact title as a second way in (a body is the one channel
// GitHub is known to rewrite). It is NOT the `finding` label and NOT
// `open`:
// - `finding` comes off the moment the anchor is graded, which is
// what first-touch grading MEANS. Keying on it made a properly
// triaged anchor invisible and turned every further ejection into
// a new anchor.
// - a CLOSED anchor is still evidence about this key: closed as a
// duplicate says the conversation moved; closed on its own merits
// says the next ejection is a REGRESSION, not a continuation.
const isThisAnchor = (i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title);
const labelNames = (i) => (i.labels ?? []).map((l) => (typeof l === 'string' ? l : l?.name));

// The passes, in order. `firstMatchWins` says whether a pass may stop
// at its first hit: the identity pass may NOT, because the resolution
// order below needs this key's CLOSED anchors as well as its open one.
// `adopts` marks the transitional pass — a bounded scan of OPEN issues
// that finds anchors filed before the identity label existed (a graded
// one carries neither label) and puts the identity label on them, so
// the first pass owns them from the next ejection on.
const ANCHOR_QUERIES = [
{ state: 'all', labels: ANCHOR_IDENTITY_LABEL, firstMatchWins: false, adopts: false },
{ state: 'open', firstMatchWins: true, adopts: true },
];

const candidates = [];
let adopted = null;
let scanComplete = true;
let refusal = null;
for (const [qi, q] of ANCHOR_QUERIES.entries()) {
// A later pass is skipped only once an OPEN anchor is in hand.
// Closed candidates are not enough to stop: a key can have a
// labelled closed duplicate and an UNLABELLED open survivor at the
// same time — that is exactly what a half-migrated key looks like
// — and stopping there would read the survivor as absent.
if (candidates.some((i) => i.state !== 'closed')) break;
const { firstMatchWins = false, adopts = false, ...params } = q;
const found = [];
let complete = true;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, ...params,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
found.push(...res.data.filter(isThisAnchor));
if ((firstMatchWins && found.length > 0) || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) complete = false;
}
} catch (error) {
complete = false;
refusal = describe(error);
}
// Completeness is the IDENTITY pass's argument — it is the bounded,
// label-keyed one. A truncated adoption pass is a missed migration,
// not an unestablished absence, and must not block a first anchor.
if (qi === 0) scanComplete = complete;
if (adopts && found.length > 0) adopted = found[0];
for (const i of found) {
if (!candidates.some((c) => c.number === i.number)) candidates.push(i);
}
}

if (adopted && !labelNames(adopted).includes(ANCHOR_IDENTITY_LABEL)) {
try {
await github.rest.issues.addLabels({
owner, repo, issue_number: adopted.number, labels: [ANCHOR_IDENTITY_LABEL],
});
} catch (error) {
core.warning(`Could not put the identity label on the existing anchor #${adopted.number} for ${a.key} (${describe(error)}); it is refreshed anyway and adoption retries next run.`,
{ title: 'Queue-signature anchor not adopted' });
}
}

// Resolution order. OPEN beats closed, and the OLDEST open anchor
// beats a newer one: that is the issue the duplicates were closed
// against.
const openAnchors = candidates.filter((i) => i.state !== 'closed')
.sort((x, y) => x.number - y.number);
const closedAnchors = candidates.filter((i) => i.state === 'closed')
.sort((x, y) => y.number - x.number);
const existing = openAnchors[0] ?? null;
const closedAsDuplicate = !existing && closedAnchors[0]?.state_reason === 'duplicate'
? closedAnchors[0]
: null;
// A closed anchor that was NOT closed as a duplicate is a RESOLVED
// one: the flake was answered once and this key is ejecting PRs
// again. A new anchor is legitimate there, but it has to say which
// issue it regressed from or the previous answer is lost.
const resolvedBefore = closedAnchors.find((i) => i.state_reason !== 'duplicate') ?? null;
const priorAnchor = resolvedBefore && (!existing || existing.number > resolvedBefore.number)
? resolvedBefore
: null;

const prRows = [...a.prs.entries()].sort((x, y) => x[0] - y[0]).map(([pr, runs]) => {
const s = a.stackOf.get(pr);
const cell = !s || s.size < 2
Expand All@@ -572,6 +677,13 @@ jobs:
'that conversation; it is refreshed by the merge-queue-triage workflow on every',
'further ejection.',
'',
...(priorAnchor
? [
`⚠️ 同签名的上一个汇总 issue #${priorAnchor.number} 已经关闭(不是作为重复关闭的),`,
'之后这个文件又开始弹出 PR ⇒ 这是一次**回归**,上一轮的结论在那张 issue 里。',
'',
]
: []),
'| PR | stack | queue build |',
'|---|---|---|',
...prRows,
Expand DownExpand Up@@ -616,29 +728,6 @@ jobs:
anchorMarker,
].join('\n');

let existing = null;
let scanComplete = true;
let refusal = null;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, state: 'open', labels: ANCHOR_LABEL,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
// Identity is the body marker; the exact title is a second
// way in, because a body is the one channel GitHub is known
// to rewrite and an anchor that cannot be found is an anchor
// that gets duplicated.
existing = res.data.find((i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title)) ?? null;
if (existing || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) scanComplete = false;
}
} catch (error) {
scanComplete = false;
refusal = describe(error);
}

if (existing) {
try {
await github.rest.issues.update({
Expand All@@ -664,11 +753,25 @@ jobs:
continue;
}

if (closedAsDuplicate) {
// The newest anchor for this key was closed AS A DUPLICATE and no
// open anchor is left. Filing a fresh one here is exactly what
// feeds the loop — it would be closed as a duplicate in turn.
// GitHub's issue payload does not carry a duplicate's TARGET (only
// `state_reason: duplicate`), so the canonical cannot be followed
// from here; this says where the conversation was sent instead of
// guessing at a new home for it.
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 同签名的汇总 issue #${closedAsDuplicate.number} 已作为**重复**关闭,且没有仍然打开的同签名汇总 issue ⇒ 本次不新建(新建只会再被判重)。请到它指向的那张 issue 上谈,或重开 #${closedAsDuplicate.number}。`);
core.warning(`The newest anchor for ${a.key} (#${closedAsDuplicate.number}) was closed as a duplicate and no open anchor is left; not filing another one.`,
{ title: 'Queue-signature anchor not created' });
continue;
}

try {
const created = await github.rest.issues.create({
owner, repo, title, body, labels: [ANCHOR_LABEL],
owner, repo, title, body, labels: [ANCHOR_LABEL, ANCHOR_IDENTITY_LABEL],
});
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(新建)`);
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(${priorAnchor ? `新建,回归自已关闭的 #${priorAnchor.number}` : '新建'})`);
} catch (error) {
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 汇总 issue 新建失败(${describe(error)}),上面的名单就是全部事实。`);
core.warning(`Could not file the anchor issue for ${a.key} (${describe(error)}).`,
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 128 additions & 25 deletions .github/workflows/merge-queue-triage.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -550,13 +550,118 @@ jobs:
};

const ANCHOR_LABEL = 'finding';
// The anchor's IDENTITY label, and the only label the lookup keys on.
// `finding` is a TRIAGE-STATE label: first-touch grading takes it off by
// definition, so a lookup keyed on `finding` stops seeing an anchor the
// moment a human triages it and files a fresh one on the next ejection.
// This label is the machine's, is never graded off, and is what keeps
// the anchor findable for the rest of its life, open or closed.
const ANCHOR_IDENTITY_LABEL = 'queue-flake-anchor';
const MAX_ANCHOR_PAGES = 3;
const anchorNotes = [];
for (const a of aggregated.slice(0, 3)) {
const anchorMarker = `<!-- queue-signature-anchor:${a.key} -->`;
// Stable across refreshes — the victim count lives in the body, so
// a growing count cannot make the anchor unfindable by title.
const title = `Queue-flake anchor: ${a.key}`;

// ── Anchor lookup ─────────────────────────────────────────────
// The anchor's identity is the FILE KEY, carried by the body marker
// with the exact title as a second way in (a body is the one channel
// GitHub is known to rewrite). It is NOT the `finding` label and NOT
// `open`:
// - `finding` comes off the moment the anchor is graded, which is
// what first-touch grading MEANS. Keying on it made a properly
// triaged anchor invisible and turned every further ejection into
// a new anchor.
// - a CLOSED anchor is still evidence about this key: closed as a
// duplicate says the conversation moved; closed on its own merits
// says the next ejection is a REGRESSION, not a continuation.
const isThisAnchor = (i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title);
const labelNames = (i) => (i.labels ?? []).map((l) => (typeof l === 'string' ? l : l?.name));

// The passes, in order. `firstMatchWins` says whether a pass may stop
// at its first hit: the identity pass may NOT, because the resolution
// order below needs this key's CLOSED anchors as well as its open one.
// `adopts` marks the transitional pass — a bounded scan of OPEN issues
// that finds anchors filed before the identity label existed (a graded
// one carries neither label) and puts the identity label on them, so
// the first pass owns them from the next ejection on.
const ANCHOR_QUERIES = [
{ state: 'all', labels: ANCHOR_IDENTITY_LABEL, firstMatchWins: false, adopts: false },
{ state: 'open', firstMatchWins: true, adopts: true },
];

const candidates = [];
let adopted = null;
let scanComplete = true;
let refusal = null;
for (const [qi, q] of ANCHOR_QUERIES.entries()) {
// A later pass is skipped only once an OPEN anchor is in hand.
// Closed candidates are not enough to stop: a key can have a
// labelled closed duplicate and an UNLABELLED open survivor at the
// same time — that is exactly what a half-migrated key looks like
// — and stopping there would read the survivor as absent.
if (candidates.some((i) => i.state !== 'closed')) break;
const { firstMatchWins = false, adopts = false, ...params } = q;
const found = [];
let complete = true;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, ...params,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
found.push(...res.data.filter(isThisAnchor));
if ((firstMatchWins && found.length > 0) || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) complete = false;
}
} catch (error) {
complete = false;
refusal = describe(error);
}
// Completeness is the IDENTITY pass's argument — it is the bounded,
// label-keyed one. A truncated adoption pass is a missed migration,
// not an unestablished absence, and must not block a first anchor.
if (qi === 0) scanComplete = complete;
if (adopts && found.length > 0) adopted = found[0];
for (const i of found) {
if (!candidates.some((c) => c.number === i.number)) candidates.push(i);
}
}

if (adopted && !labelNames(adopted).includes(ANCHOR_IDENTITY_LABEL)) {
try {
await github.rest.issues.addLabels({
owner, repo, issue_number: adopted.number, labels: [ANCHOR_IDENTITY_LABEL],
});
} catch (error) {
core.warning(`Could not put the identity label on the existing anchor #${adopted.number} for ${a.key} (${describe(error)}); it is refreshed anyway and adoption retries next run.`,
{ title: 'Queue-signature anchor not adopted' });
}
}

// Resolution order. OPEN beats closed, and the OLDEST open anchor
// beats a newer one: that is the issue the duplicates were closed
// against.
const openAnchors = candidates.filter((i) => i.state !== 'closed')
.sort((x, y) => x.number - y.number);
const closedAnchors = candidates.filter((i) => i.state === 'closed')
.sort((x, y) => y.number - x.number);
const existing = openAnchors[0] ?? null;
const closedAsDuplicate = !existing && closedAnchors[0]?.state_reason === 'duplicate'
? closedAnchors[0]
: null;
// A closed anchor that was NOT closed as a duplicate is a RESOLVED
// one: the flake was answered once and this key is ejecting PRs
// again. A new anchor is legitimate there, but it has to say which
// issue it regressed from or the previous answer is lost.
const resolvedBefore = closedAnchors.find((i) => i.state_reason !== 'duplicate') ?? null;
const priorAnchor = resolvedBefore && (!existing || existing.number > resolvedBefore.number)
? resolvedBefore
: null;

const prRows = [...a.prs.entries()].sort((x, y) => x[0] - y[0]).map(([pr, runs]) => {
const s = a.stackOf.get(pr);
const cell = !s || s.size < 2
Expand All@@ -572,6 +677,13 @@ jobs:
'that conversation; it is refreshed by the merge-queue-triage workflow on every',
'further ejection.',
'',
...(priorAnchor
? [
`⚠️ 同签名的上一个汇总 issue #${priorAnchor.number} 已经关闭(不是作为重复关闭的),`,
'之后这个文件又开始弹出 PR ⇒ 这是一次**回归**,上一轮的结论在那张 issue 里。',
'',
]
: []),
'| PR | stack | queue build |',
'|---|---|---|',
...prRows,
Expand DownExpand Up@@ -616,29 +728,6 @@ jobs:
anchorMarker,
].join('\n');

let existing = null;
let scanComplete = true;
let refusal = null;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, state: 'open', labels: ANCHOR_LABEL,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
// Identity is the body marker; the exact title is a second
// way in, because a body is the one channel GitHub is known
// to rewrite and an anchor that cannot be found is an anchor
// that gets duplicated.
existing = res.data.find((i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title)) ?? null;
if (existing || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) scanComplete = false;
}
} catch (error) {
scanComplete = false;
refusal = describe(error);
}

if (existing) {
try {
await github.rest.issues.update({
Expand All@@ -664,11 +753,25 @@ jobs:
continue;
}

if (closedAsDuplicate) {
// The newest anchor for this key was closed AS A DUPLICATE and no
// open anchor is left. Filing a fresh one here is exactly what
// feeds the loop — it would be closed as a duplicate in turn.
// GitHub's issue payload does not carry a duplicate's TARGET (only
// `state_reason: duplicate`), so the canonical cannot be followed
// from here; this says where the conversation was sent instead of
// guessing at a new home for it.
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 同签名的汇总 issue #${closedAsDuplicate.number} 已作为**重复**关闭,且没有仍然打开的同签名汇总 issue ⇒ 本次不新建(新建只会再被判重)。请到它指向的那张 issue 上谈,或重开 #${closedAsDuplicate.number}。`);
core.warning(`The newest anchor for ${a.key} (#${closedAsDuplicate.number}) was closed as a duplicate and no open anchor is left; not filing another one.`,
{ title: 'Queue-signature anchor not created' });
continue;
}

try {
const created = await github.rest.issues.create({
owner, repo, title, body, labels: [ANCHOR_LABEL],
owner, repo, title, body, labels: [ANCHOR_LABEL, ANCHOR_IDENTITY_LABEL],
});
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(新建)`);
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(${priorAnchor ? `新建,回归自已关闭的 #${priorAnchor.number}` : '新建'})`);
} catch (error) {
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 汇总 issue 新建失败(${describe(error)}),上面的名单就是全部事实。`);
core.warning(`Could not file the anchor issue for ${a.key} (${describe(error)}).`,
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 128 additions & 25 deletions .github/workflows/merge-queue-triage.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -550,13 +550,118 @@ jobs:
};

const ANCHOR_LABEL = 'finding';
// The anchor's IDENTITY label, and the only label the lookup keys on.
// `finding` is a TRIAGE-STATE label: first-touch grading takes it off by
// definition, so a lookup keyed on `finding` stops seeing an anchor the
// moment a human triages it and files a fresh one on the next ejection.
// This label is the machine's, is never graded off, and is what keeps
// the anchor findable for the rest of its life, open or closed.
const ANCHOR_IDENTITY_LABEL = 'queue-flake-anchor';
const MAX_ANCHOR_PAGES = 3;
const anchorNotes = [];
for (const a of aggregated.slice(0, 3)) {
const anchorMarker = `<!-- queue-signature-anchor:${a.key} -->`;
// Stable across refreshes — the victim count lives in the body, so
// a growing count cannot make the anchor unfindable by title.
const title = `Queue-flake anchor: ${a.key}`;

// ── Anchor lookup ─────────────────────────────────────────────
// The anchor's identity is the FILE KEY, carried by the body marker
// with the exact title as a second way in (a body is the one channel
// GitHub is known to rewrite). It is NOT the `finding` label and NOT
// `open`:
// - `finding` comes off the moment the anchor is graded, which is
// what first-touch grading MEANS. Keying on it made a properly
// triaged anchor invisible and turned every further ejection into
// a new anchor.
// - a CLOSED anchor is still evidence about this key: closed as a
// duplicate says the conversation moved; closed on its own merits
// says the next ejection is a REGRESSION, not a continuation.
const isThisAnchor = (i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title);
const labelNames = (i) => (i.labels ?? []).map((l) => (typeof l === 'string' ? l : l?.name));

// The passes, in order. `firstMatchWins` says whether a pass may stop
// at its first hit: the identity pass may NOT, because the resolution
// order below needs this key's CLOSED anchors as well as its open one.
// `adopts` marks the transitional pass — a bounded scan of OPEN issues
// that finds anchors filed before the identity label existed (a graded
// one carries neither label) and puts the identity label on them, so
// the first pass owns them from the next ejection on.
const ANCHOR_QUERIES = [
{ state: 'all', labels: ANCHOR_IDENTITY_LABEL, firstMatchWins: false, adopts: false },
{ state: 'open', firstMatchWins: true, adopts: true },
];

const candidates = [];
let adopted = null;
let scanComplete = true;
let refusal = null;
for (const [qi, q] of ANCHOR_QUERIES.entries()) {
// A later pass is skipped only once an OPEN anchor is in hand.
// Closed candidates are not enough to stop: a key can have a
// labelled closed duplicate and an UNLABELLED open survivor at the
// same time — that is exactly what a half-migrated key looks like
// — and stopping there would read the survivor as absent.
if (candidates.some((i) => i.state !== 'closed')) break;
const { firstMatchWins = false, adopts = false, ...params } = q;
const found = [];
let complete = true;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, ...params,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
found.push(...res.data.filter(isThisAnchor));
if ((firstMatchWins && found.length > 0) || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) complete = false;
}
} catch (error) {
complete = false;
refusal = describe(error);
}
// Completeness is the IDENTITY pass's argument — it is the bounded,
// label-keyed one. A truncated adoption pass is a missed migration,
// not an unestablished absence, and must not block a first anchor.
if (qi === 0) scanComplete = complete;
if (adopts && found.length > 0) adopted = found[0];
for (const i of found) {
if (!candidates.some((c) => c.number === i.number)) candidates.push(i);
}
}

if (adopted && !labelNames(adopted).includes(ANCHOR_IDENTITY_LABEL)) {
try {
await github.rest.issues.addLabels({
owner, repo, issue_number: adopted.number, labels: [ANCHOR_IDENTITY_LABEL],
});
} catch (error) {
core.warning(`Could not put the identity label on the existing anchor #${adopted.number} for ${a.key} (${describe(error)}); it is refreshed anyway and adoption retries next run.`,
{ title: 'Queue-signature anchor not adopted' });
}
}

// Resolution order. OPEN beats closed, and the OLDEST open anchor
// beats a newer one: that is the issue the duplicates were closed
// against.
const openAnchors = candidates.filter((i) => i.state !== 'closed')
.sort((x, y) => x.number - y.number);
const closedAnchors = candidates.filter((i) => i.state === 'closed')
.sort((x, y) => y.number - x.number);
const existing = openAnchors[0] ?? null;
const closedAsDuplicate = !existing && closedAnchors[0]?.state_reason === 'duplicate'
? closedAnchors[0]
: null;
// A closed anchor that was NOT closed as a duplicate is a RESOLVED
// one: the flake was answered once and this key is ejecting PRs
// again. A new anchor is legitimate there, but it has to say which
// issue it regressed from or the previous answer is lost.
const resolvedBefore = closedAnchors.find((i) => i.state_reason !== 'duplicate') ?? null;
const priorAnchor = resolvedBefore && (!existing || existing.number > resolvedBefore.number)
? resolvedBefore
: null;

const prRows = [...a.prs.entries()].sort((x, y) => x[0] - y[0]).map(([pr, runs]) => {
const s = a.stackOf.get(pr);
const cell = !s || s.size < 2
Expand All@@ -572,6 +677,13 @@ jobs:
'that conversation; it is refreshed by the merge-queue-triage workflow on every',
'further ejection.',
'',
...(priorAnchor
? [
`⚠️ 同签名的上一个汇总 issue #${priorAnchor.number} 已经关闭(不是作为重复关闭的),`,
'之后这个文件又开始弹出 PR ⇒ 这是一次**回归**,上一轮的结论在那张 issue 里。',
'',
]
: []),
'| PR | stack | queue build |',
'|---|---|---|',
...prRows,
Expand DownExpand Up@@ -616,29 +728,6 @@ jobs:
anchorMarker,
].join('\n');

let existing = null;
let scanComplete = true;
let refusal = null;
try {
for (let p = 1; p <= MAX_ANCHOR_PAGES; p++) {
const res = await github.rest.issues.listForRepo({
owner, repo, state: 'open', labels: ANCHOR_LABEL,
sort: 'created', direction: 'desc', per_page: 100, page: p,
});
// Identity is the body marker; the exact title is a second
// way in, because a body is the one channel GitHub is known
// to rewrite and an anchor that cannot be found is an anchor
// that gets duplicated.
existing = res.data.find((i) => !i.pull_request
&& (String(i.body ?? '').includes(anchorMarker) || i.title === title)) ?? null;
if (existing || res.data.length < 100) break;
if (p === MAX_ANCHOR_PAGES) scanComplete = false;
}
} catch (error) {
scanComplete = false;
refusal = describe(error);
}

if (existing) {
try {
await github.rest.issues.update({
Expand All@@ -664,11 +753,25 @@ jobs:
continue;
}

if (closedAsDuplicate) {
// The newest anchor for this key was closed AS A DUPLICATE and no
// open anchor is left. Filing a fresh one here is exactly what
// feeds the loop — it would be closed as a duplicate in turn.
// GitHub's issue payload does not carry a duplicate's TARGET (only
// `state_reason: duplicate`), so the canonical cannot be followed
// from here; this says where the conversation was sent instead of
// guessing at a new home for it.
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 同签名的汇总 issue #${closedAsDuplicate.number} 已作为**重复**关闭,且没有仍然打开的同签名汇总 issue ⇒ 本次不新建(新建只会再被判重)。请到它指向的那张 issue 上谈,或重开 #${closedAsDuplicate.number}。`);
core.warning(`The newest anchor for ${a.key} (#${closedAsDuplicate.number}) was closed as a duplicate and no open anchor is left; not filing another one.`,
{ title: 'Queue-signature anchor not created' });
continue;
}

try {
const created = await github.rest.issues.create({
owner, repo, title, body, labels: [ANCHOR_LABEL],
owner, repo, title, body, labels: [ANCHOR_LABEL, ANCHOR_IDENTITY_LABEL],
});
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(新建)`);
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。汇总 issue:#${created.data.number}(${priorAnchor ? `新建,回归自已关闭的 #${priorAnchor.number}` : '新建'})`);
} catch (error) {
anchorNotes.push(`- \`${a.key}\` — ${victimPhrase(a)}。⚠️ 汇总 issue 新建失败(${describe(error)}),上面的名单就是全部事实。`);
core.warning(`Could not file the anchor issue for ${a.key} (${describe(error)}).`,
Expand Down
Loading
Loading