From c833182e7a6698f549f0c2b00a79b513cc157cd5 Mon Sep 17 00:00:00 2001 From: bigbes Date: Wed, 22 Jul 2026 14:30:32 +0300 Subject: [PATCH] docs: add the project schema and record the filter-polarity trap Projects are no longer deferred, so the schema section carries them. Records why the meta-project is an implicit filter rather than a row: a stored +everything needs a sync job on every space creation, whose only failure mode is silently omitting a space, and a corpus-wide view must not be renameable or deletable. Flags the polarity hazard between the two layers. An empty project selects nothing while an empty search space list means every space, so passing a project's members straight into a query turns an empty project into the whole corpus. The durable fix is for queries to take the filter type rather than a bare slice, which is deferred while the read surfaces are mid-build against the current signature. --- docs/DESIGN.md | 61 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 61fc06628bd36462c53daeb80738c1f7b1c7d408..2978a94c7c30b26affca908b9c6ff09e1ab5f1b2 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -1107,13 +1107,55 @@ CREATE TABLE digest_mark ( ); ``` -Projects are deliberately absent: a project is a saved filter, so it is a name -plus a space-id list, and adding it before there are several spaces to filter -would be speculative. It arrives with the meta-project in Phase 2. +```sql +CREATE TABLE project ( + id SERIAL PRIMARY KEY, + owner TEXT NOT NULL, + name TEXT NOT NULL, + created TIMESTAMPTZ NOT NULL DEFAULT now(), + UNIQUE (owner, name) +); + +-- Membership is a SET: the composite key both dedupes and indexes project->space. +CREATE TABLE project_space ( + project_id INTEGER NOT NULL REFERENCES project(id) ON DELETE CASCADE, + space_id INTEGER NOT NULL REFERENCES space(id) ON DELETE CASCADE, + PRIMARY KEY (project_id, space_id) +); +CREATE INDEX ix_project_space_space ON project_space (space_id); +``` -`comment` is likewise absent — inline comments are post-v1, and the anchoring -model (`doc_id`, heading path, block index, block hash) should be settled by -building the review UI before it is committed to a schema. +**The meta-project is not a row.** `~owner/+everything` resolves to an implicit +"all spaces" filter, and the name is reserved so nothing can create a row that +shadows it. A stored `+everything` would need a **sync job** appending every +newly created space — which the Project section explicitly rules out — and that +job's single failure mode is a meta-project silently missing a space, which is +invisible. It also must not be renameable or deletable: a corpus-wide view is not +an object with a lifecycle. Consequence to remember: a project listing cannot +show the meta-project, and any UI must add it by hand. + +**Empty-means-nothing, and the polarity trap that follows.** A freshly created +project has no members and must select **nothing**; the corpus-wide filter is a +distinct `All` flag, not an empty list. This is the opposite polarity from the +search layer, where an empty space list means *every* space: + +> **Wiring a project to a search query by passing its member list straight +> through inverts the meaning of an empty project — from "nothing" to "the entire +> corpus".** A silent scope inversion, invisible in any test that only uses +> non-empty projects. + +Documented on both types today; the durable fix is for the query to take the +filter type itself rather than a bare slice, so the mistake stops being +expressible. That change is deferred only because the read surfaces were being +built against the current signature at the time. + +The meta-project is **corpus-wide, not viewer-scoped**. With no visibility levels +and no ACL rows in v1 there is no "spaces the caller may read" to scope it to; +that phrase implies machinery that does not exist. + +`comment` remains absent — inline comments are post-v1, and the anchoring model +(`doc_id`, heading path, block index, block hash) should be settled by building +the review UI before it is committed to a schema. ## Implementation plan @@ -1259,9 +1301,10 @@ degrading, which is the point of listing them. - **Naming.** `spec.sr.ht` / `spec.srht.bigb.es` follows the named-by-function pattern of the two siblings. `docs.sr.ht` collides conceptually with upstream `man.sr.ht`. -- **Project URL namespace.** `~user/+project` distinguishes projects from spaces - (`~user/space`) in one character; alternatives are `/projects/~user/name` or - reusing hub.sr.ht's own namespace. +- ~~**Project URL namespace.**~~ **Settled: `~user/+project`**, one character + distinguishing a project from a space (`~user/space`), with `+everything` + reserved for the meta-project. Isolated in the ref parser, so changing it later + is one function. - **Port.** compare.sr.ht is on 5090, dolt.sr.ht on 5306–5308. 5091 is free and is what the config block above assumes. - **MCP transport.** Streamable HTTP on the same chi router (`/mcp`) keeps it to