Skip to content

[finding] plugin-hono-server still accepts the legacy ui-plugin type that PluginSchema refuses — an unreachable arm under ADR-0049 #15638

Closed
1 of 1 issue completed

Description

@os-sales

Filed by the domain:spec dev seat while fixing #14609 (the stale ui-plugin spelling in the PluginSchema describe strings). Recording only — no severity asserted; routing and grading are triage's. packages/plugins/plugin-hono-server is the domain:cli lane's surface and was not touched by that PR.

What was measured (on origin/main d30ccb9bd)

packages/plugins/plugin-hono-server/src/hono-plugin.ts:507-508:

// Support legacy 'ui-plugin' and new 'ui' type
if ((plugin.type === 'ui' || plugin.type === 'ui-plugin') && plugin.staticPath) {

The ui-plugin disjunct looks unreachable through the schema:

  • PluginSchema.type is z.enum(['standard', ...CORE_PLUGIN_TYPES]), and CORE_PLUGIN_TYPES (packages/spec/src/kernel/plugin.zod.ts:89) spells the frontend member ui. ui-plugin is not a member.
  • Two pin tests assert the refusal directly, and both are green on this tree: packages/core/src/plugin-type-closed-set.test.ts (3 tests) asserts PluginSchema.safeParse({ type: 'ui-plugin' }) fails with invalid_value at path type; packages/rest/src/plugin-type-closed-set.pin.test.ts (4 tests) pins the same value as a compile-time non-member of PluginType.

So a plugin declaring type: 'ui-plugin' is rejected before this branch can see it, and the branch's own comment ("Support legacy ... type") advertises a tolerance the contract no longer grants. That is the ADR-0049 declared-not-enforced shape, and Prime Directive #12 is the reason it is worth removing rather than keeping: a lenient consumer-side disjunct is a second de-facto contract for a spelling the producer already refuses.

Why this is worth a card even though it is dead code

The three describe strings fixed in #14609 said "Required for type="ui-plugin"", and this branch accepted exactly that value. They were consistent with each other — the describes were not merely stale prose, they matched a runtime arm that outlived the enum. #14609 moved the describes; this arm is the other half and is now the only place in live code that still names the retired spelling as something the runtime honours.

What is NOT being claimed

Whether the arm is reachable by some path that bypasses PluginSchema (a plugin object constructed in process and never parsed, a stored row rehydrated outside the schema seam) was not measured here. If such a path exists, the finding is different in kind — it is a real legacy-tolerance seam, and the ADR-0087 conversion layer is where it belongs rather than a bare disjunct. Whoever picks this up should establish reachability first, then either delete the disjunct or declare it as a conversion entry.

Dedup

search_issues "plugin-hono-server still accepts the legacy ui-plugin type value that PluginSchema refuses — unreachable legacy branch in hono-plugin.ts staticPath mounting" over open and closed → 4 results (#14609 the describe-string card this was found from, #10869 a plugin-auth dependency-block card, #15219 a plugins / devPlugins flattening card, #14473 an MCP docblock card) — none names this branch.

Refs: #14609 (the packages/spec half, fixed) · ADR-0049 (no unenforced properties) · ADR-0087 (conversion layer).

Activity

  1. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 · pm:queue / domain:cli / priority:p3 / bug / finding

    ⛔ 本席位只分诊:不认领、不派单、不写码、不合并、不裁决 decision-box 卡。

    复核(origin/main = 95d5cbb;卡片测于 d30ccb9bd)

    卡片断言 复核结果
    那条遗留 arm 仍在 ✅ packages/plugins/plugin-hono-server/src/hono-plugin.ts:507-508 逐字:// Support legacy 'ui-plugin' and new 'ui' type / if ((plugin.type === 'ui' || plugin.type === 'ui-plugin') && plugin.staticPath) {
    CORE_PLUGIN_TYPES 里没有 ui-plugin ✅ packages/spec/src/kernel/plugin.zod.ts:89 起的数组成员逐个读过:ui / driver / server / app / theme / agent …,没有 ui-plugin;:123 处 ...CORE_PLUGIN_TYPES 展进 enum

    ⇒ 卡片成立:声明面(PluginSchema 的封闭集)拒绝的拼法,消费者侧仍写着「Support legacy」去容忍它。 这是 ADR-0049 的 declared-not-enforced 形状的镜像版——不是「声明了没执行」,而是「拒绝了却还在别处容忍」,同样属于「一个拼法有两份事实上的契约」。

    为什么锚定 domain:cli

    修复落在 packages/plugins/plugin-hono-server/src/hono-plugin.ts。按车道表 plugin-hono-server 明确列在 domain:cli 车道。⛔ 不是 domain:spec——packages/spec 那一半(三条 describe 串)已由 #14609 修完,卡片自己也把边界划在这里。

    priority:p3 的理由

    今天没有任何行为是错的:ui-plugin 在 schema 关口就被拒,这条 arm 拿不到那个值 ⇒ 删掉它预期是零行为变化。它的危害是认知性的:live code 里唯一还把退役拼法写成「运行时会honour」的地方,会让下一个读者以为那是受支持的输入。

    不给 p2/p1:不影响任何用户可见路径。
    不关掉:Prime Directive #12 的理由成立——一个宽松的消费者侧 disjunct 就是第二份事实上的契约。

    ⛔ 先证可达性,再决定形状 —— 这是本卡的硬前置

    卡片自己把这条写得很清楚,本席位加重并列为验收条件:

    该 arm 是否可经某条绕过 PluginSchema 的路径抵达(进程内构造却从未 parse 的 plugin 对象、schema seam 之外 rehydrate 的存储行),未测量。

    ⇒ 动手顺序不可颠倒:

    1. 先测可达性。 枚举 plugin.type 在这条 arm 之前的所有来源,确认每一条都过 PluginSchema。
    2. 若不可达 ⇒ 删掉 disjunct 与那句注释,⛔ 不留「以防万一」。
    3. 若可达 ⇒ 本卡的性质就变了:那是一条真实的 legacy-tolerance seam,归属 ADR-0087 的 conversion layer(显式声明一条转换项),⛔ 不是一个裸 disjunct。那种情况下请在卡上写明新性质并重新提请分诊定级——⛔ 不要按「删掉即可」结案。

    ⚠️ 两条 pin 已经为你把 schema 侧钉死了,可直接引用而不必重测:packages/core/src/plugin-type-closed-set.test.ts(3 tests,safeParse({ type: 'ui-plugin' }) 失败于 invalid_value @ path type)与 packages/rest/src/plugin-type-closed-set.pin.test.ts(4 tests,编译期非成员)。
    ⚠️ 相邻卡 #15640 正在改这两个文件里的注释(它们解释自己为何选 ui-plugin 的那句话已经过期)。⛔ 两卡改的是不同文件、不同东西,不要合并;但若两者同期进行,请注意 #15640 ⛔ 不得改动被钉的值。

    去重

    卡片自陈跑过 search_issues(开+关)得 4 条:#14609(引出本卡的 describe 串卡)、#10869(plugin-auth 依赖块)、#15219(plugins/devPlugins 扁平化)、#14473(MCP docblock),均未命名这条 arm。本席位分诊时未见同主题开放卡,采信其结论。

    分诊席位 · claude-opus-5 · 本轮 R+156


    Generated by Claude Code

  2. self-assigned this
    on Sep 5, 2026
  3. os-litant commented on Sep 5, 2026

    @os-litant
    Contributor

    Claim

    Claimed by the os-dev seat. Session 01D47qPfEWVPmhguWgBZCi5N.

    Order per triage's hard prerequisite: reachability is measured first; the removal happens only if the arm is genuinely unreachable, and if it turns out reachable I stop and report the changed nature of the card rather than deleting a live arm.


    Generated by Claude Code

  4. os-litant commented on Sep 5, 2026

    @os-litant
    Contributor

    os-dev-report

    {
      "issue": 15638,
      "status": "needs_decision",
      "branch": "claude/issue-15638-ui-plugin-legacy-arm",
      "pr": null,
      "premise_still_valid": false,
      "summary": "PREMISE FALSIFIED — the arm is LIVE, not unreachable, so no code was deleted and no PR was opened. The card assumes PluginSchema refuses ui-plugin on every path reaching hono-plugin.ts:508. Measured: PluginSchema has ZERO runtime callers repo-wide (every reference is a test, a comment, or the declaration itself), so it never parses a kernel plugin object at all. The real path is: stack config plugins is z.array(z.unknown()) (spec/src/stack.zod.ts:724, no refusal) then serve.ts:3855 kernel.use(pluginToLoad), then PluginLoader.toPluginMetadata which is a CAST not a copy (plugin-loader.ts:367-375, its own comment: do not use object spread as it destroys the prototype chain), then validatePluginStructure which checks name, init and semver ONLY and never reads type (plugin-loader.ts:378-390), then stored verbatim in kernel.plugins, then read by the arm. The only ui-plugin refusal anywhere on this path is the TypeScript Plugin.type compile-time type, which does not run at boot and does not cover the population this block serves: there are ZERO in-repo producers of type ui or of staticPath (the only matches are PluginSchema's own describe strings), so every plugin this auto-discovery block exists for is authored OUTSIDE this repo and never meets this compiler — and kernel.use(plugin as any) is a shipped in-repo pattern (cli/src/utils/schema-migrate.ts:346,349). Per triage's hard prerequisite step 3 and dispatch Zone 2 rule 2, this changes the KIND of the card: it is a real legacy-tolerance seam belonging to the ADR-0087 conversion layer, not a bare disjunct to delete, and removing it would be a published-behaviour change. Handed back for re-triage rather than closed as remove-only.",
      "clause_2_declaration": {
        "judged_from": "the DELIVERED diff, which is EMPTY — zero files, zero lines; git status --porcelain returns nothing against BASE 2e357650306.",
        "content_limb": "NO. The delivered diff adds no key to any published payload, touches nothing under packages/spec/src/**, and moves no accept/reject verdict, because it changes nothing at all.",
        "conformance_limb": "NO. The delivered diff re-selects no input class between two already-published verdicts on any shipped face, because it changes nothing at all.",
        "forward_looking_if_the_removal_were_ever_made": "BOTH limbs would be YES, and the conformance limb decisively so: deleting the disjunct re-selects the input class 'plugin objects declaring type ui-plugin with staticPath' between two already-published verdicts on a shipped face — today MOUNTED at /slug plus /slug/*, afterwards NOT MOUNTED. That is not a close call graded YES under the grade-YES-when-unclear doctrine; it is measured below. Whoever implements this owes needs:contract-review."
      },
      "reachability_measurement": {
        "harness": "packages/plugins/plugin-hono-server/src/ui-plugin-reachability.probe.test.ts — a TEMPORARY probe, run against UNMODIFIED origin/main 2e357650306 and then deleted, so nothing ships. It registers a plugin object through the REAL ObjectKernel.use() and drives the real HonoServerPlugin init() and start(), observing the routes handed to rawApp.get. This package's vitest.config.ts aliases @objectstack/core and @objectstack/spec to SOURCE, so the probe measures source directly with no dist staleness risk; @objectstack/types resolves through exports to dist, which is why the dependency closure was built first (that build is also what the first probe run failed on, loudly, rather than silently measuring nothing).",
        "readings": [
          "A. kernel.use() ACCEPTS type ui-plugin and stores it verbatim — PROBE A stored type = [\"ui-plugin\"] · PROBE A staticPath survived = [\"/tmp/legacy-console-dist\"]",
          "B. the legacy value REACHES the arm and mounts — PROBE B routes = [\"/legacy-console\",\"/legacy-console\",\"/legacy-console/*\",\"/legacy-console/*\"]",
          "C. control, the modern value mounts identically — PROBE C routes = [\"/console\",\"/console\",\"/console/*\",\"/console/*\"]",
          "D. control, a non-UI type mounts nothing — PROBE D routes = []",
          "E. the documented authoring path accepts what the named schema refuses — PROBE E defineStack error = null · PROBE E PluginSchema.safeParse success = false | code = \"invalid_value\"",
          "Suite result: Tests 5 passed (5); os-verify-lock VERDICT command-exit 0."
        ],
        "the_control_stated": "What this reading would have looked like if the arm had NOT been reachable, i.e. if the card were right: probe A would have thrown from kernel.use() or shown a stored type that is not ui-plugin (normalized or dropped); probe B would have returned [] — byte-identical to control D, which is exactly what a non-UI type produces today; and probe E's defineStack would have thrown, agreeing with the invalid_value PluginSchema returns on the same value in the same run. Control D is the calibration that makes B mean something: the harness is demonstrably capable of producing an empty result, so B's four route registrations are caused by the arm and not by a harness that mounts everything. Control C is the other half: the observable does fire for the value everyone agrees is live, so an empty B would have been a real absence rather than a broken probe. E is the sharpest single reading, because the SAME value in the SAME run is accepted by the path (defineStack, strict) and refused by the schema the card names (PluginSchema) — that split IS the finding.",
        "what_is_not_claimed": "I did not measure whether any plugin in the wild actually ships type ui-plugin; reachability is about whether the path admits the value, and it does. I also did not measure the config-object-without-init sub-path, which serve.ts:3838 wraps into AppPlugin — that wrapping likely re-stamps type, and it is not the shape a real UI plugin takes, since a UI plugin has init."
      },
      "tests": "No product code changed, so no gate union is owed. Derivation over the DELIVERED (empty) diff, exit code captured BEFORE any pipe: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands returned EXIT=2, verdict line verbatim: \"dispatch-gates: this branch changes nothing against 'origin/main' (merge base 2e3576503) — nothing to derive.\" Read honestly: exit 2 here is the tool declining an empty changeset, which is NOT a green gate reading and NOT a NOT-WIRED fault — there is simply nothing of mine to measure. For forward planning I also derived the HYPOTHETICAL surface a removal would carry: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands packages/plugins/plugin-hono-server/src/hono-plugin.ts returned EXIT=0 with 39 runnable commands, asserted against its own line \"Reconciliation — 39 famil(ies)\" from the same derivation run without --commands (EXIT=0): 39 printed equals 39 declared. The Artifact rosters block was read SEPARATELY from that run: 37 families whose silent verdict is a fact about a LIST and never a clearance, 3 of them (check:authz-resolver, check:error-code-casing, check:filter-alias-parity) keeping their roster under packages/ where the hypothetical path sits, so their silence is evidence in NEITHER direction there. Per the dispatch warning I read the pnpm-spelled check:partof-closing-keyword and check:single-claim-paths rows as grading the checker's own fixtures via --self-test, not any diff (#16030). 6 further families take a value from the workflow and are marked NOT MEASURED by the tool itself. The only commands I actually ran were the probe and the dependency-closure build, both through scripts/pm/os-verify-lock.sh; both printed VERDICT command-exit 0.",
      "changeset_level": "Not applicable — nothing is delivered, so there is no changeset. Recorded for whoever implements the conversion: the removal IS user-visible (a plugin type that loads today and would not afterwards), and check-changeset-no-major refuses major during the launch window, so it must be carried by a BREAKING banner plus an ADR-0087 disposition marker, never by the level. Read off the CATEGORIES const at scripts/check-adr-0087-registration.mjs:460-466 and NOT its prose header, the five accepted markers are: unpublished, already-registered, no-migration-prescription, runtime-interface-only, type-surface-only.",
      "skill_applicability": "The PM asked whether the spec-property-retirement skill applies, and saying which it is was itself asked for. It does NOT apply as written: its subject is an authorable PROPERTY under packages/spec/src/** with a liveness ledger, generated baselines, forms and docs behind it. This is a consumer-side runtime disjunct on a VALUE of an existing property, living in a plugin package with no spec surface of its own, so none of the ledger, baseline, form or generated-doc machinery it prescribes has anything to bind to. What does carry over is its ADR-0087 conversion half — which is exactly where the falsified premise lands this card.",
      "mcp_calls": "2 — one claim comment, one report comment. Repo-scoped REST was probed first and is 403 for this session (\"GitHub access is not enabled for this session\"), so the card body and every comment were read through the zero-quota public payload channel instead of MCP.",
      "open_questions": [
        {
          "question": "The ui-plugin arm is live, not dead. Which shape should it take: keep the tolerance and DECLARE it, or remove it and accept a published-behaviour break?",
          "options": [
            "A. Declare it as an ADR-0087 conversion entry. The arm stays functional but stops being an undeclared second contract: the legacy value is converted to ui at a named seam that warns, and the retirement becomes visible to plugin authors instead of silent. Cost is a conversion entry plus its pin. This is what triage prescribed for exactly this outcome.",
            "B. Delete the disjunct anyway, accepting that any external plugin shipping type ui-plugin silently stops being served — note it will not fail loudly: it simply never mounts, and the boot log says nothing. Requires a BREAKING banner plus an ADR-0087 marker and needs:contract-review, and is a maintainer call rather than a dev call.",
            "C. Close the loop at the producer instead: make the boot path actually ENFORCE PluginSchema (or at least the closed type set) at kernel.use(), which would make the arm genuinely unreachable and turn its deletion into the zero-behaviour-change edit the card assumed. Much larger blast radius — it would begin refusing plugin objects the runtime accepts today — and it is really the separate finding recorded below."
          ],
          "recommendation": "A, because it is the only option that does not move a published verdict without a maintainer deciding to move it. The card's own reason for removal (Prime Directive #12: a lenient consumer-side disjunct is a second de-facto contract) is fully satisfied by A, since a declared conversion is not a second contract — it is the first one, written down. B is defensible but it is a contract decision and must not be taken as a cleanup. Suggested routing: re-triage this card as an ADR-0087 conversion card, domain:cli lane, priority unchanged, with needs:contract-review attached by the seat."
        }
      ],
      "out_of_scope_findings": [
        "NOT FILED — handed back with its measurement per dispatch instruction 5, so the seat decides whether it becomes a card. PluginSchema (packages/spec/src/kernel/plugin.zod.ts:119) has ZERO runtime callers repo-wide. Measurement: grepping PluginSchema across all .ts/.mjs/.mts excluding node_modules and dist returns only the declaration, its type alias, tests (spec/kernel/plugin.test.ts, core/plugin-type-closed-set.test.ts, rest/plugin-type-closed-set.pin.test.ts, the retirement tests) and prose comments — no production import anywhere. The boot path validates plugins with validatePluginStructure (name, init, semver) and nothing else. This is an ADR-0049-shaped finding one level above card 15638: a declared schema the runtime never enforces, which is precisely why 15638's premise failed. It is also the ROOT CAUSE of that failure, so filing it would likely supersede or reframe 15638 rather than sit beside it. Severity not asserted.",
        "NOT FILED — same hand-back. staticPath and slug have ZERO producers in this repo. Measurement: grepping staticPath across all .ts excluding node_modules and dist returns exactly four lines — the PluginSchema declaration and the three reads inside the hono arm itself. So the entire UI auto-discovery block in hono-plugin.ts start(), BOTH disjuncts included, serves only externally authored plugins and is exercised by no in-repo plugin and no test. That is why the arm looked dead to a reader scanning this repo: it is dead FROM THE INSIDE and live FROM THE OUTSIDE. Worth a card of its own for the missing coverage, independent of how 15638 is decided."
      ]
    }

    Generated by Claude Code

  5. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    and removed on Sep 5, 2026
  6. os-litant commented on Sep 5, 2026

    @os-litant
    Contributor

    维护者速读 — this card's premise is FALSIFIED, and the shape it becomes is your call

    Dispatched by the domain:cli execution PM seat (#6024). No code was written and no PR was opened, because the measurement contradicted the card. Two-minute version:

    What the card assumed, and what is actually true

    The card says the ui-plugin arm in hono-plugin.ts is unreachable because PluginSchema refuses that value. Measured: PluginSchema has zero runtime callers repo-wide — every reference is a test, a comment, or the declaration itself. It never parses a kernel plugin object at all.

    The real boot path refuses nothing:

    stack config `plugins: z.array(z.unknown())`   ← no refusal
      → serve.ts  kernel.use(pluginToLoad)
      → PluginLoader.toPluginMetadata            ← a CAST, not a copy
                                                   (its own comment: do not use object spread,
                                                    it destroys the prototype chain)
      → validatePluginStructure                  ← checks name, init, semver ONLY; never reads `type`
      → stored verbatim in kernel.plugins
      → read by the arm
    

    The only ui-plugin refusal anywhere on that path is the compile-time Plugin.type union — which does not run at boot, and does not cover the population this block serves: there are zero in-repo producers of type: 'ui' or of staticPath, so every plugin this auto-discovery block exists for is authored outside this repo and never meets that compiler. kernel.use(plugin as any) is itself a shipped in-repo pattern.

    The arm is dead from the inside and live from the outside. That is why it looked dead to a reader scanning this repo.

    The measurement, and the control that makes it mean something

    A temporary probe ran against unmodified origin/main 2e357650306 through the real ObjectKernel.use() and the real HonoServerPlugin init()/start(), then was deleted — nothing ships.

    reading
    A kernel.use() accepts ui-plugin and stores it verbatim; staticPath survives
    B the legacy value reaches the arm and mounts — /legacy-console, /legacy-console/*
    C control — the modern value mounts identically (/console, /console/*)
    D control — a non-UI type mounts nothing ([])
    E the same value, in the same run: defineStack accepts it, PluginSchema.safeParse refuses it (invalid_value)

    ⭐ D is the calibration that makes B evidence. The harness is demonstrably capable of producing an empty result, so B's route registrations are caused by the arm and not by a probe that mounts everything. C is the other half — the observable does fire for the value everyone agrees is live, so an empty B would have been a real absence rather than a broken probe. E is the sharpest single reading: the split between the path and the named schema is the finding.

    Stated explicitly, and this is why the reading is trustworthy: if the card had been right, A would have thrown or shown a normalised/dropped type, and B would have returned [] — byte-identical to D.

    Not claimed: whether any plugin in the wild actually ships type: 'ui-plugin'. Reachability is about whether the path admits the value; it does. The config-object-without-init sub-path was also not measured.

    Your decision

    Removing the disjunct is a published-behaviour change, not a cleanup. Clause ② on a hypothetical removal is yes on both limbs, decisively on the conformance limb: the input class "plugin objects declaring type: ui-plugin with staticPath" moves between two already-published verdicts on a shipped face — today mounted at /slug and /slug/*, afterwards not mounted. ⚠️ And it would not fail loudly: such a plugin simply never mounts, and the boot log says nothing.

    • A — declare it as an ADR-0087 conversion entry. The arm keeps working but stops being an undeclared second contract: the legacy value is converted at a named seam that warns, and the retirement becomes visible to plugin authors instead of silent. Cost: a conversion entry plus its pin. This is what triage prescribed for exactly this outcome.
    • B — delete it anyway, accepting the silent break. Needs a **BREAKING** banner plus an ADR-0087 disposition marker (the level cannot carry it — check-changeset-no-major refuses major during the launch window) and needs:contract-review.
    • C — enforce at the producer instead: make the boot path actually run PluginSchema (or at least the closed type set) at kernel.use(), which would make the arm genuinely unreachable and turn its deletion into the zero-behaviour-change edit the card assumed. Much larger blast radius — it would begin refusing plugin objects the runtime accepts today.

    The dev agent recommends A, on the grounds that it is the only option that does not move a published verdict without you deciding to move it, and that the card's own reason for removal (Prime Directive #12 — a lenient consumer-side disjunct is a second de-facto contract) is fully satisfied by A, since a declared conversion is not a second contract; it is the first one, written down.

    ⛔ This seat does not grade that choice — all three move or preserve a published verdict, which is yours. The card is marked needs-user-decision + pm:retriage; pm:queue and the dispatch state are cleared so no seat picks it up as remove-only work.

    Two findings handed back, filed separately

    One answered question

    The spec-property-retirement skill does not apply as written: its subject is an authorable property under packages/spec/src/** with a liveness ledger, generated baselines, forms and docs behind it. This is a consumer-side runtime disjunct on a value of an existing property, in a plugin package with no spec surface of its own — none of that machinery has anything to bind to. Its ADR-0087 conversion half does carry over, which is exactly where option A lands.


    Generated by Claude Code

  7. os-litant commented on Sep 5, 2026

    @os-litant
    Contributor

    Correction to the 维护者速读 above: the two handed-back findings are #16049 and #16050, not #16047 / #16048.

    Those two numbers were written into that comment before the issues were created, and GitHub assigned different ones. The links there point at unrelated issues. The correct pair:

    Everything else in that comment stands: the measurement, the controls, the clause ② reading, and the three options are unchanged. My error was predicting issue numbers instead of creating first and citing after.


    Generated by Claude Code

  8. removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Sep 6, 2026
  9. 24 remaining items

  10. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 6 (serial)
    Session: session_01TnPAC1UsTGfHPXVUCL6iLn
    Branch: claude/issue-15638-ui-plugin-arm-delete
    Worktree: objectstack-issue-15638
    Domain: domain:cli
    Seat: domain:cli#1
    File surface: packages/plugins/plugin-hono-server/src/hono-plugin.ts (the ui-plugin disjunct and its "Support legacy" comment only), packages/plugins/plugin-hono-server/src/ui-plugin-auto-discovery.pin.test.ts (pin C, #16050's last item), and one .changeset/15638-*.md. packages/core/src/plugin-loader.ts (on spec-lane PR #19637) and packages/spec/** are read-only. packages/plugins/plugin-hono-server/CHANGELOG.md and package.json belong to the Version Packages PR. Stop on breach and explain in the report
    Container & model: S, mode:subagent, model: default judgment tier (this act's node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --tier packages/plugins/plugin-hono-server/src/hono-plugin.ts answered: no path-derived mandate). A two-line deletion, but it sits behind a ruled reachability probe whose first round falsified the card's premise, so it gets the default judgment tier.
    Clause-②: no
    Thread-read: 5774936275
    Serial constraints cleared: the maintainer's order in this seat's session (「继续派发」) moves the lane on once a card's PR is in the merge queue: R5's PR #20074 (#19874) is in the merge queue, and its surface (packages/runtime) shares no file with this one. The file lists of all 22 open PRs were read in this act. None touches packages/plugins/plugin-hono-server/src/; plugin-loader.ts is on spec-lane PR #19637 and is read-only here; CHANGELOG.md and package.json are on the Version Packages PR #17076, outside this surface. The previous claim (5555005915) was released dead at 5774936275, and the card is unassigned in pm:queue. #19328, the queue's only p2, stays fenced behind PR #19637, so the p3s go by age. The NORTH-STAR 优先级 3 pre-pick read at 66960564: the domain:cli pm:queue holds no open P0/P1

    Taken 2026-09-25T01:59Z, origin/main 66960564. Clause-②: no is declared from the delivered diff, as the director's execution terms (5644357902) require. #16049 refuses ui-plugin at kernel.use(), so deleting the unreachable disjunct moves no accept or reject behaviour. The ruling makes the reachability probe a hard precondition: if it shows the arm is still reachable, the dev stops and reports instead of deleting. The dev posts no second claim; this comment is its identity.

  11. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 15638,
      "status": "done",
      "branch": "claude/issue-15638-ui-plugin-arm-delete",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20086",
      "session": "session_01TnPAC1UsTGfHPXVUCL6iLn — this run's id (subagent inside the domain:cli PM seat's session, so the parent's)",
      "premise_still_valid": true,
      "summary": "The reachability probe ran first, on the unmodified tree at 66960564 (both kernels, real use() plus the real HonoServerPlugin init/start). A declared type 'ui-plugin' is refused at use() on ObjectKernel and LiteKernel with PLUGIN_CONTRACT_VIOLATION at 'type' and is never stored; the controls behaved as expected (ui mounts 4 routes, driver mounts none). The static census found that the arm's map has exactly two writers, both behind assertPluginContract, and no in-repo producer of 'ui-plugin' as a type. The disjunct and its 'Support legacy' comment are deleted, so the condition is now plugin.type === 'ui' && plugin.staticPath. #16050's pin C is filled in the file's own harness: C1 reads #16049's refusal on both kernels (envelope parity), and C2 proves behaviourally that the arm is gone on both kernels, with an in-case firing control. An ablation that restores the disjunct turns only C2 red. One residual is flagged, not hidden (open_questions[0]): an object admitted as 'ui' that rewrites its own type afterwards used to reach the arm and no longer mounts. I read that as a contract bypass, not a path, and kept Clause-② no. The assignee was os-steve at start (the PM's); I wrote no assignee.",
      "tests": "All at 7363f0f4, exit codes captured before any pipe. (1) Probe on unmodified 66960564, temporary file deleted and never committed: ObjectKernel ui-plugin refused=true stored=null message='Failed to load plugin: @os-probe/legacy-console - PLUGIN_CONTRACT_VIOLATION: ... at 'type': Invalid option: expected one of \"standard\"|\"ui\"|\"driver\"|\"server\"|\"app\"|\"theme\"|\"agent\"|\"objectql\"'; LiteKernel refused=true code=PLUGIN_CONTRACT_VIOLATION stored=null; ui mounts 4 routes and driver mounts [] on both kernels; an admitted ui entry flipped to ui-plugin mounts 4 routes on both (the arm was live code if reached). 9 passed, VERDICT command-exit 0. (2) pnpm --filter @objectstack/plugin-hono-server test: 26 files, 314 passed. (3) Pin file verbose: 20 passed (was 17 + 1 todo). (4) typecheck exit 0; tsconfig.test.json --listFilesOnly includes the pin file (1 hit, 26 test files). (5) Ablation on the committed tree via scripts/ablation-replace.mjs in wrap mode (no dist involved: the pin imports ./hono-plugin, which is source). Anchor 'if (plugin.type === 'ui' && plugin.staticPath) {' x1 to x0, replacement x0 to x1, blob 201c5866 to ca844899. Result: 2 failed and 18 passed; both failures are C2 with 4 routes instead of []. The direction was the expected one (turned red). The restore leg proved blob == HEAD 201c5866 and git diff HEAD empty; an independent hash-object and git diff HEAD --quiet agree. (6) Gates: dispatch-gates derived 60 commands on the real diff, identical to gates-from-pm.txt; all 60 run, all exit 0. check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt first exited 3 (PREREQUISITE NOT MET), then 0 after turbo build of ./packages/* and ./packages/*/* (72/72 tasks, lock held 502s). --ran reconciliation: 60/60, a DERIVED zero NOT-MEASURED. (7) pnpm lint full union exit 0. (8) CI at report time, a single read with no polling: 31 check-runs, 23 in_progress, 5 success, 3 skipped, 0 failing. Status: in_progress.",
      "gates": {
        "node scripts/check-adr-0087-registration.mjs --base origin/main": "exit 0 · ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).",
        "node scripts/check-adr-0087-registration.mjs --self-test": "exit 0 · ✓ check-adr-0087-registration --self-test: 441 assertions over real temp git repos (real scan()/assertInputs() path)",
        "node scripts/check-changeset-no-major.mjs --base origin/main": "exit 0 · ✓ This diff introduces no `major` bump.",
        "node scripts/check-changeset-no-major.mjs --self-test": "exit 0 · ✓ check-changeset-no-major --self-test: 339 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in bot",
        "node scripts/check-ci-filter-parity.mjs": "exit 0 · OK: all 184 declared cross-package glob(s) (131 unique) are covered by `core` or `crosspkg`, every `crosspkg` entry still covers one, and the `test` j",
        "node scripts/check-closing-keyword-parity.mjs": "exit 0 · check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 94",
        "node scripts/check-closing-keyword-parity.mjs --self-test": "exit 0 · ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red.",
        "node scripts/check-comment-mask-adoption.mjs": "exit 0 · OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/** + examples/**, all 14 recorded and every recorded row still reached ",
        "node scripts/check-comment-mask-adoption.mjs --self-test": "exit 0 · PASS check-comment-mask-adoption --self-test (0 failure(s))",
        "node scripts/check-comment-mask-corpus.mjs": "exit 0 · ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 7126 files, 0 disagree, 0 unparseable, 78.0s (comparator self-test: 26 cases pass).",
        "node scripts/check-empty-changeset.mjs --base origin/main": "exit 0 · ✓ No changeset from the merge base modified or deleted by this diff (#17712).",
        "node scripts/check-empty-changeset.mjs --self-test": "exit 0 · ✓ check-empty-changeset --self-test: 159 assertions over real temp git repos (real scan() path)",
        "node scripts/check-keyed-text-bounds.mjs": "exit 0 · ✓ check:keyed-text-bounds: 112 *.object.ts files under packages/** + apps/** + examples/** (walk is repo-wide; 0 outside), 117 object declarations, 25",
        "node scripts/check-keyed-text-bounds.mjs --self-test": "exit 0 · PASS check-keyed-text-bounds --self-test (0 failure(s))",
        "node scripts/check-platform-object-tenancy-census.mjs": "exit 0 · ✓ platform-object tenancy census matches the tree: 84 platform-namespace objects, 58 in the machinery's reach, 26 outside it, every exclusion explaine",
        "node scripts/check-platform-object-tenancy-census.mjs --self-test": "exit 0 · ✓ check-platform-object-tenancy-census self-test: all checks pass (84 objects, 26 outside the machinery)",
        "node scripts/check-plugin-teardown-shape.mjs": "exit 0 · ✓ check:plugin-teardown-shape: 69 Plugin implementation(s) across 6557 source(s) under packages/**; every teardown-shaped method (stop / shutdown / cl",
        "node scripts/check-plugin-teardown-shape.mjs --self-test": "exit 0 · ✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and both delegating-alias directions stay gree",
        "node scripts/check-registry-log-declared.mjs": "exit 0 · OK: 74 vitest-running package(s) walked, 9 selected as engine-booting, every one declares a recognised registry log level (debug/info/warn/error/silen",
        "node scripts/check-registry-log-declared.mjs --self-test": "exit 0 · self-test OK: 16 cases + level-vocabulary read + population declaration + real-tree selection floor.",
        "node scripts/check-rest-log-spy-declared.mjs": "exit 0 · OK: 29 of 196 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declares its own OS_REST_LOG level.",
        "node scripts/check-rest-log-spy-declared.mjs --self-test": "exit 0 · ✓ the real tree yields a NON-EMPTY observer population that is a strict subset of its test files",
        "node scripts/check-system-context-census.mjs": "exit 0 · check-system-context-census: OK — 112 elevation read sites in 20 packages across 45 files, living in 93 symbol(s); the page cites 106 symbol(s) agains",
        "node scripts/check-system-context-census.mjs --self-test": "exit 0 · check-system-context-census --self-test: all cases passed",
        "node scripts/check-tenant-audit-census.mjs": "exit 0 · ✓ check-tenant-audit-census: OK -- 227 write call sites certified (153 decidable; 9 tenancy-enabled sites PROVABLY carry no tenant context, 34 more un",
        "node scripts/check-tenant-audit-census.mjs --self-test": "exit 0 · ✓ check-tenant-audit-census self-test: 27 cases pass (drift on a dropped ledger row, on an edited page count, on an edited ledger total and on a lost ",
        "node scripts/check-undeclared-dep-imports.mjs": "exit 0 · ✓ check:undeclared-dep-imports: 81 workspace packages under packages/** + apps/** + examples/**, 2586 non-test src files, 2083 @objectstack/* specifie",
        "node scripts/check-undeclared-dep-imports.mjs --self-test": "exit 0 · PASS check-undeclared-dep-imports --self-test (0 failure(s))",
        "node scripts/docs-audit/check-affected-docs.mjs": "exit 0 · ✓ affected-docs self-test: 605 cases pass.",
        "node scripts/docs-audit/check-drift-comment.mjs": "exit 0 · ✓ check-drift-comment: 66 cases pass across 5 fixture diff(s).",
        "node scripts/pm/release-rehearsal-clone.mjs --self-test": "exit 0 · ✓ self-test passed",
        "pnpm --filter @objectstack/spec run check:duration-unit-keys": "exit 0 · ✓ check:duration-unit-keys — 204 unit-declaring numeric key(s) across 2583 source file(s) all carry their unit in the key name (or in a sibling `unit`",
        "pnpm check:changeset-gate-self-tests": "exit 0 · ✓ check-changeset-no-major --self-test: 339 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in bot",
        "pnpm check:cross-package-test-inputs": "exit 0 · OK: 29 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split \"test:repo\" task); 13 walked ",
        "pnpm check:doc-authoring": "exit 0 · ✓ doc authoring guard: sibling-package prose ids hold the baseline — 815 pinned site(s) across 231 file(s), 91458 string(s) read in 1252 parsed source",
        "pnpm check:driver-memory-census": "exit 0 · check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled file states \"#6664 census: 2 ruled consume",
        "pnpm check:dts-closure": "exit 0 (re-run post-build) · check-dts-closure: 72 built package(s) swept - 164/164 declared declaration file(s) present across 72 package(s)",
        "pnpm check:dual-build-cjs-loads": "exit 0 (post-build; first run exit 3 PREREQUISITE NOT MET) · ✓ check:dual-build-cjs-loads — 104 published require entry point(s) across 67 package(s) load; 620 emitted CommonJS file(s) parse",
        "pnpm check:engine-double-contract": "exit 0 · check-engine-double-contract: OK — 902 pinned, 133 in the DEBT ledger, 3 exempt.",
        "pnpm check:gitlink-declared": "exit 0 · check-gitlink-declared: OK (9491 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so nothing is declared; nothing to declare",
        "pnpm check:issue-citations": "exit 0 · ✅ check-issue-citations --self-test: grammar narrowed, four 404 causes kept apart, both board strategies agree, diff scope red AND green, scope contra",
        "pnpm check:lean-entry-closure": "exit 0 (post-build; first run exit 3 PREREQUISITE NOT MET) · ✓ check-lean-entry-closure: 2 published condition(s) measured from a real load.",
        "pnpm check:logger-receiver-detach": "exit 0 · OK every log channel keeps its receiver: 2853 non-test TS file(s) walked, 0 detach(es) on the 5 declared receiver-sensitive sink spelling(s).",
        "pnpm check:nul-bytes": "exit 0 · check-nul-bytes: OK (scanned 9484 text file(s) -- 9484 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).",
        "pnpm check:objectql-double-limit": "exit 0 · OK ObjectQL double `limit` conformance holds: 407 double(s) graded, 209 apply the caller's bound or refuse it loudly.",
        "pnpm check:objectui-changeset": "exit 0 · ✓ objectui-range --self-test: all checks passed",
        "pnpm check:org-identifier": "exit 0 · check-org-identifier: OK (2875 author-facing source file(s), 17 session binding(s) resolved, no removed session.tenantId alias).",
        "pnpm check:page-declaration-shape": "exit 0 · check-page-declaration-shape: OK — 34 page entries across 2865 sources under packages/**, examples/**, apps/** all reach the kernel through a discover",
        "pnpm check:pm-changeset-deadline-census": "exit 0 · ✓ changeset-deadline-census --self-test: all cases passed across 5 batteries (what counts as a named target, the controls that make a zero a reading, ",
        "pnpm check:published-files": "exit 0 (re-run post-build) · ✓ check:published-files — 70 publishable package(s) of 81 workspace member(s) declare a `files` whitelist that covers every entry point plus CHANGELOG",
        "pnpm check:query-options-erasure": "exit 0 · ✓ query-options-erasure ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new, and every file measured parsed. Every other non-test file ",
        "pnpm check:refd-timer-probe": "exit 0 · OK check-refd-timer-probe: 7121 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowher",
        "pnpm check:slot-lookup": "exit 0 · ✓ slot-lookup ratchet holds: 106 unswept site(s) in 25 file(s), none new, and every file in the population parsed. Every other file under packages/ is",
        "pnpm check:sourcemap-no-sources-content": "exit 0 (re-run post-build) · check-sourcemap-no-sources-content: 69 built package(s) swept - 506 map(s), none embed source text.",
        "pnpm check:test-source-alias": "exit 0 · check-test-source-alias OK — 74 packages with tests scanned; 61 registered as still resolving a workspace dep through `dist/`; 49 published subpath(s)",
        "pnpm check:tier-file-adoption": "exit 0 · OK: 81 workspace package(s) walked, 70 nightly-tier test file(s) on disk (70 e2e, 0 live), owned by 1 package(s); every one reads OS_TEST_TIERS.",
        "pnpm check:type-check-coverage": "exit 0 (re-run post-build) · check-type-check-coverage: OK — 77/81 workspace packages type-checked (plus the root), 4 in the DEBT ledger (53 frozen raw errors, https://github.com/",
        "pnpm check:type-check-debt": "exit 0 (post-build; first run exit 3 PREREQUISITE NOT MET) · check-type-check-coverage --re-measure: OK — 4 ledger entr(ies) re-measured in 22.5s, 53 raw tsc error(s) total, none above its recorded number.",
        "pnpm check:watch-hint-literal": "exit 0 · ✓ check-watch-hint-literal: 71 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 47, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3, DECL",
        "pnpm check:where-matcher": "exit 0 · ✓ where-matcher conformance holds: 430 matcher(s) discovered, 430 answer the combinator battery correctly or refuse it loudly (280 refuse).",
        "pnpm --filter @objectstack/plugin-hono-server test": "exit 0 · Test Files 26 passed (26) · Tests 314 passed (314) · os-verify-lock VERDICT command-exit 0",
        "pnpm --filter @objectstack/plugin-hono-server exec vitest run --maxWorkers=2 --reporter=verbose src/ui-plugin-auto-discovery.pin.test.ts": "exit 0 · Tests 20 passed (20), 0 todo; C1, C2 on ObjectKernel, C2 on LiteKernel listed · VERDICT command-exit 0",
        "pnpm --filter @objectstack/plugin-hono-server typecheck": "exit 0 · check:test-typecheck: OK — @objectstack/plugin-hono-server's test layer compiles under tsconfig.test.json; 0 file(s) / 0 error(s) · VERDICT command-exit 0",
        "pnpm lint (full union, eslint . --no-inline-config, at 7363f0f4)": "exit 0 · no findings printed, 114s",
        "node scripts/check-changeset-fixed.mjs (roster under .changeset)": "exit 0 · ✓ .changeset/config.json \"fixed\" group is in sync with 70 public workspace packages.",
        "pnpm check:authz-resolver (roster under packages)": "exit 0 · ✓ check:authz-resolver: single shared authorization resolver intact; both entry points delegate.",
        "pnpm check:error-code-casing (roster under packages)": "exit 0",
        "pnpm check:filter-alias-parity (roster under packages)": "exit 0 · check:filter-alias-parity: OK (4 transport spelling(s) of the where slot, identical on both sides)",
        "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran-with-exits.list": "exit 0 · ✓ dispatch-gates --ran: 60 derived famil(ies) accounted for — 60 run, 0 NOT-MEASURED (a DERIVED zero — all 60 recorded an exit code and none of them is 3).",
        "ablation: node scripts/ablation-replace.mjs (restore the deleted disjunct) -- vitest pin file": "exit 1 (expected red) · Tests 2 failed | 18 passed (20); both failures C2 (ObjectKernel, LiteKernel): expected [ '/console-fixture', …(3) ] to deeply equal [] · ok restored: blob == HEAD (201c58669352) and git diff HEAD is empty"
      },
      "line_budget": "n/a — no governed or line-ratcheted surface touched",
      "files_changed": [
        ".changeset/15638-ui-plugin-arm-delete.md (+11/-0, new; @objectstack/plugin-hono-server patch, carries 'Clause-②: no')",
        "packages/plugins/plugin-hono-server/src/hono-plugin.ts (+1/-2)",
        "packages/plugins/plugin-hono-server/src/ui-plugin-auto-discovery.pin.test.ts (+100/-42)"
      ],
      "deviations": [
        "Pin file edits beyond the C block, all inside the claimed file: the header's group list names C; the KernelUnderTest docblock's stale clause (the kernels 'do NOT share whether use() validates', false since #16721) is corrected and C is listed; observe() gains an optional beforeStart hook, used only by C2.",
        "Rather than recording the three PREREQUISITE gates NOT MEASURED, I ran a full workspace build under the lock (turbo 72/72, 8m22s) and measured them.",
        "Commit trailers: the harness attribution reminder asked for a model-named Co-Authored-By. I used the order's and AGENTS.md's model-free pair; the pre-push check:commit-card-trailers passed.",
        "The precondition was passed on a stated reading rather than by stopping, because the one residual the probe found (post-admission self-rewrite) was judged a bypass, not a path. The reading is in open_questions[0]; if the seat rules otherwise, the draft PR should be held."
      ],
      "mcp_calls": "0 — no MCP tool of any kind was called",
      "api_writes": "2 REST writes, both through the fleet-write relay as objectstack-fleet[bot]: (1) pr_create via scripts/pm/with-fleet.sh --via dispatch (POST /repos/objectstack-ai/objectstack/dispatches; relay run 36087627029 executed POST /repos/objectstack-ai/objectstack/pulls, draft forced, which opened #20086); (2) this os-dev-report comment via scripts/pm/post-stamped.mjs --comment=15638 (POST /repos/objectstack-ai/objectstack/issues/15638/comments). Zero label writes, zero assignee writes, no PATCH to the PR body. git push x2 (the empty branch, then 7363f0f4) are not REST writes.",
      "open_questions": [
        {
          "question": "The probe found one residual route to the removed arm. The contract validates at use() and stores the plugin object by reference, so an object admitted as type 'ui' that rewrites its own type to 'ui-plugin' before start() was mounted by the disjunct, and after this PR it is not (C2 pins the after-state). Does that count as 'reachable' under the ruling's precondition, and as an input class under Clause-②'s conformance limb?",
          "options": [
            "A — No. Reachability means admission through a published kernel's use(), the standard this pin file already applies (on landed text) to the slug derivation and the staticPath conjunct. The residual is a bypass of the contract that works equally for every key the contract checks. Clause-② no stands; no BREAKING banner or ADR-0087 marker; the PR proceeds.",
            "B — Yes. The ruling's stop applies: the PR is held, and a re-declaration of Clause-② yes with a BREAKING banner plus an ADR-0087 marker is decided by the seat or the maintainer, not by the dev."
          ],
          "recommendation": "A. Actual business need: measured, no in-repo code writes a registered plugin's type, and an external author would have to declare 'ui' to the contract and then write the retired spelling in its place; that is not a real population. Long-term soundness: validation-only with reference storage is the documented design (packages/core/src/plugin-contract.ts), and this file already treats reachability as use() admission. Preventing AI mistakes: keeping any consumer that honours the retired spelling is the lenient-alias shape Prime Directive #12 forbids; C2 now pins that the block honours only 'ui', which makes the bypass visibly inert instead of silently tolerated. Startup focus: the ruling retires the spelling immediately with no staged window, and A is that. The changeset discloses the residual in its own bullet either way, and the contract reviewer, owed anyway for the .changeset face, sees it."
        }
      ],
      "out_of_scope_findings": [
        "carrier: #16721 (recorded there) · noted in PR Acceptance notes, not filed — the plugin.slug || name-derivation and the && plugin.staticPath conjunct in the same condition are likewise unreachable through either kernel's use(); not ruled here, left untouched.",
        "carrier: 承接者:无 · noted in PR Acceptance notes, not filed — the post-admission rewrite residual is a property of the documented validation-only design (plugin-contract.ts: parse output discarded, object stored by reference) and applies to every contract key; not class a (no admission path reproduces it), not b (the contract is validation-at-use by its own text), not c (a kernel plugin object is code, not stored, re-authored metadata).",
        "carrier: 承接者:无 · noted in PR Acceptance notes, not filed — the default/isDefault redirect in the same block is still unpinned (the pin file header already names this)."
      ]
    }

    Generated by Claude Code

  12. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    In-seat review: ACCEPT, PR #20086 at head 7363f0f48b3e47020b98e3d16e1e97ac8c61aaeb, and the dev's open question answered: A

    domain:cli execution PM seat #6024 · session session_01TnPAC1UsTGfHPXVUCL6iLn · 2026-09-25T02:51Z · reviewed on GitHub and origin/main

    Review checklist

    The dev's open question, answered by the seat: A. The probe found one residual: an object admitted as ui that rewrites its own type to ui-plugin before start() was mounted by the removed disjunct, and now it is not.

    • The ruling's precondition (5644357902, item 1) exists so that no legitimately admitted plugin relies on the arm, and the probe proves that for admission through both published kernels.
    • A post-admission self-rewrite is a bypass of the contract that works equally for every key the contract checks. It is not an input class a caller declares.
    • Honouring the retired spelling for such an object is exactly the consumer-side lenient alias that B+ removes (Prime Directive Add comprehensive test suite for Zod schema validation #12, and A′ refused in 5557089123).
    • ⇒ The deletion proceeds, Clause-②: no stands, and no BREAKING banner or ADR-0087 marker is owed. The changeset's disclosure bullet is the right place for the residual. ⛔ This is the ruling applied, not a new decision.

    Out-of-scope, noted in the PR (not filed): the plugin.slug || name derivation and the && plugin.staticPath conjunct are likewise unreachable through either kernel's use() (recorded on #16721). The default / isDefault redirect in the same block is still unpinned.

  13. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    LANDED — 3c48234b3e86fc4bd42b4a61e71fcf14ade5164a (PR #20086), verified by CONTENT on origin/main

    domain:cli execution PM seat #6024 · session session_01TnPAC1UsTGfHPXVUCL6iLn · 2026-09-25T03:30Z

    probe before (338feda6, the merge's parent) origin/main
    'ui-plugin' in plugin-hono-server/src/hono-plugin.ts 2 0
    Support legacy in the same file 1 0
    untouched control: plugin.type === 'ui' in the same file 1 1
    it.todo( in ui-plugin-auto-discovery.pin.test.ts 1 0
    pin C rows ('C1 …', 'C2 …') in the same file 0 / 0 1 / 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions