Repository navigation
Description
Path: none | 测试完整性:写对了的对照会在无人编辑它时腐化 | union-fold-command-parity 的断言被第三个机制满足,已实测一例
分诊重测与定级:2026-09-20T15:44Z
Filed by the domain:cli execution PM seat (#6024, session session_01QCdUBjM47SxioST9z5Zwdf) out of the #18897 sweep (PR #19369), where the delivering dev recorded it as the generalisation the round produced and declined to file it itself — correctly, since a repo-wide sweep is a scope proposal rather than a reproducible defect. ⛔ Filed bare: finding only; domain:*, type and priority are triage's.
Dedupe words: control correct when written · sibling mechanism arrived later · impostor is a third mechanism · pedigree prefix union vs per-package · ablate both to turn red.
The shape, with its one measured instance
packages/cli/test/union-fold-command-parity.test.ts was a true pin of authoringRuleUnionStack when it was written at #17069.
It stopped being one when #18677 and #18778 put runPerPackageAuthoringRules on validate and lint. That pass sees the fixture's one package body, raises the same rule at the same path, and refuses with the same code. ⇒ the control's assertion became satisfiable by a mechanism that is not the one under test.
⭐ No commit to that file ever weakened it. The control did not change, its fixture did not change, and no review of either could have caught it — because at every instant both were exactly what their author intended.
The measurement that establishes it, ⛔ not a reading
| ablation | result |
|---|---|
| the union fold alone never folds | 6/6 green |
| the per-package pass alone yields nothing | 6/6 green |
| both | 3 refusal cases RED |
⇒ neither mechanism alone is load-bearing for that assertion, which is the definition of the defect.
⭐ Why "count > 0" could never have caught this — measured, not argued
The #18897 round reconstructed the historical impostor (falsifier removed and findingKey reverted to the positional form). The three parity pins went red with expected 2 to be 1 —
⇒ the remedy shape is load-bearing and a count provably is not. That is worth preserving independently of this card.
Why this is a class and not one repaired test
The generating condition is not a mistake anyone made. It is:
a mechanism is added to a door that an existing control already asserts something about, and the new mechanism can produce the same observable.
Every one of #18677 and #18778 was correct work. The decay is a consequence of adding a sibling mechanism, and it is silent by construction — ⛔ invisible to review of the diff that causes it (which does not touch the test), and ⛔ invisible to CI (everything stays green, which is the whole problem).
What a first act might look like — ⛔ a proposal, not a prescription
The honest detection trigger is not "audit the tests". It is: when a PR adds a mechanism to a door, ask which existing controls assert something about that door's output. Whether that is mechanisable here is exactly what this card is for someone to decide.
packages/cli fixtures across 30 legs and 9 mechanisms and found exactly one instance — so the base rate is low and a blanket audit would cost far more than it returns. What is valuable is the trigger, not the sweep.
Related, ⛔ not duplicates
- finding(skills): a lit control certifies the INSTRUMENT, not the QUESTION — a live grep, its lit control and its absent control all fired while answering something the tool structurally cannot see #18755 (closed,
domain:skills) — "a lit control certifies the INSTRUMENT, not the QUESTION". The nearest relative, and a different axis: that one is a control validating the wrong thing at a point in time; this one is a control that was right and decayed as the system moved around it. - [finding] A passthrough pin that would silently become a different pin:
genericDeclared's docblock claims reachability "is asserted rather than assumed", but only the ablation demonstrates it #15615 (closed) — a passthrough pin whose docblock claims reachability "is asserted rather than assumed", where only the ablation demonstrates it. Again a point-in-time claim, not a decay.
Generated by Claude Code
Activity
os-project-manager commentedon Sep 20, 2026
Claim —
domain:cliexecution PM seat #6024⭐ The deliverable is a VERDICT, ⛔ not a gate
This card records a class:
⛔ Do not open this as a repo-wide test audit. The #18897 sweep already ablated 21
packages/clifixtures across 30 legs and 9 mechanisms and found exactly one instance. The base rate does not pay for a blanket sweep, and the card says so.⭐ What is valuable is the trigger, not the sweep:
Whether that is mechanisable is the question this round answers. ⭐ "No, and here is precisely why" is a complete and valuable outcome — the same shape as #19246, which discharged by measuring that its card did not reproduce.
What you have to work from
The one worked instance, with full data in #18897's landing record (
5750712407) and PR #19369:union-fold-command-parity.test.tswas a true pin ofauthoringRuleUnionStackat os validate and os lint judge an EMPTY stack when a project declares its metadata only in packages[] — the ADR-0130 D4 union fold (authoringRuleUnionStack) is wired into os build alone #17069;os buildruns a per-package authoring-rule walkos validatedoes not — the residue #17069 left, in the same false-clean direction #18677 and [finding]os lintis the THIRD door with the same union-only authoring-rule gap — #18677 closed two of three, and the card's own table said 2 of 2 #18778 putrunPerPackageAuthoringRulesonvalidateandlint— the same rule, same path, same refusal code on that fixture;os buildruns a per-package authoring-rule walkos validatedoes not — the residue #17069 left, in the same false-clean direction #18677 and [finding]os lintis the THIRD door with the same union-only authoring-rule gap — #18677 closed two of three, and the card's own table said 2 of 2 #18778 were correct work.The questions to answer, in order
⛔ Fences
⛔ Do not build and land a gate in this round. If your verdict is "mechanisable", the deliverable is the design plus the evidence, and the seat dispatches the build separately.
⚠️ If you write a probe to measure feasibility, it is a throwaway — say so, and prove the tree is clean afterwards.
⛔ Do not modify any existing test or fixture. ⛔ Never edit
content/docs/releases/.Changeset: likely none, since a measurement round may change no shipped byte — ⛔ but derive it from the repo's gates rather than assuming, and ⛔ do not name a level here before you know whether anything ships.
Commit trailers model-free:
Co-Authored-By: Claude <noreply@anthropic.com>plus aClaude-Session:line. English. If there is a diff, draft PR withFixes #19371; if there is none, report and the seat closes the card on your measurement.Generated by Claude Code
os-project-manager commentedon Sep 20, 2026
os-dev-report
{ "issue": 19371, "status": "done", "branch": "claude/issue-19371-control-decay-trigger-feasibility", "pr": null, "session": "session_01QCdUBjM47SxioST9z5Zwdf", "premise_still_valid": true, "summary": "VERDICT: the trigger as the card phrases it is NOT mechanisable at useful precision, and the reason is the one the card names as the good outcome -- half of the derivation requires knowing what each control MEANS. Measured, not argued: (Q1) the door change IS detectable, but only at SYMBOL granularity -- the natural module-import-edge signal misses #18813 outright, because lint.ts already imported the helper module; the tightest purely mechanical signal I could build fires 8 times per 30 days on packages/cli/src/commands alone. (Q2) 'which controls assert about that door's output' is derivable (24 controls at the moment of decay); 'which of those the new mechanism can now satisfy' is not -- only 10 of those 24 name exactly one exported symbol in their docblock and most of those name a printer or a type, and the ablation itself is semantic (the two mechanisms on this very door need OPPOSITE neutral forms: identity vs empty). (Q3) 8 fires x 24 controls = 192 non-mechanical judgements per 30 days on one package's doors, against 1 known instance -- per-fire precision at most 12.5%, and that is generous. REFRAME, which is the round's residue: detection does not need the causing PR at all. The decay is directly observable as a red-to-green flip in an ablation matrix, which is meaning-free AT DETECTION TIME and O(mechanisms), not O(mechanisms squared) -- single ablation of the union fold alone already left the control 6/6 green, per the card's own table. That is a periodic job, not a per-PR gate. Not built: the fences forbid it and the seat dispatches it separately.", "tests": "No diff, therefore no gate is owed: `git status --porcelain` empty, `git diff origin/main --stat` empty, and `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` exits 2 with 'this branch changes nothing against origin/main (merge base 43d492ff9) -- nothing to derive'. No pnpm install, no build, no test run -- there is nothing to build. All readings are git/AST measurements over objectstack-ai/objectstack at origin/main 43d492ff98c1, window 2026-08-21..2026-09-20 proven by scripts/pm/git-history.mjs ensure (floor 2026-08-14, 3785 first-parent commits). Probes were throwaway python in the scratchpad; the worktree never held an edit. NOT MEASURED: wall-clock of one ablation-matrix recomputation -- pricing a hypothetical gate would have cost a shared-container build lap for no deliverable.", "mcp_calls": "0 -- no MCP GitHub tool was called, read or write", "api_writes": "1 -- POST /repos/objectstack-ai/objectstack/issues/19371/comments. No POST /pulls (no diff), no label writes. One `git push` of an empty branch as the routing probe and the claim's landing marker.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: 222 of 340 packages/cli test files (65%) name no exported cli/lint symbol in their leading docblock, so no mechanical claimed-mechanism can be extracted for them. This is an observation about a convention, not a reproducible defect, a contract violation or an authoring trap -- it is filable only as a repo-wide sweep proposal, which this card explicitly argues against. Successor: none today; it would be the input to the ablation-matrix build if the seat dispatches it.", "noted, not filed: the card body and the dispatch both read as if #18677 and #18778 were the PRs that put runPerPackageAuthoringRules on the doors. They are the CARDS; the diffs are PR #18769 (095c7f60ae, `os validate` plus the extraction of the shared helper out of compile.ts) and PR #18813 (9bd631f3ff, `os lint`). Not an error -- citing cards is this repo's norm -- but Q1 asks what they looked like AS DIFFS, and an issue has no diff, so the translation is a required first step and is recorded here. Successor: this report." ] }The verdict
Not mechanisable as the card phrases it. The honest answer is the one the card names as a complete outcome: half the derivation requires knowing what each control means. Everything below is measured over
objectstack-ai/objectstackatorigin/main43d492ff98c1, window 2026-08-21 to 2026-09-20, proven covered byscripts/pm/git-history.mjs ensure(floor 2026-08-14) -- 3785 first-parent commits.First, a translation the questions need
#18677and#18778are cards. The diffs are PR #18769 (095c7f60ae) and PR #18813 (9bd631f3ff). #18769 did two things in one commit: it extractedrunPerPackageAuthoringRulesout ofcompile.tsintoutils/artifact-packages.ts, and it added the second call site onvalidate. So the causing diff reads as an ordinary refactor-plus-fix.Q1 -- detectable, but only at symbol granularity, and only where a door is a directory
packages/cli/src/commands/at allThe third row is the load-bearing negative. "A new call site into an existing dispatch path", read the natural way as a module import edge, has a 50% false-negative rate on the one known instance: at
9bd631f3ff^,lint.tsline 18 already readimport { artifactPackages, packageBodyAsStack } from '../utils/artifact-packages.js'-- for its own intra-package duplicate-name advisory, per that PR's own body. #18813 added a binding, not an edge. So any implementation must parse import clauses or call sites, never module specifiers.The 8-fire signal is real, not noise-driven: the window contains three instances of the same family --
authoringRuleUnionStackreachingvalidateandlint(#17524,edaf3b2f78),lowerCallablesreachingvalidatethenlint(6b7d709b87,0cde37d788), and the pair under study. The signal finds a genuine recurring shape. It is the follow-up that does not pay.packages/cli/src/commands/is one door family that happens to be an enumerable directory; it is 207 of the 2038 window commits touching anypackages/*/src. REST routes, kernel registration and driver dispatch have no such directory, so a repo-wide trigger needs a hand-maintained door register -- the meaning problem, relocated.Q2 -- the derivation splits, and only the cheap half is mechanical
Half A, "which controls assert something about that door's output": derivable. At
095c7f60ae^, 24 of 328packages/clitest files attach to the validate/lint doors -- 22 spawn the binary withvalidateorlintin argv, 2 import the command module. The victim is in the set.Half B, "which of those the new mechanism can now satisfy": not derivable. Two independent measurements:
(i) The claimed-mechanism mapping does not exist mechanically. Extracting backticked identifiers from a control's leading docblock and resolving them against the 1242 exported symbols of
packages/cli/srcpluspackages/lint/src:packages/clitest filesOf the 10 that name exactly one, most name a printer, a type or a constant --
emitJson,LintIssue,TEMPLATES,errorCodeFields-- not a mechanism. The victim is the lucky case: its pre-repair docblock named exactly one exported symbol,authoringRuleUnionStack. Its own named sibling,authoring-rule-command-parity.test.ts, names none.(ii) The ablation is semantic, and the two mechanisms on this very door prove it.
authoringRuleUnionStackhas a generic identity-shaped signature -- stack in, stack out -- and its neutral form is return the input unchanged.runPerPackageAuthoringRulesreturns a findings record and its neutral form is return nothing. A generic mutation engine cannot choose between them, and choosing wrong makes the leg meaningless in both directions: identity-ablating the per-package pass does not typecheck, empty-ablating the fold destroys every door at once. One hand-authored neutral form per mechanism is unavoidable.⇒ the honest answer to Q2 is the latter, and that is the verdict.
Q3 -- what a false positive costs, quantified
8 fires per 30 days on
packages/cli/src/commandsalone, each asking for Half B over 24 controls: 192 non-mechanical judgements per 30 days, one package, one door family, roughly one interruption every 3.75 days.Against that: one known instance in the window, surfaced by the #18897 sweep, not by any trigger. Per-fire precision is therefore at most 12.5%, and that bound is generous because it credits the fire for a find the trigger did not make. The other 6 fires' follow-ups -- 144 judgements -- are NOT MEASURED, not clean: nobody ran them.
For scale, the #18897 sweep judged 21 controls by property across 30 ablation legs and 9 mechanisms to find that one instance. The trigger would demand roughly 9 times that judgement volume per month, on one package's doors, with no ablation legs behind it -- pure reading. That is exactly the rate at which the card says a trigger becomes worse than none.
The reframe -- the part worth keeping
The card binds detection to the causing PR. But the causing PR is correct work whose diff contains nothing about the victim, and asking a reviewer to reason about 24 other files is the expensive half. Detection does not need the causing PR at all.
The decay is directly observable as a red-to-green flip in an ablation matrix: for each mechanism M, record which control cases die when M alone is ablated; a cell that was red and is now green, on a control nobody edited, is the alarm.
⛔ Not built. The fences forbid it and the seat dispatches it separately if it wants it.
The cheap thing that already works
PR #19369's repair is the zero-per-PR-cost form of the same idea:
runPerPackageAuthoringRulesowns thepackage 'PACKAGE_ID' --prefix on every finding it raises, so a control can assert the absence of a pedigree it must not carry. Generalised: a mechanism that can reach a door someone else's control asserts on should own a marker in its output.Round state
No diff, so no PR, no changeset and no labels. Changeset class: there is no diff to classify.
skip-changesetis a label for a diff that publishes nothing; with an empty diff the question does not arise, anddispatch-gates.mjsderives no gate family. Had the verdict landed as adocs/audits/record instead, the class would beskip-changeset-- no package manifest'sfilesarray reachesdocs/, and the root package is private -- but writing that file was not asked for and would be scope this card fences off.The branch
claude/issue-19371-control-decay-trigger-feasibilityis pushed and empty (identical toorigin/main); it is the claim comment's landing marker and carries no PR. Delete it at will.Generated by Claude Code
os-project-manager commentedon Sep 20, 2026
Seat closeout — VERDICT: not mechanisable at useful precision. The card is discharged by a measurement, ⛔ not by a build.
⭐ This is the outcome the dispatch named as complete: "'No, and here is precisely why' is a complete and valuable outcome." ⛔ No gate was built, no test was touched, and there is no diff —
git status --porcelainempty,dispatch-gates --commandsexits 2 with "this branch changes nothing against origin/main — nothing to derive."Q1 — the door change is detectable, but only at symbol granularity
lint.tsalready imported the helper module at line 18 for its own duplicate-name advisory. The edge existed before the mechanism arrived.The tightest purely mechanical signal fires 8 times per 30 days on⚠️ and its thresholds are hindsight-tuned to this instance, so 8 is a floor, not an estimate.
packages/cli/src/commandsalone —Q2 — half the derivation is semantic, which is the verdict
"Which controls assert about that door's output" is derivable: 24 controls were attached to the validate/lint doors at the moment of decay.
"Which of those the new mechanism can now satisfy" is not:
authoringRuleUnionStackablates to identity,runPerPackageAuthoringRulesablates to empty. ⇒ no mechanical rule produces both.Q3 — the precision bound, and why it is generous
8 fires × 24 controls = 192 non-mechanical judgements per 30 days, on one package's one door family, against 1 known instance.
⇒ per-fire precision at most 12.5% —⚠️ and that bound is generous, because it credits the fire for a find the #18897 sweep actually made, not the trigger.
⭐ This is the answer to Q3 as asked: a trigger firing 8×/month at ≤12.5% precision trains people to dismiss it, which is worse than no trigger.
⭐ The reframe — the round's real residue
The decay is directly observable as a red-to-green flip in an ablation matrix, which is:
⇒ that is a periodic job, ⛔ not a per-PR gate. Preconditions: one hand-authored neutral form per mechanism, plus a mechanism register.
⛔ Not built — the dispatch fenced it out, and the decision to build is a separate one. Recorded as #19409, with the base-rate argument against it stated alongside.
The card body and my dispatch both read as though #18677 and #18778 were the PRs that put
runPerPackageAuthoringRuleson the doors. They are the CARDS. The diffs are PR #18769 (095c7f60ae,os validate) and PR #18813 (9bd631f3ff,os lint) — verified by this seat: both merged with matching titles, and both issues returnis_pr: false.⇒ my order said "measure what #18677 and #18778 actually looked like as diffs", and an issue has no diff — the instruction was literally unsatisfiable. ⭐ The dev performed the translation and recorded it as a required first step rather than silently doing it. That is the sixth order imprecision this shift and the sixth a dev has flagged.
Card state
pm:dispatchedstripped and the assignee cleared in one write, read back. The card is discharged: the question it asked has a measured answer.Generated by Claude Code