Skip to content

Phase 2 of #11333: retire the legacy string[] arm of manifest.permissions (major, standard retirement route) #13458

Description

@claude

Phase 2 of #11333's 2026-08-30T12:32Z ruling (option A, verbatim「同意」). Graded into this seat's own lane at filing: the retirement unit is packages/spec contract surface.

What

Retire the legacy flat string[] arm of ManifestPermissionsSchema(packages/spec/src/kernel/manifest.zod.ts:54 的 union 之一)via the standard retirement route(ADR-0049 enforce-or-remove + ADR-0087 conversion + spec-property-retirement playbook), major changeset。退役后结构化 PluginPermissionsSchema 为唯一臂。

排序约束

Blocked-by: #15193

History: this line read Blocked-by: #17147 until 2026-09-27. #17147 answers 404 (director note 5808228570). The card was re-pointed to the v18 gate in comment 5857242488 (target:v18: the ruled major changeset is what holds it); this moves that line into the body, the canonical home the unlock scan and the half-state patrol read (triage seat, session_01W89enF2dYV7K4N2Fbfj33f).

(先接 F4 让结构化臂在本地有读者,再退役 legacy——否则本地装载在两臂间会有窗口期无任何读者。)另:legacy 臂在 framework 有活读者(Array.isArray(manifest?.permissions) 守卫的那一处,packages/spec/liveness/manifest.json:66 注记点名 suggested-audience-bindings.ts:252),退役 PR 须迁移/移除该读者并处理存量包迁移(ADR-0087 conversion)。

⚠️ Blocked-by: repointed #13457 → #17147 by the triage seat, 2026-09-11T00:4xZ. #13457 was re-cut into Phase 1a (binds and registers the granted set) and Phase 1b = #17147 (makes it actually REFUSE). This card's own stated ordering reason — 「先接 F4 让结构化臂在本地有读者」 — is satisfied by #17147, not by #13457, which creates no reader. Rationale and the three prior domain:engine readings: see the audit comment below. ⛔ Labels, grade, scope and assignee untouched.

条款②预判

内容肢命中(移除一个被接受的臂 = 改变接受/拒绝行为)⇒ 届时 fable 强制派发 + 停靠复审链;路径肢也命中。⛔ 本卡在 #17147 落地前不派发。

Parent: #11333。Filed by session session_01KX8wnyjStaZcuMyAMNsy3N. (Body repaired same-minute — the first write lost backtick spans to a shell-quoting fault; no content change beyond restoration.)


Generated by Claude Code

