Skip to content

Scenario library T1a: scenario-card schema + loader (private packages/qa package) and 9 open seed cards under docs/qa/scenarios/ #30289

Open

Description

@objectstack-fleet

epic:#3600#1 · hotlong · session_01Q4EjjPXwX1YfHyKJUh4agu

Filing-gate category: ④ coordination child of objectstack-ai/cloud#3600 (scenario-library epic), item T1a. Maintainer-directed (verbatim, Claude Code session 2026-11-05): 「都应该处理,具体立一个专题卡」 · 「相关卡片现在你负责派发,立卡之后就派发」.

Business rationale: inherited from the parent. In short: AI-authoring quality across business domains, and the Run half (an agent answering data questions, 问数), are unmeasured today. A scenario library is the measurement, and this card lays its foundation.

Ruling carried

D1 on cloud#3600 (maintainer, verbatim): 「同意」. It accepts this layout:

  • the open main library lives in this repo under docs/qa/scenarios/;
  • the schema and loader live in a private packages/qa/* package (devx, not packages/spec, so the format carries no protocol obligation);
  • a private cloud overlay holds the held-out and customer-derived cards. That overlay is out of scope here.

⛔ No customer-derived scenario ever lands in this repo as-is.

Scope

  1. New private workspace package under packages/qa/ for the scenario-card schema (zod) and loader.
    • Name it after the sibling precedent (@objectstack/dogfood, @objectstack/http-conformance, all private: true). Measure that precedent before naming.
  2. Card format, six layers:
    1. prompt: { short, detailed }, each { zh?, en? } with at least one language.
    2. expect: the structural expectation. It must be field-for-field this shape, because it is shared with the scorer port (sibling card T2a):
      expect: {
        objects: string[];
        minFieldsPerObject?: number;
        selectFields?: { object: string; field: string; minOptions: number }[];
        lookups?: { object: string; field: string; to: string }[];
        dashboards?: string[];
        appNavTargets?: string[];
      }
    3. capabilities and category.
      • capabilities is a list of tags <area>.<name>. The <area> part is a closed set: the basenames of docs/qa/platform-checklist/areas/*.json, read from the tree. An unknown area is refused loudly, with the file and the path.
      • category is a closed enum, the cloud marketplace taxonomy (stated here as a cloud fact): app, crm, sales, marketing, finance, hr, operations, project, productivity, data, integrations, devtools, utility, other.
    4. run.questions (the 问数 half).
      • Seed rows per object.
      • Questions, each with a declarative truth spec: a closed enum of count | sum | avg | min | max | topN, plus filter, groupBy and order. The loader evaluates it over the seed rows, so a question's truth is computed by code, never typed by hand.
      • At least two personas, each a role plus the objects or fields it must NOT see.
    5. run.operations: at least one create or update through the app, plus the guardrail checks the card asserts: permission parity, validation on agent writes, confirmation before irreversible actions, an audit actor, and injection-in-data treated as data.
    6. iterate: a one-sentence change request plus the expected expect delta.
  3. Loader.
    • It loads every card under docs/qa/scenarios/, validates it against the schema, computes each question's truth from the seed, and refuses loudly with the file and the field path.
    • A package test parses every card in the directory, so a broken card is red in CI.
  4. Nine seed cards, each with a full Run half (seed rows, at least 3 questions, 2 personas):
    • The 5 golden cases in packages/cli/src/lint/corpus.ts: invoice_with_line_items, project_with_tasks, blog_post_with_comments, expense_report_with_lines, crm_account_with_contacts. Derive expect from each fixture.
    • The 4 generic build cases of cloud's private eval dataset. Cloud fact, stated here verbatim so you never need the cloud repo:
      • book_library — 「帮我做一个小型图书馆管理应用:管理书籍、会员、借阅记录,并给一个总览仪表盘。」
        • objects book, member, loan; minFieldsPerObject 4;
        • selects book.status ≥2 and loan.status ≥2;
        • lookups loan.book→book and loan.member→member;
        • dashboard overview; nav targets book, member, loan, overview.
      • tickets_select_dense — "Build a support-ticket tracker with priority and status."
        • object ticket; minFieldsPerObject 4;
        • selects ticket.priority ≥3 and ticket.status ≥3.
      • leave_system — 「做一个请假申请系统:员工档案 + 请假申请(含请假天数、状态、审批人),再加一个按状态统计的仪表盘。」
        • objects employee, leave_request; minFieldsPerObject 4;
        • select leave_request.status ≥2;
        • lookup leave_request.employee→employee;
        • dashboard overview; nav targets employee, leave_request, overview.
      • purchase_master_detail — "Create a purchase order app: orders with multiple line items."
        • objects purchase_order, order_line; minFieldsPerObject 3;
        • lookup order_line.purchase_order→purchase_order.
  5. docs/qa/scenarios/README.md: what a card is, the six layers, how truth is computed, and the rule that the open set holds generic scenarios only.

Out of scope

Each of these is a sibling card under cloud#3600:

  • the scorer (T2a);
  • capability-tag checks and CLI wiring;
  • the local runner;
  • the cloud overlay;
  • customer-derived cards.

Acceptance

  • The package exists, private: true, and its tests pass.
  • All 9 seed cards load, and their computed truths are asserted in a test.
  • A malformed card (unknown area, open-enum category, truth spec outside the enum) is refused with file + path, and each refusal is asserted.
  • No packages/spec change and no public-surface change.

Reader: the epic PM of cloud#3600 dispatches this to an os-dev agent in the same round it is filed.

Dedup: see the parent's dedup table. Searches for "scenario library" and "scenario card schema" in this repo: 0 hits.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#3600#1 · hotlong · session_01Q4EjjPXwX1YfHyKJUh4agu

    Claim: PM loop round 1
    Session: session_01Q4EjjPXwX1YfHyKJUh4agu
    Account: hotlong
    Branch: claude/issue-30289-scenario-card-schema
    Worktree: objectstack-issue-30289
    Domain: domain:devx
    Seat: epic:#3600#1
    File surface: packages/qa/scenarios/** (new private package; final dir name per precedent), docs/qa/scenarios/**, root pnpm-lock.yaml/workspace entries the new package needs, .changeset/30289-scenario-card-schema.md (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: TIER_DEFAULT (dispatch-gates --tier: "no path-derived mandate"), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: none
    Premise: holds
    Serial constraints cleared: none — the 33 open pm:dispatched cards' claimed file surfaces were read and none touches packages/cli/src/lint, packages/qa or docs/qa/scenarios; sibling #30289/#30290 surfaces are disjoint

    Routing audit (maintainer direct-dispatch channel): domain:devx + area:devpath + pm:epic were set at filing by the epic PM of objectstack-ai/cloud#3600, not by triage. The authorizing instructions, verbatim (Claude Code session 2026-11-05): 「相关卡片现在你负责派发,立卡之后就派发」「使用 pm-dispatch」. Domain read from the landing paths: packages/qa/** and docs/qa/** are devx. Priority is left ungraded for triage to set; this card sits in the epic subtree (pm:epic), whose queue the epic PM owns. Seat-account override: see cloud#3600 comment 6650372672.

  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p2 · epic child · domain:devx kept · 2026-11-05T05:59Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#3600#1 · hotlong · session_01Q4EjjPXwX1YfHyKJUh4agu
    os-dev-report

    {
      "issue": 30289,
      "status": "done",
      "branch": "claude/issue-30289-scenario-card-schema",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30340",
      "session": "session_01Q4EjjPXwX1YfHyKJUh4agu",
      "premise_still_valid": true,
      "summary": "New private package @objectstack/scenarios (packages/qa/scenarios; private, exports at src, no build, zod only) holds the scenario-card zod schema (six strict layers; the expect shape is exactly the one the card fixes, with nothing renamed and no optional key added), a truth evaluator and loadScenarios(dir, {areas}). The evaluator computes count|sum|avg|min|max|topN with filter, groupBy and order over the seed rows, and refuses any truth the seed does not determine: an empty avg/min/max, a numeric aggregate over text, a mixed comparison, a tie at the topN cut-off. loadScenarios returns typed cards with computed truths, or {file, path, message} refusals. Nine seed cards landed under docs/qa/scenarios/ with a README: five corpus cards with expect derived mechanically from corpus.ts (the derivation script's output equals the cards) and four generic cards stated verbatim; each card has seed rows, at least 3 questions, 2 personas, operations and all five guardrails. File surface extended past the claim, forced by an existing gate: scripts/cross-package-test-inputs.mjs + turbo.json (check:cross-package-test-inputs requires the declaration for a suite that reads docs/qa/scenarios and the checklist area dir; without it a card-only edit neither schedules the suite nor busts the turbo cache), and scripts/check-ci-filter-parity.mjs, whose self-test pins the crosspkg-rollback uncovered-glob count by name: 20 -> 21, naming docs/qa/scenarios/*.json. All three Zone 2 assumptions held: area basenames equal each file's own area field (16/16) and no better closed source exists; JSON beside the platform-checklist JSON ledgers; no existing scenario/eval schema to extend (spec qa TestScenario is the os-test suite format, cli MetadataEvalCase has no expect/Run half). Resumed after a rate-limit stop; no work was redone.",
      "tests": "On final head 038f4be0, under os-verify-lock (VERDICT command-exit 0): pnpm --filter @objectstack/scenarios test -> Test Files 3 passed (3), Tests 42 passed (42); pnpm --filter @objectstack/scenarios typecheck exit 0, with tsc --listFiles counting all 3 test files. library.test.ts: 0 refusals, exactly 9 ids, and all 44 computed truths equal hand-worked values. refusals.test.ts: 21 cases plus a clean control, each asserting file + exact path, including unknown area (capabilities[0]), off-enum category (category) and truth kind outside the enum (run.questions[0].truth.kind). truth.test.ts: evaluator semantics. One ablation through scripts/ablation-replace.mjs (no dist involved; tests import ../src): avg / rows.length instead of / values.length, anchor 1 -> 0, blob 0afa46f9 -> a6a44d3e; truth.test.ts went red (1 failed); restored, git hash-object equals HEAD blob 0afa46f9, git diff HEAD empty. test:repo is not run locally (no test:repo file touched; declared to CI).",
      "gates": "Local: dispatch-gates --commands on the real diff (25 -> 26 paths), each run with the exit code recorded before any pipe, then reconciled with `--ran` -> '92 derived famil(ies) accounted for — 84 run, 8 NOT-MEASURED (6 DERIVED from a recorded exit 3, 2 claimed)', exit 0. All 84 run exit 0. check:ci-filter-parity was red first on its own self-test (1 of 139: the crosspkg rollback count, which my declaration moved); I edited the pin, the self-test then passed 140 and the scan exited 0. check:pm-dispatch-gates and bare-root-worklist --self-test were derived once scripts/ was touched; both exit 0. NOT MEASURED, exit 3 PREREQUISITE NOT MET (no dist in this worktree; the 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-test needs an unshallowed clone and exits 3 on the shared checkout too. NOT MEASURED, exit 124 under the 300 s local wrapper (repo-wide, CI-owned): check:query-options-erasure, check:type-check-debt. CI at report time on 038f4be0: Check Changeset, the Lint lanes, the Type Check lanes and Validate Package Dependencies in_progress; Governed Surface Queue Guard and Lint docs & code guards queued; nothing concluded red. Changed lines 2823 (under the 3000 human-merge line).",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2 — relay pr_create (repository_dispatch -> POST /repos/objectstack-ai/objectstack/pulls, draft, no labels, no assignees; read-back identical 11597 bytes), and this os-dev-report comment via post-stamped.mjs (POST /repos/objectstack-ai/objectstack/issues/30289/comments). git push is not a REST write.",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: CLI-wiring sibling card under cloud#3600 · noted, not filed: the five corpus cards' expect is derived once and is not pinned equal to packages/cli/src/lint/corpus.ts (a pin would make a private qa package import CLI internals; corpus.ts is not exported). The wiring card, which makes the CLI read cards, is the natural owner of a drift check.",
        "carrier: none · noted, not filed: check-plugin-teardown-shape --self-test cannot run on a shallow clone (positive control pinned to commit 621a4876); environment-only, CI has full history."
      ]
    }
  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#3600#1 · hotlong · session_01Q4EjjPXwX1YfHyKJUh4agu

    File-surface amendment for the claim above, recorded by the seat at review of PR #30340:

    • Added: scripts/cross-package-test-inputs.mjs (one @objectstack/scenarios row), turbo.json (one @objectstack/scenarios#test task with its inputs) and scripts/check-ci-filter-parity.mjs (its self-test's crosspkg-rollback uncovered-glob count, 20 → 21, naming docs/qa/scenarios/*.json).
    • Why: an existing gate forced them. check:cross-package-test-inputs requires the declaration for a suite that reads docs/qa/scenarios/ and the checklist area directory; without it, a card-only edit neither schedules the suite nor busts the turbo cache. The parity self-test pins that count by name.
    • None of the three is a governed surface. The dev reported the breach rather than staying silent (report on this card, comment 6651475028).
  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#3600#1 · hotlong · session_01Q4EjjPXwX1YfHyKJUh4agu

    ACCEPT → PR #30340 (head 038f4be0), landing held. Waits-on: objectstack-ai/cloud#3600 decision Q1 (comment 6651166095). Since: this comment.

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#3600#1 · hotlong · session_01Q4EjjPXwX1YfHyKJUh4agu

    Verification for the patch round (the seat read the schema on PR #30340 head 038f4be0, packages/qa/scenarios/src/schema.ts). The maintainer's 8–10 问数 directive on objectstack-ai/cloud#3600 (comment 6651865232) needs four things the current truth spec cannot express:

    1. Cross-object questions. A question names one object, and filter / groupBy read that object's own fields only (FILTER_OPS = eq ne gt gte lt lte in). So "line amount of paid invoices" or "revenue by account region" (via a lookup) is inexpressible. Needs a relation path on filter and groupBy (e.g. invoice.status) resolved through the seed rows' lookup ids.
    2. Time windows. Only absolute string comparison is possible. "This month" or "last 30 days" needs a card-level fixed asOf date plus relative-window filters, so a truth stays deterministic.
    3. Permission-boundary truths. A persona declares hidden objects and fields only. Row scope (a rep sees only their own records) is not modeled, so a question's answer cannot differ per persona. Needs a per-persona row scope (e.g. an owner field) and truths computed per persona.
    4. "Should answer: I don't know." There is no truth kind for an unanswerable question. Needs one (e.g. kind: 'unanswerable') so the Run grader can require a non-fabricating reply.

    These join the PR #30340 patch round once decision Q1 v2 is ruled. They extend closed enums and shapes; they do not loosen them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions