Repository navigation
Commit 8f4e8fb
feat(cloud-connection)!: the marketplace catalog list filters by taxonomy facets on the server, and retires ?category= (#31234)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XHJo3uQpXK1vctgUQNzvpkw)_
Fixes #31173
Clause-②: no (narrowing) — the facets are declared by the published
`MarketplaceSearchRequestSchema`; refusing the retired `?category=`
filter, and a facet the catalog cannot answer, narrows what the proxy
honours
Metadata-protocol: none
Skills-docs: none — no page under `skills/**` or `content/docs/**`
describes the query of `GET /api/v1/marketplace/packages` (a `git grep`
for `category=` and `marketplace/packages` there finds none); the
generated `references/marketplace/marketplace.mdx` already lists the
facet keys from the schema, and nothing in it moves
## 维护者速读
- 改了什么:市场目录列表 `GET /api/v1/marketplace/packages`
现在在服务端按职能(`businessFunctions`)、行业(`industries`)、解决方案类型(`solutionTypes`)和成熟度(`maturity`)筛选,同一维度内取「或」、维度之间取「与」,再在服务端分页;旧的
`?category=` 筛选删除,带它的请求回 400,并附上规格里退役该键时给出的改法(改用哪个维度)。
- 为什么改:控制台要按三个维度筛几百个模板,只拉 100 条在前端筛不动(#31011 第 5 项、#30904 裁决
§6);`?category=` 在 cloud#3661 去掉该列后会静默失效。
- 风险与回滚:只改 `@objectstack/cloud-connection` 一个文件加一个测试,patch
级;`?category=`、拼错的维度值、重复的维度键、空维度现在回 400
`VALIDATION_ERROR`,仓内没有任何调用方发这些参数;回滚即还原本 PR。
- 先看:`packages/cloud-connection/src/marketplace-proxy-plugin.ts` 的
`readCatalogListQuery`(拒绝规则,含 `category`)与
`catalogRowMatches`(或/与、缺省值),再看 handler 里在 R2 与 cloud 两条路之前读查询的那一段。
- 两分钟验证:`pnpm --filter @objectstack/cloud-connection exec vitest run
src/marketplace-proxy-browse-only.test.ts`,预期 16 个用例全绿。
- 需要你做的:无,席位落地。
## What changes
`packages/cloud-connection/src/marketplace-proxy-plugin.ts`, the catalog
list door `GET /api/v1/marketplace/packages` (and `HEAD`):
- **The facets, the spec's spelling.** `businessFunctions`,
`industries`, `solutionTypes` (plural, each a list) and `maturity` (one
value), the keys `MarketplaceSearchRequestSchema` declares in
`@objectstack/spec/marketplace`. A facet is ONE comma-separated value,
`?businessFunctions=sales,marketing`, the spelling the platform's other
list-valued query parameters take (`names` on the `/meta` list, `fields`
/ `searchFields` on the data routes); each value trimmed, blanks
dropped, repeats folded. The allowed values are read from the spec
schemas' `.options`, never copied.
- **OR within, AND across.** A row passes a facet when it carries any
selected value; facets AND with each other, with `maturity` and with
`q`. Rows are the catalog's own (snake_case `business_functions`,
`industries`, `solution_type`, `maturity`, the columns
objectstack-ai/cloud#3661 adds and #31011's console reads). A row with
no `solution_type` counts as the spec's default `app`
(`DEFAULT_SOLUTION_TYPE`) and one with no `maturity` as `stable`, the
spec's reading of an absent key and the console's
(`effectiveSolutionType` on PR #31187). A row without the facet columns
passes no business-function or industry filter, so until the cloud
serves the taxonomy those two facets return nothing, as #31011 plans.
- **Paged on the server.** On the public snapshot path the proxy filters
the whole snapshot, then `limit` / `offset` page it (`limit` 1–100,
default 50, as before) and `total` counts every match.
- **`?category=` retired, and refused.** The `category` branch is gone.
A `category` parameter is refused `400 VALIDATION_ERROR` with the spec's
own prescription,
`retiredCategoryGuidance('MarketplaceSearchRequest.category')` from
`@objectstack/spec/marketplace`: the text a parse of
`MarketplaceSearchRequestSchema` gives the same retired key (migration
entry `18.marketplace-category-retired`), naming the facets to send
instead. No second spelling is kept, and a caller still filtering by
category is told so instead of receiving the whole catalog.
- **Refused, before either path is asked**, `400` with the proxy's `{
success: false, error: { code: 'VALIDATION_ERROR', message } }`:
`category` (above), a value the spec does not declare for the facet
(`?businessFunctions=crm`, `?maturity=preview`), a facet or `maturity`
supplied twice (`?industries=retail&industries=logistics`, the shared
`repeatedQueryParamMessage`), and a facet naming no value
(`?solutionTypes=`). The read sits in the handler ahead of the R2 fast
path and the cloud path, so the answer does not depend on which one
serves the list.
## Tests
The catalog pins live in the proxy's existing harness,
`packages/cloud-connection/src/marketplace-proxy-browse-only.test.ts`
(its `captureHandler` and `fakeCtx`, plus a stubbed `fetch`), 13 new
beside its 3: OR within a facet, AND across, the `app` / `stable`
defaults, rows without the taxonomy, folded values, `q` plus server
paging (`total`, `limit`, `offset`), `?category=` refused with the spec
prescription alone, beside a real filter and on the cloud-only
configuration, each facet refusal on the R2 configuration and on the
cloud-only one with no fetch made, and the cloud path forwarding the
facet query verbatim.
<details><summary>Suites and ablations</summary>
- `pnpm --filter @objectstack/cloud-connection test`: on head `868cfd7e`
(in the dogfood chain, under the lock), Test Files 73 passed (73), Tests
952 passed (952). `typecheck` (`tsc --noEmit` plus `tsconfig.test.json`,
which compiles the test): exit 0 on `868cfd7e`.
- Ablations through `scripts/ablation-replace.mjs` on the committed
implementation (`868cfd7e`), each anchor hit once, each restore proven
by blob == HEAD and an empty `git diff HEAD`. The suite imports the
plugin from `src/` relatively, so no build sits on the path.
- A1, the `category` refusal disabled (red-first for the retargeted
pins): both `?category=` cases red, `expected undefined to be 400`, 2
failed / 14 passed.
- A2, the undeclared-value refusal disabled: 2 failed / 14.
- A3, the repeated-key refusal disabled: 1 failed / 15.
- A4, the empty-facet refusal disabled: 1 failed / 15.
- A5, OR within a facet turned into every-of: 3 failed / 13.
</details>
## Acceptance notes
- **Card vs spec spelling.** The card writes `solutionType`; the spec's
request schema declares `solutionTypes` (a list, like the other two
facets). The door implements the declared spelling only;
`?solutionType=` is ignored like any parameter the door does not read
(the proxy refused no query parameter before this change, and `category`
is the one retired key it now refuses). `solutionType` (singular) is the
listing's own key, not a filter.
- **The cloud path.** With no `OS_MARKETPLACE_PUBLIC_BASE_URL` (the
default: `resolveMarketplacePublicBaseUrl` returns empty when it is
unset) every list request takes the cloud path, which forwards `pathname
+ search` to the control plane unchanged once the query passes the door
(pinned by the last test). Whether the cloud filters by these names is
the cloud's: NOT MEASURED here, reason: objectstack-ai/cloud is not
readable from this session. Until it does, the console's switch-over
sees server filtering only where the public snapshot path is configured.
- **The request schema is not this route's whole query.**
`MarketplaceSearchRequestSchema` also declares `query` / `page` /
`pageSize`, while this door (and the console, and the cloud route it
proxies) speak `q` / `limit` / `offset`, and the list answers the
catalog's row shape, not `MarketplaceSearchResponseSchema`. This PR
takes only the facet keys and values from the schema and leaves the
paging names as they were.
- **Kept as before.** `?limit=abc` still reads `NaN` and answers an
empty page on the snapshot path (`limit: null` on the wire): unchanged
here, not in this card. The console switch-over to the server parameters
is the `domain:ui` follow-up the card names.
## Verification
Head `868cfd7e` (rework round after contract review 6711888512;
`origin/main` merged once at `248a01c7`). Gate union derived by `node
scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (60 families), run after a full `pnpm turbo
run build --concurrency=2` (VERDICT command-exit 0), each exit code
captured before any pipe:
- **60 run, exit 0 each.** `--ran`: "60 derived famil(ies) accounted for
— 60 run, 0 NOT-MEASURED". `check-plugin-teardown-shape --self-test`
needed `git fetch --depth=1 origin 621a487` for its fixture on this
shallow clone.
- **Full dogfood battery** (the `(narrowing)` arm), `pnpm --filter
@objectstack/dogfood test` under the lock on `868cfd7e`: VERDICT
command-exit 0, Test Files 435 passed, 3 skipped (438), Tests 3609
passed, 41 skipped (3650).
- `pnpm lint` scoped: `eslint --no-inline-config` over the two touched
source files, exit 0.
- CI families are read from this PR's check-runs, not re-run here.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5kRdiXKLNW3aoF5Zhj)_
<!-- signed: domain:server#5 · os-project-manager ·
session_01FEkQ5o8PE7ZtVjcor51wBZ -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent fd3ab0a commit 8f4e8fb
3 files changed
Lines changed: 418 additions & 31 deletions
File tree
- .changeset
- packages/cloud-connection/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
9 | 16 | | |
10 | 17 | | |
11 | | - | |
| 18 | + | |
| 19 | + | |
12 | 20 | | |
13 | 21 | | |
14 | 22 | | |
| |||
78 | 86 | | |
79 | 87 | | |
80 | 88 | | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
0 commit comments