Skip to content

QA run · access-security.record-share-grant-revoke · 1/1 · 4a7b3604 · 2026-08-20 #10085

Description

@os-warren

Dedicated follow-up run owed by #9702. The item became executable for the first time when PR #9697 (#9237) retargeted its evaluate clause; per #9308's sequencing ruling this is a run at a NEW sha on main, deliberately not attached to wave #9296's pinned e4e5c6e3 baseline.

Item verdict: pass — 7 of 7 acceptance clauses passed, reproduced on two independent fresh boots.

Environment fingerprint

framework sha4a7b3604c319d28e3ae7fbc98a4bc1486602044c (4a7b3604c)
ancestry check4012a70cd (#9697/#9237) and 06f9848f9 (#9789/#9308) are both ancestors of the run sha — verified with git merge-base --is-ancestor
objectui pin82a94170c4058d451ce3ac179d99296d90554479console never built and never exercised; the item's surface is api
appshowcase, 132 seeded rows, 47 plugins
boot Aport 3702, file:/tmp/run9702/data2.db, --seed-admin, fresh DB
boot Bport 3703, file:/tmp/run9702/data3.db, --seed-admin, fresh DB, independent process
runneros-warren, session session_01PnJHU45vPJj5UQrxe946Bx, model claude-opus-5
date2026-08-20
concurrencyThis round was executed SEQUENTIALLY by a single reader — no subagent fan-out. The findings below stand on their own; the claim "nothing else was missed" does not carry the independence a parallel round would give it.

Scope

Selector access-security.record-share-grant-revoke — resolved by node scripts/checklist-select.mjs to exactly 1 runnable item, P1, surface api, revision 3.

⛔ Deliberately not widened to #9308's newly unblocked items (approvals.per-group-signoff, approvals.viewer-gating-submitter-side, approvals.ooo-delegation-reroute, access-security.share-link-capability-tokens, access-security.fls-mask-and-strip). #9702 reserves the combine-or-not decision for the wave PM in its own words; taking it here would be a decision this seat does not hold. See "Open for the wave PM" below.

Per-clause verdicts (revision 3)

Oracle for every clause is api — live server traces against a real boot. No screenshot or DOM was consulted; none was needed.

#clauseverdictoracle evidence (text)
1PRE-grant isolation holdspassBefore any share exists, B's GET /api/v1/data/showcase_private_note/<A id> answers 404RECORD_NOT_FOUND ("Record Nlji2uPrSLttIcwJ not found in showcase_private_note") — a not-found-shaped denial, which the clause admits. B's list answers 200 with total 0.
2the manual grant lands as a sys_record_share rowpassPOST .../shares201, body {id: shr_2bdd3fd4-…, object_name: showcase_private_note, record_id: Nlji2uPrSLttIcwJ, recipient_type: "user", recipient_id: <B>, access_level: "read", source: "manual", source_id: null, granted_by: <A>}. A system-context read of sys_record_share filtered on record_id returns exactly that one row with the same fields.
3the grant widens B both ways and stays scopedpassB by-id → 200 (title: "A share probe pass2"); B list → 200, total 1, contains the id. C by-id → 404RECORD_NOT_FOUND; C list → 200, total 0, does not contain it.
4the shares list is management-gatedpassA GET .../shares200 with the one grant present. B (visible, not manager) POST .../shares403PERMISSION_DENIED: "managing shares on showcase_private_note/… requires record ownership or Modify All Data (ADR-0111 D1)". Probed twice — B granting C, and B granting themselvesedit — both 403.
5revoke retracts accesspassDELETE .../shares/<shareId>204 (empty body). B's next by-id → 404RECORD_NOT_FOUND. System-context sys_record_share filtered on record_idtotal 0, row gone.
6revoke is record-scopedpassDELETE /data/showcase_private_note/<A SECOND note id>/shares/<shareId>404NOT_FOUND: "share shr_2bdd3fd4-… not found on showcase_private_note/gDsNat3RCg1Ilp1T". Survival re-read of sys_record_share/<shareId>200, row intact; B's by-id read still 200. ⚠️ Ordering note below — this probe was run before clause 5's revoke, deliberately.
7rule evaluate reconciles the audiencepassAdmin assigns manager to persona B (POST /api/v1/data/sys_user_position {user_id: <B>, position: "manager"} → 201). POST /api/v1/sharing/rules/share_key_account_qualified_contacts_with_managers/evaluate200{ruleId: "srule_bae1c31a-730e-4114-b945-e0747a7427c9", matchedRecords: 1, expandedUsers: 2, grantsCreated: 1, grantsUpdated: 0, grantsRevoked: 0}. Rule-sourced sys_record_share rows go 1 → 2, both on showcase_contact/WJ5Cot2OydOKqqYu (= Nora West, company Northwind, stage qualified), each source: "rule", source_id: "srule_bae1c31a-730e-4114-b945-e0747a7427c9" — which equals the sys_sharing_rule ROW id read back separately, and is not the rule name. This is the value revision 2 corrected the clause to demand, and the implementation writes it.

Supplementary, beyond the clauses: persona B, once holding manager, reads the matched contact GET /api/v1/data/showcase_contact/WJ5Cot2OydOKqqYu200, where the same read answered 403 before the assignment+evaluate. The rule-materialised grant is one the read gate actually consults end to end — not merely a row in a table.

Reproduction

No fail, so no reproduction rule is owed. Both boots ran the identical sequence and agreed clause for clause:

  • boot A (3702): note Nlji2uPrSLttIcwJ, second note gDsNat3RCg1Ilp1T, share shr_2bdd3fd4-f00d-4cc2-8b45-ecde7fde39ff, rule row srule_bae1c31a-730e-4114-b945-e0747a7427c9
  • boot B (3703): note YU4FbwJcDP61GmvX, second note Wmy4HDQnMbivIls_, share shr_6115174c-faa0-4b22-bdd0-371b25953c6d, rule row srule_ee5195ce-7914-4693-8677-41a6f36c6cdf; evaluate → {matchedRecords: 1, expandedUsers: 2, grantsCreated: 1}, same shape.

Checklist-accuracy findings → item revised to 4 (PR #10083)

Neither is a product defect; both are the checklist being wrong about itself, found the only way they could be — by executing the item.

  1. Clause 6's evidence requirement was unsatisfiable in the steps' own order. The steps put the revoke-scope probe after the real revoke, but the clause asks for the mis-scoped DELETE to be refused and the share to still exist (re-read). Once the share is revoked the shareId is gone, so the refusal a literal runner observes is the ordinary missing-share 404 — indistinguishable from a correct record-scope refusal, and proving nothing. This run reordered the probe ahead of the revoke, which is what made clause 6 measurable at all; revision 4 writes that ordering into the steps and adds the second note the probe needs.

  2. The manager-position fixture note now mis-teaches. It says a grantsCreated 0 reconcile means an empty recipient expansion, i.e. a fixture gap. Measured on a stock boot at this sha, that reading is wrong: the seeded dev admin already holds manager (sys_user_position row usp_showcase_admin_manager) and the boot backfill already materialises the grant, so a bare evaluate answers grantsCreated 0 out of idempotence. Confirmed directly — an evaluate run before assigning any extra persona returned {matchedRecords: 1, expandedUsers: 1, grantsCreated: 0} with the rule-sourced row already present from boot. Revision 4 says so and points the runner at expandedUsers/matchedRecords instead.

Neighbouring ground — observed, not chased

#9702 is explicit that these are "nobody has looked since the fixtures moved", not assertions that anything is wrong. Recorded and left alone:

Environment notes (cost this run a cycle each; no card owed)

  • os dev has no --no-ui flag.objectstack dev --no-ui exits with Error: Nonexistent flag: --no-ui and prints usage — which, backgrounded, looks exactly like a server that booted and then died silently. RUNNER.md's absence-inference row documents --no-ui for serve, and the distinction is easy to carry across. Omit it on dev.
  • The CLI entry point is packages/cli/bin/run.js, not packages/cli/dist/index.js. Invoking the dist entry directly exits 0 with zero output — a silent no-op that reads like a crashed boot. Use node_modules/.bin/objectstack (or pnpm exec).
  • $select on the data API takes a comma-separated field list; a JSON array is rejected with INVALID_FIELD. $filter, by contrast, must be JSON.

Close-out

No product defect was observed, so nothing is extracted. The two checklist-accuracy findings are closed by PR #10083 (revision 4) rather than parked, since they are edits to this very item and §3 of the runner playbook puts that on the runner.

Parked-on: #9296 · the #9237/#9308 seed moves leave neighbouring items unlooked-at; the widen-or-combine call is the wave PM's

Open for the wave PM

Both #9237 and #9308 have landed, and #9308 revised five further items whose fixtures moved on the same tree. #9702 reserves the combine decision explicitly ("that is the wave PM's call, not this seat's"), so this run stayed at the stated minimum of one item. If a combined round is wanted, those five plus this one are one tree at one post-#9789 sha — this run's environment recipe above is reusable as-is.

Refs: #9702 (the card) · PR #10083 (revision 4) · #9697 / #9237 · #9308 · #9296


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions