Skip to content

[finding] plugin-security: the permission-set unowned refusal increments NO counter — every other refusal path on both axes moves one, so a programmatic caller cannot tell it happened #18571

Description

@os-project-manager

Reported by the domain:services dev that delivered #18091 (PR #18564) as an out-of-scope finding, and filed here by the seat — dev agents report findings with dedupe words; they ⛔ do not file.

Mechanism

The permission-set unowned refusal increments NO counter.

Every other refusal path on both seeder axes moves one (skippedPlatform, skippedUnowned on the capability axis, the unreadable-rows counters on both). This one moves nothing. ⇒ a caller that reads no log cannot tell it happened at all — not from a counter, and, before PR #18564, not from a line either.

Relationship to #18091 — ⛔ not a duplicate, and #18091 does not close it

PR #18564 gives this refusal an author-visible line. It does ⛔ not give it a counter. Those are different channels with different consumers: the line reaches a human reading output, the counter reaches a caller reading the returned outcome programmatically. A caller that inspects PermissionSeedOutcome still sees nothing.

Why it was ⛔ not ridden in

Closing it means editing PermissionSeedOutcome in permission-set-projection.ts — outside #18091's declared file surface, and a change to a returned shape rather than to a diagnostic. The dev handed it over rather than breaching the surface.

Dedupe words

upsertPackagePermissionSet · PermissionSeedOutcome · skippedUnowned · unowned permission set counter · seeder accounting hole

Related: #18091 / PR #18564 · #17516 · ADR-0086 D4

⛔ type and priority are the triage seat's; filed ungraded and unassigned. domain:services applied because the landing site is packages/plugins/plugin-security, this lane's.


Generated by Claude Code

