Skip to content

v0.8.57: knowledgebase connector improvements, distributed oauth refresh - #8180

Merged
waleedlatif1 merged 16 commits into
mainfrom
staging
Sep 23, 2026
Merged

waleedlatif1 merged 16 commits into
mainfrom
staging

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

…hes (#8171)

* fix(oauth): scope unauthorized_client as terminal to Atlassian refreshes

* fix(oauth): look up provider terminal codes in a Map
… removing a large source cannot time out (#8165)

* fix(knowledge): release kept connector documents in the background so removing a large source cannot time out

* fix(knowledge): reserve kept connector storage at removal and settle it when the release finishes

* fix(knowledge): carry detach reservations through payer moves and refuse credentials to removed connectors

* test(workspaces): add knowledge_connector to the payer-move lock-order fixture schema
…ojection fan-out (#8169)

* fix(knowledge): refresh unchanged mirrored ACLs without firing the projection fan-out

An admin-mode sync rewrote the ACL of every listed document on every page, even
when it had not changed. Assigning `acl` fires the document trigger that copies
it onto every chunk's search projection rows, and a projection row the backfill
has not filled yet is distinct from every ACL, so each unchanged document still
rewrote all of its chunks, each one a vector index insert. A single ACL batch
could then exceed the statement timeout and fail the whole sync.

Unchanged ACLs now only refresh their evidence timestamp, which never assigns
`acl`. Documents whose ACL or restrictions actually changed are written in
smaller batches, since each one fans out to all of its chunks.

* test(knowledge): run the ACL write Postgres test in CI and cover unresolved evidence and multi-batch changes
…jection chunks (#8175)

* fix(knowledge): stop document ACL changes from rewriting unfilled projection chunks

The document trigger copied a changed ACL onto every enabled chunk whose
projected ACL differed, and a chunk the projection backfill has not filled yet
(acl IS NULL) always differs. Each such write re-inserts the row into the vector
index, so one document ACL change inside a sync or member rewrite could rewrite
every unfilled chunk of that document in the writer's own statement.

Unfilled chunks now keep a NULL ACL: search already decides them on their
document, and the backfill copies the document's current values under a share
lock. A document that moves to another source still carries the source onto
them, since source filters read it from the row. The new body ships as script
migration 0023, which replaces the function in place; 0022 installs the same
body on a fresh database.

* test(knowledge): expect the projection ACL trigger migration in the upgrade run

* chore(ci): create the ACL test database in the projection trigger step itself
…r as out of scope, not listing failures (#8168)

* fix(knowledge): treat Google Workspace users without Gmail or Calendar as out of scope, not listing failures

Admin-mode Gmail recorded a Directory user without a mailbox, and Calendar recorded a
403 notACalendarUser, as per-user listing failures. Both are standing account properties,
so the connector stayed partial, deletion reconciliation never ran, and the scheduler
re-probed the same accounts every retry window.

- Directory enumeration no longer schedules Gmail users whose mailbox is not set up; the
  hourly Directory refresh picks them up once provisioned. A partition queued earlier
  completes with an empty page instead of a failure.
- A Calendar 403 whose only reason is notACalendarUser completes the user's partition
  cleanly and re-probes it no sooner than the Directory refresh (permissions keep their
  own refresh cadence). Bare forbidden and mixed-reason 403s stay retryable failures.

* fix(knowledge): keep a Google Workspace user's visible documents until a missing service outlasts propagation

Google applies service and organizational-unit changes within 24 hours, so a missing
mailbox or notACalendarUser can be transient for a user whose documents are already
indexed, and a mid-listing answer does not prove the whole account lacks the service.

- The scheduler skips a service-not-enabled user only on their first provider page, and
  only when readers see none of their documents or the condition was first observed at
  least 24 hours ago. Otherwise it is a retained failure, as before, whose first
  observation is kept in the partition failure; after 24 hours a mid-listing user
  restarts from their first page.
- Permission passes skip on the first page, since a retained failure refreshes nothing.
- Gmail Directory enumeration keeps scheduling a user without a mailbox while readers
  still see their mail; the crawl reports the missing mailbox to the scheduler instead of
  completing the user.
- Visibility is read through doc_acl_gin_idx for the user's token behind an OFFSET 0
  fence, bounded by that user's grants.

* fix(knowledge): read a user's visible documents from the ACL index and cover the probe in PostgreSQL

The visibility probe moves to its own module so it can run against a real database.
Planned inline, LIMIT 1 made a sequential scan of the document table look cheaper than
doc_acl_gin_idx, because PostgreSQL cannot estimate array overlap. A materialized CTE now
reads the user's grants from the index first, bounding the probe by that user's grants.
The email goes through userToken, so a mixed-case or padded directory address matches the
normalized ACL token.

The new PostgreSQL integration test covers fresh, stale and missing permission evidence,
another user's grant, another connector, excluded, archived and deleted documents, and
email normalization, and runs in the Search progress PostgreSQL CI step.
…document (#8176)

* fix(knowledge): read an unfilled keyword candidate's source from its document

The Tin keyword page took each candidate's source from its projection row. A
row the source and ACL fill has not reached carries a NULL source, so a chunk
from a source that needs a live reader proof was never recognized as one: the
proof was not resolved, hydration ran without the caller's grants, and the
document was hidden from a member who can read it.

Until the keyword projection is filled, a page's unfilled rows now take their
source from the document, one primary-key read per row of the page after its
limit. The excluded-sources filter asks the document the same way, as the
vector leg already does, so a denied source's unfilled rows stop taking slots
on a rebuilt page. The filled path's statement is unchanged.

* test(knowledge): assert the ACL write's fan-out, not unfilled rows the trigger now skips

The projection trigger no longer writes an ACL onto chunks the backfill has not
filled, so the ACL write tests can no longer see an unchanged write through
those rows. They now count the document writes that fire the fan-out trigger,
and expect a changed ACL on filled chunks only.

* fix(knowledge): read the document only for unfilled rows in the keyword source exclusion
…, not app configuration faults (#8178)

* fix(knowledge): unschedule connectors only for credential revocations, not app configuration faults

* test(knowledge): mark the revocation fixture as const
…hanged (#8172)

* fix(knowledge): bound the members-mode tombstone pass by what a run changed

The tombstone phase of the member document lifecycle ran one anti-join over
every live document of the connector on every run, and LIMIT could not bound
it because almost every document is observed. On a large connector that
statement outgrew the statement timeout, rolled back the member's page, and
walked the connector toward disabled.

Tombstoning is now driven by the documents whose observations the run itself
removed (unseen after a complete listing, withdrawn by a change feed, or held
by a member being removed), then by a resumable backstop that walks the
reconciliation index one page per statement and checks observations only in
the UPDATE over that page's live ids. The backstop saves its position on the
connector and does a bounded number of pages per run. Resurrection and purge
are unchanged.

* fix(knowledge): walk the members-mode reconcile by external id and tombstone explicit removals without a completed listing

The absence reconcile ordered its resumable walk by source_seen_at, which
every member listing rewrites for what it observed. On a connector larger than
one run's page budget the walk chased re-stamped documents and a pass never
ended, so a document that lost its observers behind the cursor was never
revisited. The walk now keys on external id through
doc_connector_external_id_idx, which a document never changes, so a pass
completes within ceil(documents / budget) runs.

Documents a run explicitly unobserved (a complete listing, a change-feed
withdrawal, or a member removal) are now tombstoned even when no member with a
completed listing remains, as the stale-member sweep already does. Removing
the only listed member previously left every document only it observed live
and unobserved indefinitely. The reconcile and the purge stay gated on a
completed listing.

* fix(knowledge): keep member removal ids across interrupted runs and hide the reconcile cursor

- A member removal interrupted by the run deadline resumed from its saved
  document cursor and only handed the lifecycle the documents above it. The
  resumed walk now re-reads the removed member's observations below the
  cursor first. They still exist until the member row is deleted.
- The absence reconcile writes its cursor once per run instead of once per
  page, and documents how many runs a pass takes.
- Connector presenters drop member_tombstone_cursor alongside the stored API
  key, so the internal cursor never reaches API responses.
- CI runs member-document-lifecycle.integration.ts with the other knowledge
  PostgreSQL suites.

* chore(db): drop the tombstone cursor migration to regenerate it after staging

* chore(db): regenerate the tombstone cursor migration on top of staging

* chore(db): format the tombstone cursor migration metadata

* fix(knowledge): tombstone a removed member's sole documents with the removal itself

A membership removal handed the documents its member alone observed to the
lifecycle through run memory. If the run stopped after the member row was
deleted, the ids were gone, the observations had cascaded away, and with no
member left with a completed listing the absence reconcile never ran. Those
documents stayed live indefinitely.

Each removal page now tombstones the documents no other member observes in
the same transaction that advances the removal's checkpoint, so nothing
depends on the run surviving to its lifecycle. This applies only where
observations decide existence; a corpus owned by a dedicated content
credential is left alone, as before. The in-memory re-collection added
earlier is removed.

The absence reconcile's external_id IS NOT NULL filter is documented. Every
writer that sets connector_id copies a required external id, so the filter
excludes nothing.

* fix(knowledge): resurrect a withdrawn removal's tombstones with the member's restored ACLs

A multi-run member removal tombstones, page by page, the documents only that
member observes. If directory re-listing withdrew the removal mid-walk, the
restarted walk restored the member's token to those ACLs, but the documents
stayed deleted until the lifecycle's resurrection phase ran. That phase runs
only after the member loop finishes before the deadline.

A membership walk that is not a removal now applies the lifecycle's own
resurrect predicate to each page, in the same transaction. The predicate is
shared through one helper and covers: connector-owned, not excluded, not
archived, content present, observed. Only where observations decide
existence.

* test(knowledge): insert the content-less fixture row with its own typed insert
@vercel

vercel Bot commented Sep 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
docs Skipped Skipped Sep 23, 2026 2:18am UTC

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 74 files

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread apps/sim/lib/knowledge/connectors/detachment.ts
Comment thread apps/sim/connectors/google-workspace/company-crawl.ts
Comment thread apps/sim/lib/knowledge/__integration__/drain-connector-event.ts
Comment thread apps/sim/lib/knowledge/connectors/user-document-visibility.ts
Comment thread scripts/test-knowledge-acls.ts
@greptile-apps

greptile-apps Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no concrete correctness, security, migration, or repository-rule violation remained after review.

Summary

This release hardens OAuth refresh classification and delegated executor reads while substantially revising knowledge-connector lifecycle, access synchronization, search projections, and storage accounting.

  • Moves keep-document connector removal into a bounded, durable detachment outbox flow with reservation-based storage accounting.
  • Bounds members-mode tombstone reconciliation and couples explicit observation removals to document lifecycle updates.
  • Treats selected Google Workspace service-disabled users as out of scope while protecting recently visible documents during propagation.
  • Avoids unnecessary ACL projection fan-out and correctly resolves unfilled keyword candidates through their source document.
  • Distinguishes credential revocation from OAuth app-configuration faults and retries transient delegation database reads.
  • Adds schema, script-migration, PostgreSQL integration, and CI coverage for the new lifecycle behavior.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Remove connector] --> B{Keep documents?}
  B -- No --> C[Mark connector deleted]
  C --> D[Cleanup outbox]
  D --> E[Delete chunks and documents in bounded pages]
  B -- Yes --> F[Admit storage and reserve bytes]
  F --> G[Mark connector detached]
  G --> H[Detachment outbox]
  H --> I[Release projection rows in bounded pages]
  I --> J[Convert documents to standalone entries]
  J --> K[Consume storage reservation]
  K --> L[Delete drained connector and settle remainder]
Loading

Reviews (1) · Last reviewed commit: "fix(knowledge): bound the members-mode t..."

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
* fix(traces): allow explicitly skipping oversized backfill records

* fix(traces): support larger execution trace archives
…urged (#8184)

* fix(knowledge): settle detach reservations when a knowledge base is purged

- Retention purge settles a detached connector's remaining detach_reserved_bytes
  (same lock order and settlement branches as the detach job) and zeroes it
  before the knowledge base delete cascades the connector away
- Test drain helper picks the oldest pending outbox row
- Unfilled-projection fixture writes its Tin row the way the projection trigger does
- Knowledge ACL harness also runs the 0021 projection source/ACL postgres test

* fix(knowledge): settle a purged base's detach reservations as one net amount

* fix(knowledge): settle a purged base's reservations before deleting its documents

* test(memory): always change a byte when tampering with a checkpoint's auth tag

* fix(knowledge): settle overdrawn reservations before a purge's documents and the rest after

* fix(knowledge): pause a connector detach while its knowledge base is deleted
* fix(knowledge): restore GitLab PAT setup and secret references

* fix(knowledge): align secret reference styling and caret tracking

* fix(knowledge): reuse shared secret reference highlighting
…8186)

* fix(desktop): replace a stale staged update with the newest release

* fix(desktop): track Squirrel staging so update refreshes never cancel an install

* fix(desktop): clear a cancelled update replacement so refreshes resume
@waleedlatif1
waleedlatif1 merged commit 3975ae5 into main Sep 23, 2026
50 checks passed

This branch was previously deployed

1 inactive deployment
Preview fb2c3f32 Deployed Sep 23, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants