You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 8cf1fd0
Browse filesBrowse the repository at this point in the historyBrowse files
fix(spec)!: spell the seven multi-word taxonomy values as identifiers so a select can carry them (#31078)
Fixes#31066
Clause-②: yes (narrowing) — seven accepted facet values change spelling,
inside the marketplace-taxonomy narrowing of the same release; no
published version carried the hyphenated spelling
Metadata-protocol: narrowed
Skills-docs: none — no `skills/**` file names a facet value; the
hand-written handbook example and the generated reference pages carry
the identifier spellings in this diff
## 维护者速读
- **改了什么**:市场分类里 7 个带连字符的值改成下划线写法(customer-service →
customer_service、supply-chain → supply_chain,行业 5
个同理),顺序和数量上限不变;另加一条守卫测试:分类里任何值都必须能当下拉选项的值。
- **为什么改**:按你的裁决「spec 改成下划线(推荐)」。下拉选项的值只允许小写字母、数字、下划线和点,带连字符的值进不了
select,cloud#3661 就没法把「职能」「行业」做成带选项的下拉,控制台 #31011 也按选项取标签。**下一版 CLI
发版要等这张卡和 objectstack-ai/cloud#3661 都完成。**
- **风险与回滚**:低。这 7 个值是 #30980 引入的,还没有任何版本发布过(spec 18.3.0
在它合并前就已发布),所以没有已发布的数据或包会用到旧写法;回滚就是 revert 本 PR。
- **先看**:① `packages/spec/src/marketplace/taxonomy.zod.ts`(两个枚举里的 7 个值和
`LEGACY_CATEGORY_MAPPING` 的 erp 一行);②
`packages/spec/src/marketplace/taxonomy.test.ts` 末尾新增的守卫测试;③
`.changeset/30904-marketplace-taxonomy.md` 里被改正的一句(未发布的发布说明,下面
Acceptance notes 有说明)。
- **两分钟验证**:`pnpm --filter @objectstack/spec exec vitest run
src/marketplace/taxonomy.test.ts`,预期 41 个测试全绿;把任一值改回连字符再跑,守卫测试变红。
- **需要你做的**:无,席位落地。
## What changes
- `packages/spec/src/marketplace/taxonomy.zod.ts`:
`MarketplaceBusinessFunctionSchema` respells `customer-service` →
`customer_service` and `supply-chain` → `supply_chain`;
`MarketplaceIndustrySchema` respells `trade-distribution`,
`professional-services`, `financial-services`, `public-sector` and
`real-estate-construction` with underscores. Order and arity (1-3 / 0-3)
unchanged. `LEGACY_CATEGORY_MAPPING.erp` now maps to `['finance',
'supply_chain']`; the legacy category KEYS (`developer-tools`,
`ui-theme`) are old values written in the wild and stay as they are. The
module doc now states that every value is a `SystemIdentifierSchema`
identifier.
- Guard test (`taxonomy.test.ts`, block `id`): walks every export of
`taxonomy.zod.ts` that has string `options` (so an enum added later is
judged without being listed), floors the walk at the four known enums
and 26 values, and parses every value as `SystemIdentifierSchema` and as
a full `SelectOptionSchema` `{ label, value }`.
- Carriers: `docs/qa/scenarios/service_desk.json` and
`requisition_to_receipt.json`;
`packages/cli/test/package-publish-taxonomy.test.ts` (the refusal that
lists business functions, and the wire-name test);
`content/docs/deployment/marketplace-handbook.mdx`; regenerated
`content/docs/references/marketplace/*.mdx` (`check:generated --fix`).
- Nothing to change, checked: the three connector manifests carry
`collaboration`/`legal`, `it` and `finance`; the liveness rows
(`packages/spec/liveness/template_manifest.json`) name no value; the
scenario schema and the CLI publish path read the spec enums
(`MarketplaceBusinessFunctionSchema.options`), so only their fixtures
and tests carried spellings. `git grep` for the seven hyphenated
spellings now hits only the new changeset's FROM/TO table, CHANGELOG
history and an unrelated `supply-chain` comment in `release.yml`.
- Changesets: new `.changeset/31066-taxonomy-identifiers.md`
(`@objectstack/spec: minor`, both declaration lines, FROM/TO table).
ADR-0087 disposition: `not-required (already-registered
marketplace-category-retired)` — reasoning under Acceptance notes.
## Tests
Readings in Verification; ablation on `b56f1fb013` (the guard block is
byte-identical on the merged head).
<details><summary>Suites</summary>
- `pnpm --filter @objectstack/spec test`: see Verification.
- `pnpm --filter @objectstack/cli exec vitest run --project unit`
(integration tier declared to CI; the diff touches no integration-tier
file or spawn entry).
- `pnpm --filter @objectstack/scenarios test`, the three connector
`test` scripts (each runs `objectstack validate` then vitest).
</details>
<details><summary>Ablation of the guard test</summary>
Through `scripts/ablation-replace.mjs` under the verify lock, on
committed head `b56f1fb013`: anchor `'real_estate_construction', //` x1
→ x0, replacement `'real-estate-construction', //` x0 → x1, blob
`c960cff330ba` → `8b9e00123016`. `vitest run
src/marketplace/taxonomy.test.ts`: **1 failed | 40 passed**, the failure
exactly the guard (`MarketplaceIndustrySchema: real-estate-construction:
expected false to be true`). Restore: blob after restore == HEAD blob
`c960cff330ba`, `git diff HEAD` empty. A value no other test names was
chosen so only the guard could turn red.
</details>
## Acceptance notes
- **DELIBERATE CORRECTION of a pending release note.**
`.changeset/30904-marketplace-taxonomy.md` (pending, from #30980,
unreleased) listed the business functions with the old spelling; this PR
made that sentence false, so it is corrected in place rather than
restored. Corrected sentence: "`businessFunctions` (1-3 distinct values,
the primary one first: `sales`, `marketing`, `customer_service`,
`finance`, `hr`, `supply_chain`, `administration`, `project`, `legal`,
`it`, `collaboration`)". `check-empty-changeset` stays red on that file
by design until the dispatching seat confirms the correction by its own
record.
- **ADR-0087: no new ledger entry.** The seven values were introduced by
#30980 (merged `468e0c16`, 2026-11-06T18:46Z);
`@objectstack/spec@18.3.0`, the latest published version, was published
2026-11-03T15:15Z, before that merge, and #30980's changeset is still
pending in `.changeset/`. So no published spec ever accepted the
hyphenated spelling, and no stored row or authored source made against a
release can hold it. `marketplace-category-retired` (present at the
merge base) is the upgrade path from a released spec — `category` to the
facets of `taxonomy.zod.ts` and `LEGACY_CATEGORY_MAPPING` — and names no
value, so it stays complete. A value-rename entry would project into the
upgrade guide a rename no released author can meet (ADR-0087's table
calls that false data in the ledger); enum values also have no
`retiredKey` surface. Disposition: `not-required (already-registered
marketplace-category-retired)`; `check-adr-0087-registration` passes.
- Consumers that follow: objectstack-ai/cloud#3661 (selects with options
and translations, after its pin moves past this merge) and #31011
(console labels keyed by `.options`). objectstack-ai/cloud#3689 pins
`90523379` and is unaffected until that follow-up.
- Merged `origin/main` (`3f61e4f8fb`) before opening: main had moved
`taxonomy.test.ts`, `publish.ts` and the template-manifest reference
page; the deferred page was regenerated from the merged tree
(`f7a05c8600`), and every reading below was retaken on that head.
## Verification
Head `f7a05c8600` for every line. Exit codes captured before any pipe;
suites under `scripts/pm/os-verify-lock.sh`.
<details><summary>Suites</summary>
- `pnpm --filter @objectstack/spec test` :: exit 0 — Test Files 820
passed (820), Tests 25920 passed, 1 todo
- `pnpm --filter @objectstack/spec typecheck` :: exit 0
- `pnpm --filter @objectstack/spec check:generated` :: exit 0 — All 14
generated artifacts are up to date
- `pnpm --filter @objectstack/cli exec vitest run --project unit` ::
exit 0 — Test Files 396 passed (396), Tests 5574 passed
- `pnpm --filter @objectstack/cli typecheck` :: exit 0
- `pnpm --filter @objectstack/scenarios test` :: exit 0 — 23 files, 341
tests; `typecheck` :: exit 0
- connector-app-docusign / -github / -stripe `test` (`objectstack
validate` + vitest) :: exit 0 — 28 / 14 / 20 tests
</details>
<details><summary>Derived gate families (dispatch-gates --commands,
104)</summary>
- 103 of 104 exit 0, among them `check-adr-0087-registration --base
origin/main` (1 declared-breaking changeset, each carrying a
disposition), `check-changeset-no-major --base origin/main`,
`check:liveness`, `check:api-surface`, `check:authorable-surface`,
`check:docs`, `check:nul-bytes`, `check:dual-build-cjs-loads`.
- `check-empty-changeset --base origin/main` :: exit 1 — the DELIBERATE
CORRECTION above, red by design.
- `dispatch-gates --ran`: 104 derived, 104 run, 0 NOT-MEASURED (derived
from recorded exit codes).
- Level axis driven offline with this body's declaration
(`check-changeset-no-major --event`): exit 0, `yes (narrowing)` +
`narrowed`, no moved package graded `patch`.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Q4EjjRhUZjdy8VXs6Kkur2)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: .changeset/30904-marketplace-taxonomy.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,7 +13,7 @@ Metadata-protocol: narrowed
13
13
14
14
**BREAKING** for a package source, publish request or listing that still writes `category`.
15
15
16
-
- **`@objectstack/spec`.** The marketplace taxonomy is defined once, in `@objectstack/spec/marketplace` (`taxonomy.zod.ts`): `businessFunctions` (1-3 distinct values, the primary one first: `sales`, `marketing`, `customer-service`, `finance`, `hr`, `supply-chain`, `administration`, `project`, `legal`, `it`, `collaboration`), `industries` (up to 3, optional; absent means cross-industry), `solutionType` (`app` | `template` | `connector`; absent means `app`) and `maturity` (`beta` | `stable`; absent means `stable`), on `PackageSchema`, `CreatePackageRequestSchema`, `UpdatePackageRequestSchema`, `TemplateManifestSchema` (`objectstack.manifest.json`) and `MarketplaceListingSchema`, where `businessFunctions` is required. A search filters by `businessFunctions` / `industries` / `solutionTypes` / `maturity` and reads one facet count list per facet. `explainSolutionTypeContradiction` is the publish cross-check (a `template` needs the `unmanaged` install mode, a `connector` a declared connector) and `LEGACY_CATEGORY_MAPPING` says what every old category value becomes.
16
+
- **`@objectstack/spec`.** The marketplace taxonomy is defined once, in `@objectstack/spec/marketplace` (`taxonomy.zod.ts`): `businessFunctions` (1-3 distinct values, the primary one first: `sales`, `marketing`, `customer_service`, `finance`, `hr`, `supply_chain`, `administration`, `project`, `legal`, `it`, `collaboration`), `industries` (up to 3, optional; absent means cross-industry), `solutionType` (`app` | `template` | `connector`; absent means `app`) and `maturity` (`beta` | `stable`; absent means `stable`), on `PackageSchema`, `CreatePackageRequestSchema`, `UpdatePackageRequestSchema`, `TemplateManifestSchema` (`objectstack.manifest.json`) and `MarketplaceListingSchema`, where `businessFunctions` is required. A search filters by `businessFunctions` / `industries` / `solutionTypes` / `maturity` and reads one facet count list per facet. `explainSolutionTypeContradiction` is the publish cross-check (a `template` needs the `unmanaged` install mode, a `connector` a declared connector) and `LEGACY_CATEGORY_MAPPING` says what every old category value becomes.
17
17
-**Retired.**`category` on all six schemas, and `MarketplaceSearchResponse.facets.categories`, are `retiredKey()` tombstones: `tsc` refuses them and a parse refuses them with the prescription. `MarketplaceCategorySchema` / `MarketplaceCategory` and `PackageCategorySchema` / `PackageCategory` are removed. `RETIRED_KEYS_BY_MAJOR[18]` lists the six keys, `RETIRED_DEFS_BY_MAJOR[18]` both defs, and the upgrade guide carries the semantic entry `marketplace-category-retired`. No ADR-0087 conversion: none of these shapes is a stack collection member, so `os migrate meta` has nothing to rewrite; a runtime boot never reads them.
18
18
-**`@objectstack/cli`.**`os package publish` refuses a `category` in `objectstack.manifest.json`, or the now-hidden `--category` flag, before any network call, naming the facets for the value it held. It judges each facet with the spec's schema, checks a declared `solutionType` against the built artifact, and sends the facets as `business_functions`, `industries`, `solution_type` and `maturity`; an absent facet is not sent.
19
19
-**`create-objectstack`.** The blank template no longer ships `category: "starter"`, which every project scaffolded from it inherited and which the hosted publish route refused.
fix(spec)!: the seven multi-word marketplace taxonomy values are spelled as identifiers (`customer_service`, `supply_chain`, …), so a select field can carry the facets as its options
6
+
7
+
Clause-②: yes (narrowing) — seven accepted facet values change spelling, inside the marketplace-taxonomy narrowing of the same release; no published version carried the hyphenated spelling
8
+
Metadata-protocol: narrowed
9
+
10
+
<!-- adr-0087: not-required (already-registered marketplace-category-retired) The respelled values were introduced by the same unreleased change that registered this entry: @objectstack/spec 18.3.0 was published before it merged and its changeset is still pending, so no published spec ever accepted the hyphenated spelling and no stored row or authored source made against a release can hold it. The entry's prescription (category to the facets of marketplace/taxonomy.zod.ts and LEGACY_CATEGORY_MAPPING) names no value and stays the complete upgrade path; a value-rename entry would project a rename no released author can meet. -->
11
+
12
+
`businessFunctions` and `industries` used to accept seven values with hyphens. A select option's value is a `SystemIdentifierSchema` (lowercase letters, digits, `_` and `.`), so no select field — the cloud catalog's facet columns, a console filter — could carry them. They are now spelled with underscores; order and arity are unchanged, and a guard test refuses any future taxonomy value that is not an identifier.
`LEGACY_CATEGORY_MAPPING` maps `erp` to `["finance", "supply_chain"]`, and `os package publish` lists the identifier spellings when it refuses a retired `category`.
Copy file name to clipboardExpand all lines: content/docs/references/marketplace/marketplace.mdx
+8-8Lines changed: 8 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -180,8 +180,8 @@ Public-facing package listing on the marketplace
180
180
|**tagline**|`string`| optional | Short tagline (max 120 chars) |
181
181
|**description**|`string`| optional | Full description (Markdown) |
182
182
|**category**|`never`| optional |[REMOVED]`MarketplaceListing.category` was removed in @objectstack/spec 18 (ADR-0087) — one category had to say both who a package is for and what it is, and the platform and the cloud catalog spelled it from lists that disagreed. Delete the key. Declare `businessFunctions` (1-3 business functions, the primary one first), optionally `industries` (up to 3; absent means cross-industry) and `solutionType` (`app`, `template` or `connector`; absent means `app`). `LEGACY_CATEGORY_MAPPING` in @objectstack/spec/marketplace gives the facets for every old category value. |
183
-
|**businessFunctions**|`Enum<'sales' \| 'marketing' \| 'customer-service' \| 'finance' \| 'hr' \| 'supply-chain' \| 'administration' \| 'project' \| 'legal' \| 'it' \| 'collaboration'>[]`| ✅ | Business functions the package serves: 1-3 distinct values, the first is primary |
184
-
|**industries**|`Enum<'manufacturing' \| 'trade-distribution' \| 'retail' \| 'logistics' \| 'professional-services' \| 'financial-services' \| 'healthcare' \| 'education' \| 'public-sector' \| 'real-estate-construction'>[]`| optional | Industries the package is built for: up to 3 distinct values; absent means cross-industry |
183
+
|**businessFunctions**|`Enum<'sales' \| 'marketing' \| 'customer_service' \| 'finance' \| 'hr' \| 'supply_chain' \| 'administration' \| 'project' \| 'legal' \| 'it' \| 'collaboration'>[]`| ✅ | Business functions the package serves: 1-3 distinct values, the first is primary |
184
+
|**industries**|`Enum<'manufacturing' \| 'trade_distribution' \| 'retail' \| 'logistics' \| 'professional_services' \| 'financial_services' \| 'healthcare' \| 'education' \| 'public_sector' \| 'real_estate_construction'>[]`| optional | Industries the package is built for: up to 3 distinct values; absent means cross-industry |
185
185
|**solutionType**|`Enum<'app' \| 'template' \| 'connector'>`| optional (default: `"app"`) | What a marketplace package is: a managed app, an unmanaged template, or a connector |
186
186
|**maturity**|`Enum<'beta' \| 'stable'>`| optional (default: `"stable"`) | Package maturity: beta shows a badge on the listing; absent means stable |
|**category**|`never`| optional |[REMOVED]`MarketplaceSearchRequest.category` was removed in @objectstack/spec 18 (ADR-0087) — one category had to say both who a package is for and what it is, and the platform and the cloud catalog spelled it from lists that disagreed. Delete the key. Declare `businessFunctions` (1-3 business functions, the primary one first), optionally `industries` (up to 3; absent means cross-industry) and `solutionType` (`app`, `template` or `connector`; absent means `app`). `LEGACY_CATEGORY_MAPPING` in @objectstack/spec/marketplace gives the facets for every old category value. |
249
-
|**businessFunctions**|`Enum<'sales' \| 'marketing' \| 'customer-service' \| 'finance' \| 'hr' \| 'supply-chain' \| 'administration' \| 'project' \| 'legal' \| 'it' \| 'collaboration'>[]`| optional | Filter: any of these business functions |
250
-
|**industries**|`Enum<'manufacturing' \| 'trade-distribution' \| 'retail' \| 'logistics' \| 'professional-services' \| 'financial-services' \| 'healthcare' \| 'education' \| 'public-sector' \| 'real-estate-construction'>[]`| optional | Filter: any of these industries |
249
+
|**businessFunctions**|`Enum<'sales' \| 'marketing' \| 'customer_service' \| 'finance' \| 'hr' \| 'supply_chain' \| 'administration' \| 'project' \| 'legal' \| 'it' \| 'collaboration'>[]`| optional | Filter: any of these business functions |
250
+
|**industries**|`Enum<'manufacturing' \| 'trade_distribution' \| 'retail' \| 'logistics' \| 'professional_services' \| 'financial_services' \| 'healthcare' \| 'education' \| 'public_sector' \| 'real_estate_construction'>[]`| optional | Filter: any of these industries |
251
251
|**solutionTypes**|`Enum<'app' \| 'template' \| 'connector'>[]`| optional | Filter: any of these solution types |
252
252
|**maturity**|`Enum<'beta' \| 'stable'>`| optional | Filter by maturity |
253
253
|**tags**|`string[]`| optional | Filter by tags |
@@ -291,8 +291,8 @@ Public-facing package listing on the marketplace
291
291
|**tagline**|`string`| optional | Short tagline (max 120 chars) |
292
292
|**description**|`string`| optional | Full description (Markdown) |
293
293
|**category**|`never`| optional |[REMOVED]`MarketplaceListing.category` was removed in @objectstack/spec 18 (ADR-0087) — one category had to say both who a package is for and what it is, and the platform and the cloud catalog spelled it from lists that disagreed. Delete the key. Declare `businessFunctions` (1-3 business functions, the primary one first), optionally `industries` (up to 3; absent means cross-industry) and `solutionType` (`app`, `template` or `connector`; absent means `app`). `LEGACY_CATEGORY_MAPPING` in @objectstack/spec/marketplace gives the facets for every old category value. |
294
-
|**businessFunctions**|`Enum<'sales' \| 'marketing' \| 'customer-service' \| 'finance' \| 'hr' \| 'supply-chain' \| …>[]`| ✅ | Business functions the package serves: 1-3 distinct values, the first is primary |
295
-
|**industries**|`Enum<'manufacturing' \| 'trade-distribution' \| 'retail' \| 'logistics' \| …>[]`| optional | Industries the package is built for: up to 3 distinct values; absent means cross-industry |
294
+
|**businessFunctions**|`Enum<'sales' \| 'marketing' \| 'customer_service' \| 'finance' \| 'hr' \| 'supply_chain' \| …>[]`| ✅ | Business functions the package serves: 1-3 distinct values, the first is primary |
295
+
|**industries**|`Enum<'manufacturing' \| 'trade_distribution' \| 'retail' \| 'logistics' \| …>[]`| optional | Industries the package is built for: up to 3 distinct values; absent means cross-industry |
296
296
|**solutionType**|`Enum<'app' \| 'template' \| 'connector'>`| optional (default: `"app"`) | What a marketplace package is: a managed app, an unmanaged template, or a connector |
297
297
|**maturity**|`Enum<'beta' \| 'stable'>`| optional (default: `"stable"`) | Package maturity: beta shows a badge on the listing; absent means stable |
298
298
|**tags**|`string[]`| optional | Search tags |
@@ -316,8 +316,8 @@ Public-facing package listing on the marketplace
316
316
| Property | Type | Required | Description |
317
317
| :--- | :--- | :--- | :--- |
318
318
|**categories**|`never`| optional |[REMOVED]`MarketplaceSearchResponse.facets.categories` was removed in @objectstack/spec 18 (ADR-0087) — one category had to say both who a package is for and what it is, and the platform and the cloud catalog spelled it from lists that disagreed. Delete the key. Declare `businessFunctions` (1-3 business functions, the primary one first), optionally `industries` (up to 3; absent means cross-industry) and `solutionType` (`app`, `template` or `connector`; absent means `app`). `LEGACY_CATEGORY_MAPPING` in @objectstack/spec/marketplace gives the facets for every old category value. |
0 commit comments