Skip to main content

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:

KindParser
PDFdynamically loaded pdfjs-dist worker
DOCXdynamically loaded mammoth
PPTXdynamically loaded JSZip plus OOXML text/notes parsing
textbrowser 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

  1. Create a pending company_documents row.
  2. Request documents:upload_url.
  3. Function verifies membership, uploader/admin, company opt-in, file metadata, count/byte quota, and safe deterministic key.
  4. Browser PUTs bytes directly to R2.
  5. Browser calls confirm_upload.
  6. Function HEADs the object and calls commit_document_storage.
  7. 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

FailureExpected handling
Unsupported/oversized fileReject before parsing
Empty/scanned PDFExplain no readable text/OCR
Parser exceptionKeep profile unchanged
Gemini malformed resultControlled failure; no partial unsafe merge
R2 unconfigured/opt-outSynthesis succeeds; original not retained
PUT failsRow remains pending/failed and can be cleaned
Object absent at confirmMark failed
Real object exceeds quotaDelete orphan best-effort; mark failed
Cross-tenant document ID403/404; no signed URL
Stale pending rowUI treats old pending as failed/retryable

Extension Guidance

For a new file type:

  1. add MIME/extension detection and lazy parser,
  2. bound decompression/pages/entries/CPU,
  3. normalize text and preserve truncation metadata,
  4. add Edge Function kind/MIME allowlist,
  5. test adversarial/corrupt files,
  6. 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.