Repository navigation
fix(scenarios): the seed loads a user-handle field with a user id, and both author loops run verify - #30903
Conversation
…d both author loops run verify The seed loop left a persona-scoped owner field holding the handle text (`owner: "u_sam"`), which a build that models the field as a user lookup refuses, and sent a handle no persona holds (`u_tia`) as-is. A user-handle field (a direct scope field the card declares as neither lookup nor select, and not `id`) now carries the persona's user id, or the admin's for an unheld handle, in the field and in `owner_id`; the record lists the unheld handles. Scope fields naming a row or an option are sent verbatim, as before. The build and iterate prompts now name the same `objectstack verify` invocation `buildHalf` scores with (`verifyArgs`), next to validate and build, and the data contract says the handle field is loaded with a user id. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2ebbZkr2QAEG1x7UC8
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2ebbZkr2QAEG1x7UC8
📓 Docs Drift Check24 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. 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 29b4cb91e9d31281ca4e67ed1197737467515a3a && git checkout 29b4cb91e9d31281ca4e67ed1197737467515a3a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 98f640d581339769d54cf27c8f7b02cbc46849f4 b1f06284dc614f299377ceb493c2cb7a6bb4ed0e && git checkout -B drift-repro 98f640d581339769d54cf27c8f7b02cbc46849f4 && git merge --no-ff b1f06284dc614f299377ceb493c2cb7a6bb4ed0e
node scripts/docs-audit/affected-docs.mjs --json 98f640d581339769d54cf27c8f7b02cbc46849f4 |
…and the scorecard says so A refused seed row and, through the card's declared lookups, every seed row under it are out. A question on an object holding such a row, and an operation whose target, lookup values or post-state name one, are not asked: each is recorded with the reason, and counts in neither passed nor total. The scorecard gains a Seed row (`7/8 ⚠ seed: 1 refused`), the ask and operation cells name how many were not measured, and a half with nothing measured stays `not-run` in the record. The build prompt's ownership paragraph now states the seed contract exactly: the handle field and `owner_id` both carry the user's id, the persona's user for a held handle and the admin the runner signs in as for an unheld one. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2ebbZkr2QAEG1x7UC8
|
domain:devx#2 · marchtian · session_017ihL2ebbZkr2QAEG1x7UC8 Contract reviewServed-tier: Inputs: card #30882 (body and all three comments, read as claims), PR #30903 body, labels ( ① Derived judgmentsDefect 1, asks 1 and 2 — a user-handle field carries a user id in the row and in
Unheld handle to admin. The card's ask 2 offers two branches: provision a non-login user, or state another contract in the build prompt. The PR takes the second and states it: the Row ownership paragraph now reads, for crm, that in Defect 1, ask 3 — a refused seed row makes the Run half not measured.
Defect 2 — both author loops name the scored verify invocation. Tests. Reverting each fix reds its pin: deleting The landing. Check-runs on the head (all ② Semver levelNone. The package is private and unpublished; ③ Boundary flags
④ Design judgment
No must-fix. The tree does what the card's four asks say, the prompt matches the code word for word, the tests pin each ask and go red when its fix is reverted, and the half with nothing measured is Implemented-by: VERDICT: PASS |
|
domain:devx#2 · marchtian · session_017ihL2ebbZkr2QAEG1x7UC8 席位核对 — ACCEPT ·
|
Requested by 田騏 · project thread
Fixes #30882
Clause-②: no
Metadata-protocol: none
维护者速读
u_sam)换成该角色真实用户的 id(字段本身和owner_id都换);没有对应角色的代号(u_tia)记在管理员名下,提示词照此写明。种子有被拒的行时,依赖这些行的问数和写操作记为「未测」而非失败,计分卡显示⚠ seed: N refused。构建与迭代两轮的提示词也让 AI 自己跑一遍跑分时用的同一条verify命令。verify,于是构建"降级"并不说明它修不好。@objectstack/scenarios(QA 工具),不影响产品运行时;回滚即还原本 PR。其他卡片中指向记录或选项的范围字段照旧原样发送。packages/qa/scenarios/src/runner/orchestrate.ts的种子循环与seedDependence;packages/qa/scenarios/src/runner/scorecard.ts的未测计数;packages/qa/scenarios/src/runner/prompts.ts的userHandleFields与 Row ownership 文案。pnpm --filter @objectstack/scenarios exec vitest run test/runner,预期全部通过,其中"loads all 8 rows"断言种子 8/8 入库,"a refused seed row"两条断言⚠ seed警告与未测标记。What changes
Defect 1 — the seed lost every owner-scoped row. The seed loop set
owner_idfor a row whose persona-scope field named a persona's handle, but sent the field itself as the handle text (owner: "u_sam"). A faithful build models that field as a user lookup and refuses the handle; a handle no persona holds (u_tia) had no user at all, and every contact under a refused account failed as a cascade.prompts.tsuserHandleFields(card): the direct scope fields whose seed values are user handles — a field a persona's directscoperule names that the card declares as neither a lookup nor a select, and that is notid. Across the nine cards indocs/qa/scenarios/that is exactlyaccount.owner(crm),invoice.owner(invoice) andtask.assignee(project). Every other direct scope field names a seeded row (asset.custodian=e06,department.id) or an option (ticket_comment.visibility=public) and is still sent verbatim — replacing those values would have broken the lookups on six other cards.orchestrate.tsseed loop: a user-handle field now carries the persona's user id in the field itself and inowner_id.owner_idstays mapped: it is the platform's canonical, reassignable record owner (packages/objectql/src/registry.ts), the column owner-based sharing reads. A handle no persona holds is loaded as the admin user (field andowner_id), and the run record lists it underseed.handlesAsAdmin.prompts.tsdataContract: the Row ownership paragraph now states the contract the seed keeps. The handle field andowner_idboth get the user's id: the persona's user for a held handle, and the admin the runner signs in as for an unheld one. For crm it reads: "Indemocrm_account.ownerthe value is a handle that stands for a user, and the handle text is never stored: the runner loads that field, and the row's owner (owner_id), with that user's id (u_sam→ thesdruser;u_tia, which no person below holds → the admin user the runner signs in as). Make that field accept a user id; a rule on it matches that user." This is the card's "state another contract in the build prompt" branch.Defect 1, ask 3 — a refused seed row makes the Run half not measured, never a fail.
orchestrate.tsseedDependence(card, refused)takes out the refused rows and, through the card's declared lookups (expect.lookups), every seed row under one, transitively. A contact of a refused account is out whether or not the app refused it too.objectholds a row that is out, because its truth was computed over rows the app does not hold. An operation is not measured when itstarget, a lookup value it writes, or a post-state row (id, or a lookup value in itsvalues) names a row that is out.runHalfdoes not ask those turns. Each is recorded withnotMeasuredand the reason, for exampleseed: needs democrm_contact ct2, a child of democrm_account ac1, which the app refused. Everything else is asked and graded as before.scorecard.tsadds a Seed row and an Iterate · Seed row, for example7/8 ⚠ seed: 1 refused. The ask and operation tallies leave not-measured turns out of both passed and total, and their cells name the count, for example4/4 · 5 not measured. A half with nothing measured staysnot-runin the record, neverfail.Defect 2 — the author loop never ran verify.
verifyArgs(card)is['verify', ...verifyFlags(card)].buildHalfruns exactly that, and the build prompt and the iterate prompt both name it next tovalidateandbuild: for the crm card,npx objectstack validate,npx objectstack buildandnpx objectstack verify --reference-shapes, "until all three pass". The template-mode iterate prompt is unchanged: that mode edits the installed copy through the metadata API and runs noverify.Tests
test/runner/orchestrate.test.ts, new top-level describerunCard — a refused seed row makes what reads it not measured, never a fail, withRefusingApp, a double that refuses the seed rows it is told to:shows ⚠ seed: 1 refused, leaves the questions on that object unasked and unscored, and grades the rest as usual. It refuses ct4: the scorecard reads| Seed | 7/8 ⚠ seed: 1 refused |; the five contact question turns are not measured and were not asked; the four account turns are graded (4/4); the five operations are graded (5/5); and the record's run-ask ispasswith notes4/4 · 5 not measured.an operation whose target hangs off a refused row is not run; a half with nothing measured is not-run, not fail. It refuses all three accounts: onlycontact_bad_emailruns, all 9 question turns are out, run-ask isnot-runand run-operations ispass.| Seed | 8/8 | — |, with no warning.test/runner/units.test.tsa refused seed row takes out its children and what the card names on them, and nothing elsepins the rule on ac1: its target, a child, and the account an operation writes are out;add_contactandcontact_bad_emailare not.test/runner/orchestrate.test.ts, new top-level describerunCard — the seed against a build whose owner field is a user lookup. It usesUserLookupApp, an app double that refuses anowner/owner_idnaming no user and a contact whoseaccountnames no loaded account, as the first live run's app did.the double refuses the handle the old seed sent, so it can fail.loads all 8 rows: the owner field carries the persona user id, and a handle no persona holds is the admin:seedequals{ inserted: 8, refused: [], handlesAsAdmin: ['u_tia'] }; ac1 and ac3 carry the sdr user inownerandowner_id, ac2 the admin; all five contacts load; the five operations pass.owner_idto be the admin (it was undefined).test/runner/units.test.ts:both authoring loops name the verify invocation the runner scores with, next to validate and build.a user-handle field is a scope field the card declares as neither lookup nor select; the contract says it is loaded with a user id, which also pins thatservice_deskhas none.The regression test is built from the card in the repo (
crm_account_with_contacts), not from the first run's recording, which the epic seat holds. No live model was run.Acceptance notes
personaHandlestreats every direct scope string value as a handle, so the seed loop'sowner_idmapping (unchanged here) also fires on fields that name rows or options. Onservice_desk, 237 publicticket_commentrows are owned by the portal_customer user (visibility = public), and 15 tickets name two personas, so the last field read winsowner_id. This is reported to the seat, not fixed here.Verification
Package tests and typecheck (head b1f0628)
pnpm --filter @objectstack/scenarios test:: exit 0 — Test Files 18 passed (18), Tests 245 passed (245)pnpm --filter @objectstack/scenarios typecheck:: exit 0 (the tsconfig includestest/**/*)Ablations (each restored, blob equal to HEAD, git diff HEAD empty)
body[k] = userId;in the seed loop: the 8-row pin goes red.insertedis not 8 andrefusedis not empty: the double refuses the handle.seedDependencean empty refused list inrunHalf: both refused-seed run tests go red.⚠ seed: N refusedsuffix inseedCell: both refused-seed run tests go red. The first attempt used an empty--replacement, which the tool refused before running anything; it was redone with--delete.Derived gates on the final diff: 48 of 48 run, every one exit 0 (head b1f0628, merge base 98f640d, origin/main not moved since round 1)
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 48 commands for the 5 changed paths.pnpm buildoutput is still in place forcheck:dual-build-cjs-loads).dispatch-gates --ranwith the exit-coded record: "48 derived famil(ies) accounted for — 48 run, 0 NOT-MEASURED (a DERIVED zero — all 48 recorded an exit code and none of them is 3)".@objectstack/scenariosisprivate: true, so nothing publishes; this PR carriesskip-changeset, as this package's recent PRs (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, fix(scenarios): make the crm control card agree with its own personas #30801) did.pnpm test,pnpm lintand the CI-only families the derivation lists outside its 48.🤖 Generated with Claude Code