Repository navigation
feat(scenarios): scenario-card schema + loader (private packages/qa package) and nine open seed cards - #30340
objectstack-fleet[bot] wants to merge 4 commits into
Conversation
Claude-Session: https://claude.ai/code/session_01Q4EjjPXwX1YfHyKJUh4agu Co-authored-by: Claude <noreply@anthropic.com>
… changeset Claude-Session: https://claude.ai/code/session_01Q4EjjPXwX1YfHyKJUh4agu Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4EjjPXwX1YfHyKJUh4agu Co-authored-by: Claude <noreply@anthropic.com>
…spkg rollback pin Claude-Session: https://claude.ai/code/session_01Q4EjjPXwX1YfHyKJUh4agu Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 19 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 387c9368badb5155825e5d368973f3c78ed7a434 && git checkout 387c9368badb5155825e5d368973f3c78ed7a434
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bfca0efb79ac5df2049f392cdecd3bbeeea9ec61 038f4be0d81bfaff184a620879a88c96421b46ae && git checkout -B drift-repro bfca0efb79ac5df2049f392cdecd3bbeeea9ec61 && git merge --no-ff 038f4be0d81bfaff184a620879a88c96421b46ae
node scripts/docs-audit/affected-docs.mjs --json bfca0efb79ac5df2049f392cdecd3bbeeea9ec61
|
|
epic:#3600#1 · hotlong · session_01Q4EjjPXwX1YfHyKJUh4agu 席位核对Verdict: ACCEPT at head
|
Fixes #30289
Clause-②: no
Metadata-protocol: none
维护者速读
@objectstack/scenarios(packages/qa/scenarios,不发布、无构建步骤、不调用任何模型),内含场景卡的 zod schema 与加载器;在docs/qa/scenarios/落下开放场景库的头 9 张卡(5 张来自packages/cli/src/lint/corpus.ts的 golden case,4 张通用构建场景)。每张卡六层:提示词、结构期望expect、能力标签与分类、问数(种子数据 + 声明式真值 + 角色)、写操作与五项护栏、一次迭代请求。问题的答案由代码从种子数据算出,不手填。docs/qa/scenarios/,schema 与加载器放私有packages/qa/*包,不进packages/spec。这是给评分器(姊妹卡,expect形状共用)和后续本地 runner 打地基。packages/spec、不改任何公开面、包为private: true。仓级改动只有三处登记:scripts/cross-package-test-inputs.mjs与turbo.json声明本包测试读docs/qa/scenarios/*.json和清单 area 文件(否则改卡不会触发测试、缓存会回放旧绿),以及scripts/check-ci-filter-parity.mjs自测里按名记下这条新 glob(计数 20 → 21)。回滚 = revert 本 PR。packages/qa/scenarios/src/loader.ts的checkCard(交叉引用与真值计算的拒收点);②packages/qa/scenarios/src/truth.ts的evaluateTruth与topN(平局截断、空集平均的拒收);③docs/qa/scenarios/invoice_with_line_items.json(一张卡的完整样子)。pnpm --filter @objectstack/scenarios test,预期Test Files 3 passed、Tests 42 passed;把任一张卡的category改成retail再跑,library 用例转红并点名该文件与category。What changes
New private package
@objectstack/scenarios(packages/qa/scenarios), named after its siblings (@objectstack/dogfood,@objectstack/http-conformance,@objectstack/vitest-filter-preflight:private: true,exportsatsrc, no build step). Its only runtime dependency iszod.src/schema.ts: the card shape. Every object is strict, so an unknown key is refused rather than dropped. The six layers:prompt:{ short, detailed }, each{ zh?, en? }with at least one language.expect: field-for-field the shape the scorer card fixes (objects,minFieldsPerObject?,selectFields?,lookups?,dashboards?,appNavTargets?). Nothing renamed, no required key added, and no optional key added.capabilities(AREA.NAME) andcategory. The category is a closed enum:app, crm, sales, marketing, finance, hr, operations, project, productivity, data, integrations, devtools, utility, other.run.seed,run.questions(at least 3),run.personas(at least 2). A truth spec iscount | sum | avg | min | max | topNwithfilter(eq ne gt gte lt lte in),groupByandorder.run.operations(create, update or delete; at least one create or update) andrun.guardrails: all five checks,permission_parity,write_validation,confirm_irreversible,audit_actorandinjection_as_data. Each check names the persona, operation or seed cell it is about.iterate: one change sentence plus an additiveexpectDelta.src/truth.ts: evaluates a truth spec over the seed rows. It refuses any truth the seed does not determine: an avg, min or max over no values, a numeric aggregate over text, a mixed number/string comparison, atopNwhose cut-off falls inside a tie, or atopNasking for more entries than exist.src/loader.ts:loadScenarios(dir, { areas })returns typed cards with their computedtruths, or a list of{ file, path, message }refusals. A card with any refusal is not returned at all. The second pass holds the card together:idequals the file's basename; lookups, selects and nav targets resolve insideexpect; the capability area is in the closed set; every expected object is seeded, and only those; each seeded object carriesminFieldsPerObjectfields; lookup values name seeded rows; question, persona and operation references resolve against the seed; guardrail references resolve; the iterate delta is additive and consistent.readCapabilityAreas(dir)reads the area closed set as the basenames ofdocs/qa/platform-checklist/areas/*.json.Nine seed cards in
docs/qa/scenarios/, plus aREADME.mdcovering the format, the six layers, how truth is computed, and the rule that this set holds generic scenarios only.expectmechanically from their fixtures. Every object is listed.minFieldsPerObjectis the smallest object's field count, because the scorer applies it to every object. Each select field becomes aselectFieldsrow whoseminOptionsis its option count, and each lookup or master_detail field becomes alookupsrow. The derivation ran over a copy ofcorpus.ts, and its output is identical to what the cards carry.expectvalues exactly as the card states them.Repo wiring that an existing gate requires (outside the claimed file surface; see Acceptance notes):
scripts/cross-package-test-inputs.mjs: an@objectstack/scenariosentry declaring the two globs this suite reads, with aheldBywitness for the area directory, which is listed withreaddirSync.turbo.json: an@objectstack/scenarios#testtask whoseinputscarry both globs, so the cache hash moves when a card changes.scripts/check-ci-filter-parity.mjs: its self-test pins how many declared globs a pre-ci.yml's core filter misses ten more declared cross-package test inputs — content/ (7), docs/, skills/, .github/workflows/ #10015crosspkgrollback leaves uncovered.docs/qa/scenarios/*.jsonis a new member of that set, so the pin moves from 20 to 21 and names the glob. That pin's own comment prescribes exactly this edit.Tests
test/library.test.tsloads every card underdocs/qa/scenarios/and asserts zero refusals and exactly the nine ids. It then asserts every question's computed truth (44 questions) against values I worked out by hand from the seed rows, not against a snapshot of the evaluator's output.test/refusals.test.tsholds 21 refusal cases plus one control. Each case starts from a real library card, changes exactly one thing, and asserts the refusal'sfileand exact fieldpath. The control shows the unmutated card loads clean.capabilities[0]), a category outside the enum (category), and a truth kind outside the enum (run.questions[0].truth.kind).minFieldsPerObject, a field typo in a question, a tiedtopNcut-off, an avg over zero rows, a sum over text, a broken lookup value in the seed, a persona that hides nothing, an operation on a missing row, a missing guardrail check, an injection cell that is not text, and a delta that re-adds an existing object.test/truth.test.ts: the evaluator's semantics (null skipping, an empty sum, string and date comparison,inandeq null, group ordering with a null key and numeric keys,orderwithoutgroupByrefused, ranking, oversizedtopN).Acceptance notes
scripts/cross-package-test-inputs.mjsandturbo.jsonare whatpnpm check:cross-package-test-inputsrequires of any package whose tests read outside it. Without them, a card-only edit would not schedule this suite in CI (Layer A) and turbo would replay a cached green (Layer B). Adding that declaration in turn moves thecheck:ci-filter-parityself-test count. No workflow edit was needed, becausedocs/**is already in ci.yml'scrosspkgfilter.areafield for all 16 files. No other closed area source exists, so the PM's suggested source is used. (2) Cards as JSON beside thedocs/qa/platform-checklist/JSON ledgers fit the repo's precedent. (3) No existing schema to extend. The only scenario schema in the tree ispackages/spec/src/qaTestScenario(theos testsuite format), which is a different concept, andpackages/cliMetadataEvalCaseis a prompt plus a fixture with noexpector Run half.expectis derived once and is not held equal tocorpus.tsby a test. A drift pin would make a private qa package import CLI internals (corpus.tsis not exported), so that direction is left to the CLI-wiring card.minFieldsPerObjectforinvoice_with_line_itemsis1, because the fixture'saccountobject has one field. The derivation is mechanical, and the scorer applies the bound to every object.Verification
Package suite and typecheck (final head 038f4be)
Ablation (one, on a tested behaviour; restored byte-identical)
truth.tsavg divided byrows.lengthinstead of the non-null value count, applied throughscripts/ablation-replace.mjs(anchor 1 → 0, blob 0afa46f9 → a6a44d3e).test/truth.test.tsturned red on "skips null values in a numeric aggregate". After the restore,git hash-objectequals the HEAD blob 0afa46f9 andgit diff HEADis empty. The library cards hold no null in an averaged field, so the library suite stayed green under this mutation, as expected. The null-skipping semantics are pinned bytruth.test.ts.Derived gates (dispatch-gates --commands, reconciled with --ran)
Tree: objectstack-ai/objectstack at 038f4be (merge base 4be9734). Reconciliation:
dispatch-gates --ran→92 derived famil(ies) accounted for — 84 run, 8 NOT-MEASURED (6 DERIVED from a recorded exit 3, 2 claimed), exit 0.check:cross-package-test-inputs,check:ci-filter-parity(red first on the rollback pin, 1 of 139 assertions; after the pin edit 140 assertions, exit 0),check:pm-dispatch-gates,check:type-check-coverage,check:test-source-alias,check:console-intercept-disarm,check:nul-bytes,check:doc-authoring,check:issue-citations,check:changeset-fixed,check-changeset-no-major,check-empty-changeset,check:turbo-task-graph,check:undeclared-dep-imports,check:manifest-repository-directory,check:pnpm-filter-targets.dist/this worktree never built; this diff builds no package):check:dts-closure,check:dual-build-cjs-loads,check:lean-entry-closure,check:sourcemap-no-sources-content,@objectstack/lint check:doc-formula-expressions;check-plugin-teardown-shape --self-testneeds an unshallowed clone (it exits 3 on the shared checkout's own tree too).check:query-options-erasure,check:type-check-debt.Generated by Claude Code