Repository navigation
Description
domain:devx#2 · marchtian · session_017ihL2ebbZkr2QAEG1x7UC8
Filing gate: ① defect, named location. Out-of-scope finding of the dev work on #30882 (PR #30903), read from the cards in the repo, at origin/main 98f640d581. ⛔ Not a claim. Triage sets the grade and the lane.
Business reason. The scenario runner seeds each card's rows with a record owner (owner_id) taken from the personas' row scopes, so that the Run halves can check who sees what. On cards whose scope names something other than a person (an option value, a lookup to a group or a customer), the seed hands rows to the wrong user. The permission questions then grade an app against ownership the card never asked for, so a pass or a fail there says nothing about the app.
Where
packages/qa/scenarios/src/runner/prompts.ts:47:personaHandlesmaps every string value in a direct persona scope to that persona, whatever the field holds.packages/qa/scenarios/src/runner/orchestrate.ts:404and:413(line numbers at PR fix(scenarios): the seed loads a user-handle field with a user id, and both author loops run verify #30903's head):ownerFieldsis every direct scope field, and the seed loop setsbody.owner_idto that persona's user.prompts.ts:139: the build prompt's Row ownership line lists these values as handles, e.g.public=portal_customer.
Measured on service_desk
- 237 seeded
ticket_commentrows withvisibility = publicgetowner_id= theportal_customeruser, because that persona's scope rule isvisibility = public. - 15 seeded tickets name two personas through different scope fields. For example, t008 has
customer = cus_03(portal_customer) andgroup = grp_tech(team_lead), and t009 hascustomer(portal_customer) andassignee(support_agent). Whichever field the loop reads last setsowner_id.
PR #30903 does not change this. Its fix covers only the scope fields that hold a person handle and are neither a lookup nor a select (account.owner, invoice.owner, task.assignee across the nine cards).
Wanted
owner_id is set only from a field that holds a person handle. A scope on an option or on a lookup to a non-user leaves the row owner as the runner's default. A row that names two personas through two handle fields gets one owner by a rule the build prompt states. Pin it with the service_desk card.
Level: QA tooling only, Clause-②: no, Metadata-protocol: none.
Dedupe (open and closed): "personaHandles owner_id" (0 hits); "scenario seed owner_id service_desk" (only #30882, the sibling it came from). New.
Reader: the domain:devx lane seat, from pm:queue, after PR #30903 lands. The seed loop is in flight there.
Activity
objectstack-fleet commentedon Nov 6, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p2 ·
domain:devx· 2026-11-06T10:55Z⛔ Not a claim.
owner_idfrom scope values that are not person handles. A runner defect from the first live runs (Scenario runner: the seed drops every owner-scoped row (handle left in the owner field; non-persona handles have no user), and the author loop never runs verify #30882). p2: it skews what a persona can see, so it skews grades.Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
domain:devx#2 · marchtian · session_017ihL2ebbZkr2QAEG1x7UC8
Claim: PM loop round 1
Session:
session_017ihL2ebbZkr2QAEG1x7UC8Account:
marchtianBranch:
claude/issue-30913-seed-owner-id-handlesWorktree:
objectstack-issue-30913Domain:
domain:devxSeat:
domain:devx#2File surface:
packages/qa/scenarios/src/runner/orchestrate.ts: the seed'sowner_idmapping. It is set only from a field that holds a person handle. A scope on an option or on a lookup to a non-user leaves the runner's default owner. A row that names two personas through two handle fields gets one owner, by a stated rule.packages/qa/scenarios/src/runner/prompts.ts:personaHandlesand the build prompt's Row ownership line, which stop listing non-handle values such aspublic.packages/qa/scenarios/test/, pinned with theservice_deskcard.loader.tsorschema.tschange. Those belong to Scenario cards: declare which scope fields hold a person handle, and drop only the turns a refused seed row actually feeds #30914 and Scenario cards: an explicit origin marker (generic / customer-derived / held-out) and a zh+en title, so the gallery exporter can export anything #30932.Container & model:
S,mode:subagentClause-②: no
Metadata-protocol: none
Responsibility: n/a — QA tooling (the scenario runner); independent contract review before ACCEPT
Thread-read: none
Premise: holds. At
origin/main6ddf827236:personaHandles(prompts.ts:43-51) still maps every direct-scope string value to its persona.body.owner_idfrom it (orchestrate.ts:409,:413).Serial constraints cleared: open PR build(scenarios): a minimal unpublished build, and a narrow ./score entry that reads no objectql or plugin-sharing #30938 (@objectstack/scenarios ships .ts-only exports, so a consumer's built entry cannot load
./score(ERR_MODULE_NOT_FOUND) or must inline it; give it a minimal unpublished build step and a narrow scorer entry #30915, the scenarios build) touchespackage.json, the build config,src/score/, the READMEs andtest/build/; this card touches none of them.objectstack-fleet commentedon Nov 6, 2026
domain:devx#2 · marchtian · session_017ihL2ebbZkr2QAEG1x7UC8
os-dev-report
{ "issue": 30913, "status": "needs_decision", "branch": "claude/issue-30913-seed-owner-id-handles", "pr": null, "premise_still_valid": true, "change": "None. The order's stop condition fired before any edit: under the ordered rule (owner_id only from a userHandleFields field) the personas of six of the nine cards lose every row they own today, and on a private object owner_id is the only lever an owner-scoped build has for those rows. Measured from the cards at origin/main 6ddf827236 by replaying seedApp's owner loop (orchestrate.ts:398-420) over every seed row, with every persona given an account, and the loader's inScope rule (loader.ts:366-369) for what each persona should see.", "owner_rule": { "today": "orchestrate.ts:400: ownerFields is every direct (non-path) scope field of every persona, id included. For each row, in the row's key order, a string value that personaHandles (prompts.ts:43-51) maps to a persona sets owner_id to that persona's user; a userHandleFields field also sets owner_id (the persona's user, or the admin for an unheld handle). The last such key in the row wins.", "ordered": "owner_id only from a userHandleFields field (a direct scope field that is not id and not a declared lookup or select). Every other row carries no owner_id in the body, so the platform stamps the inserting caller: the admin user the runner signs in as (insertRow posts with the admin session, app.ts:216-218).", "two_persona_rule": "Under the ordered rule no card has two handle fields on one row: userHandleFields is account.owner (crm), invoice.owner (invoice), task.assignee (project), and empty on the other six. The 15 two-persona service_desk tickets involve no handle field (customer, group and assignee are all declared lookups). The rule would be: the first handle field in the card's declared scope order (persona order, then rule order) sets owner_id. Today it never triggers." }, "stop_condition_evidence": { "why_owner_id_is_the_only_lever": [ "The scorer's access-security.row-scope tag passes only an object that is private (owner-scoped) or controlled_by_parent (src/score/scenario-tags.ts:332-345).", "On a private object a member sees its own rows. RLS only narrows and cannot make a private row visible (content/docs/permissions/rls.mdx:105-113). viewAllRecords short-circuits business RLS (plugin-security security-plugin-rls-filter.ts:177-184), so viewAllRecords plus an RLS policy does not narrow.", "Sharing-rule recipients are user, team, position, unit_and_subordinates, business_unit, queue and field (packages/spec/src/security/sharing.zod.ts:137-145). The runner gives a persona one user and one permission-set grant and nothing else (app.ts createPersona), and the field recipient needs a user-typed field. None of these names the persona's runtime user from a lookup to an employee, agent, customer or group row.", "The one remaining route is readScope org plus a literal RLS in the persona's permission set (custodian == 'e06'). That app hard-codes a seed row id. A faithful app that models 'my assets' cannot write it, because no seed row links a user to employee e06." ], "rows_lost_under_the_ordered_rule": "Each persona below owns these rows today, all in its scope, and would own none: asset_lifecycle employee 14 (asset 4, checkout 7, repair_order 2, disposal_request 1), dept_manager 33 (asset 28, department 1, employee 4); leave_balances employee 12 (leave_request 10, leave_balance 2), dept_manager 4 (employee); order_to_cash sales_rep 22 (account 2, sales_order 20); requisition_to_receipt requester 10 (purchase_requisition), dept_manager 1 (department); travel_expense employee 22 (trip_request 12, expense_claim 10), dept_manager 1 (department); service_desk support_agent 15 (ticket), team_lead 82 (ticket 78, agent 4), portal_customer 46 in scope (ticket 5, customer 1, ticket_comment 40), plus 197 comments it owns today that are out of its scope.", "overlap_no_single_owner_can_serve": "The cards' scopes nest. Every row an employee, requester or sales_rep persona sees, its manager persona also sees: asset 4, checkout 7, repair_order 2, disposal_request 1; leave_request 10, leave_day 28, leave_balance 2, approval_step 10; account 2, sales_order 20, order_line 40, invoice 16, activity 14; purchase_requisition 10, approval_step 15, purchase_order 9, po_line 11, goods_receipt 10; trip_request 12, expense_claim 10, claim_line 20, approval_step 18. On service_desk, 15 tickets, 30 comments and 14 ratings are seen by portal_customer and by team_lead or support_agent. One owner_id per row serves one of the two. The other persona's visibility has to come from something the card does not provide today (a manager hierarchy, a position, a share). That is a card or ruling question, not a runner fix." }, "per_card_effect": { "crm_account_with_contacts": "unchanged: account.owner is a handle field; ac1, ac3 to the sdr user, ac2 (u_tia) to the admin, before and after.", "invoice_with_line_items": "unchanged: invoice.owner is a handle field; 2 invoices to the sales_rep user, before and after.", "project_with_tasks": "unchanged: task.assignee is a handle field; 3 tasks to the contractor user, before and after.", "asset_lifecycle": "would change: asset 32 rows (employee 4, dept_manager 28), checkout 7, repair_order 2, disposal_request 1 (employee), department dep_sales (id scope) and employee 4 rows (dept_manager), each from the persona's user to the admin. The old mapping is also wrong today: 4 assets in both scopes go to whichever key comes last (employee).", "leave_balances": "would change: leave_request 10, leave_balance 2 (employee) and employee 4 rows (dept_manager), from the persona's user to the admin.", "order_to_cash": "would change: account 2, sales_order 20 (sales_rep, a lookup to employee e03), from the sales_rep user to the admin.", "requisition_to_receipt": "would change: purchase_requisition 10 (requester) and department dep_sales (dept_manager, id scope), from the persona's user to the admin.", "travel_expense": "would change: trip_request 12, expense_claim 10 (employee) and department dep_sales (dept_manager, id scope), from the persona's user to the admin.", "service_desk": "would change: ticket 98 rows (support_agent 15, team_lead 78, portal_customer 5), ticket_comment 237 public rows, agent 4 rows (team_lead) and customer cus_03 (portal_customer, id scope), from the persona's user to the admin. The old mapping is wrong today on ticket_comment (197 of the 237 are outside portal_customer's scope: public comments on other customers' tickets) and on the 15 two-persona tickets (whichever key comes last). The 15 involve no handle field." }, "gates": "none run: no diff, so dispatch-gates --commands has no change set to derive from; node_modules was not installed because no gate needed it.", "files_changed": [], "deviations": [ "Stopped before the work at the order's stop condition (a persona whose visibility came only from owner_id on a non-handle value would lose its rows). No commit, no push, no PR, no merge of origin/main.", "The measurement is a scratch replay of seedApp's owner loop and the loader's inScope over the card JSON (plain node, outside the repo). It is not a test and is not committed." ], "open_questions": [ { "question": "How should a persona whose rows are named by a lookup to a person record (employee e06, agent a02), or to a group or customer, get those rows on a private object once owner_id stops coming from non-handle values?", "options": [ "A. Card change, declared: each persona names the seed row it is (for example a personas[].record naming employee e06 or agent a02). The runner treats a direct lookup to that row as the persona's handle for owner_id, and the build prompt says so. Option values, id scopes and lookups to non-person rows (customer, group) stop setting owner_id. Cost: a schema.ts and card change in the lane that owns the cards, then a small runner change here (one declared rule, not a heuristic). portal_customer and team_lead still need a second mechanism (next question).", "B. Implement the order as written (userHandleFields only) and tell the build to express every other scope as readScope org plus a literal RLS in the persona's permission set. Cost: small runner change. Six cards' personas lose every owned row in an owner-scoped build, and a passing build has to hard-code seed ids, so the row-scope grades stop saying anything about a faithful app.", "C. Runner only, card semantics: owner_id is the first persona (declared order) whose every scope rule on that object holds for the row (the loader's inScope). It fixes the 197 out-of-scope public comments (237 to 40) and decides the 15 tickets by a stated rule. It also gives managers rows on path-scoped objects, and the second persona of each overlapping row still sees nothing. Cost: small runner change; it contradicts the order's reuse-userHandleFields instruction, so it needs the seat's word." ], "recommendation": "A, because it is the only option where owner_id comes from a value the card itself declares as a person, which is what the issue's Wanted asks for, and the six cards' personas keep their own rows in an owner-scoped build. It needs the card lane first: schema.ts and the card JSON are outside this order." }, { "question": "The cards' scopes nest (every employee row is also its manager's; 15 tickets are both portal_customer's and team_lead's or support_agent's). What should give the second persona those rows, given one owner_id per row?", "options": [ "A. A card ruling that a manager-type persona's scope is expressed through a platform hierarchy the runner provisions (a position or business unit per persona, with a unit readScope), so the build can model it faithfully.", "B. Grade only the owning persona's view on overlapping rows, and mark the other persona's questions on those objects not measured." ], "recommendation": "A, because B takes most manager-persona questions out of grading on five cards." } ], "out_of_scope_findings": [ "carrier: the seat (card lane) · noted, not filed · On asset_lifecycle, leave_balances, requisition_to_receipt, travel_expense and order_to_cash, the manager persona's path-scoped rows (checkout, leave_day, approval_step, order_line, invoice, claim_line and others) get no owner from the seed today, before and after this card. In an owner-scoped build those personas already see none of them unless the object is controlled_by_parent. Same cause as the open questions · dedupe words: scenario manager persona path scope owner; seed owner_id nested persona scope; scenario row scope owner-scoped manager" ], "mcp_calls": "2: mcp__github__issue_read (get, get_comments) on this card; reads only, no write tool", "api_writes": "1: this comment, through scripts/pm/post-stamped.mjs --comment=30913", "filed_issues": [], "security_findings": "none" }objectstack-fleet commentedon Nov 6, 2026
domain:devx#2 · marchtian · session_017ihL2ebbZkr2QAEG1x7UC8
Parked · 2026-11-06T14:14Z ·
pm:dispatched→pm:on-hold. The card's Wanted, as filed, would break six cards. It needs a design ruling before any runner change.What the dev measured (report 6686876786; no code written, nothing pushed):
owner_idcame only from handle fields, six of nine cards lose every row their personas own:asset_lifecycle,leave_balances,order_to_cash,requisition_to_receipt,travel_expenseandservice_desk. For example, onservice_deskthe support_agent loses 15 tickets, team_lead 82 and portal_customer 46.owner_idis the only thing that gives a persona those rows. The scorer's row-scope check needs a private orcontrolled_by_parentobject, and no sharing rule can name a persona's runtime user through a lookup to an employee, agent, customer or group row.visibility = publiccomments fall outside portal_customer's scope.owner_idper row can never cover both personas.The questions (the seat filed this card on a premise that turned out to be half right):
personas[].record). The runner then treats a direct lookup to that row as the persona's handle, and option values, id scopes and customer or group lookups stop settingowner_id. This is theschema.tsand card surface of Scenario cards: declare which scope fields hold a person handle, and drop only the turns a refused seed row actually feeds #30914 and Scenario cards: an explicit origin marker (generic / customer-derived / held-out) and a zh+en title, so the gallery exporter can export anything #30932.readScope). The alternative, grading only the owner and marking the other persona's turns not measured, would drop most manager questions on five cards.Out-of-scope note: on the four HR cards and
order_to_cash, the seed today gives the manager persona's path-scoped rows (checkout, leave_day, approval_step, order_line, invoice, claim_line and others) no owner, with or without this card. The cause is the same as question 2.The card stays assigned to
domain:devx#2, and the slot is released. These are design questions for the scenario epic, so they go to the maintainer.Restart-when: the maintainer has ruled questions 1 and 2, and the card-format change for question 1 has landed or is folded into #30914/#30932.