refactor: make the filter-polarity trap inexpressible service.SpaceFilter meant "empty membership selects nothing" — a newly created project has no members — while search.Query.Spaces was a bare []core.SpaceRef whose empty case meant every space. Passing a project's members into a query therefore turned an empty project into the whole corpus: a silent scope inversion, invisible when it happens, and passing every test written with a non-empty project. The filter moves to core.SpaceFilter with unexported fields, and search.Query takes it whole. There is no slice to hand over any more, so the inversion cannot be written. Its three states are distinct: every space (EverythingFilter), exactly these — possibly none — (SpacesFilter), and the zero value, which is neither. Search refuses the zero value rather than defaulting it, because both plausible defaults are wrong for one of the two callers that can produce one, and returns no hits for a filter that selects no space without asking the index. service.SpaceFilter is now an alias for the core type, so ResolveProject and its callers keep their names. Tests that built a Query without a scope now say core.EverythingFilter(), which is what they always meant.
feat: projects — a saved filter over one global index, not a container A project is a named space set; querying one filters the single global index. The meta-project is an implicit filter at a reserved address rather than a row: a stored +everything would need a sync job on every space creation, and its one failure mode is silently omitting a space. SpaceFilter distinguishes All from an empty member list, because a freshly created project has no members and must mean selects-nothing. Collapsing the two would make every new project silently match the whole corpus.