Repository navigation
fix(objectql,driver-sql): the tenant-audit warning skips an insert whose rows carry their organization, and the engine declares the D8 wall-exemption to the driver - #25274
Conversation
…ws carry their organization; declare the D8 wall-exemption to the driver The SQL driver's [tenant-audit] line no longer fires for a create or bulkCreate whose every row already carries its tenant column: an insert is confined to the organizations its rows name. Update, delete, upsert and the predicate writes with no tenantId still warn. The engine fills in bypassTenantAudit when the recorded Layer 0 verdict is none and the same ladder without the exemption would have walled the operation, so an exempt platform admin's predicate write is no longer told to declare an option it cannot set. Diagnostics only: no write changes. Claude-Session: https://claude.ai/code/session_01S9iQu7nAw2Ce5EXSzDVTho Co-authored-by: Claude <noreply@anthropic.com>
…nd the engine's wall-exemption declaration Claude-Session: https://claude.ai/code/session_01S9iQu7nAw2Ce5EXSzDVTho Co-authored-by: Claude <noreply@anthropic.com>
…nce an insert carrying its organization is no longer audited Claude-Session: https://claude.ai/code/session_01S9iQu7nAw2Ce5EXSzDVTho Co-authored-by: Claude <noreply@anthropic.com>
… reads the wall-exemption Claude-Session: https://claude.ai/code/session_01S9iQu7nAw2Ce5EXSzDVTho Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 24 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 e5150730fb8d51cc44a0dfab3026cba84403a8fe && git checkout e5150730fb8d51cc44a0dfab3026cba84403a8fe
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1eb1afae071530f66f4d93d416e7f452ceab3de3 1313821b578acc2702e364c74aac8c7b1400b4b5 && git checkout -B drift-repro 1eb1afae071530f66f4d93d416e7f452ceab3de3 && git merge --no-ff 1313821b578acc2702e364c74aac8c7b1400b4b5
node scripts/docs-audit/affected-docs.mjs --json 1eb1afae071530f66f4d93d416e7f452ceab3de3
|
…guard and the wall-exemption declaration Claude-Session: https://claude.ai/code/session_01S9iQu7nAw2Ce5EXSzDVTho Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Reviewed at 2026-10-22T19:35Z. Inputs: card #25230 (body, claim 6347729103, dev report 6349387651), record 6341425088 on #25210 (③ item 3 only), this PR's body, file list and net diff against its merge base The head moved after the dev report. The report and the review brief both name ① Derived judgments
② Semver levelRIGHT.
③ Boundary flagsFAIL item 1: the head conflicts with
Check-run tally at posting:
Dev deviations: both ACCEPTED.
Observations, not blocking:
Implemented-by: VERDICT: FAIL |
…-44gu3q # Conflicts: # content/docs/permissions/tenant-audit-census.mdx
Claude-Session: https://claude.ai/code/session_01S9iQu7nAw2Ce5EXSzDVTho Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: The seat's review of record on the head that answers the FAIL of 6349582997 (on ① Derived judgments
② Semver levelUnchanged: two ③ Boundary flags
Implemented-by: VERDICT: PASS |
席位核对ACCEPT ·
|
…etes, as the SqlDriver twin does The SQLite WASM driver inherits SqlDriver.create, so an insert whose row carries its organization is no longer audited there either. Claude-Session: https://claude.ai/code/session_01S9iQu7nAw2Ce5EXSzDVTho Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: The seat's review of record on the head that answers ① Derived judgments
② Semver levelUnchanged: test-only; no changeset moves. ③ Boundary flags
Implemented-by: VERDICT: PASS |
|
Landing note · Head Generated by Claude Code |
Requested by Bill · project thread
Fixes #25230
Part of #15212
Clause-②: no
Metadata-protocol: none
What changes
Diagnostics only. No write changes: no statement, scope, row or refusal moves. The
[tenant-audit]prefix and text are byte-identical.SqlDriver.auditMissingTenanttakes the inserted rows from the two insert doors (create,bulkCreate). When every row carries a non-empty tenant column (empty is spelled asinjectTenantOnInsertspells it), the line is skipped: an insert is confined to the organizations its rows name. One row without the column keeps the line.update,delete,updateMany,deleteMany,bulkDeleteandupsertwith notenantIdstill warn, whatever the payload says. For an update or delete the column is a value being written, not the scope, and an upsert's merge leg can reach another organization's row.buildDriverOptionsfills inbypassTenantAudit: truewhen the Layer 0 verdict that plugin-security recorded on the operation isnoneand the engine's own fallback asking of the same ladder (computeTenantLayer0Verdictwith the exemption withheld, the scope an unrecorded operation already gets) would have walled it. This is plugin-security's owncrossedByPlatformAdmintest, read from the recorded value. It is the existing option, so there is noDriverOptionswidening and no newOperationContextmember, and the driver reads no posture it did not read before. It is not filled in when no verdict was recorded (a by-id write, a system context, no security layer), undersingle, for an insert (write target), for an object outside the tenant machinery (tenancy-disabled, federated, no tenant field), for a verdict that does not parse, or when the caller supplied its own value.Other drivers: Turso's local face and the SQLite WASM driver inherit
SqlDriver.create/bulkCreate, so they get (1). Turso's remote face never called the audit.driver-memoryanddriver-mongodbhave no tenant-audit warning.Measured: a walled
os serveboot, before and afterSetup (as on #25210's R4):
pnpm dev -- --freshon the showcase withOS_TENANCY_POSTURE=isolated, a 32-byteOS_SECRET_KEY,OS_AUTH_MEMBERSHIP_POLICY=invite-onlyandOS_PLATFORM_OWNER_EMAIL.@objectstack/organizationswas declared in the showcase'spackage.jsonand linked for the run only; both were reverted and are not in this diff. Health 200 both times; 20 s settle after health; seeded admin sign-up included.[tenant-audit]lines0f1eeeeebcreateshowcase_*, 14sys_*)bulkCreateshowcase_*)updateupdateManyEvery insert line is gone; every update and updateMany line stays, as the card requires.
Tests
sql-driver-tenant-audit-insert-carries-tenant.test.ts(new, realSqlDriveron in-memory SQLite, 11 tests). Pins:createandbulkCreatewhose rows carry the organization print nothing. Controls: a row without it, an empty organization, one row of a batch without it, and the byte-identical prefix all still warn;update(payload naming an organization),delete,updateMany,deleteManyandupsert(row naming an organization) all still warn.engine-tenant-audit-wall-exemption.test.ts(new, theDriverOptionsseam and harness ofengine-driver-read-scope.test.ts, 10 tests). Declared: recordednoneunderisolatedonupdateManyanddeleteMany, and undergroup. Not declared: a walled verdict, no recorded verdict,single, an insert, a tenancy-disabled object, an unparseable verdict, a caller's ownbypassTenantAudit: false.createwhose row carriedorganization_idas the "write without tenant" shape:sql-driver-tenant-audit-posture.test.tsnow uses adeletefor that helper (and itsbypassTenantAuditcase), andsql-driver-tenant-scope.test.ts's throttle pin now uses two deletes. Their assertions are unchanged.@objectstack/objectqltest 464 files, 8686 passed; typecheck exit 0.@objectstack/driver-sqltypecheck exit 0; test 3773 passed, 1 failed on the first run (the throttle pin above, which encoded the old insert behaviour), and the fixed file passes 61/61 with its two sibling audit files. Cross-package readers of the prefix:service-messagingdelivery-claim-tenant-auditanddelivery-update-tenant-auditintegration tests 13/13;service-storagetenant-audit-update-delete-half-repairs30/30.Ablation
Both run through
scripts/ablation-replace.mjswith the fix committed, and each restore was verified (blob equalsHEAD,git diff HEADempty). The tests import source, so no build leg is needed.auditMissingTenant: the 2 insert pins go red and the 9 controls stay green (2 failed, 9 passed).opts.bypassTenantAudit = truefor the exemption: the 3 "declared" pins go red and the 7 "not declared" stay green (3 failed, 7 passed). My first attempt was refused by the tool before running anything, because the replacement text was a substring of the anchor. The second attempt used a distinct replacement.Gates
Run on the final head
d323330402.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 74 commands over 8 changed paths. I ran each one and captured its exit code before any pipe: 74/74 exit 0.--ranreports "74 derived famil(ies) accounted for — 74 run, 0 NOT-MEASURED". Two shallow-clone prerequisites were met first, by fetching621a48760(check-plugin-teardown-shape --self-test) and running--shallow-since=2026-07-17(check-engine-split-ratio --days 90). On the earlier headb57e83d5, the same 72 were green and those two exited 3 and 2 on the shallow clone. CI runs everything else; this PR does not touch a governed surface.Acceptance notes
updateandupdateManyonshowcase_*objects with notenantId. That is the case the line exists for, and the card keeps it. Whether the showcase seed path should run those writes in an organization's context is outside this card. I'm noting it here and not filing it, since nothing is shown wrong.none. This is ADR-0131 D8 as written; this PR does not move it.DriverOptionsseam, where plugin-security's recorded verdict meets the engine. Boundary:NOT MEASURED: live exempt-admin predicate write.Generated by Claude Code