Company Knowledge and Documents
Company knowledge is shared across sessions in company_profiles. Users can
add notes or extract text from PDF, DOCX, PPTX, TXT, Markdown, and CSV files.
The browser performs text extraction; Gemini synthesizes structured facts.
Retaining the original file in private Cloudflare R2 is optional and
company-controlled.
Data Model
company_profiles contains facts, learnings, legacy aggregate readiness,
authoritative maturity state, version, and session/extraction metadata.
company_profile_history records versioned provenance.
company_documents records filename/type/counts, uploader, source/session,
truncation, synthesis state, and optional storage status/key/size.
Derived profile fields are database-guarded. Manual edits must use
update-company-facts; synthesis uses synthesise-company-info.
Browser Extraction
frontend/src/utils/documentText.ts:
| Kind | Parser |
|---|---|
dynamically loaded pdfjs-dist worker | |
| DOCX | dynamically loaded mammoth |
| PPTX | dynamically loaded JSZip plus OOXML text/notes parsing |
| text | browser File.text() |
Limits:
- 15 MiB input file
- 80,000 extracted characters
- 1,000 PDF pages
- 5,000 ZIP entries
- 2,000 PPTX slides
Text is cleaned and marked truncated when capped. Scanned PDFs without a text
layer return an empty-text error; OCR is not implemented.
Dynamic imports keep parser weight out of the initial bundle.
Synthesis Flow
file/notes
└─ browser extraction and bounds
└─ POST synthesise-company-info
├─ verify user
├─ verify admin/consultant company access
├─ rate and budget checks
├─ Gemini structured extraction
├─ merge cleaned facts
├─ increment profile version/extraction metadata
├─ append profile history
└─ record token usage
The model may return known fields plus a custom object. Empty/null values are
discarded. Model output is parsed/validated rather than written wholesale.
Knowledge Administration
Company Admin → Knowledge contains:
- overview of facts/learnings
- add-note and document upload
- profile history
- optional original-file retention toggle
- document history/download/delete
Only company admins/consultants/system admins edit knowledge. Members may read company-scoped information according to RLS.
update-company-facts verifies profile ownership and admin-equivalent access,
writes through the service role, and appends admin_edit history. MCP writes
use change_source='mcp'.
Optional R2 Retention
Retention requires:
companies.store_documents = true
R2_ACCOUNT_ID
R2_ACCESS_KEY_ID
R2_SECRET_ACCESS_KEY
R2_BUCKET
Browser extraction/synthesis does not require retention.
Upload protocol
- Create a pending
company_documentsrow. - Request
documents:upload_url. - Function verifies membership, uploader/admin, company opt-in, file metadata, count/byte quota, and safe deterministic key.
- Browser PUTs bytes directly to R2.
- Browser calls
confirm_upload. - Function HEADs the object and calls
commit_document_storage. - RPC atomically enforces real-size and aggregate quotas and marks stored.
The client-declared size is not trusted.
Storage controls
- private bucket
- five-minute signed URLs
company/<company-id>/...key prefix- safe key validation/path traversal rejection
- kinds
pdf,docx,pptx,text - constrained MIME types
- 15 MiB object cap
- 500 retained documents/company
- 2 GiB/company
- advisory locking around quota-sensitive operations
Download requires active membership. Delete requires admin/consultant and removes the object before/with metadata cleanup.
Privacy
Documents and extracted text can contain confidential strategy and personal data:
- do not log extracted text,
- Sentry replay masks all text and blocks media,
- send only bounded needed text to Gemini,
- use private R2 and least-privilege credentials,
- include document/profile data in export/deletion analysis,
- do not place object keys or signed URLs in analytics.
Failure Modes
| Failure | Expected handling |
|---|---|
| Unsupported/oversized file | Reject before parsing |
| Empty/scanned PDF | Explain no readable text/OCR |
| Parser exception | Keep profile unchanged |
| Gemini malformed result | Controlled failure; no partial unsafe merge |
| R2 unconfigured/opt-out | Synthesis succeeds; original not retained |
| PUT fails | Row remains pending/failed and can be cleaned |
| Object absent at confirm | Mark failed |
| Real object exceeds quota | Delete orphan best-effort; mark failed |
| Cross-tenant document ID | 403/404; no signed URL |
| Stale pending row | UI treats old pending as failed/retryable |
Extension Guidance
For a new file type:
- add MIME/extension detection and lazy parser,
- bound decompression/pages/entries/CPU,
- normalize text and preserve truncation metadata,
- add Edge Function kind/MIME allowlist,
- test adversarial/corrupt files,
- update CSP only if a new browser worker/origin is required.
For OCR, treat it as a new provider/data-processing boundary with explicit consent, budget, retention, privacy and accounting.
See Database Schema, Edge Functions, and Security and Privacy.