Skip to content

Marketplace classification: one multi-facet taxonomy (industry, function, …) defined in the spec for hundreds of templates; today spec and cloud carry two category enums that disagree #30904

Closed

Description

@objectstack-fleet

repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2

Filing gate: ③ a task on the maintainer's word. In the repo:cloud#3 seat session (2026-11-06), on how gallery (scenario demo) templates appear in the marketplace, the maintainer said, verbatim:

  • 「至于分类以后有个模板的时候,我们还是需要有行业或者是多角度的分类吧。」
  • 「以后可能会有上百个模板呀」

Record: objectstack-ai/cloud#3649 comment 6682254371.

Owner. The repo:cloud lane (repo:cloud#3) follows it to close. The taxonomy is a platform contract (@objectstack/spec), so the definition lands here first and cloud consumes it (maintainer, 2026-11-06: 「平台的问题应该在平台处理」, #30765). Step 1 is a proposal for the maintainer, not code.

维护者速读

以后模板会有上百个,只按一个分类找不过来,需要多维度分类,比如按行业、按部门或职能,可能还有场景类型、公司规模,可以组合筛选。另外现在有个现成的问题:平台 spec 和 cloud 各有一套分类,取值不一样。平台写的是 integration、developer-tools,有 erp、ai;cloud 写的是 integrations、devtools,有 sales、marketing、app。分类应该只有一套,定在平台,cloud 照用。第一步只出方案(有哪几个维度、每个维度有哪些取值、是否多选),给你看过再开工。

Today (measured)

  • Spec: packages/spec/src/marketplace/marketplace.zod.ts:166-183, MarketplaceCategorySchema, a single-value enum: crm, erp, hr, finance, project, collaboration, analytics, integration, automation, ai, security, developer-tools, ui-theme, storage, other. The listing also has free-form tags (:255-256) and a tags filter (:417-418).
  • Cloud: packages/service-tenant/src/marketplace-taxonomy.ts:48-64, PACKAGE_CATEGORY_OPTIONS, a strict single-value sys_package.category: app, crm, sales, marketing, finance, hr, operations, project, productivity, data, integrations, devtools, utility, other. The browse page derives its category chips from it. is_starter is a separate template flag.
  • The two lists disagree in spelling (integration / integrations, developer-tools / devtools) and in membership. Nothing reconciles them.

Asked

  1. Proposal (step 1). Facets and their values, for example industry, function or department, solution type (app / template / connector / theme), and maybe company size. State, for each facet, whether it is single or multi-valued, and what becomes of the current category and of free tags. Show the mapping from both existing enums. Post it on this card for the maintainer. ⛔ No code before their word.
  2. After approval: the spec defines the facets once. The listing and manifest schemas carry them (Clause-②: judged then). The liveness ledger names the consumers: cloud's catalog (follow-up objectstack-ai/cloud#3661) and the console marketplace UI (ui/).

Related: the package-level Beta marker for gallery templates (objectstack-ai/cloud#3649, in rework) may also belong in the listing schema. That card reports its carrier.

Dedupe (2026-11-06): repo:objectstack-ai/objectstack marketplace category taxonomy is:issue → 4 hits (#30289, #13285 closed and on other subjects; #12708, #6015 seat posts); marketplace facet filter industry is:issue → 3 hits (#29002 a QA run, #15213 closed, #12708 a seat post). None covers a marketplace taxonomy.

Suggested-grade: p2 · the scenario library's gallery grows toward hundreds of templates.

Activity

  1. added
    enhancementNew feature or request
    pm:queueReady for the PM dispatch loop
    repo:cloudCard routed whole to the cloud PM: needs cloud code, a producer or a fact (cloud owns it to closure)
    domain:specDomain lane — seat card indexed by label:pm:seat
    on Nov 6, 2026
  2. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p1 · domain:spec · needs-user-decision · 2026-11-06T10:53Z

    ⛔ Not a claim.

    • A marketplace classification: one multi-facet taxonomy (industry, function, …) defined in the spec and carried by the listing and manifest schemas. The body itself says "after approval".
    • p1 under the standing rule that a protocol addition is p1. New protocol keys are the maintainer's call, so the card waits on that approval (needs-user-decision), not in pm:queue. Once ruled, the cloud catalog follow-up is objectstack-ai/cloud#3661.

    Generated by Claude Code

  3. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2

    Ruling: objectstack#30904 step 1, taxonomy proposal · maintainer · 2026-11-06T10:54Z

    Provenance:

    • Who: the maintainer, in person.
    • Where: the repo:cloud#3 seat session session_01Q4EjjRhUZjdy8VXs6Kkur2.
    • What they selected on the proposal below, verbatim:
    D Selection Effect
    D1 Industry facet now 「现在定义,可选(推荐)」 industries is defined in the spec now. It is optional and multi-valued (0–3). The filter is hidden while no listing carries it. Values follow §2 F2. Only manufacturing and trade-distribution are measured; the rest may be edited at review.
    D2 Current single category 「退役,由职能 + 类型取代(推荐)」 Both spec category schemas retire, under ADR-0087 conversion. They are replaced by businessFunctions (1–3, ordered) and solutionType. A publish carrying category is refused by name with the repair. Cloud migrates its rows once (cloud#3661), and unmapped values go to tags.
    D3 Beta 「单独字段 maturity(推荐)」 A separate maturity: 'beta' | 'stable' on the spec listing and package schemas. It is not a facet. It replaces the closed cloud#3649 / PR #3660.

    Execution: the spec work below (§6, objectstack#30904) is claimed and dispatched by this seat as epic-adjacent work. cloud#3661 stays pm:blocked on it. The console ui/ part is filed as its own objectstack card.


    市场多维分类方案(objectstack#30904 第 1 步,待维护者审)

    读数基线:objectstack origin/main 98f640d,cloud origin/main 8f574b9,hotcrm main 6961ab1(2026-11-05)。
    标注约定:[实测] = 从仓库代码或 GitHub 卡面直接读到;[推断] = 根据实测做的判断,未运行验证。
    依据的维护者原话:「我们还是需要有行业或者是多角度的分类吧」「以后可能会有上百个模板呀」(cloud#3649 评论 6682254371);「先不细化行业,先考虑 通用的职能型场景」(cloud#3600 决策评论 6651166095,针对场景库选题);「平台的问题应该在平台处理」(objectstack#30765)。

    维护者速读

    • 问题: 今天一个包只能挂一个分类。这一个分类同时承担了"给谁用(职能)"和"是什么(应用 / 连接器 / 模板)"两件事,而且平台和 cloud 各有一套取值。实际上一共有 4 份互不一致的分类表,还有 2 份照抄的标签表。
    • 方案: 用 3 个维度取代单一分类:职能(多选,必填)、行业(多选,可选,不填就是"通用")、类型(单选:应用 / 模板 / 连接器)。另外,Beta 不做成维度,做成一个独立字段。tags 保留为自由文本,只用于搜索。is_starter 不动。
    • 规模: 不加"公司规模"这个维度。仓库里没有任何包、场景卡或文档用到它。
    • 需要你裁 3 件事: D1 行业维度现在就定义,还是等第一个行业模板出现再加;D2 是否退役现有的 category;D3 Beta 的放法。三项都有推荐,见下文。
    • 定了以后: spec 先落地(objectstack#30904),cloud 跟进(cloud#3661),控制台 ui/ 改筛选栏和 Beta 徽标。

    1. 现状实测

    1.1 现在有几份分类表 [实测]

    # 位置 形状 取值 谁在读
    ① spec packages/spec/src/marketplace/marketplace.zod.ts:167-183 MarketplaceCategorySchema 闭合枚举,单值 crm, erp, hr, finance, project, collaboration, analytics, integration, automation, ai, security, developer-tools, ui-theme, storage, other MarketplaceListingSchema.category(:253,必填)、MarketplaceSearchRequestSchema.category(:415)、MarketplaceSearchResponseSchema.facets.categories(:469-472)。除 spec 自己的测试和生成文档外,objectstack 和 cloud 都没有运行时代码 import 这三个 schema(git grep MarketplaceListingSchema|MarketplaceSearchRequestSchema 在 spec 包外命中 0 次)。所以它是"声明了但没人执行"的契约。
    ② spec packages/spec/src/marketplace/package.zod.ts:47-51 PackageCategorySchema 开放字符串(z.string().min(1)) 任意值 PackageSchema.category(:236)、CreatePackageRequestSchema.category(:309)、UpdatePackageRequestSchema.category(:331);TemplateManifestSchema 继承前两者(即 objectstack.manifest.json)。这是 CLI 发布和 cloud 发布门真正经过的一条路径。
    ③ cloud packages/service-tenant/src/marketplace-taxonomy.ts:48-64 PACKAGE_CATEGORY_OPTIONS 闭合枚举,单值,严格 Field.select app, crm, sales, marketing, finance, hr, operations, project, productivity, data, integrations, devtools, utility, other 见 1.2
    ④ objectstack packages/qa/scenarios/src/schema.ts:89-105 SCENARIO_CATEGORIES 闭合枚举 ③ 的手抄副本(注释:"The cloud marketplace taxonomy") 场景卡 docs/qa/scenarios/*.json 的 category
    ⑤ 控制台 i18n ui/packages/i18n/src/namespaces/marketplace/en.ts:223-239(另有 8 种语言) 标签表 照抄 ① 的 15 个值 MarketplacePage.tsx:562-566、MarketplacePackagePage.tsx:1425-1426
    ⑥ cloud packages/service-tenant/src/translations/{zh-CN,ja-JP,es-ES}.ts(category.options) 标签表 照抄 ③ 的 14 个值 sys_package 的选择框和安装对话框

    直接后果 [实测 + 推断]:

    • 控制台的数据来自 cloud(取值是 ③),翻译键却按 ① 建(⑤)。两者只有 crm / hr / finance / project / other 五个值重合。app、integrations、devtools、sales、marketing、operations、productivity、data、utility 这些值在控制台都找不到翻译,筛选按钮上直接显示原始英文值(MarketplacePage.tsx:564-565 的兜底逻辑)。[推断:按代码路径判断,未在浏览器里看]
    • 拼写本身就不一致:integration / integrations,developer-tools / devtools。
    • 空白模板 packages/create-objectstack/src/templates/blank/objectstack.manifest.json:8 写的是 "category": "starter"。CLI 发布时会把它原样带上(packages/cli/src/commands/package/publish.ts:639-640),cloud 发布门则会用 400 拒绝它(package-publish.ts:896-899,isValidPackageCategory('starter') 返回 false)。也就是说,从空白模板新建的项目不改分类,第一次发布就会失败。[推断:代码路径,未实际运行]

    1.2 cloud 侧有哪些读者 [实测]

    读者 位置 读什么
    存储 sys-package.object.ts:420-425 category(严格 select);:427-431 tags(JSON 文本);:502-511 is_starter —
    发布门 service-cloud/src/routes/package-publish.ts:896-899 校验 category;:187-240 校验 tags(≤20 个,每个 ≤64 字符,:169-170);:952-955 只有第一方 service 发布才能设 is_starter category、tags、is_starter
    环境导出 routes/package-export.ts:408,438 固定写 category: 'app'、is_starter: false category
    "我的已发布应用"视图 sys-package.object.ts:292 my_apps 用 category = 'app' 作判别条件 category
    公共目录 API routes/marketplace.ts:319,358-360 ?category= 精确匹配;?q= 只匹配 display_name / manifest_id(:351-356),不搜 tags category
    R2 快照 + Worker marketplace-snapshot.ts 按 PUBLIC_PACKAGE_FIELDS 写出 category / tags / is_starter;apps/cloud/cloudflare/worker.ts:787-813 同样只做 ?category= 精确过滤,q 同样不搜 tags category
    管理端页面 pages/sys-package.page.ts:97-110 筛选按钮从 category 的 select 选项自动生成;:175-179 "App Templates" 页的基础过滤条件是 is_starter = true category、is_starter
    一次性迁移 service-cloud/src/migrations/category-taxonomy-migration.ts + marketplace-taxonomy.ts:79-92 的别名表(tools→devtools、business/ops→operations、analytics→data、connector(s)/integration→integrations、starter→app 并置 is_starter=true……) category
    连接器上架脚本 scripts/marketplace/publish-connector-apps.mjs:64-66,239-248 把清单里写的 category(finance / devtools / productivity)一律改写成 integrations,只在日志里提一句 category

    1.3 objectstack 侧有哪些读者 [实测]

    • CLI:os package publish --category(publish.ts:401-403),回落顺序是 flag 优先、清单次之(:639-640);tags 原样转发(:646);isStarter 和 publisher 由平台决定,不随发布携带(:286-289)。
    • 控制台:MarketplacePage.tsx:552-577。分类按钮从已加载的条目里去重生成,然后在客户端过滤;目录一次只取 100 条(:407 limit: 100)。卡片上显示一个 category 徽标(:965-966),详情页也是一个(MarketplacePackagePage.tsx:1542)。控制台不读 tags,也不读 is_starter(grep 命中 0 次)。
    • 场景库:SCENARIO_CATEGORIES(④),9 张卡各带一个 category。

    1.4 目录里今天有什么 [实测,仓库可见范围;生产数据查不到]

    包 来源 category tags isStarter
    HotCRM app.objectstack.hotcrm hotcrm objectstack.manifest.json crm crm, sales, accounts, leads, opportunities, cases, campaigns false
    connector-stripe objectstack packages/apps/connector-stripe/objectstack.manifest.json finance(上架时被改写为 integrations) stripe, openapi, connector, payments —
    connector-github packages/apps/connector-github/… devtools(上架时被改写为 integrations) github, mcp, connector, issues —
    connector-docusign packages/apps/connector-docusign/… productivity(上架时被改写为 integrations) docusign, esignature, openapi, connector, oauth —
    Blank Starter create-objectstack/src/templates/blank/… starter(③ 已不收这个值) — true
    Local Sample App com.local.sample cloud scripts/dev-local/seed.mjs:256-266 devtools — —
    任意环境导出 package-export.ts:408 app — false
    examples/app-crm、app-todo、app-showcase objectstack examples/ 不声明 不声明 由 CI 置位(据 sys-package.object.ts:509 的字段说明;CI 实际是否这样发布,未核实)

    场景卡(docs/qa/scenarios/*.json):crm_account_with_contacts=crm;invoice_with_line_items=finance;travel_expense=finance;leave_balances=hr;order_to_cash=sales;project_with_tasks=project;asset_lifecycle、requisition_to_receipt、service_desk 三张都是 operations。

    这张表说明了两件事:

    1. 单一分类同时装着"类型"和"职能",两者在打架。 Stripe 连接器本来属于财务,上架后只能挂 integrations,"财务"这个信息就丢了(publish-connector-apps.mjs:64-66)。app 也不是职能,而是"整套应用"这个类型。
    2. 粒度不够,而且无法多选。 operations 一个值里装了行政资产、采购、客服三种不同的职能。HotCRM 实际覆盖销售、客服、营销(看它的 tags 和 src/{sales,service,marketing}),却只能挂一个 crm。

    1.5 场景库会覆盖哪些领域 [实测,cloud#3600]

    • 职能(第 0 阶段 9 张已裁,第 1 阶段 v2 的 11 张已呈,暂缓):销售、客服、财务、人事、采购、供应链、行政、项目、法务;市场营销目前空缺,决策评论写明"可以在第 2 阶段补"。
    • 行业:目前为零。 维护者 2026-11-05 的原话是「先不细化行业,先考虑 通用的职能型场景」。仓库里唯一的行业素材是 cloud#3567:一家化工集团领导层的 23 个场景,包括质量 8D、LIMS/MES、化验超标扣款、危化品进厂核验、外贸询价、出口单证。按 D1 裁决,它只能脱敏后进入开源库。
    • 已下架的旧模板(template-registry.ts:46-52 RETIRED_TEMPLATES):todo、compliance、content、contracts、procurement。全部是职能型。
    • cloud docs/strategy/marketplace-bootstrap.md §2 列的 M0 模板:Customer Operations、Support Tickets、Approval Workflow。同样是职能型。

    结论: 证据强力支持"职能"多选,以及把"类型"拆出来。"行业"在仓库数据里还没有需求,只有维护者的原话。"规模"既没有原话也没有数据。

    1.6 先例 [实测]

    • spec 和 ADR 里没有"行业"或"solution type"这个概念。git grep -i 'industry|solutionType|vertical' 在 packages/spec/src 里只命中示例字段(比如 CRM 示例里的 industry 选择字段);在 docs/adr 里只有"industry precedent"这类泛指。
    • ADR-0019(plugin.zod.ts:110 CONSUMER_INSTALLABLE_TYPES = ['app']):消费者市场只有"应用"这一个名词,driver、plugin、theme 都不上架。所以 ① 里的 ui-theme、storage 在 ADR-0019 下根本不可能出现在列表里,"类型"维度也只能是应用内部的细分,不能是 packageType。
    • ADR-0131 D6(manifest.zod.ts:640-666 installModes):unmanaged 表示"装进去归客户、可整包接管",这正是模板的语义。cloud#3600 的 Q5 v2 已裁:场景库毕业的模板走 unmanaged 上架,hot* 套件走 managed 上架。
    • ADR-0025 §3.10:市场要承载的东西列为"vertical apps, templates, drivers, connectors, …",按类型的划分方式和这里一致。
    • ADR-0046:分组(group)用来导航,标签(tag)用来做分面筛选。

    2. 维度定义

    总则

    • 只在 spec 里定义一次。 新文件 packages/spec/src/marketplace/taxonomy.zod.ts 导出各维度的闭合枚举,以及一张旧值映射表 LEGACY_CATEGORY_MAPPING。cloud 的迁移和 CLI 的拒绝提示都从这里 import,不再各抄一份。
    • 筛选语义: 同一维度内取 OR,不同维度之间取 AND。筛选按钮按"当前结果里出现过的值"自动生成,并带计数。cloud 管理页(showCount: true)和控制台今天已经是这个做法,保留。
    • 值怎么增加:
      • 只加不改。改名或删除一个值,要走 ADR-0087 退役流程,并且同时提供数据迁移。
      • 新增一个值,需要一个 spec PR,写明哪个真实的上架包,或哪张已裁定阶段清单里的场景卡需要它(需求拉动)。同一个 PR 里同时补齐控制台 i18n(9 种语言);cloud 的翻译在跟进 PR 里补。
      • 每个维度保持在约 12 个值以内。超过的话,先考虑合并,而不是继续加。
    • 多选上限由 spec 卡住,防止作者(尤其是 AI 作者)把能勾的全勾上。

    F1 职能 businessFunctions —— 多选,1–3 个,有序,第一个是主职能

    为什么要有: 场景库和现有所有模板都按职能来组织,这是用户找模板最主要的入口。字段名不用 functions,避免和函数、hook 这些概念混淆。

    值 中文 证据
    sales 销售 场景库(销售管道、客户跟进、订单到收款)、HotCRM
    marketing 市场营销 cloud ③、HotCRM src/marketing;场景库第 2 阶段待补
    customer-service 客户服务 场景库(客服工单、SLA)、bootstrap §2 Support Tickets、HotCRM src/service
    finance 财务 场景库(发票、报销、应收)、connector-stripe
    hr 人力资源 场景库(请假、招聘、培训)
    supply-chain 采购与供应链 场景库(采购申请、进销存、供应商评分)、旧模板 procurement
    administration 行政与资产 场景库(资产全生命周期、会议室、设备维保)
    project 项目管理 场景库、① 和 ③ 都有
    legal 法务与合规 场景库(合同到期)、旧模板 contracts / compliance
    it IT 与开发 ③ devtools、① developer-tools、connector-github
    collaboration 协同办公 ① collaboration、③ productivity、cloud#3567 "日常办公"(会议纪要、公文、制度问答)
    • 规则:上架市场时(cloud 的提交审核策略里,替换今天"必须有 category"的要求)至少要有 1 个职能;schema 层面字段可以不填,因为本地包和私有包不需要。
    • 卡片上只显示第一个职能的徽标,详情页显示全部。

    F2 行业 industries —— 多选,0–3 个,可选;不填就是"跨行业通用"

    为什么要有: 维护者明确提出要按行业分类。模板到了上百个以后,"制造业的采购"和"零售业的采购"需要分开找。今天的数据里还没有一个包带行业,所以这个维度做成可选,没有值时不显示(见 D1)。

    值 中文 证据
    manufacturing 制造业(含化工) cloud#3567 化工集团 [实测]
    trade-distribution 贸易与分销(含外贸) cloud#3567 B7/B8 外贸询价、出口单证 [实测]
    retail 零售与消费品 [推断:主流市场的常见值]
    logistics 物流与运输 [推断]
    professional-services 专业服务 [推断]
    financial-services 金融服务 [推断]
    healthcare 医疗健康 [推断]
    education 教育 [推断]
    public-sector 政府与公共事业 [推断]
    real-estate-construction 房地产与建筑 [推断]
    • 不设 general 或 cross-industry 这样的值。 不填就是通用。如果设一个"通用"值,就等于同一件事有两种写法,而且 AI 作者会给每个包都勾上它。
    • 只有前两个值有实测依据。其余 8 个参照 AppExchange / AppSource 的行业分面列出,可以在 D1 里删减。如果你只想先放有证据的,就只保留前两个,其余等需求拉动再加。

    F3 类型 solutionType —— 单选,默认 app

    为什么要有: 把"它是什么"从分类里拆出来,连接器就不会再丢掉自己的职能。模板和完整应用的区别(安装后归谁、有没有升级义务)是用户在安装前就需要知道的。字段名不用 kind 或 type,避免和 packageType、页面的 kind 冲突,也和 #30904 卡面上 "solution type" 的说法一致。

    值 中文 含义 发布门的交叉校验 [方案]
    app 应用 有升级义务的完整应用(managed),例如 HotCRM、hot* 套件 —
    template 模板 装进去以后归客户,可以完全修改(cloud#3600 Q5 v2:场景毕业作品) 要求 resolveInstallModes(manifest) 包含 unmanaged,否则用 400 明确拒绝
    connector 连接器 主要作用是接入某个外部厂商的 API(cloud#3447 的定义) 要求包至少声明 1 个 connector 实例,否则拒绝
    • 不加 theme。 ADR-0019 规定主题随应用发布、不单独上架,仓库里也没有主题包。以后真出现了,按"值怎么增加"的规则补。
    • 选择由作者声明,发布门交叉校验,而不是平台从包内容里推导。理由:推导"是不是连接器"只能靠启发式,判错了不会有任何提示,属于静默错误;作者声明以后,不一致会被直接拒绝,错误是看得见的。[置信缺口:template 其实可以完全从 installModes 推导出来。如果你倾向"能推导的就不声明",可以只对 template 改成推导,其余不变。]

    不采纳的维度:规模

    仓库里没有任何包、场景卡、ADR 或策略文档提到公司规模,套餐分档也是另一套机制。按"零需求就不做"的原则,这一维度不加。

    不进入分类的字段

    字段 处理
    tags 保留,作者自由填写,只用于搜索,不生成筛选按钮。沿用 cloud 现有的限制(20 个 × 64 字符),去重、去空格、统一小写。迁移时,把映射过程中丢失的旧分类值追加成 tag(见 3.1、3.2),这样搜索召回率不会下降。cloud 的 ?q= 应该同时搜 tags:今天 API(marketplace.ts:351-356)和 Worker(worker.ts:805-810)都不搜,tags 等于没人读。这一项放进 cloud#3661。
    is_starter 不动。 它不是分类,而是平台的策展标记:表示这个包由平台挑选、会出现在 App Templates 页和新建环境的蓝图选择器里,只有第一方 service 发布才能设(package-publish.ts:952-955;CLI 不携带这个字段,见 publish.ts:286-289)。它和 solutionType: template 不是一回事:前者回答"平台推不推荐",后者回答"装上以后归谁"。旧值 category = 'starter' 映射成 solutionType: template,is_starter 维持原状。以后要不要把两者合并,等 Q5 毕业机制跑起来再看,本轮不提。

    3. 映射表

    规则:

    • 一个旧值确定对应某个新值时,自动映射。
    • 一个旧值在新体系里没有对应位置时,把它追加成 tag,职能留空,由迁移的 dry-run 计划列出,需要人工补填;补齐之前,这些包没法提交市场审核。
    • 不会把它们静默塞进一个兜底值。今天 cloud 把未知值落到 other(marketplace-taxonomy.ts:122),新方案取消 other。

    3.1 spec MarketplaceCategorySchema(①)→ 新维度

    ①在运行时没有读者(见 1.1),所以这张表是语义上的对应关系,不涉及数据迁移。

    ① 值 businessFunctions solutionType industries 追加的 tag 说明
    crm [sales] app — crm CRM 跨销售、客服、营销;自动映射只给主职能,其余由作者补
    erp [finance, supply-chain] app — erp
    hr [hr] app —
    finance [finance] app —
    project [project] app —
    collaboration [collaboration] app —
    analytics 需人工 app — analytics 分析是一种能力,不是职能
    integration 需人工 connector — 职能取连接器本身服务的领域
    automation 需人工 app — automation 能力,不是职能
    ai 需人工 app — ai 平台本身就是 AI 原生,"AI"作为分类没有区分度
    security [it] app — security
    developer-tools [it] app — devtools
    ui-theme — — — — 删除。 ADR-0019 规定不可上架
    storage — — — — 删除。 属于 driver,ADR-0019 规定不可上架
    other 需人工 app —

    3.2 cloud PACKAGE_CATEGORY_OPTIONS(③)及别名 → 新维度(对应 cloud#3661 的数据迁移)

    ③ 值(含别名) businessFunctions solutionType 追加的 tag 说明
    app(application, apps) 需人工 app 环境导出的默认值。my_apps 视图的过滤条件改为 solution_type = 'app'
    crm [sales] app crm
    sales [sales] app
    marketing [marketing] app
    finance [finance] app
    hr [hr] app
    operations(business, ops) [administration],并在 dry-run 中标记复核 app operations 实测这个值装着行政、采购、客服三种职能
    project [project] app
    productivity [collaboration] app
    data(analytics) 需人工 app analytics
    integrations(integration, connector, connectors) 取清单里原本写的 category 再映射(stripe→finance,github→it,docusign→collaboration);没有就需人工 connector 解决 1.4 里连接器丢职能的问题
    devtools(tools, developer) [it] app devtools
    utility 需人工 app utility
    other(travel 及其他未知值) 需人工 app 原值
    starter(旧标记) 需人工 template is_starter 维持原状(旧迁移已经置过位)

    3.3 仓库内现有包的建议标注 [方案,供 spec/cloud 卡落地时参考]

    包 businessFunctions industries solutionType
    HotCRM [sales, customer-service, marketing] — app
    connector-stripe [finance] — connector
    connector-github [it] — connector
    connector-docusign [collaboration](也可以选 legal,电子签主要用于合同) — connector
    Blank Starter [collaboration] — template
    场景卡 crm_account_with_contacts / order_to_cash [sales] / [sales, finance] — —
    invoice_with_line_items / travel_expense [finance] — —
    leave_balances [hr] — —
    project_with_tasks [project] — —
    asset_lifecycle / requisition_to_receipt / service_desk [administration] / [supply-chain] / [customer-service] — —

    4. Beta 标记怎么放(cloud#3649 已裁)

    已裁内容:Beta 是包级别的"尚不成熟"标记,在列表和详情页显示徽标;不是版本的预发布;Beta 模板保留它真实的分类;设置 Beta 不能绕过审核。

    推荐:作为独立字段,不做成维度。 在 spec 的 MarketplaceListingSchema、PackageSchema、CreatePackageRequestSchema 和 UpdatePackageRequestSchema 上加 maturity: 'beta' | 'stable',可选,不填视为 stable。

    理由:

    • 维度回答的是"这个包是关于什么的",是固定属性;成熟度回答的是"现在能不能放心用",会随毕业而变化(cloud#3600 Q5 的毕业规则),两者不是一类东西。
    • 已裁的"保留真实分类"本来就要求 Beta 和分类互不干扰。如果做成维度值(比如 solutionType: beta-template),就会把 Beta 包从它真实的类型里挤出去。
    • 用枚举而不用布尔值:以后如果要加 preview 这类状态,只需加一个值,不必再加一个布尔字段。今天只有两个值,比布尔多出的维护成本几乎为零。
    • 放在 spec 里,是因为徽标要在控制台 ui/ 里显示,载体就是 spec 的列表 schema(「平台的问题应该在平台处理」)。PR feat(spec): publish ISecurityService and enforce it at both ends #3660 返工时,直接用这个字段,不再在 cloud 里另起一个字段。

    搜索请求可以带一个 maturity 过滤参数(例如"只看稳定版"),但它不出现在维度的筛选按钮里。


    5. 需要你裁的事

    D1 行业维度现在就进 spec 吗?

    一句话问题: 你要按行业找模板,但今天没有一个包、一张场景卡标了行业,而且场景库按你的指示先做职能型场景。

    选项 做什么 客户感知 工作量
    A(推荐) 现在就在 spec 里定义 industries:可选、多选 ≤3、闭合枚举(10 个值,或者只留有证据的 2 个)。没有值时,控制台和 cloud 都不显示这组筛选按钮 今天看不到任何变化;第一个行业模板一上架,行业筛选就自动出现 spec 里多一个枚举和一个字段,与 F1、F3 在同一个 PR 里;没有存量数据要迁移;无先例
    B 本轮只做职能和类型;等第一个行业模板出现时再单独加行业 同上 本轮少一个字段;以后要再走一轮 spec → cloud → ui,并且要发一次 spec 小版本
    C 行业不做成维度,作者写进 tags 搜得到但筛不出来,同一个行业会出现"制造""manufacturing""mfg"几种写法 零

    业务含义: A 像商场开业前就把楼层导视牌的位置留好,店没来就空着;B 是等店进驻了再挂牌;C 是让每家店自己在门口贴纸条。

    os-decision-facets
    ① 项目长远合理性:A 一次定型,两年后上百个模板时,行业、职能、类型三个维度并列,这和 Salesforce AppExchange(Industry / Business Need / Solution Type)、Microsoft AppSource(Industries / Categories)的建模一致;B 以后会多一次契约变更;C 会把行业变成无法筛选的自由文本。
    ② 实际业务拉动:拉动来自你的原话(「多角度的分类」「上百个模板」);数据上目前是零。A 用"可选,无值不显示"来承接这个零。
    ③ 防 AI 犯错:A、B 都是闭合枚举,写错会被直接拒绝;C 是自由文本,写错不会有任何提示。A 限定最多 3 个,并且不设"通用"值,AI 没法通过全选来作弊。
    ④ 创业阶段不扩散:A 多声明一个字段,这是永久义务,但不需要存量迁移;B 本轮更省,但会多一轮跨仓变更。
    Prior rulings read: 行业 / industry / 多角度 → cloud#3649 6682254371、cloud#3600 6651166095; none in ADRs
    

    自检:只看①选 A;②③④ 是否翻转:否(② 的零数据靠"可选、无值不显示"来消化,不改选项)。
    置信缺口:10 个值里只有 2 个有实测依据。如果你希望只放有证据的值,选"A,只留 2 个"即可,后续的值走需求拉动。

    D2 现有的 category 怎么处理?

    一句话问题: 现在一个"分类"同时表示"给谁用"和"是什么",只能选一个,而且平台和 cloud 的取值不一样。连接器因此丢掉了自己的职能,一个 operations 里混着三种职能。

    选项 做什么 客户感知 工作量
    A(推荐) 退役 category,由 businessFunctions(有序,第一个是主职能,显示在卡片徽标上)和 solutionType 取代。spec 里的两份 category(① 和 ②)一起删除,按 ADR-0087 退役;带 category 的发布被明确拒绝,提示里写出对应的新维度值。cloud 用一次性迁移把存量数据转过去 可以按职能多选、按类型筛选,连接器能出现在"财务"下面;第一方的 5 份清单(3 个连接器、blank、hotcrm)在同一轮里改掉,第三方作者第一次发布时会收到明确的改法提示 spec 1 个 PR(删除 + 新增 + 生成产物);cloud 1–2 个 PR(字段、迁移、筛选);ui 1 个 PR;先例:category-taxonomy-migration.ts(cloud 曾把 category 从自由文本收紧为 select,并做过同样的一次性迁移)
    B 保留 category 作为单值的"主分类"(值域等于职能),另加多选的 businessFunctions 和 A 类似,但作者要填两个意思重叠的字段 少一次删除,多一个永久重复的字段
    C 保留单一 category,取 ① 和 ③ 的并集,只额外加一个 industries 筛选能力不变,连接器继续丢职能 最少

    业务含义: A 是把"商品类别"和"适用部门"做成两组货架标签;B 是在原来那张标签上再贴一张意思差不多的新标签;C 是把两个旧标签本子合成一本继续用。

    os-decision-facets
    ① 项目长远合理性:A 缩小了契约(4 份分类表合并成 spec 里的一处,两个职责拆开);B 让同一个事实有两种写法(category 和 businessFunctions[0]),会出现特例;C 保留了单值同时承担两种职责的混用。
    ② 实际业务拉动:今天就撞上了:连接器上架脚本改写清单分类(publish-connector-apps.mjs:64-66),控制台显示未翻译的分类值,空白模板的 starter 会被发布门拒绝。
    ③ 防 AI 犯错:A 是闭合枚举 + 交叉校验 + 发布时直接拒绝;B 两个字段写得不一致时不会有任何提示;C 继续让 AI 在"类型"和"职能"之间二选一,选错也没有提示。
    ④ 创业阶段不扩散:A 删除 2 份 spec 声明、1 份 cloud 枚举、1 份 qa 副本,是"删除优于继续维护";B 和 C 都是继续维护。
    Prior rulings read: category taxonomy → cloud marketplace-taxonomy.ts 头注释(app 保留、starter 并入 is_starter); objectstack#30904 卡面; thread: none
    

    自检:只看①选 A;②③④ 是否翻转:否。
    置信缺口:solutionType 由作者声明再交叉校验(推荐做法),还是对 template 直接从 installModes 推导(见 F3),两种都可以。差别只在 template 这一个值上,不影响 A 的方向。

    D3 Beta 标记放哪里?

    一句话问题: 你已经定了要给不成熟的模板打 Beta 标记,并且保留它的真实分类。现在要决定这个标记放在分类体系里,还是放在分类体系外。

    选项 做什么 客户感知 工作量
    A(推荐) 放在分类体系外:spec 上加独立字段 maturity: 'beta' | 'stable'(可选,默认 stable);控制台在列表和详情页显示徽标;搜索可以按它过滤,但它不出现在维度筛选按钮里 卡片上有 Beta 徽标,分类照常;毕业时去掉标记 和本方案的 spec PR 合并提交;PR #3660 改用这个字段
    B 做成维度的一个值(例如 solutionType: beta-template) Beta 模板从"模板"类型里消失 与 A 相当
    C 用一个 tag beta 搜得到,但没有徽标;"Beta""beta""预览"几种写法并存 零

    业务含义: A 像商品上贴的"新品试用"小标签,商品仍然摆在原来的货架上;B 是把试用品单独搬到另一个货架;C 是让店员手写一张便签贴上去。

    os-decision-facets
    ① 项目长远合理性:A 让分类回答"是什么、给谁用",成熟度回答"现在能不能放心用",两者互不干扰,主流应用商店的 Beta / Preview 徽标都是这样独立于分类的;B 把生命周期状态混进了分类。
    ② 实际业务拉动:cloud#3649 已裁,画廊模板马上就要用。
    ③ 防 AI 犯错:A 是闭合枚举,写错会被直接拒绝;C 是自由 tag,写错没有提示,徽标也无从显示。
    ④ 创业阶段不扩散:A 只多一个字段,并且替代了 PR #3660 原本要在 cloud 里加的字段,所以整体上声明数量不增加。
    Prior rulings read: beta / 成熟 → cloud#3649 6682254371; thread: none
    

    自检:只看①选 A;②③④ 是否翻转:否。
    置信缺口:maturity 由谁设置。推荐由作者声明(画廊导出器是第一方,第三方作者自己标 Beta 也是诚实的自我标注),毕业就是重新发布时不再带这个标记。如果你希望只有平台能去掉 Beta,就需要像 isStarter 一样把它列为"平台决定的字段",这一点可以另外裁。


    6. 裁定后的分工

    顺序:spec 先合并并发版 → cloud 升级依赖版本 → cloud 和控制台并行。

    objectstack#30904(spec,也就是本卡)

    1. 新建 packages/spec/src/marketplace/taxonomy.zod.ts:MarketplaceBusinessFunctionSchema、MarketplaceIndustrySchema、MarketplaceSolutionTypeSchema、PackageMaturitySchema、LEGACY_CATEGORY_MAPPING(即 3.1 / 3.2 的表)。
    2. PackageSchema、CreatePackageRequestSchema、UpdatePackageRequestSchema(从而 TemplateManifestSchema)、MarketplaceListingSchema:加 businessFunctions / industries / solutionType / maturity;MarketplaceSearchRequestSchema 的过滤参数和 MarketplaceSearchResponseSchema.facets 改成按维度计数。
    3. 按 spec-property-retirement 手册和 ADR-0049/0087,退役 MarketplaceCategorySchema、PackageCategorySchema 和三处 category 键,同步更新生成的基线和文档。
    4. 在活性台账(liveness ledger)里写明消费方:cloud 目录(cloud#3661)、控制台(ui/)、CLI 发布。
    5. CLI os package publish:从清单读取各维度并携带;退役 --category flag,改成带改法提示的明确拒绝。
    6. 改第一方清单:3 个连接器、blank 模板(starter 改为 solutionType: template)。
    7. packages/qa/scenarios:删除 SCENARIO_CATEGORIES 副本,改为 import spec 的维度;9 张卡按 3.3 重标。(这属于场景库席位 epic cloud#3600 的范围,需要协调。)
    8. HotCRM 清单按 3.3 更新(hotcrm 仓,属于另一条车道)。

    cloud#3661(cloud)

    1. sys_package 加列:business_functions、industries(multiselect)、solution_type(select)、maturity(select)。选项直接从 spec 的枚举生成,不再保留第二份枚举。删除 marketplace-taxonomy.ts 和 PACKAGE_CATEGORY_OPTIONS。
    2. 发布门:用 spec 的 schema 校验各维度,执行 template 和 connector 的交叉校验,拒绝带 category 的请求;环境导出写 solution_type: 'app';my_apps 改为按 solution_type 过滤;提交审核策略的"必须有 category"改为"至少 1 个职能"。
    3. 一次性迁移 package-category-taxonomy-v2:使用 spec 的 LEGACY_CATEGORY_MAPPING,支持 dry-run,dry-run 列出需人工补填和需复核(operations)的包。
    4. 公共目录 API、R2 快照、Worker:各维度可组合过滤(维度内 OR、维度间 AND),?q= 同时搜 tags。
    5. 管理页 sys-package.page.ts:userFilters 换成各维度。连接器上架脚本改为写 solution_type: connector,并保留清单里原来的职能。
    6. 翻译(⑥)改为按维度组织。
    7. cloud#3649 / PR feat(spec): publish ISecurityService and enforce it at both ends #3660:Beta 改用 spec 的 maturity。

    控制台 ui/(objectstack,归入 #30904 的同一组卡或作为它的子卡)

    1. MarketplacePage.tsx:用三组维度筛选替代单行分类按钮,其中行业组没有值时隐藏;类型适合做成页签或分段按钮。
    2. 卡片显示主职能徽标,以及 Beta 徽标(maturity === 'beta');详情页显示全部维度。
    3. i18n:marketplace.category.* 换成 marketplace.businessFunction.*、marketplace.industry.*、marketplace.solutionType.*、marketplace.maturity.beta,9 种语言都要补。
    4. 相关缺口(不在本方案范围内): 控制台一次只取 100 条,然后在客户端过滤(MarketplacePage.tsx:407)。模板到了上百个以后,必须改成服务端过滤加分页。cloud#3600 Q5 已经把"分页"列为市场的能力缺口,建议把维度过滤和分页放在同一张卡里做。
  4. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2

    Claim: PM loop round R5 · 2026-11-06T10:55Z
    Session: session_01Q4EjjRhUZjdy8VXs6Kkur2
    Account: hotlong (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-30904-marketplace-taxonomy
    Worktree: /home/user/w30904/objectstack-issue-30904
    Domain: domain:spec (claimed by the repo:cloud lane that filed it; every posted domain:spec seat reads vacant)
    Seat: repo:cloud#3
    File surface: a new packages/spec/src/marketplace/taxonomy.zod.ts (facets + maturity); marketplace.zod.ts and package.zod.ts (the two category schemas retired under ADR-0087, new keys on listing/package schemas); the liveness ledger; packages/cli publish (forward the new keys, refuse category); first-party manifests and templates (create-objectstack blank template's category: "starter"); packages/qa/scenarios SCENARIO_CATEGORIES; tests and generated baselines
    Container & model: M, mode:subagent, model: TIER_DEFAULT, effort: high
    Clause-②: yes (widening) — new facet keys and maturity; it also narrows (category retired and refused by name); a contract review runs before the queue
    Metadata-protocol: none
    Responsibility: one marketplace taxonomy for hundreds of templates | none | every package listing, CLI publish and the console marketplace
    Thread-read: 6683195046
    Premise: holds — four disagreeing category lists measured in the proposal (this card's ruling comment)
    Serial constraints cleared: objectstack#30880 (in flight) edits packages/spec/src/data/field.zod.ts / object.zod.ts, not the marketplace files; no other in-flight claim on marketplace.zod.ts / package.zod.ts found.
    Carrier: objectstack (spec, CLI, first-party manifests). Cloud's adoption is objectstack-ai/cloud#3661; the console ui/ is its own objectstack card.

  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 6, 2026
  6. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    priority:p1High: required for production / M2
    and removed on Nov 6, 2026
  7. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2
    os-dev-report

    {
     "issue": 30904,
     "status": "done",
     "branch": "claude/issue-30904-marketplace-taxonomy",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/30980",
     "session": "session_01Q4EjjRhUZjdy8VXs6Kkur2",
     "premise_still_valid": true,
     "summary": "The marketplace taxonomy is defined once in packages/spec/src/marketplace/taxonomy.zod.ts: businessFunctions (11 values, 1-3 distinct, ordered, required on a listing), industries (10 values, 0-3, no catch-all), solutionType (app|template|connector, no theme), maturity (beta|stable, a flag not a facet), explainSolutionTypeContradiction (template needs unmanaged, connector needs a declared connector) and LEGACY_CATEGORY_MAPPING (rulings 3.1 + 3.2, aliases included, manual never folded into a catch-all). Both category schemas retire under ADR-0087: retiredKey tombstones on Package, CreatePackageRequest, UpdatePackageRequest, TemplateManifest, MarketplaceListing, MarketplaceSearchRequest (+ facets.categories), MarketplaceCategorySchema/PackageCategorySchema deleted, 6 RETIRED_KEYS + 2 RETIRED_DEFS entries, D3 marketplace-category-retired, no D2 conversion (no stack collection member; boot never reads these shapes, so nothing to tolerate or refuse at boot — refuse at publish, cloud migrates stored rows once). os package publish refuses category (file or hidden --category) before any network call naming the facets for the value, validates and forwards the four facets, and cross-checks solutionType against the artifact. Connector manifests re-tagged; the blank template drops category starter and declares no facet (it is copied into every scaffolded project; a template there would be refused by the cross-check). SCENARIO_CATEGORIES is gone: cards carry businessFunctions typed by the spec, and the gallery exporter that landed on main meanwhile reads the primary function. objectstack.manifest.json enters the liveness ledger as template_manifest naming cloud#3661 and the console card as consumers. Declaration lines deviate from the claim (see deviations).",
     "tests": "Final head 2f243e0d68 (verification pass ran on 598f76c3b3; the one later commit regenerates content/docs/references/marketplace/taxonomy.mdx only): spec test exit 0; spec repo scripts/file-description.test.ts exit 0 (was CI-red on the old head: the new module header opened at level 1, demoted); spec typecheck 0; cli unit exit 0 Test Files 394 passed (394) Tests 5559 passed (5559); cli typecheck 0; create-objectstack test/typecheck 0/0; scenarios test/typecheck 0/0; connector-stripe/github/docusign test 0/0/0. New: spec src/marketplace/taxonomy.test.ts (39), cli test/package-publish-taxonomy.test.ts (14). Ablations, one per rule via scripts/ablation-replace.mjs (mutation landed by anchor count + blob change, restore proven git diff HEAD empty, tree 0 modified after): F1 max 1 failed/39; F1 min 1/39; distinct 2/39; F2 max 1/39; template cross-check 1/39; connector cross-check 1/39; CreatePackageRequest.category tombstone deleted 2/39; CLI manifest refusal 2/14; CLI flag refusal 1/14; CLI cross-check wiring 3/14. Blank-template defect reproduced in two halves, not end to end: cloud half executed (cloud 8f574b9 isValidPackageCategory('starter') = false, the route's 400 at package-publish.ts:896); CLI half read at base (publish sent m.category verbatim; create-objectstack copied it into every project).",
     "gates": "dispatch-gates --ran on 2f243e0d68: 119 derived, 116 run exit 0, 3 NOT-MEASURED exit 3 (check:skill-examples — client-react has no .d.ts; check:dual-build-cjs-loads — packages outside the closure unbuilt; check:type-check-debt — its locked closure build never got the shared lock), 0 UNRUN. Fixed on the way: check:llms-txt, check:platform-checklist (template_manifest waiver), check:test-source-alias (connector configs alias @objectstack/spec/kernel). spec check:generated current after --fix; check:liveness 0; check:spec-parsed-alias 0; check-changeset-no-major level axis 0 against the PR body (exit 1 with the claim's lines: none-baseline-moved); check-adr-0087-registration 0; check-platform-checklist --changed-from origin/main 0. PR check-runs on 2f243e0d68: 58 success, 9 skipped, 0 failure — Lint & Repo Gates success, TypeScript Type Check success (Type Check · workspace/debt ledger/source gates/consumer gates success), Test Core success (8/8 shards), Build Core success, Governed Surface Queue Guard success, Type Check UI success, Test UI success, Build & E2E UI success, Live E2E UI success, Lint UI skipped, Check Changeset success. The previous head e71622a128 had Test Core (2/8)/(4/8) and Type Check · workspace red from main's new scenario exporter and the module header; both fixed.",
     "dogfood_battery": "e71622a128 · os-verify-lock VERDICT command-exit 0 (held 30m48s) · closure build exit 0 · pnpm --filter @objectstack/dogfood test exit 0 · Test Files 425 passed | 3 skipped (428) · Tests 3543 passed | 41 skipped (3584) · no failure. Not re-run on 2f243e0d68: the delta is a merge of main plus the scenario exporter conversion, a doc-comment demotion and a regenerated reference page; CI's Dogfood Regression Gate is opt-in and skipped on this PR.",
     "mcp_calls": "0",
     "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches, run 42239679609, PR #30980; one earlier attempt refused by write-pace before sending, nothing written); this os-dev-report comment via post-stamped.mjs (POST /repos/objectstack-ai/objectstack/issues/30904/comments). git push is not a REST write.",
     "open_questions": [
      {
       "question": "Declaration lines: the claim and dispatch said Clause-②: yes (widening) / Metadata-protocol: none; Check Changeset refuses none on this diff (none-baseline-moved: authorable-surface/marketplace.json moved), and AGENTS.md defines a tombstoned key as narrowed. Which pair stands?",
       "options": [
        "A keep what the PR carries: Clause-②: yes (narrowing) + Metadata-protocol: narrowed, spec and cli minor (Check Changeset green)",
        "B restore the claim's pair: edit the PR body and regrade the changeset to patch; Check Changeset stays red"
       ],
       "recommendation": "A, because the gate and AGENTS.md agree that a tombstoned authorable key is narrowed, and the (narrowing) arm is what makes the break visible to the contract review."
      },
      {
       "question": "Release order with cloud: at cloud 8f574b9 the publish route drops the four facets and marketplace-policy.ts still requires category at submission, so a CLI released from this PR before cloud#3661 deploys cannot satisfy submission for a package without a stored category.",
       "options": [
        "A hold the next CLI release until cloud#3661 (accept facets, require a business function instead of category) is deployed",
        "B release as is and accept a window where new marketplace submissions are refused"
       ],
       "recommendation": "A, because the window refuses every new marketplace submission and the fix is entirely on the cloud side already filed."
      }
     ],
     "security_findings": "none",
     "out_of_scope_findings": [
      "carrier: none · noted, not filed — enrolling objectstack.manifest.json in the liveness ledger measured name, specVersion, skills and scaffold with no reader of the file's value, and visibility/publisher/isStarter read only to be warned about and never sent; recorded dead in packages/spec/liveness/template_manifest.json, not retired (dedupe: template manifest dead keys, objectstack.manifest.json specVersion skills scaffold)",
      "carrier: objectstack-ai/cloud#3661 · noted, not filed — cloud publish route drops business_functions/industries/solution_type/maturity and marketplace-policy.ts requires category; ruling §6 already assigns both",
      "carrier: hotcrm lane · noted, not filed — hotcrm objectstack.manifest.json still carries category: crm and will be refused by the next CLI (ruling §6 item 8)",
      "carrier: none · noted, not filed — docs/design/marketplace-publishing.md shows os marketplace search --category, a design-record example of a command this repo does not ship"
     ],
     "ui_card_needs": [
      "ui/packages/app-shell/src/console/marketplace/MarketplacePage.tsx: replace the single category chip row (state at :363, chips built from it.category at :555, client filter at :571, chips at :710-722) with facet filters — businessFunctions (multi-select, OR), industries (hidden while no loaded listing carries one), solutionType (tab or segment); AND across facets; counts per value",
      "MarketplacePage.tsx card badge (:965 pkg.category): show the primary business function businessFunctions[0], plus a Beta badge when maturity === 'beta'",
      "ui/packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx (:1425-1426 categoryLabel, :1542 badge): show every business function, the industries, the solution type and the Beta badge",
      "ui/packages/i18n/src/namespaces/marketplace/*.ts (9 locales; en.ts :223 category block = a copy of the retired spec enum): delete marketplace.category.*; add marketplace.businessFunction.* (11), marketplace.industry.* (10), marketplace.solutionType.* (3), marketplace.maturity.beta, keyed by iterating MarketplaceBusinessFunctionSchema / MarketplaceIndustrySchema / MarketplaceSolutionTypeSchema .options from @objectstack/spec/marketplace so no second list exists, with a parity test",
      "data source: the cloud catalog rows' snake_case business_functions / industries / solution_type / maturity once cloud#3661 lands; until then the fields are absent and every facet must hide rather than render raw values (drop the raw-value fallback at :560-566)",
      "related gap (ruling §6 ui item 4): listMarketplacePackages({ limit: 100 }) at :407 filters client-side; facets at hundreds of templates need server-side filtering and pagination — same card or a sibling"
     ],
     "deviations": "Declaration lines changed from the claim's Clause-②: yes (widening) / Metadata-protocol: none to yes (narrowing) / narrowed, with spec and cli at minor, because Check Changeset refuses none over the moved authorable-surface shard and AGENTS.md (which outranks the dispatch) defines a tombstone as narrowed; the claim's pair is quoted in the PR body for the seat to overrule. Blank template: suggested route set solutionType template; I declared no facet instead (reason in summary). A full dogfood re-run on the final head was not made (reading is on e71622a128)."
    }
  8. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2

    Ruling: objectstack#30904 release order · maintainer 「等 cloud 上线再发 CLI(推荐)」 · 2026-11-06T17:23Z

    Provenance: the maintainer, in person, in the repo:cloud#3 seat session session_01Q4EjjRhUZjdy8VXs6Kkur2, on open question 2 of os-dev report 6690814417.

    What binds. The next CLI, which publishes the four facets and refuses category, is not released before objectstack-ai/cloud#3661 is deployed. Today the cloud publish route drops the facets and marketplace-policy.ts requires category. Sequence:

    1. PR feat(spec)!: one marketplace taxonomy — business functions, industries, a solution type and maturity replace the single category #30980 lands on main after the seat's contract review. Landing is not held.
    2. The cloud pin moves past it; cloud pins main commits, so no release is needed. cloud#3661 is dispatched at once.
    3. The maintainer deploys cloud#3661.
    4. Only then is objectstack's chore: version packages release PR merged. The maintainer holds it until step 3.

    Seat correction to claim 6684237536's declaration lines (os-dev open question 1, the seat's call): Clause-②: yes (narrowing) + Metadata-protocol: narrowed, with spec and cli at minor. Check Changeset refuses none over the moved authorable-surface/marketplace.json, and AGENTS.md defines a tombstoned authorable key as narrowed. The contract review runs on the narrowing face.

    Console part filed as objectstack#31011 (domain:ui, pm:blocked on this card).

  9. removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Nov 6, 2026
  10. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: state · needs-user-decision removed · 2026-11-06T17:56Z

    ⛔ Not a claim.


    Generated by Claude Code

  11. added
    pm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-set
    on Nov 6, 2026
  12. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2
    os-dev-report

    {
     "issue": 30904,
     "status": "done",
     "branch": "claude/issue-30904-marketplace-taxonomy",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/30980",
     "session": "session_01Q4EjjRhUZjdy8VXs6Kkur2",
     "premise_still_valid": true,
     "summary": "REWORK round: PR #30980 was `dirty` against objectstack main 69e133ea0d. Merged origin/main with git merge (no rebase, no force-push): merge commit 95b6e07094, which is also the final head — nothing was committed after it. Two conflicts, both from main's scenario-library work. packages/qa/scenarios/src/schema.ts: kept main's new ROW_SCOPE_TAG and kept this PR's BusinessFunctionsSchema in place of main's re-carried SCENARIO_CATEGORIES/CategorySchema. docs/qa/scenarios/crm_account_with_contacts.json: kept main's added capability `access-security.row-scope` and this PR's businessFunctions [\"sales\"] in place of category: crm. Main brought no new user of category, SCENARIO_CATEGORIES, CategorySchema, MarketplaceCategorySchema or PackageCategorySchema (scanned packages/qa, packages/cli, packages/create-objectstack, packages/apps and every objectstack.manifest.json; the remaining `c.category` hits are migration-plan change categories, unrelated). All nine scenario cards still carry businessFunctions and no category. The diff itself is unchanged from the first report: the taxonomy defined once in packages/spec/src/marketplace/taxonomy.zod.ts; both category schemas retired under ADR-0087 (tombstones, no D2 conversion); os package publish refuses category and forwards and cross-checks the facets; the manifests, the scenario cards and the gallery exporter re-tagged; template_manifest enrolled in the liveness ledger. Seat ruling Q1 = A: the PR keeps `Clause-②: yes (narrowing)` + `Metadata-protocol: narrowed`, with spec and cli at minor.",
     "tests": "On merged head 95b6e07094, one locked pass (os-verify-lock VERDICT command-exit 0): turbo closure build exit 0. spec check:generated --fix exit 0 and regenerated nothing (tree clean after). spec test exit 0, Test Files 820 passed (820), Tests 25888 passed | 1 todo. spec repo scripts/file-description.test.ts exit 0. spec typecheck 0. cli unit exit 0, Test Files 396 passed (396), Tests 5571 passed (5571). cli typecheck 0. create-objectstack test/typecheck 0/0. scenarios test exit 0, Test Files 23 passed (23), Tests 341 passed (341); scenarios typecheck 0. connector-stripe/github/docusign test 0/0/0. Ablations are unchanged from the first round (the rule code did not move in this merge): ten, one per rule, each turned its rule's test red and was restored with git diff HEAD empty.",
     "gates": "dispatch-gates --ran on 95b6e07094: 119 derived, 116 run with exit 0, 3 NOT-MEASURED with exit 3, 0 UNRUN. The three not measured: check:skill-examples (client-react has no .d.ts); check:dual-build-cjs-loads (packages outside the closure unbuilt); check:i18n-coverage (`@objectstack/connector-slack` has no build output in this worktree). check:type-check-debt ran this time, exit 0. The checks the seat named: pnpm --filter @objectstack/spec check:generated 0; check:liveness 0; check-adr-0087-registration --base origin/main 0 (self-test 0); check-changeset-no-major --base origin/main 0; check-empty-changeset 0. The level axis against the live PR body passes: `✓ LEVEL AXIS: yes (narrowing) + narrowed` grading a moved package at minor. PR check-runs on 95b6e07094: 58 success, 9 skipped, 0 failure. Lint & Repo Gates success; TypeScript Type Check success; Test Core success; Build Core success; Governed Surface Queue Guard success; Type Check UI success; Test UI success; Build & E2E UI success; Live E2E UI success; Lint UI skipped; Check Changeset success. GitHub reports mergeable true, mergeable_state clean.",
     "dogfood_battery": "e71622a128 (first round) · os-verify-lock VERDICT command-exit 0 · Test Files 425 passed | 3 skipped (428) · Tests 3543 passed | 41 skipped (3584) · no failure. Not re-run on 95b6e07094: since then the diff gained the scenario-exporter conversion, a doc-comment demotion, a regenerated reference page and two merges of main. The merge resolution touched only the scenario library (outside the dogfood radius). CI's Dogfood Regression Gate is opt-in and skipped.",
     "mcp_calls": "0",
     "api_writes": "1 this round: this os-dev-report comment via post-stamped.mjs (POST /repos/objectstack-ai/objectstack/issues/30904/comments). git push is not a REST write. The first round's 2 writes were the relay pr_create (#30980) and comment 6690814417.",
     "open_questions": [],
     "security_findings": "none",
     "out_of_scope_findings": [
      "carrier: none · noted, not filed — enrolling objectstack.manifest.json in the liveness ledger measured name, specVersion, skills and scaffold with no reader of the file's value, and visibility/publisher/isStarter read only to be warned about and never sent; recorded dead in packages/spec/liveness/template_manifest.json, not retired (dedupe: template manifest dead keys, objectstack.manifest.json specVersion skills scaffold)",
      "carrier: objectstack-ai/cloud#3661 · noted, not filed — cloud publish route drops business_functions/industries/solution_type/maturity and marketplace-policy.ts requires category; ruling §6 already assigns both",
      "carrier: hotcrm lane · noted, not filed — hotcrm objectstack.manifest.json still carries category: crm and will be refused by the next CLI (ruling §6 item 8)",
      "carrier: none · noted, not filed — docs/design/marketplace-publishing.md shows os marketplace search --category, a design-record example of a command this repo does not ship"
     ],
     "ui_card_needs": [
      "ui/packages/app-shell/src/console/marketplace/MarketplacePage.tsx: replace the single category chip row (state at :363, chips built from it.category at :555, client filter at :571, chips at :710-722) with facet filters — businessFunctions (multi-select, OR), industries (hidden while no loaded listing carries one), solutionType (tab or segment); AND across facets; counts per value",
      "MarketplacePage.tsx card badge (:965 pkg.category): show the primary business function businessFunctions[0], plus a Beta badge when maturity === 'beta'",
      "ui/packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx (:1425-1426 categoryLabel, :1542 badge): show every business function, the industries, the solution type and the Beta badge",
      "ui/packages/i18n/src/namespaces/marketplace/*.ts (9 locales; en.ts :223 category block = a copy of the retired spec enum): delete marketplace.category.*; add marketplace.businessFunction.* (11), marketplace.industry.* (10), marketplace.solutionType.* (3), marketplace.maturity.beta, keyed by iterating MarketplaceBusinessFunctionSchema / MarketplaceIndustrySchema / MarketplaceSolutionTypeSchema .options from @objectstack/spec/marketplace so no second list exists, with a parity test",
      "data source: the cloud catalog rows' snake_case business_functions / industries / solution_type / maturity once cloud#3661 lands; until then the fields are absent and every facet must hide rather than render raw values (drop the raw-value fallback at :560-566)",
      "related gap (ruling §6 ui item 4): listMarketplacePackages({ limit: 100 }) at :407 filters client-side; facets at hundreds of templates need server-side filtering and pagination — same card or a sibling"
     ],
     "deviations": "None new. The declaration pair is now the seat's ruled pair (Q1 = A); Q2, the release order with cloud#3661, is with the maintainer and changes nothing in this diff. The worktree was recreated from the pushed branch at 2f243e0d68 for this round (it had been removed after the first report). Merge commit 95b6e07094 is the final head; I will push nothing further while the contract review runs.",
     "merge_commit": "95b6e07094",
     "final_head": "95b6e07094"
    }
  13. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2

    LANDED · 2026-11-06T19:19Z

    reading value
    PR #30980 merged 2026-11-06T19:16Z through the merge queue, merge commit 468e0c16
    this card closed completed by Fixes #30904

    State: pm:dispatched and pm:blocking removed. The taxonomy is defined once in packages/spec/src/marketplace/taxonomy.zod.ts; category is retired under ADR-0087. Next, per the release-order ruling: the cloud pin moves past 468e0c16 and objectstack-ai/cloud#3661 is dispatched now; the maintainer holds chore: version packages until #3661 deploys. Follow-ups: #31011 (console facets), #31035 and the uniqueItems card (spec), plus the hotcrm manifest (with the maintainer).

  14. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    pm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-set
    on Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:specDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p1High: required for production / M2repo:cloudCard routed whole to the cloud PM: needs cloud code, a producer or a fact (cloud owns it to closure)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions