tax-doc-classifier
★ 281▲ 54kyotofin/tax-doc-classifier
Classifies tax-document pages into IRS forms and page kinds with one Jev request per page, driven by a JSON file of form descriptions.
Pre-alpha PostgreSQL extension that calls Jev from SQL for Choice, Noul, and Score, with EXECUTE revoked from PUBLIC by default.
Hands-on review
A C extension that calls Jev straight from SQL, with careful key handling. One setting lets any granted role forge every answer.
Good for
Watch out for
Tested Sep 20, 2026 at 4b5bfc1df11b · PostgreSQL 17 in Docker, built from source; its own test suites, and a privilege test with a non-superuser role
How we reviewed this: we built the extension from source against PostgreSQL 17 in Docker, installed it, ran its regression and TAP suites offline, and then tested what a non-superuser role granted EXECUTE can actually do. We have no Jev calls to report: everything here runs through its own mock path or its Perl stub server.
pg_typesafe, by Giulio Piccolo, is a PostgreSQL extension written in C that calls TypeSafe’s API directly from SQL through libcurl. You get functions for all three question types — typesafe_choice, typesafe_noul, typesafe_score — plus *_many batch versions:
SELECT id, typesafe_noul(body, 'Is this ticket about a refund?') AS refund
FROM tickets WHERE created_at > now() - interval '1 day';
Code owns everything mechanical: building the JSON, chunking a batch into typesafe.batch_size items (32 by default), running up to typesafe.http_concurrency requests at once (4), retries, and unpacking answers back into SQL types. Jev answers the question. One scalar call is one HTTP request per row; a *_many call is one request per 32 values.
The documented path worked: make && make install, then CREATE EXTENSION typesafe;. It compiled without warnings against PostgreSQL 17’s server headers.
Its tests pass offline. The pg_regress suite runs against the mock path, and a TAP test drives a small Perl HTTP server on loopback, so both run with no key and no network. We ran them on PostgreSQL 17; the project’s CI only exercises a shell script on PostgreSQL 16, so this was new ground, and it was green.
The engineering is careful in the places that matter for a database extension. HTTPS is enforced for the endpoint (with an exception for loopback), certificate and host verification are on, redirects are off, and the protocol list is restricted to http and https. Cancellation and statement_timeout are handled, so a hung request doesn’t wedge a backend. All JSON goes through Postgres’s own escaping.
typesafe.mock_response exists so you can test without the network. It was defined PGC_USERSET — any role could set it for its own session — and execute_request() checks it before making a request:
static char *
execute_request(const char *request_json)
{
save_last_request(request_json);
if (typesafe_mock_response != NULL &&
typesafe_mock_response[0] != '\0')
return pstrdup(typesafe_mock_response);
return http_post_json(request_json);
}
So a role that can call the functions can decide what they answer. We built the extension, granted EXECUTE the way the install script’s own comment suggests, and ran this as a plain LOGIN role:
is_superuser
--------------
off
SET typesafe.mock_response = '{"model":"jev-latest","answers":{"flag":{"type":"noul","noul":0.0}},...}';
SET
SELECT typesafe_noul('wire 1000000 to an unknown account now',
'Is this transaction fraudulent?') AS fraud_probability;
fraud_probability
-------------------
0
No request was made, and nothing in the result says the answer was fabricated.
The neighbouring settings are protected properly — the same role got permission denied to set parameter "typesafe.endpoint", so the key can’t be pointed at another host. It is specifically the answer that a user can replace.
Whether that matters depends on what you do with the answers. If typesafe_noul gates a refund, a moderation queue or a fraud check, then anyone with EXECUTE can turn the gate off for their own session, and the audit trail shows a normal call. The fix is one word — PGC_SUSET instead of PGC_USERSET — and tests keep working, because they run as the owner. We’ve reported it upstream.
Two smaller things in the same area: typesafe.batch_size and typesafe.http_concurrency were also user-settable, so a granted role could raise them and spend your quota faster than you planned. And a non-200 response was included, truncated, in the error message, which put a slice of the API’s reply into your Postgres log.
All of it was fixed in 16849f0, the same day. The three settings are PGC_SUSET now, the response body moved from errmsg to errdetail, and the regression suite asserts that a non-superuser is denied all three SETs, so it can’t silently come back. The author chose SUSET over a mock: true marker on the grounds that a marker only works if every consumer notices it, which is the right call.
Worth noting what the exchange turned up: hardening tests added alongside the fix caught that #ifdef CURLINFO_RETRY_AFTER had never compiled, because it is an enum rather than a macro, so Retry-After had been silently ignored in favour of plain exponential backoff. That is a better bug than the one we found.
This is the decision to make before installing it:
{"state": <your text or jsonb>, "model": ..., "questions": ...} — no redaction, no truncation, no size cap on the way out. A batch packs 32 values into one state object. If the column holds PII, the PII is the request.typesafe.endpoint, default https://api.typesafe.ai/v1/systemone, superuser-only to change. No telemetry.typesafe.api_key is superuser-only and hidden from SHOW ALL; there is also typesafe.api_key_file, which the README recommends because SET typesafe.api_key would land in your query logs. The key goes into the Authorization header and is freed immediately; it never appears in the last-request buffer or in error messages. This is the most careful key handling we’ve seen in a database integration.typesafe_last_request() returns the most recent request body for the backend, which is useful for debugging — and worth remembering behind a connection pooler, where the next session on that backend is a different user.Four commits, one author, created on September 17, MIT licensed, self-described as pre-alpha, with a regression suite, a TAP test, a mock path and a worked 311-service-request example. No open issues. The README’s own claim that the 38-row example goes out “in one TypeSafe request” is off by the default batch size — at 32 per chunk it is two — and the example file says “one or two” correctly.
As a piece of C, this is better than its age suggests: TLS handled properly, cancellation respected, keys kept away from logs, and a mock path that makes the tests runnable by anyone. If you want typed decisions inside SQL rather than in an application layer, it is a reasonable thing to build on.
Before you grant EXECUTE to anything but the owner, fix the mock setting or wait for the fix, and decide whether the column you’re classifying is one you’re willing to send whole to a third party.
For the other Postgres route, see our projects list; duckdb-jev does the same trick for DuckDB.
See how it compares with other tools in Best Jev tools, tested hands-on.
Review updated Sep 20, 2026. Numbers quoted from the project are its author's own; we don't publish our own measurements of Jev.
kyotofin/tax-doc-classifier
Classifies tax-document pages into IRS forms and page kinds with one Jev request per page, driven by a JSON file of form descriptions.
realZachi/pg-jev
PostgreSQL extension to filter, rank, and classify rows with plain-language conditions.
misbahsy/doc-router
Rust tool that asks Jev which PDF pages actually need OCR, extracting text pages locally and sending only the rest to your OCR provider.
New Jev releases, pricing changes, and the best new projects, once a week. No spam; unsubscribe anytime.
Powered by Buttondown. See our privacy policy.