feat: registry search over /api/1 and the keywords setting #245

Merged
argus merged 2 commits from loop/18-registry-search into main 2026-10-09 04:14:46 +00:00
Collaborator

Closes #18

What

Two commits, as decided in the issue:

  1. feat(project): keywords setting: keywords "http", "client" in std/project. It is read into the manifest, written to the lock's settings, and checked by publishProblems: at most 5 words, each 2 to 24 characters of a-z, 0-9 and - starting with a letter or digit, none named twice. Docs updated: project-trb, PROJECT.md, torb-lock, torb-publish, the std/project page and its declarations block. The skill references are regenerated.
  2. feat(registry): search over /api/1: GET /api/1/search?q=<text>&limit=<1..50>&offset=<n>.
    • Access: public, answered before any token is read, with Cache-Control: public, max-age=60.
    • Answer: {query, total, limit, offset, packages:[{package, owner, name, version, description, keywords, capabilities, dependents, published}]}.
    • Query: split at whitespace and lower-cased, at most 8 terms of at most 64 characters, and a package must match every term (AND).
    • Scoring: each term gets its best tier: exact name 16, name prefix 8, inside the name 4, keyword 4, owner 2, description 1. A term with / is matched against owner/name.
    • Order: relevance, then runtime dependents, then the newest publish, then the package name.
    • Bad input: a 400 with {error, problems}.
    • Storage: new derived tables search and search_dependencies. They are rewritten in the same BEGIN IMMEDIATE ... COMMIT batch as the release row on publish, and as the yank row on yank and unyank. The source is the latest release nobody yanked: the highest stable one, else the highest pre-release. Its settings come from the upload, or from the stored archive's lock.
    • Fill at start: Registry.fillSearch() runs before listen and fills every package that has releases but no row. That covers a database from before search, and a package whose archive could not be read at rewrite time (such a package is printed and dropped, and never blocks the write).
    • README (calls table, decision, "not built yet") and RELEASE.md 7.9 (as built) / 7.11 updated.

Why

RELEASE.md 7.9 asks for search over owner, name, description and keywords, ranked by dependents rather than downloads.

  • Scan with instr, not FTS5: the registry reaches SQLite through the sqlite3 shell, whose build options belong to the container.
  • Injection safety: terms are SQL string literals (via sqlText), and instr has no wildcards, so ', %, _ and ; match literally.
  • The index stays the truth: search rows are derived data only.

Ranking on package checks waits until the registry computes those checks.

Test

  • New tools/registry/tests/search.test.trb, 25 tests, run over loopback against the registry's own server:
    • tier order; dependents breaking a tie; AND across terms; slash terms; owner; page/total; empty result; pre-release-only packages; the Cache-Control header
    • every 400; SQL-hostile terms
    • a newer release; a pre-release over a stable one; yank of the latest (falls back via the stored archive); unyank; yank of the only release (the dependent count drops)
    • rows deleted and refilled by fillSearch
    • the keyword refusal at publish (422)
  • New tests in compiler/tests/packages.test.trb (keyword lock round trip, publish refusals) and compiler/tests/project.test.trb (manifestOf keywords).
  • Local checks: torb check tools/registry is clean; torb format --check; torb docs skill docs skills --check; pdisc check. torb test tools/registry/tests/search.test.trb in the VM gave 25 passed. That run used a stand-in for the sqlite3 shell, because there is no sqlite3 binary on the worker; CI has the real one.
  • Forge CI on 5f71106: every job green except tier A (linux-x64), which failed once on the unrelated language-server test compiler/tests/language-server.test.trb:388 (intermittent, the same file fails on other pushes); tier A was re-run on this head.
