128 lines
7.9 KiB
Markdown
128 lines
7.9 KiB
Markdown
# Shared research API (in progress)
|
||
|
||
This work belongs to Thesisly 研迹 and uses Foundry Core rather than a separate
|
||
application backend. The corpus is public; account sessions and future private
|
||
collections remain project scoped.
|
||
|
||
## Current implementation boundary
|
||
|
||
`internal/research` implements the HTTP contract and a PostgreSQL repository
|
||
based on the fixed Paper Atlas `a3b677b` schema (TEXT/BIGINT columns, native
|
||
English title/abstract search index). `cmd/foundry` enables it only when
|
||
`FOUNDRY_RESEARCH_DATABASE_URL` is configured. The Paper Atlas migration is now published: 21 tables passed its independent
|
||
count/content-hash acceptance. A dedicated `foundry_research_reader` role has
|
||
SELECT on the ten tables consumed here, schema USAGE, and read-only defaults.
|
||
Its connection is stored only in the protected server file below.
|
||
The existing SQLite implementation was withdrawn. Full-corpus API performance
|
||
and frontend integration still require verification before enabling production.
|
||
Do not add `FOUNDRY_RESEARCH_DATABASE`.
|
||
|
||
The server-side deployment may load a protected
|
||
`/opt/endoleap-infra/foundry/research.env` containing
|
||
`FOUNDRY_RESEARCH_DATABASE_URL` and `FOUNDRY_RESEARCH_SCHEMA` (default `research`).
|
||
Keep the connection string out of Git, Actions variables, browser configuration
|
||
and command arguments. Use a dedicated role with schema USAGE and table SELECT
|
||
only. Core uses a separate two-connection pool, read-only connection defaults and
|
||
read-only transactions. Each research transaction disables JIT locally: the
|
||
measured count plan spent about 2.85 seconds compiling JIT functions, and its
|
||
end-to-end execution exceeded eight seconds. This does not alter shared
|
||
PostgreSQL settings or replace exact counts with estimates. Startup checks the published schema/SELECT permissions;
|
||
an explicitly enabled but invalid connection fails candidate readiness.
|
||
|
||
Deployment migration defaults now follow the shared `foundry-postgres`
|
||
container name. `FOUNDRY_POSTGRES_CONTAINER` or the existing `--container` option
|
||
can select a different container without changing database/table names.
|
||
|
||
## Routes
|
||
|
||
- `GET /projects/{project}/research/papers`
|
||
- `GET /projects/{project}/research/papers/{id}`
|
||
|
||
Only configured Foundry projects are accepted. Reading the public corpus does
|
||
not require a session; these routes do not expose private data or accept writes.
|
||
|
||
List parameters are `q` (200 Unicode characters, 20 words), `searchIn`
|
||
(`text` or `author`), `topic` (an exact arXiv category), `sort` (`recent` or
|
||
`title`), `page` (1–1000), and `pageSize` (1–50, default 8). Unknown, malformed
|
||
or repeated parameters return 400. `recent` means descending stable paper ID,
|
||
not publication date. Search terms must become bound query parameters, never
|
||
raw SQL or user-supplied full-text syntax.
|
||
|
||
Title/abstract search uses the migration's English vector with
|
||
`plainto_tsquery`; author search uses simple-language terms over the stored
|
||
author JSON. Both treat input as terms, never full-text operators. Initial full-corpus measurements showed eight-second timeouts for text,
|
||
author and topic counts; the unfiltered count took 7.25 seconds. Execution plans
|
||
confirmed sequential scans for authors and category joins.
|
||
`deploy/research/query-indexes.sql` supplies operator-run concurrent indexes for
|
||
those two missing access paths. It is separate from Core startup and auth
|
||
migrations; do not grant DDL to the runtime reader. Both indexes completed on the confirmed ECS (category 35.4 seconds, author
|
||
436.47 seconds); end-to-end timings are still under verification.
|
||
|
||
List responses contain `items`, `total`, `topics`, `page`, and `pageSize`.
|
||
Each paper preserves original title/abstract, authors, source dates, topics and
|
||
source links. Publication decisions need source evidence; absent decisions stay
|
||
unknown. No invented repositories, metrics or generated research conclusions.
|
||
|
||
The repository counts and reads each list in one read-only snapshot.
|
||
Paging first materializes matching IDs, then joins only that page to the wide
|
||
paper rows. Filtered searches first materialize matching IDs without an
|
||
ORDER BY/LIMIT, then sort/page those IDs, preventing sparse terms from choosing
|
||
a backward heap scan with repeated text-vector computation. Activity sampling showed that selecting full rows before LIMIT
|
||
caused sparse category queries to read large portions of the paper heap. The
|
||
HTTP layer permits two in-flight queries with an eight-second context budget.
|
||
Busy requests return 429 with `Retry-After: 1`; deadlines return 504; storage
|
||
errors return a generic 503 without database details. Missing papers return 404.
|
||
|
||
## Validation
|
||
|
||
`go test -race -count=1 ./internal/research` covers routing, query limits,
|
||
Unicode, anonymous reads, rejected mutations, project lookup, errors, saturation
|
||
and timeout slot release. Run compilation/tests only on the user-confirmed ECS
|
||
runner. These transport tests do not prove a database connection, full-corpus
|
||
query performance or production availability.
|
||
|
||
The PostgreSQL repository batches source/tag/identifier/submission queries in
|
||
the same read-only repeatable-read transaction. Author tuples are displayed as
|
||
given name, family name and suffix; an optional fourth affiliation field
|
||
is excluded from the display name (observed in real arXiv records); null abstracts/dates remain null. Publication
|
||
status comes from the latest observed decision, using decision date and ID as
|
||
tie-breakers, and stays unknown when no decision exists. Unit checks cover
|
||
author shapes, bound user values and schema qualification; real PostgreSQL
|
||
the real Core detail and homepage HTTP routes have returned 200.
|
||
The frontend has also rendered the actual 3,526,999-paper corpus through a
|
||
loopback SSH tunnel: eight rows, matching detail title/source, desktop and
|
||
mobile layout, no page exceptions or horizontal overflow in that check.
|
||
Author search returned 146 records in 0.336 seconds. After paging/author fixes, category `cs.AR` returned 9,457 records in 4.906
|
||
seconds, `CartoonGAN` returned three in 0.332 seconds, and the multi-term search
|
||
`attention is all you need` returned 8,653 in 6.325 seconds. These were sequential
|
||
manual HTTP checks, not a load test. Very broad text searches can still exceed
|
||
the eight-second budget and need further performance work. This is not a
|
||
production deployment or an authentication acceptance result.
|
||
|
||
The existing `deploy/check-schema.sh` also loads the synthetic fixture in
|
||
`internal/research/testdata/corpus.sql` inside its disposable PostgreSQL
|
||
container (`--network none`, no host port). The research test process receives a
|
||
separate SELECT-only fixture role via `RESEARCH_TEST_DATABASE_URL`. These
|
||
integration checks execute real SQL for filtering, stable paging, nullable
|
||
content, source/tag/identifier joins, latest decision tie-breakers, permission
|
||
denial and SQL-backed HTTP routes. They never connect to the shared migration
|
||
instance and do not establish full-corpus performance or live-data correctness.
|
||
|
||
## Text storage compatibility (repair 4e62710)
|
||
|
||
The Paper Atlas repair uses `nul-soh-v1`: NUL is stored as SOH/SOH, and a
|
||
literal SOH as SOH/STX, preserving binary text ordering. Core reads the schema
|
||
comment `paper-atlas:text-codec=nul-soh-v1` through `pg_catalog` at startup,
|
||
restores SQL text before JSON parsing/API responses, and encodes bound search
|
||
terms. An absent/unrelated schema comment keeps legacy text unchanged; an
|
||
unsupported marked codec or malformed encoded text fails explicitly.
|
||
|
||
Schema comments are public metadata, so consumers need no privileges on the
|
||
private migration controller. The research role still needs only schema USAGE
|
||
and table SELECT. The codec adaptation remains local without CI or branch pushes;
|
||
the published schema marker has been verified with the runtime reader.
|
||
All research tests passed manually against disposable PostgreSQL on the
|
||
confirmed ECS, including the encoded SQL fixture for schema detection, raw TEXT restoration,
|
||
JSON author/metadata decoding, search and the HTTP round trip. Passing isolated
|
||
fixtures does not establish full-corpus API performance.
|