Free learning / 50-minute retrieval proof workshop
Retrieval limits: an empty result can hide available evidence
Run actual SQLite FTS5 queries to compare late filtering, bounded overfetch and eligible result limits. Recheck changed grants and source revisions before assembling context.
No account needed. Use your own copy for writing and keep the original as a reference.
Engineering lab library / Run locally
Choose your next agent lab.
Start with the durable workflow reference, then choose the boundary you want to test. Open its walkthrough for setup, commands and expected observations.
Synthetic records. No provider key or model inference. Runs locally with the standard library.
Start here / Python 3.11+ · Standard library
Retrieval candidate limits and current-source gates
Compare actual ranked populations, changed grants and revisions without weakening context admission.
Python 3.11+ · Built-in SQLite FTS5 · Synthetic in-memory rows · No network or model
Original LockedIn Labs workshop · 50 minutes · Python 3.11+ with built-in SQLite FTS5. Eight independently reset in-memory cases use fictional documents and grants. No model, embeddings, account, network, persistent database or production authorization service is involved.
1. Ask which population the limit applies to
A search service returns its two highest-ranked candidates. Both are outside the current reader's permitted population. Removing them afterward can correctly prevent disclosure while leaving an empty context, even though three matching permitted sources exist farther down the ranking. “Nothing admitted from this candidate list” is not “no permitted evidence exists.”
Retrieval-augmented generation, or RAG, supplies selected source material before a model answers. Its candidate budget affects which evidence can reach later stages. Permission, source freshness, ranking and that budget have different jobs. A relevant result is not permission; an empty limited result is not a complete collection search.
The runner uses actual SQLite FTS5 full-text matching and its BM25 ranking function. It does not simulate semantic retrieval or execute an approximate nearest-neighbour index. All five source texts have 20 tokens; their occurrences of handoff are 9, 7, 5, 3 and 1. That isolates an understandable ranking comparison without making the score a probability or a portable confidence threshold.
2. Bind eligibility before limiting returned candidates
The host fixture supplies subject U-7 and active workspace North. It has three North read grants and a separate South grant. North's restricted document has no read grant. The active workspace still matters even when the same supplied subject has access elsewhere. The wrong-workspace request field says South, but the query retains trusted North context. This fixture supplies identity; it does not authenticate anyone or implement a workspace switch.
The eligible query joins the source on both tenant and document identity, requires active/current source revision, and checks the matching subject/read grant. Those result predicates appear in the same SELECT before its ORDER BY and LIMIT. Bound parameters protect SQL values; they do not invent missing authorization predicates.
-- Shape only; inspect the runner's complete ELIGIBLE_SQL and BODY_SQL.
WHERE search MATCH ?
AND source_workspace = trusted_workspace
AND source_is_active AND source_revision = indexed_revision
AND matching_current_read_grant
ORDER BY actual_bm25_rank, indexed_row
LIMIT ?This is a statement about eligible result rows and their limit. It is not a claim about the query planner's physical evaluation order. FTS5 uses the shared index and corpus statistics. The lab does not establish physical-read isolation, independent tenant indexes, side-channel resistance or a database-enforced production policy.
UNDERSTAND THE SYSTEM
Limit the eligible result, then recheck the source
Six authored frames explain eight actual in-memory SQLite FTS5 comparisons. They separate candidate coverage from current permission and source revision. This is not a live search engine, vector benchmark or physical tenant-isolation proof.
Watch one example, step by step.
Read the full worked example
- 011 · A global cap can hide available evidence
Post-filter selects South/S-OTHER and North/N-RESTRICTED at limit two, then correctly withholds both. The resulting empty context does not mean the three matching permitted North sources are absent. Inspect the ranked population before interpreting the empty packet.
ACTUAL LATE-FILTER RESULT: Two ineligible candidates · zero context · three eligible matches
- 022 · One extra candidate is not a guarantee
Overfetch selects limit plus one: the same two ineligible rows and N-A. Its gate admits N-A, still short of the requested two. This fixed increment can improve coverage without establishing completeness. At limit one, the two-candidate pool still admits nothing.
ACTUAL BOUNDED OVERFETCH: Three candidates · one admitted source
- 033 · Eligibility changes the returned population
The eligible SELECT binds trusted North/U-7, source identity/current revision and the current read grant before limiting result rows. It returns N-A/N-B. A request saying South does not change that trusted context. No-grant and no-match are separate empty controls, not zero-score winners.
ACTUAL ELIGIBLE RESULT: N-A and N-B at limit two
- 044 · Earlier eligibility does not survive revocation
Revoked selects N-A/N-B, then removes N-A's read grant. The final gate admits N-B only. The deliberately unchecked indexed copy still contains N-A. A separately observed new selection finds N-B/N-C; it did not refill the earlier packet automatically or atomically.
ACTUAL SEQUENTIAL GRANT CHANGE: N-A withheld: permission_missing · N-B admitted
- 055 · Indexed text can outlive its source revision
Corrected selects N-A/N-B, then changes N-A's source to revision two while its indexed body remains revision one. The source gate withholds N-A instead of using stale index text. Requery selects N-B/N-C; it does not refresh the index or recover N-A's changed evidence.
ACTUAL SEQUENTIAL SOURCE CHANGE: N-A withheld: source_revision_changed
- 066 · A smaller budget is not a permission change
Change the limit from two to one. Eligible selection returns N-A alone; late-filter and its fixed one-extra overfetch remain empty. The same grants exist. Query predicates constrain result rows, not the engine's physical read order; this shared FTS5 corpus is not an ANN or separate-tenant index experiment.
CONTROLLED LIMIT CHANGE: Limit 1 · eligible N-A · late-filter empty
Read the connections in plain English
- 01Ranked search index → Trusted eligibility
Bind source and grant relationships in the result query.
- 02Trusted eligibility → Candidate budget
Apply the result budget to the eligible population.
- 03Ranked search index → Candidate budget
The late-filter comparison caps global candidates first.
- 04Candidate budget → Current source read
Validate each selected source again before reading its body.
- 05Current source read → Context packet
Retain only current permitted source bodies.
- 06Current source read → Receiving evidence
Keep the specific missing permission, changed revision or scope reason.
- 07Context packet → Receiving evidence
Inspect the actual admitted population and its remaining gaps.
Check your understanding: Does an empty context after filtering prove no permitted matching source exists?
No. The post-filter case discards two globally selected ineligible rows while three permitted matching North sources remain outside that cap. Inspect candidate coverage and current-source decisions separately; never fill a gap with a rejected source to meet a count.
3. Run eight independently reset cases
Download the Python runner into a disposable folder:
python3 --version
python3 fde-retrieval-limits.py > retrieval-limit-two.json
python3 fde-retrieval-limits.py --limit 1 > retrieval-limit-one.json
python3 fde-retrieval-limits.py --case revoked
python3 fde-retrieval-limits.py --case correctedOn Windows, use py -3 in place of python3. SQLite must have built-in FTS5; the program reports a missing feature instead of downloading an extension. Each case creates and closes a fresh memory database. Redirection overwrites an existing output file: keep the original report under a new name before repeating. The runner accepts only its authored case and a limit of 1, 2 or 3.
Predict the default limit-two observations:
| Case | Candidates selected | Context after the current-source gate |
|---|---|---|
post-filter | South/S-OTHER and North/N-RESTRICTED | Empty, despite three matching eligible North sources |
overfetch | The same two, then North/N-A | N-A only; one extra candidate still does not fill two eligible places |
pre-limit | North/N-A and North/N-B | Both current permitted sources |
revoked | N-A and N-B, then N-A's read grant is removed | N-B only; the unchecked index copy still contains N-A |
corrected | N-A and N-B, then N-A's source changes to revision 2 | N-B only; the unchanged index still has N-A revision 1 |
wrong-workspace | Request says South, trusted context stays North | N-A and N-B; the request field does not change the workspace |
no-grant | Supplied subject U-8 has no grants | No candidates or admitted context |
no-match | Query volcano has no full-text matches | No candidates or admitted context |
overfetch asks for limit + 1 global candidates before applying the same final gate. It is a bounded comparison, not a claim that overfetch can never work. A larger pool can improve coverage, but this fixed increment supplies no completeness guarantee. The corpus and actual rank order determine what it admits.
4. Check the source again when building context
Inspect candidate_sql, trusted_fixture_context, all_matching_eligible_before and candidates. Candidate rows contain index identity, revision and actual score; they are not source-body permission receipts. The final gate retains a decision per candidate and uses a separate body SELECT with workspace, document, active state, exact revision and current grant predicates.
Original selection: North/N-A r1, North/N-B r1
After revocation: N-A -> permission_missing; N-B -> admitted_current_source
After correction: N-A -> source_revision_changed; N-B -> admitted_current_sourceThe body in admitted context comes from the source table. In the two changed-source cases, deliberately_unchecked_index_context is a separate broken comparison: it reads selected indexed bodies without rechecking current permission or revision. It is not accepted context. Locate N-A in that unsafe copy and explain the exact failed condition before proposing a repair. All source text is fictional.
The program changes rows sequentially on one connection. It does not prove race-free revocation between a database read, model call and final delivery. Real owners must define the allowed ordering, snapshot/lease or version contract and test it in the receiving system. Removing a grant cannot recall text already delivered.
5. Change the budget without changing permission
Run with limit 1. Global post-filter and the two-candidate overfetch now admit nothing, while eligibility-before-limit selects N-A. Restore limit 2 and explain why N-A/N-B become available without granting access to another source. A smaller result budget is not an access decision.
After revocation or correction, the admitted packet is not automatically refilled. Its one remaining N-B source does not prove there is no other usable evidence. fresh_selection_after_change is a separately observed second query that selects N-B/N-C under the changed state; it is not an atomic continuation of the earlier selection. The corrected N-A remains excluded until the index supports its new revision.
Do not fill a gap with a rejected candidate to meet a requested count. Choose a bounded requery, return a partial/unsupported result for the permitted purpose, or ask the accountable source owner for the missing evidence. Context admission does not establish that a source supports the eventual answer.
6. Receive the retrieval boundary as a delivery pod
- Builder: retain exact runtime/SQLite versions, candidate SQL, original/changed commands and reports. Keep selected index rows, source-read decisions and the deliberately unchecked copy distinct.
- Receiver: reproduce the empty late-filter result, change only the limit and locate the still-available eligible sources. Then reproduce one source change and identify the failed gate using your own evidence.
- Application, source and search owners: agree trusted scope, actual index filtering semantics, source/permission ordering and a bounded refill policy. Name the next receiving-store or provider-specific test; a literal metadata filter alone is not an authorization system.
Each participant keeps their own record:
Synthetic alias / role:
Exact runner / Python / SQLite version / command:
Prediction before execution:
Trusted context / requested workspace:
Candidate SQL / depth / actual selected identities:
Own observed context / withheld reason / evidence path:
Available permitted source omitted by the candidate cap:
Changed limit / separate prediction and observation:
Own contribution / assistance:
Receiving action actually attempted / assistance:
Remaining gap / next bounded test / accountable role:Use the Identity 101 guide for token and grant binding, and index lifecycle for actual refresh, deletion and late-job handling. These practice results do not establish qualification, independence, client placement or deployment permission.
7. Defend the tradeoff in an interview
Explain why zero admitted candidates can coexist with matching permitted sources, and why a fixed overfetch increment is a workload-dependent choice. Trace which population each limit actually constrained. Distinguish a full-text no-match from an access refusal without promising which diagnostic details a production caller may see.
Explain why a correctly scoped selection still needs a current-source gate, and why that gate can reduce a previously full packet. Describe the separate requery rather than claiming it happened automatically or atomically. Identify the source revision and grant that your next test must bind.
Finally, compare this exhaustive local SQL result model with an approximate vector engine's documented prefilter and postfilter modes, including per-shard candidate effects. The workshop has no vectors, shards, ANN recall measurement, cloud adapter or production security proof. Inspect the actual provider contract and measure authorized evidence coverage on the intended workload before transferring an implementation choice.
Sources checked 28 October 2026: SQLite FTS5 and BM25, SQLite SELECT processing and LIMIT, and Microsoft's vector-filter mode documentation. The last reference describes a different engine; this lab does not reproduce its algorithms. The synthetic corpus, comparisons, diagrams and receiving exercise are original.