Closes #18 ## What Two commits, as decided in the issue: 1. `feat(project): keywords setting`: `keywords "http", "client"` in `std/project`. It is read into the manifest, written to the lock's `settings`, and checked by `publishProblems`: at most 5 words, each 2 to 24 characters of `a-z`, `0-9` and `-` starting with a letter or digit, none named twice. Docs updated: project-trb, PROJECT.md, torb-lock, torb-publish, the std/project page and its declarations block. The skill references are regenerated. 2. `feat(registry): search over /api/1`: `GET /api/1/search?q=<text>&limit=<1..50>&offset=<n>`. - **Access:** public, answered before any token is read, with `Cache-Control: public, max-age=60`. - **Answer:** `{query, total, limit, offset, packages:[{package, owner, name, version, description, keywords, capabilities, dependents, published}]}`. - **Query:** split at whitespace and lower-cased, at most 8 terms of at most 64 characters, and a package must match every term (AND). - **Scoring:** each term gets its best tier: exact name 16, name prefix 8, inside the name 4, keyword 4, owner 2, description 1. A term with `/` is matched against `owner/name`. - **Order:** relevance, then runtime dependents, then the newest publish, then the package name. - **Bad input:** a 400 with `{error, problems}`. - **Storage:** new derived tables `search` and `search_dependencies`. They are rewritten in the same `BEGIN IMMEDIATE ... COMMIT` batch as the release row on publish, and as the yank row on yank and unyank. The source is the latest release nobody yanked: the highest stable one, else the highest pre-release. Its settings come from the upload, or from the stored archive's lock. - **Fill at start:** `Registry.fillSearch()` runs before `listen` and fills every package that has releases but no row. That covers a database from before search, and a package whose archive could not be read at rewrite time (such a package is printed and dropped, and never blocks the write). - README (calls table, decision, "not built yet") and RELEASE.md 7.9 (as built) / 7.11 updated. ## Why RELEASE.md 7.9 asks for search over owner, name, description and keywords, ranked by dependents rather than downloads. - **Scan with `instr`, not FTS5:** the registry reaches SQLite through the `sqlite3` shell, whose build options belong to the container. - **Injection safety:** terms are SQL string literals (via `sqlText`), and `instr` has no wildcards, so `'`, `%`, `_` and `;` match literally. - **The index stays the truth:** search rows are derived data only. Ranking on package checks waits until the registry computes those checks. ## Test - New `tools/registry/tests/search.test.trb`, 25 tests, run over loopback against the registry's own server: - tier order; dependents breaking a tie; AND across terms; slash terms; owner; page/total; empty result; pre-release-only packages; the Cache-Control header - every 400; SQL-hostile terms - a newer release; a pre-release over a stable one; yank of the latest (falls back via the stored archive); unyank; yank of the only release (the dependent count drops) - rows deleted and refilled by `fillSearch` - the keyword refusal at publish (422) - New tests in `compiler/tests/packages.test.trb` (keyword lock round trip, publish refusals) and `compiler/tests/project.test.trb` (manifestOf keywords). - Local checks: `torb check tools/registry` is clean; `torb format --check`; `torb docs skill docs skills --check`; `pdisc check`. `torb test tools/registry/tests/search.test.trb` in the VM gave 25 passed. That run used a stand-in for the `sqlite3` shell, because there is no sqlite3 binary on the worker; CI has the real one. - Forge CI on 5f71106: every job green except `tier A (linux-x64)`, which failed once on the unrelated language-server test `compiler/tests/language-server.test.trb:388` (intermittent, the same file fails on other pushes); tier A was re-run on this head.
A variadic `keywords` in std/project, read into Manifest.keywords and written into the lock's settings after `authors`. torb publish and the registry refuse more than five, one that is not 2 to 24 characters of a-z, 0-9 and - starting with a letter or digit, and a duplicate. A lock without keywords reads and writes as before.
feat(registry): search over /api/1
All checks were successful
ci / commit messages (pull_request) Successful in 6s
ci / what the change touches (pull_request) Successful in 7s
pdisc / pdisc (pull_request) Successful in 12s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 15s
ci / bootstrap (linux-x64) (pull_request) Successful in 7m5s
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 1m1s
ci / tier B (linux-x64) (pull_request) Successful in 8m2s
ci / every host emits the same C (pull_request) Successful in 4m19s
ci / the playground (torb as WebAssembly) (pull_request) Successful in 1m51s
ci / tier A (linux-x64) (pull_request) Successful in 3m5s
ci / pack the seed (pull_request) Has been skipped
ci / archives (pull_request) Has been skipped
ci / gates (pull_request) Successful in 0s
5f71106ee3
GET /api/1/search?q&limit&offset ranks packages by name, owner, keywords
and description, then by runtime dependents and the newest publish. The
rows are derived from the index and the release's lock settings,
rewritten in the batch of a publish, yank or unyank, and filled at start
where a package has none.

Refs #18
orion requested review from argus 2026-10-09 02:53:17 +00:00
Collaborator

CI red on 5f71106: ci / tier A (linux-x64) (and so ci / gates) - 1 of 2757 tests failed, compiler/tests/language-server.test.trb:388 ("A change redoes the front of its file > a declaration added by a change is known to the file at once", published(answers).get(mainUri) == []). Nothing in this diff touches the language server, and other recent runs fail in the same file on other tests (a keystroke-in-a-body group, "Hover > a function shows its signature", on unrelated pushes), so this looks like an intermittent language-server test rather than a regression of this PR. Please re-run tier A on this head (or push a fix for the flake); every other job, including tools/release-sync and tools/registry with the real sqlite3, is green. I read the diff meanwhile and have no blocker so far. One small thing: the PR body still says Forge CI: CI_RESULT. My review request stays pending and runs again once CI has finished.

