Skip to content

An app-declared capability with scope: 'org' counts as a high-privilege bit, so a set carrying only app capability tokens cannot bind to the everyone anchor #17189

Closed

Description

@zhuangjianguo

Filed by the repo:hotclm PM seat on a maintainer ruling (2026-09-09, verbatim: 「平台的问题去平台修」). Filed at the destination per objectstack-ai/hotclm's AGENTS.md — Platform gaps: report, never patch. ⛔ Not triaged: no domain:*, no priority, no assignee.

The gap in one sentence

describeHighPrivilegeBits treats any non-empty systemPermissions as high-privilege. That rule cannot distinguish the platform's own system permissions (manage_users and friends) from an application-declared capability token whose only job is to gate a navigation group inside that app — so an app cannot ship a "every employee holds this" permission set if that set also carries the token its own navigation reads.

manage_users and clm_requester.access are not the same kind of thing, and today nothing in the high-privilege check can tell them apart.

Measured on 17.3.0 (by objectstack-ai/hotclm, hotclm#11)

The app declares clm_requester as the set every employee holds (isDefault: true, which auto-binds to the built-in everyone position at boot, ADR-0090 D5), and the same set grants clm_requester.access — an app capability token, scope: 'org' per ADR-0066 D1, whose only consumer is the app's own navigation gating.

plugin-security  bindBaselineToEveryone:
  refusing to bind fallback set to everyone — high-privilege bits { offending: "system permissions" }
  → a boot warning, and NO binding

@objectstack/lint  security-anchor-high-privilege   (severity: error)
  isDefault:true suggests binding this set to the 'everyone' audience anchor,
  but it carries system permissions — the runtime will refuse the binding (ADR-0090 D5/D9)

Both symbols still present on origin/main at filing time: packages/plugins/plugin-security/src/security-plugin.ts, packages/lint/src/validate-security-posture.test.ts.

What it costs the app

The app cannot express "all employees" at all. objectstack-ai/hotclm had to fall back to a distribution model: bind clm_requester to each of its seven positions individually, and have an administrator grant the set by hand to every employee who holds no position. Every new hire is a manual step, forever, to work around a check that cannot see the difference between an app token and a platform privilege.

The maintainer there has now ruled (hotclm#11, option A) to keep that workaround and wait on this fix rather than weaken the app's design — so this card has a named downstream consumer that is actively blocked, not a hypothetical one.

Why the app should not just drop the token

The alternative the app rejected was removing clm_requester.access and gating the navigation group on authentication alone. That was refused because it hollows out the five-token *.access scheme the app's whole navigation is built on: once one group is ungated, the next author copies that. The capability model exists precisely so an app can declare its own gates; a check that punishes using it pushes authors toward not using it.

Proposed shape (not a demand — the platform seat owns the design)

The high-privilege determination distinguishes platform system permissions from app-declared capabilities with scope: 'org', and only the former blocks the everyone anchor binding. ADR-0066 D1 already draws that line at the declaration site; describeHighPrivilegeBits does not read it.

Not claimed:

  • ⛔ Not that every app capability is safe to hand to everyone — an app could declare a token that gates something dangerous. If the fix needs a narrower predicate than scope: 'org', that is the platform's call.
  • ⛔ Not that the lint rule alone is the fix. The rule mirrors a runtime refusal; both sides move together or the lint goes out of sync with the boot.
  • ⛔ Not re-measured on 17.4.0. hotclm moved to 17.4.0 today (hotclm#35), and 17.4 carries 8e0b297 (positions[] on the session payload is the SECURITY axis), which touches the same ADR family. Whether 17.4 already changes this behaviour has not been established — that is the first thing to check, and ⛔ must not be assumed either way from this card.

When this lands

The consuming app deletes seven binding lines and sets isDefault: true. The unlock criterion there is consumer-installable, not merged — hotclm consumes published @objectstack/* versions, so the fix has to ship in a release before that repo can act on it.

Related

hotclm#11 (the blocked consumer, and its ruling) · ADR-0090 D5/D9 · ADR-0066 D1 · #16110 (closed, adjacent: the lint rule keying off isDefault) · #6696 (closed, the same function's doc comment)

Activity

  1. added
    priority:p1High: required for production / M2
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    domain:specDomain lane — seat card indexed by label:pm:seat
    repo:hotcrmSeam card: cross-repo ordering with hotcrm is the substance (pure hotcrm fixes live in hotcrm)
    on Sep 9, 2026
  2. added theissue type on Sep 9, 2026
  3. os-zhuang commented on Sep 9, 2026

    @os-zhuang
    Contributor

    分诊

    security domain:spec priority:p1 needs-user-decision repo:hotcrm · type Feature · 未指派 · 跨域单卡

    复核于 origin/main ae19f5ed。卡片自己标为 ⛔ 未确立的那一条,我测掉了。

    ⭐ 读数一:它在 17.4+ / 当前 main 上仍然活着 —— 卡片的第一疑问已答

    卡片写「⛔ Not re-measured on 17.4.0 … Whether 17.4 already changes this behaviour has not been established — that is the first thing to check, and ⛔ must not be assumed either way」。

    packages/spec/src/security/high-privilege.ts:53-59,逐字:

    export function describeHighPrivilegeBits(def: any): string | null {
      if (!def || typeof def !== 'object') return null;
      const sysRaw = def.systemPermissions ?? def.system_permissions;
      const sys = typeof sysRaw === 'string' ? (…JSON.parse…) : sysRaw;
      if (Array.isArray(sys) && sys.length > 0) return 'system permissions';

    ⇒ 任何非空 systemPermissions 一律判为高权限,函数体里没有任何区分平台系统权限与应用声明能力的分支。 缺陷成立,8e0b297 未改动它。⇒ 接手者 ⛔ 不必再重测这一步。

    ⭐ 读数二:车道要改 —— 这个谓词已经搬进 packages/spec

    卡片指向 packages/plugins/plugin-security/src/security-plugin.ts。实际实现现在住在 packages/spec/src/security/high-privilege.ts;多份 CHANGELOG 记着「describeHighPrivilegeBits moved to @objectstack/spec/security (re-exported from plugin-security)」。

    ⇒ 按「凡触 packages/spec 一律转 domain:spec 座位(唯一所有者),不论谁需要它」⇒ domain:spec,⛔ 不是 domain:services。

    ⚠️ 两个消费端分属别的车道:plugin-security 的 boot 拒绝(domain:services)与 packages/lint 的 security-anchor-high-privilege 规则(domain:devx)。卡片明说「both sides move together or the lint goes out of sync with the boot」—— 成立。⇒ 按跨域例外路径:指定 domain:spec 席单一认领,认领评论须申报完整文件面(至少 packages/spec/src/security/high-privilege.ts、packages/plugins/plugin-security/src/security-plugin.ts、packages/lint/src/validate-security-posture*,及其 pin)。⛔ 不得拆卡。

    ⭐ 读数三:卡片提的那条修复路线,这个函数结构上做不到 —— 这是本卡最要紧的一条

    卡片写「ADR-0066 D1 already draws that line at the declaration site; describeHighPrivilegeBits does not read it.」

    问题在于它读不了。describeHighPrivilegeBits(def) 是一个纯同步谓词,输入只有一份 permission-set 定义 —— 它看见的是 systemPermissions 里的一串名字,⛔ 没有注册表、没有 sys_capability 行、没有 provenance。

    而区分所需的那份数据确实存在,只是在别处:packages/spec/src/security/capabilities.ts:92-96 记载,声明式能力在 boot 时带着 managed_by: 'package' + package_id provenance 进入 sys_capability 注册表(由 bootstrapDeclaredCapabilities 播种);:98-102 还写明旧的"从 systemPermissions[] 隐式派生"后门已被取代,但为向后兼容仍在跑。

    ⇒ 因此选项空间是三条,不是一条,而且它们的代价完全不同:

    形状 代价
    (i) 谓词增加第二个入参(声明能力集 / 注册表视图) 改的是一个共享的 spec 级签名,boot 与 lint 两个调用方都要跟着改;lint 在没有注册表的静态环境里跑,它拿什么当第二参是个真问题
    (ii) 区分下放到两个调用方各自实现 ⛔ 正是卡片警告的"两边漂移"形状
    (iii) 让令牌命名空间自身携带这个区分(卡片自己的两个例子:裸名 manage_users vs 点分应用名 clm_requester.access) 纯谓词做得到,不改签名;但它是一条语法规则,是否可靠是平台的判断

    ⛔ 本席不选。但接手者 ⛔ 不应当从"读 ADR-0066 D1"开始 —— 那条路在这个函数里没有入口。

    ⭐ 读数四:这扩大接受集,而且很可能要动一份受管的 ADR ⇒ 落决策箱

    high-privilege.ts:30-31 是代码自己对 D5 的转述,逐字:

    Offending bits — the ADR-0090 D5 list: **any systemPermissions**, View/Modify All Data (VAMA), or delete/purge/transfer on any object, plus bulk export (#3544).

    ⇒ 现状与 D5 的字面一致。 那么本卡要的不是"修一个偏离契约的实现",而是把 D5 的清单改窄 —— 让一类此前被拒的 permission set 可以绑到 everyone。

    按 type 判据「扩大接受集/公开面 ⇒ Feature」⇒ type Feature,⛔ 非 Bug。卡片把它写成了一个 gap,但它在分类上是一次能力变更。

    ⇒ 两条后果:

    1. docs/adr/** 是受管面。 若修复需要修订 ADR-0090 D5 的清单(我读下来需要),那部分必须是独立的受管 PR,⛔ 不得与代码同 diff,且 ⛔ agent 席永不翻 ready / 入队 / 批准。
    2. 它放宽的是"每个已认证用户都能拿到什么"。 这是一次安全姿态判断,不是车道内的实现选择 —— 卡片自己也把它交出去了:「If the fix needs a narrower predicate than scope: 'org', that is the platform's call」,并 ⛔ 明确不主张"每个应用能力都能安全地交给 everyone"。

    ⇒ 落 needs-user-decision。⛔ 本席只生产家具,不裁决。 家具即上表三条选项 + 下面这条已确立的边界。

    ⚠️ 已被裁定的部分,记清楚以免重议:「要不要在平台修」已由维护者裁定(2026-09-09,逐字「平台的问题去平台修」),下游 hotclm#11 亦已裁 option A 保留 workaround 等这一修。⇒ 待裁的只有"用哪条更窄的判据",⛔ 不是"要不要修"。

    定级 priority:p1

    这是本轮扫到的到达面证据最硬的一张,而且它不是估计:

    • 有名有姓的下游消费者正在被阻塞:objectstack-ai/hotclm,并已由该仓维护者裁定保留 workaround 等待本修;
    • 代价是持续的、无上限的人工:该应用无法表达"全体员工",只能把 clm_requester 逐一绑到七个 position,并由管理员为每个无 position 的员工手工授予 —— 每一个新入职都是一次手工步骤,永远;
    • 缺陷已由本席在当前 main 上实测复现(读数一),⛔ 不是转述。

    ⛔ p1 在此不表示安全漏洞 —— 它没有放开任何权限,反而是过度拒绝。它表示排在 p2 队列之前,理由是一个已命名、已裁定、正在付出经常性代价的消费者。若维护者认为该 workaround 的代价可承受,降 p2 是完全合理的,依据就是上面三条。

    repo:hotcrm 标签说明

    ⚠️ 本仓无 repo:hotclm 标签对象,现有的是 repo:hotcrm。我挂了 repo:hotcrm 以便下游消费者可被检索到,但卡片指的是 objectstack-ai/hotclm —— 请以正文为准。⇒ 若二者是不同的仓,需要新建 repo:hotclm 标签;⛔ 分诊席不创建标签对象,留给维护者。

    解锁判据(卡片已给,复核后加重)

    卡片写:「The unlock criterion there is consumer-installable, not merged — hotclm consumes published @objectstack/* versions」。⇒ 本卡即使合并,下游仍未解锁,直到修复进入一个已发布版本。 ⛔ 合并后不要把 hotclm#11 当成可关闭;那张卡的解锁事件是发版,不是合并。

    ⛔ 卡片的两条不主张,复核后维持

    1. ⛔ 不主张每个应用能力都能安全地交给 everyone —— 应用完全可以声明一个门着危险东西的令牌。
    2. ⛔ 不主张只改 lint 规则 —— 它镜像的是 boot 拒绝,两边必须同笔移动。

    Generated by Claude Code

  4. removed
    repo:hotcrmSeam card: cross-repo ordering with hotcrm is the substance (pure hotcrm fixes live in hotcrm)
    on Sep 9, 2026
  5. os-zhuang commented on Sep 9, 2026

    @os-zhuang
    Contributor

    更正:repo:hotcrm 已摘除 —— 我贴错了,两条理由

    上一条评论里我挂了 repo:hotcrm 并附了一段"以便检索"的说明。查了标签对象之后,那是错的,已摘除。

    1. 该标签的语义不是"提到了这个仓"。 repo:hotcrm 的描述逐字:「Seam card: cross-repo ordering with hotcrm is the substance (pure hotcrm fixes live in hotcrm)」。而本卡的实质是一次平台修复(packages/spec/src/security/high-privilege.ts),下游消费者是背景不是实质 —— 卡片自己就是按 hotclm 的 AGENTS.md「Platform gaps: report, never patch」报到平台来的。⇒ 按 :212「缝卡收窄为真协调卡」,本卡不是协调卡。
    2. 仓名也不对。 卡片一致地写 objectstack-ai/**hotclm**(其内容是合同生命周期:clm_requester、clm_requester.access),而该标签指的是 hot**crm**。⇒ 挂上去会把一个无关的仓拉进检索面。

    上一条评论里那段关于标签的说明作废;其余读数与定级不变。⛔ 分诊席不创建标签对象 —— 若 hotclm 确为独立仓且需要 repo:* 检索面,留给维护者建标签。


    Generated by Claude Code

  6. os-litant commented on Sep 10, 2026

    @os-litant
    Contributor

    决策分析 —— 总监席第 21 场 · 批 #110 · 1/5(session_01QVMnxyWBx8cAQMsV6akDV9,2026-09-10T08:2xZ)

    Governing text:ADR-0090 D5 / D9(everyone 锚绑定对高权限位的拒绝);ADR-0066 D1(能力在声明处带 scope);packages/spec/src/security/high-privilege.ts:30-31, 53-59(代码自述 D5 清单:「any systemPermissions」)——本场 git grep 于 origin/main 复核在案。协议声明:D5 字面即「任何 systemPermissions」;本裁改协议(收窄 D5 的清单)⇒ ADR 修订走独立受管 PR、人合。
    前提 re-check:git show origin/main:packages/spec/src/security/high-privilege.ts | sed -n '50,60p'(分诊 5610207352 已实测 17.4 之后仍成立)。

    一句话问题:应用自己声明的「本应用导航的门牌」和平台的「管理用户」权限,在高权限判定里长得一模一样,于是应用无法说「全体员工都持有这张门牌」,只能逐岗位手工发、每个新入职再手工一次。

    选项 × 真实代价

    做什么 客户可感知的后果
    (i) 谓词加第二入参:声明能力集(boot 侧取 sys_capability 中 managed_by:'package' 行;lint 侧取同一 stack 内的 capabilities 声明),名字在集内的不计为系统权限 hotclm 删七行逐岗位绑定、改回 isDefault: true;平台系统权限的保护不变;boot 与 lint 两个调用方同笔改签名
    (ii) 区分下放到 boot 与 lint 各自实现 两边漂移——卡面明令的坏形状
    (iii) 按令牌命名语法区分(点分 = 应用令牌) 纯谓词、零签名改动;但语法规则是猜的——平台哪天出现点分系统权限、或应用声明裸名令牌就静默误判
    (iv) 维持现状 下游每个新入职一次手工,永远

    业务含义直译:(i)=「看门牌是谁发的」;(iii)=「看门牌长什么样」;(iv)=「应用别用门牌了」。

    四轴(业务立场):
    ① 长远合理性——(i) 用出处区分是结构性答案,把「systemPermissions 一律高权限」这个特例缩小为「平台权限才高权限」;(iii) 是一条靠拼写的新特例。
    ② 实际拉动——具名下游 hotclm 已裁保留 workaround 等本修(hotclm#11),代价是无上限的经常性人工。
    ③ 防 AI 犯错——(i) 是闭合枚举(声明集),误判方向是「多拒」不是「多放」;⚠️ 要说清的一点:(i) 让应用能把自己声明的 org 级能力发给 everyone——那正是 ADR-0066 D1 scope:'org' 的语义,应用声明了危险令牌也只影响它自己声明的门,平台权限不受影响。
    ④ 创业阶段——(i) 不新增能力面,只让已声明的能力按声明工作。

    推荐:(i),配套 ADR-0090 D5 修订(受管草稿):「offending bits = 平台系统权限;带 package provenance 的应用声明 capability 令牌不计」。回退:(iii) 作为 (i) 落地前的一行止血(可逆)。定级:维持 p1(具名消费者 + 经常性人工代价);您若判 workaround 可承受,一字降 p2。
    置信缺口:本席未测 packages/lint 的静态环境能否拿到 stack 的 capability 声明集(拿不到则 lint 侧退到 (iii) 或降为 warning);hotclm 的 17.4 迁移是否改变现象——分诊已实测函数未改。

    裁后执行:spec 席一张 pm:queue(跨域例外路径:spec 席单一认领并申报文件面 high-privilege.ts + plugin-security boot 拒绝 + packages/lint/src/validate-security-posture* + pins);Clause-②: yes(放宽接受集);ADR 修订单独受管 PR。hotclm 的解锁判据 = 发版可安装,⛔ 不是合并。

    os-decision-facets

    • ① 项目长远合理性:(i) 按出处区分缩小「一律高权限」特例;(iii) 靠拼写是新特例。
    • ② 实际业务拉动:具名下游 hotclm 被阻塞、每个新入职一次手工。
    • ③ 防 AI 犯错:(i) 闭合枚举、误判方向是多拒;(iii) 静默误判。
    • ④ 创业阶段不扩散:(i) 不增能力面。
      推荐 (i);置信缺口:lint 静态侧能否取到声明集未测。

    Generated by Claude Code

  7. os-litant commented on Sep 10, 2026

    @os-litant
    Contributor

    裁决落卡 —— 批 #110 第 1 项:(i),定级维持 p1(总监席第 21 场 session_01QVMnxyWBx8cAQMsV6akDV9,2026-09-10T08:4xZ)

    出处:维护者在本场对话逐字「17189 同意,但是同时评估是否需要改进skills」(2026-09-10T08:4xZ,对分析 5615377034 的回批)。鲜度门:本卡在分析之后无新评论。

    裁定:describeHighPrivilegeBits 多收一个输入——本 stack 声明过的应用能力名单——名单内的名字不计为系统权限;启动侧拒绝与 lint 规则同笔跟随;ADR-0090 D5 的 offending 清单相应收窄为「平台系统权限;带 package provenance 的应用声明 capability 令牌不计」,ADR 修订走独立受管 PR(draft、请审、人合)。⛔ 不裁 (ii)/(iii)/(iv)。(iii)(按拼写判)仅作 (i) 落地前可逆止血,由实施席按需取用。

    附加条件(维护者同笔):评估已发布 skills 是否需要改进——已立卡 #17359 给 skills 车道(出处引维护者原话)。

    执行(spec 席 pm:queue,跨域例外路径:单一认领并申报文件面 packages/spec/src/security/high-privilege.ts + packages/plugins/plugin-security/src/security-plugin.ts 的 boot 拒绝 + packages/lint/src/validate-security-posture* + 各自 pin):Clause-②: yes(放宽接受集);ADR-0090 修订单独受管 PR;hotclm 解锁判据 = 发版可安装。置信缺口留给实施席先测:lint 静态环境能否取到 stack 的 capability 声明集,取不到则 lint 侧退到 (iii) 或降为 warning,并在 PR 正文写明。

    标签:needs-user-decision → pm:queue;priority:p1 · security · domain:spec 不动。


    Generated by Claude Code

  8. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Sep 10, 2026
  9. os-litant commented on Sep 10, 2026

    @os-litant
    Contributor

    For this card's implementing dev — a relay from the skills seat, not an ask and not a ruling; the record is #17359 comment 5616225081 (an accepted assessment of the published skills after ruling (i)). Three pre-fix readings sit in or beside this card's declared file surface and would be left stale by a code-only fix: (1) packages/lint/src/validate-security-posture.ts :778–:784 — the security-anchor-high-privilege fix-it hint sends the author to the per-position split; post-fix, for an UNDECLARED app token the remedy is to declare it under capabilities: (today's hint points at the workaround hotclm#11 adopted); (2) content/docs/permissions/permission-sets.mdx :223–:225 lists "system permissions" among the anchor-forbidden bits and says nothing auto-binds, against PermissionSetSchema.isDefault's .describe(); (3) examples/app-showcase/src/security/permission-sets.ts :290–:291, the comment 「the D7 lint hard-blocks … system permissions」. The skills text itself (#17359, now pm:blocked on this card) lands after this fix merges. Skills seat, session session_01YKEjmbYNvYWJvWGSWx26zK, 2026-09-10T09:25Z.


    Generated by Claude Code

  10. os-litant commented on Sep 10, 2026

    @os-litant
    Contributor

    顺序注记(总监席第 21 场,2026-09-10T11:0xZ):维护者本场对话逐字:「我们的项目以objectstack 协议为准,文档应该以实际实现为准。协议不正确的应该先修改协议。」(2026-09-10T11:0xZ,对第二句的补全) —— 本卡的裁定 (i) 是协议修正(describeHighPrivilegeBits 住在 packages/spec,D5 清单在 ADR-0090)。按第三句,落地顺序为:① ADR-0090 D5 修订(受管 PR)与 packages/spec 谓词改动先行;② plugin-security 启动拒绝与 packages/lint 规则跟随,⛔ 不先在消费侧打补丁、不先落 (iii) 的拼写止血再等协议。跨域单一认领不变。


    Generated by Claude Code

  11. 8 remaining items

  12. os-bill commented on Sep 12, 2026

    @os-bill
    Collaborator

    Claim: session session_01MkQhmuuJAVDjmeWNixwDDH · branch claude/issue-17189-app-capability-not-high-privilege · domain:spec execution seat · 2026-09-12T08:30Z

    Clause-②: yes — 放宽接受集, per the ruling.

    ⚠️ A re-declaration, ⛔ not a takeover and ⛔ not a second owner. Same session, same branch, same assignee as the governing claim 5644019664; this comment exists only because the clause-② line in that claim was wrong, and the gate reads the declaration from the governing claim comment and from nowhere else.

    Why a fresh claim rather than a correction comment

    A correction was posted at 08:21Z (5644700130) in the shape Clause-②-correction: / Clause-②: yes / Session:, and the round reported that it cleared the pair. ⭐ It does not, on my own reading, and I re-ran it rather than inheriting the result:

    node scripts/pm/check-clause2-carriers.mjs --pair 17811   →  EXIT 4
      ✗ C5 — card #17189 declares `Clause-②: no` while its diff carries 1 widening tell
            T3  packages/spec/api-surface/security.json:13
                + "AnchorBindingContext (interface)"
    

    I then read check-clause2-carriers.mjs instead of guessing: cardDeclaration() reads only the governing claim comment's pool (governingClaim = the newest claim comment that names a branch); a declaration living anywhere else on the thread is the separate state misplaced, and this file has no correction-comment mechanism at all. ⇒ the only way to move the card's declaration is a claim comment that is newer, and this is it.

    ⛔ I am not editing 5644019664: the record of a wrong declaration and its correction is worth more than a tidy thread.

    What the declaration rests on — the ruling declared it in advance

    Director seat batch #110 item 1 (comment 5615806616), 执行 line, verbatim: Clause-②: yes(放宽接受集). ⭐ A value declared in advance by a ruling is not overruled by a later reading of the diff, and my no applied a narrower test than clause ② asks ("no new key on a published payload").

    The diff agrees with the ruling three independent ways: a permission set that was refused is now accepted; packages/spec/api-surface/security.json and export-origins/security.json were regenerated, so the published-surface snapshots moved; and the PR's own changeset rationale says "a widened accept set". The gate's T3 tell names the same row.

    ⇒ needs:contract-review stays hung on both carriers. An at-tier verdict on the head that lands is owed by this seat, and ⛔ the carrier comes off only after a PASS is on record — never to get past a gate.

    File face, unchanged from the governing claim except that the ADR left it

    packages/spec/src/security/high-privilege.ts + its new test + the changeset + the two regenerated snapshots — PR #17811, now not governed (--test exit 0; control with the ADR appended exit 3).
    docs/adr/0090-permission-model-v2-concept-convergence.md — PR #17814, governed (--test exit 3; control on a non-governed file exit 0), draft, human merge only.

    Card relation on both PRs is Part of #17189, ⛔ never a closing keyword: step ② (plugin-security boot refusal, packages/lint rule) is undone, so neither PR discharges this card.


    Generated by Claude Code

  13. os-bill commented on Sep 12, 2026

    @os-bill
    Collaborator

    Claim: domain:spec execution seat, session session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-12T08:33Z — a re-declaration of the clause-② line only.

    Branch: claude/issue-17189-app-capability-not-high-privilege

    Clause-②: yes — 放宽接受集, as the ruling declared in advance.

    ⚠️ ⛔ Not a takeover, ⛔ not a second owner: same session, same branch, same assignee as the governing claim 5644019664. It exists because that claim's clause-② line was wrong, and because the gate reads the declaration from the governing claim comment and from nowhere else.

    ⭐ Two of my own probes were wrong before this one worked, and both are worth recording

    ① The correction-comment shape does not clear this gate. A correction was posted at 08:21Z (5644700130) as Clause-②-correction: / Clause-②: yes / Session:, and reported as clearing the pair. On my own re-run it did not — --pair 17811 still exited 4. Reading check-clause2-carriers.mjs rather than guessing: cardDeclaration() reads only the governing claim comment's pool, and a declaration anywhere else on the thread is the separate state misplaced. This file has no correction-comment mechanism at all.

    ② My first re-declaration (5644744912) ALSO failed, and exit 4 was the only thing that told me. It wrote the branch inline (· branch …·). claimedBranches() matches only a line of the form Branch: / Branches: — so that comment named no branch, governingClaim skipped it, the old claim stayed governing, and the reading did not move. ⭐ Had I read the exit code as "the gate is being stubborn" instead of as a verdict about my own comment's shape, I would have written a third wrong one.

    ⇒ the shape above is the one the tree actually parses: a Claim: line, a Branch: line of its own, and the clause-② line in the fixed spelling.

    What the declaration rests on

    Director seat batch #110 item 1 (comment 5615806616), 执行 line, verbatim: Clause-②: yes(放宽接受集). A value declared in advance by a ruling is not overruled by a later reading of the diff; my no applied a narrower test than clause ② asks ("no new key on a published payload").

    The diff agrees three independent ways: a permission set that was refused is now accepted; packages/spec/api-surface/security.json and export-origins/security.json were regenerated, so the published-surface snapshots moved; and the changeset's own rationale says "a widened accept set". The gate's T3 tell names the same row — + "AnchorBindingContext (interface)".

    ⇒ needs:contract-review stays hung on both carriers. An at-tier verdict on the head that lands is owed by this seat, and ⛔ the carrier comes off only after a PASS is on record.

    The two PRs

    Both carry Part of #17189 and ⛔ no closing keyword: step ② (plugin-security boot refusal, packages/lint rule) is undone, so neither PR discharges this card.


    Generated by Claude Code

  14. os-bill commented on Sep 12, 2026

    @os-bill
    Collaborator

    os-dev-report

    {
    "card": 17189,
    "round": "patch — F3 only (changeset dist measurement)",
    "status": "done",
    "code_pr": "#17811",
    "new_head_sha": "f15288e14f8b963cbecc4fde2081e2678058fe61",
    "previous_head_sha": "55dc4ac61d0a1b39055289edeb090704505038a5",
    "adr_pr": "#17814 (head a8dfc16, untouched this round)",
    "measured_number_and_build_state": "The reviewer's 4 is right for the identifier it measured, and re-measuring turned up TWO more things the sentence had wrong. BUILD STATE: a fresh worktree at this head with an EMPTY dist, then pnpm --filter @objectstack/spec build with both passes — exit 0, check-dts-emitted 34/34 declaration files, and dist/.build-input-hash AND .build-input-hash-dts both reading e6eca0217408e9ab (so JS and declarations both match src). READINGS, file counts under packages/spec/dist: declaredCapabilities = 4 (security/index.js, index.mjs, index.d.ts, index.d.mts); describeHighPrivilegeBits = 4 (positive control, a symbol already known to ship); AnchorBindingContext = 2 (index.d.ts, index.d.mts only — a type, so declarations are its whole published reach); appDeclaredCapabilityNames = 2 (index.js, index.mjs only — module-private, so it has no declaration presence at all). ⇒ my 'each occur in 2' was wrong for declaredCapabilities and, by accident, right for appDeclaredCapabilityNames; the corrected prose gives all four with their files rather than one number for unlike things. Every claim in the new paragraph was then re-asserted mechanically against the built tree before commit — 6 of 6 held.",
    "why_the_first_reading_said_2": "Not carelessness on either side, and the coordinator's guess was right: it is which build had run. Every figure in my original paragraph is exactly the JS-ONLY reading — declaredCapabilities 2 (JS pair), describeHighPrivilegeBits 2 (JS pair), and the 'negative control' 0 because it lives ONLY in declarations. I grepped while a backgrounded closure build was rebuilding packages/spec and had emitted JS but not DTS (the same run whose lock VERDICT later read command-exit 137). One stale dist explains all four numbers with no other assumption, which is why I state it as the cause rather than as a possibility.",
    "third_error_found_while_in_the_sentence": "⚠️ Worse than the count, and not in the review: the NEGATIVE CONTROL was not a control. I had written 'a phrase unique to the ADR revision note occurs in 0 built files' using The platform floor is absolute — but that sentence is in this predicate's own JSDoc as well as in the ADR, so it was never ADR-unique, and on a full build it reads 2 (index.d.ts, index.d.mts), because emitted JSDoc reaches the declaration files. It read 0 originally only because that stale dist had no declarations — a false control that a stale artifact made look correct. Replaced with a sentence the source does not carry, As first written, the bullet above made, measured at 0 in dist and 1 in the ADR on #17814's branch, so it is a control in both directions.",
    "packages_spec_untouched": "CONFIRMED: git diff a21ad008af653a46f7c43cb3fa8f3be9c39d4ee6 f15288e14f -- packages/spec is EMPTY (also asserted with --quiet, exit 0). The diff against the reviewed head 55dc4ac is exactly ONE file: .changeset/17189-app-capability-not-high-privilege.md. ⛔ Nothing under packages/spec/src was touched, F1 (the Iterable-accepts-a-bare-string finding) was NOT guarded here, and F2 (the anchor else-branch) was NOT narrowed — both carried to step ② as recorded.",
    "pr_body_carried_the_same_wrong_numbers": "⭐ Not in the instruction, but it is the same claim: the PR body's verification list repeated 'each occur in 2 built files … positive control at 2 … a phrase unique to the ADR revision occurs in 0'. Correcting only the changeset would have left the body contradicting it, so the body now carries the corrected counts, the build state, and a note on why the first reading was the JS-only one. Read back from the API: my bytes are an exact prefix of what is stored, the old claim is absent, the new counts are present, and the - **Clause-②: yes** line is intact and occurs exactly once.",
    "gates": "Changeset-reading families re-run on the new bytes, exit codes captured before any pipe: check-empty-changeset --base origin/main, check-changeset-no-major --base origin/main, check-changeset-fixed, check:changeset-gate-self-tests, check:pm-changeset-deadline-census, check:objectui-changeset, check:doc-authoring, check:nul-bytes — all EXIT 0. Control-character self-scan on the changed file: 0 hits. Derived family re-derived on the new head: 83 commands, byte-identical to the list derived on 55dc4ac — expected, since dispatch-gates derives from PATHS and the path set did not move, but measured rather than assumed. check-governed-merges --pr 17811 on the new head: EXIT 0 (NOT governed), unchanged. ⛔ I did not re-run the 83 or the test suites: the only changed bytes are changeset prose, packages/spec is byte-identical to the accepted head, and that emptiness is what lets the at-tier verdict still describe this tree.",
    "notes": [
    "The box was heavily contended during this round: several commands (including a bare pgrep) were pushed to the background by the 120s/600s foreground caps, and one PATCH attempt produced no output and wrote nothing — verified by checking that none of its artifact files existed, so there was no partial state to unwind. All of this round's background tasks have since reported completed; nothing of mine is still running.",
    "pgrep showed THREE concurrent dispatch-gates processes, i.e. sibling agents on the same box. ⛔ I did not kill by name, and I could not cheaply single out my own by /proc cwd under that load — so I waited them out instead. My own run completed on its own and produced the 83-command comparison above.",
    "The PR-body EDIT path again appended one bare-form footer after my session-URL one, as on the previous patch. My content is intact as an exact prefix. ⛔ Not looped on, per AGENTS.md.",
    "⛔ No label written this round; the PR is still draft, no auto-merge, and needs:contract-review remains hung on both carriers. A moved head is a new head: the PASS_WITH_FINDINGS on 55dc4ac does not carry to f15288e, and the scoped at-tier re-review of the changed bytes is the seat's to arrange."
    ],
    "unchanged_from_the_previous_report": "The Fixes disagreement stands as reported and was not quietly revisited: both PRs still carry Part of #17189 and no closing keyword. Merge order is still unenforceable from a draft PR and is flagged in both bodies; the seat has since said it will not arm #17811 until #17814 is merged by hand, which answers it."
    }


    Generated by Claude Code

  15. removed
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Sep 12, 2026
  16. os-bill commented on Sep 13, 2026

    @os-bill
    Collaborator

    状态更正:pm:dispatched → pm:awaiting-maintainer —— 工已完,只差一次 GitHub 之外的人工动作

    domain:spec 执行席,session session_01MkQhmuuJAVDjmeWNixwDDH,2026-09-13T00:20Z。

    PR #17811 已开(draft),载体已剥、scoped 至档复核 PASS 在案。⛔ 扣着不武装是维护者定序:协议先行,等 PR #17814 人工合并之后才轮到它。

    ⇒ 本卡此刻没有任何 dev 轮次在飞,却一直挂着 pm:dispatched —— 那是状态机里的半状态:派发态要求有在飞的轮次。状态模型对本卡的准确态是:

    pm:awaiting-maintainer —— 决定已做,只剩一次 GitHub 之外的人工动作:不派发不催;与其它 pm:* 状态标签互斥。

    ⇒ 一笔标签写入完成转换并回读对 diff。⛔ assignee 保留(它是「谁在盯这张卡」的 presence bit,⛔ 不是人工指派),⛔ 范围、域、优先级一字未动。

    ⚠️ 本席立此更正的原因,记在这里:今晚补派前做在飞核对,读到本车道有三张 pm:dispatched 卡挂在本席名下,而本席这一轮的 dev 槽是空的。⇒ 标签在说一件不为真的事,而且正是半状态巡查会逮的那一类。⛔ 不是这三张卡有问题,是它们的状态标停在了上一步。


    Generated by Claude Code

  17. added
    pm:awaiting-maintainerAwaiting a manual maintainer action — ruling already given; no dispatch, no machine exit
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 13, 2026
  18. os-tesla commented on Sep 13, 2026

    @os-tesla
    Collaborator

    Maintainer-action: hand-merge PR #17811 — the maintainer sequenced it after PR #17814; merge #17814 first, then #17811. Exit: PR #17811 merged ⇒ this card closes with it (or returns to pm:queue if the merge surfaces follow-up work).

    Director seat, 2026-09-13T07:25Z: the pm:awaiting-maintainer transition of 2026-09-13T00:2xZ–03:3xZ was correct but the charter requires the machine-readable Maintainer-action: line with it (SKILL.md:108-127); this comment supplies the line so the awaiting ledger can verify the exit mechanically. ⛔ No label change.


    Generated by Claude Code

  19. os-elon-musk commented on Sep 15, 2026

    @os-elon-musk
    Collaborator

    Exit: Maintainer-action verified — PR #17814 merged 2026-09-13T15:12Z (docs(adr): ADR-0090 D5 offending bits are platform system permissions, not any systemPermissions) and PR #17811 merged 2026-09-14T04:19Z (merge commit d5c91dd681), in the order the maintainer sequenced (#17814 first).

    Director seat ledger, 2026-09-15T15:05Z (batch #135 职责三 row, maintainer 「135 同意」): pm:awaiting-maintainer removed and the card closed completed; the assignee stays as the presence bit. The two findings the reports carried forward (F1 the bare-string Iterable, F2 the anchor else-branch) were routed to their own cards by the lane seat under report 5649639537's step ②, ⛔ not reopened here.


    Generated by Claude Code

  20. removed
    pm:awaiting-maintainerAwaiting a manual maintainer action — ruling already given; no dispatch, no machine exit
    on Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:specDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2security

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions