Command Palette

Search for a command to run...

Hectal
PHASE 10Advanced ~23 min· topic 8 of 9

Topic 10.8

Build: Guards for AskDocs

In one line

AskDocs gets four guards: access labels enforced inside retrieve, a quarantine and wrapper for untrusted text, a redactor at ingestion, and a delete that reaches every store and is checked by an audit.

Think of it like this

After the house is built and furnished you fit locks, smoke alarms and a fuse box. None of them changes what the rooms are for, and every room is safer. The guards in this topic wrap the AskDocs you already have in the same way: the questions and answers stay the same, but each step now has a lock.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Guard
A small check added around a step to stop a particular kind of failure.
Access label
The metadata on a chunk saying which groups may read it.
Quarantine
Keeping a suspicious passage out of the prompt and recording it for review.
Wrapper
Text around the sources that marks them as data, not instructions.
Audit
A check across all stores that nothing from a deleted document remains.
Interface
The shape of a function (its arguments and results) that other code depends on.

Step by step

01Step 1: label chunks and redact at ingestion

ingest stores each document with its access label and replaces email addresses with [EMAIL] before anything else sees the text.

02Step 2: filter inside retrieve

The chunk list is reduced to those whose label is in the user's groups, then scored and cut to k. The groups come from the session, as an argument.

03Step 3: quarantine and wrap in the answer path

Hits whose text contains a suspicious phrase are removed and their ids returned. The rest go into the prompt between source tags under a line saying they are data.

Step 3: quarantine and wrap in the answer pathdiagram
Rendering diagram…

04Step 4: key the cache by groups and question

The same question from a public user and a staff user must not share an answer. The key holds the sorted groups and the normalised question.

05Step 5: write delete and audit

delete removes the chunks, the cache entries that cite the document and the log lines that name it. audit returns whatever remains. Run the audit right after the delete.

06Step 6: read the run

Check each line against the design: the public user does not get S1, the stored S1 text is redacted, X1 is quarantined, nothing is left after deleting D2, and the question about holidays is now refused.

Try it yourself

  1. 1

    Ask as a third user

    Call show with an empty set of groups. What does the product do? Is that the behaviour you want for a broken profile?

  2. 2

    Remove the groups from the cache key

    Change the key to use only the question. Run the staff question for the public user and then for the staff user. Who gets which answer? (Topic 10.4 explained why.)

  3. 3

    Delete X1

    Delete the planted passage with "X1" and ask the holiday question again. What changes in the sources line?

  4. 4

    Redact a second pattern

    Add a phone number to S1's text and a pattern for it next to the email one. Print the stored text.

Code & diagrams

AskDocs with access labels, quarantine, redaction and delete New tab

AskDocs after Phase 10. The retrieval inside is still the word-overlap scorer from Phase 0 so that every guard's effect is easy to read. Swap in any retriever from Phases 1 to 6 behind the same function.

Sign in to run this example in your browser.

Expected output

stored S1: Staff get a 30 percent discount on the monthly pass. Questions go to [EMAIL].
Q (public): How do I get the staff discount on the monthly pass?
  sources: D8 (3)   quarantined: X1
  answer:  The monthly pass costs 25 dollars and includes unlimited rides of up to 45 minutes. [D8]
Q (public+staff): How do I get the staff discount on the monthly pass?
  sources: S1 (7), D8 (3)
  answer:  Staff get a 30 percent discount on the monthly pass. [S1]
Q (public): When do shops close on public holidays?
  sources: D2 (5)   quarantined: X1
  answer:  Shops open at 7 am and close at 9 pm every day. [D2]
left after deleting D2: 0
Q (public): When do shops close on public holidays?
  sources: none   quarantined: X1
  answer:  I can't find that in the handbook.

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Filter after the cut

In the Python tab, make retrieve rank first and filter afterwards: remove the label test from the first hits = [...] line, change the last line of retrieve to return [hit for hit in hits[:k] if hit[1]["label"] in groups], and call retrieve(question, groups, 1) in answer (a small k, as many products use). Run.

terminal
$ Run
── what you'll see ──
stored S1: Staff get a 30 percent discount on the monthly pass. Questions go to [EMAIL].
Q (public): How do I get the staff discount on the monthly pass?
sources: none
answer: I can't find that in the handbook.
Q (public+staff): How do I get the staff discount on the monthly pass?
sources: S1 (7)
answer: Staff get a 30 percent discount on the monthly pass. [S1]
Q (public): When do shops close on public holidays?
sources: D2 (5)
answer: Shops open at 7 am and close at 9 pm every day. [D2]
left after deleting D2: 0
Q (public): When do shops close on public holidays?
sources: none quarantined: X1
answer: I can't find that in the handbook.

Myth vs fact

Myth

Guards slow the product down and change its behaviour.

Fact

These cost a few list scans and string checks. The answers for ordinary questions are unchanged. Only the unsafe cases behave differently.

Myth

Once a guard works for one question it works for all.

Fact

Guards are checked by tests across users, documents and phrasings. One passing question proves almost nothing.

Myth

Deleting from the chunk list is deleting the document.

Fact

The cache and the logs held copies. Only an audit that checks every store shows the document is gone.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Keep the guards in one module that every entry path calls (API, batch, evaluation harness). A guard that exists only in the web handler is skipped by the script that someone writes next month.

  • ▸

    Return the quarantined ids and the access decisions in the response object, as the example does, so tests and the evaluation harness (Phase 9) can assert on them. Log them with the request id.

  • ▸

    Make the ingestion redactor and the retrieval filter share one source of truth for labels and patterns, tested with the same fixtures. Divergence between the write path and the read path is where gaps appear.

Remember this

  1. 1

    Why the product needs this now. Until this phase AskDocs served one handbook to everyone. Real handbooks have staff-only pages, pasted tickets with email addresses, and files that anyone can upload; and sooner or later a document has to be taken out. The next phase puts the product online as a service, and it must not go online without these guards.

  2. 2

    What the step adds. (1) retrieve(question, groups, k) gets a groups argument and filters by each chunk's access label before ranking (Topic 10.3). (2) The answer path scans retrieved passages for orders, quarantines suspicious ones and wraps the rest as data (Topic 10.2). (3) ingest redacts email addresses before a chunk is stored (Topic 10.5). (4) delete(doc_id) removes the chunks, the cached answers that cite them and the log lines, and audit(doc_id) proves it (Topic 10.6).

  3. 3

    How it plugs into what exists. The retrieval interface gains one argument and keeps its shape: it still returns scored hits, best first, ties kept in document order, and a passage still needs a minimum score (two shared words here) to count as a hit. The prompt builder still lists sources, now between data tags, and the answer still carries a citation and a refusal. Everything else (the tokeniser, the scoring, the stand-in model) is unchanged and could be replaced by the BM25, vector or hybrid retrieval from Phases 1 to 6 behind the same interface.

  4. 4

    What the run shows. A public user asking about the staff discount is not given the staff passage (S1) and gets the closest public passage instead (D8, with the planted passage X1 also caught and quarantined, because it shares a few small words with the question). A staff user is given S1, and the stored text shows [EMAIL] where the address was. The holiday hours question retrieves D2 and X1, quarantines X1 for its order, and answers from D2. After D2 is deleted, the audit finds nothing, and the same question is refused: the planted passage is still quarantined and the real one is gone.

  5. 5

    What is still missing. The cache and the logs are one process's memory, not a service's (Phase 11 moves them into real stores with tenant keys). The detector is a phrase list (a classifier should join it). Redaction handles emails only (Topic 10.5 shows the others). There is no automatic test run yet: the playbook gives one. And there is one handbook, so tenants (Topic 10.4) do not appear until the service needs them.

Explain it without notes

01

List the four guards added to AskDocs and the threat each one answers.

02

Why does the cache key include the user's groups?

03

Why is the delete followed by an audit, and what does the audit in this topic check?

04

How does this step fit into the earlier retrieval interface without changing it much?

Practice

01

Add a groups check to a minimal retrieve: given CHUNKS = [("C1", "public"), ("C2", "staff")] return the ids visible to a set of groups. Print the result for {"public"} and for set().

02

Write cache_key(question, groups) that returns the sorted groups joined by commas, a bar, and the lower-case words of the question joined by spaces. Print the keys for {"staff", "public"} and {"public"}.

Trade-offs

  • ↔

    Guards in one shared module: every path is covered, and one bug in it affects every path. Test it heavily.

  • ↔

    Redacting at ingestion: clean everywhere downstream, but the original is lost unless you keep it in a restricted store.

  • ↔

    Caching with groups in the key: correct, and a lower hit rate than a shared cache.

Done when you can

  • Done when you can run the build example in all three languages and explain each output line.

  • Done when you can say which topic each guard comes from and what it blocks.

  • Done when you can show that a public user never receives staff text, in the answer and in the cache.

  • Done when you can show an audit that returns zero after a delete.