Set up document management for Stirling Engine (#6476)

# Description of Changes
Change Stirling Engine to support deleting documents automatically. This
happens both on user logout and after an amount of time specified by the
Java when ingesting a document (allowing for personal documents to have
short lifetimes but org documents to be left in the db with no expiry
date). Also sets up an [ACL
policy](https://en.wikipedia.org/wiki/Access-control_list) for the
documents so the database knows which users have access to which
documents. This is not fully implemented in the Java, so currently all
docs are treated as having a single owner, the uploader, but
theoretically when we need to support org storage, we shouldn't need to
change the db schema.
This commit is contained in:
James Brunton
2026-06-03 11:52:11 +00:00
committed by GitHub
parent 71633861d0
commit 1264f4cfed
48 changed files with 2039 additions and 411 deletions
@@ -42,6 +42,7 @@ from .documents import (
Page,
PageRange,
PageText,
PurgeOwnerResponse,
)
from .execution import (
AgentExecutionRequest,
@@ -137,6 +138,7 @@ __all__ = [
"ContradictionSeverity",
"ConversationMessage",
"DeleteDocumentResponse",
"PurgeOwnerResponse",
"PdfToMarkdownCannotDoResponse",
"PdfToMarkdownOrchestrateResponse",
"PdfToMarkdownRequest",
+19 -3
View File
@@ -1,8 +1,10 @@
from __future__ import annotations
from datetime import datetime
from pydantic import Field
from stirling.models import ApiModel
from stirling.models import ApiModel, OwnerId, PrincipalId
from .common import FileId
@@ -35,20 +37,27 @@ class PageRange(ApiModel):
class IngestDocumentRequest(ApiModel):
"""Replace-ingest a document's content under the given ``document_id``.
"""Replace-ingest a document's content under ``(document_id, owner_id)``.
Each call wipes any previously-stored content for the document and writes
Each call wipes any previously-stored content for the pair and writes
both the vector-chunk and ordered-page representations from the supplied
pages.
``source`` is a human-readable label (typically the original filename)
that flows into chunk metadata so search results are readable when
``document_id`` is a hash.
``owner_id`` and ``read_principals`` are required: the engine never
defaults them. Callers must declare ownership and access explicitly.
"""
document_id: FileId = Field(min_length=1)
source: str = Field(min_length=1)
page_text: list[PageText] | None = None
owner_id: OwnerId = Field(min_length=1)
read_principals: list[PrincipalId] = Field(min_length=1)
# When to delete this doc. ``None`` means "persistent" (keep until an explicit delete)
expires_at: datetime | None
class IngestDocumentResponse(ApiModel):
@@ -59,3 +68,10 @@ class IngestDocumentResponse(ApiModel):
class DeleteDocumentResponse(ApiModel):
document_id: FileId
deleted: bool
class PurgeOwnerResponse(ApiModel):
"""Returned by ``DELETE /api/v1/documents/by-owner``."""
owner_id: OwnerId
deleted: int = Field(ge=0)