Skip to content

Commit e2c2355

Browse files
fix(dogfood): the criteria-less legacy sharing-rule row carries the organization the admin acts in (#24913)
Fixes #24879 Clause-②: no **The hourly full run's `Dogfood Regression Gate (1/3)` red (run 39419131283): one dogfood fixture went stale against the ruled retirement of the organization-less sharing-rule readers. This PR repairs the fixture. No product code changes, and no assertion changes.** ## What changed One file, one case: `packages/qa/dogfood/test/sharing-rule-criteria-required.dogfood.test.ts`, "a criteria-less rule already IN the table shares nothing and loses its grants". - The case writes its legacy `sys_sharing_rule` row at the driver seam, and that row had no `organization_id`. - Since `7cacad5348` (PR #24828, 18.x step 7 part 7a-3), no admin read resolves a rule with no organization. `findRuleRow` and `findRuleRowByName` read the caller's own organization only (ADR-0131 D1/D8). So `POST /sharing/rules/srule_3896_legacy/evaluate` answered `404 RULE_NOT_FOUND` before the criteria guard this case pins was reached. - The row now carries `organization_id`: the organization the admin acts in. It is read off the admin's own session (`GET /auth/get-session`, `session.activeOrganizationId`), which is the field the `evaluate` request's context is resolved from. No id is hard-coded. - The case asserts that `get-session` answers 200 and that the organization id is a non-empty string, so a missing id fails loudly instead of writing an organization-less row again. - That is the shape a pre-gate row has once the organization-ownership ceremony attributes it (ADR-0131 D10), which is the triage's done-when. - Every assertion is unchanged: `evaluate` answers below 300, matches 0 records, and materialises no `sys_record_share` grant. **The file's other writes do not share the cause.** The direct-insert case (`ql.insert('sys_sharing_rule', …, { context: SYS })`) is refused for its missing criteria. It was green on `main` before this change (4 of 5 passed), and it is unchanged. Every other system-context call in the file is a read. There is no skip and no retry, and a 404 is not accepted anywhere. The retirement in PR #24828 stays as it is, and nothing under `packages/plugins/plugin-sharing/**` or `packages/spec/**` is touched. ## Evidence Each row ran `pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/sharing-rule-criteria-required.dogfood.test.ts`, after building the dogfood closure (`turbo run build --filter=@objectstack/dogfood`) at the same commit. The test file, `test/showcase-boot.ts` and `vitest.config.ts` are byte-identical on the first three trees. | tree | result | |---|---| | `abd5acdaf7` (`7cacad5348^`, the commit of the last green hourly run, 39412016980) | `Tests 5 passed (5)`. The boot logs `rule has no usable criteria — matching NO records instead of every record` for `legacy_match_all_3896`. | | `7cacad5348` (PR #24828) | `Tests 1 failed \| 4 passed (5)`: `AssertionError: expected 404 to be less than 300` at `:153` | | `c2d4072281` (`main`, this branch's base) | the same red at `:153` | | `30629ffe1f` (this change) | `Tests 5 passed (5)` | | `2ecc76b48e` (head: this change merged with `main` at `fcb40ad673`) | `Tests 5 passed (5)`, with the same `matching NO records` line for `legacy_match_all_3896` | - **Without the organization on the row:** on the committed change, deleting only the `organization_id: organizationId,` line turns the case red again, with `expected 404 to be less than 300`. - The mutation went through `scripts/ablation-replace.mjs`: anchor count 1 to 0, blob `c22aa9f438f5` to `c129bbc072f1`. - The restore was proven: the blob equals HEAD (`c22aa9f438f5`), and `git diff HEAD` is empty. - **The gate's own invocation:** `OS_TEST_SHARD=1/3 pnpm turbo run test --filter=@objectstack/dogfood` ran on `2ecc76b48e`. The result was `Test Files 96 passed (96)`, `Tests 772 passed (772)`, `Tasks: 67 successful, 67 total`. - `VITEST_MAX_WORKERS` was 2 here, where the runner's computed cap is 3. That is a concurrency difference only. - The file falls in shard 1. Computed with vitest 4.1.11's own shard algorithm over this tree's 287 files: shard 1 holds 96 files, including this one. The 772 matches the red run's count. - **Variants:** the dogfood vitest projects are `isolated` and `shared-showcase`, and this file runs only in `isolated` (it is not in `SHARED_SHOWCASE`). The two variants run are the file alone and its gate shard, shown above. The file's one boot is posture `single`, with the Default Organization. ## Gates (head `2ecc76b48e`) - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 56 commands. All 56 ran, each with its exit code captured before any pipe. - 55 exited 0 on the first run. - `pnpm check:dual-build-cjs-loads` first answered `PREREQUISITE NOT MET` (exit 3), because this worktree had no `dist/` for 8 packages outside the dogfood closure. After building those packages, it exited 0 ("112 published require entry point(s) across 69 package(s) load"). - The 11 declared-WIDE families (`check:init-service-contract`, `check:live-db-isolation`, `check:meta-type-normalized`, `check:optional-error-sink`, `check:resume-authority-declared`, `check:route-envelope`, `check:runner-env-posture`, `check:settings-bind-window`, `check:startup-registry-verdict`, `check:verify-stand-in`, `check:wildcard-fallthrough`) all exited 0. - Reconciliation, `--ran` with the exit codes recorded: `Run reconciliation — 56 derived, 56 run, 0 NOT-MEASURED, 0 UNRUN.` It derives the zero from the 56 recorded exit codes. - `pnpm --filter @objectstack/dogfood run typecheck`: exit 0. The tsc program lists this file (`--listFilesOnly`, 1 hit). - `pnpm lint` (`eslint . --no-inline-config`) over the whole tree: exit 0, with no findings. - `Dogfood Regression Gate` does not run on `pull_request` or `merge_group`. Its first CI reading of this change is `main`'s push and hourly battery after the merge, so the local red-then-green above is the pre-merge reading. ## Changeset `skip-changeset`: the diff is one test file in `@objectstack/dogfood`, a `private: true` package that publishes nothing. No published package's `files[]` changes. ## Acceptance notes - The dispatch named "isolated and group" variants. As measured, the vitest projects are `isolated` and `shared-showcase`, and the file's boot is posture `single`. No `group` posture boot is in this file. The gate's grouping is its shard, and both the file alone and its shard ran green. - The organization is read through the session (`GET /auth/get-session`), not through `sys_member`. `resolveAuthzContext` reads `session.activeOrganizationId` into the request context's `tenantId` (`packages/core/src/security/resolve-authz-context.ts`), and `SharingRuleService.callerOrgId` reads that value back. So the row lands in the organization that the `evaluate` request is scoped to. --- _Generated by [Claude Code](https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent eee65af commit e2c2355

1 file changed

Lines changed: 16 additions & 0 deletions

File tree

‎packages/qa/dogfood/test/sharing-rule-criteria-required.dogfood.test.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,21 @@ describe('#3896 — a sharing rule POSTed without criteria is rejected, never ma
134134
// defense-in-depth the flip bought. A true legacy row predates all of it:
135135
// write it at the DRIVER seam, the same level a pre-gate deployment's
136136
// storage actually holds it.
137+
//
138+
// The row carries the organization the admin acts in. That is the shape a
139+
// pre-gate row has once the organization-ownership upgrade ceremony has
140+
// attributed it (ADR-0131 D10), and the only shape a certified deployment
141+
// holds. A row with no organization is one no admin read resolves any more
142+
// (ADR-0131 D1/D8: the caller's own organization only), so `evaluate`
143+
// would answer 404 before the criteria guard this case pins is reached.
144+
// The organization is read off the admin's own session, the field the
145+
// request context of the `evaluate` call below is resolved from.
146+
const me = await stack.apiAs(admin, 'GET', '/auth/get-session');
147+
expect(me.status, 'get-session answers for the admin').toBe(200);
148+
const organizationId = ((await me.json()) as { session?: { activeOrganizationId?: string | null } })
149+
.session?.activeOrganizationId;
150+
expect(organizationId, 'the admin acts in an organization').toMatch(/^\S+$/);
151+
137152
const id = 'srule_3896_legacy';
138153
const driver = (ql as any).getDriverForObject('sys_sharing_rule');
139154
expect(driver, 'sys_sharing_rule driver must be resolvable').toBeTruthy();
@@ -147,6 +162,7 @@ describe('#3896 — a sharing rule POSTed without criteria is rejected, never ma
147162
recipient_id: 'sales_rep',
148163
access_level: 'read',
149164
active: true,
165+
organization_id: organizationId,
150166
});
151167

152168
const res = await stack.apiAs(admin, 'POST', `${RULES}/${id}/evaluate`, {});

0 commit comments

Comments
 (0)