Repository navigation
Commit 4b59a38
Fixes #21226
Clause-②: no (narrowing)
The remote (libSQL) face of `TursoDriver` now applies the caller's
tenant scope at every door that reads rows or picks rows to write, and
its `create` stamps the caller's organization. Each door now answers the
same row set as the local face for the same `DriverOptions`. This body
names classes and positions only; the measured detail stays with the
seats.
## Reach (step 1 of the card), by class
Measured at the REST data doors over a real engine with the security
layer installed, remote face against local face, as a member of one
organization against another organization's rows, at base `95e24b0096`:
- **Walled postures.** The engine's Layer 0 wall held other
organizations' rows back at every data door on both faces. The remote
`create` diverged: its row landed with no organization.
- **The posture in which Layer 0 is inert, and an elevated engine caller
that carries its organization.** The driver scope is the only fence
here. The remote face answered across organizations where the local face
did not.
The seat applied triage's raise rule on the card. After this change, the
same measurement shows no difference between the faces at any measured
door, in all three postures and for the elevated caller.
**Step 2.** The hosted cloud topology is one database per environment
(ADR-0002). But nothing in the repository keeps a walled
multi-organization posture off a remote libSQL primary database, so one
organization per remote database is not guaranteed by construction.
## Mechanism
- **`TursoDriver.remoteTenantScope`** (`turso-driver.ts`) hands the
local face's own chokepoint, `SqlDriver.applyTenantScope`, a bare Knex
query builder and compiles what it added. Compiling needs no connection,
and the remote face's Knex has none. The predicate is therefore not a
copy: the NULL-organization arm, the group posture's `tenantIds` union
and the no-tenant-context exit all stay in that one method. If the probe
compiles to a shape it cannot read (anything beside `where` terms), the
call is refused with `INTERNAL_ERROR` / 500. It is never sent unscoped.
- **`RemoteTransport`** (`remote-transport.ts`) takes that fragment as
an optional `RemoteTenantScope` on each door and ANDs it onto the
statement's `WHERE` (`scopedWhereSQL`, `byIdWhereSQL`). The caller's
filter is parenthesized, so a top-level `$or` cannot escape the scope.
With no scope, every statement is byte-identical to before. This is the
per-call shape the upsert `fence` already uses. That fence is
equality-only and cannot carry the NULL arm or the union, so the scope
is its own parameter.
- **The remote `create`** calls `injectTenantOnInsert` on its copy of
the row before the record numbers are issued, as the local `create`
does.
## One conclusion per door
| door | remote face before | now |
|:--|:--|:--|
| `find` | unscoped | scoped |
| `findOne` | unscoped | scoped |
| `count` | unscoped | scoped |
| `aggregate` | unscoped | scoped (bounded in-place fix, below) |
| `update` (by id, and its read-back) | key only | key AND scope; out of
scope answers `null` |
| `delete` (by id) | key only | key AND scope; out of scope answers
`false` |
| `updateMany` / `deleteMany` | the caller's filter only | filter AND
scope |
| `bulkUpdate` / `bulkDelete` | key / id set only | AND scope (bounded
in-place fix, below) |
| `create` / `bulkCreate` | no organization stamp | stamps the caller's
organization; `bulkCreate` goes through `create` |
| `upsert` | already conformant: the fence and the stamp landed in
`95e24b009` | unchanged |
| `distinct` | refuses a tenant-scoped call | still refuses; answering
it would accept a call refused today, a widening this change does not
make |
| `findWithWindowFunctions`, `analyzeQuery` | refused on the remote face
| unchanged |
No door refuses instead of scoping: the compiled predicate carries the
full local scope, the group posture's union included.
**Bounded in-place fix.** The card names `find`, `findOne`, `count`,
`update`, `delete`, `updateMany`, `deleteMany` and `create`.
`aggregate`, `bulkUpdate` and `bulkDelete` have the same defect, in the
same two files. The same helper fixes them, the same file pins them, and
the same gate families cover them. Leaving them unscoped would keep the
invariant false. Their pins go red with the fix reverted, as listed
below.
## Pins
`packages/drivers/driver-turso/src/turso-local-remote-tenant-scope-parity.test.ts`
runs on both faces: a local `TursoDriver`, and a remote one over the
SQLite-backed libSQL stub. It uses the option shapes the engine sends
when nothing above the driver composes a tenant predicate: `tenantId`,
and `tenantId` with `bypassTenantAudit`. It asserts:
- **Each door**, as a member of one organization against another
organization's rows, answers excluded, `null`, `false` or `0`, or leaves
the row untouched on disk. A same-organization control answers as an
unscoped call would.
- **A platform row with no organization** is inside the scope on both
faces.
- **A top-level `$or`** in the caller's filter does not escape the
scope.
- **The group posture's `tenantIds`** scopes to the union and no
further.
- **`create` and `bulkCreate`** stamp the caller's organization and keep
an explicit one.
- **A call with no tenant context** reaches every row, as before.
- **A scope the remote face cannot read** is refused (`code` and
`status`) and writes nothing.
- **One script run on both faces** gives equal answers, and they are the
scoped ones.
**Reverse verification.** The fix was committed first (`e47eee6dcb`).
Then the two source files were restored from the base commit, with the
pin file kept. The fix was then restored from `HEAD`, proven by blob
hash and an empty `git diff HEAD`.
- With the fix reverted: 17 failed and 19 passed of 36. Every
remote-face door pin, the refusal pin and the parity pin went red. Every
local-face pin and the two remote-face controls stayed green.
- With the fix restored: 36 of 36 passed.
## The `packages/spec` edit
The changeset's FROM → TO is a migration prescription, so the ADR-0087
disposition is `registered driver-remote-doors-tenant-scoped`, following
the precedent landed in `95e24b009`:
- one semantic entry,
`packages/spec/src/migrations/entries/semantic/18.driver-remote-doors-tenant-scoped.ts`;
- the `registry.ts` region that `gen:migration-registry` regenerates;
- `'@objectstack/spec': patch`.
`check:generated` is green before and after. `driver-sql` is not
touched.
## Verification (head `71d3bcb35e`, after merging `origin/main`)
- **`pnpm --filter @objectstack/driver-turso test`:** 87 files, 2349
passed, 33 skipped (all in files this diff does not touch); exit 0.
- **`pnpm --filter @objectstack/driver-turso typecheck`:** exit 0. The
pin file is in the program, counted with `--listFiles`.
- **`pnpm --filter @objectstack/spec check:generated`:** all 15
artifacts up to date. After the merge, `gen:migration-registry`
reproduced `registry.ts` byte for byte.
- **Spec:** `vitest run --project local src/migrations` 169 passed; `tsc
--noEmit` exit 0.
- **`pnpm check:driver-conformance`:** before the first edit and after
the last commit, both `OK — 50 covered cell(s), 0 in the DEBT ledger, 0
exempt`.
- **`pnpm check:tenant-chokepoint`:** green before and after (22
bindings across 3 files).
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derives 90 families. All 90 ran
with exit 0 on this head, and `--ran` reconciles 90 run, 0 NOT MEASURED.
- **Lint, narrowed:** `eslint --no-inline-config --format json` over the
5 changed source files. All 5 are in the config's population (no
ignored-file warnings), with 0 errors and 0 warnings. The config enables
no type-aware linting, so this diff cannot change the verdict on a file
it does not touch. The repo-wide `pnpm lint` is CI's.
## Acceptance notes
- The remote face's write doors do not call `auditMissingTenant`, which
the local face's write doors call. It is a warning log, not the scope,
and is outside this card. Not filed; no carrier.
- `distinct` could now carry the scope through the same helper. That
would turn a refusal into an answer, which is a widening, so it is left
for the seat.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent d150c30 commit 4b59a38
6 files changed
Lines changed: 834 additions & 49 deletions
File tree
- .changeset
- packages
- drivers/driver-turso/src
- spec/src/migrations
- entries/semantic
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
949 | 949 | | |
950 | 950 | | |
951 | 951 | | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
952 | 971 | | |
953 | 972 | | |
954 | 973 | | |
| |||
1255 | 1274 | | |
1256 | 1275 | | |
1257 | 1276 | | |
1258 | | - | |
| 1277 | + | |
| 1278 | + | |
| 1279 | + | |
| 1280 | + | |
| 1281 | + | |
| 1282 | + | |
1259 | 1283 | | |
1260 | 1284 | | |
1261 | | - | |
| 1285 | + | |
1262 | 1286 | | |
1263 | 1287 | | |
1264 | 1288 | | |
| |||
1302 | 1326 | | |
1303 | 1327 | | |
1304 | 1328 | | |
1305 | | - | |
| 1329 | + | |
| 1330 | + | |
1306 | 1331 | | |
1307 | 1332 | | |
1308 | 1333 | | |
| |||
1328 | 1353 | | |
1329 | 1354 | | |
1330 | 1355 | | |
1331 | | - | |
| 1356 | + | |
| 1357 | + | |
| 1358 | + | |
| 1359 | + | |
| 1360 | + | |
| 1361 | + | |
1332 | 1362 | | |
1333 | | - | |
| 1363 | + | |
1334 | 1364 | | |
1335 | 1365 | | |
1336 | 1366 | | |
| |||
1347 | 1377 | | |
1348 | 1378 | | |
1349 | 1379 | | |
1350 | | - | |
| 1380 | + | |
| 1381 | + | |
| 1382 | + | |
| 1383 | + | |
| 1384 | + | |
| 1385 | + | |
1351 | 1386 | | |
1352 | 1387 | | |
1353 | 1388 | | |
| |||
1502 | 1537 | | |
1503 | 1538 | | |
1504 | 1539 | | |
1505 | | - | |
| 1540 | + | |
| 1541 | + | |
| 1542 | + | |
1506 | 1543 | | |
1507 | 1544 | | |
1508 | 1545 | | |
| |||
1626 | 1663 | | |
1627 | 1664 | | |
1628 | 1665 | | |
| 1666 | + | |
1629 | 1667 | | |
1630 | 1668 | | |
1631 | 1669 | | |
1632 | 1670 | | |
1633 | 1671 | | |
1634 | 1672 | | |
1635 | 1673 | | |
1636 | | - | |
1637 | | - | |
| 1674 | + | |
| 1675 | + | |
| 1676 | + | |
| 1677 | + | |
| 1678 | + | |
| 1679 | + | |
| 1680 | + | |
1638 | 1681 | | |
1639 | 1682 | | |
1640 | 1683 | | |
1641 | | - | |
1642 | | - | |
| 1684 | + | |
| 1685 | + | |
1643 | 1686 | | |
1644 | 1687 | | |
1645 | 1688 | | |
1646 | 1689 | | |
1647 | 1690 | | |
| 1691 | + | |
| 1692 | + | |
| 1693 | + | |
| 1694 | + | |
| 1695 | + | |
| 1696 | + | |
| 1697 | + | |
| 1698 | + | |
| 1699 | + | |
1648 | 1700 | | |
1649 | 1701 | | |
1650 | 1702 | | |
| |||
1736 | 1788 | | |
1737 | 1789 | | |
1738 | 1790 | | |
1739 | | - | |
1740 | | - | |
1741 | | - | |
| 1791 | + | |
| 1792 | + | |
| 1793 | + | |
| 1794 | + | |
| 1795 | + | |
1742 | 1796 | | |
1743 | 1797 | | |
1744 | 1798 | | |
| |||
1750 | 1804 | | |
1751 | 1805 | | |
1752 | 1806 | | |
1753 | | - | |
| 1807 | + | |
| 1808 | + | |
| 1809 | + | |
| 1810 | + | |
| 1811 | + | |
| 1812 | + | |
1754 | 1813 | | |
| 1814 | + | |
| 1815 | + | |
1755 | 1816 | | |
1756 | | - | |
1757 | | - | |
| 1817 | + | |
| 1818 | + | |
1758 | 1819 | | |
1759 | 1820 | | |
1760 | 1821 | | |
1761 | 1822 | | |
1762 | | - | |
| 1823 | + | |
| 1824 | + | |
| 1825 | + | |
| 1826 | + | |
| 1827 | + | |
| 1828 | + | |
1763 | 1829 | | |
1764 | 1830 | | |
1765 | | - | |
| 1831 | + | |
1766 | 1832 | | |
1767 | 1833 | | |
1768 | 1834 | | |
| |||
1829 | 1895 | | |
1830 | 1896 | | |
1831 | 1897 | | |
| 1898 | + | |
1832 | 1899 | | |
1833 | 1900 | | |
1834 | 1901 | | |
1835 | | - | |
| 1902 | + | |
1836 | 1903 | | |
1837 | 1904 | | |
1838 | 1905 | | |
1839 | 1906 | | |
1840 | 1907 | | |
1841 | | - | |
| 1908 | + | |
| 1909 | + | |
| 1910 | + | |
| 1911 | + | |
| 1912 | + | |
| 1913 | + | |
1842 | 1914 | | |
1843 | 1915 | | |
1844 | 1916 | | |
1845 | 1917 | | |
| 1918 | + | |
| 1919 | + | |
1846 | 1920 | | |
1847 | | - | |
1848 | | - | |
| 1921 | + | |
| 1922 | + | |
1849 | 1923 | | |
1850 | 1924 | | |
1851 | 1925 | | |
1852 | | - | |
| 1926 | + | |
| 1927 | + | |
| 1928 | + | |
| 1929 | + | |
| 1930 | + | |
| 1931 | + | |
| 1932 | + | |
1853 | 1933 | | |
1854 | 1934 | | |
1855 | 1935 | | |
1856 | 1936 | | |
1857 | 1937 | | |
1858 | 1938 | | |
1859 | | - | |
| 1939 | + | |
1860 | 1940 | | |
1861 | 1941 | | |
1862 | 1942 | | |
1863 | 1943 | | |
1864 | 1944 | | |
1865 | 1945 | | |
1866 | 1946 | | |
1867 | | - | |
| 1947 | + | |
| 1948 | + | |
| 1949 | + | |
| 1950 | + | |
| 1951 | + | |
| 1952 | + | |
1868 | 1953 | | |
1869 | 1954 | | |
1870 | | - | |
| 1955 | + | |
1871 | 1956 | | |
1872 | 1957 | | |
1873 | 1958 | | |
| |||
2608 | 2693 | | |
2609 | 2694 | | |
2610 | 2695 | | |
2611 | | - | |
| 2696 | + | |
| 2697 | + | |
| 2698 | + | |
| 2699 | + | |
| 2700 | + | |
| 2701 | + | |
2612 | 2702 | | |
2613 | 2703 | | |
2614 | 2704 | | |
2615 | 2705 | | |
2616 | 2706 | | |
2617 | 2707 | | |
2618 | 2708 | | |
2619 | | - | |
2620 | | - | |
| 2709 | + | |
| 2710 | + | |
2621 | 2711 | | |
2622 | 2712 | | |
2623 | 2713 | | |
| |||
2712 | 2802 | | |
2713 | 2803 | | |
2714 | 2804 | | |
| 2805 | + | |
| 2806 | + | |
| 2807 | + | |
| 2808 | + | |
| 2809 | + | |
| 2810 | + | |
| 2811 | + | |
| 2812 | + | |
| 2813 | + | |
| 2814 | + | |
| 2815 | + | |
| 2816 | + | |
| 2817 | + | |
| 2818 | + | |
| 2819 | + | |
| 2820 | + | |
| 2821 | + | |
| 2822 | + | |
| 2823 | + | |
| 2824 | + | |
| 2825 | + | |
| 2826 | + | |
| 2827 | + | |
2715 | 2828 | | |
2716 | 2829 | | |
2717 | 2830 | | |
2718 | 2831 | | |
2719 | 2832 | | |
2720 | 2833 | | |
2721 | | - | |
2722 | | - | |
2723 | | - | |
| 2834 | + | |
| 2835 | + | |
| 2836 | + | |
| 2837 | + | |
2724 | 2838 | | |
2725 | 2839 | | |
2726 | 2840 | | |
| |||
0 commit comments