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 8ef4d30
Browse filesBrowse the repository at this point in the historyBrowse files
feat(scenarios): a card names its origin and a zh+en title; the gallery exports generic starter and delivery cards (#31136)
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiNjyvCdXYygFqntMNR7S2dW)_Fixes#30932
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:场景卡新增必填字段 `origin`(generic / customer-derived /
held-out),标题改为中英双语 `{ zh, en }`;九张公开卡全部标为 generic 并补上中文标题;画廊导出器只导出
origin 为 generic、tier 为 starter 或 delivery 的卡,index.json 的中英标题都从卡上取。
- 为什么改:画廊是获客主渠道,按 D1 裁决只能展示通用场景;此前卡上没有明确的"通用"标记,导出器一张卡也导不出,index.json
的中文标题一直是 null。
- 风险与回滚:私有包、不发布;私有 overlay 里的卡在补上 origin 和双语标题之前会被拒收,overlay 里标为 generic
的卡会被拒收;反过来,开放库里标为 customer-derived 或 held-out 的卡也会被拒收;私有 overlay
由其维护者迁移到新卡片形态;回滚即 revert 本 PR。
-
先看:`packages/qa/scenarios/src/export/generic.ts`(导出守卫)、`packages/qa/scenarios/src/loader.ts`(双向拒收:overlay
不收 generic,开放库不收
customer-derived/held-out)、`packages/qa/scenarios/src/schema.ts`(origin
与 title 字段)。
- 两分钟验证:`pnpm --filter @objectstack/scenarios exec vitest run
test/export/exporter.test.ts`,预期 36 个用例全部通过,其中 generic starter
导出成功,customer-derived、held-out 和 control 都被拒。
- 需要你做的:无,席位落地。
## What changes
- **`origin`, a required card field**
(`packages/qa/scenarios/src/schema.ts`): `generic | customer-derived |
held-out`, a closed set with a located refusal when missing. It is the
explicit marker the exporter reads; `provenance` stays prose for people.
- **The loader knows which library it loads, because the caller says
so** (`src/loader.ts`). The loader could not tell the overlay from the
open library: a card file carries nothing that says where it lives, and
the loader reads whatever directory it is handed. So `LoadOptions` gains
a required `library: 'open' | 'overlay'`, and the loader refuses a
`generic` card loaded as the `overlay`, and a `customer-derived` or
`held-out` card loaded as the `open` library (round 2), both at
`origin`. Both CLIs (`run-scenarios`, `export-gallery`) answer it with
`libraryOf(dir, repo)`: the repository's `docs/qa/scenarios` by real
path is `open`, and any other `--cards` directory is `overlay`.
- **`title` becomes `{ zh, en }`**, both required and non-blank, strict
(a third language is an unknown key; the retired one-string shape is
refused with the new shape named). Every reader picks a language
explicitly:
- the gallery `index.json`: `title.en` and `title.zh` from the card
(`manifest.ts` no longer allows `zh: null`);
- the app README the exporter writes: `title.en` as the heading,
`title.zh` under it;
- `transcript.md`: `title.en` (its body is English);
- the runner's `CardRun.title` (the run record): carries the `{ zh, en
}` object whole, as data; nothing renders it.
- The prompts, the scorecard and the runner's console output read no
title (checked: no reader there).
- **The exporter guard** (`src/export/generic.ts`): `NO_GENERIC_MARKER`
is replaced by `GALLERY_GUARD`, which passes a card only when `origin
=== 'generic'` AND `tier` is `starter` or `delivery` (the tier rule from
the epic ruling comment 6687463653 on #30349), and names every rule a
card fails. The option is renamed `isGeneric` to `guard`; the CLI still
offers no flag to pass another guard.
- **The nine open-library cards**: each gets `"origin": "generic"` and a
Chinese title. Every card's `provenance` says it was written from the
domain alone or derived mechanically from a golden corpus case; none
says it came from a customer, so none is `customer-derived`.
- **Docs**: `docs/qa/scenarios/README.md` (the format's reference) gains
an Origin section and the title shape, and its stale "the exporter reads
no tier" paragraph is replaced; `packages/qa/scenarios/README.md`'s
loader example passes `library`.
## Tests
- `test/refusals.test.ts`: no origin; an origin outside the set; the old
one-string title; no title; no `zh`; no `en`; an empty `en`; a blank
`zh`; a third language. A `generic` card loaded as the overlay is
refused at `origin` (directory and single file), with `customer-derived`
and `held-out` in the overlay as the green controls. Round 2: a
`customer-derived` and a `held-out` card loaded as the open library are
each refused at `origin`. `libraryOf` answers `open` for the
repository's directory, through a non-canonical spelling and through a
symlink, and `overlay` elsewhere.
- `test/library.test.ts`: every open card is `generic` and carries a
`zh` title with CJK text that is not the English one.
- `test/export/exporter.test.ts`, on the recorded crm run
(`RecordingDriver` over a scripted double, no model): with the default
guard a generic `starter` and a generic `delivery` card export, and the
index carries both titles; `customer-derived` and `held-out` (starter
and delivery) and `control` are each refused with an empty index, and
each refusal's control leg exports the same run once the guard lets it
through. Over the real library the guard passes the six starter cards
and refuses the three control cards for their tier. The CLI end to end
refuses the crm card for its tier, and a `--cards` directory outside the
repository is loaded as the overlay, where the loader refuses the
generic card before the export.
- Ablations (each mutation landed and was restored by
`scripts/ablation-replace.mjs`, blob equal to HEAD after restore): the
loader's overlay rule disabled turns 2 refusal tests red; the
open-library rule (round 2) disabled turns its test red (1 failed, 87
passed); the guard's origin rule disabled turns 4 exporter tests red;
the guard's tier rule disabled turns 4 exporter tests red.
## Acceptance notes
- **Consequence for the private overlay.** `origin` and the `{ zh, en }`
title are required, so every overlay card is refused until it gains
both, and an overlay card marked `generic` is refused. The overlay lives
outside this repository; nothing here could migrate it.
- **Both directions (round 2, seat ruling Q1 A):** a `customer-derived`
or `held-out` card found in the OPEN library is refused too, so a
non-generic card cannot sit in the public tree. The overlay owner
migrates its cards to the new shape (Q2 A, one line in
`packages/qa/scenarios/README.md`), and the loader keeps no lenient
conversion. `libraryOf` is unchanged (Q3 A).
- `skip-changeset`: `packages/qa/scenarios` is `private: true` and
`docs/qa/scenarios` ships in no package, so nothing published moves.
- Out of scope, untouched: the runner's `owner_id` and seed logic, and
`personas[].record`.
## Verification
Head: see the report comment on #30932 for the head sha the gates were
run on.
<details><summary>Package suite and typecheck</summary>
- `pnpm --filter @objectstack/scenarios build && pnpm --filter
@objectstack/scenarios test && pnpm --filter @objectstack/scenarios
typecheck` (after the merge of `origin/main`): Test Files 23 passed
(23), Tests 454 passed (454); `tsc --noEmit` exit 0, its program
covering the 24 files under `test/`.
</details>
<details><summary>Derived gates</summary>
- `node scripts/pm/dispatch-gates.mjs --commands` derives 54 commands
for this diff; every one was run on the final head, each recorded as
`CMD :: exit N`, and reconciled with `--ran`. The list and the exit
codes are in the report comment on #30932.
</details>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- signed: domain:devx#2 · marchtian ·
session_017ihL2ebbZkr2QAEG1x7UC8 -->
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/qa/scenarios/asset_lifecycle.json
+2-1Lines changed: 2 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,8 @@
3
3
"id": "asset_lifecycle",
4
4
"code": "asset",
5
5
"tier": "starter",
6
-
"title": "Asset lifecycle: register, checkout and return, repairs, monthly depreciation and a scrap approval",
6
+
"origin": "generic",
7
+
"title": { "zh": "固定资产全生命周期:资产登记、领用与归还、维修、按月折旧和报废审批", "en": "Asset lifecycle: register, checkout and return, repairs, monthly depreciation and a scrap approval" },
7
8
"provenance": "Mid-tier scenario 资产全生命周期 (asset lifecycle) of the open library, written for this card from the domain alone: generic names, equipment and amounts, no customer-derived content. The seed follows the arithmetic rule in `$comment`, so each truth can be worked out by hand.",
"provenance": "Golden case `crm_account_with_contacts` of packages/cli/src/lint/corpus.ts; `expect` derived mechanically from its fixture (every object; minFieldsPerObject = the smallest object's field count; one selectFields row per select field with minOptions = its option count; one lookups row per lookup or master_detail field).",
7
8
"prompt": {
8
9
"short": {"en": "Build a simple CRM with accounts and contacts."},
Copy file name to clipboardExpand all lines: docs/qa/scenarios/invoice_with_line_items.json
+2-1Lines changed: 2 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,8 @@
2
2
"id": "invoice_with_line_items",
3
3
"code": "invoice",
4
4
"tier": "control",
5
-
"title": "Invoicing: invoices with inline line items and a summed total",
5
+
"origin": "generic",
6
+
"title": { "zh": "发票管理:发票随单录入明细行,自动汇总金额", "en": "Invoicing: invoices with inline line items and a summed total" },
6
7
"provenance": "Golden case `invoice_with_line_items` of packages/cli/src/lint/corpus.ts; `expect` derived mechanically from its fixture (every object; minFieldsPerObject = the smallest object's field count; one selectFields row per select field with minOptions = its option count; one lookups row per lookup or master_detail field).",
7
8
"prompt": {
8
9
"short": {"en": "Build an invoicing app with invoices and their line items."},
Copy file name to clipboardExpand all lines: docs/qa/scenarios/leave_balances.json
+2-1Lines changed: 2 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,8 @@
3
3
"id": "leave_balances",
4
4
"code": "leave",
5
5
"tier": "starter",
6
-
"title": "Leave and balances: leave types, yearly balances, requests with day lines and a route set by the days",
6
+
"origin": "generic",
7
+
"title": { "zh": "请假与假期余额:假期类型、年度余额、按天明细的请假单,按天数分级审批", "en": "Leave and balances: leave types, yearly balances, requests with day lines and a route set by the days" },
7
8
"provenance": "Mid-tier scenario 请假与假期余额 (leave and balances) of the open library, written for this card from the domain alone: generic names, cities and dates, no customer-derived content. The seed follows the arithmetic rule in `$comment`, so each truth can be worked out by hand.",
Copy file name to clipboardExpand all lines: docs/qa/scenarios/order_to_cash.json
+2-1Lines changed: 2 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,8 @@
3
3
"id": "order_to_cash",
4
4
"code": "order",
5
5
"tier": "starter",
6
-
"title": "Order to cash: accounts, an activity stream, sales orders with lines, a discount approval, invoices, payments and receivables aging",
6
+
"origin": "generic",
7
+
"title": { "zh": "订单到收款:客户、跟进活动、销售订单及明细、折扣审批、发票、收款和应收账龄", "en": "Order to cash: accounts, an activity stream, sales orders with lines, a discount approval, invoices, payments and receivables aging" },
7
8
"provenance": "Mid-tier scenario 订单到收款 (order to cash) of the open library, written for this card from general business knowledge alone: generic account names, cities, products and amounts, no customer-derived content. The seed follows the arithmetic rule in `$comment`, so each truth can be worked out by hand.",
"provenance": "Golden case `project_with_tasks` of packages/cli/src/lint/corpus.ts; `expect` derived mechanically from its fixture (every object; minFieldsPerObject = the smallest object's field count; one selectFields row per select field with minOptions = its option count; one lookups row per lookup or master_detail field).",
7
8
"prompt": {
8
9
"short": {"en": "Build a project management app with projects and tasks."},
Copy file name to clipboardExpand all lines: docs/qa/scenarios/requisition_to_receipt.json
+2-1Lines changed: 2 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,8 @@
3
3
"id": "requisition_to_receipt",
4
4
"code": "purchase",
5
5
"tier": "starter",
6
-
"title": "Requisition to receipt: purchase requisitions, an amount-threshold approval, purchase orders with lines and goods receipts",
6
+
"origin": "generic",
7
+
"title": { "zh": "采购申请到收货:采购申请、按金额分级审批、采购单及明细、收货登记", "en": "Requisition to receipt: purchase requisitions, an amount-threshold approval, purchase orders with lines and goods receipts" },
7
8
"provenance": "Mid-tier scenario 采购申请到收货 (requisition to receipt) of the open library, written for this card from the procurement domain alone: generic names, suppliers, items and amounts, no customer-derived content. The seed follows the arithmetic rule in `$comment`, so each truth can be worked out by hand.",
Copy file name to clipboardExpand all lines: docs/qa/scenarios/service_desk.json
+2-1Lines changed: 2 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,8 @@
3
3
"id": "service_desk",
4
4
"code": "helpdesk",
5
5
"tier": "starter",
6
-
"title": "Service desk: tickets with comment threads, SLA policies, time-based escalation and satisfaction surveys",
6
+
"origin": "generic",
7
+
"title": { "zh": "客服工单:工单及沟通记录、SLA 策略、超时升级和满意度调查", "en": "Service desk: tickets with comment threads, SLA policies, time-based escalation and satisfaction surveys" },
7
8
"provenance": "Mid-tier scenario 客服工单 (service desk) of the open library, written for this card from the domain alone: invented company and agent names, example.com addresses and invented figures, no customer-derived content. The seed follows the arithmetic rule in `$comment`, so each truth can be worked out by hand.",
Copy file name to clipboardExpand all lines: docs/qa/scenarios/travel_expense.json
+2-1Lines changed: 2 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,8 @@
3
3
"id": "travel_expense",
4
4
"code": "travel",
5
5
"tier": "starter",
6
-
"title": "Travel expense: trip requests, claims with lines, grade-based standards and a two-step approval",
6
+
"origin": "generic",
7
+
"title": { "zh": "差旅报销:出差申请、报销单及明细、按职级的差旅标准,两级审批", "en": "Travel expense: trip requests, claims with lines, grade-based standards and a two-step approval" },
7
8
"provenance": "Mid-tier scenario 差旅报销 (travel expense) of the open library, written for this card from the domain alone: generic names, cities and amounts, no customer-derived content. The seed follows the arithmetic rule in `$comment`, so each truth can be worked out by hand.",
0 commit comments