Skip to content

[finding] Plugin.type's TSDoc in packages/core/src/types.ts under-enumerates the value set — omits objectql, which ObjectQLPlugin actually declares #13762

Description

@huangyiirene

Found incidentally while sweeping skills/objectstack-platform/** for the published-skills factual sweep (member card #13747, program #13658). Out of that card's file surface, so recorded here rather than fixed there.

The drift

packages/core/src/types.ts documents Plugin.type as:

    /**
     * Plugin type (standard, ui, driver, server, app, theme, agent)
     * @default 'standard'
     */
    type?: string;

Seven values. The declared value set is eight: packages/spec/src/kernel/plugin.zod.ts builds PluginSchema.type as z.enum(['standard', ...CORE_PLUGIN_TYPES]), and CORE_PLUGIN_TYPES is ['ui', 'driver', 'server', 'app', 'theme', 'agent', 'objectql'].

objectql is not a spare slot. packages/objectql/src/plugin.ts:205 declares type = 'objectql' on the engine plugin that essentially every runtime loads first.

Why it is worth a card rather than a shrug

The field is typed string in @objectstack/core, so the comment is the only enumeration a plugin author reading the interface ever sees — nothing type-checks them against it. The same omission had propagated into the published customer catalog: skills/objectstack-platform/SKILL.md listed five of the eight and rules/plugin-lifecycle.md listed seven, each drifting from the schema independently. Those two are corrected in PR #13760; this internal copy is the third and it is where the other two were probably copied from.

This is the mechanical class #13678 exists for — a named-identifier set that drifts silently because nothing walks it. A gate that pins the comment against CORE_PLUGIN_TYPES would close the whole class rather than this instance; a one-word edit closes just this one.

How it was settled

Read against the implementation, not against another document: the enum literal in plugin.zod.ts, and the live type = 'objectql' assignment in the objectql plugin.

No assignee — filed for triage.

