Document Routing
Audience: Engineers configuring document routing rules or privilege classification; compliance officers reviewing the privilege gate.
What you will accomplish: Understand the 11-step document routing pipeline — from idempotency check to audit record — including the non-overridable privilege gate, ACL derivation, and classification confidence thresholds.
Prerequisites: Read Core Services and RBAC & Privilege first.
Estimated time: ~8 minutes.
Classify, name, file, and permission documents to the correct matter folder — with non-overridable privilege gates that prevent privileged documents from ever reaching external recipients.
Tool: document.route
Risk tier:
- Internal filing (
write confirm) — Filing to matter folders within the document store - External share (
gated human) — Sharing with recipients outsidematter.participants
Pipeline
Step 1: Derive idempotency key
sha256(document_id:checksum:recipient_id:routing_intent_version)[:16]. Same document + same recipient + same intent = duplicate.
Idempotency key derivationStep 2: Idempotency check
Return duplicate if already routed. No second move, no second share.
IdempotencyStoreStep 3: Classify
Determine document class from name hints, extraction confidence, and content analysis. Classes: engagement_letter, court_filing, id_document, correspondence, internal_memo, form_filing, unknown.
Classifier.classifyStep 4: Resolve destination
Map document class to folder path via taxonomy. Totality: every class has an entry. Unknown → review_queue.
Folder taxonomyStep 5: Derive canonical name
Name template: {matter_reference}_{doc_class}_{date}_v{version}. Ensures consistent naming across all documents.
Name templateStep 6: Build ACL
ACL = matter.allowed_principals ∩ class_permission_policy. Always a subset of matter.allowed_principals.
ACLBuilderStep 7: Privilege gate (non-overridable)
If document.privileged=True and external target → blocked. No approval token can override. Default-deny: unknown privilege + external → blocked.
PrivilegeGate.check — fail-closedStep 8: Review queue
If classification confidence < threshold or doc_class=unknown, queue for human review instead of filing.
Confidence threshold: 0.80Step 9: Dry run
If dry_run flag set, compute destination, name, and ACL but do not move. Returns computed result for preview.
Preview modeStep 10: DocStoreConnector.move
Execute the move. Returns moved Document with updated URI and ACL.
DocStoreConnector.move — write (confirm)Step 11: Audit record
Write hash-chained audit record before returning. Includes document_id, recipient_id, destination, ACL, and privilege verdict.
AuditService.record
Privilege invariant
The privilege gate is fail-closed and non-overridable:
if document.privileged and recipient.external:
return CheckResult(
status="fail",
check_id="privilege",
reason=f"Privileged document {document.id} cannot reach external recipient {recipient.id}",
)document.privileged=True+ external target → always blockeddocument.privileged=None(unknown) + external target → blocked (default-deny)document.privileged=False+ external target → allowed (subject to normal gate)
This is proven by a Hypothesis property-based test (test_pbt_privilege_never_passes_external) that generates hundreds of (privileged, external) combinations.
ACL correctness
The ACL is always a subset of matter.allowed_principals:
allowed: set[str] = set(matter.external_ids.get("allowed_principals_json", "[]"))
# or fallback: matter.external_ids.get("allowed_principals", "").split(",")
class_policy: set[str] = CLASS_PERMISSION_POLICIES[doc.classification]
final_acl = allowed ∩ class_policy
assert final_acl ⊆ allowed # Property-based test invariantThe ACL can never broaden access beyond what the matter allows.
Idempotency key
sha256(document_id : checksum : recipient_id : routing_intent_version)[:16]- Replay →
duplicate, no second move - New checksum (different document version) → new key → new decision
ASSUMPTION (confirm)
- R-001 — Document class enumeration
- R-002 — Folder taxonomy and naming template
- R-003 — Classification confidence threshold (default 0.80)
- R-004 — Recipient routing for v1 (filing+ACL only or also external share)
- R-005 — Internal vs external boundary for co-counsel and the client themselves
- R-006 —
warnprivilege verdict for internal filing
Verification
document.route(doc, recipient_internal)succeeds for privileged documents (internal filing)document.route(doc, recipient_external)is blocked for privileged documents- Audit log shows
document.routewithprivilege_verdict: blockedfor blocked attempts - PBT
test_pbt_privilege_never_passes_externalpasses across generated inputs
Next steps
- Document Generation — Where documents are created before routing
- Security: RBAC & Privilege — The full access control model
- Core Services: QC Verification — How checks are composed
Last updated: 2026-06-01