Now domain-agnostic — configure any practice with Domain Packs (immigration is the reference pack)
Workflows
Document Routing

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 outside matter.participants

Pipeline

  1. Step 1: Derive idempotency key

    sha256(document_id:checksum:recipient_id:routing_intent_version)[:16]. Same document + same recipient + same intent = duplicate.

    Idempotency key derivation
  2. Step 2: Idempotency check

    Return duplicate if already routed. No second move, no second share.

    IdempotencyStore
  3. Step 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.classify
  4. Step 4: Resolve destination

    Map document class to folder path via taxonomy. Totality: every class has an entry. Unknown → review_queue.

    Folder taxonomy
  5. Step 5: Derive canonical name

    Name template: {matter_reference}_{doc_class}_{date}_v{version}. Ensures consistent naming across all documents.

    Name template
  6. Step 6: Build ACL

    ACL = matter.allowed_principals ∩ class_permission_policy. Always a subset of matter.allowed_principals.

    ACLBuilder
  7. Step 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-closed
  8. Step 8: Review queue

    If classification confidence < threshold or doc_class=unknown, queue for human review instead of filing.

    Confidence threshold: 0.80
  9. Step 9: Dry run

    If dry_run flag set, compute destination, name, and ACL but do not move. Returns computed result for preview.

    Preview mode
  10. Step 10: DocStoreConnector.move

    Execute the move. Returns moved Document with updated URI and ACL.

    DocStoreConnector.move — write (confirm)
  11. 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 blocked
  • document.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 invariant

The 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-006warn privilege verdict for internal filing

Verification

  1. document.route(doc, recipient_internal) succeeds for privileged documents (internal filing)
  2. document.route(doc, recipient_external) is blocked for privileged documents
  3. Audit log shows document.route with privilege_verdict: blocked for blocked attempts
  4. PBT test_pbt_privilege_never_passes_external passes across generated inputs

Next steps


Last updated: 2026-06-01