Repository navigation
v7.71.0
Pairs with engine-service-v0.1.146 (tagged on 2b85b33, live in the cloud since
19:02Z, before this client tag: every wire-ledger entry is additive). The engine
carries three RDR-192 changesets: vectors-027 (a fraction floor on the
quarantine move route), vectors-028 (the reaper expires client-moved knowledge
quarantine) and vectors-029 (the hybrid text-gate probe uses the GIN indexes
under row-level security, guarded, with a boot isolation check). Measured on a
PITR fork of production at the same tenant data, the text-gate probe went from
1,176 ms to 93 ms for a rare token in one collection and from 25.7 s to 157 ms
across all collections; the post-deploy gate read hybrid client p95 from
1,724 ms to 1,161 ms. The MCP surface shrinks: the ten operator_* tools are no
longer advertised (nx_answer runs them through plans), and Claude Code defers
the nexus-catalog schemas. See conexus/CHANGELOG.md for the plugin side.
Added
- The cloud client-path gate checks the RDR-192 sweep routes and the reaper through the public edge (nexus-wbfpw.50). Leg K posts requests that move nothing (refusals, a missing route, a read of an unregistered collection name) to
/v1/vectors/gc/*,/v1/vectors/reapableand/v1/vectors/manifest-less-census, and asserts the engine's own JSON and the 404/400/422/200 the client reads; leg J asserts thereaperobject of/v1/statusis present, enabled, fresh and free of failed passes. The success shapes of the sweep routes and the typed 503quarantine_restore_busyare listed in the script as uncovered, since they need writes.
Changed
- The ten analytical operators are no longer MCP tools (nexus-ivi4s; outside review, T2
nexus/outside-critique-mcp-surface-proposal-2026-10-04).operator_extract,_rank,_compare,_summarize,_generate,_filter,_check,_verify,_groupbyand_aggregatelose their@mcp.tool()registration and stay plain functions innexus.mcp.core. Each spawns aclaude -psubprocess on the user's credentials, and together they cost every session about 4k tokens of tool schema;nx-mcpnow registers 44 tools.nx_answerand plan execution are unaffected: a plan step names the bare verb and the runner resolves it in-process withgetattr(nexus.mcp.core, "operator_<verb>"), which never went through the MCP registry. A session that called an operator directly now gets tool-not-found; asknx_answer, or save a plan whose steps name the verbs. The auto-approve hook's ten operator entries and thenexusskill's direct-call examples go with them. - Claude Code defers the
nexus-catalogtool schemas (nexus-ivi4s).conexus/.mcp.jsonsetsalwaysLoad: falseonnexus-catalog, so its 10 tools (about 3.4k tokens) load through tool search on first use rather than in every session.nexusandsequential-thinkingstay always-loaded: conexus 4.34.4 turnedalwaysLoadon for every server because sessions stopped invoking tools whose schemas were deferred. - The move into quarantine has a fraction floor on the engine route, so the indexer's prune and
nx t3 gcshare one guard (nexus-wbfpw.52, RDR-192).POST /v1/vectors/gc/quarantine-orphans, bounded or not, takes optionalfloor_fraction,floor_min_chunks(default 100) andforce. The engine judges the reaper's reading inside the move's own call, under the sweep gate, on the whole reapable set (at least the minimum, and strictly more than the fraction of every stored chunk): over it, nothing moves, the response saysrefused: truewithreapable_countandtotal_count, and onegc_auditrow (operation=gc_quarantine_orphans_refused) records it (one per state: a repeat with identical counts and floor values under an hour after the last row answersrefused: trueand writes no row). Every response echoes thefloorit applied, or{"given": false}. A request without the fields is unchanged. New functionnexus.gc_quarantine_orphans_floored(changesetvectors-027), a wrapper over the released move functions, none of which changes. Client half:nx index repo's prune used to move with no floor at all (NX_GC_FLOOR_FRACTIONguarded only the later hard delete); it now sendsNX_GC_FLOOR_FRACTION, the 100-chunk minimum andNX_GC_FORCE(soNX_GC_FORCE=1exported in a shell disables this floor for every later index run; set it per command), logsgc_prune_refused_by_flooron a refusal and still runs its expiry.nx t3 gcsends the same fields on the first batch of its drain (the floor is judged on the whole set and a later batch cannot be refused) and exits 1 on an engine refusal (a set that grew after its listing). Both read the echo: against an engine that predates the fields,nx index repologsgc_prune_floor_not_applied_by_engineandnx t3 gckeeps its listing check, which still runs first, and says in the summary that the engine did not judge the floor. Paired change: the engine half needs the next engine tag; an older engine is safe, just unguarded. - The engine reaper also expires the quarantine rows a client moved, in
quarantine-knowledge__*siblings only (nexus-wbfpw.75; Sam, 2026-10-04). Nonx index reposweep runs on a knowledge collection, so rowsnx t3 gcmoved there waited for a hand-runnx t3 gc. A new engine changeset (vectors-028,nexus.reaper_expire_client_quarantine) deletes the rows the reaper does not own whoseorigin_collectiontag names a registered, liveknowledge__origin, in a registeredquarantine-sibling withcontent_typeknowledge and lifecyclequarantine, once the row's ownquarantined_atisNX_REAPER_CLIENT_QUARANTINE_RETENTION_DAYS(new engine setting, default 14) old. It never deletes a chash the origin's manifest names, has no fraction floor, and writesgc_auditoperationreaper_expire_client_quarantinewith every deleted chash. Code, docs and rdr quarantine stays with the client and the user'sNX_GC_QUARANTINE_DAYS, which no longer governs knowledge quarantine on an engine with this change (a user value above 14 does not keep those rows).GET /v1/statusis unchanged. Seedocs/operations/engine-reaper.md.- Deploy note: the first reaper pass after deploy deletes any client-moved knowledge quarantine already past 14 days (cap 5,000 per origin per pass). The 5,835 rows that prompted the decision were force-expired on 2026-10-03 (nexus-wbfpw.69), so few or none are expected; conexus should run a read-only count of the function's selection predicate before deploying. Audit:
nx catalog gc-audit list --operation reaper_expire_client_quarantine --json. Restore window: 14 days. To hold it, setNX_REAPER_CLIENT_QUARANTINE_RETENTION_DAYS=3650, orNX_REAPER_ENABLED=false(no separate off switch; that stops the whole pass).
- Deploy note: the first reaper pass after deploy deletes any client-moved knowledge quarantine already past 14 days (cap 5,000 per origin per pass). The 5,835 rows that prompted the decision were force-expired on 2026-10-03 (nexus-wbfpw.69), so few or none are expected; conexus should run a read-only count of the function's selection predicate before deploying. Audit:
Fixed
- The hybrid search gate probe reaches the GIN text indexes under the service role's row-level security (nexus-wbfpw.48; engine change, vectors-029). Under
nexus_svc(FORCE RLS)nexus.text_gate_probe_<dim>was a sequential scan of the tenant's chunks: 62 to 80 ms against 2 to 3 ms with row-level security out of the way, 24,000 chunks, one fixture (the bead records both runs). The operators the gate evaluates (@@on tsvector,<%) are not leakproof, and PostgreSQL will not make a non-leakproof operator an index condition on a relation that carries a security qual. The probe is nowSECURITY DEFINERwith its own fail-closed tenant predicate (tenant_id = NULLIF(current_setting('nexus.tenant', true), '')) and a pinnedsearch_path; its results are the same chashes (live(c), collection filter, where filters andLIMITunchanged). On the same fixture asnexus_svcthe selective gate (144 rows) went from 76 ms to 2 ms, and the dense gate (a token in 70% of rows, 5,001 rows returned) from 52 ms to 44 ms; neither was measured at a larger size, and the dense gate's body is now planned without the call's parameter values, so it can no longer choose a sequential scan with an earlyLIMIT. A definer function runs as the migration role, which is the table owner, and FORCE RLS applies to an owner, so the changeset also adds a SELECT-only permissive policychunks_gate_probe_owner_readonnexus.chunksfor that role alone. The cost, stated: the migration role can now read every tenant's chunks without stamping a tenant; it gains no write access (UPDATE and DELETE still match nothing without a tenant). The policy isTO CURRENT_USER, so the changeset is guarded: it is skipped whole (probes staySECURITY INVOKER, correct and slower) whennexus_svcis the migrating role, as in the single-role dev posture, or has that role's privileges, or when any other login role that row-level security binds (not a superuser, not BYPASSRLS) has them, because the policy would then bind to that role and let it read every tenant's chunks; it also ends with a post-condition that fails the migration, and rolls the whole changeset back, unless the probes are definer functions owned by the migrating role, the policy names that role alone and no such role exists. A grant made after the changeset ran, or a service that connects as a role other thannexus_svc, is not something a changeset can see, so the engine now checks at every boot, on the pool that serves tenant traffic, that no permissive policy onnexus.chunksother thantenant_isolationapplies to its role (through PUBLIC, the role itself or a role it inherits), and refuses to start naming the policy and the role if one does (log eventchunks_isolation_check_failed).GET /v1/statusreports the same question live aschunks_tenant_isolation_intact(a boolean, additive; the route is unauthenticated, so it names no policy or role), andnx doctorhas aChunk tenant isolationrow that fails hard onfalseand reads not applicable on an engine that predates the field.EXECUTEon the three probes moves from PUBLIC tonexus_svc. The gate insidetext_gated_search_hnsw_first_<dim>andhybrid_search_<dim>is unchanged. Deploy needs: the PITR-fork walk with a before and after probe timing on real natural-language queries (the dense case above is the one to watch at production scale), and confirmation of the cloud's migration role and that no login role other than the migrator,nexus_svcincluded, has its privileges (SELECT r.rolname FROM pg_roles r WHERE r.rolname <> '<that role>' AND pg_has_role(r.oid, '<that role>', 'USAGE') AND NOT r.rolsuper AND NOT r.rolbypassrls AND r.rolcanloginreturns no row), since a row makes the guard skip the changeset and the probe stays a sequential scan; that query has not been run against the cloud.