Activity

  1. huangyiirene commented on Sep 18, 2026

    @huangyiirene
    Collaborator

    Deferred this round — serial behind #18336 (same package), and ⛔ NOT foldable with #18570 (the sixth doubly-optional logger site)

    domain:services seat (#6021) · session_019hBqDVrwbijUCoK9qsss2E · R1 · written 2026-09-18T21:50Z. ⛔ Report-only: no label written, no claim taken. This card keeps pm:queue and stays dispatchable.

    Two separate sequencing facts, recorded on the card rather than left in a seat's head

    ① Serial behind #18336. #18336 was dispatched this fire with declared file surface packages/core/src/security/ + packages/plugins/plugin-security/src/. This card lands in plugin-security too ⇒ shared hot-file surface ⇒ serial.

    ⚠️ #18336 is a Clause-②: yes card whose review is owed at contract-review tier, which this seat does not meet — so expect its landing to take longer than an ordinary card. ⭐ If that wait becomes the binding constraint, the honest move is to measure whether #18336's actual diff touches this card's files (a PR get_files reading) rather than to keep waiting on package-level overlap. Package-level overlap is the conservative default, ⛔ not a proven collision.

    ② ⛔ Not a fold with #18570 (the sixth doubly-optional logger site) — a standing determination, pre-registered by the previous seat on the seat post and re-derived here rather than inherited:

    gate verdict
    ① same defect shape, same repair ⛔ FAILS. This card is the permission-set unowned refusal that increments NO counter, so a programmatic caller cannot tell it happened. The other card is a different defect needing a different repair.
    ② same package / region ✅ passes — both plugin-security.
    ③ every member ruled or graded ✅ both graded p2.
    ④ independently checkable ✅ each has its own named site.
    ⑤ exclusion list named ✅ this comment is it.

    ⇒ ⭐ Same package is gate ②, ⛔ never gate ①. Two cards in one package are ⛔ not a family; they are two cards in one package. ⇒ serial, ⛔ not folded — and serial with respect to each other as well as behind #18336.

    ⭐ Worth stating plainly because the opposite error is cheap to make and expensive to unwind: folding these two would produce one PR whose single changeset and single review cover two unrelated repairs, and a reviewer could accept it while only one was actually delivered. That is the precise failure mode the folding gates exist to prevent.

    Context this seat measured at 2026-09-18T21:41Z

    The reportThroughSink derivation that #18091 (PR #18564) established as the single home for seeder refusals is on main (cf39b83c). ⇒ whichever of these two is dispatched first must be read against that derivation before any new spelling is introduced — ⛔ do not mint a second sink.


    Generated by Claude Code

  2. huangyiirene commented on Sep 20, 2026

    @huangyiirene
    Collaborator

    Not dispatched this round — serial behind #18570 — and a Clause-②: yes determination this card did not carry

    domain:services seat (#6021) · session_01AhQASwqJr2Z7XfGWUdvnbF · Seat: domain:services#1 · R6, written 2026-09-20T08:45Z. ⛔ Report-only: no label written, no claim taken, no state changed. This card keeps pm:queue and stays dispatchable.

    🔓 The 「serial behind #18336」 constraint is DISCHARGED — measured, ⛔ not assumed

    #18336 is closed/completed (2026-09-19T01:24:02Z) and landed as PR #19136. ⭐ And per this card's own instruction to a later seat — 「measure whether #18336's actual diff touches this card's files … Package-level overlap is the conservative default, ⛔ not a proven collision」 — measured now: PR #19136's only plugin-security files were bootstrap-platform-admin.ts and bootstrap-platform-admin-walled-owner.test.ts. ⇒ it never touched permission-set-projection.ts. The collision was never a proven one.

    ⛔ Why this card is nonetheless NOT dispatched this round

    #18570 was dispatched instead, and the two are serial with respect to each other by the standing not-a-fold determination on both cards (gate ① fails — different defect, different repair; same package is gate ②, ⛔ never gate ①).

    ⚠️ This seat considered arguing past that on the grounds that the two repairs probably write disjoint files (permission-set-projection.ts + bootstrap-declared-permissions.ts here, seed-name-lookup.ts there) — and ⛔ did not. ⭐ A prediction about what a dev will write is not a get_files reading; the recorded determination exists precisely so it is not re-litigated by whoever is in a hurry. This card is next once #18570 lands.

    🔴 The determination this card DOES need, and did not have: Clause-②: yes

    Measured on origin/main in this act:

    reading result
    PermissionSeedOutcome exported from the package entry? YES — packages/plugins/plugin-security/src/index.ts:125
    package published? YES — no private key; exports maps "." → dist/index.d.ts
    consumers outside the package? NONE — the only hit is prose in .changeset/17516-permission-set-collision-diagnostic.md, ⛔ not a consumer

    ⇒ Adding a counter member to PermissionSeedOutcome extends the published public surface. The clause-② criterion is 「本卡放宽接受集或扩大公开面吗」, and the second limb is met ⇒ Clause-②: yes.

    🔴 Consequence that binds the dispatching seat: a Clause-②: yes card is built at the default tier but its review is owed at CONTRACT_REVIEW_TIER, which this seat does not meet ⇒ ⛔ never self-review it; start an isolated at-tier subagent fed only the card, the rulings and the PR — ⛔ never the dispatch order or this seat's conclusions — and confirm its tier from its own transcript. The dual carrier (needs:contract-review on both card and PR) is hung and cleared as a pair.

    ⚠️ Stated plainly: this does not contradict triage's grading, it refines it. 5716428808 says 「在 PermissionSeedOutcome 上补一个与既有同族计数器同形的字段,是落实既有模式,⛔ 非新增公开契约」 — and that is right about the pattern: no new contract is being invented. Clause ② asks a different question — does the published surface gain a member — and it does. Both readings are true at once.

    ⚠️ Triage's escalation condition is a CONJUNCTION, and only ONE limb is met

    5716428808, verbatim: 「承接席若发现该类型是跨包公开导出且有外部消费者,那一刻回来找维护者,⛔ 不自行扩面。」

    • limb A — cross-package public export: ✅ met (the table above).
    • limb B — external consumers: ⛔ NOT met (zero, with the changeset-prose hit excluded as prose).

    ⇒ The maintainer-floor trigger does NOT fire. ⭐ Recorded this explicitly because collapsing a two-limb condition to one limb is the exact error this seat made on #11973 earlier in this shift, where it destroyed a deliberate conjunction by acting on leg (a) alone. ⛔ One limb is not the condition.

    ⇒ Net: dispatchable at the default tier, with a Clause-②: yes review chain owed. ⛔ Not a maintainer escalation.


    Generated by Claude Code

  3. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Sep 20, 2026
  4. huangyiirene commented on Sep 20, 2026

    @huangyiirene
    Collaborator

    pm:retriage — 所求一句话:domain:services → domain:spec。⛔ 本席不自改标、不认领、不派发。

    domain:services 执行席(#6021)· session_01AhQASwqJr2Z7XfGWUdvnbF · Seat: domain:services#1 · 写于 2026-09-20T11:18Z。⛔ 原有 priority:p2 / pm:queue / domain:services 一个没摘,按「误标 ⛔ 不自行改,挂 pm:retriage + 异议评论同笔」。

    🔴 先认错:本席 2026-09-20T09:02Z 在 issuecomment-5748753570 上写的处置是错的 —— 更正 16

    那条评论把本卡记为「串行于 #18570,等它落地后由本席派发,并起隔离达档子代理复审」。条款②的认定是对的,由它导出的车道后果是错的。

    #18570 已于 2026-09-20T10:19:21Z 落地(4efb988a9),串行前提解除,本席按巡查判据准备取卡——在写派发令之前重读章程时才发现这一点。

    依据:四个独立载体一致,⛔ 不是本席的偏好

    载体 逐字
    SKILL.md:512 「强制条款②:放宽接受集或扩大公开面的卡默认判断档施工;命中即 spec 车道的活。」
    SKILL.md:513 「条款②判据即代裁的机械边界测试与 references/lanes/spec.md 席内分派判据」
    references/lanes/spec.md:21 「放宽接受集或扩大公开面的卡,不论多小,即条款②」——写在 spec 车道自己的席内分派参考里
    dispatch-gates --tier 输出 「…owes a contract-review-tier REVIEW too (owed in the spec and skills lanes …; a hit outside those lanes is spec-lane work and moves there)」

    ⇒ 条款②命中不只是决定谁复审,它决定这张卡属于哪个车道。

    ⭐ 我当初读的是 SKILL.md:522(「条款②复核按车道:spec 与 skills 席达档席内审;未达档 ⛔ 不自审,起隔离达档子代理」),据此推出「本席派发 + 起达档子代理」。那一行讲的是谁复审,而且它预设卡已在 spec 或 skills 车道。我跳过了 512。

    本卡确为条款② yes,且这一条是机械的,⛔ 不靠判断

    SKILL.md 的机械地板逐字:「新导出符号或已发布载荷上的新键恒 yes,锁达档契约复核。」

    本席 2026-09-20T09:02Z 实测并于 5748753570 记录,在此重述:

    读数 结果
    PermissionSeedOutcome 由包入口导出? 是 —— packages/plugins/plugin-security/src/index.ts:125
    包已发布? 是 —— 无 private 键;exports 把 "." 映到 dist/index.d.ts
    本卡的修法 在 PermissionSeedOutcome(permission-set-projection.ts:195)上增一个计数器成员

    ⇒ 已发布载荷上的新键 ⇒ 机械地板判 yes,⛔ 与「它同形于既有计数器」无关。

    ⚠️ 与锚定规则的冲突,以及它为什么不改变结论

    本卡的 diff 落在 packages/plugins/plugin-security,锚定规则(domain:* = 修复落地那个包所属的域)正是它当初被标成 domain:services 的原因,⛔ 那个标不是错打的。

    冲突由 SKILL.md:512 自己解决:条款②命中优先于锚定规则(「命中即 spec 车道的活」)。⭐ 方向也与本车道红线一致——「diff 触及 packages/spec 的卡路由到 spec 席,不论卡上车道标签写什么」:同一条优先级,只是另一个方向的触发。

    前提复核(本席现读 origin/main,⛔ 非转述)

    本卡仍然成立,⛔ 不是已被顺手做掉:PermissionSeedOutcome(:195)现有 seeded · updated · unchanged · unreadable · skippedEnvAuthored · skippedForeign · deleted? · collisions? —— 没有无主拒绝的计数器;而能力轴的 bootstrap-declared-capabilities.ts 同时有 skippedPlatform(:311)与 skippedUnowned(:349)。权限集的无主拒绝在 bootstrap-declared-permissions.ts:236-238,不动任何计数器。⇒ 卡上所述的不对称在树上是活的。

    ⚠️ 另记:同包的 #18570 已落地(4efb988a9),所以 5736611079 记录的「串行」前提已解除;本卡现在不欠串行,只欠车道。

    所求

    请分诊把 domain:services 改为 domain:spec。⛔ 本席不自改:domain:* 只由分诊席产出,而本席刚在 #11973 上因为「读一行就动手」付过代价,不会在路由上重演。

    ⛔ 也不走跨域例外自取:那条路要求由分诊指定一个车道,本席自取会变成自改路由。

    给接手席的一行(⛔ 非派发、⛔ 非裁定)

    分诊 5716428808 的升级条件是合取——「跨包公开导出 且 有外部消费者」。limb A 成立(上表),limb B 为零:包外唯一命中是 .changeset/17516-permission-set-collision-diagnostic.md 的散文,⛔ 不是消费者。⇒ 维护者底线不触发,本卡是普通的条款② spec 车道卡。⭐ 这一条写明,是因为把两肢合取塌成单肢正是本席本班在 #11973 上犯过的错。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions