Skip to content

Commit 10b8250

Browse files
test(dogfood): pin the reason #25591's verdict gives a staff share on a real showcase_project record (#25604)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuDsgJg9TZyi9CRq2YkCkKxa)_ Fixes #25602 Clause-②: no Metadata-protocol: none `main`'s `Dogfood Regression Gate (1/3)` went red when #25591 landed (`4a251927c`, `a57643d3d`) on one test, `showcase-d3-d4-capabilities.dogfood.test.ts:135`. #25605 cleared it by loosening the pin to the prefix "consulted only for an external member", still on a record id naming no row. That prefix also matches the incidental "that organization could not be resolved" reason, and it stays green if the verdict admits staff. This PR pins the ruled reason for a staff recipient on a real record, and keeps the contrast the pin exists to draw. Test-only, in the private `@objectstack/dogfood` package (`skip-changeset`). Branch `claude/project-thread-pluvu5` is harness-assigned and does not follow `claude/issue-N-…`; the card is #25602. ## Why the new reason is the intended one #25591 (letter A of the ruling on #25549) made the D7 pre-flight in `plugin-sharing` read the recipient on an effective-`public` object with a real `owner_id`: an `external` member of the record's organization is admitted, every other recipient is refused with "… consulted only for an external member of the record's organization, and the recipient is not one", and a grant whose organization cannot be resolved is refused with "… and that organization could not be resolved". The old pin granted on `showcase_project` (`public_read_write`) to a record id naming no row, so on today's `main` it is refused for the third reason, an incidental one. Nothing in `plugin-sharing` changes here. ## What changes | Where | Before | After | |:--|:--|:--| | `beforeAll` | — | inserts one `showcase_project` (`D34 Project`, the file's own account) and reads the signed-up member's `sys_user` id | | third case, the contrast | `recordId: 'pj_absent'`, recipient `u_d34_probe`, expects the prefix "... has a public sharing model, so a share row on it is consulted only for an external member" (#25605) | the real project and the member; asserts the project carries an organization and the member's `sys_member.role` there is `member`; expects "… has a public sharing model, so a share row on it is consulted only for an external member of the record's organization, and the recipient is not one" | | comments | "refused for the OWD", "computed from the OBJECT before any record is read", "caller- and record-independent", plus #25605's note that `u_d34_probe` resolves to no organization | the D7 verdict on a public object reads whether the recipient is an `external` member of the record's organization; the retired rules' `position` recipients were that organization's staff, which is the population the contrast now uses. The header's `showcase_project` sentence says "nothing to widen for the organization's staff" | The `showcase_contact` half (a share on a private-OWD object is accepted) is unchanged. ## Verification (local, worktree on `main` `a57643d3`, then merged with `main` `9f390945`) - The file, at `c29e83c4`: `os-verify-lock.sh -c 'pnpm --filter @objectstack/dogfood exec vitest run test/showcase-d3-d4-capabilities.dogfood.test.ts'` → `VERDICT command-exit 0`, Tests 5 passed (5). - Ablation 1, the pin tells the reasons apart (`scripts/ablation-replace.mjs`, wrap mode): `recordId: pjId` → `recordId: 'pj_absent'`, anchor 1 → 0, blob `3b0184f2` → `1593274b`. Red, 1 failed / 4 passed, received "… and that organization could not be resolved". Restored: blob equals HEAD, `git diff HEAD` empty. - Ablation 2, the pin catches the verdict admitting staff: in `plugin-sharing/src/sharing-service.ts`, `if (recipient === 'external') return null;` → `if (recipient === 'external' || recipient === 'internal') return null;` (hold mode, blob `c1f616f5` → `d0e5e5e9`), rebuilt `plugin-sharing`; `ablation-dist-preflight.mjs` found the marker in both `dist/` bundles. Red, 1 failed / 4 passed, "promise resolved … instead of rejecting". Restored: blob equals HEAD, rebuilt (cache hit, 6 files), preflight `--absent` passes, tree clean; the file is green again (5/5). - `pnpm exec eslint --no-inline-config` on the file: exit 0. `tsc --noEmit --listFiles` for `@objectstack/dogfood` under the lock: exit 0, the file listed once. - Final head `61e0a268` (this commit merged with `main` `9f390945`, which touches only `core`'s import coercion), closure rebuilt under the lock: - Full dogfood battery, `os-verify-lock.sh -c 'pnpm --filter @objectstack/dogfood test'` (no shard, no path): `VERDICT command-exit 0`, Test Files 292 passed / 3 skipped (295), Tests 2508 passed / 18 skipped (2526). - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 58 commands; all 58 exit 0, each exit captured before any pipe. Two first answered exit 3 (PREREQUISITE NOT MET: the pinned control commit for `check-plugin-teardown-shape --self-test` absent from the shallow clone, and seven dists outside the dogfood closure for `check:dual-build-cjs-loads`); after fetching that commit and building those seven packages both re-ran exit 0. `--ran` reconciliation: 58 derived, 58 run, 0 NOT-MEASURED, 0 UNRUN. - CI skips the `Dogfood Regression Gate` shards on `pull_request` and `merge_group`, so the CI proof for shard 1/3 is `main`'s first push battery after this lands. - Round 2, head `acbef37e` (merged with `main` `8801f2bb`; one conflict, in this file's contrast block, resolved to this branch's side): the file 5/5; ablation 1 red as before; ablation 2b, under one ablated `plugin-sharing` dist that admits internal recipients, this file red (promise resolved instead of rejecting) while `main`'s #25605 version of it is green 5/5; eslint and dogfood tsc exit 0; `dispatch-gates` 58/58 exit 0; full dogfood battery green (counts in report round 2 on #25602). ## Acceptance notes - **Prose that #25591 left stale, outside this card's surface, no checker reads it:** the header of `examples/app-showcase/src/security/sharing-rules.ts` says that on `showcase_project` / `showcase_task` "no sharing rule can ever take effect there", and `examples/app-showcase/test/inert-wirings.test.ts` §6's docblock describes the refusal as the object-only one. Since #25591 a rule there is effective for external recipients. Noted, not filed. - **Merge freeze:** opened as a draft during the 18.1.0 freeze; it is landed only after the coordinator relays the unfreeze. --- _Generated by [Claude Code](https://claude.ai/code/session_014rFBGqoBLiroi35e82xEa7)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2351c8d commit 10b8250

1 file changed

Lines changed: 37 additions & 19 deletions

File tree

‎packages/qa/dogfood/test/showcase-d3-d4-capabilities.dogfood.test.ts‎

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
// [#9237] The compound rule moved from `showcase_project` to
99
// `showcase_contact`, and the D3 proof got its missing half with it. On
1010
// `showcase_project` (OWD `public_read_write`) sharing has nothing to
11-
// widen, so every grant the rule reconciled was REFUSED
12-
// (`SHARING_NOT_ENABLED`, ADR-0111 D7) — this file could assert the
13-
// criteria compiled and matched, and never that anything was ENFORCED,
14-
// while its own header claimed both. `showcase_contact` is OWD `private`,
15-
// so the grant lands and the enforcement half is now measured rather than
16-
// implied.
11+
// widen for the organization's staff, so every grant the rule reconciled
12+
// was REFUSED (`SHARING_NOT_ENABLED`, ADR-0111 D7; since #25549 a share
13+
// there is consulted for an `external` member alone) — this file could
14+
// assert the criteria compiled and matched, and never that anything was
15+
// ENFORCED, while its own header claimed both. `showcase_contact` is OWD
16+
// `private`, so the grant lands and the enforcement half is now measured
17+
// rather than implied.
1718
// • ADR-0058 D4 — an RLS `check` clause validates the write POST-IMAGE: a
1819
// contributor cannot reassign an invoice they own to a different owner.
1920
//
@@ -53,6 +54,8 @@ describe('showcase: D3 compound sharing (#1887) + D4 RLS check', () => {
5354
let ql: any;
5455
let token: string;
5556
let invId: string;
57+
let pjId: string;
58+
let memberId: string;
5659

5760
beforeAll(async () => {
5861
stack = await bootShowcase({
@@ -77,6 +80,11 @@ describe('showcase: D3 compound sharing (#1887) + D4 RLS check', () => {
7780

7881
// An invoice owned by the member (system insert sidesteps authoring rules).
7982
invId = idOf(await ql.insert('showcase_invoice', { name: 'INV-D34', account: acct, owner: MEMBER, status: 'draft' }, SYS));
83+
84+
// [#25549] A real project, and the member's user id: the D7 contrast on
85+
// `showcase_project` is judged against a record's organization and a recipient.
86+
pjId = idOf(await ql.insert('showcase_project', { name: 'D34 Project', account: acct, status: 'planned' }, SYS));
87+
memberId = String((await ql.findOne('sys_user', { where: { email: MEMBER }, context: SYS.context }))?.id ?? '');
8088
}, 90_000);
8189

8290
afterAll(async () => { await stack?.stop(); });
@@ -108,8 +116,9 @@ describe('showcase: D3 compound sharing (#1887) + D4 RLS check', () => {
108116
});
109117

110118
it('[#9237] the grant the rule reconciles is one the gates CONSULT — not refused as inert', async () => {
111-
// The half this proof could never carry on `showcase_project`. The verdict
112-
// is caller-independent (ADR-0111 D7) and computed from the OWD alone, so
119+
// The half this proof could never carry on `showcase_project`. On a
120+
// private object the verdict is caller-independent (ADR-0111 D7) and
121+
// computed from the OWD alone, so
113122
// it is asserted directly rather than inferred from a reconcile count that
114123
// an empty recipient expansion would leave silently at zero — which is
115124
// exactly how the sibling rule on `showcase_project` produced no boot WARN
@@ -122,21 +131,30 @@ describe('showcase: D3 compound sharing (#1887) + D4 RLS check', () => {
122131
expect(granted, 'a share row on a private-OWD object is accepted').toBeTruthy();
123132

124133
// The contrast that names the retired rules' defect, on the object they
125-
// used to sit on: same call, same caller, refused for the OWD. The
126-
// record id deliberately names no row — the D7 verdict is computed from
127-
// the OBJECT before any record is read, which is precisely why it is
128-
// caller- and record-independent. Both the code and the object are
129-
// asserted, so a refusal arriving for some other reason cannot pass.
130-
// [#25549] A public object's share row is now consulted for an EXTERNAL
131-
// member of the record's organization, so the public-OWD refusal names the
132-
// recipient's standing: `u_d34_probe` resolves to no organization, and the
133-
// row would still never be consulted for it.
134+
// used to sit on: same call, same caller, refused. [#25549] On a public
135+
// object the D7 verdict is no longer the OWD's alone: the external OWD
136+
// floor consults a share there, so the verdict reads one fact about the
137+
// recipient — is it an `external` member of the RECORD's organization?
138+
// The retired rules' `position` recipients expand to that organization's
139+
// staff, so the probe is a real project and a staff member of its
140+
// organization (both preconditions asserted). Both the code and the reason
141+
// are asserted, so a refusal arriving for some other reason cannot pass —
142+
// a record the verdict cannot place answers "that organization could not
143+
// be resolved" instead.
144+
const pjOrg = (await ql.findOne('showcase_project', { where: { id: pjId }, fields: ['organization_id'], context: SYS.context }))?.organization_id;
145+
expect(pjOrg, 'fixture: the project carries an organization').toBeTruthy();
146+
const membership = await ql.findOne('sys_member', {
147+
where: { user_id: memberId, organization_id: pjOrg }, fields: ['role'], context: { ...SYS.context, tenantId: pjOrg },
148+
});
149+
expect(membership?.role, 'fixture: the member is staff of that organization, not external').toBe('member');
134150
await expect(
135151
sharing.grant(
136-
{ object: 'showcase_project', recordId: 'pj_absent', recipientType: 'user', recipientId: 'u_d34_probe', accessLevel: 'read' },
152+
{ object: 'showcase_project', recordId: pjId, recipientType: 'user', recipientId: memberId, accessLevel: 'read' },
137153
{ isSystem: true },
138154
),
139-
).rejects.toThrow(/SHARING_NOT_ENABLED: 'showcase_project' has a public sharing model, so a share row on it is consulted only for an external member/);
155+
).rejects.toThrow(
156+
/SHARING_NOT_ENABLED: 'showcase_project' has a public sharing model, so a share row on it is consulted only for an external member of the record's organization, and the recipient is not one/,
157+
);
140158
});
141159

142160
// ── ADR-0058 D4 ────────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)