- Authority enrichment now enforces **document-level** visibility on every user-facing entry point. `EnrichmentService.discover`, `EnrichmentService.scan`, and `EnrichmentService.apply` (`opencontractserver/enrichment/services/enrichment_service.py`) load their document set through `_load`, which always routes via `CorpusDocumentService.get_corpus_documents_visible_to_user` (the `MIN(corpus, document)` semantic) instead of the corpus-as-gate loader. A user who can read a corpus but lacks READ on a private document inside it no longer has that document scanned by the LLM citation extractor / grammar tier, surfaced through `discover`'s `review_candidates` or `scan`'s verbatim `raw_text` samples, or — most importantly — persisted by `apply` into `Annotation` / `CorpusReference` / `DocumentRelationship` rows (the durable form of the leak). Regression tests for all three surfaces (plus positive controls proving the corpus owner is unaffected) in `opencontractserver/tests/test_enrichment_llm_integration.py`.
- The exclusion is no longer silent: `scan` / `discover` / `apply` each report a `documents_excluded_by_visibility` count and emit a `logger.WARNING` when the caller's `creator_id` (the agent tools pass the conversation user, not necessarily the corpus owner) lacks per-document READ on some documents — so `apply` marking the `Analysis` `COMPLETED` over a truncated document set is auditable rather than a permanent, invisible gap in coverage (`EnrichmentService._document_visibility_audit`). `scan` and `apply` now also return `documents_total_in_corpus` alongside the exclusion count (mirroring `discover`), and `excluded` is clamped to `max(0, total_in_corpus - len(documents))` so a document removed from the corpus between the two unsynchronized queries backing the audit can't surface a nonsensical negative exclusion count. `apply`'s internal `documents_total` variable is renamed to `documents_visible_to_caller` (it always held the MIN-filtered count, not the corpus total) so its progress log can't be misread as covering the whole corpus. The WARNING path is now pinned by `assertLogs` in `opencontractserver/tests/test_enrichment_llm_integration.py::test_apply_uses_document_visibility_for_shared_corpus`, and the negative-clamp guard has a dedicated regression test (`test_document_visibility_audit_clamps_negative_exclusion`).