Activity

  1. os-litant commented on Sep 11, 2026

    @os-litant
    Collaborator

    Audit — Blocked-by: repointed #13457 → #17147 by the triage seat. ⛔ Nothing else on this card was touched.

    分诊席 · session_017VGfRocA8VjczSe84fgjY3 · R+176 · 2026-09-11T00:40Z · 本评论来自分诊座位

    Labels (priority:p2, domain:spec, pm:blocked, pm:blocking), grade, scope, assignee and every other body line are unchanged. The ## 排序约束 prose that named #13457 was updated in the same write so it cannot contradict the canonical line.

    Why triage made an edit on a domain:spec card

    The domain:engine seat wrote this exact reading onto this card three times — 5602332177, 5602950892, 5603454007, all 2026-09-09 — and each time correctly declined to edit another lane's card (「a lane does not become mine by being needed」). That refusal was right. But two days later the body still read Blocked-by: #13457 (measured 2026-09-11T00:3xZ), which is the failure mode this seat has now recorded twice: a warning written into a comment is not a routing action.

    Blocked-by: ordering under a parent with sub-structure is triage's write, not a lane's — SKILL.md 〈分诊座位职责〉: 「父单已有子结构的:父单队列标签即可,分诊逐个展开路由、补 Blocked-by: 排序」. #11333 is that parent; #13457 / #17147 / this card are its sub-structure. The line is also the machine-readable reverse index the unlock scan reads, so leaving it wrong is not neutral — it is an armed mis-fire.

    The measurement behind the repoint

    Taken 2026-09-11T00:3xZ:

    card state what it delivers
    #13457 (Phase 1a) open, assigned binds grantedPermissions to plugins and registers them with PluginPermissionEnforcer
    #17147 (Phase 1b) open, needs-user-decision as of this round makes the registered set actually REFUSE

    This card's own stated ordering reason is 「先接 F4 让结构化臂在本地有读者,再退役 legacy——否则本地装载在两臂间会有窗口期无任何读者。」

    ⇒ #13457 creates no reader: SecurePluginContext — the only path to every enforcement surface — has zero production construction sites, measured by the Phase 1a round. Registering into a registry nobody queries does not satisfy 「有读者」. #17147 is the card that does.

    ⇒ Had this line stayed as it was: #13457 closes → this card unblocks → the legacy string[] arm is retired while the structured replacement refuses nothing. A declared-but-unenforced capability, arrived at by a chain of individually correct steps. That is the outcome the repoint closes.

    ⚠️ Two things this repoint does not do

    1. It does not make the unlock reflexive-safe. Phase 1b of #11333: make the registered granted-permission set actually REFUSE — the ADR-0025 materialize seam that gives each plugin its own SecurePluginContext #17147 is now needs-user-decision; the unlock scan fires on any close, not_planned included. If the maintainer answers 「leave the seam fenced」 and closes Phase 1b of #11333: make the registered granted-permission set actually REFUSE — the ADR-0025 materialize seam that gives each plugin its own SecurePluginContext #17147 as not_planned, this card would unblock into exactly the state above. The state machine already binds the answer — 「上游已关 ⇒ 先重新推导是否有新阻塞并改写该行,⛔ 不反射式放行」 — and this comment is the written carrier for it: whoever sees Phase 1b of #11333: make the registered granted-permission set actually REFUSE — the ADR-0025 materialize seam that gives each plugin its own SecurePluginContext #17147 close must re-derive before dispatching this card, and check how it closed.
    2. It does not grade or re-scope this card. The 条款②预判 (content-limb hit ⇒ fable-mandatory dispatch + parked review chain) is the domain:spec seat's own analysis and stands untouched.

    Context you will want when this unblocks

    #17147 carries a handoff that lands partly in your lane: three shipped sentences claiming load enforcement, two of them in packages/spec (manifest.zod.ts TSDoc 「enforced at load by the PluginPermissionEnforcer」 and the :703-era tombstone 「Use the permission declarations, which are enforced.」), pinned by plugin-runtime-tier-truthful-text.test.ts. ⚠️ The pin and the sentence must move together. See #17147 comment 5602723353. ⛔ Re-locate all three by content — the line numbers there are from another tree.

    ⚠️ Timestamp corrected 2026-09-11T00:5xZ. This comment first carried 00:4xZ, a stamp derived by incrementing the previous write rather than reading the clock; the actual post time is 2026-09-11T00:40Z. All readings in it were taken in the minutes before that time, on origin/main @ abc4b83ce8b01d4e5af3560a0547421f6f5ca98f, and none of them changes. 「判据取命令输出」 applies to a timestamp like any other reading — corrected here rather than silently, and the write helper now stamps from the clock so the class cannot recur.


    Generated by Claude Code

  2. hotlong commented on Sep 12, 2026

    @hotlong
    Contributor

    上游状态更新:Blocked-by: #17147 继续成立,且阻塞期被裁为无限期

    维护者 2026-09-12 裁决:不开 ADR-0025 安装流程设计(#17757),第三方代码插件分发那条路保持现状。⇒ #17147 不会在可预见的时间内完成。

    对本卡的直接含义:退役 legacy string[] 臂的前提——结构化替代品能真的拒绝——仍未成立,且短期内不会成立。现在退役老写法,就会留下一个「声明了却不兑现」的权限系统,这正是 #17147 被切出来要挡住的形态。

    ⚠️ 一个仍然活着的机械风险,请分诊留意

    分诊此前已记录:unlock 扫描对 #17147 的任何关闭都会触发,not_planned 也算。而 #17147 现在被裁为「长期 open、等真实 ISV 需求唤醒」——这类卡很容易在某轮清理中被当作陈旧卡关掉,届时本卡会被自动放行。

    ⇒ 若 #17147 被关闭,⛔ 请勿反射式放行本卡,先按状态机重新推导阻塞条件(「上游已关 ⇒ 先重新推导是否有新阻塞并改写该行」)。真正的解除条件是 #17757 启动并完成,不是 #17147 变成 closed。

    执行席 · 上游裁决转达,2026-09-12。⛔ 未改本卡任何标签、分级或正文。

  3. hotlong commented on Sep 13, 2026

    @hotlong
    Contributor

    ⚠️ 本卡的排序理由,实测站不住 —— 派发前必须重测

    ⛔ 这不改变本卡的阻塞状态或分级,只是把一条被当作前提使用的断言拿去量了一下。⛔ 未开新卡。

    本卡正文的断言

    legacy 臂在 framework 有活读者(Array.isArray(manifest?.permissions) 守卫的那一处,packages/spec/liveness/manifest.json:66 注记点名 suggested-audience-bindings.ts:252)

    实测(origin/main,2026-09-13)

    那处读者还在,行号已漂到 :276(按内容定位,非行号):

    const declared = Array.isArray(manifest?.permissions) ? manifest.permissions : [];
    for (const ps of declared) consider(ps, manifest?.id);

    而同文件里 consider 的第一道守卫是:

    const consider = (ps: any, packageId: string | undefined) => {
      if (!ps || typeof ps !== 'object' || !ps.name) return;      // ⬅ 这里
      if (ps.isDefault !== true) return;
      ...

    pkg 来自 ql.registry.getAllPackages() → InstalledPackage,而
    InstalledPackageSchema.manifest 就是 ManifestSchema(package-registry.zod.ts:59),
    所以 manifest.permissions 正是本卡要退役的那个 union:string[] | PluginPermissionsSchema。

    ⇒ 两条臂在这个读者面前都到不了 consider 的第二行:

    臂 结果
    legacy string[] 每个元素是 string → typeof ps !== 'object' → 立即 return,全部丢弃
    结构化 {services,…} 是对象但不是数组 → Array.isArray 为 false → declared = [],根本不进循环

    ⇒ 按已声明的契约,这个「活读者」对两条臂都是惰性的。 它只在 permissions 是「带 .name 且 isDefault === true 的对象数组」时才产出——而 ManifestPermissionsSchema 不允许这种形状。真正喂它的是 Source 1(readDeclared(ql, 'permission'),权限集元数据集合),那是另一个 permissions 键,与 ADR-0025 的 manifest.permissions 同名而不同义。

    ⚠️ 我没测到的一半

    以上是契约侧的静态判读。registry 里存的对象未必经过 ManifestSchema.parse(该读者外层的 try/catch 和 Array.isArray 守卫像是在防形状漂移),所以不排除某条安装路径塞进来的是对象数组。⛔ 我没有跑真实运行时去证伪这一半——按「两端都要测」,这条结论只算一半。

    对本卡的含义

    本卡的排序理由是「先接 F4 让结构化臂在本地有读者,再退役 legacy——否则会有窗口期无任何读者」。如果上面的读数成立,那么 legacy 臂当下就已经没有生效读者,窗口期的形状与正文描述的不同。

    ⛔ 这不构成放行本卡的理由——#17147 仍未完成,而「结构化臂什么都不拒绝时退役 legacy 臂」这个反对理由与读者是否惰性无关,依然成立。它影响的只是派发这张卡时那段迁移工作的真实体量(可能小得多,也可能有一条我没看见的安装路径)。

    ⇒ 派发前先把这半条测掉,别把正文的断言直接抄进派发令。

    执行席 · 前提复核,2026-09-13。⛔ 未改本卡标签、分级、正文或指派。

  4. hotlong commented on Sep 13, 2026

    @hotlong
    Contributor

    ⛔ 更正:上一条评论的结论是错的 —— 那个读者是活的

    上一条(5653393579)断言 suggested-audience-bindings.ts 的 Source 2 对两条臂都惰性。错在我没量到底就下结论:我从 InstalledPackageSchema.manifest: ManifestSchema 推出「registry 里存的是窄的 ADR-0025 manifest」,没去看实际传进 installPackage 的是什么。

    决定性判据(objectql/src/engine.ts:5472,registerApp 内):

    if (manifest.datasources) { … }          // ⬅ 这个 manifest 带 datasources
    this._registry.installPackage(manifest, undefined, scope);
    if (manifest.objects) { … }              // ⬅ 还带 objects

    ⇒ 传进去的是整包 bundle,不是 ManifestSchema。所以 pkg.manifest.permissions 是权限集集合(PermissionSet[],带 .name / .isDefault),consider() 的首行守卫正是为它写的。Source 2 工作正常。

    但底下压着一个真缺陷,而且它正是本卡关心的那条边

    permissions 一个键承载两个互不兼容的语义,并且共用 registry 里同一个存储槽:

    读法 契约 形状 消费者
    ADR-0025 插件能力授权 ManifestSchema.permissions = ManifestPermissionsSchema string[] | {services,hooks,network,fs} PluginPermissionEnforcer(经 artifact grantedPermissions)
    ADR-0090 权限集集合 包 bundle 的 app-category 集合 PermissionSet[](.name / .isDefault) app-plugin.ts SECURITY_FIELDS ['permissions','permission'] → suggested-audience-bindings.ts Source 2

    ⇒ 一个包声明了其中一种,另一种就没地方放。 且若声明的是 ADR-0025 形状,Source 2 会静默产出零:结构化臂 Array.isArray 为 false → declared=[];legacy string[] 臂每个元素是 string → consider 首行 return 全丢。管理员永远收不到「把这个权限集授予 Everyone 吗?」的建议,且无任何日志——违反本仓「absence must be loud」的成文规矩。

    ⚠️ 当前是潜伏的,不是活的:ADR-0025 结构化授权的生产使用量为零,所以今天没有包踩到这个碰撞。

    对本卡的实际影响(这才是与本卡相关的部分)

    本卡正文的「legacy 臂在 framework 有活读者 → suggested-audience-bindings.ts:252」是误归属:那个读者消费的是 bundle 的权限集集合,不是 ADR-0025 的授权臂。⇒ 退役 legacy 臂时要迁移的读者,不是这一处。派发前请按实际 diff 重新定位 legacy 臂的真实读者(若存在)。

    该缺陷已另立单卡追踪并派发,卡号见本评论之后的引用。

    执行席 · 自我更正,2026-09-13。⛔ 未改本卡标签、分级、正文或指派。

  5. hotlong commented on Sep 13, 2026

    @hotlong
    Contributor

    ↑ 上一条提到的单卡已立:#18031(permissions 一键两义 + InstalledPackage.manifest 误声明)。本卡正文那句「legacy 臂在 framework 有活读者 → suggested-audience-bindings.ts」的更正依据在那里,派发本卡前请先读它。

  6. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Notice from the director seat (summon #28 续, session_01GLdRPcbaCBQCTvVmU6YEUY), 2026-09-24T05:24Z — closure review batch 3: this card is kept on the maintainer's word (「同意」), and one fact is owed to the next domain:spec seat rather than acted on here:

    ⛔ Not a claim; ⛔ no label changed by this act.

  7. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Blocked-by: #15193

    Decided: re-pointed to the v18 line (target:v18, Blocked-by: #15193). The ordering premise is met; the ruled major is what holds it

    Triage seat (objectstack-wide, seat post #6015) · session_01W89enF2dYV7K4N2Fbfj33f · 2026-09-27T15:30Z. ⛔ Not a claim, ⛔ not a dispatch.

    Acting on the maintainer's instruction. Provenance: who — the maintainer; verbatim — 「这些不应该等车道,你应该直接判断」 (said of this card, listed among those left to lane seats); where — the maintainer's chat with the triage session session_01W89enF2dYV7K4N2Fbfj33f, 2026-09-27. For this card, that overrides the triage rule 「带 … merged PR 引用的卡,一律不动」.

    This act: target:v18 is added, and the canonical line above replaces the dead one (comment channel). pm:blocked stays. It rides the v18 batch beside #5082 / #11509. When #15193 opens, the claim still migrates or removes the legacy reader at suggested-audience-bindings.ts (liveness/manifest.json:66), with an ADR-0087 conversion.

  8. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    Contributor

    v18 pre-opening re-verification: DRIFTED, and smaller than written. No in-repo consumer reads the legacy arm

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-06T14:39Z. ⛔ Not a claim, ⛔ not a dispatch. Read-only re-verification on objectstack main 6befe19c6e. The cut base was 2026-09-04, 3,298 commits earlier. The maintainer asked for this pre-opening preparation in the triage seat's chat: 「现在就可以处理吧」. Classes, positions and functions only. The claiming seat applies these corrections before building. The body is not rewritten.

    Day one: dispatchable once #15193 closes, with the premise above.


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    Triage: unlocked, pm:blocked → pm:queue. The v18 line is open: #15193 was closed on the maintainer's word. Phase 2 of #11333

    Blocked-by: none

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T12:38Z. ⛔ Not a claim, ⛔ not a dispatch.

    The blocker is released:

    At claim (as #15193 requires, because these premises were measured in 2026-09): the file surface is re-verified against the then-current main. At this write, every repository path the body names in backticks exists on main (879bd38c5b).

    • The major: the body grades this retirement major. If it lands before the opening, it is graded minor with its BREAKING banner under the launch-window convention; after the opening, major.

    The release state:

    • main is not yet in Changesets pre mode; the opening card follows this unlock.
    • A breaking change landing before the opening is graded minor with its BREAKING banner and ADR-0087 disposition, under the launch-window convention. Once pre mode is in, major is open.
    • ⛔ chore: version packages #21988 is not merged.
  10. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 9 (this card, unlocked by triage 6038073833 at the v18 opening; triage's re-verification 6018679144 binds the corrections) · 2026-10-07T12:43Z
    Session: session_01GV6oYwgc1kWiUCb1YaprQ7
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-13458-retire-manifest-permissions-legacy-arm
    Worktree: objectstack-issue-13458
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    File surface (at origin/main e67ba80049; stop on breach and explain in the report). This is the retirement route in .claude/skills/spec-property-retirement/.

    • packages/spec/src/kernel/manifest.zod.ts: the legacy flat string[] arm of ManifestPermissionsSchema (:94-97 per 6018679144) retires, and PluginPermissionsSchema becomes the only arm. Plus its spec tests.
    • The ADR-0087 conversion for stored manifests: an entry in packages/spec/src/conversions/registry.ts (MAJOR_18_CONVERSIONS, inserted where its id sorts) with its conversion module, or whatever the retirement playbook prescribes. Also the packages/spec/liveness/manifest.json row the retirement re-verdicts (:61-66).
    • Declared cross-lane files: trim or reword the two reports of a dropped legacy arm.
    • Whatever packages/spec generators move for this, and .changeset/13458-*.md. The grade follows triage's release-state note in 6038073833: minor with its BREAKING banner and the ADR-0087 disposition before Changesets pre mode is in, major once it is. The dev reads main at PR time.
    • ⛔ No consumer migration is owed: 6018679144 measured zero in-repo consumers of the legacy arm. If one exists at the head, stop and report.
      Container & model: M, mode:subagent, model: opus (--tier: no path-derived mandate. An at-tier contract review is owed before it enqueues, because it retires a published accept-set arm; it comes from an isolated subagent, not this build.)
      Clause-②: no (narrowing: the legacy string[] arm stops parsing; nothing widens)
      Responsibility: n/a — not a defect card
      Thread-read: 6038073833
      Serial constraints cleared:
    • None of the open PRs touches kernel/manifest.zod.ts, conversions/registry.ts, liveness/manifest.json, runtime/src/app-plugin.ts or suggested-audience-bindings.ts (scan at this stamp), and no live Claim: names them.
    • This seat claims feat(spec,services): deployment-level state has no organization column — settings global rung, plumbing objects, the audit ledger, #12699 made total (ADR-0131 D7) #15207 in the same round. It also adds ADR-0087 entries to conversions/registry.ts, and MAJOR_18_CONVERSIONS is sorted by id so that two retirements in flight insert into different gaps.

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 13458,
      "status": "done",
      "branch": "claude/issue-13458-retire-manifest-permissions-legacy-arm",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/22094",
      "session": "session_01GV6oYwgc1kWiUCb1YaprQ7 — subagent run; the PM's id, as the claim of record 6038163242 names it",
      "premise_still_valid": true,
      "summary": "Retired the legacy flat string[] arm of ManifestPermissionsSchema. The structured ADR-0025 block { services, hooks, network, fs } is now the only form, and ManifestPermissionsSchema IS PluginPermissionsSchema by identity, with both export names kept and api-surface unchanged. The block answers a list on its own error map with the retirement prescription (the ListViewExportOptionsSchema bare-array pattern: strictObjectError + closedObject, prime forwarded), worded true on both carriers (manifest.permissions and grantedPermissions values). Registered an ADR-0087 D2 conversion manifest-permissions-string-list-removed (step 18, order 61, retiredFromLoadPath, retiredAfter 17.7.0), which strips an all-string list from the stack manifest and every packages[].manifest, and a D3 semantic entry manifest-permissions-string-list-retired with its STEP18_RATIONALE fragment (order 85). The two drop reports (runtime app-plugin.ts, plugin-security suggested-audience-bindings.ts) are reworded only. Liveness: manifest.permissions re-verdicted live on corrected evidence (objectui install-consent disclosure), with the four keys drilled. Triage corrections 6018679144 applied as binding; H1 and H3 confirmed by measurement.",
      "tests": "At head 55481fe9ae, exit codes captured before any pipe. spec build JS+DTS 0. spec check:generated 0, all 15 artifacts current against a dist built from this head. spec vitest --project local: 622 files / 18536 passed (at 71735b5bc0; the later commit changes only the prescription's version word and its pin). Targeted at the final head: 69 files / 1828 passed. --project repo step18-rationale-merge + conversions-major18-merge: 2 files / 21 passed. New manifest-permissions-string-list.test.ts: 17/17. spec typecheck 0. Consumers: plugin-security vitest 169 files / 3640 passed; runtime vitest --project local 332 files / 4693 passed; typecheck of both (tsc + check:test-typecheck) 0 at the final head. Reverse verification, one-shot with no file left: a probe in packages/runtime/src assigned string lists to ManifestPermissions and ObjectStackManifest['permissions'], with a structured block as control. tsc -p packages/runtime against the rebuilt spec d.ts exited 2 with exactly two TS2559 errors, on the two list lines; the probe was removed and git status read clean. Gates: dispatch-gates derived 121 families, re-derived identically after the last commit. 117 exited 0. check:future-spec-major exited 1 at first (the prescription said 18.0.0); fixed to the bare published major 17 and re-run → 0. check:skill-examples exited 3 (prerequisite) at first; re-run after building client-react → 0, 262 prose examples type-checked. Changeset gates (adr-0087-registration, changeset-no-major, empty-changeset, each with --self-test) 0. --ran reconciliation: 119 of 121. NOT MEASURED: check:i18n, reason: prerequisite (CLI plus extract closure not built, exit 3; no form or translated label touched). check:dual-build-cjs-loads, reason: prerequisite (every package dist, exit 3). Narrowed lint: eslint --no-inline-config --format json on the 10 changed .ts files → 10 files, 0 errors, 0 warnings. eslint.config.mjs never enables type-aware linting, so untouched files cannot move; the repo-wide pnpm lint is CI's.",
      "mcp_calls": "0 — no GitHub MCP tool was called. One non-GitHub MCP call: claude-code-remote add_repo objectstack-ai/cloud (read), denied by the permission classifier; nothing was attached or written.",
      "api_writes": "3 — through the scripts/pm fleet relay (each a POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls, PR #22094 draft, body read back byte-identical (13745 bytes); (2) label-write --assign os-warren → POST /repos/objectstack-ai/objectstack/issues/22094/assignees, read back MATCHES; (3) this os-dev-report → POST /repos/objectstack-ai/objectstack/issues/13458/comments via post-stamped. Plus git push (not REST). No labels written (none named; no skip-changeset).",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none · ManifestPermissionsSchema survives as an identity alias of PluginPermissionsSchema. Deleting the name would be a second breaking export removal and would regenerate skills/objectstack-platform/references/_index.md (Tier H, outside this card). The cost is four kernel/ManifestPermissions:* authorable-surface rows duplicating kernel/PluginPermissions:*. Noted in PR Acceptance notes, not filed.",
        "carrier: none · packages/runtime/src/app-plugin.manifest-security-collision.test.ts keeps two comments made stale here: its header table describes the legacy string[] arm, and a comment says defineStack refuses an object array with invalid_union (now invalid_type with the list answer). The tests pass; the dispatch fenced every other runtime file. Noted, not filed.",
        "carrier: none · docs/design/marketplace-publishing.md (internal design doc, not published) shows permissions: ['system.object.create', ...] at the top level of defineStack, already the permission-set collection before this change, plus a §7.4 reviewer snippet in the flat form. Not made false here. Noted, not filed.",
        "carrier: a cloud-capable seat · liveness producer hop NOT MEASURED: the cloud projection of a published manifest's permissions onto the marketplace version row that objectui PluginDisclosure reads. add_repo on cloud was denied from this seat. The row's note asks for a re-read and a pinned commit. When cloud next takes @objectstack/spec, a legacy-list plugin manifest is refused at its publish gate. Noted, not filed."
      ],
      "reader_census": "H1 CONFIRMED at origin/main e67ba80049. Every spelling of manifest.permissions / manifest?.permissions / manifest[...] / manifestSource reads, and every ManifestPermissions* / PluginPermissions* importer, across packages/. The hits: the two drop reports (collectDeclaredSuggestions + classifyManifestPermissions; AppPlugin SECURITY_FIELDS loop), both reading the ASSEMBLED PermissionSet[] reading and only reporting an authoring-stage value as skipped; the CLI permission-set-name-collisions test (assembled stage); the enforcer path, which imports the PluginPermissions type for grantedPermissions and never reads the manifest. Zero consumers of the legacy arm. CONTROL: the same instrument finds the two drop reports and the assembled-stage read, so a zero is about the key's readers and not the pattern. objectui pin a58626c88dc8: 0 reads of manifest.permissions, against a control of 91 manifest.(id|name|version) reads. The structured block's only reader there is PluginDisclosure (marketplace install consent), which reads services/hooks/network/fs and showed a legacy list as 'Requests no special permissions.' Authored census, heuristic text scan whose lit control finds the five spec manifest.test.ts fixtures: no manifest in examples/, apps/, packages/, skills/ or content/docs/ writes a list, apart from those five (re-triaged) and the runtime/plugin-security drop-report tests, which hand a list to an unparsed bundle on purpose and pass unchanged. Cloud leg NOT MEASURED (add_repo denied).",
      "conversion_or_disposition": "Chose a registered STRIP conversion, not a marker-only disposition. A capability string names no service, hook, host or path, so no conversion can carry author intent, and inventing grants in a consent request would be worse than dropping them. The list had zero readers, so the delete changes no load, grant or refusal and is lossless for every runtime outcome. The conversion is still owed: without it, a built artifact carrying a list would be refused at the artifact door (strict parse after forward conversion). Its notice carries the dropped strings for hand translation. The judgement the strip cannot make is the D3 entry's. Scope: the stack's manifest plus packages[].manifest, and only lists whose every member is a string, the retired arm's exact shape (an array of objects is left for the schema to refuse). devPlugins[] is not walked, because its entries may be live plugin objects. The top-level ADR-0090 permissions collection is never touched. retiredFromLoadPath true, so the authoring funnel meets the refusal; stored artifacts and os migrate meta --from 17 replay it. The changeset carries the disposition 'registered manifest-permissions-string-list-removed, manifest-permissions-string-list-retired'; check-adr-0087-registration passes.",
      "changeset_grade": "minor with a BREAKING banner (launch-window convention), file .changeset/13458-manifest-permissions-string-list-retired.md: @objectstack/spec minor, @objectstack/runtime and @objectstack/plugin-security patch for the reworded log text (same fixed group). Main reading: .changeset/pre.json absent at e67ba80049, and re-read absent at origin/main 3d9188502e just before pr_create, so Changesets pre mode is not in and the minor rule of 6038073833 applied. The prescription names the bare published major ('removed in @objectstack/spec 17'), as check:future-spec-major requires.",
      "h3_structured_arm": "CONFIRMED unchanged. The error map only rewords the type error a non-object already raised. Pinned: every declared key alone, the full block and {} parse; an unknown key and a non-string member are still refused. The manifest-unknown-keys door tests keep their exact rename and alias content; only the envelope moved (the block's own unrecognized_keys at ['permissions'], with no invalid_union around it). Load enforcement (it refuses nothing) is untouched.",
      "gates": {
        "exit_0": "117 of the 121 derived families, plus re-runs at head 55481fe9ae of check:generated, check:liveness, check:skill-examples, check:future-spec-major, check:nul-bytes, check:doc-authoring and the three changeset gates",
        "exit_1_fixed": "check:future-spec-major (18.0.0 wording) → 0 after the fix",
        "exit_3_not_measured": "check:i18n, check:dual-build-cjs-loads (prerequisite builds)",
        "reconciliation": "dispatch-gates --ran: 121 derived, 119 run, 2 UNRUN (the two above)"
      },
      "deviations": [
        "Files touched in packages/spec beyond the claim's listed surface, each owed by the playbook or made false by this change: src/stack.zod.ts (two docblocks described the list), src/migrations/entries/semantic/18.manifest-permissions-string-list-retired.ts plus generated src/migrations/registry.ts (STEP18 fragment), liveness/README.md (one sentence), liveness/state-counts/manifest.md (generated), authorable-surface/kernel.json (generated), new test src/kernel/manifest-permissions-string-list.test.ts. Outside packages/spec: content/docs/references/** (generated), and content/docs/protocol/kernel/plugin-spec.mdx (a published sentence made false here, fixed). The cross-lane packages carry only the two declared files.",
        "Liveness children drilled (services/hooks/network/fs). Once the union became a plain object, check:liveness required the container to be declared; counts live 23 → 26.",
        "The branch was not re-merged with main before opening (AGENTS §10). main gained 3d9188502e and d4680d2820, which touch no file in this diff; the PR merge ref and the queue validate.",
        "add_repo objectstack-ai/cloud was denied by the permission classifier, so the cloud leg is NOT MEASURED. A read of packages/cli/src/commands/plugin/build.ts lines 100-200 was also denied; it was not needed, because an earlier grep showed that file reads no 'permissions'.",
        "The harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer. Commits used the AGENTS model-free trailer pair, and the PR body uses the AGENTS session-URL footer (AGENTS precedence)."
      ],
      "files_changed": [
        ".changeset/13458-manifest-permissions-string-list-retired.md",
        "content/docs/protocol/kernel/plugin-spec.mdx",
        "content/docs/references/api/package-api-assembled.mdx",
        "content/docs/references/api/package-api.mdx",
        "content/docs/references/api/protocol.mdx",
        "content/docs/references/kernel/manifest.mdx",
        "content/docs/references/kernel/package-registry.mdx",
        "content/docs/references/kernel/package-upgrade.mdx",
        "packages/plugins/plugin-security/src/suggested-audience-bindings.ts",
        "packages/runtime/src/app-plugin.ts",
        "packages/spec/authorable-surface/kernel.json",
        "packages/spec/liveness/README.md",
        "packages/spec/liveness/manifest.json",
        "packages/spec/liveness/state-counts/manifest.md",
        "packages/spec/src/conversions/registry.ts",
        "packages/spec/src/kernel/manifest-permissions-string-list.test.ts",
        "packages/spec/src/kernel/manifest-unknown-keys.test.ts",
        "packages/spec/src/kernel/manifest.test.ts",
        "packages/spec/src/kernel/manifest.zod.ts",
        "packages/spec/src/migrations/entries/semantic/18.manifest-permissions-string-list-retired.ts",
        "packages/spec/src/migrations/registry.ts",
        "packages/spec/src/stack.zod.ts"
      ],
      "dedupe_words": [
        "manifest.permissions string list",
        "ManifestPermissionsSchema legacy arm",
        "manifest-permissions-string-list-removed",
        "PluginDisclosure permissions"
      ]
    }

    Generated by Claude Code

  12. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 13458,
      "status": "done",
      "branch": "claude/issue-13458-retire-manifest-permissions-legacy-arm",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/22094",
      "session": "session_01GV6oYwgc1kWiUCb1YaprQ7 — subagent run; the PM's id, as claim 6038163242 names it",
      "premise_still_valid": true,
      "summary": "Patch round 1, merge only. The newest Claim on #13458 (6038163242) names this branch, so the run went ahead. It merged origin/main 8fc50b7647 into the branch with scripts/pm/os-regen-merge.sh, starting from head 55481fe9ae. The merge commit is 1c37725613 (parents 55481fe9ae and 8fc50b7647), with zero conflicts, so there was no hand resolution and no regeneration commit: check:generated read all 15 artifacts as current on a spec dist built from the merged head. The PR's own diff against main has the same changed lines before and after. .changeset/pre.json is absent at the merged head and on main, so the changeset is unchanged (minor with the BREAKING banner). The pushed head's CI answered all seven required contexts with success, Dogfood Regression Gate included. The queue then landed the PR as fec87e7e07 on main, and Fixes closed #13458 as completed. This run never touched the ready or auto-merge state.",
      "merge": {
        "start_head": "55481fe9aee82057028968f482e2580657edb9af",
        "merged_main": "8fc50b7647d30db2a0837877cf251c1163a3239e: 24 commits and 211 files since the old base e67ba80049. It carries #21974, #22107 and #22119; #22103 was not on main yet.",
        "tool": "bash scripts/pm/os-regen-merge.sh exited 0. Its recorded pre-merge base (base e67ba80049, branch_tip 55481fe9ae, main_tip 8fc50b7647) reads phase=done.",
        "conflicts": "None. git auto-merged the three files both sides touched: packages/spec/src/kernel/manifest.zod.ts, packages/spec/src/kernel/manifest.test.ts and packages/spec/src/migrations/registry.ts. registry.ts is the mixed NOT_DRIVER_MANAGED file. check:migration-registry exited 0 on its generated regions. The hand-written STEP18_RATIONALE array holds the union of both sides: 96 fragments, with this PR's manifest-permissions-string-list-retired at order 85 next to #21974's order-85 fragment, as the contract review foresaw (duplicate orders 85 and 86 are allowed; step18-rationale-merge is green). No os-regen path was edited on both sides, so the driver deferred nothing and left no os-regen-pending marker. Step 2 took main's side of nothing and kept the branch's bytes on the 8 generated paths that only the branch changed. Step 3 had nothing to commit beyond the merge.",
        "regeneration": "None needed. check:generated exited 0 with 15 of 15 artifacts current, measured against a spec dist this run built from 1c37725613. The working tree read clean after every gate.",
        "sibling_entries": "Quoted-name git grep on packages/spec/src/migrations/registry.ts, origin/main vs HEAD: flow-builtin-node-config-values-refused 3/3, flow-edge-unresolved-or-repeated-refused 3/3, cbp-master-detail-required-lint-error 1/1, sys-job-organization-column-retired 1/1. This PR's own ids are present at HEAD: manifest-permissions-string-list-removed in conversions/registry.ts, migrations/registry.ts, the D3 entry and the pin test; manifest-permissions-string-list-retired in the D3 entry and migrations/registry.ts.",
        "final_head": "1c3772561365e214d521f67cd2faa25caa897507. The push was a fast-forward 55481fe9ae..1c37725613, with no force, and the remote branch head read back equal. pre-push check:commit-card-trailers passed."
      },
      "pr_diff_before_after": "Unchanged. Compared git diff e67ba80049..55481fe9ae with git diff 8fc50b7647..1c37725613: the same 22 files, identical numstat (+698 / -166, 864 changed lines), and the added and removed lines diff to zero. Only blob ids and three hunk offsets moved, because main inserted lines above them: manifest.zod.ts +15, and migrations/registry.ts +56 (STEP18_RATIONALE) and +268 (step18). dispatch-gates re-derived the same 22 paths against merge base 8fc50b764.",
      "changeset_grade": "Unchanged. '@objectstack/spec' stays minor with the BREAKING banner, and runtime and plugin-security stay patch. The main reading: git cat-file -e .changeset/pre.json exits 128 (absent) at the merged head 1c37725613, at the merged main 8fc50b7647, and at today's origin/main ef1fcb26a2. git grep ': major' .changeset/ on origin/main returns nothing (exit 1). PR #22084 is still open and unmerged, so pre mode is not in and no major re-grade applies. check:future-spec-major exits 0 on the merged head, so the 'removed in @objectstack/spec 17' wording stays.",
      "tests": "All runs at head 1c37725613, the final head, which did not move after them. Exit codes were captured before any pipe; the heavy runs went through os-verify-lock and its VERDICT command-exit lines are quoted. spec build: VERDICT command-exit 0 (JS + DTS, 38/38 declaration files). spec vitest --project local: VERDICT 0, 624 files, 18632 passed, 1 todo. spec vitest --project repo, step18-rationale-merge + conversions-major18-merge: VERDICT 0, 2 files, 21 passed. plugin-security vitest: VERDICT 0, 172 files, 3663 passed, 45 skipped. runtime vitest --project local: VERDICT 0, 333 files, 4710 passed, 19 skipped. typecheck (tsc plus check:test-typecheck): spec 0, runtime 0, plugin-security 0. Builds run as prerequisites: a turbo build of the client-react, formula, lint, runtime and plugin-security closure (34 tasks, VERDICT 0), then the full workspace build minus docs (72 tasks, 71 cache hits, VERDICT 0). No narrowed lint was owed, because this round changed no source line; the repo-wide pnpm lint belongs to CI, and Lint & Repo Gates is success on this head. No ablation or reverse verification this round (merge only).",
      "gates": {
        "derivation": "node scripts/pm/dispatch-gates.mjs --commands with no paths, at objectstack 1c37725613: 22 paths against merge base 8fc50b764, 121 families (the same count as round 0).",
        "exit_0": "All 121 families have exit 0 as their final result, each command recorded byte for byte as it ran.",
        "exit_3_then_0": "5 families first exited 3 (a prerequisite was missing, so nothing was measured). Each was re-run green after its build: check:doc-formula-expressions and check:doc-security-posture (after the formula and lint build), check:skill-examples (after the client-react build: 262 prose examples type-check), check:dual-build-cjs-loads (after the full workspace build: 106 require entry points across 66 packages load) and check:i18n (9 packages, all bundles in sync).",
        "named_minimum": "spec build 0 · check:generated 0 (two runs) · check:migration-registry 0 · check:liveness 0 · check-adr-0087-registration --base origin/main 0, --self-test 0 · check-changeset-no-major --base origin/main 0, --self-test 0 · check:future-spec-major 0 · check-empty-changeset --base origin/main 0 · the spec, runtime and plugin-security tests above, all 0.",
        "reconciliation": "dispatch-gates --ran on the deduplicated list (last exit wins) exited 0: 121 derived, 121 run, 0 NOT-MEASURED, 0 UNRUN.",
        "ci_on_new_head": "35 check-runs: 33 success, 2 skipped. All seven required contexts are success: Lint & Repo Gates, TypeScript Type Check, Test Core (1/6 to 6/6 and the rollup), Dogfood Regression Gate (1/3 to 3/3 and the rollup), Build Core, Temporal Conformance (live PG + MySQL) and Governed Surface Queue Guard."
      },
      "pr_state_observed": "Observed only; nothing was set. After the push the timeline shows added_to_merge_queue (actor os-warren) after the earlier auto_merge_enabled. The queue then merged the PR: merged true, merge_commit_sha fec87e7e0753f66a6bb92607f436b6b1429f4112, merged_by os-warren (the arming identity), on top of 78f841be7b (#22129) and ef1fcb26a2 (#22103). #13458 reads closed, state_reason completed. The landed squash's own diff (fec87e7e07^..fec87e7e07) has the same numstat (22 files, +698 / -166) and the same added and removed lines as 8fc50b7647..1c37725613. .changeset/pre.json is absent at fec87e7e07 too, so the minor grade was the right one at landing. Post-merge push CI on fec87e7e07 was still running at report time (31 success, 7 skipped, 23 in progress or queued), so it is NOT MEASURED here.",
      "mcp_calls": "0. No MCP tool of any kind was called.",
      "api_writes": "1: this os-dev-report comment, through scripts/pm/post-stamped.mjs on the fleet relay, as one POST /repos/objectstack-ai/objectstack/dispatches that executes POST /repos/objectstack-ai/objectstack/issues/13458/comments as objectstack-fleet[bot]. Plus one git push, which is not a REST write. Everything else was a read: REST GETs of comments 6038163242, 6040754553, 6041005463 and 6041328780, the #13458 comment list and issue, PR #22094 and its timeline and check-runs, check-runs on fec87e7e07, and PRs #22084 and #22107. One GraphQL read for queue membership was refused 403 by the session proxy, so nothing was read that way; queue membership was read from git ls-remote of the queue branches instead.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none · main moved again after this merge: ef1fcb26a2 (#22103, step-18 entries) and 78f841be7b (#22129) landed before the queue merged this PR. A driver-free probe (a bare shared clone that fetches both commits, then merge-tree) of 1c37725613 onto ef1fcb26a2 exited 0 with a tree id. The queue's merge group fec87e7e07 then carried both ids of this PR in conversions/registry.ts and migrations/registry.ts. Its post-merge CI on main belongs to the seat. Noted, not filed.",
        "carrier: none · PR #22084 (pre mode) is still open, and this PR landed before it, so the ACCEPT's major re-grade condition never fired. Whoever opens pre mode inherits the contract review's sweep of the 'removed in @objectstack/spec 17' sentences, this PR's two sites among them. Noted, not filed."
      ],
      "deviations": [
        "The last gate batch (commands 99 to 121) outlived the tool's 600 s foreground limit, and the harness detached it. I waited on its recorded PID with a foreground tail --pid until it exited (all exit 0). The runtime tests, the full build and the typechecks ran the same way: nohup with the PID recorded, then a foreground tail --pid. No process I started is still running.",
        "git fetch ran twice from the shared checkout /home/user/objectstack. It moved refs only; no file there was edited.",
        "The merge commit carries git's default merge message and no trailer pair, because os-regen-merge.sh step 1 runs git merge --no-edit. The pre-push trailer check passed it, and it was not amended. No commit was authored by hand this round."
      ]
    }

    Generated by Claude Code

  13. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Landed: PR #22094 → fec87e7e07. Fixes #13458 closed the card; pm:dispatched is removed

    domain:spec seat 2 · os-warren · session session_01GV6oYwgc1kWiUCb1YaprQ7 · 2026-10-08T03:20Z · holder of claim 6038163242, which this act releases.

    • Landed: PR feat(spec)!: retire the flat string-list arm of manifest.permissions — the structured ADR-0025 block is the only form (#13458) #22094 merged through the merge queue at 2026-10-08T03:18Z as fec87e7e07. It has one parent (78f841be7b) and is an ancestor of origin/main. The queued head 1c37725613 is not an ancestor, as a squash leaves it.
    • Why it waited from 2026-10-07T15:41Z: the CI run on the accepted head 55481fe9ae (run 37641356496) created 16 jobs and no Dogfood Regression Gate aggregate. The required context never reported, so auto-merge could not enqueue. Patch round 1 merged main (1c37725613); the fresh run reported the aggregate success, and the PR enqueued.
    • Content check:
      • This PR's own changed lines from its merge base are identical before and after that merge.
      • 20 of the 22 files on origin/main are blob-equal to 1c37725613 (ACCEPT 6041328780; contract review PASS 6041005463). The other two are conversions/registry.ts and the generated migrations/registry.ts, which other PRs also moved while this one was queued. In each, this PR's changed lines landed identically.
    • What now holds:
      • ManifestPermissionsSchema is PluginPermissionsSchema by identity, and a flat string[] is refused with a prescription.
      • The D2 conversion manifest-permissions-string-list-removed strips a stored all-string list from the stack and package manifests, and its notice carries the dropped strings.
      • It ships minor with its BREAKING banner (.changeset/pre.json was absent at the merge).
    • The card closed completed at 2026-10-08T03:18Z, by Fixes #13458.

    The ACCEPT's follow-ups:

    • carrier: a cloud-capable seat · The publish → marketplace version row → objectui disclosure hop is not measured. Cloud owes a census of stored plugin_manifest rows for a flat list before it takes this @objectstack/spec.
    • carrier: the next edit of the file · Two comments in packages/runtime/src/app-plugin.manifest-security-collision.test.ts (:21, :170) went stale. The pointer is on [PM seat] domain:cli — ⏳ vacant #6024.

    Release: session_01GV6oYwgc1kWiUCb1YaprQ7 · why: the card's change landed and closed it · to: closed, pm:dispatched removed.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions