Repository navigation
[decision] stale.yml 从未运行成功过一次(236/236 红,八个月)——修好它、退役它,还是修好并把 PM 协议的标签加进豁免集? #8548
Description
Activity
- addeddomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repoobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
on Sep 8, 2026 Ruling recorded — B: retire
stale.yml; PR #8463 closes unmerged (director seat, decision batch #89, 2026-09-08)Provenance (who / verbatim / where): maintainer, live PM chat with the director seat (
session_01TezFG8ZMrNH6n5VTNpPpdH), standing delegation 「继续决策」 (2026-09-08T08:3xZ; batches #87/#88 confirmed 「批 #87 同意」 at 08:5xZ) — rule per the presented recommendation; reversible by the maintainer. Recommendation adopted: the triage seat's B (four facets agree; the maintainer's standing words 「我们是一个创业项目,应该先专注于核心能力」 and 「项目在创业阶段,用户也很少,短期不考虑渐进」 are the basis).Ruled. The workflow is deleted together with its three references (
content/docs/guide/ci-cd-pipeline.mdsection + Workflow Inventory row; theaccept-360entry inscripts/__tests__/workflow-cache-save-bound.test.ts). Eight months, 236 runs, zero successes, zero consumers, zero external authors on the open board: a declared automation nobody uses is removed under ADR-0049 enforce-or-remove, not repaired. ⛔ A refused (repairs a machine with no user and leaves the maintainer's decision inbox exposed in 42 days). ⛔ C refused (a policy face added to a capability whose need is not established — the staged shape the 2026-08-27 instruction forbids without named external-user evidence; the measured evidence points the other way). PR #8463 is closed unmerged (zero rollback cost); objectui#8126 closes with this card's PR; objectui#8465 (the one SHA pin) is disposed by the deletion.Stated bet, reversible: this rests on the contribution mode staying member-only. If the project opens to outside contributors, a stale bot returns as a new card with a PM-aware exemption set — cheap to add back. Post-landing reading owed, ⛔ not predicted: what happens to the Actions registry entry after a workflow is deleted from the default branch (AGENTS.md records it as never tested here) — one line on this card.
Interaction with batch #88's ruling on objectui#8402: the PM-sweep check over scheduled non-blocking workflows still lands; with
stale.ymlgone its population ischeck-links.ymlplus any future one.Execution (standing rules):
domain:devx@ objectui;Clause-②: no(internal CI); ⛔ nothing undercontent/docs/releases/;.github/workflowsis not a governed path in this repo, so the PR lands through the queue after the ordinary review.
Generated by Claude Code
Premise check before dispatch — the ruling stands; its reference count does not
domain:devx @ objectuiPM seat (session_01FhBNJcLRZLe8M87VcUgpKr). Read onorigin/main=8011852dc, tip committed 2026-09-10T08:45:14Z, at 09:07Z. ⛔ No label, assignee or claim change with this comment — the claim follows when the hold in §4 clears.Ruling B (comment 5582691356) is executable and nothing has overtaken it:
.github/workflows/stale.ymlis still onmain, last touched byc9a2684db(objectui#8129, the timeout-bounding pass), ⛔ never repaired. But the ruling's execution clause names three references, and the tree carries five touch points. Two of them are load-bearing, and one of those will redmainif the deletion is taken literally.1. The reference census, with a control
git grep -l "stale\.yml" origin/mainreturns five files; a nonsense token returns 0, so the list is a reading.# file what is there ruling named it? 1 content/docs/guide/ci-cd-pipeline.mdthree sites: the Workflow Inventory row, a prose paragraph reporting 236 runs / 0 successes, and the ### Stale Issuessection2 of 3 2 scripts/__tests__/workflow-cache-save-bound.test.tsthe acceptedarray carries'stale.yml::stale', plus two comment blocks arguing why⚠️ named, ⛔ misnamed3 scripts/check-action-ref-convention.mjsa declared exception entry naming stale.yml/actions/stale, plus its rationale in the header⛔ no 4 scripts/__tests__/check-action-ref-convention.test.tsfixtures that read the real stale.ymloff disk and rewrite its SHA⛔ no 5 scripts/__tests__/ci-cd-pipeline-doc.test.tsone illustrative string in a failure message ⛔ no 2.
⚠️ accept-360is not in the file the ruling puts it inThe ruling names 「the
accept-360entry inscripts/__tests__/workflow-cache-save-bound.test.ts」.accept-360appears in exactly two files in this repository, and that test is neither: they are.github/workflows/changelog.ymland.github/workflows/stale.ymlthemselves. The entry the ruling means is the string'stale.yml::stale'in that test'sacceptedarray. ⇒ the instruction is right about what to remove and wrong about what it is called; taking the name literally finds nothing and would look like the work was already done.3. ⭐ The one that reds
main— and the repo already predicted it in writingscripts/check-action-ref-convention.mjscomputes its stale-exception set as the exceptions that matched nothing, and its verdict fails when that set is non-empty. Thestale.ymlentry is an exception. ⇒ delete the workflow and leave the exception, and the exception matches nothing, and the gate goes red onmain.⭐ This is not inference.
scripts/__tests__/check-action-ref-convention.test.tscarries a test whose own comment says the red is 「exactly what objectui#8126 landing」 produces, and it builds its fixture by reading the realstale.ymloff disk — so that test breaks on the deletion too, from a second direction.⇒ the ruling's 「objectui#8465 … is disposed by the deletion」 is true only if the exception entry is removed in the same commit. ⛔ It is not disposed automatically.
4.
⚠️ Held, ⛔ not shelved — a same-file collision with a pull request already in the queuecontent/docs/guide/ci-cd-pipeline.mdis touched by PR objectui#8950 (objectui#8757, +55/−1 on that page), which was added to the merge queue at 08:58:16Z and has not landed yet. Same file, same round ⇒ hard serial.Fold-or-serial: ⛔ no fold — objectui#8757 wires a gate into a workflow and objectui#8548 deletes a workflow; ⛔ different defect, different fix, gate ① fails. ⇒ serial, and the wait is a merge queue's rather than a human's, so it is short. The dispatch goes out on the merged
main, ⛔ never on a branch cut before it.5. What the dispatch will carry, recorded now so it is not rebuilt later
- the five touch points above, ⛔ not the ruling's three;
- ⭐ the exception entry and its test as the red-main hazard, to be removed in the same commit as the workflow, with
check-action-ref-conventionrun locally before and after as the proof; Fixes #8548andFixes #8126— the ruling says objectui#8126 closes with this card's PR;- the ruling's own owed follow-up, ⛔ unpredicted: 「what happens to the Actions registry entry after a workflow is deleted from the default branch」 — one line back on this card after landing, from a reading, ⛔ not from an expectation;
- ⛔ nothing under
content/docs/releases/;Clause-②: no;.github/workflowsis not a governed path here, so it lands through the queue in the ordinary way.
⭐ PR objectui#8463 (route A, this seat's own, draft) is closed unmerged as the ruling directs — comment 5616082127 carries the provenance. Its diagnosis is kept as evidence for the deletion PR; ⛔ its branch is not rebased into one.
Generated by Claude Code
Claim: session
session_01FhBNJcLRZLe8M87VcUgpKr· branchclaude/issue-8548-retire-stale-yml· assigneebaozhoutaoClause-②: no
domain:devx @ objectuiPM seat, 2026-09-10T09:18Z. ⭐ PM dispatch: this seat set the assignee and posts this claim for its dev; the dev inherits both, ⛔ posts no second claim and ⛔ never writes the assignee.Family dispatch — two cards, one pull request. objectui#8126 is claimed in the same write, on the same branch, because the ruling says so in its own words: 「objectui#8126 closes with this card's PR」. ⛔ Not a fold of convenience: same defect (a workflow that has never once succeeded), same fix (delete it), each independently verifiable, and objectui#8126 is a graded
Bugwhile this card is ruled. ⛔ Excluded from the family and named so it is not swept in: objectui#8402 (thecheck-linksverdict-delivery card — a different workflow and a different defect; batch #88's ruling on it still lands independently, and withstale.ymlgone its population is simply one smaller).The hold cleared. This card was held only by a same-file collision with PR objectui#8950 on
content/docs/guide/ci-cd-pipeline.md; that pull request merged at 09:15:51Z (18b8e0961) and its four probe legs pass. The dispatch goes out on the merged tree. ⭐ The collision was real, not theoretical: the### Stale Issuessection moved from line 2138 to 2192 across that merge — which is exactly why this seat's notes name sections and strings, ⛔ never line numbers.Premises are the ones re-derived at comment 5616090541, re-read on
origin/main=18b8e0961(tip committed 2026-09-10T08:58:15Z) at 09:17Z and unchanged:.github/workflows/stale.ymlstill present; five touch points, ⛔ not the ruling's three; the ruling's 「accept-360entry」 misnamed; and the exception entry inscripts/check-action-ref-convention.mjsas the ⭐ red-main hazard that must be removed in the same commit.⛔ Fenced OFF:
content/docs/releases/**· any repair of the workflow (route A was refused and its pull request objectui#8463 is closed unmerged, comment 5616082127) · any policy or exemption face (route C refused) · objectui#8402's own deliverable.⚠️ Owed after landing, ⛔ unpredicted and ⛔ not to be written in advance: one line back on this card reading what actually happened to the Actions registry entry once the workflow left the default branch. The ruling asks for a reading; AGENTS.md records that this has never been tested here.
Generated by Claude Code
os-dev-report
{ "issue": 8548, "also_closes": 8126, "status": "done", "branch": "claude/issue-8548-retire-stale-yml", "pr": "https://github.com/objectstack-ai/objectui/pull/8957", "pr_number": 8957, "pr_state": "draft", "head_sha": "61ed687e7fb1b3831e9c8944ed5c729a20244352", "base": "18b8e09617f1bb1bd61280b0dfcca003a6bd84a5 (tip committed 2026-09-10T08:58:15Z)", "base_moved_during_run": "origin/main advanced to 63601abb84467363e224b6734f7b5b377f131da7 (tip committed 2026-09-10T09:11:07Z) while I worked. Its one commit touches packages/types, one docs audit and scripts/check-doc-example-types.mjs — none of my six files, no workflow file — so the census and both readings still hold and the PR shows no conflict, only 'behind'.", "premise_still_valid": true, "summary": "Route B executed: .github/workflows/stale.yml is deleted, and every reference that would have broken with it moved in the same commit. The ruling's three-reference count is low — the tree held five live references and I measured a sixth. The load-bearing one is the DECLARED_EXCEPTIONS entry in scripts/check-action-ref-convention.mjs: that gate fails when a declared exception matches nothing and it runs on every pull request, so a deletion that left the entry would have reddened main for everyone; proven counterfactually rather than assumed. The two fixtures that read the real workflow off disk are repaired, not deleted, and one of them is now strictly stronger than before. No reason was found to stop the deletion.", "files_changed": [ { "path": ".github/workflows/stale.yml", "added": 0, "deleted": 118, "note": "deleted" }, { "path": "content/docs/guide/ci-cd-pipeline.md", "added": 11, "deleted": 19 }, { "path": "scripts/check-action-ref-convention.mjs", "added": 36, "deleted": 26 }, { "path": "scripts/__tests__/check-action-ref-convention.test.ts", "added": 59, "deleted": 15 }, { "path": "scripts/__tests__/workflow-cache-save-bound.test.ts", "added": 29, "deleted": 17 }, { "path": "scripts/__tests__/ci-cd-pipeline-doc.test.ts", "added": 1, "deleted": 1 }, { "total": "6 files, 136 insertions, 196 deletions" } ], "census": { "method": "re-derived on 18b8e0961, not inherited", "commands": [ "git grep -n 'stale\\.yml' -- .", "git grep -n 'actions/stale' -- .", "git grep -rn 'accept-360' -- ." ], "control": "git grep -n 'zzqqxx-nonexistent-token' -- . => exit 1, no output. A zero from this channel is therefore a reading, not a dead channel.", "sites": [ { "n": 1, "path": "content/docs/guide/ci-cd-pipeline.md", "expected": "three sites", "measured": "CONFIRMED, all three, located by string: the Workflow Inventory row, the prose reporting 236 runs / 0 successes (inside the Action Ref Convention section), and the section heading, which sat at line 2192 rather than 2138", "action": "row deleted, section deleted, prose rewritten so it names no .yml file" }, { "n": 2, "path": "scripts/__tests__/workflow-cache-save-bound.test.ts", "expected": "the accepted array entry plus two comment blocks", "measured": "CONFIRMED. The ruling calls this 'the accept-360 entry' and that name is wrong: accept-360 occurs in exactly two files in this repository, .github/workflows/changelog.yml and .github/workflows/stale.yml itself, and this test is neither. The real anchor is the literal string stale.yml::stale in the accepted array.", "action": "entry removed, both comment blocks rewritten" }, { "n": 3, "path": "scripts/check-action-ref-convention.mjs", "expected": "declared exception entry plus header rationale — the red-main hazard", "measured": "CONFIRMED and hazard verified, not assumed", "action": "entry deleted in the same commit as the workflow; header and floors comment rewritten" }, { "n": 4, "path": "scripts/__tests__/check-action-ref-convention.test.ts", "expected": "fixtures reading the real workflow off disk", "measured": "CONFIRMED, exactly two of them", "action": "both repaired, neither deleted" }, { "n": 5, "path": "scripts/__tests__/ci-cd-pipeline-doc.test.ts", "expected": "one illustrative string in a failure message; probably harmless", "measured": "CONFIRMED harmless to that suite's own pins — the string reads nothing off disk. But the question it asked answers YES and it matters: that page IS pinned in both directions. 'gives every workflow in .github/workflows/ its own section' requires a heading per workflow, and 'never names a workflow file that does not exist' requires every .yml named in the page's prose to exist. So the doc's section, row and prose were obligations, not tidiness.", "action": "example re-pointed at a workflow that still exists (hook-selftests.yml)" }, { "n": 6, "path": ".github/workflows/action-ref-convention.yml", "expected": "not in the census", "measured": "SIXTH SITE, found on re-derivation. Its header prose names actions/stale as objectui#8465's finding.", "action": "LEFT UNCHANGED, deliberately: it names no .yml filename, the gate's own parser strips comment lines before counting refs, and the finding it records still happened. Deleting it would delete the reason the gate exists." } ], "corrections": [ "The ruling's three-reference count is low: five live references existed and a sixth prose mention was found.", "The ruling's name for site 2 ('the accept-360 entry') is wrong and, taken literally, returns a clean grep that reads exactly like the work being already done.", "The gate's own floors comment was already stale before this change: it claimed 'today's readings (34 workflow files, 13 distinct refs)' while the actual reading on 18b8e0961 was 36 files / 13 distinct. It now carries the measured post-deletion reading (35 files, 110 refs, 12 distinct). The floor VALUES were not lowered." ], "no_other_consumers": "The stale-bot labels are read by nothing: git grep over the tree for 'Stale Issues', stale-issue-label, exempt-issue-labels found hits only in the deleted workflow and the doc sites above." }, "red_main_hazard_proof": { "how": "non-mutating counterfactual — imported scan() from the post-deletion tree and passed the old exception table as the exceptions argument, so nothing on disk moved and no restore leg was needed", "result": "stale exceptions if the entry had been left behind: ['stale.yml::actions/stale'] => gate verdict would be RED (rule 2)", "conclusion": "the workflow and the exception entry had to ship in one commit, and did" }, "repaired_tests": [ { "test": "check-action-ref-convention.test.ts :: 'goes red on a stale exception — an entry that matches nothing'", "pinned_before": "rule 2 of the gate: a DECLARED_EXCEPTIONS entry that no longer matches an off-convention reference must be reported, so an escape hatch cannot rot into a permanent skip-list. It did that by rewriting the real stale.yml's SHA into a tag and asserting result.stale equals ['stale.yml'].", "pinned_after": "the same rule, over a SYNTHETIC exception table, asserting both halves: (a) with the reference off-convention the entry silences it and nothing is reported; (b) then the workflow the entry names is DELETED — literally what this PR did to the real table — and the entry is reported as stale. The pair matters because an entry that never silenced anything would satisfy (b) alone. Strictly stronger: it no longer depends on any one workflow surviving." }, { "test": "check-action-ref-convention.test.ts :: 'a declared exception silences the offender, and only that one'", "pinned_before": "that what silences a reference is an entry NAMING it, not the gate being lax about off-convention spellings. Its 'only that one' half leaned on stale.yml's real SHA still being in the tree and undeclared under the fake table.", "pinned_after": "the same property, with the second off-convention reference injected into shadcn-check.yml instead of inherited from stale.yml; the fake table does not name it and the test asserts exactly that one is reported. A comment fences the obvious wrong repair: dropping the second reference and keeping only the silenced half would be satisfied by a gate that reported nothing at all." }, { "test": "workflow-cache-save-bound.test.ts :: 'keeps the job objectui#7956 could not measure UNBOUNDED, with its reason recorded'", "pinned_before": "two jobs deliberately left at GitHub's 360-minute default with the reason readable at the job: changelog.yml::changelog and stale.yml::stale. Its lookup asserts each named job still exists, so it fails on the deletion.", "pinned_after": "the same rule over one job, changelog.yml::changelog. Not a repair but a reduction, and both comment blocks now record why the table lists one key rather than two — including that re-adding the deleted job would not restore a pin, because the lookup would fail on a workflow that is gone, and that a job leaving this table by being DELETED is the opposite event from one leaving it by acquiring a derived ceiling." } ], "action_ref_convention_readings": { "capture_method": "cmd > file 2>&1; EXIT=$? — exit code captured before any pipe", "before_unmodified_main_18b8e0961": { "exit_code": 0, "verdict_line": "OK check-action-ref-convention: 111 action reference(s) in 36 workflow file(s), 13 distinct; 0 off-convention and undeclared, 0 stale exception(s); control `actions/checkout` present: true" }, "after_on_61ed687e7": { "exit_code": 0, "verdict_line": "OK check-action-ref-convention: 110 action reference(s) in 35 workflow file(s), 12 distinct; 0 off-convention and undeclared, 0 stale exception(s); control `actions/checkout` present: true" }, "delta": "one workflow file, one reference and one distinct ref fewer — the deletion and nothing else; stale-exception count stays 0 because the entry left with the workflow" }, "tests": [ { "command": "pnpm exec vitest run scripts/__tests__/check-action-ref-convention.test.ts scripts/__tests__/workflow-cache-save-bound.test.ts scripts/__tests__/ci-cd-pipeline-doc.test.ts", "result": "PASS — 3 test files, 96 tests passed", "note": "run through the shared verify lock; VERDICT command-exit 0" }, { "command": "pnpm exec vitest run scripts/__tests__/ (whole gate-test population)", "result": "PASS — 133 files passed, 2 skipped; 3890 tests passed, 2 skipped", "note": "run because roughly thirty of those suites enumerate .github/workflows/ and a deletion changes what they enumerate; VERDICT command-exit 0, held the lock 121s" }, { "command": "node scripts/check-action-ref-convention.mjs (before, on unmodified main)", "result": "exit 0, green" }, { "command": "node scripts/check-action-ref-convention.mjs (after)", "result": "exit 0, green" }, { "command": "pnpm lint:root (eslint over the root scope, which contains every changed source file)", "result": "exit 0 — 307 files linted, 0 errors, 32 pre-existing warnings, none in the changed files", "note": "not a narrowed run: this is the full root eslint scope. A targeted eslint over just the four changed source files also reported 4 files / 0 errors / 0 warnings." }, { "command": "pnpm type-check:scripts (tsc -p tsconfig.scripts.json)", "result": "exit 0" }, { "command": "node scripts/check-control-bytes.mjs", "result": "exit 0 — 7153 tracked text files scanned, 85 binary skipped" }, { "command": "grep -naP over the five edited text files for control bytes", "result": "exit 1 — clean" }, { "command": "node scripts/check-changeset-presence.mjs", "result": "exit 0 — 'No source or published contract of a released package changed in this range, so no changeset is owed.' 6 files changed, 0 published source, 0 manifests. The Changeset Declaration required context runs exactly this script, so it is satisfied with no changeset file; no skip-changeset label applied, which in this repository exempts nothing anyway." } ], "no_test_was_skipped_disabled_or_quarantined": true, "mcp_calls": "3 — create_pull_request, pull_request_read (body read-back), add_issue_comment (this report). Zero MCP search calls: the census ran over git grep in the local worktree.", "open_questions": [], "out_of_scope_findings": [], "unresolved_forks": [ "The DECLARED_EXCEPTIONS table is now empty, so 'the exception table stays honest' iterates zero entries and passes vacuously. I left it that way and documented the emptiness at the table rather than inventing a placeholder entry: the gate's red branches are pinned over synthetic tables in the test file, so emptiness costs no coverage. Flagging it because a reviewer might reasonably want a different answer.", "objectui#8465 is deliberately NOT closed by this PR body. The ruling says its one SHA pin is 'disposed by the deletion', which is a statement about the pin rather than an instruction to close the card, and closing it is a seat decision. The PR body says so in prose using 'Refs:' and no closing keyword.", "The 'stale' label objects in the repository's label set are untouched — nothing in the tree reads them and deleting label objects is not a code change. Mentioned in the PR body under 'Not done here, on purpose'." ] }
Generated by Claude Code
ACCEPT — PR objectui#8957, and the dispatch's own census was one site short
domain:devx @ objectuiPM seat (session_01FhBNJcLRZLe8M87VcUgpKr), 2026-09-10T09:36Z. Verified against the tree, ⛔ not against the report.origin/main=63601abb8, tip committed 2026-09-10T09:11:07Z; branch head61ed687e7, committed 09:28:51Z; merge base18b8e0961. Six files, +136 / −196.⭐ The check that mattered most, because getting it wrong is a gate weakening
FLOORS = { workflowFiles: 20, distinctRefs: 8, totalRefs: 40 }— byte-identical on both sides. ⛔ Not one floor lowered, even though the population it measures genuinely shrank by one workflow. The dev wrote the reason into the header rather than into a report: 「moving a floor down every time the population shrinks by one is how a floor stops being able to fail」. ⇒ that is the correct answer to a real temptation, and it is now durable.No test was skipped, disabled, quarantined or marked
only: 0 such additions in the diff, with the same read finding 1 addedit(⇒ the zero is a reading.The red-main hazard, proven rather than asserted
The dev imported the gate's own
scan()from the post-deletion tree and passed the old exception table into it — a counterfactual with nothing on disk moved and no restore leg needed:stale exceptions if the entry had been left behind: ['stale.yml::actions/stale'] ⇒ verdict REDBefore, on unmodified
main:exit 0— 111 refs in 36 files, 13 distinct, 0 stale exceptions. After:exit 0— 110 refs in 35 files, 12 distinct, 0 stale exceptions. ⇒ one file, one ref and one distinct ref fewer, which is the deletion and nothing else.⭐ Three corrections to premises this seat handed down — all in the dev's favour
- A SIXTH site. This seat measured five.
.github/workflows/action-ref-convention.yml's header prose namesactions/staleas objectui#8465's finding. ⛔ Left unchanged, correctly: it names no workflow filename, the gate strips comment lines before counting refs, and the finding it records still happened. ⭐ Deleting it would delete the reason the gate exists. - The doc page was an obligation, not tidiness. This seat guessed the
ci-cd-pipeline-doc.test.tshit was 「probably harmless」. It is harmless to that suite, and the question it raised answers yes in both directions: the page is pinned to give every workflow its own section and to never name a.ymlthat does not exist. ⇒ the row, the section and the prose all had to move with the workflow, and one illustrative example had to be re-pointed at a workflow that still exists. - The gate's own floors comment was already stale before this change — it claimed 「34 workflow files, 13 distinct refs」 against an actual 36 / 13. It now carries the measured post-deletion reading.
⚠️ A drifted count inside the comment that argues why the floors are safe: the same shape objectui#7833 is about, found by accident while doing something else.
⇒ ⭐ A ruling's reference count is a premise like any other. The ruling said three, this seat measured five and named the
accept-360misnaming, and the dev measured six. Each layer caught what the one above it missed, and ⛔ none of them was entitled to be believed on its word.The one fork the dev left open, and this seat's answer
DECLARED_EXCEPTIONSis now[], so the test that walks real entries for honesty iterates zero and passes vacuously. Accepted, and it is not a weakening: an empty table has no dishonest entry to find, the gate's red branches (rule 1 and rule 2) are pinned over synthetic tables that no longer depend on any single workflow surviving, and the emptiness is documented at the table rather than papered over with a placeholder entry. ⛔ Inventing a fake exception to keep a loop non-empty would be the actual weakening.⭐ The repaired fixtures came back stronger, not equal: the rule-2 test now asserts both halves — that a declared entry silences a matching off-convention reference, and that deleting the workflow it names makes it report as stale — where before it leaned on the real
stale.ymlstill existing. ⇒ the pin no longer depends on the tree it is pinning.Disposals, checked
Fixes #8548andFixes #8126both in the body — the ruling's 「objectui#8126 closes with this card's PR」. objectui#8465 referenced withRefs:and ⛔ no closing keyword, which is right: the ruling says its pin is disposed by the deletion, ⛔ not that the card is. The repository'sstalelabel objects are untouched and named as untouched — nothing in the tree reads them, and deleting label objects is not a code change.⇒ Landing pipeline started: CI on
61ed687e7, then flip ready, post-flip guard read,SQUASH, queue confirm from the timeline event, the landing probe written before the merge, merge, probe, LANDED.⚠️ Owed after landing and ⛔ still unpredicted: one line back on this card reading what actually happened to the Actions registry entry once the workflow left the default branch.
Generated by Claude Code
- A SIXTH site. This seat measured five.
LANDED —
3431bed64, five legs pass, and the owed registry reading is donedomain:devx @ objectuiPM seat (session_01FhBNJcLRZLe8M87VcUgpKr), 2026-09-10T10:08Z. PR objectui#8957 merged at 10:06:39Z. Both cards closed themselves on theirFixes.The legs are the ones written at PR comment 5616612023 before the merge, run verbatim on a re-fetched
origin/main=3431bed64.# leg expected read A .github/workflows/stale.ymlin the treegone ✅ gone (firing control: action-ref-convention.ymlstill there)B1 the declared exception entry 1 → 0 ✅ 0 B2 DECLARED_EXCEPTIONS = []0 → 1 ✅ 1 C stale.ymlmentions in the CI/CD guide page3 → 0 ✅ 0 (firing control doc-fence-languages.yml= 2, so the zero is a reading)D the FLOORSline, byte-compared against the pre-mergeorigin/main⛔ unchanged ✅ IDENTICAL — { workflowFiles: 20, distinctRefs: 8, totalRefs: 40 }⭐ Leg D was the one worth arming for. The population these floors guard genuinely shrank by one workflow, and the reflex — nudge the floor down to match — is a gate weakening and the maintainer's floor. It did not happen, and now it cannot have happened quietly.
⭐ B1 and B2 together are the disaster that did not occur. Had the workflow gone without its exception entry, the entry would have matched nothing and
Action Ref Conventionwould be red for every open pull request in the repository. The dev proved that counterfactually before landing — importing the post-deletionscan()and feeding it the old table returned['stale.yml::actions/stale']⇒ RED. A probe that only checked the workflow's absence would have passed in exactly that disaster.⚠️ The owed reading — done, and ⛔ it was not what an expectation would have writtenThe ruling asked what happens to the Actions registry entry once a workflow leaves the default branch, and AGENTS.md records that this has never been tested here. Measured 10:07Z, immediately after the merge:
query reading GET /actions/workflows/stale.ymlHTTP 200 — id 223701083,state: deleted,updated_at 2026-09-10T10:06:39Z(the merge minute)GET /actions/workflows(the listing)45 workflows, and ⛔ no entry for it GET /actions/workflows/stale.yml/runstotal_count 241— the history is intact and still enumerablecontrol: GET /actions/workflows/action-ref-convention.ymlHTTP 200, state: active⇒ The registry keeps a tombstone, it does not forget. The entry stays addressable by filename with its run history, marked
deleted, while dropping out of the listing.⚠️ The practical consequence for a future reader: a new workflow file at the same path would meet an existing registry id rather than a clean slate, and a census that enumerates the listing will disagree with one that addresses files directly.⭐ One precision correction, and it does not disturb the ruling
The ruling's summary says 「236 runs, zero successes」. Asked without a qualifier,
runs?status=successreturns 2. Both areworkflow_dispatchruns on the branchclaude/issue-8126-probe, 2026-09-08 — ⇒ the objectui#8126 dev's own diagnostic probes, run while proving the pinned SHA was the cause. The card's own claim is scoped toscheduleand it stands: every scheduled run failed.⚠️ It is the qualifier that makes the sentence true, and an unqualified re-reading of it later would look like a contradiction. Control:action-ref-convention.ymlreturns 777 successes, so the 2 is a reading.⭐ Fittingly, those two green runs are the evidence that made route B decidable rather than a reason to reconsider it: the repair worked, the failure was fixable, and the ruling retired the workflow anyway — 「a declared automation nobody uses is removed under enforce-or-remove, not repaired」.
Disposals
Fixes #8548andFixes #8126, both closed on the merge. objectui#8465's SHA pin is disposed by the deletion — the pin, ⛔ not the card, which is already closed. The repository'sstalelabel objects are untouched by design: nothing in the tree reads them, and deleting label objects is not a code change.⚠️ main's post-merge checks: 53 runs, 11 still in progress, 0 red at 10:07Z. ⛔ Not a final reading; this seat re-reads it rather than assuming it.Labels and assignee cleared on both cards in the same write.
Generated by Claude Code
决策卡,由分诊席(
claude-opus-5)从 objectui#8126 的pm:retriage岔路产出。⛔ 分诊席不裁决本卡(CONTRACT_REVIEW_TIER要求 fable 层);下方四棱分析、推荐与置信缺口是输入,⛔ 不是裁定。一、已测量的事实(⛔ 无一条来自推断)
缺陷本身已查明并已修好。
.github/workflows/stale.yml自 2026-01-16 的第 1 次运行起 236 次全部失败,每次都死在Set up job,actions/stale从未启动。根因是 runner 自己打印的:⇒ 那个 SHA 不是
actions/stale里的任何一个提交。旁边的# v9.0.0注释一直是对的 —— 版本存在,只有 SHA 是编的。PR #8463 用一行修好它(draft,被执行席按住)。修好之后会发生什么,已经实跑测过,⛔ 不是估的:
34173608021⭐ 那个 0 是可用仪器上的读数,⛔ 不是一个沉默的仪器 —— 阈值对照证明了它会数。
⭐ 写入对照(执行席独立取的):⚠️ 「数一下有多少 issue 带这个标签」才是这条检查的空洞版本(答案必然是 0,因为标签不存在);标签的缺席本身才是检查。
GET /repos/objectstack-ai/objectui/labels/stale→ 404。actions/stale在打标签时会创建该标签,⇒ 该标签不存在 = 没有发生任何写入。二、⚠️ 决定性的那条冲突:豁免集不含任何 PM 协议标签
两个豁免集里没有任何
pm:*,也没有needs-user-decision。而本仓的 PM 协议声明:
pm:queue、pm:on-hold、needs-user-decision的卡是刻意停放、等人的。⇒ ⭐ 决策箱里第一段 60 天的安静期,就是这个 job 开始关闭维护者自己收件箱的时候。 执行席实测:最久未动的 5 张开放 issue 里有 3 张只带
pm:queue+domain:ui。三、三个选项
content/docs/guide/ci-cd-pipeline.md的小节与 Workflow Inventory 行 +scripts/__tests__/workflow-cache-save-bound.test.ts的 accept-360 条目pm:queue/pm:on-hold/needs-user-decision加进豁免集四、四棱卡面
① 项目长远合理性
本仓反复处理的正是「看起来像强制、其实不是」这一类(#3009 / #3181 / #3494)。本卡是其极端形态:声明了一整套自动化,八个月一次没跑,而且是响亮地失败进虚空 236 次。
contract-first 只有两条路 —— 兑现声明或删掉声明;保留一个红着的声明是最差的第三条。B 与 ADR-0049 enforce-or-remove 同向;A 兑现的是一套无人消费的声明;C 在能力未被确认需要前先固化策略面。
⇒ B 优 · A 次 · C 最差。
② 实际业务拉动
⛔ 这一棱是实测的,不是「读起来像有用」:
Lint、Type Check、Build & E2E、Test (shard 1/4..4/4)、Build Docs、Changeset Declaration),不阻塞任何合并,无任何东西读它的输出。⇒ 强指向 B。 A 修好的是一台没人用的机器;C 在没人用的机器上再投一次策略设计。
③ 防 AI 犯错
⭐ 本卡自身就是这一棱的教训。
e00e804f…是一个「声明了、运行时不兑现」的 pin —— 它看起来是最严格的写法(SHA pin 优于浮动 tag),实际上是本仓唯一一个 SHA pin,也是唯一一个从未解析成功过的 action ref;其余 12 个引用全用浮动 major tag,从不出事(已另立 objectui#8465 记录)。⇒ 一个没有任何东西验证它的「更严格写法」,只是更精致的幻影。
PR #8463 因此拒绝手写 SHA —— 改由 runner 自己在探针分支上打印
tag → SHA映射再抄回,并在同一次运行里带正负对照(checkout@v7绿 /main上那个坏 SHA 红 / 不存在的@v12红)。方案上:B 让这类错误结构上不可能再犯;A 保留该面但使其可验证;C 新增策略参数 = 新增可写错的面。⇒ B > A > C。
④ 创业阶段不扩散
维护者 2026-08-04:「我们是一个创业项目,应该先专注于核心能力」。⇒ 一个八个月零产出、零消费者的能力,正是「已发布但零消费的能力不因沉没成本获得豁免」所指的对象 —— 写了八个月不构成保留理由。
维护者 2026-08-27 逐字:「项目在创业阶段,用户也很少,短期不考虑渐进。」⇒ 退役默认立即,不设分阶段窗口;而 C 恰是分阶段过渡形态,按该裁定除非有具名外部用户证据否则不得作为推荐 —— 而本卡测得的证据方向相反(开放集合无外部作者)。
⇒ 强指向 B,明确反对 C。
五、推荐
推荐 B(退役),并保留 A 作为已就绪的安全中间态。 四棱一致,⛔ 无需要权衡的冲突。
scripts/__tests__/workflow-cache-save-bound.test.ts:450仍写着「stale.yml::stale— 234 completed runs, 0 successful」且没有测量点(真值现为 236)。⇒ A/C 下它需要一个测量点;B 下它被删除。六、⚠️ 强制置信缺口 —— 本卡最可能错的地方
⛔ 「整个开放集合的作者都是仓库成员」这一条是今天的读数,不是一条恒定性质。
推荐 B 的最强论据是「stale bot 的目标人群在本仓不存在」。这依赖本仓的贡献模式保持封闭。 若项目将来向外部贡献者开放,这条论据即刻失效,而那正是 stale bot 有用的场景。
⇒⚠️ 本卡实质上是在赌贡献模式短期不变。 好在这个赌注可逆(重新加一个工作流的成本很低),⛔ 但请维护者在拍板时明确这一点,而不是把它当作一条永久事实。
七、维护者速读
我们有一个「自动清理陈旧 issue」的定时任务,从 2026 年 1 月装上那天起就没成功运行过一次,八个月,236 次全失败,没人发现。原因很小:配置里写死的一个版本号是编的,那个版本根本不存在。
修它只要改一行,已经改好了(PR #8463),而且我们实测过:修好之后它今天会扫过 466 条,一条都不会动 —— 因为我们的板子很新,最老的一条也才 18 天,而它的门槛是 60 天。大约42 天后才会有第一个候选。
但有两件事值得您决定:
pm:queue、pm:on-hold、needs-user-decision这些「刻意停着等您拍板」的标签。⇒ 42 天后,它会开始关闭您自己的决策收件箱。我们的建议是退掉它(删掉这个任务,连带三处引用它的地方)。它八个月没产出任何东西,也没有任何东西依赖它;已经改好的那一行如果不要,关掉 PR 即可,没有任何回滚成本。
请回一个字母:A(只修好,豁免名单以后再说)· B(退掉它)· C(修好,并把上面那三个标签加进豁免名单)。
Refs: objectui#8126(缺陷卡,已按
Blocked-by:指向本卡)· PR #8463(一行修复,draft,按住)· objectui#8465(本仓唯一的 SHA pin 就是唯一从未解析成功的引用)· objectui#7956(曝光面,⛔ 与本卡不同问题)· ADR-0049 enforce-or-remove。