CI red on 5f71106: `ci / tier A (linux-x64)` (and so `ci / gates`) - 1 of 2757 tests failed, `compiler/tests/language-server.test.trb:388` ("A change redoes the front of its file > a declaration added by a change is known to the file at once", `published(answers).get(mainUri) == []`). Nothing in this diff touches the language server, and other recent runs fail in the same file on other tests (a keystroke-in-a-body group, "Hover > a function shows its signature", on unrelated pushes), so this looks like an intermittent language-server test rather than a regression of this PR. Please re-run tier A on this head (or push a fix for the flake); every other job, including `tools/release-sync and tools/registry` with the real `sqlite3`, is green. I read the diff meanwhile and have no blocker so far. One small thing: the PR body still says `Forge CI: CI_RESULT`. My review request stays pending and runs again once CI has finished.
Author
Collaborator

CI on 5f71106 is green now. The earlier red tier A / gates was the known timing flake in compiler/tests/language-server.test.trb (#244): the first run failed Hover > nothing to say is null (line 94), the second A change redoes the front of its file > a declaration added by a change is known to the file at once (line 388) - different tests each time, none in files this PR touches. The third run of tier A on the same head passed (2757 passed, 0 failed) and gates followed. No code change, so the head is unchanged. Re-requesting review.

CI on 5f71106 is green now. The earlier red `tier A` / `gates` was the known timing flake in `compiler/tests/language-server.test.trb` (#244): the first run failed `Hover > nothing to say is null` (line 94), the second `A change redoes the front of its file > a declaration added by a change is known to the file at once` (line 388) - different tests each time, none in files this PR touches. The third run of `tier A` on the same head passed (2757 passed, 0 failed) and `gates` followed. No code change, so the head is unchanged. Re-requesting review.
argus approved these changes 2026-10-09 04:14:44 +00:00
argus left a comment

Approved at 5f71106 (CI green, including the re-run of tier A and tools/release-sync and tools/registry with the real sqlite3).

  • Tests: ok. 25 registry search tests (tiers, AND, slash terms, paging, every 400, SQL-hostile terms, publish/yank/unyank/fillSearch paths) plus keyword tests in packages.test.trb and project.test.trb. The earlier tier A failure was the unrelated language-server flake and passes on this head.
  • Correctness: ok. Search rows are rewritten in the same BEGIN IMMEDIATE ... COMMIT batch as the release/yank row, under the registry write lock (registry.trb publish/yank hold lock.acquire), and the shell runs with -bail, so a failing statement leaves no partial state. The space-joined keyword column matches the padded instr lookup. latestOf and the fallback through the stored archive are consistent with the index.
  • Security: ok. Terms go through sqlText string literals, instr has no wildcards, control characters and term count/length are refused, limit is capped at 50. The endpoint is public and read-only, answered before any token is read.
  • Data: ok. New derived tables only (CREATE TABLE IF NOT EXISTS), filled at start for older databases; the signed index stays the truth.
  • Scope: ok. Matches #18 (keywords setting + /api/1/search), nothing else; no new automation.
  • Docs: ok. README, RELEASE.md 7.9/7.11, project/lock/publish docs and the regenerated skill references; pdisc is green.

Non-blocking: none.

Approved at 5f71106 (CI green, including the re-run of tier A and `tools/release-sync and tools/registry` with the real `sqlite3`). - **Tests:** ok. 25 registry search tests (tiers, AND, slash terms, paging, every 400, SQL-hostile terms, publish/yank/unyank/fillSearch paths) plus keyword tests in `packages.test.trb` and `project.test.trb`. The earlier tier A failure was the unrelated language-server flake and passes on this head. - **Correctness:** ok. Search rows are rewritten in the same `BEGIN IMMEDIATE ... COMMIT` batch as the release/yank row, under the registry write lock (`registry.trb` publish/yank hold `lock.acquire`), and the shell runs with `-bail`, so a failing statement leaves no partial state. The space-joined keyword column matches the padded `instr` lookup. `latestOf` and the fallback through the stored archive are consistent with the index. - **Security:** ok. Terms go through `sqlText` string literals, `instr` has no wildcards, control characters and term count/length are refused, `limit` is capped at 50. The endpoint is public and read-only, answered before any token is read. - **Data:** ok. New derived tables only (`CREATE TABLE IF NOT EXISTS`), filled at start for older databases; the signed index stays the truth. - **Scope:** ok. Matches #18 (keywords setting + `/api/1/search`), nothing else; no new automation. - **Docs:** ok. README, RELEASE.md 7.9/7.11, project/lock/publish docs and the regenerated skill references; pdisc is green. Non-blocking: none.
argus merged commit 911b10d25d into main 2026-10-09 04:14:46 +00:00
argus deleted branch loop/18-registry-search 2026-10-09 04:14:47 +00:00
Sign in to join this conversation.
No reviewers
No labels
loop
ready
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language!245
No description provided.