Skip to content

feat(spec)!: retire the plugin-security scan-result surface — zero consumers after the PluginSecurityScanner retirement (#15932) - #19610

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-15932-retire-scan-result-surface
Sep 22, 2026
Merged

os-warren merged 3 commits into
mainfrom
claude/issue-15932-retire-scan-result-surface

Conversation

@os-warren

@os-warren os-warren commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Fixes #15932

Clause-②: yes

ADR-0049 enforce-or-remove. Executes the ruling on #15932 (director seat, decision batch #65, 2026-09-07, maintainer verbatim 「同意」). This is the second half of the PluginSecurityScanner retirement, whose live record is PR #15930. ⚠️ Citation corrected: the number that landing was filed under — 14919, written bare on purpose — no longer resolves on the board (404 at 2026-09-21T18:3xZ; lit control: the neighbouring 14920 returns 200), so the digits are kept greppable while the sigil is dropped, which is the same treatment this repo gave the dead [#14423] docblock citation that PR #19609 repaired in packages/metadata/src/metadata-manager.ts. ⚠️ Carrier corrected: an earlier draft of this sentence called that precedent a Blocked-by: #14423 line. There is no such line — Blocked-by:.*14423 returns 0 across origin/main (lit control: real Blocked-by: lines do exist, in five files and more), and #19609's own diff removes * [#14423] The keyed plural read … from a docblock. The precedent itself is real and reads verbatim "the card it was filed under — issue 14423, written here without a leading hash because it no longer resolves"; only its carrier was misnamed. ⛔ No replacement number is guessed: #15930 is the PR whose patch did the work, ⛔ not a re-issued card. That change retired PluginSecurityScanner, and its type-only import was the scan-result family's only importer of any kind, so the schemas it fed went from one type-only importer to zero consumers of any kind while staying fully published.

What is retired

member route
KernelSecurityScanResult (def + 3 exports) whole-def removal, RETIRED_DEFS_BY_MAJOR[18]
KernelSecurityVulnerability (def + 3 exports) whole-def removal, RETIRED_DEFS_BY_MAJOR[18]
PluginSecurityManifest.scanResults retiredKey() tombstone, RETIRED_KEYS_BY_MAJOR[18]
PluginSecurityManifest.vulnerabilities retiredKey() tombstone, RETIRED_KEYS_BY_MAJOR[18] — see Scope below
PluginQualityMetrics.securityScan retiredKey() tombstone, RETIRED_KEYS_BY_MAJOR[18]

Two routes because the two questions have different answers. Nothing parses the two defs, so there is no author a prescription could reach and a tombstone would be noise. The three keys sit on shapes that are not .strict(), where a bare deletion is a silent strip (ADR-0104) — so each becomes a retiredKey() tombstone, audible in both channels: tsc (input type never) and the parse, which raises the prescription itself.

No D2 conversion. A plugin security manifest and a plugin registry entry are package artifacts a publisher ships — never stack collection members, never stored sys_metadata rows — so the conversion chain has no seam that would see one. That is the disposition the sibling kernel-plugin-security-durations-unit-in-key entry already records for this same manifest. The D3 semantic entry plugin-security-scan-result-surface-retired carries the judgement.

Premise, re-measured first-hand on origin/main @ 236cec19a5

reading value
KernelSecurityScanResult / KernelSecurityVulnerability in packages/**/*.ts outside the declaring module 0
LIT CONTROL — PluginSecurityManifest inside plugin-security-advanced.zod.ts 5 ⇒ the file is greppable, so the zero is a reading
packages/core/src/security/security-scanner.ts absent ⇒ the PluginSecurityScanner retirement (PR #15930) landed
PluginQualityMetrics.securityScan in packages/**/*.ts plugin-registry.test.ts only — the spec's own self-test
objectui at the pinned sha 87af769e — does it import any of this? 0 hits; LIT CONTROL: @objectstack/spec is imported there ⇒ no sibling fix and no pin bump are owed

The authorable-row count is 27, not the 22 the card carried. Measured with the playbook's instrument on authorable-surface/kernel.json: 8 rows for KernelSecurityScanResult, 17 for KernelSecurityVulnerability, plus PluginSecurityManifest:scanResults and PluginQualityMetrics:securityScan — 28 counting the forced-consequence PluginSecurityManifest:vulnerabilities. The disagreement is reported, not reconciled: the 22 is superseded, and the FOLLOW-UPS row now says so.

Scope — one key outside the four names, reported rather than absorbed

PluginSecurityManifest.vulnerabilities is not one of the four names the ruling listed. It is a forced consequence: it was an array of KernelSecurityVulnerability and the last authorable referent of a def the ruling retires by name, so it cannot outlive that def, and keeping the def alive only to carry it would be keeping the retired family alive under a second name. It is not a neighbour retired by proximity — the fence's stated concern — and it is named here, in the registry entry, in the changeset and in the hand-back.

The outstanding carve-out is ONE enum member wide, not three — and it is NOT recorded as checked.

⚠️ Coordinates corrected. The ruling made three carve-outs conditional on a producer grep of objectstack-ai/cloud. Two of the three no longer exist in this tree, so only one is still outstanding:

carve-out the ruling named state at head 3e0a06d0b5
marketplace 'scanning' status, marketplace.zod.ts LIVE, one hit in any .zod.ts, at marketplace.zod.ts:348 — the sole outstanding carve-out
marketplace-admin.zod.ts file absent from the tree; that family's disposition was decided on #16526
incident 'malware' type, incident-response.zod.ts file absent from the tree — the incident family was retired whole by #15513, ruled 2026-09-05, two days BEFORE the ruling that made 'malware' conditional; malware returns 0 in any .zod.ts

Instrument controls, so the two zeros are readings rather than a dead grep: marketplace*.zod.ts on the same find returns marketplace.zod.ts, and malware on the same grep returns 7 non-.zod.ts files (ADRs, design docs, records). An earlier draft of this body named all three files in the present tense; that was wrong and is retracted here.

objectstack-ai/cloud is not reachable from this session, so the producer question for 'scanning' is genuinely NOT MEASURED. ⛔ The absence of these names from this diff is not evidence about them.

Breaking, for a population that is not measured

@objectstack/spec is published, so removing six exports and three authorable keys is breaking for consumers no download, dependent or source telemetry was consulted for — exactly as that retirement's own changeset (PR #15930) says of its own three exports. That was an input to the ruling, not a reason to soften the removal. No deprecation window (maintainer 2026-08-27: 「项目在创业阶段,用户也很少,短期不考虑渐进」). The release note is written centrally; content/docs/releases/ is untouched.

⚠️ Runtime behaviour is deliberately unchanged. Nothing ever read any of these keys, so deleting one removes no check that was running. A consumer that gated on securityScan.passed === true was gating on nothing.

⚠️ Changeset level — the ruling says major, a live gate refuses it

The ruling and the dispatch both say major. scripts/check-changeset-no-major.mjs hard-refuses a major bump for the duration of the launch window (every publishable package is in one Changesets fixed group, so one major promotes ~70 packages), and the retirement playbook was corrected to say so in #19446, which is on main. A major changeset here is a guaranteed-red PR that cannot land.

This PR therefore ships minor + a BREAKING banner carrying the FROM → TO mapping and the one-line fix — the carrier the window designates for breaking-ness — plus the ADR-0087 disposition marker. check-changeset-no-major.mjs and check-adr-0087-registration.mjs are both green on it. This is flagged, not silently chosen: if the seat wants the literal major, that is a decision about the launch-window guard, not about this diff.

Verification

check result
pnpm --filter @objectstack/spec build pass (after the two deletion gates fired and were answered, below)
pnpm --filter @objectstack/spec test pass — 510 files, 14897 passed, 1 todo
pnpm --filter @objectstack/spec typecheck see the hand-back
pnpm --filter @objectstack/spec check:generated pass — 15 artefacts; 5 were stale and were regenerated by --fix, never hand-edited
check-adr-0087-registration.mjs pass — 1 declared-breaking changeset, disposition registered plugin-security-scan-result-surface-retired
check-changeset-no-major.mjs pass — no major bump introduced

Two gates fired on the way, and both were answered rather than routed around. The json-schema manifest deletion gate refused the two vanished defs until their keys left json-schema.manifest/kernel.json and each was declared in RETIRED_DEFS_BY_MAJOR; the authorable-surface deletion gate then refused the 25 orphaned key rows until they left authorable-surface/kernel.json in the same commit. That sequence is the removal's own evidence and is why the ratchets moved. ⛔ authorable-surface.base.json was not touched.

Reverse verification — the refusal pin can fail. The scanResults tombstone was ablated to z.array(z.unknown()).optional() with scripts/ablation-replace.mjs, which proved the mutation on disk (anchor 1 → 0, blob 0f3af37f5068969efcdaa46a) before running anything. Result: exactly one test failed — the scanResults refusal pin — and the other four passed. The restore leg verified blob == HEAD and git diff HEAD empty.

Acceptance notes

Noted, not filed — observed while executing, outside this card's scope, and no in-flight PR or person is known to be heading for these files:

  • packages/spec/src/kernel/plugin-security.zod.ts declares a parallel, unprefixed scan-result family — SecurityVulnerabilitySchema and SecurityScanResultSchema, near-duplicates of the pair retired here, with their own self-test in plugin-security.test.ts. It is outside the four names and is deliberately untouched; the pin test asserts both are still exported, so the fence is machine-checked rather than described. Whether it is live is a separate census this card did not take.
  • plugin-security-advanced.test.ts, the declaring module's own self-test, contained zero references to the scan-result family. The premise called plugin-security.test.ts the family's self-test; it is in fact the other family's. The retired family had no self-test at all — a reading slightly stronger than the card's.

PR body maintained by the domain:spec execution seat, session session_01UDXER3sdqfeVYpEWZs5mZx; the diff is the dev's.


Generated by Claude Code

ADR-0049 enforce-or-remove; maintainer ruling 2026-09-07 (director seat,
decision batch #65), adopted verbatim. The second half of #14919: that change
retired `PluginSecurityScanner`, whose type-only import was the family's only
importer of any kind, leaving `KernelSecurityScanResult`,
`KernelSecurityVulnerability`, `PluginSecurityManifest.scanResults` and
`PluginQualityMetrics.securityScan` fully published with zero consumers and no
`.parse`/`.safeParse` site anywhere.

The two defs leave the build whole (`RETIRED_DEFS_BY_MAJOR[18]`) because nothing
parses them. The authorable keys are `retiredKey()` tombstones registered in
`RETIRED_KEYS_BY_MAJOR[18]` — neither carrying shape is `.strict()`, so a bare
deletion would strip an authored key in silence (ADR-0104).
`PluginSecurityManifest.vulnerabilities` is a forced consequence: it was the last
authorable referent of `KernelSecurityVulnerability`.

No D2 conversion — a plugin security manifest and a plugin registry entry are
package artifacts a publisher ships, never stack collection members and never
stored `sys_metadata` rows. The D3 semantic entry
`plugin-security-scan-result-surface-retired` carries the judgement.

The marketplace `'scanning'` status and the incident `'malware'` type are
deliberately untouched: the ruling made them conditional on a producer grep of
`objectstack-ai/cloud`, which is not reachable from this session.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
@github-actions

github-actions Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 14 documentable anchor(s). ⚠️ 11 changed file(s) yielded no anchor (packages/spec/api-surface/kernel.json, packages/spec/authorable-defaults/kernel.json, packages/spec/authorable-surface/kernel.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/kernel/cluster.mdx (via RETIRED_DEFS_BY_MAJOR (symbol, a top-level const object))
What this run could not see
  • 11 changed file(s) yielded no anchor (packages/spec/api-surface/kernel.json, packages/spec/authorable-defaults/kernel.json, packages/spec/authorable-surface/kernel.json, …) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 5c5b67fc4140f76ca3158acea9e0845d9eebfad8packageMentionDocs.

Which tree this was computed on

This run read content/docs from 7efd0522b900787f168562d2ccf4c4d81194f9e5 — the merge of head 3e0a06d0b521e0df9d54dd2356b860f84d9a45ab into base 5c5b67fc4140f76ca3158acea9e0845d9eebfad8, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7efd0522b900787f168562d2ccf4c4d81194f9e5 && git checkout 7efd0522b900787f168562d2ccf4c4d81194f9e5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5c5b67fc4140f76ca3158acea9e0845d9eebfad8 3e0a06d0b521e0df9d54dd2356b860f84d9a45ab && git checkout -B drift-repro 5c5b67fc4140f76ca3158acea9e0845d9eebfad8 && git merge --no-ff 3e0a06d0b521e0df9d54dd2356b860f84d9a45ab

node scripts/docs-audit/affected-docs.mjs --json 5c5b67fc4140f76ca3158acea9e0845d9eebfad8

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 5c5b67fc4140f76ca3158acea9e0845d9eebfad8 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ger serves

`check:issue-citations` went red at 0a1bac8 with eight dangling sites across
five files: issue 14919 at six of them and issue 8715 at two. Measured cause,
from the gate's own `--probe-cause`: deleted, all eight — not transferred, not
mistyped. Probed with a lit control beside each, since both dead numbers sit
next to live ones: 8714 404 / 8715 404 / 8716 200, and 14918 404 / 14919 404 /
14920 200. Scattered pairs, not a contiguous band, which is what
deletion-by-author looks like. Both were live references when the prose was
written.

⛔ No number is guessed and none is swapped for a plausible neighbour. Each site
keeps its number in prose and now says it no longer resolves, then names a
record that DOES — verified by probe, not inferred:

  - issue 14919 -> PR #15930, `feat(core)!: retire PluginSecurityScanner`,
    merged 2026-09-05, whose body opens with a closing line naming that very
    issue number. Probe: 200.
  - issue 8715  -> #11825, the half of the pair this tree cites together that
    still resolves, and the same whole-def disposition shape. Probe: 200.

The `#` sigil is what the gate judges (`CITATION_RE`); a bare number in prose is
not a citation, so the number survives verbatim and the reference stops
dangling. `migrations/registry.ts` is GENERATED and was NOT hand-edited: its
three copies come from the two entry files, re-emitted by
`gen:migration-registry`. The module docblock feeds a reference page, so
`check:generated --fix` regenerated
`content/docs/references/kernel/plugin-security-advanced.mdx` through `gen:docs`.

⛔ Nothing else moves: no schema, no key, no registry entry, no changeset level,
no authorable row. Comments and the prose they generate, only.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
…tes were stale

From the at-tier review, item ③.3. The POSTURE on the carve-out was right and is
kept verbatim: the marketplace 'scanning' status stays untouched and ⛔ NOT
recorded as checked. What was wrong is a COORDINATE — two of the three files
those records name no longer exist, so the present-tense clause "stay exactly as
they are, unremoved" was an assertion nobody had measured, and false for one half.

Verified here by shape rather than taken on report, since stale coordinates are
the defect being repaired. Tree entries on this branch AND on origin/main:

  marketplace.zod.ts        1   ('scanning' live)
  marketplace-admin.zod.ts  0
  system/incident-response.zod.ts  0
  'malware' in any *.zod.ts 0   (lit control: 'scanning' returns a live
                                 declaration, so the zero is a reading)

The incident 'malware' type was a member of system/IncidentCategory, and the
whole incident-response family was retired by #15513 — maintainer ruling
2026-09-05, two days BEFORE the 2026-09-07 ruling that made it conditional.
marketplace-admin.zod was deleted outright with the cloud subpath (#16526).

⇒ The conditional question is ONE enum member wide, not three, and the records
now say so — so whoever finally runs the objectstack-ai/cloud producer grep knows
its real size. ⛔ The 'scanning' half stays genuinely unmeasured; it does not
become "checked and clean".

Also repaired in the same prose, same defect class as the previous commit: ten
citations of the deleted issue 14919 that this card's own records were handing
on. None was visible to check:issue-citations — FOLLOW-UPS.md and .changeset are
not judged surfaces, and the semantic entry's are inside string literals, which
the comment-prose projection blanks. The changeset is what an upgrading agent
greps after a tombstone error, so a dead number there is the defect the gate
exists to prevent, one surface out of its reach. Same repair form: the number is
kept in prose, said to no longer resolve, and the live record named.

⛔ Nothing about the retirement changes: no schema, key, def, registry entry,
changeset level, authorable row or generated artefact. check:generated reports
all 15 up to date.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
@os-warren os-warren changed the title feat(spec)!: retire the plugin-security scan-result surface — zero consumers after #14919 (#15932) feat(spec)!: retire the plugin-security scan-result surface — zero consumers after the PluginSecurityScanner retirement (#15932) Sep 21, 2026
@os-warren
os-warren marked this pull request as ready for review September 21, 2026 18:40

Copy link
Copy Markdown
Collaborator Author

Ready, green, reviewed — and the last step is blocked with no channel

All three landing preconditions are met and recorded on the card:

  • ① at-tier contract review: PASS, with the served tier measured from the reviewer's transcript per assistant row, and the tier constant re-read from origin/main at 2026-09-21T18:06Z before the round was dispatched.
  • check-clause2-carriers --pair: EXIT=0, exit code captured before any pipe.
  • ③ CI by job conclusion, latest run per check NAME: 35 distinct names, 0 failure, 0 cancelled; every skip is on the EXPECTED_SKIPS roster. ⛔ No aggregate roll-up was read as the verdict.
  • Governance: ungoverned, 0 of 6 — predicate executed (GOVERNED_SURFACES + governedPathsIn imported from origin/main) rather than recalled, with a lit control (6/6) and a near-miss control (0/8). ⛔ .github/CODEOWNERS was not consulted; it is not a governed surface.

This PR has been flipped draft → ready, confirmed by GET /pulls/{n} returning draft: false — ⛔ not by the POST's status code.

auto_merge could NOT be enabled. The seat's session permission classifier refused the call, and there is no second channel: the MCP enable_pr_auto_merge tool is on this session's deny roster, and ⛔ working around a classifier refusal is not a channel. Recorded rather than retried, per the standing rule.

The seat will not merge this PR, enqueue it by hand, or submit an approving review on it. The only remaining act is enabling auto-merge with merge_method: SQUASH so the merge queue lands it — ⛔ never a direct merge, never a queue bypass.

Action needed from the maintainer or a seat with the channel: enable auto-merge (SQUASH). Everything else here is finished.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

✅ Correction — this PR is NOT blocked any more. It is in the merge queue.

domain:spec execution seat 2, session session_01UDXER3sdqfeVYpEWZs5mZx, 2026-09-22T00:3xZ.

The maintainer re-granted the permission and the call was retried. ⛔ The earlier standing-down note on this PR — "auto_merge could not be enabled … the only remaining act is enabling auto-merge" — is now false and is retracted here.

⚠️ And the seat's first read-back of that retry was WRONG. Recording it, because it is the more useful half.

PUT .../ccr/auto_merge returned 200 {"enabled":true,"merge_method":"squash"}. The seat then read GET /pulls/{n} and saw auto_merge: null on all four, and was one step from reporting "returned 200 but stored nothing" — the known 「状态码不作数」 failure shape.

That reading was the wrong instrument. On a repository with a merge queue, the action does not populate the auto_merge attribute at all — it enqueues the PR. The repo's own channel table says so in as many words: 「问本仓 auto-merge 是否经队列,答案来自尝试动作,不来自属性字段」, and its criterion ② is the added_to_merge_queue timeline event. The seat read the field the table warns has no discriminating power, ⛔ not the event the table names.

The evidence, on two independent instruments:

  1. Timelineadded_to_merge_queue on all four, at 00:35:03 / 00:35:05 / 00:35:06 / 00:35:08Z, the exact moment of the four PUTs.
  2. git, zero quota — the queue branches exist on origin and are chained, each built on the previous one's result:
gh-readonly-queue/main/pr-19602-1c16889a…  -> dc9e29bb
gh-readonly-queue/main/pr-19609-dc9e29bb…  -> 71f94e29
gh-readonly-queue/main/pr-19610-71f94e29…  -> 157c62f9
gh-readonly-queue/main/pr-19493-157c62f9…  -> 85265e6f

⇒ queue order #19602#19609#19610#19493, each tested against the cumulative result of the ones ahead of it. That is the merge queue doing its job, and it is ⛔ not a bypass: the seat did not merge, did not enqueue by hand, and submitted no approving review.

What happens next

Each PR merges as its queue branch goes green. ⚠️ A queue branch can still fail — it tests a combination that never existed before — and if it does, the PR is ejected and that is this seat's to diagnose, ⛔ not a re-enqueue on reflex.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Carrier stripped on a PASS that is on record — and the seat's own enqueue error, stated plainly

domain:spec execution seat 2, session session_01UDXER3sdqfeVYpEWZs5mZx, 2026-09-22T00:5xZ.

⛔ What I got wrong

I enqueued this PR at 00:35Z while needs:contract-review was still hung on both carriers. The merge queue's Governed Surface Queue Guard refused the queue build and ejected it, exit code 6, verbatim:

#19610 — ⛔ CARRIES \needs:contract-review` — this pull request may not be in the queue.`

The guard was right and the rule it cites is one I had already read: 「双肢命中任一 ⇒ 无席内条款②复核 PASS 在案 ⛔ 禁止入队」. I ran the three landing preconditions (at-tier PASS, --pair EXIT=0, CI green by job conclusion) and then did ready + enqueue — and skipped 剥标, which 「PASS ⇒ 同席剥标并引记录、ready、auto-merge」 puts before the enqueue, not after.

⚠️ Why --pair did not catch it, so nobody re-derives this: --pair EXIT=0 reported 「the needs:contract-review LABEL is in the same state on both LABEL carriers」. That is a statement about the two carriers agreeing with each other — ⛔ not a statement that the carrier should be gone. I read a consistency row as a release. Two distinct questions, one of which no gate was asking.

✅ Why stripping now is the sanctioned act and ⛔ not a way past the check

The guard's own text draws the line: "⛔ Stripping the label to get past this check, with no verdict on record, is the defect this leg was built from — not a way through it."

There is a verdict on record, and it is cited here rather than asserted:

At-tier contract review, VERDICT: PASS — comment 5765611333 on card #15932, head 3e0a06d0b521e0df9d54dd2356b860f84d9a45ab (this PR's current head, unmoved), served tier measured from the reviewer's transcript at 115/115 rows at CONTRACT_REVIEW_TIER. It was a scoped re-review discharging the single fail basis of the prior record 5765428945. Seat disposition adopting it verbatim: comment 5765643574.

⇒ the condition 「PASS ⇒ 同席剥标并引记录」 is satisfied. The carrier is stripped from both carriers — this PR and its card — by the same seat that adopted the verdict, in the four-step label write, with read-back.

What happens next

Carrier stripped on both sides → --pair re-run → re-enqueued. ⛔ The seat does not merge by hand, does not bypass the queue, and submits no approving review. If the queue ejects it again, that is a different failure and gets its own diagnosis — ⛔ no reflex re-enqueue.


Generated by Claude Code

@os-warren
os-warren added this pull request to the merge queue Sep 22, 2026
Merged via the queue into main with commit 744a0a3 Sep 22, 2026
61 checks passed
@os-warren
os-warren deleted the claude/issue-15932-retire-scan-result-surface branch September 22, 2026 01:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: the plugin-security-advanced scan-result surface has ZERO consumers after #14919 — 22 published authorable rows with no author and no parser

2 participants