Repository navigation
feat(scenarios): cards carry an app code; every scenario app is built in the demo+code namespace and a bare item name fails its build - #30837
Conversation
… in the demo+code namespace and a bare item name fails its build - schema/loader: a required `code` (2-8 lowercase letters); the loader refuses a missing or malformed code, and every card sharing one across the library. - every card under docs/qa/scenarios gets a code. - runner: scratch mode scaffolds the project as demo+code; template mode installs a template declaring that namespace into a host named host+code (the unmanaged copy keeps its namespace and the import refuses one the host owns). - prompts state the every-item-prefixed rule; persona permission sets, agent actions and expected dashboards are read namespace-relative too. - scorer: one failing Build check per bare non-object item name; a build with one is never verified. Template mode scores the installed app, not the environment's own items. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cyKDwJet4nwPKBn5KP
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
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 686c7a10ae7ffaa2dd28a383ad1917376305e894 && git checkout 686c7a10ae7ffaa2dd28a383ad1917376305e894
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 25e1c1ad8bec483748d9a7e36adf6676aa8df895 bea14b472850ce018c6b8ce68c9ccf0ad97920a6 && git checkout -B drift-repro 25e1c1ad8bec483748d9a7e36adf6676aa8df895 && git merge --no-ff bea14b472850ce018c6b8ce68c9ccf0ad97920a6
node scripts/docs-audit/affected-docs.mjs --json 25e1c1ad8bec483748d9a7e36adf6676aa8df895
|
…rror-code casing guard does not read it as an emission `check:error-code-casing` (ADR-0112 D1) reads a `code: 'lowercase'` pair in an object literal as an error.code emission. The twin card's app code `crmtwin` is a namespace code, not an error code, so the test now assigns it the way the refusal cases already do. The guard is unchanged. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cyKDwJet4nwPKBn5KP
|
domain:devx#1 · os-elon-musk · session_016WV4cymvo9YoyLwKCXXBNw 席位核对 · ACCEPT · #30795 · 2026-11-06T06:46ZHead-sha:
|
Requested by Elon · project thread
domain:devx#1 · os-elon-musk · session_016WV4cyKDwJet4nwPKBn5KP
Fixes #30795
Clause-②: no
Metadata-protocol: none
维护者速读
code(2–8 个小写字母,全库唯一,加载器拒收缺失、畸形、重复);运行器把每个场景应用固定放进demo+code 命名空间(如democrm);构建提示要求每个元数据项都带前缀,评分器把每个不带前缀的非对象项记为一条 Build 失败,这样的构建不会成为模板或画廊候选。@objectstack/scenarios包和场景卡 JSON 及两份 README;回滚即 revert。packages/qa/scenarios/src/runner/orchestrate.ts的 template 模式(宿主名改为hostCODE,如hostcrm,安装前先取环境基线再扣除);②src/score/scenario-naming.ts(裸名判定覆盖哪些集合);③src/loader.ts的跨卡 code 去重。pnpm --filter @objectstack/scenarios exec vitest run test/runner/orchestrate.test.ts,应看到 9 个测试全绿,其中一个证明「带裸名视图和仪表盘的构建 Build 不通过、template 模式被跳过」。What changes
1. A card
code(schema.ts,loader.ts). Every card now carriescode: 2–8 lowercase letters, no digit, no underscore. The schema refuses a missing or malformed code. Uniqueness is a property of the library, soloadScenariosjudges it across the directory: every card that shares a code is refused atcode, naming the other file(s), even when that card is also refused for another fault.loadScenarioFile, given one file, cannot judge it, and its docblock says so.scenarioNamespace(card)(schema.ts) is the one place that spells the namespace:demo+ code.Every card under
docs/qa/scenarios/has a code:codeasset_lifecycleassetdemoassetcrm_account_with_contactscrmdemocrminvoice_with_line_itemsinvoicedemoinvoiceleave_balancesleavedemoleaveorder_to_cashorderdemoorderproject_with_tasksprojectdemoprojectrequisition_to_receiptpurchasedemopurchaseservice_deskhelpdeskdemohelpdesktravel_expensetraveldemotravelThe longest namespace is 12 characters, inside
create-objectstack's 13-character scaffold cap.2. The demo+code namespace in both modes (
runner/orchestrate.ts,runner/namespace.ts,runner/cli.ts).create-objectstackderives the namespace from the project name, so scratch mode scaffolds the project as demo+code (democrm), in a directory of its own per card and mode. Before the build, the runner checks the scaffolded config declares that namespace. A project declaring any other namespace is skipped with the reason and never read against it.hostcrm). See the first acceptance note for why it cannot be demo+code.3. Every item is prefixed (
runner/prompts.ts,score/scenario-naming.ts,score/scenario-tags.ts,runner/scorecard.ts).democrm_sdr, wassdr), and the runner grants that name.gradeItemNames(artifact, namespace)reads every manifest collection the spec lists inPLURAL_TO_SINGULARexceptobjects, throughresolveStackCollection, so a multi-package artifact is judged too. Each item whose name does not start with the namespace and an underscore is one failing check,bare-name:TYPE:NAME. A clean build adds no check, so the rule never pads a score.scoreCardArtifactpasses the card's namespace, soScenarioArtifactScore.namingcarries these checks and counts them inpassed / total.buildPassedalso requiresnamingto be clean. So a build with one bare item is never verified: no template install and no gallery candidate. The scorecard lists each bare item as a finding (build: bare item name view all_accounts (…)), and the run record carries the checks as Build evidence.4. Namespace-relative reading kept (ruling A on #30292), now always against demo+code. Objects are read as before. Because every item is prefixed, the other names a card states are read the same way: expected dashboards, every nav target, agent action names, and permission sets. Otherwise the scorer would require a bare
dashboard:overviewand also report it as a finding. Fields, row ids, persona ids and truths are unchanged.Tests
pnpm --filter @objectstack/scenarios typecheck && pnpm --filter @objectstack/scenarios testatf8d3de9a: typecheck exit 0 (the tsconfig includestest/**), Test Files 18 passed (18), Tests 238 passed (238),VERDICT command-exit 0.refusals.test.ts: a missing code, an underscore code, and a nine-letter code (each one refusal atcode). Two cards sharingcrmare both refused, each naming the other, with a control where distinct codes load. A shared code is also named on a card refused for another fault.test/score/entry.test.ts: barepermission:sdrandview:all_accountsare two failing checks with exact details, objects and prefixed items add none, and the total grows by 2 while passed does not. A multi-package artifact's bare dashboard is found underpackages[].test/runner/units.test.ts:namespacedScenarioprefixes objects, agent action names, dashboards and nav targets, and is idempotent.permissionSetNameandtemplateHostNameare tested. Every authoring prompt states the rule, and the contract namesdemocrm_partner_useranddemocrm_delete_contact.withoutItemskeeps only what the install added.test/runner/orchestrate.test.ts: the crm card end to end. The scaffolds arecrm-account-with-contacts-scratch/democrmandcrm-account-with-contacts-template/hostcrm, and the seed lands ondemocrm_*. The grants aredemocrm_sdranddemocrm_partner_user. The installed copy declaresdemocrm, and the template build is not degraded although the fake environment servessetupandadmin_full_accessunprefixed. A scaffold declaringcrm_account_w, and a supplied template declaringcrm, are each skipped. A build with a bare view and a bare dashboard passes everyexpectcheck but is degraded, lists one finding per item, and template mode is skipped.Ablations (each mutation written by scripts/ablation-replace.mjs, proven on disk, then restored with git diff HEAD empty)
if (false && others.length > 0)(no duplicate-code refusal)if (true) continue;(no bare-name check)buildPassedignoresnamingtemplateHostNamereturns the app's namespaceafterwithout the baseline subtractedbare-name:permission:admin_full_access)The package is source-only (
exportspoint atsrc/), so no rebuild sits between a mutation and the test run.Acceptance notes
hostcrm), not demo+code. Measured live oncreate-objectstack@18.3.0withobjectstack dev --fresh. Installing ademocrmpackage unmanaged into a host scaffolded asdemocrmanswers409 RESOURCE_CONFLICT: "The namespace 'democrm' of package 'com.example.democrm' is already owned by 'com.example.democrm'. The unmanaged copy keeps the source namespace, so it cannot sit beside a package that owns it". The same install into a host scaffolded ashostcrmanswers200,targetPackageId: com.example.democrm-copy,namespace: democrm. The refusal is step 6 of the import verb inpackages/metadata-protocol/src/protocol-package-commits.ts. So the scored app is in demo+code in both modes, and only the host is renamed./api/v1/meta/*serves unprefixed platform items (appsetup, dashboardsystem_overview, permission sets such asadmin_full_access, positions, datasourcedefault). Without the subtraction, every template build would carry bare-name findings for items that are not the app's. The subtraction also keeps the platformsetupapp's nav out of thenav-resolves:checks, which already read every app in the environment's metadata before this change.os buildof a blank project emits only the project's own items (measured:democrm_noteplus 3 plugins), so scratch mode needs no subtraction.docs/qa/scenarios/holds 9 cards on c17719d. The 10 in the order countedREADME.md.codeplacement: the line right afteridin each card.defineStackalready refuses an unprefixed object.Verification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(exit 0) derived 52 families for this change set atf8d3de9a(basec17719dd). All 52 ran atf8d3de9a. 51 exited 0 on the first pass.pnpm check:dual-build-cjs-loadsfirst exited 3 (prerequisite: built output); afterturbo run build --filter='@objectstack/dogfood^...' --filter='@objectstack/runtime^...'it exited 0 (116 require entry points across 71 packages load).dispatch-gates.mjs --ranon the record: exit 0, "52 derived famil(ies) accounted for — 52 run, 0 NOT-MEASURED".Head note: this draft opens before #30802 lands, by the seat's call, so CI runs early. #30802 edits the same loader,
refusals.test.tsand three cards. After it merges, this branch mergesorigin/main, re-runs the package suite and the gates on the new head, and the seat gets that head.52 gates, exit codes at f8d3de9
Size: 26 files, +518 / −123 against the merge base (641 changed lines), under the 3,000-line threshold.
Human Minimum
packages/specholds no runtime logic. (packages/specis untouched; the card schema is the private package's Zod schema.)pnpm lintis left to CI.main, one pull request.@objectstack/scenariosisprivate: trueand the cards are docs, so nothing publishes:skip-changeset. Both declaration lines are above.packages/specnot touched.🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4cyKDwJet4nwPKBn5KP