Repository navigation
feat(objectql,core): one tenant read predicate for Layer 0 and every driver (ADR-0131 C8 stage 8a) - #24389
Conversation
…er 0 and the driver (ADR-0131 D8)
…ult-posture isolation pins move to an explicit wall
…tion under the default posture move to an explicit wall; the single-posture explain divergence joins the invariant
…icate engine; ADR-0087 semantic entry and changeset for the widened driver read
… an explicit wall
…alled posture they model; control B's memory probe is the org-bound write
…dger, not the harness's audience-gate rows a single install now shows
📓 Docs Drift CheckThis PR changes 5 package(s): 26 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 151 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 f0c0fc895a85c965dddcd9f8314f5fd5f3858d71 && git checkout f0c0fc895a85c965dddcd9f8314f5fd5f3858d71
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b2e78701f779809b588096d82f5d86ba4ab5cfde cc6801a0e207fb22d960c8493304f28cb56b5568 && git checkout -B drift-repro b2e78701f779809b588096d82f5d86ba4ab5cfde && git merge --no-ff cc6801a0e207fb22d960c8493304f28cb56b5568
node scripts/docs-audit/affected-docs.mjs --json b2e78701f779809b588096d82f5d86ba4ab5cfde
|
Contract reviewServed-tier: Rendered by an isolated at-tier reviewer subagent of the adopting seat ( ① Derived judgments(a) Can the recorded verdict be forged? — No caller-reachable path. Right. (b) Under a wall, a wider driver scope for a caller who is neither the exempt PLATFORM_ADMIN nor the verified owner? — None found, with one env-only exception that is in the declared direction (see ②). Traced on
(c) (d) Writes. Right. The insert path passes (e) Engine-side fallback. Right; the env-unset case is (f) The semantic entry. Shape and registration right; one wording under-statement (non-blocking). (g) #15831 closes by pointer. Right. Its tripwire ② Semver level
③ Boundary flagsOpen questions of report 6254147295:
Deviations:
Out-of-scope findings:
Further non-blocking items from this review, each with a carrier:
Implemented-by: VERDICT: PASS |
Part of #15212
Clause-②: yes
ADR-0131 C8, stage 8a — one tenant read predicate (D8), per the stage plan 6251466182 and the claim 6251491183. #15831 (the driver leg ignored
platformGlobalObjects) is closed by pointer after this merges; its tripwire pin already reads 2 (measured below).What changed
computeTenantLayer0VerdictandTenantLayer0Inputmove topackages/core/src/security/tenant-layer0-verdict.ts(new@objectstack/coreexport).plugin-security'stenant-layer.tsre-exports and calls it; its Layer 0 filter, the recorded verdict and the intersect are byte-identical.buildDriverOptions(packages/objectql/src/engine.ts) no longer derivesDriverOptions.tenantId/tenantIdson its own. For a read, count, aggregate, update or delete it projects:OperationContext.tenantLayer0Verdict, parsed withTenantLayer0VerdictSchema), where the security layer composed its wall. That verdict carries the exemptPLATFORM_ADMIN, the verified owner's read lift and the delegator intersection;resolveEnginePosture), the context's organization and membership set, no admin exemption. This covers a system context, a by-id write, and a composition with no security layer.none→ no scope;organization→tenantId;organizations→tenantIds(with the active organization astenantId, which the driver keys on);deny→ the active organization if any (the narrowest scope a driver can express).writeTarget: trueand keeps the active organization astenantId(and thegroupmembership set) in every posture, so the row stamp and the counter scope hold. The engine has no upsert door (ENGINE_UPDATE_UPSERT_REMOVED); the SQL driver's own upsert cross-organization conflict refusal is reached only by direct driver callers and is untouched by this change. (Wording corrected by the epic seat from contract review 6254710664 ①(d).)orWhereNullarms insql-driver.ts(stage 8c-1),sharing-rule-service.ts, the ownership ceremony, and the 8b-0a / 8b-0b producers.buildDriverOptions,memory-tenancy-guard.ts). ADR-0087 semantic entrydriver-read-scope-follows-tenant-wall; changeset15212-8a-one-predicate.md.Clause-② — what widens
single, a caller naming an organizationPLATFORM_ADMINon a posture-permitting object, or the verified owner's readisolated/groupmember; walled system context naming an organizationConsequence, settled by ADR-0131 D8/D11: a
singleinstall holding more than one organization (theorg_budogfood fixture shape) loses the driver isolation it had by accident.singleenforces no wall. Organizations are kept apart onisolated/group.Mechanism assumptions (Section 2), measured
A1 — holds.
buildDriverOptionsderivedtenantIdfromexecCtx.tenantIdalone (andtenantIdsonly via the provider'sgroup). Implemented as above.A2 — holds.
@objectstack/objectqland@objectstack/plugin-securityboth already depend on@objectstack/core, andcoredepends on neither.check-undeclared-dep-importsis green.A3 — already true before this PR.
tenant-layer0-verdict-end-to-end.test.ts:181readstoBe(2)onmain. ADR-0131 D7 (C6) made the carve-out total: a declared object is planned with no organization column, so the driver has nothing to scope. This PR leaves that pin as it is.A4 — holds, with the table above.
A5 — measured. 8-M's heuristic counted up to 38 suites. The suites that actually failed were 13 files across 6 packages:
engine.test.ts,engine-external-tenant-scope,engine-reference-tenant-scope,engine-findone-contract,engine-aggregate-search;explain-enforce-parity;rule-criteria-org-scope,record-share-organization-stamp;chunk-part-record-concurrency;schedule-sweep-organization-scope,schedule-acting-organization,invitation-ledger-row-scope.Each pin that asserted cross-organization isolation now runs under an explicit
isolatedposture. Thesinglecases assert the widened answer. Inexplain-enforce-parity, theNATIVE_SCOPING_UNDER_SINGLE"measured divergence" joins the invariant: explain and enforcement now agree undersingle.A6 — holds. 1,008 changed lines in total (763 added, 245 deleted, 20 files): 609 in source, changeset and entry, and 399 in tests.
Landing points outside the claim's named list. These are the default-posture suites the claim's last bullet covers, measured above:
service-storage/src/chunk-part-record-concurrency.test.ts,plugin-sharing/src/record-share-organization-stamp.test.ts, and the three dogfood suites. No source file outside the claim surface was edited.Verification (on
cc6801a0e,mainmerged)@objectstack/rest: only the ObjectQL-with-tenant suite was run (52 passed), because the full suite is longer than the foreground cap. CI runs the rest.scripts/ablation-replace.mjs, test readssrc/): re-adding the active-organization scope on anoneverdict reddens exactly the two widening pins inengine-driver-read-scope.test.ts(2 failed, 11 passed). The restore was verified: blob equals HEAD andgit diff HEADis empty.dispatch-gates --commandsderived 100 families, and--ranreconciled 100 run with 0 NOT-MEASURED.pnpm --filter @objectstack/spec check:generatedis green, andcheck:adr-0087-registrationreads 1 non-breaking changeset.Acceptance notes
singleinstall, an org-bound session's reads no longer reach the memory driver with a tenant scope. The guard's seam 3 judges whatever scope arrives. Writes still carry the target, so the memory driver still refuses them.@objectstack/verifyharness comment is now stale. It says the audience-gate invitations underorg_verify_audience_gateare invisible to fixtures (harness.ts,inviteForAudienceGate). Onsinglethey are now visible to the owner, andinvitation-ledger-row-scopefilters them. The comment was left as it is because it is outside this stage's surface.isolatedaround the bind. They model a walled deployment without booting the enterprise organizations plugin, and the driver scope now follows the engine's posture rather than the context's organization alone.Generated by Claude Code