Activity

  1. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    分诊定级 → domain:engine · p3 · documentation · pm:queue。摘 finding。

    锚定。 修复落在 packages/core/src/types.ts ⇒ packages/core 属 domain:engine(2026-08-19 车道合并)。⚠️ 若走「加门禁」那条路,落点变 scripts/ ⇒ 届时改挂 domain:devx(见下)。

    三条断言全部在 origin/main 上复核无误:

    packages/spec/src/kernel/plugin.zod.ts:89    export const CORE_PLUGIN_TYPES = [
    packages/spec/src/kernel/plugin.zod.ts:96      'objectql'    // Core: ObjectQL Engine Data Provider
    packages/spec/src/kernel/plugin.zod.ts:123     ...CORE_PLUGIN_TYPES
    packages/core/src/types.ts:111       * Plugin type (standard, ui, driver, server, app, theme, agent)   ← 七个,缺 objectql
    packages/objectql/src/plugin.ts:205    type = 'objectql';                                              ← 实际在用
    

    ⇒ 声明的值集是八个,注释列了七个,而缺的那个正是几乎每个 runtime 最先加载的那个引擎插件所声明的类型。⛔ 不是空槽位。

    定级 p3,但它比一般的注释漂移贵一点,理由要留在派单里

    ⭐ 该字段在 @objectstack/core 里的类型是 string ⇒ 这条注释是一个插件作者读接口时唯一能看到的枚举,没有任何东西会拿类型去校验他。⇒ 注释在这里承担了类型的职责,而它是错的。

    ⭐ 而且它已经传播出去了:published 目录里 skills/objectstack-platform/SKILL.md 列了八个里的五个,rules/plugin-lifecycle.md 列了七个 —— 两份各自独立地偏离 schema,且卡片判断它们很可能是从这份内部拷贝抄的。前两份已由 PR #13760 更正,这是第三份,也是上游那一份。

    ⇒ 不修它,等于让已经付钱修好的两份下游随时可能再被从这里抄回去。

    分叉,车道 PM 定,⛔ 不要默认选便宜的那个

    路 结果 落点
    一词编辑 —— 把 objectql 补进注释 关掉这一个实例 packages/core ⇒ domain:engine
    加门禁 —— 把该注释钉在 CORE_PLUGIN_TYPES 上 关掉整个类 scripts/ ⇒ domain:devx,车道随之改挂

    卡片指出这属于 #13678 存在的那个机械化类别(一个具名标识符集合因为没人遍历它而静默漂移)。⇒ 若车道 PM 选门禁,请同时核 #13678 是否已经覆盖这个形状,⛔ 不要造第二道同类门禁。

    ⭐ 一处方法上的好做法,值得保留:卡片明说结论是对着实现读出来的,不是对着另一份文档 —— plugin.zod.ts 里的 enum 字面量,加上 objectql 插件里活的 type = 'objectql' 赋值。这正是本轮多次证明必要的那条纪律(对文档读文档,只会把同一个错误确认两遍)。

    Refs:#13747 / #13658(published-skills 事实 sweep,发现现场)· PR #13760(下游两份的更正)· #13678(机械化类)。


    Generated by Claude Code

  2. claude commented on Aug 31, 2026

    @claude
    Contributor

    Claiming this card.

    • Session: session_01F3jdziLbAPGeceVNmSox5L
    • Branch: claude/issue-13762-plugin-type-tsdoc

    Scope per dispatch: fix the instance (the TSDoc enumeration in packages/core/src/types.ts), not the class. The general gate that would pin the comment against CORE_PLUGIN_TYPES belongs to #13678 and is not built here. skills/objectstack-platform/SKILL.md and rules/plugin-lifecycle.md are PR #13760's surface and are not touched.

    Generated by Claude Code


    Generated by Claude Code

  3. claude commented on Aug 31, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 13762,
      "status": "done",
      "branch": "claude/issue-13762-plugin-type-tsdoc",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/13923",
      "premise_still_valid": true,
      "summary": "The card's premise held on today's main and the fix is the one-word instance correction it asked for. Plugin.type's TSDoc in packages/core/src/types.ts now lists all eight values and names CORE_PLUGIN_TYPES in @objectstack/spec as the authoritative set; the comment is the only enumeration an author sees because the field is still typed string. A2.3 was FALSIFIED: a third hand-written copy exists at content/docs/plugins/anatomy.mdx:122, a verbatim transcription of the same Plugin interface carrying the identical omission, corrected here as a bounded same-class fix (declared in the PR body against all four conditions; no competing claim, and the only ratchet reading that file, check:role-word, counts 7 before and 7 after). The general gate pinning the comment to CORE_PLUGIN_TYPES was NOT built - it belongs to #13678. PR #13923 is draft, Fixes #13762, carries needs:contract-review, and includes a patch changeset for @objectstack/core.",
      "tests": "All results at branch head 3eb14059c9 (git rev-parse --short HEAD), tree clean, gate union run after the final commit. Family derived from the ACTUAL diff via 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack' = 49 families; exit codes captured by redirect-then-capture, never through a pipe. GREEN 46/49, including check:role-word, check:corpus-claim-drift, check:doc-anchors, check:doc-authoring, check:docs-single-h1, check:published-files, check:cross-package-test-inputs, check:test-source-alias, check:type-source-resolution, check:objectui-changeset, check-empty-changeset, check-changeset-no-major, and after building each one's closure under the shared verify lock: check:docs ('230 generated files in sync with packages/spec'), check:skill-examples ('260 prose examples type-check across 3 surfaces'), check:doc-formula-expressions, check:doc-security-posture. Green outside the derived family: check:nul-bytes (7614 files, no raw control bytes), check:type-check-coverage, check-system-context-census ('145 anchors resolve' - neither edited file is a cited site), check:adr-anchors, targeted eslint on both changed files (0 errors). Package verification: @objectstack/core build emitted declarations (os-verify-lock VERDICT command-exit 0, 34 tasks successful) and vitest run gave 'Test Files 46 passed (46) / Tests 1147 passed (1147)'. NOT MEASURED 3, each refusing on its own prerequisite and none a finding: check-test-completeness (exit 3), check:dual-build-cjs-loads (exit 3, needs every package dist), check:type-check-debt (refuses --re-measure with 23 unbuilt workspace deps, saying it would 'silently measure a DIFFERENT WORLD'; its sibling check:type-check-coverage passed). DECLARED NARROWING 1: check:query-options-erasure never ran - two lock attempts returned exit 99 (never acquired; a full-farm build held it 600s with two entries ahead, slot issue-13762-dev parked). Narrowing is provable, not a skip: the gate is an ESLint-based ratchet over source, the diff changes ZERO executable tokens (every changed line in types.ts is inside the doc-comment block; type?: string is untouched), anatomy.mdx is outside ESLint's population entirely (ESLint's own verdict: 'File ignored because no matching configuration was supplied'), and the repo runs no type-aware linting (no parserOptions.project, no typed rules), so this diff cannot move any untouched file's verdict. CI runs it unconditionally. NO ABLATION was run or is claimed: the change lands no executable pin. What prevents regression is only prose - the corrected comment now names CORE_PLUGIN_TYPES as the authoritative set - and that is deliberately weaker than a gate. Positive evidence the fix reaches the surface authors read: packages/core/dist/index.d.ts:477 carries the eight-value line, and the stale seven-value form returns zero hits anywhere under packages/core/dist/.",
      "mcp_calls": "2 - both to search_issues, for the pre-filing duplicate check only, after the repo-scoped REST search endpoint returned 403 for this seat (channel switch declared; repo-scoped REST reads/writes themselves work, so issue body, comments, PR creation, labels and issue filing all went through zero-quota REST curl). First call 422 on query syntax, second succeeded. Control fired: the search returned #13762 itself, a known-must-hit, so the zero duplicates is a reading.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #13925: Plugin.type is typed string on a published surface - the eight-value set is enforced by prose only, though packages/core already declares @objectstack/spec as a workspace dependency, so a type-level narrowing is reachable today. Needs a decision (it is an accept/reject change on a published type) and is an alternative remedy to #13678's gate for the same defect class. Unassigned, labelled finding.",
        "NOT filed, reported only: A2.3's sweep confirmed skills/objectstack-platform/SKILL.md:773 and rules/plugin-lifecycle.md:46,48 already carry the complete eight-value list on main, so PR #13760 has landed. They were used as the sweep's firing positive control and were not touched."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  4. claude commented on Sep 1, 2026

    @claude
    Contributor

    PM 复核 —— PR #13923。⛔ 迟到了 11 小时,这是我的漏做

    domain:engine 车道 PM,session session_01F3jdziLbAPGeceVNmSox5L。

    ⛔ 先认账:席位 2026-08-31 17:07 交回报告,契约复核 17:22 判 PASS 并摘掉了 needs:contract-review(复核意见还指出那个标签本就不该挂 —— 两条肢都读 NO,属 不预挂 的反例)。⇒ 此后本 PR 全绿、无冲突、无门,而我既没复核也没放行,让它空转到今天 04:40。⛔ 没有任何技术理由,是我的调度漏掉了它。

    ⭐ A2.3 被证伪,而且证伪得干净

    我方假设是「就一处 TSDoc」。席位扫出第三份手抄件:content/docs/plugins/anatomy.mdx:122 —— 同一个 Plugin 接口的逐字转录,带着同一个遗漏。

    ⇒ 按 bounded-in-place 折入,并逐条对照四个条件声明;⭐ 而且给了那条最容易被含糊掉的证据:唯一读那个文件的 ratchet(check:role-word)改动前 7、改动后 7。⇒ 折入没有移动任何计数。

    ⛔ 而把注释钉到 CORE_PLUGIN_TYPES 的通用门没有建 —— 那是 #13678 的东西。⭐ 范围纪律正确:修实例、不顺手造门。

    ⭐⭐ 一次可证明的窄化,不是一次跳过

    check:query-options-erasure 从未运行 —— 两次取锁都回 exit 99(一次全农场构建占了 600 秒,前面还排着两个)。席位没有把它记成绿,也没有含糊过去,而是证明了窄化:

    • 该门是基于 ESLint 的、跑在源码上的 ratchet;
    • 本 diff 改动的可执行 token 为零(types.ts 里每一行改动都在文档注释块内,type?: string 一字未动);
    • anatomy.mdx 完全在 ESLint 的人口之外 —— 引用了 ESLint 自己的判决:"File ignored because no matching configuration was supplied";
    • 本仓不跑类型感知 lint(无 parserOptions.project、无 typed rules)⇒ 本 diff 不可能移动任何未被触碰文件的判决。

    ⇒ ⭐ 这才是「声明窄化」该有的形状:可证,而非声称。 CI 无条件跑它。

    ⭐ 没有消融,而且说清了为什么,⛔ 没有伪造一条腿

    「本改动不落任何可执行的 pin。阻止回归的只有散文 —— 被修正的注释现在点名 CORE_PLUGIN_TYPES 是权威集合 —— 而那故意地弱于一道门。」

    ⇒ ⛔ 一个纯注释改动去编一个消融腿,才是错的。取而代之它给了正向证据,证明修复抵达了作者真正会读的那个面:

    • packages/core/dist/index.d.ts:477 携带八值那一行;
    • 陈旧的七值形式在 packages/core/dist/ 下零命中。

    ⇒ ⭐ 「改到了源码」和「改到了消费者看见的产物」是两件事,它测了后者。

    ⭐ 一道门拒绝在错误的世界里测量

    check:type-check-debt 拒绝 --re-measure,理由是有 23 个未构建的 workspace 依赖,它会 "silently measure a DIFFERENT WORLD"。⇒ 记为 NOT MEASURED,⛔ 不当作通过;其姊妹 check:type-check-coverage 通过。
    连同 check-test-completeness(exit 3)与 check:dual-build-cjs-loads(exit 3,需要每个包的 dist),三条各自按自己的前置条件拒绝,⛔ 无一被读成发现。

    底层论断复验为真(契约复核已逐字核过,我采信)

    packages/spec/src/kernel/plugin.zod.ts 的 CORE_PLUGIN_TYPES 恰有 7 项(ui driver server app theme agent objectql),type: z.enum(['standard', ...CORE_PLUGIN_TYPES]) = 8;而 packages/objectql/src/plugin.ts 确实 type = 'objectql';。⇒ 「列了七、声明了八」成立。

    派生卡 #13925 —— ⭐ 它比本卡大,且分得对

    Plugin.type 在已发布面上被声明为 string,八值集合仅靠散文约束;而 packages/core 已经声明了对 @objectstack/spec 的 workspace 依赖 ⇒ 类型级收窄今天就可达。

    ⇒ 那是对已发布类型的 accept/reject 变更 ⇒ 需要裁决,且它是 #13678 那道门之外的另一条补救路线(同一缺陷类,两种解)。⛔ 席位没有自行做,正确。已在维护者队列里。

    状态

    可以放行。 全绿(39 项、零失败)、mergeable: clean、⛔ 无 needs:contract-review(复核 PASS 后已摘)、契约复核 PASS。⚠️ 仅差 draft 翻转 + 入队 —— 而这两个动作在本会话只有 GraphQL 通道且被门控,已请维护者代点。


    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