A data table backed by the instance database resolved to exactly one Postgres connection,
`custom_instance_user`, for everyone who could reach it at all. There was no way to say
this job reads, that one writes, this one never sees the salaries table.
A data table role is now a real Postgres login on the cluster, defined once for the
instance by a superadmin and named exactly as they named it. A script that declares
`-- role analytics` connects as `analytics`, and Postgres decides what it may touch —
grants are ordinary SQL. Windmill answers only "may this caller ask for this role", from
the tenant lists on the data table entry: `u/alice`, `g/analysts`, `f/finance` or `*`.
A data table with no `permissions` block behaves exactly as before.
Everything that opens a connection on someone's behalf goes through one chokepoint,
`get_datatable_resource_from_db`, which takes the identity explicitly and fails closed when
there is none. The role logs in as itself — never `SET ROLE`, which a script could
`RESET ROLE` its way out of.
A fork's data table entry becomes a pointer at the workspace that governs it rather than a
copy of it. The settings clone used to hand a fork a byte-identical entry naming the
parent's database, which a fork admin could edit to grant themselves `admin` there; a
pointer has nothing local to edit, and its tenants are evaluated as a member of the
governing workspace, by email. `permissions` is stripped from the workspace export and
ignored on import: tenants name principals of one workspace, and a settings push is not
where an access decision should be made.
Operations that see the whole database whatever the roles grant stay with the governing
workspace's admins: editing the roles, a migration that declares none, and opening a
replication stream for a Postgres trigger or capture.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* feat: add unit tests for S3BufferedReader.read and improve read method implementation
* feat: refactor S3BufferedReader.read method and add unit tests for its functionality
* feat: implement peek() on S3BufferedReader with buffered reads
* fix(python-client): keep the read(size) contract and trim the test surface
Drop the duplicated `TestS3BufferedReaderRead` class from
`python-client/tests/wmill_client_test.py`: CI runs `pytest tests/` from
`python-client/wmill`, so that legacy manual harness never executes, and the
same assertions already live in `python-client/wmill/tests/test_s3_reader.py`.
Narrow that file to the four behaviours a future change could break, and make
the `bytes_generator` guard actually call `bytes_generator`.
Align `peek()` with `io.BufferedReader.peek`, which does at most one read on
the underlying stream, rather than looping until `size` bytes are buffered.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(python-client): hold read1 to one underlying read
read1 forwarded to read, so read1(-1) drained the whole object — the same
unbounded buffering this branch removes from read. Now that a buffer exists,
read1 can honour its own contract: fill only when the buffer is empty, then
serve from it.
Also treat read(None) as read(-1), per the BufferedReader contract, and pin
that read(0) does not pull from the stream: that holds only because the
drain sentinel is a negative size, and widening it to any falsy size would
reintroduce whole-file buffering.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(python-client): return from read1(0) without touching the stream
A zero-length read has nothing to serve, so pulling a chunk to satisfy it
both wastes a round trip and advances the stream. Guard it ahead of the
fill, and pin it with a chunk source that counts pulls.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Tushar <tusharanshu18@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(wac): pin the failure record with one corpus both SDKs read
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(wac): add the behaviour matrix that verified the failure record
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(wac): record how to exercise an unreleased SDK change
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(wac): guard the whole extra pair, not just its value
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(wac): never rehash an untrusted extra key
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(wac): walk only a real __dict__ when collecting extra
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(wac): name the divergence the corpus cannot pin
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore(wac): state the extra-encoding constraints in four lines
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* ci: run the python and typescript SDK suites
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* ci: run the SDK suites on release tags only
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore(ci): state the constraint without the incident
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(sdk): claim only what functools.wraps actually restores
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(ci): note the interpreter the suite runs on is not the worker's
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>