From a43614dad6e415755c66752d308a7ab8265373c4 Mon Sep 17 00:00:00 2001 From: kl3inIT Date: Mon, 10 Aug 2026 09:17:23 +0700 Subject: [PATCH 01/10] docs(assistant): design governed file evidence --- .../challenge-brief.md | 61 +++++ .../design.md | 226 ++++++++++++++++++ .../plan.md | 68 ++++++ .../reference-study.md | 104 ++++++++ docs/roadmap.md | 8 +- 5 files changed, 460 insertions(+), 7 deletions(-) create mode 100644 docs/increments/active/2026-08-10-assistant-file-evidence/challenge-brief.md create mode 100644 docs/increments/active/2026-08-10-assistant-file-evidence/design.md create mode 100644 docs/increments/active/2026-08-10-assistant-file-evidence/plan.md create mode 100644 docs/increments/active/2026-08-10-assistant-file-evidence/reference-study.md diff --git a/docs/increments/active/2026-08-10-assistant-file-evidence/challenge-brief.md b/docs/increments/active/2026-08-10-assistant-file-evidence/challenge-brief.md new file mode 100644 index 000000000..78aef1697 --- /dev/null +++ b/docs/increments/active/2026-08-10-assistant-file-evidence/challenge-brief.md @@ -0,0 +1,61 @@ +# Architecture Challenge Brief: Assistant File Evidence + +## Reviewer role + +Act as an adversarial read-only OrgMemory architect. Your job is to find the +strongest concrete reason to reject the proposal below, not to polish it. + +Read `AGENTS.md`, `ARCHITECTURE.md`, `docs/conventions.md`, +`docs/guidelines/agent-safety.md`, the `assistant-and-mcp` and +`knowledge-ingestion` domain spec/test pairs, this increment's `design.md` and +`reference-study.md`, and the current implementation paths named there. Do not +edit files, commit, push, or use Northstar. + +## Commit under review + +`755aab1a9a86888fc547a7bf684421f4bb1a645b` + +The branch is intentionally stacked on the completed typed-block and +`structured-block-v1` work of the Knowledge ingestion coverage increment. + +## Proposed decision + +1. Extract the concrete Tika/Spring AI parser from `apps:worker` into + `integrations:document-parsing-spring-ai`; keep canonical contracts in + `components:graph-rag-core`, channel admission in Knowledge, and processing, + chunking, embedding, retry, and publication orchestration in worker. +2. Deliver Assistant composer upload only as governed durable evidence. It calls + `SourceUploadService`, requires a Knowledge Space/classification, waits for + the ordinary worker pipeline, persists a conversation binding to immutable + Source/revision identity, rechecks authorization/readiness at turn claim, and + retrieves only through the existing permission-scoped citable path. +3. Reject a transient turn-local file lane for the first delivery. + +## Mandatory attack surface + +- Is the new parser module a justified replaceable integration or premature + modularization without a second direct parser caller? +- Can format capability remain separate from channel admission without recreating + the current five-gate drift? +- Does a conversation binding to Source/revision identity create stale-reference, + retirement, deletion, or revision-currentness contradictions? +- Is requiring Knowledge Space/classification too much friction for an Assistant + attachment, and would async ingestion make the feature noncompetitive? +- Does constrained retrieval over selected sources actually preserve all current + ACL, classification, publication, and citation checks? +- Is a dual-lane design safer or clearer if transient files can never become + governed evidence implicitly? +- What exact invariant or test would catch possession-of-UUID authorization, + revocation between upload and send, retry drift, and cross-tenant replay? + +## Required response + +Return a compact report with: + +1. verdict: `ACCEPT`, `REVISE`, or `REJECT`; +2. strongest counterargument; +3. repository evidence with paths/symbols; +4. specific failure scenario; +5. required changes, if any; and +6. the rejected alternative after your verdict. + diff --git a/docs/increments/active/2026-08-10-assistant-file-evidence/design.md b/docs/increments/active/2026-08-10-assistant-file-evidence/design.md new file mode 100644 index 000000000..f4a1d6f5d --- /dev/null +++ b/docs/increments/active/2026-08-10-assistant-file-evidence/design.md @@ -0,0 +1,226 @@ +# Reusable Document Parsing and Assistant File Evidence + +## Intent + +Design two related capabilities without collapsing their boundaries: + +1. a reusable, channel-neutral document parsing adapter that produces OrgMemory's + canonical typed-block IR; and +2. a governed Assistant composer upload that can become citable evidence without + bypassing Source Ledger, processing identity, or authorization. + +The first Assistant delivery is deliberately durable governed evidence. It does +not introduce an ephemeral “send these bytes directly to the model for this turn” +path. + +## Current facts + +- `components:graph-rag-core` already owns channel-neutral `DocumentParser`, + `DocumentParseRequest`, `DocumentParseResult`, `CanonicalDocument`, and typed + `DocumentBlock` contracts. +- The concrete `SpringAiDocumentParser` is package-private in `apps:worker` and + mixes extraction with Knowledge-specific admission and rejection exceptions. +- `DocumentProcessingEngine` is correctly worker-owned: it resolves the immutable + processing profile, chunks, embeds, and coordinates durable publication. +- `structured-block-v1` is the production processing policy on the stacked + ingestion branch. It dispatches by canonical block kind and pins requested and + resolved processing identity for deterministic retry. +- `SourceUploadService` already validates the actor, Knowledge Space, + classification, declared access, immutable object key, and upload limits before + registering an asynchronous Source revision. +- Assistant currently accepts text only. Its answers and persisted citations use + permission-scoped Knowledge retrieval and re-hydrate canonical Source evidence. + +## Architecture decision 1: reusable parsing boundary + +### Ownership + +Keep the deterministic contracts and canonical IR in +`components:graph-rag-core`. Extract the concrete Tika/Spring AI implementation +from `apps:worker` into a new replaceable integration: + +`integrations:document-parsing-spring-ai` + +This module is justified by both reuse and an external framework boundary. It +owns: + +- PDF page reading; +- Tika XHTML extraction for structured office and HTML documents; +- the dedicated CSV reader; +- HTML boilerplate and active-content removal; +- block-kind-aware normalization; +- format detection and parser capability descriptors; and +- the parser component identity/version. + +It does not own: + +- which product channel admits a format; +- upload size or per-format resource policy; +- Source, revision, organization, Space, classification, or actor identity; +- chunker selection, embedding, indexing, publication, or retry orchestration; +- HTTP/UI error copy; or +- Spring component discovery for a particular application. + +The implementation is a public adapter constructed by worker configuration. +Framework exceptions are translated into a small channel-neutral parse failure +taxonomy at the `DocumentParser` boundary. The worker maps those failures to its +durable ingestion terminal reasons. + +### Capability is not admission + +Parser capability and product admission are intentionally separate: + +```text +parser can extract format + ∩ +channel admits format + ∩ +resource policy accepts this file + = +accepted upload +``` + +The parser may know how to extract a format that Knowledge or Assistant has not +yet opened. Knowledge remains the authority for the accepted upload set. The API +publishes that channel capability to the browser; the web `accept` list is a UX +hint, never an authorization rule. Contract tests prove every admitted format has +a parser and every advertised format is admitted server-side. + +### Why `DocumentProcessingEngine` stays in worker + +Parsing is reusable; ingestion orchestration is not a parsing concern. Assistant +governed uploads call the existing Source use case and are processed by the same +worker. Moving `DocumentProcessingEngine` into a shared module would duplicate +Source lifecycle semantics in a nominally generic layer and encourage request- +thread processing. + +## Architecture decision 2: governed Assistant evidence + +### One upload, one Source identity + +Composer upload invokes an Assistant application use case which delegates byte +registration to `SourceUploadService`. The user must choose a Knowledge Space; +classification defaults and validation remain exactly those of ordinary Source +upload. The operation creates the same immutable Source object, revision, blob, +and ingestion job as the Documents workspace. + +Assistant adds only a binding, not another copy of the file: + +```text +Assistant conversation + -> Assistant evidence binding + -> immutable Source object + requested revision + -> Source blob / ingestion job + -> READY Knowledge Asset / chunks +``` + +The binding is conversation-scoped and records creator identity and creation +time for audit. It never grants read access. A submitted binding is usable only +when all of these are true at turn claim time: + +1. the binding belongs to the current organization and conversation; +2. the authenticated actor may use that conversation and currently read the + target Source under the canonical Knowledge policy; +3. the bound revision is current, `READY`, and its evidence integrity checks + pass; and +4. the resulting chunks survive the existing authorization filter and citation + hydration path. + +Return 404 across ownership/tenant boundaries and a typed not-ready state for a +valid binding still processing. Never turn possession of an attachment UUID into +authority. + +### Turn semantics + +- The client uploads at most three files per turn and polls their Source-derived + preparation state. +- Text may be sent without waiting, but a selected file is never silently + omitted. A turn carrying a selected binding is enabled only when it is ready; + terminal processing failure is shown before submit. +- Turn claim atomically persists the ordered binding IDs beside the user message + and rechecks them. A retry reuses the same ordered, immutable selection. +- Retrieval is constrained to the selected Source revisions and still applies + the ordinary actor scope, ACL ceiling, ranking bounds, and citation hydration. + Raw extracted text is not concatenated directly into the prompt. +- The answer emits ordinary OrgMemory citations. Transcript replay uses existing + citation behavior and fails closed if evidence later becomes unavailable. +- Uploaded content remains untrusted evidence. Existing prompt/data boundaries + apply on every turn in which it can be retrieved. + +### Lifecycle + +The file is durable Knowledge evidence from creation; there is no automatic +promotion step and no hidden personal-file store. Removing it from the composer +only removes the pending binding selection. Source retirement/deletion follows +the normal governed lifecycle and must account for persisted conversation +references in the same way citations already do. + +The MVP accepts only formats admitted by Knowledge ingestion. Images, direct +provider file handles, OCR, archive recursion, MSG/EML attachment recursion, and +model-generated files are out of scope. + +## Rejected first-delivery alternative: dual lane + +A second lane could create an `AssistantTransientAttachment` scoped to +organization, actor, conversation, and turn, with TTL deletion, bounded +synchronous parsing, no publication, and no canonical citation. Explicit +promotion would copy its bytes into a new Source identity and reprocess them. + +That is a legitimate later product, but it needs its own retention, malware/DLP, +egress, authorization, replay, deletion, tool-access, and disclosure model. It +would also tempt the API to reuse the parser on the request thread and create an +uncitable answer path. The first delivery rejects this complexity. + +## API and UI shape + +The exact OpenAPI names are implementation details, but the contracts are: + +- create governed evidence binding with multipart bytes, conversation ID, + Knowledge Space ID, and classification; +- list/get binding status with `UPLOADING`, `PROCESSING`, `READY`, `FAILED`, and + `UNAVAILABLE` presentation states derived from canonical Source state; +- submit an ordered list of at most three binding IDs with the Assistant turn; + and +- open the resulting evidence through the existing governed document/citation + viewer, not a new raw-file endpoint. + +The composer owns pending local upload progress and selected binding IDs. Server +state remains in TanStack Query. The generic prompt-input primitive exposes file +selection events but contains no Knowledge or permission policy. + +## Security invariants + +1. Attachment reference never grants access. +2. Tenant, actor, Space, and classification come from authenticated/server-owned + context and canonical Source commands, not trusted client ownership fields. +3. Authorization occurs before retrieval/ranking/context assembly and again + during citation hydration/open. +4. Unsupported, unready, retired, stale, or integrity-failing evidence fails + closed and is never silently excluded from a selected turn. +5. Parser detection cannot widen channel admission and embedded-resource + recursion remains disabled. +6. Heavy parsing, chunking, embedding, and graph publication remain off API + request threads. +7. No uploaded bytes or extracted text enter logs, traces, audit payloads, or + Northstar notes. + +## Upstream influence + +The pinned evidence is in [reference-study.md](reference-study.md). + +- From Northstar: immutable content hashing, visible async readiness, a small + per-turn limit, bounded context, and repeated untrusted-evidence fencing. +- From Onyx: separate transient and durable identities, use-time ownership + verification, explicit processing/deletion states, and context-budget-aware + selection between direct context, retrieval, and tools. +- Deliberately not inherited: Northstar's UUID-sufficient attachment access and + Onyx's user/project authorization ceiling. + +## Required challenge + +The independent reviewer must attack both decisions, with special focus on +whether governed-only uploads make the Assistant UX unacceptably slow and +whether extracting a parser integration before a second direct caller is +premature. The final verdict belongs in `challenge-verdict.md` before +implementation starts. + diff --git a/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md b/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md new file mode 100644 index 000000000..aec3fdc2a --- /dev/null +++ b/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md @@ -0,0 +1,68 @@ +# Reusable Document Parsing and Assistant File Evidence Plan + +## Status + +Design and architecture challenge only. No Assistant upload behavior is +implemented by this increment yet. + +## 1. Ground the decision + +- [x] Inspect the current parser contracts, worker implementation, Source upload + lifecycle, Assistant conversation/citation path, safety guideline, and domain + specs/tests. +- [x] Study pinned Northstar and Onyx source and record mechanisms worth learning + and authorization assumptions that OrgMemory must not inherit. +- [ ] Complete the independent architecture challenge and record the verdict, + strongest counterargument, evidence, final choice, and rejected alternative. + +## 2. Reusable parser adapter + +- [ ] Create `integrations:document-parsing-spring-ai` and move only the concrete + extraction adapter, readers, canonical block construction, capability + descriptors, and adapter tests out of `apps:worker`. +- [ ] Add a channel-neutral parse failure taxonomy; keep Knowledge rejection + mapping and ingestion terminal state in worker/core Source ownership. +- [ ] Make format capability queryable without making it an admission policy. +- [ ] Wire the worker through explicit configuration and prove unchanged parser + component identity/profile hashing for behavior-preserving moves; bump the + identity only for extraction behavior changes. +- [ ] Implement ingestion-coverage item 3 readers/admission against this boundary: + dedicated CSV, sanitized HTML, and the coordinated format gates. + +## 3. Governed Assistant evidence + +- [ ] Define the Assistant evidence-binding aggregate and Flyway migration with + organization/conversation/creator/source/revision identity, ordering, and + immutable turn association. +- [ ] Add a core use case that delegates bytes to `SourceUploadService`; do not + duplicate object storage, content-type, Space, classification, or permission + validation. +- [ ] Add status projection derived from Source processing state and a turn-claim + validator that rechecks organization, conversation, actor authorization, + current READY revision, and evidence integrity. +- [ ] Add retrieval constraints for the selected Source revisions while + preserving authorization-before-ranking and ordinary citation hydration. +- [ ] Extend OpenAPI and the composer with upload progress, Space/classification + choice, at-most-three selection, processing/failed states, retry-safe ordered + binding submission, and the existing document viewer. +- [ ] Keep images, direct provider uploads, transient attachments, OCR, archives, + MSG/EML recursion, and generated files closed. + +## 4. Verification and consolidation + +- [ ] Cover cross-organization ID probing, another conversation's binding, + revoked access between upload and send, unready/failed/retired revisions, + retry identity, selected-source retrieval, citation recheck, and prompt + injection fixtures. +- [ ] Cover parser capability/admission mismatches and prove no parsing or + embedding occurs on the API request thread. +- [ ] Reconcile `assistant-and-mcp` and `knowledge-ingestion` specs and test + matrices, refresh `Source:`/`Reconciled:`, add a durable decision, complete + static/full gates, and move this increment to completed. + +## Sequencing + +Finish Knowledge ingestion coverage item 3 and the reusable parser extraction +before wiring the composer. Assistant may depend on the resulting Source +pipeline; ingestion must never depend on Assistant. + diff --git a/docs/increments/active/2026-08-10-assistant-file-evidence/reference-study.md b/docs/increments/active/2026-08-10-assistant-file-evidence/reference-study.md new file mode 100644 index 000000000..94c1f9758 --- /dev/null +++ b/docs/increments/active/2026-08-10-assistant-file-evidence/reference-study.md @@ -0,0 +1,104 @@ +# Assistant File Evidence Reference Study + +## Pins + +The study uses local, clean, immutable reference checkouts rather than product +documentation or recollection. + +| Reference | Checkout | Commit | +| --- | --- | --- | +| Northstar | `D:\northstar` | `caef9a9a55e60e8bc99b47275b4840d6cd940372` | +| Onyx | `D:\OrgMemory\tmp\onyx` | `618b5031bf21463f44e3bed9eb9d5073b806fec0` | + +## Northstar + +### Mechanism + +- `AttachmentService` stores immutable bytes, deduplicates by SHA-256, limits an + upload to 25 MB, strips path segments from filenames, and publishes a stored + event. `Attachment` has no organization, user, project, or conversation owner. +- `AttachmentController` admits a bounded type set after byte inspection, exposes + an index-status poll, allows at most three IDs in that poll, serves only + magic-byte-verified raster images inline, and forces other content through a + sandboxed download response. +- `SearchIndexingWorker` keeps Tika, embedding, and vision work off request + threads. `SearchService.indexAttachment` keys idempotency on immutable file + SHA-256 plus an index version, records processing status, and atomically replaces + indexed chunks. +- `AssistantController.ChatRequest` carries at most three attachment IDs. A turn + refuses document context until every requested document is ready, bounds the + selected context, emits source parts, and inserts excerpts under an + `UNTRUSTED_ATTACHMENT_EVIDENCE_JSON` marker. The system-level trust policy is + repeated on every turn because conversation memory can replay old attachment + evidence. + +### What OrgMemory should learn + +1. Preparation is asynchronous and visible; Tika and embedding do not belong on + the interactive request thread. +2. Immutable bytes plus content hash and processing-version identity make retries + cheap and deterministic. +3. A small attachment-count limit, explicit terminal states, bounded context, and + prompt-injection fencing are useful UX and safety contracts. +4. Serving is a separate security boundary from parsing: authenticated access, + `nosniff`, sandboxing, safe disposition, and immutable private caching all + matter. + +### What OrgMemory must not copy + +An attachment ID is effectively sufficient authority inside Northstar's local +model. There is no tenant or caller ownership on the entity and no governed +publication/classification ceiling. OrgMemory cannot make a stored file usable by +Assistant merely because a client submits its UUID. + +## Onyx + +### Mechanism + +Onyx separates two identities: + +- a chat file descriptor points at content-addressed file-store bytes and is + carried by chat history; +- a durable `UserFile` is owned by a user, has a processing/indexing lifecycle, + and may be associated with a `UserProject` or persona. + +`upload_user_files` checks project ownership before creating user files and +queues per-file background processing. Statuses include `PROCESSING`, `INDEXING`, +`COMPLETED`, `SKIPPED`, `FAILED`, `CANCELED`, and `DELETING`. Deletion refuses to +proceed silently while project or assistant associations remain. + +At turn time, `verify_user_files` checks user ownership or project membership of +every submitted descriptor. `resolve_context_user_files` makes project-versus- +persona precedence explicit. `extract_context_files` injects text only when the +whole set fits a bounded fraction of the model context; otherwise it uses vector +search or metadata plus a file-reader tool. Tabular data is metadata-only. Chat +files and durable user files remain separate allowlists for tools. File download +returns 404 across ownership boundaries. + +### What OrgMemory should learn + +1. Transient chat files and durable reusable files are different product + identities, not two flags on the same row. +2. File availability to a model/tool is assembled from an explicit allowlist and + verified again at use time. +3. Processing state, project/persona association, deletion state, context-budget + decisions, and file-type-specific treatment are explicit contracts. +4. Large or structured files should not be flattened blindly into the prompt; + retrieval and file tools are distinct fallback mechanisms. + +### What OrgMemory must not copy + +Onyx's primary durable boundary is user/project ownership. OrgMemory must also +intersect organization, Knowledge Space audience, classification, source ACL, +publication/readiness, and current evidence integrity. A `UserFile`-style private +library would introduce a second authorization universe unless it is designed as +a governed Knowledge Space. + +## Consequence for OrgMemory + +Use Northstar's asynchronous, bounded, hash-idempotent preparation discipline and +Onyx's separation of identities and use-time verification. Do not copy either +authorization model. The first OrgMemory composer upload should create governed +Knowledge evidence through the existing Source Ledger. A separate transient-turn +lane remains a future architecture decision, not an implementation shortcut. + diff --git a/docs/roadmap.md b/docs/roadmap.md index c63a768ca..f3e77bdbd 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -54,6 +54,7 @@ The table is a delivery index, not a second description of current behavior. | Increment | Status | Remaining gate | | --- | --- | --- | +| [Reusable document parsing and Assistant file evidence](increments/active/2026-08-10-assistant-file-evidence/plan.md) | design challenge | challenge the reusable parser boundary and governed-only composer upload before implementation | | [Knowledge ingestion coverage](increments/active/2026-08-07-knowledge-ingestion-coverage/plan.md) | active | typed block IR so a table survives parsing, content-type strategy resolution, then the organization formats an upload is refused today | | [Assistant turn activity continuity](increments/completed/2026-08-06-assistant-turn-activity-continuity/verification.md) | shipped | one stable pre-answer surface now preserves thinking through renderable text and presents Skill detail only when useful; the unrelated browser timing baseline gap is recorded | | [Assistant conversation memory SSOT](increments/completed/2026-08-06-assistant-conversation-memory-ssot/verification.md) | shipped | collapsed the conversation to one owned store read back through a project-owned read-only turn advisor, and gave a failed turn a sentence naming its cause | @@ -116,13 +117,6 @@ implementation-active until their predecessor exit gates pass. ## Engineering Backlog -- Decide whether a file attached at the Assistant composer may become evidence. - The composer has no attachment path at all today: `prompt-input.tsx` submits - `files: []` and `AssistantPage` forwards only `message.text`. Copying - Northstar's per-turn attachment flow would bypass the permission model that - makes an OrgMemory answer citable, so the question is a permission boundary — - who owns the file, who may see it, and whether it is turn-local or durable - evidence — and needs its own architecture challenge, not a UI change. - Widen Knowledge Base ingestion coverage. `KnowledgeContentType` allows upload of PDF, DOCX, PPTX, MD and TXT only; `SpringAiDocumentParser.ALLOWED_MEDIA_TYPES` matches. Spreadsheets (XLSX/XLS/CSV), HTML, JSON/XML and legacy Office are From 558b7d415f08feb5399cbdc25261385a11304fa3 Mon Sep 17 00:00:00 2001 From: kl3inIT Date: Mon, 10 Aug 2026 09:36:35 +0700 Subject: [PATCH 02/10] docs(assistant): resolve file evidence challenge --- .../challenge-brief.md | 6 +- .../challenge-verdict.md | 101 ++++++++++++++++++ .../design.md | 74 +++++++++---- .../plan.md | 36 +++++-- docs/roadmap.md | 2 +- 5 files changed, 187 insertions(+), 32 deletions(-) create mode 100644 docs/increments/active/2026-08-10-assistant-file-evidence/challenge-verdict.md diff --git a/docs/increments/active/2026-08-10-assistant-file-evidence/challenge-brief.md b/docs/increments/active/2026-08-10-assistant-file-evidence/challenge-brief.md index 78aef1697..4c9677c9e 100644 --- a/docs/increments/active/2026-08-10-assistant-file-evidence/challenge-brief.md +++ b/docs/increments/active/2026-08-10-assistant-file-evidence/challenge-brief.md @@ -13,7 +13,10 @@ edit files, commit, push, or use Northstar. ## Commit under review -`755aab1a9a86888fc547a7bf684421f4bb1a645b` +Reviewer checkout: `a43614dad6e415755c66752d308a7ab8265373c4`. + +The code baseline beneath the design is +`755aab1a9a86888fc547a7bf684421f4bb1a645b`. The branch is intentionally stacked on the completed typed-block and `structured-block-v1` work of the Knowledge ingestion coverage increment. @@ -58,4 +61,3 @@ Return a compact report with: 4. specific failure scenario; 5. required changes, if any; and 6. the rejected alternative after your verdict. - diff --git a/docs/increments/active/2026-08-10-assistant-file-evidence/challenge-verdict.md b/docs/increments/active/2026-08-10-assistant-file-evidence/challenge-verdict.md new file mode 100644 index 000000000..0b9e28465 --- /dev/null +++ b/docs/increments/active/2026-08-10-assistant-file-evidence/challenge-verdict.md @@ -0,0 +1,101 @@ +# Architecture Challenge Verdict: Assistant File Evidence + +## Execution record + +- Reviewer: Claude Fable 5, independent read-only Orca session +- Orca worktree: + `f5c5bcc9-78f9-4956-9b62-91d4021ee102::C:/Users/admin/orca/workspaces/assistant-file-evidence-architecture/assistant-file-evidence-review` +- Terminal: `term_8c754642-8c07-4420-b0d8-f6d79c37b5ea` +- Reviewed commit: `a43614dad6e415755c66752d308a7ab8265373c4` +- Code baseline: `755aab1a9a86888fc547a7bf684421f4bb1a645b` +- Reviewer made no repository edits. + +## Verdict + +**REVISE.** All three proposed directions survive attack: + +1. extract `integrations:document-parsing-spring-ai`; +2. deliver governed-only Assistant file evidence; and +3. reject the transient turn-local lane for the first delivery. + +Implementation must not begin until the design closes two load-bearing gaps: +Source `READY` is not active-engine answerability, and current retrieval has no +selected Source/Asset ceiling. + +## Strongest counterargument + +The proposed status enabled Send when the Source revision became `READY`. Under +the default GraphRAG runtime, that revision may still be absent from the winning +projection batch because graph indexing is separately queued, LLM-bearing, +retryable, and independently failable. A selection-constrained query in that +window returns zero evidence and the existing Assistant correctly emits its +model-free not-found answer. The UI would call the file ready while the runtime +could not answer from it, silently violating the design's own no-omission rule. + +The second gap is structural: `PermissionAwareKnowledgeSearch.search` accepts +actor, query, limit, and request identity, but not a selected Source/Asset set. +The design cannot claim attachment-constrained retrieval without defining how +that ceiling enters seed search, graph expansion, closure verification, budgets, +and citation hydration. + +## Repository evidence + +- `ARCHITECTURE.md` states that the graph-index job is enqueued in the same + transaction that makes a revision ready, that readers validate the winning + batch before scoring/traversal, that `SECURE_MIX` is the default internal plan, + and that one budget spans the authorized Knowledge Spaces. +- `docs/specs/domains/assistant-and-mcp.md` makes GraphRAG the configured default + engine and requires empty authorized evidence to remain model-free and + citation-free. +- `PermissionAwareKnowledgeSearch` and the reconciled public contract contain no + per-request selected Source/Asset allowlist. +- `SpringAiDocumentParser` is package-private under worker, owns a format + allowlist, and throws worker-specific rejection exceptions. The parsing + contracts in `components:graph-rag-core` remain framework-free. +- No second production parser caller exists. The honest module justification is + the replaceable framework boundary and imminent CSV/HTML reader family, not a + current Assistant caller. +- `SourceIngestionProcessor` sends an unclassified `DocumentParsingException` + through the generic retryable `PARSING_FAILED` path. Deterministically corrupt + input can therefore consume all five attempts. +- Parser identity is the stable + `spring-ai-document-reader@2.1.0`; a package move can preserve pinned profile + hashes, but unavailable pinned identity is a permanent processing failure. + +## Failure scenario + +An employee uploads a 40-page PDF. Source parsing finishes and the revision +becomes `READY`, so the initial design enables Send. Its graph-index job is still +queued or spending minutes in extraction. The user asks to summarize the file. +The winning batch does not contain the new Asset, retrieval produces no evidence, +and Assistant returns the correct bounded not-found response. Retrying repeats +the result; the attachment appears broken without ever presenting an error. + +## Required revisions accepted by the owner design + +1. Define presentation readiness as answerability under the configured engine. + For GraphRAG, require the selected Asset's projection batch to be published. +2. Add an internal `KnowledgeEvidenceSelection` contract. The first delivery is + selection-only; the selected set is a second ceiling after actor + authorization and is reapplied through graph closure and citations. The + normal global budget applies to that selected subset. +3. Require usable evidence from every selected binding before generation rather + than letting another file hide a missing selected file. +4. Disclose that composer upload publishes to the selected Space audience. An + actor with no `can_create_asset` Space has no attachment path in this phase. +5. Permit binding creation only as the result of the governed multipart upload; + expose no bind-existing-Source-by-ID endpoint. +6. Preserve parser identity and byte-identical processing-profile hashes across + a behavior-preserving module move. +7. Make deterministic parse failures non-retryable and keep channel admission + outside the parser adapter. + +## Rejected alternative + +Do not add `AssistantTransientAttachment` in the first delivery. Although Onyx +proves the two-identity split can be legitimate, OrgMemory lacks the second +lane's retention, malware/DLP, egress, authorization, replay, deletion, and +disclosure model. It would invite request-thread parsing and an uncitable answer +path outside the existing citation-recheck discipline. The accepted residual +cost is explicit: all initial Assistant files are Space-visible governed +evidence, and users without Space upload permission cannot attach files. diff --git a/docs/increments/active/2026-08-10-assistant-file-evidence/design.md b/docs/increments/active/2026-08-10-assistant-file-evidence/design.md index f4a1d6f5d..2ba5e884c 100644 --- a/docs/increments/active/2026-08-10-assistant-file-evidence/design.md +++ b/docs/increments/active/2026-08-10-assistant-file-evidence/design.md @@ -41,8 +41,11 @@ from `apps:worker` into a new replaceable integration: `integrations:document-parsing-spring-ai` -This module is justified by both reuse and an external framework boundary. It -owns: +This module is justified by the replaceable framework boundary and the imminent +CSV/HTML reader family in Knowledge ingestion coverage item 3. Worker remains +the only direct production caller after Assistant ships, because Assistant uses +the Source pipeline rather than calling the parser. Future reuse is enabled but +is not the present-day justification. The module owns: - PDF page reading; - Tika XHTML extraction for structured office and HTML documents; @@ -63,8 +66,11 @@ It does not own: The implementation is a public adapter constructed by worker configuration. Framework exceptions are translated into a small channel-neutral parse failure -taxonomy at the `DocumentParser` boundary. The worker maps those failures to its -durable ingestion terminal reasons. +taxonomy at the `DocumentParser` boundary. It distinguishes deterministic +unsupported, corrupt, empty, and encoding failures from retryable infrastructure +failure. The worker maps those failures to its durable ingestion terminal +reasons; deterministic parse failure is non-retryable instead of falling through +to the current generic `PARSING_FAILED` retry path. ### Capability is not admission @@ -112,6 +118,7 @@ Assistant conversation -> immutable Source object + requested revision -> Source blob / ingestion job -> READY Knowledge Asset / chunks + -> active-engine projection published / answerable ``` The binding is conversation-scoped and records creator identity and creation @@ -121,13 +128,20 @@ when all of these are true at turn claim time: 1. the binding belongs to the current organization and conversation; 2. the authenticated actor may use that conversation and currently read the target Source under the canonical Knowledge policy; -3. the bound revision is current, `READY`, and its evidence integrity checks - pass; and +3. the bound revision is current, its evidence integrity checks pass, and it is + answerable under the active Assistant retrieval engine; and 4. the resulting chunks survive the existing authorization filter and citation hydration path. -Return 404 across ownership/tenant boundaries and a typed not-ready state for a -valid binding still processing. Never turn possession of an attachment UUID into +`SourceRevision.READY` alone is insufficient for the default GraphRAG engine. +The graph-index job is enqueued when the revision becomes ready, and the winning +projection batch may still be queued, processing, retrying, or failed. The +binding status therefore resolves through an engine-aligned readiness port. For +GraphRAG, presentation `READY` requires the selected Asset's projection batch to +be published and visible to the exact engine serving the turn. For a configured +canonical engine it requires the canonical chunks that engine reads. Return 404 +across ownership/tenant boundaries and a typed not-ready state for a valid +binding still processing. Never turn possession of an attachment UUID into authority. ### Turn semantics @@ -139,9 +153,22 @@ authority. terminal processing failure is shown before submit. - Turn claim atomically persists the ordered binding IDs beside the user message and rechecks them. A retry reuses the same ordered, immutable selection. -- Retrieval is constrained to the selected Source revisions and still applies - the ordinary actor scope, ACL ceiling, ranking bounds, and citation hydration. - Raw extracted text is not concatenated directly into the prompt. +- A turn with bindings is selection-only in the first delivery. It does not + silently mix the rest of organizational Knowledge into “summarize this file.” + A new internal `KnowledgeEvidenceSelection` argument carries the selected + Asset/Source identities into the configured `knowledge::search` engine. It is + not a public strategy selector. +- The selected identity set is a second ceiling after actor authorization: + intersect it before seed scoring, preserve it through graph expansion, and + reapply it during closure verification and citation hydration. Chunks or + citations from non-selected Assets cannot enter context. Entity/relationship + facts survive only when their accepted provenance belongs to the selected + set. The normal global context budget applies across the selected authorized + subset. +- An attachment turn must receive usable evidence from every selected binding or + return a typed evidence-unavailable result before generation; a selected file + is never treated as optional because another file produced hits. Raw extracted + text is not concatenated directly into the prompt. - The answer emits ordinary OrgMemory citations. Transcript replay uses existing citation behavior and fails closed if evidence later becomes unavailable. - Uploaded content remains untrusted evidence. Existing prompt/data boundaries @@ -150,10 +177,14 @@ authority. ### Lifecycle The file is durable Knowledge evidence from creation; there is no automatic -promotion step and no hidden personal-file store. Removing it from the composer -only removes the pending binding selection. Source retirement/deletion follows -the normal governed lifecycle and must account for persisted conversation -references in the same way citations already do. +promotion step and no hidden personal-file store. The composer explicitly says +that upload publishes the processed file to the chosen Space audience. An actor +with no Space where they hold `can_create_asset` cannot attach a file and is +directed to the Documents workflow or an administrator; the product does not +fall back to a private attachment store. Removing it from the composer only +removes the pending binding selection. Source retirement/deletion follows the +normal governed lifecycle and must account for persisted conversation references +in the same way citations already do. The MVP accepts only formats admitted by Knowledge ingestion. Images, direct provider file handles, OCR, archive recursion, MSG/EML attachment recursion, and @@ -176,9 +207,11 @@ uncitable answer path. The first delivery rejects this complexity. The exact OpenAPI names are implementation details, but the contracts are: - create governed evidence binding with multipart bytes, conversation ID, - Knowledge Space ID, and classification; -- list/get binding status with `UPLOADING`, `PROCESSING`, `READY`, `FAILED`, and - `UNAVAILABLE` presentation states derived from canonical Source state; + Knowledge Space ID, and classification; there is deliberately no + bind-an-existing-Source-by-ID endpoint; +- list/get binding status with `UPLOADING`, `PROCESSING`, `INDEXING`, `READY`, + `FAILED`, and `UNAVAILABLE` presentation states derived from Source state plus + active-engine projection readiness; - submit an ordered list of at most three binding IDs with the Assistant turn; and - open the resulting evidence through the existing governed document/citation @@ -203,6 +236,10 @@ selection events but contains no Knowledge or permission policy. request threads. 7. No uploaded bytes or extracted text enter logs, traces, audit payloads, or Northstar notes. +8. Moving the parser across modules without changing behavior preserves + `spring-ai-document-reader@2.1.0` and produces byte-identical requested and + resolved profile hashes; an identity change requires an intentional version + bump. ## Upstream influence @@ -223,4 +260,3 @@ whether governed-only uploads make the Assistant UX unacceptably slow and whether extracting a parser integration before a second direct caller is premature. The final verdict belongs in `challenge-verdict.md` before implementation starts. - diff --git a/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md b/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md index aec3fdc2a..6a132bc03 100644 --- a/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md +++ b/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md @@ -12,8 +12,11 @@ implemented by this increment yet. specs/tests. - [x] Study pinned Northstar and Onyx source and record mechanisms worth learning and authorization assumptions that OrgMemory must not inherit. -- [ ] Complete the independent architecture challenge and record the verdict, +- [x] Complete the independent architecture challenge and record the verdict, strongest counterargument, evidence, final choice, and rejected alternative. + Verdict: `REVISE`; the three proposed directions survive, with active-engine + answerability and selection-constrained retrieval added as must-fix design + gates. ## 2. Reusable parser adapter @@ -21,11 +24,14 @@ implemented by this increment yet. extraction adapter, readers, canonical block construction, capability descriptors, and adapter tests out of `apps:worker`. - [ ] Add a channel-neutral parse failure taxonomy; keep Knowledge rejection - mapping and ingestion terminal state in worker/core Source ownership. + mapping and ingestion terminal state in worker/core Source ownership. Prove a + deterministic corrupt file does not enter the generic retryable + `PARSING_FAILED` path. - [ ] Make format capability queryable without making it an admission policy. - [ ] Wire the worker through explicit configuration and prove unchanged parser - component identity/profile hashing for behavior-preserving moves; bump the - identity only for extraction behavior changes. + component identity and byte-identical requested/resolved profile hashing for a + behavior-preserving move; bump the identity only for extraction behavior + changes. - [ ] Implement ingestion-coverage item 3 readers/admission against this boundary: dedicated CSV, sanitized HTML, and the coordinated format gates. @@ -36,15 +42,23 @@ implemented by this increment yet. immutable turn association. - [ ] Add a core use case that delegates bytes to `SourceUploadService`; do not duplicate object storage, content-type, Space, classification, or permission - validation. + validation. Binding creation exists only inside this multipart use case; do + not add a bind-existing-Source-by-ID endpoint. - [ ] Add status projection derived from Source processing state and a turn-claim validator that rechecks organization, conversation, actor authorization, - current READY revision, and evidence integrity. -- [ ] Add retrieval constraints for the selected Source revisions while - preserving authorization-before-ranking and ordinary citation hydration. + current revision, evidence integrity, and answerability under the configured + engine. For GraphRAG, require the selected Asset's projection batch to be + published, not merely `SourceRevision.READY`. +- [ ] Add internal `KnowledgeEvidenceSelection` to the configured retrieval + engine. Make binding turns selection-only; intersect selected Asset/Source IDs + before scoring, retain the ceiling through graph expansion and closure + verification, reject non-selected citations, apply the global budget to the + selected subset, and require usable evidence from every selected binding. - [ ] Extend OpenAPI and the composer with upload progress, Space/classification choice, at-most-three selection, processing/failed states, retry-safe ordered - binding submission, and the existing document viewer. + binding submission, and the existing document viewer. Disclose that upload + publishes to the selected Space audience and disable attachment for actors + with no `can_create_asset` Space. - [ ] Keep images, direct provider uploads, transient attachments, OCR, archives, MSG/EML recursion, and generated files closed. @@ -54,6 +68,9 @@ implemented by this increment yet. revoked access between upload and send, unready/failed/retired revisions, retry identity, selected-source retrieval, citation recheck, and prompt injection fixtures. +- [ ] Cover the READY/projection gap, graph expansion into a non-selected Asset, + one selected file returning no usable evidence, no-Space UX, and the absence of + any bind-existing-Source endpoint. - [ ] Cover parser capability/admission mismatches and prove no parsing or embedding occurs on the API request thread. - [ ] Reconcile `assistant-and-mcp` and `knowledge-ingestion` specs and test @@ -65,4 +82,3 @@ implemented by this increment yet. Finish Knowledge ingestion coverage item 3 and the reusable parser extraction before wiring the composer. Assistant may depend on the resulting Source pipeline; ingestion must never depend on Assistant. - diff --git a/docs/roadmap.md b/docs/roadmap.md index f3e77bdbd..94af3d746 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -54,7 +54,7 @@ The table is a delivery index, not a second description of current behavior. | Increment | Status | Remaining gate | | --- | --- | --- | -| [Reusable document parsing and Assistant file evidence](increments/active/2026-08-10-assistant-file-evidence/plan.md) | design challenge | challenge the reusable parser boundary and governed-only composer upload before implementation | +| [Reusable document parsing and Assistant file evidence](increments/active/2026-08-10-assistant-file-evidence/plan.md) | designed | implement active-engine answerability, selection-constrained governed evidence, and the reusable parser adapter after the `REVISE` challenge | | [Knowledge ingestion coverage](increments/active/2026-08-07-knowledge-ingestion-coverage/plan.md) | active | typed block IR so a table survives parsing, content-type strategy resolution, then the organization formats an upload is refused today | | [Assistant turn activity continuity](increments/completed/2026-08-06-assistant-turn-activity-continuity/verification.md) | shipped | one stable pre-answer surface now preserves thinking through renderable text and presents Skill detail only when useful; the unrelated browser timing baseline gap is recorded | | [Assistant conversation memory SSOT](increments/completed/2026-08-06-assistant-conversation-memory-ssot/verification.md) | shipped | collapsed the conversation to one owned store read back through a project-owned read-only turn advisor, and gave a failed turn a sentence naming its cause | From e02508c276af184a62a4d78b7743aa43cbc524a6 Mon Sep 17 00:00:00 2001 From: kl3inIT Date: Mon, 10 Aug 2026 14:53:22 +0700 Subject: [PATCH 03/10] docs(assistant): scope file evidence after parser delivery --- .../design.md | 72 ++++++++----------- .../plan.md | 35 +++------ .../reference-study.md | 25 ++++++- 3 files changed, 65 insertions(+), 67 deletions(-) diff --git a/docs/increments/active/2026-08-10-assistant-file-evidence/design.md b/docs/increments/active/2026-08-10-assistant-file-evidence/design.md index 2ba5e884c..f321f17b7 100644 --- a/docs/increments/active/2026-08-10-assistant-file-evidence/design.md +++ b/docs/increments/active/2026-08-10-assistant-file-evidence/design.md @@ -1,13 +1,12 @@ -# Reusable Document Parsing and Assistant File Evidence +# Assistant Governed File Evidence ## Intent -Design two related capabilities without collapsing their boundaries: - -1. a reusable, channel-neutral document parsing adapter that produces OrgMemory's - canonical typed-block IR; and -2. a governed Assistant composer upload that can become citable evidence without - bypassing Source Ledger, processing identity, or authorization. +Deliver a governed Assistant composer upload that becomes citable evidence +without bypassing Source Ledger, processing identity, or authorization. It uses +the reusable parser boundary shipped by the preceding Knowledge ingestion +coverage increment; this increment does not add another parser caller or move +ingestion ownership into Assistant. The first Assistant delivery is deliberately durable governed evidence. It does not introduce an ephemeral “send these bytes directly to the model for this turn” @@ -15,37 +14,35 @@ path. ## Current facts -- `components:graph-rag-core` already owns channel-neutral `DocumentParser`, - `DocumentParseRequest`, `DocumentParseResult`, `CanonicalDocument`, and typed - `DocumentBlock` contracts. -- The concrete `SpringAiDocumentParser` is package-private in `apps:worker` and - mixes extraction with Knowledge-specific admission and rejection exceptions. +- `components:graph-rag-core` owns the channel-neutral parsing port, canonical + document IR, typed blocks, and parse-failure taxonomy. +- `integrations:document-parsing-spring-ai` owns the concrete public parser + adapter, dedicated CSV reader, sanitized XHTML extraction, format capability, + and parser identity. Worker remains its only production caller. - `DocumentProcessingEngine` is correctly worker-owned: it resolves the immutable processing profile, chunks, embeds, and coordinates durable publication. -- `structured-block-v1` is the production processing policy on the stacked - ingestion branch. It dispatches by canonical block kind and pins requested and - resolved processing identity for deterministic retry. +- `structured-block-v1` is the production processing policy. It dispatches by + canonical block kind and pins requested and resolved processing identity for + deterministic retry. - `SourceUploadService` already validates the actor, Knowledge Space, classification, declared access, immutable object key, and upload limits before registering an asynchronous Source revision. - Assistant currently accepts text only. Its answers and persisted citations use permission-scoped Knowledge retrieval and re-hydrate canonical Source evidence. -## Architecture decision 1: reusable parsing boundary +## Shipped dependency: reusable parsing boundary ### Ownership -Keep the deterministic contracts and canonical IR in -`components:graph-rag-core`. Extract the concrete Tika/Spring AI implementation -from `apps:worker` into a new replaceable integration: +The completed Knowledge ingestion coverage increment keeps deterministic +contracts and canonical IR in `components:graph-rag-core` and the concrete +Tika/Spring AI implementation in the replaceable integration: `integrations:document-parsing-spring-ai` -This module is justified by the replaceable framework boundary and the imminent -CSV/HTML reader family in Knowledge ingestion coverage item 3. Worker remains -the only direct production caller after Assistant ships, because Assistant uses -the Source pipeline rather than calling the parser. Future reuse is enabled but -is not the present-day justification. The module owns: +Worker remains the only direct production caller after Assistant ships, because +Assistant uses the Source pipeline rather than calling the parser. The module +owns: - PDF page reading; - Tika XHTML extraction for structured office and HTML documents; @@ -65,12 +62,9 @@ It does not own: - Spring component discovery for a particular application. The implementation is a public adapter constructed by worker configuration. -Framework exceptions are translated into a small channel-neutral parse failure -taxonomy at the `DocumentParser` boundary. It distinguishes deterministic -unsupported, corrupt, empty, and encoding failures from retryable infrastructure -failure. The worker maps those failures to its durable ingestion terminal -reasons; deterministic parse failure is non-retryable instead of falling through -to the current generic `PARSING_FAILED` retry path. +Framework exceptions are translated into the channel-neutral parse failure +taxonomy at the `DocumentParser` boundary. Knowledge admission and durable +failure mapping remain outside the adapter. ### Capability is not admission @@ -100,7 +94,7 @@ worker. Moving `DocumentProcessingEngine` into a shared module would duplicate Source lifecycle semantics in a nominally generic layer and encourage request- thread processing. -## Architecture decision 2: governed Assistant evidence +## Architecture decision: governed Assistant evidence ### One upload, one Source identity @@ -236,10 +230,6 @@ selection events but contains no Knowledge or permission policy. request threads. 7. No uploaded bytes or extracted text enter logs, traces, audit payloads, or Northstar notes. -8. Moving the parser across modules without changing behavior preserves - `spring-ai-document-reader@2.1.0` and produces byte-identical requested and - resolved profile hashes; an identity change requires an intentional version - bump. ## Upstream influence @@ -253,10 +243,10 @@ The pinned evidence is in [reference-study.md](reference-study.md). - Deliberately not inherited: Northstar's UUID-sufficient attachment access and Onyx's user/project authorization ceiling. -## Required challenge +## Architecture challenge result -The independent reviewer must attack both decisions, with special focus on -whether governed-only uploads make the Assistant UX unacceptably slow and -whether extracting a parser integration before a second direct caller is -premature. The final verdict belongs in `challenge-verdict.md` before -implementation starts. +The independent reviewer returned `REVISE`. The accepted design now gates READY +on active-engine answerability, carries a selected-evidence ceiling through +retrieval and citation hydration, and requires usable evidence from every +selected binding. The execution record and rejected transient-lane alternative +are in `challenge-verdict.md`. diff --git a/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md b/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md index 6a132bc03..0c84f48f2 100644 --- a/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md +++ b/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md @@ -1,9 +1,10 @@ -# Reusable Document Parsing and Assistant File Evidence Plan +# Assistant Governed File Evidence Plan ## Status -Design and architecture challenge only. No Assistant upload behavior is -implemented by this increment yet. +Active implementation. The reusable parser and coordinated Knowledge format +coverage shipped in the preceding Knowledge ingestion coverage increment. This +increment owns only governed Assistant file evidence. ## 1. Ground the decision @@ -18,24 +19,7 @@ implemented by this increment yet. answerability and selection-constrained retrieval added as must-fix design gates. -## 2. Reusable parser adapter - -- [ ] Create `integrations:document-parsing-spring-ai` and move only the concrete - extraction adapter, readers, canonical block construction, capability - descriptors, and adapter tests out of `apps:worker`. -- [ ] Add a channel-neutral parse failure taxonomy; keep Knowledge rejection - mapping and ingestion terminal state in worker/core Source ownership. Prove a - deterministic corrupt file does not enter the generic retryable - `PARSING_FAILED` path. -- [ ] Make format capability queryable without making it an admission policy. -- [ ] Wire the worker through explicit configuration and prove unchanged parser - component identity and byte-identical requested/resolved profile hashing for a - behavior-preserving move; bump the identity only for extraction behavior - changes. -- [ ] Implement ingestion-coverage item 3 readers/admission against this boundary: - dedicated CSV, sanitized HTML, and the coordinated format gates. - -## 3. Governed Assistant evidence +## 2. Governed Assistant evidence - [ ] Define the Assistant evidence-binding aggregate and Flyway migration with organization/conversation/creator/source/revision identity, ordering, and @@ -62,7 +46,7 @@ implemented by this increment yet. - [ ] Keep images, direct provider uploads, transient attachments, OCR, archives, MSG/EML recursion, and generated files closed. -## 4. Verification and consolidation +## 3. Verification and consolidation - [ ] Cover cross-organization ID probing, another conversation's binding, revoked access between upload and send, unready/failed/retired revisions, @@ -79,6 +63,7 @@ implemented by this increment yet. ## Sequencing -Finish Knowledge ingestion coverage item 3 and the reusable parser extraction -before wiring the composer. Assistant may depend on the resulting Source -pipeline; ingestion must never depend on Assistant. +The Knowledge ingestion coverage dependency is shipped and immutable. Assistant +depends on its Source pipeline and parser capability; neither Knowledge nor the +parser integration may depend on Assistant. Backend binding/readiness and +selection-constrained retrieval land before composer UI wiring. diff --git a/docs/increments/active/2026-08-10-assistant-file-evidence/reference-study.md b/docs/increments/active/2026-08-10-assistant-file-evidence/reference-study.md index 94c1f9758..558930f4d 100644 --- a/docs/increments/active/2026-08-10-assistant-file-evidence/reference-study.md +++ b/docs/increments/active/2026-08-10-assistant-file-evidence/reference-study.md @@ -32,6 +32,18 @@ documentation or recollection. repeated on every turn because conversation memory can replay old attachment evidence. +Evidence: + +- `core/src/main/java/com/northstar/core/attachment/AttachmentService.java` + (`store`, `safeFilename`) +- `apps/api/src/main/java/com/northstar/api/attachment/AttachmentController.java` + (`upload`, `indexStatus`, `serve`) +- `apps/worker/src/main/java/com/northstar/worker/search/SearchIndexingWorker.java` +- `core/src/main/java/com/northstar/core/search/SearchService.java` + (`indexAttachment`, `indexAttachmentLocked`) +- `apps/api/src/main/java/com/northstar/api/assistant/AssistantController.java` + (`chat`, `streamTurn`, `userMessageWithEvidence`, `ChatRequest`) + ### What OrgMemory should learn 1. Preparation is asynchronous and visible; Tika and embedding do not belong on @@ -75,6 +87,18 @@ search or metadata plus a file-reader tool. Tabular data is metadata-only. Chat files and durable user files remain separate allowlists for tools. File download returns 404 across ownership boundaries. +Evidence: + +- `backend/onyx/file_store/models.py` (`ChatFileType`, `FileDescriptor`) +- `backend/onyx/db/models.py` (`UserFile`) +- `backend/onyx/db/enums.py` (`UserFileStatus`) +- `backend/onyx/server/features/projects/api.py` (`upload_user_files`, + `delete_user_file`, `get_user_file`) +- `backend/onyx/file_store/utils.py` (`verify_user_files`) +- `backend/onyx/chat/process_message.py` (`resolve_context_user_files`, + `extract_context_files`, and their verified turn-setup call sites) +- `backend/onyx/tools/tool_implementations/file_reader/file_reader_tool.py` + ### What OrgMemory should learn 1. Transient chat files and durable reusable files are different product @@ -101,4 +125,3 @@ Onyx's separation of identities and use-time verification. Do not copy either authorization model. The first OrgMemory composer upload should create governed Knowledge evidence through the existing Source Ledger. A separate transient-turn lane remains a future architecture decision, not an implementation shortcut. - From 5aee75354a4a5a83585d2f662640bfe8132bd7ea Mon Sep 17 00:00:00 2001 From: kl3inIT Date: Mon, 10 Aug 2026 18:21:55 +0700 Subject: [PATCH 04/10] feat(assistant): add governed file evidence --- .../api/assistant/AssistantChatRequest.java | 12 +- .../api/assistant/AssistantConfiguration.java | 25 ++ .../api/assistant/AssistantController.java | 120 ++++++- .../orgmemory/api/OpenApiContractTests.java | 22 ++ .../AssistantChatRequestValidationTests.java | 27 ++ .../AssistantControllerStreamingTests.java | 16 + .../features/assistant/api/chat-transport.ts | 3 + .../assistant/assistant-evidence.test.ts | 30 ++ .../features/assistant/assistant-evidence.ts | 33 ++ .../assistant/components/assistant-page.tsx | 196 +++++++++++- .../components/source-upload-dialog.tsx | 30 +- apps/web/test/e2e/assistant-pipeline.spec.ts | 148 ++++++++- contracts/openapi.json | 2 +- .../AssistantConversationService.java | 46 ++- .../AssistantEvidenceAnswerabilityPort.java | 30 ++ .../assistant/AssistantEvidenceBinding.java | 66 ++++ .../AssistantEvidenceBindingRepository.java | 31 ++ .../AssistantEvidenceBindingView.java | 17 + .../assistant/AssistantEvidenceService.java | 296 ++++++++++++++++++ .../assistant/AssistantEvidenceStatus.java | 9 + .../assistant/AssistantEvidenceTurnClaim.java | 14 + .../AssistantEvidenceUploadService.java | 46 +++ .../core/assistant/AssistantService.java | 43 ++- .../AssistantTurnEvidenceBinding.java | 59 ++++ ...ssistantTurnEvidenceBindingRepository.java | 8 + .../evidence/GovernedEvidenceQuery.java | 13 + .../evidence/GovernedEvidenceRef.java | 46 +++ .../evidence/GovernedFileUpload.java | 11 + .../evidence/GovernedFileUploadCommand.java | 18 ++ .../evidence/GovernedFileUploadResult.java | 18 ++ .../core/knowledge/evidence/package-info.java | 5 + .../graph/GraphEvidenceAnswerability.java | 23 ++ .../GraphEvidenceAnswerabilityQuery.java | 68 ++++ .../core/knowledge/graph/package-info.java | 1 + ...DefaultCanonicalHybridKnowledgeSearch.java | 57 +++- ...aultGraphRagKnowledgeRetrievalService.java | 74 ++++- .../ResolvedKnowledgeEvidenceScope.java | 25 ++ .../search/KnowledgeEvidenceSelection.java | 92 ++++++ .../PermissionAwareKnowledgeSearch.java | 20 ++ .../SourceLedgerGovernedEvidenceQuery.java | 68 ++++ .../SourceLedgerGovernedFileUpload.java | 36 +++ .../SourceUploadRegistrationService.java | 19 +- .../sourceledger/SourceUploadResult.java | 17 + .../sourceledger/SourceUploadService.java | 6 + .../knowledge/sourceledger/package-info.java | 1 + .../V32__assistant_governed_file_evidence.sql | 87 +++++ .../core/ModulithVerificationTests.java | 27 ++ .../AssistantConversationServiceTests.java | 1 + .../AssistantEvidenceServiceTests.java | 258 +++++++++++++++ .../AssistantEvidenceUploadServiceTests.java | 100 ++++++ .../core/assistant/AssistantServiceTests.java | 43 +++ .../GraphEvidenceAnswerabilityQueryTests.java | 80 +++++ .../CanonicalHybridKnowledgeSearchTests.java | 39 +++ ...raphRagKnowledgeRetrievalServiceTests.java | 39 +++ .../SourceUploadServiceTests.java | 2 +- 55 files changed, 2565 insertions(+), 58 deletions(-) create mode 100644 apps/web/src/features/assistant/assistant-evidence.test.ts create mode 100644 apps/web/src/features/assistant/assistant-evidence.ts create mode 100644 core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceAnswerabilityPort.java create mode 100644 core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceBinding.java create mode 100644 core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceBindingRepository.java create mode 100644 core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceBindingView.java create mode 100644 core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceService.java create mode 100644 core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceStatus.java create mode 100644 core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceTurnClaim.java create mode 100644 core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceUploadService.java create mode 100644 core/src/main/java/com/orgmemory/core/assistant/AssistantTurnEvidenceBinding.java create mode 100644 core/src/main/java/com/orgmemory/core/assistant/AssistantTurnEvidenceBindingRepository.java create mode 100644 core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedEvidenceQuery.java create mode 100644 core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedEvidenceRef.java create mode 100644 core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedFileUpload.java create mode 100644 core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedFileUploadCommand.java create mode 100644 core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedFileUploadResult.java create mode 100644 core/src/main/java/com/orgmemory/core/knowledge/evidence/package-info.java create mode 100644 core/src/main/java/com/orgmemory/core/knowledge/graph/GraphEvidenceAnswerability.java create mode 100644 core/src/main/java/com/orgmemory/core/knowledge/graph/GraphEvidenceAnswerabilityQuery.java create mode 100644 core/src/main/java/com/orgmemory/core/knowledge/search/KnowledgeEvidenceSelection.java create mode 100644 core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceLedgerGovernedEvidenceQuery.java create mode 100644 core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceLedgerGovernedFileUpload.java create mode 100644 core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadResult.java create mode 100644 core/src/main/resources/db/migration/V32__assistant_governed_file_evidence.sql create mode 100644 core/src/test/java/com/orgmemory/core/assistant/AssistantEvidenceServiceTests.java create mode 100644 core/src/test/java/com/orgmemory/core/assistant/AssistantEvidenceUploadServiceTests.java create mode 100644 core/src/test/java/com/orgmemory/core/knowledge/graph/GraphEvidenceAnswerabilityQueryTests.java diff --git a/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantChatRequest.java b/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantChatRequest.java index eff892b23..def61d842 100644 --- a/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantChatRequest.java +++ b/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantChatRequest.java @@ -2,11 +2,21 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; +import java.util.List; import java.util.UUID; record AssistantChatRequest( @NotBlank @Size(max = 1_000) String message, Integer limit, UUID conversationId, - UUID modelActivationId) { + UUID modelActivationId, + @Size(max = 3) List evidenceBindingIds) { + + AssistantChatRequest( + String message, + Integer limit, + UUID conversationId, + UUID modelActivationId) { + this(message, limit, conversationId, modelActivationId, List.of()); + } } diff --git a/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantConfiguration.java b/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantConfiguration.java index 11880ba27..3bccf7abf 100644 --- a/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantConfiguration.java +++ b/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantConfiguration.java @@ -4,6 +4,7 @@ import com.orgmemory.core.assistant.AssistantAssetToolService; import com.orgmemory.core.assistant.AssistantAgentModelPort; import com.orgmemory.core.assistant.AssistantAssetTraceRecorder; +import com.orgmemory.core.assistant.AssistantEvidenceAnswerabilityPort; import com.orgmemory.core.assistant.AssistantService; import com.orgmemory.core.assistant.observability.AssistantStageEventSink; import com.orgmemory.core.assistant.observability.AssistantTurnEvent; @@ -14,6 +15,7 @@ import com.orgmemory.core.assetregistry.promptcontract.PromptAssistantOperations; import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearch; import com.orgmemory.core.knowledge.retrieval.GraphRagKnowledgeRetrievalService; +import com.orgmemory.core.knowledge.graph.GraphEvidenceAnswerabilityQuery; import com.orgmemory.core.knowledge.search.PermissionAwareKnowledgeSearch; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.observation.ObservationRegistry; @@ -71,6 +73,29 @@ PermissionAwareKnowledgeSearch permissionAwareKnowledgeSearch( }; } + @Bean + AssistantEvidenceAnswerabilityPort assistantEvidenceAnswerability( + AssistantProperties properties, + ObjectProvider graphAnswerability) { + return switch (properties.retrievalEngine()) { + case CANONICAL_HYBRID -> source -> + AssistantEvidenceAnswerabilityPort.Answerability.ready(); + case GRAPH_RAG -> source -> { + var graph = graphAnswerability.getIfAvailable(() -> { + throw new IllegalStateException( + "Assistant retrieval engine GRAPH_RAG requires graph evidence readiness"); + }); + var answerability = graph.evaluate(source); + return switch (answerability.state()) { + case INDEXING -> AssistantEvidenceAnswerabilityPort.Answerability.indexing(); + case READY -> AssistantEvidenceAnswerabilityPort.Answerability.ready(); + case FAILED -> AssistantEvidenceAnswerabilityPort.Answerability.failed( + answerability.failureCode()); + }; + }; + }; + } + @Bean AssistantService assistantService( PermissionAwareKnowledgeSearch retrieval, diff --git a/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantController.java b/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantController.java index 65bf78329..adb636348 100644 --- a/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantController.java +++ b/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantController.java @@ -1,5 +1,6 @@ package com.orgmemory.api.assistant; +import com.orgmemory.api.ApiRequestException; import com.orgmemory.api.security.CurrentActorProvider; import com.orgmemory.core.assistant.AssistantAnswerFeedbackView; import com.orgmemory.core.assistant.AssistantAnswerSentiment; @@ -8,6 +9,10 @@ import com.orgmemory.core.assistant.AssistantConversationMessageView; import com.orgmemory.core.assistant.AssistantConversationService; import com.orgmemory.core.assistant.AssistantConversationSummary; +import com.orgmemory.core.assistant.AssistantEvidenceTurnClaim; +import com.orgmemory.core.assistant.AssistantEvidenceBindingView; +import com.orgmemory.core.assistant.AssistantEvidenceService; +import com.orgmemory.core.assistant.AssistantEvidenceUploadService; import com.orgmemory.core.assistant.AssistantService; import com.orgmemory.core.assistant.AssistantTurn; import com.orgmemory.core.assistant.AssistantTurnRef; @@ -17,12 +22,15 @@ import com.orgmemory.core.ai.AssistantModelSelectionRef; import com.orgmemory.core.knowledge.retrieval.CitationEvidenceReference; import com.orgmemory.core.knowledge.retrieval.CitationEvidenceService; +import com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection; import com.orgmemory.core.organization.CurrentActor; +import com.orgmemory.core.permission.KnowledgeClassification; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import java.io.IOException; import java.util.List; import java.util.Map; import java.util.UUID; @@ -43,8 +51,10 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import reactor.core.publisher.Flux; import tools.jackson.databind.ObjectMapper; @@ -75,6 +85,8 @@ class AssistantController { private final AssistantModelAuthorityService modelAuthority; private final CitationEvidenceService citationEvidence; private final AssistantRetrievalScheduler retrievalScheduler; + private final AssistantEvidenceUploadService evidenceUploads; + private final AssistantEvidenceService evidence; private final ObjectMapper json; AssistantController( @@ -85,6 +97,8 @@ class AssistantController { AssistantModelAuthorityService modelAuthority, CitationEvidenceService citationEvidence, AssistantRetrievalScheduler retrievalScheduler, + AssistantEvidenceUploadService evidenceUploads, + AssistantEvidenceService evidence, ObjectMapper json) { this.assistant = assistant; this.conversations = conversations; @@ -93,9 +107,66 @@ class AssistantController { this.modelAuthority = modelAuthority; this.citationEvidence = citationEvidence; this.retrievalScheduler = retrievalScheduler; + this.evidenceUploads = evidenceUploads; + this.evidence = evidence; this.json = json; } + @PostMapping(path = "/evidence", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @ResponseStatus(HttpStatus.CREATED) + @Operation( + operationId = "uploadAssistantEvidence", + summary = "Upload one governed file and bind it to an Assistant conversation") + AssistantEvidenceBindingView uploadEvidence( + @RequestPart("file") MultipartFile file, + @RequestParam(required = false) UUID conversationId, + @RequestParam UUID knowledgeSpaceId, + @RequestParam(defaultValue = "CONFIDENTIAL") + KnowledgeClassification classification, + Authentication authentication) { + CurrentActor actor = actors.current(authentication); + try (var content = file.getInputStream()) { + return evidenceUploads.upload( + actor, + conversationId, + knowledgeSpaceId, + classification, + file.getOriginalFilename(), + file.getSize(), + content); + } catch (IOException failure) { + throw new ApiRequestException( + "The uploaded Assistant file could not be read", + failure); + } + } + + @GetMapping("/conversations/{conversationId}/evidence/{bindingId}") + @Operation( + operationId = "getAssistantEvidence", + summary = "Read the active-engine preparation state of one owned binding") + AssistantEvidenceBindingView evidence( + @PathVariable UUID conversationId, + @PathVariable UUID bindingId, + Authentication authentication) { + return evidence.get( + actors.current(authentication), + conversationId, + bindingId); + } + + @GetMapping("/conversations/{conversationId}/evidence") + @Operation( + operationId = "listAssistantEvidence", + summary = "List governed file bindings for one owned conversation") + List evidence( + @PathVariable UUID conversationId, + Authentication authentication) { + return evidence.list( + actors.current(authentication), + conversationId); + } + @PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE) @Operation(operationId = "streamAssistantChat", summary = "Stream an answer from permission-verified knowledge") ResponseEntity>> chat( @@ -108,11 +179,24 @@ ResponseEntity>> chat( request.modelActivationId()); AssistantModelSelectionRef modelSelection = modelAuthority.selectionRef( routeAuthority); - AssistantTurnRef turnRef = conversations.beginTurn( - actor, - request.conversationId(), - request.message(), - modelSelection); + List requestedEvidence = request.evidenceBindingIds() == null + ? List.of() + : request.evidenceBindingIds(); + AssistantEvidenceTurnClaim turnClaim = requestedEvidence.isEmpty() + ? new AssistantEvidenceTurnClaim( + conversations.beginTurn( + actor, + request.conversationId(), + request.message(), + modelSelection), + KnowledgeEvidenceSelection.unrestricted()) + : conversations.beginTurnWithEvidence( + actor, + request.conversationId(), + request.message(), + modelSelection, + requestedEvidence); + AssistantTurnRef turnRef = turnClaim.turn(); UUID conversationId = turnRef.conversationId(); UUID assistantMessageId = UUID.randomUUID(); Flux parts = Flux.defer(() -> { @@ -124,14 +208,24 @@ ResponseEntity>> chat( AssistantStreamPart.Activity.Phase.RETRIEVAL, AssistantStreamPart.Activity.State.ACTIVE, null)), - retrievalScheduler.schedule(() -> assistant.startTurn( - actor, - request.message(), - request.limit(), - requestId, - conversationId.toString(), - routeAuthority, - turnStartedAtNanos)) + retrievalScheduler.schedule(() -> turnClaim.selection().restricted() + ? assistant.startTurn( + actor, + request.message(), + request.limit(), + requestId, + conversationId.toString(), + routeAuthority, + turnStartedAtNanos, + turnClaim.selection()) + : assistant.startTurn( + actor, + request.message(), + request.limit(), + requestId, + conversationId.toString(), + routeAuthority, + turnStartedAtNanos)) .flatMapMany(turn -> completedTurnParts( actor, turnRef, diff --git a/apps/api/src/test/java/com/orgmemory/api/OpenApiContractTests.java b/apps/api/src/test/java/com/orgmemory/api/OpenApiContractTests.java index 7c86187a6..54ca23eec 100644 --- a/apps/api/src/test/java/com/orgmemory/api/OpenApiContractTests.java +++ b/apps/api/src/test/java/com/orgmemory/api/OpenApiContractTests.java @@ -7,6 +7,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Set; +import java.util.TreeSet; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; @@ -50,6 +52,26 @@ void theCommittedScimContractDescribesOnlyTheLiveScimApi() throws Exception { verifyContract("scim", "scim-openapi.json", "https://memory.company.com"); } + @Test + void assistantEvidenceContractHasNoBindExistingSourceEndpoint() throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode contract = objectMapper.readTree( + Files.readString(repositoryRoot().resolve("contracts/openapi.json"))); + TreeSet evidencePaths = new TreeSet<>(); + contract.path("paths").fieldNames().forEachRemaining(path -> { + if (path.startsWith("/api/assistant") && path.contains("evidence")) { + evidencePaths.add(path); + } + }); + + assertEquals( + Set.of( + "/api/assistant/evidence", + "/api/assistant/conversations/{conversationId}/evidence", + "/api/assistant/conversations/{conversationId}/evidence/{bindingId}"), + evidencePaths); + } + private void verifyContract(String group, String fileName, String serverUrl) throws Exception { String generated = mockMvc.perform(get("/v3/api-docs/{group}", group)) diff --git a/apps/api/src/test/java/com/orgmemory/api/assistant/AssistantChatRequestValidationTests.java b/apps/api/src/test/java/com/orgmemory/api/assistant/AssistantChatRequestValidationTests.java index 3fbfc292e..f7a02b80b 100644 --- a/apps/api/src/test/java/com/orgmemory/api/assistant/AssistantChatRequestValidationTests.java +++ b/apps/api/src/test/java/com/orgmemory/api/assistant/AssistantChatRequestValidationTests.java @@ -12,9 +12,13 @@ import com.orgmemory.api.security.CurrentActorProvider; import com.orgmemory.core.ai.AssistantModelAuthorityService; import com.orgmemory.core.assistant.AssistantConversationService; +import com.orgmemory.core.assistant.AssistantEvidenceService; +import com.orgmemory.core.assistant.AssistantEvidenceUploadService; import com.orgmemory.core.assistant.AssistantService; import com.orgmemory.core.knowledge.retrieval.CitationEvidenceService; import jakarta.validation.Validation; +import java.util.List; +import java.util.UUID; import org.junit.jupiter.api.Test; import tools.jackson.databind.ObjectMapper; @@ -37,6 +41,27 @@ void enforcesTheMessageLimitBoundary() { } } + @Test + void limitsOneTurnToThreeEvidenceBindings() { + try (var factory = Validation.buildDefaultValidatorFactory()) { + var violations = factory.getValidator().validate(new AssistantChatRequest( + "Compare these files", + null, + null, + null, + List.of( + UUID.randomUUID(), + UUID.randomUUID(), + UUID.randomUUID(), + UUID.randomUUID()))); + + assertEquals(1, violations.size()); + assertEquals( + "evidenceBindingIds", + violations.iterator().next().getPropertyPath().toString()); + } + } + @Test void rejectsAnOversizedMessageBeforeOpeningTheStreamOrCreatingATurn() throws Exception { var assistant = mock(AssistantService.class); @@ -55,6 +80,8 @@ void rejectsAnOversizedMessageBeforeOpeningTheStreamOrCreatingATurn() throws Exc modelAuthority, citationEvidence, retrievalScheduler, + mock(AssistantEvidenceUploadService.class), + mock(AssistantEvidenceService.class), json)) .build(); diff --git a/apps/api/src/test/java/com/orgmemory/api/assistant/AssistantControllerStreamingTests.java b/apps/api/src/test/java/com/orgmemory/api/assistant/AssistantControllerStreamingTests.java index b8e54bb2e..95f12df45 100644 --- a/apps/api/src/test/java/com/orgmemory/api/assistant/AssistantControllerStreamingTests.java +++ b/apps/api/src/test/java/com/orgmemory/api/assistant/AssistantControllerStreamingTests.java @@ -22,6 +22,8 @@ import com.orgmemory.core.assistant.AssistantCitation; import com.orgmemory.core.assistant.AssistantCitationReference; import com.orgmemory.core.assistant.AssistantConversationService; +import com.orgmemory.core.assistant.AssistantEvidenceService; +import com.orgmemory.core.assistant.AssistantEvidenceUploadService; import com.orgmemory.core.assistant.AssistantService; import com.orgmemory.core.assistant.AssistantTurn; import com.orgmemory.core.assistant.AssistantTurnRef; @@ -92,6 +94,8 @@ void delegatesFeedbackThroughTheAuthenticatedActor() { mock(AssistantModelAuthorityService.class), mock(CitationEvidenceService.class), mock(AssistantRetrievalScheduler.class), + mock(AssistantEvidenceUploadService.class), + mock(AssistantEvidenceService.class), mock(ObjectMapper.class)); AssistantAnswerFeedbackView actual = controller.setFeedback( @@ -151,6 +155,8 @@ void exposesSafeModelChoicesAndPersistsOnlyAnAuthorizedSelectionReference() { authority, mock(CitationEvidenceService.class), mock(AssistantRetrievalScheduler.class), + mock(AssistantEvidenceUploadService.class), + mock(AssistantEvidenceService.class), mock(ObjectMapper.class)); AssistantController.AssistantModelOptionsResponse response = @@ -196,6 +202,8 @@ void hydratesOnlyCurrentlyVisibleCitationsWithoutCachingTheAuthorizationResult() mock(AssistantModelAuthorityService.class), evidenceService, mock(AssistantRetrievalScheduler.class), + mock(AssistantEvidenceUploadService.class), + mock(AssistantEvidenceService.class), mock(ObjectMapper.class)); var response = controller.citations(messageId, authentication); @@ -248,6 +256,8 @@ void usesOneServerOwnedIdentityForTheStreamAndPersistedAnswer() { mock(AssistantModelAuthorityService.class), mock(CitationEvidenceService.class), scheduler, + mock(AssistantEvidenceUploadService.class), + mock(AssistantEvidenceService.class), new ObjectMapper()); List frames = controller.chat( @@ -320,6 +330,8 @@ void emitsStreamStartAndRetrievalActivityWhileRetrievalIsStillBlocked() mock(AssistantModelAuthorityService.class), mock(CitationEvidenceService.class), scheduler, + mock(AssistantEvidenceUploadService.class), + mock(AssistantEvidenceService.class), new ObjectMapper()); try { @@ -427,6 +439,8 @@ void deletesAConversationThroughTheOwnedTranscriptAlone() { mock(AssistantModelAuthorityService.class), mock(CitationEvidenceService.class), mock(AssistantRetrievalScheduler.class), + mock(AssistantEvidenceUploadService.class), + mock(AssistantEvidenceService.class), mock(ObjectMapper.class)); controller.delete(conversationId, authentication); @@ -446,6 +460,8 @@ private static AssistantController controller() { mock(AssistantModelAuthorityService.class), mock(CitationEvidenceService.class), mock(AssistantRetrievalScheduler.class), + mock(AssistantEvidenceUploadService.class), + mock(AssistantEvidenceService.class), mock(ObjectMapper.class)); } diff --git a/apps/web/src/features/assistant/api/chat-transport.ts b/apps/web/src/features/assistant/api/chat-transport.ts index e853b526f..f163447ec 100644 --- a/apps/web/src/features/assistant/api/chat-transport.ts +++ b/apps/web/src/features/assistant/api/chat-transport.ts @@ -5,10 +5,12 @@ import { csrfFetch } from "@/features/session/csrf-fetch" export function createAssistantTransport({ conversationId, modelActivationId, + evidenceBindingIds, onConversationId, }: { conversationId: () => string | undefined modelActivationId: () => string | undefined + evidenceBindingIds: () => string[] onConversationId: (conversationId: string) => void }) { return new DefaultChatTransport({ @@ -33,6 +35,7 @@ export function createAssistantTransport({ limit: 5, conversationId: conversationId(), modelActivationId: modelActivationId(), + evidenceBindingIds: evidenceBindingIds(), }, } }, diff --git a/apps/web/src/features/assistant/assistant-evidence.test.ts b/apps/web/src/features/assistant/assistant-evidence.test.ts new file mode 100644 index 000000000..58ee38a04 --- /dev/null +++ b/apps/web/src/features/assistant/assistant-evidence.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest" + +import { + assistantEvidenceReady, + assistantEvidenceUploadDisabledReason, +} from "@/features/assistant/assistant-evidence" + +describe("Assistant governed file evidence", () => { + it("requires every selected file to be ready", () => { + expect(assistantEvidenceReady([{ status: "READY" }, { status: "READY" }])).toBe(true) + expect(assistantEvidenceReady([{ status: "READY" }, { status: "INDEXING" }])).toBe(false) + expect(assistantEvidenceReady([{ status: "READY" }, { status: "FAILED" }])).toBe(false) + }) + + it("keeps upload closed without a governed Space and after three selections", () => { + const base = { + busy: false, + uploading: false, + targetsLoading: false, + targetsError: false, + targetCount: 1, + selectedCount: 0, + } + expect(assistantEvidenceUploadDisabledReason({ ...base, targetCount: 0 })) + .toBe("No Knowledge Space is available for governed upload") + expect(assistantEvidenceUploadDisabledReason({ ...base, selectedCount: 3 })) + .toBe("A turn can include at most three files") + expect(assistantEvidenceUploadDisabledReason(base)).toBeUndefined() + }) +}) diff --git a/apps/web/src/features/assistant/assistant-evidence.ts b/apps/web/src/features/assistant/assistant-evidence.ts new file mode 100644 index 000000000..dc47c298d --- /dev/null +++ b/apps/web/src/features/assistant/assistant-evidence.ts @@ -0,0 +1,33 @@ +import type { AssistantEvidenceBindingView } from "@/lib/hey-api" + +export const MAX_ASSISTANT_EVIDENCE_FILES = 3 + +export function assistantEvidenceReady(bindings: AssistantEvidenceBindingView[]) { + return bindings.every((binding) => binding.status === "READY") +} + +export function assistantEvidenceUploadDisabledReason({ + busy, + uploading, + targetsLoading, + targetsError, + targetCount, + selectedCount, +}: { + busy: boolean + uploading: boolean + targetsLoading: boolean + targetsError: boolean + targetCount: number + selectedCount: number +}) { + if (busy) return "Wait for the current turn to finish" + if (uploading) return "A governed file is uploading" + if (targetsLoading) return "Loading available Knowledge Spaces" + if (targetsError) return "Knowledge Spaces could not be loaded" + if (targetCount === 0) return "No Knowledge Space is available for governed upload" + if (selectedCount >= MAX_ASSISTANT_EVIDENCE_FILES) { + return "A turn can include at most three files" + } + return undefined +} diff --git a/apps/web/src/features/assistant/components/assistant-page.tsx b/apps/web/src/features/assistant/components/assistant-page.tsx index 88f9376be..e27a00f74 100644 --- a/apps/web/src/features/assistant/components/assistant-page.tsx +++ b/apps/web/src/features/assistant/components/assistant-page.tsx @@ -1,17 +1,20 @@ import { useChat } from "@ai-sdk/react" -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" +import { useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query" import { type SourceUrlUIPart, type UIMessage } from "ai" import { Bot, Check, ChevronsUpDown, Copy, + FileText, LoaderCircle, + Paperclip, RotateCcw, ThumbsDown, ThumbsUp, + X, } from "lucide-react" -import { Fragment, type ReactNode, type RefObject } from "react" +import { Fragment, lazy, Suspense, type ReactNode, type RefObject } from "react" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { toast } from "sonner" @@ -43,6 +46,7 @@ import { PromptInputBody, PromptInputButton, PromptInputFooter, + PromptInputHeader, type PromptInputMessage, PromptInputSubmit, PromptInputTextarea, @@ -53,6 +57,11 @@ import { Suggestion, Suggestions } from "@/components/ai-elements/suggestion" import { Button } from "@/components/ui/button" import { createAssistantTransport } from "@/features/assistant/api/chat-transport" import { ASSISTANT_MESSAGE_MAX_CHARACTERS } from "@/features/assistant/assistant-message-constraints" +import { + assistantEvidenceReady, + assistantEvidenceUploadDisabledReason, + MAX_ASSISTANT_EVIDENCE_FILES, +} from "@/features/assistant/assistant-evidence" import { activityLabel, hasVisibleAssistantOutput, @@ -67,28 +76,39 @@ import { AssistantSourcesPanel, } from "@/features/assistant/components/assistant-sources-panel" import { GovernedDocumentViewer } from "@/features/sources/components/governed-document-viewer" +import type { UploadSourceInput } from "@/features/sources/components/source-upload-dialog" import { useAssistantDraft } from "@/features/assistant/hooks/use-assistant-draft" import { scopeActorQueryKey } from "@/features/session/actor-cache-key" import { copyWithToast } from "@/lib/copy" import { deleteAssistantAnswerFeedbackMutation, getAssistantConversationHistoryOptions, + getAssistantEvidenceOptions, getAssistantMessageCitationsOptions, getAssistantModelOptionsOptions, listAssistantStartersOptions, listAssistantConversationsQueryKey, + listKnowledgeSpaceUploadTargetsOptions, selectAssistantConversationModelMutation, setAssistantAnswerFeedbackMutation, + uploadAssistantEvidenceMutation, } from "@/lib/hey-api/@tanstack/react-query.gen" import type { AssistantConversationMessageView, AssistantConversationSummary, + AssistantEvidenceBindingView, AssistantCitationResponse, AssistantModelOptionResponse, } from "@/lib/hey-api" +import { apiErrorMessage } from "@/lib/api-error" type AnswerSentiment = "HELPFUL" | "NOT_HELPFUL" +const SourceUploadDialog = lazy(async () => { + const module = await import("@/features/sources/components/source-upload-dialog") + return { default: module.SourceUploadDialog } +}) + function textFor(message: UIMessage) { return message.parts .filter((part) => part.type === "text") @@ -449,7 +469,10 @@ export function AssistantPage({ const actorKeyRef = useRef(actorKey) const conversationIdRef = useRef(conversationId) const modelActivationIdRef = useRef(undefined) + const evidenceBindingIdsRef = useRef([]) const [selectedModelActivationId, setSelectedModelActivationId] = useState() + const [evidenceBindings, setEvidenceBindings] = useState([]) + const [evidenceDialogOpen, setEvidenceDialogOpen] = useState(false) const locallyCreatedConversationRef = useRef(undefined) const nextTitleRef = useRef("New conversation") const onConversationIdChangeRef = useRef(onConversationIdChange) @@ -461,6 +484,7 @@ export function AssistantPage({ createAssistantTransport({ conversationId: () => conversationIdRef.current, modelActivationId: () => modelActivationIdRef.current, + evidenceBindingIds: () => evidenceBindingIdsRef.current, onConversationId: (nextConversationId) => { if (!conversationIdRef.current) { locallyCreatedConversationRef.current = nextConversationId @@ -558,6 +582,10 @@ export function AssistantPage({ setFinishedWithoutAnswer(true) }, 0) } + if (!isAbort && !isError) { + evidenceBindingIdsRef.current = [] + setEvidenceBindings([]) + } const invalidations = [ queryClient.invalidateQueries({ queryKey: conversationListQueryKey, @@ -617,6 +645,37 @@ export function AssistantPage({ ...modelOptionsDefinition, queryKey: scopeActorQueryKey(modelOptionsDefinition.queryKey, actorKey), }) + const uploadTargets = useQuery({ + ...listKnowledgeSpaceUploadTargetsOptions(), + queryKey: scopeActorQueryKey( + listKnowledgeSpaceUploadTargetsOptions().queryKey, + actorKey, + ), + }) + const evidenceStatusQueries = useQueries({ + queries: evidenceBindings.map((binding) => { + const options = getAssistantEvidenceOptions({ + path: { + conversationId: binding.conversationId ?? "00000000-0000-0000-0000-000000000000", + bindingId: binding.id ?? "00000000-0000-0000-0000-000000000000", + }, + }) + return { + ...options, + queryKey: scopeActorQueryKey(options.queryKey, actorKey), + enabled: Boolean(binding.conversationId && binding.id), + refetchInterval: (query: { state: { data?: AssistantEvidenceBindingView } }) => + query.state.data?.status === "PROCESSING" || query.state.data?.status === "INDEXING" + ? 1_500 + : false, + } + }), + }) + const currentEvidenceBindings = evidenceBindings.map( + (binding, index) => evidenceStatusQueries[index]?.data ?? binding, + ) + const evidenceReady = assistantEvidenceReady(currentEvidenceBindings) + const uploadEvidence = useMutation(uploadAssistantEvidenceMutation()) const selectModel = useMutation({ ...selectAssistantConversationModelMutation(), onError: () => { @@ -655,6 +714,8 @@ export function AssistantPage({ modelActivationIdRef.current = undefined locallyCreatedConversationRef.current = undefined setSelectedModelActivationId(undefined) + evidenceBindingIdsRef.current = [] + setEvidenceBindings([]) setSourcePanel(null) setPreviewSource(null) setFeedbackByMessage({}) @@ -699,6 +760,14 @@ export function AssistantPage({ ) }, [conversationId, history.data, setMessages]) const busy = status === "submitted" || status === "streaming" + const evidenceUploadDisabledReason = assistantEvidenceUploadDisabledReason({ + busy, + uploading: uploadEvidence.isPending, + targetsLoading: uploadTargets.isPending, + targetsError: uploadTargets.isError, + targetCount: uploadTargets.data?.length ?? 0, + selectedCount: evidenceBindings.length, + }) const latestMessage = messages.at(-1) const currentAssistant = latestMessage?.role === "assistant" ? latestMessage : undefined const currentAssistantVisible = currentAssistant @@ -740,6 +809,59 @@ export function AssistantPage({ [], ) + async function addEvidence(input: UploadSourceInput) { + if (evidenceBindingIdsRef.current.length >= MAX_ASSISTANT_EVIDENCE_FILES) { + throw new Error("A turn can include at most three files.") + } + let binding: AssistantEvidenceBindingView + try { + binding = await uploadEvidence.mutateAsync({ + body: { file: input.file }, + query: { + conversationId: conversationIdRef.current, + classification: input.classification, + knowledgeSpaceId: input.knowledgeSpaceId, + }, + }) + } catch (error) { + throw new Error(apiErrorMessage(error, "The file could not be uploaded.")) + } + if (!binding.id || !binding.conversationId) { + throw new Error("The upload did not return an Assistant evidence identity.") + } + if (!conversationIdRef.current) { + locallyCreatedConversationRef.current = binding.conversationId + conversationIdRef.current = binding.conversationId + queryClient.setQueryData( + conversationListQueryKey, + (current = []) => [ + { + id: binding.conversationId, + title: binding.fileName ?? "New conversation", + lastActivityAt: new Date().toISOString(), + messageCount: 0, + }, + ...current.filter((item) => item.id !== binding.conversationId), + ], + ) + onConversationIdChangeRef.current(binding.conversationId) + } + setEvidenceBindings((current) => { + const next = [...current, binding].slice(0, MAX_ASSISTANT_EVIDENCE_FILES) + evidenceBindingIdsRef.current = next.flatMap((item) => item.id ? [item.id] : []) + return next + }) + toast.success("File uploaded. Governed ingestion has started.") + } + + function removeEvidence(bindingId: string) { + setEvidenceBindings((current) => { + const next = current.filter((binding) => binding.id !== bindingId) + evidenceBindingIdsRef.current = next.flatMap((binding) => binding.id ? [binding.id] : []) + return next + }) + } + function send(rawMessage: string, clearComposer = true) { const message = rawMessage.trim() if (message.length > ASSISTANT_MESSAGE_MAX_CHARACTERS) { @@ -748,6 +870,10 @@ export function AssistantPage({ ) return } + if (evidenceBindings.length > 0 && !evidenceReady) { + toast.error("Wait until every selected file is ready.") + return + } if ( !message || busy || @@ -810,6 +936,35 @@ export function AssistantPage({ onSubmit={submit} className="w-full [&_[data-slot=input-group]]:rounded-3xl [&_[data-slot=input-group]]:border-border-subtle [&_[data-slot=input-group]]:bg-assistant-composer [&_[data-slot=input-group]]:shadow-sm [&_[data-slot=input-group]]:ring-0" > + {currentEvidenceBindings.length > 0 ? ( + + {currentEvidenceBindings.map((binding) => ( + + {binding.status === "PROCESSING" || binding.status === "INDEXING" ? ( + + ))} + + ) : null} + setEvidenceDialogOpen(true)} + > + {uploadEvidence.isPending ? ( + + {evidenceDialogOpen ? ( + + uploadTargets.refetch()} + onUpload={addEvidence} + description="This publishes the file as durable governed Knowledge to the selected Space audience. It remains available under that Space's lifecycle after this conversation." + trigger={false} + /> + + ) : null} 0 && !evidenceReady)) + } className="rounded-full" /> diff --git a/apps/web/src/features/sources/components/source-upload-dialog.tsx b/apps/web/src/features/sources/components/source-upload-dialog.tsx index 87236ce3a..5c91ef8d3 100644 --- a/apps/web/src/features/sources/components/source-upload-dialog.tsx +++ b/apps/web/src/features/sources/components/source-upload-dialog.tsx @@ -1,5 +1,5 @@ import { LoaderCircle, Upload } from "lucide-react" -import { useState, type FormEvent } from "react" +import { useState, type FormEvent, type ReactNode } from "react" import { Alert, AlertDescription } from "@/components/ui/alert" import { Button } from "@/components/ui/button" @@ -36,6 +36,8 @@ export function SourceUploadDialog({ spacesError, onRetrySpaces, onUpload, + trigger, + description, }: { open?: boolean onOpenChange?: (open: boolean) => void @@ -45,6 +47,8 @@ export function SourceUploadDialog({ spacesError: boolean onRetrySpaces: () => void onUpload: (input: UploadSourceInput) => Promise + trigger?: ReactNode | false + description?: ReactNode }) { const [internalOpen, setInternalOpen] = useState(false) const open = controlledOpen ?? internalOpen @@ -102,19 +106,27 @@ export function SourceUploadDialog({ return ( - - - + {trigger === false ? null : ( + + {trigger ?? ( + + )} + + )}
Upload a document - Office, OpenDocument, PDF, HTML, CSV, Markdown, or text files up to their displayed - format limit. + {description ?? ( + <> + Office, OpenDocument, PDF, HTML, CSV, Markdown, or text files up to their + displayed format limit. + + )} diff --git a/apps/web/test/e2e/assistant-pipeline.spec.ts b/apps/web/test/e2e/assistant-pipeline.spec.ts index e2ad03ae4..bb0f18aca 100644 --- a/apps/web/test/e2e/assistant-pipeline.spec.ts +++ b/apps/web/test/e2e/assistant-pipeline.spec.ts @@ -6,6 +6,8 @@ const THIRD_CHUNK_ID = "43000000-0000-0000-0000-000000000005" const CONVERSATION_ID = "44000000-0000-4000-8000-000000000001" const ANSWER_MESSAGE_ID = "44000000-0000-4000-8000-000000000002" const MODEL_ACTIVATION_ID = "45000000-0000-4000-8000-000000000001" +const EVIDENCE_BINDING_ID = "46000000-0000-4000-8000-000000000001" +const SPACE_ID = "46000000-0000-4000-8000-000000000002" interface HistoryMessage { id: string @@ -29,6 +31,7 @@ interface AssistantHarnessOptions { history?: HistoryMessage[] switchedActorHistory?: HistoryMessage[] selectedModelActivationId?: string + uploadTargets?: Array> } async function assistantHarness(page: Page, options: AssistantHarnessOptions = {}) { @@ -103,6 +106,55 @@ async function assistantHarness(page: Page, options: AssistantHarnessOptions = { return } + if ( + request.method() === "GET" && + url.pathname === "/api/knowledge-spaces/upload-targets" + ) { + await json( + route, + options.uploadTargets ?? [ + { id: SPACE_ID, name: "Research", departmentId: "41000000-0000-0000-0000-000000000003" }, + ], + ) + return + } + + if ( + request.method() === "POST" && + url.pathname === "/api/assistant/evidence" + ) { + await json(route, { + id: EVIDENCE_BINDING_ID, + conversationId: CONVERSATION_ID, + sourceObjectId: "46000000-0000-4000-8000-000000000003", + sourceRevisionId: "46000000-0000-4000-8000-000000000004", + knowledgeAssetId: "46000000-0000-4000-8000-000000000005", + title: "Policy", + fileName: "policy.txt", + status: "PROCESSING", + createdAt: "2026-08-10T00:00:00Z", + }, 201) + return + } + + if ( + request.method() === "GET" && + url.pathname === `/api/assistant/conversations/${CONVERSATION_ID}/evidence/${EVIDENCE_BINDING_ID}` + ) { + await json(route, { + id: EVIDENCE_BINDING_ID, + conversationId: CONVERSATION_ID, + sourceObjectId: "46000000-0000-4000-8000-000000000003", + sourceRevisionId: "46000000-0000-4000-8000-000000000004", + knowledgeAssetId: "46000000-0000-4000-8000-000000000005", + title: "Policy", + fileName: "policy.txt", + status: "READY", + createdAt: "2026-08-10T00:00:00Z", + }) + return + } + if ( request.method() === "GET" && url.pathname === "/api/assistant/conversations" @@ -300,6 +352,79 @@ async function assistantHarness(page: Page, options: AssistantHarnessOptions = { } } +test("uploads governed evidence and submits its ordered binding with the turn", async ({ page }) => { + const harness = await assistantHarness(page) + await page.goto("/") + + await page.getByRole("button", { name: "Attach governed file" }).click() + await expect(page.getByText(/publishes the file as durable governed Knowledge/i)).toBeVisible() + await page.locator("#source-file").setInputFiles({ + name: "policy.txt", + mimeType: "text/plain", + buffer: Buffer.from("The policy requires approval."), + }) + await page.getByRole("combobox", { name: "Knowledge Space" }).click() + await page.getByRole("option", { name: "Research" }).click() + await page.getByRole("button", { name: "Upload", exact: true }).click() + + await expect(page.getByText("policy.txt")).toBeVisible() + await expect(page.getByText("ready", { exact: true })).toBeVisible() + await submit(page, "Summarize the selected file") + + await expect.poll(() => harness.chatBodies.length).toBe(1) + expect(harness.chatBodies[0]?.evidenceBindingIds).toEqual([EVIDENCE_BINDING_ID]) + await expect(page.getByText("policy.txt")).toHaveCount(0) + expect(harness.unexpectedRequests).toEqual([]) + expect(harness.browserErrors).toEqual([]) +}) + +test("keeps the exact governed binding across a failed turn retry", async ({ page }) => { + const harness = await assistantHarness(page, { + chatFrames: [ + frame({ type: "start", messageId: "assistant-evidence-error" }), + frame({ type: "error", errorText: "The assistant stream failed." }), + "data: [DONE]", + ], + }) + await page.goto("/") + + await page.getByRole("button", { name: "Attach governed file" }).click() + await page.locator("#source-file").setInputFiles({ + name: "policy.txt", + mimeType: "text/plain", + buffer: Buffer.from("The policy requires approval."), + }) + await page.getByRole("combobox", { name: "Knowledge Space" }).click() + await page.getByRole("option", { name: "Research" }).click() + await page.getByRole("button", { name: "Upload", exact: true }).click() + await expect(page.getByText("ready", { exact: true })).toBeVisible() + + await submit(page, "Summarize the selected file") + await expect(page.getByRole("alert")).toContainText("OrgMemory could not complete this turn.") + await expect(page.getByText("policy.txt")).toBeVisible() + await page.getByRole("button", { name: "Retry" }).click() + + await expect.poll(() => harness.chatBodies.length).toBe(2) + expect(harness.chatBodies.map((body) => body.evidenceBindingIds)).toEqual([ + [EVIDENCE_BINDING_ID], + [EVIDENCE_BINDING_ID], + ]) + await expect(page.getByText("policy.txt")).toBeVisible() + expect(harness.unexpectedRequests).toEqual([]) + expect(harness.browserErrors).toEqual([]) +}) + +test("keeps governed attachment closed when no upload Space is authorized", async ({ page }) => { + const harness = await assistantHarness(page, { uploadTargets: [] }) + await page.goto("/") + + await expect(page.getByRole("button", { name: "Attach governed file" })).toBeDisabled({ + timeout: 15_000, + }) + expect(harness.unexpectedRequests).toEqual([]) + expect(harness.browserErrors).toEqual([]) +}) + test("anchors only server-declared citations and opens the matching source", async ({ page }) => { const secondPath = `/api/citations/${SECOND_CHUNK_ID}/content` const harness = await assistantHarness(page, { @@ -1078,8 +1203,16 @@ test("keeps high-frequency structured Assistant streams responsive", async ({ pa await page.addInitScript(() => { const stats = { rafMaxGap: 0, longTasks: [] as number[] } - ;(window as unknown as { __streamStats: typeof stats }).__streamStats = stats let last = performance.now() + const monitor = { + stats, + reset: () => { + stats.rafMaxGap = 0 + stats.longTasks = [] + last = performance.now() + }, + } + ;(window as unknown as { __streamMonitor: typeof monitor }).__streamMonitor = monitor const tick = (now: number) => { stats.rafMaxGap = Math.max(stats.rafMaxGap, now - last) last = now @@ -1095,18 +1228,25 @@ test("keeps high-frequency structured Assistant streams responsive", async ({ pa await assistantHarness(page, { chatFrames: frames }) await page.goto("/") + await page.evaluate(() => { + ;(window as unknown as { __streamMonitor: { reset: () => void } }).__streamMonitor.reset() + }) await submit(page, "Run streaming performance test") await page.getByRole("button", { name: "Submit" }).waitFor({ state: "visible", timeout: 120_000 }) const stats = await page.evaluate(() => { - const value = (window as unknown as { __streamStats: { rafMaxGap: number; longTasks: number[] } }).__streamStats + const value = ( + window as unknown as { + __streamMonitor: { stats: { rafMaxGap: number; longTasks: number[] } } + } + ).__streamMonitor.stats return { rafMaxGap: value.rafMaxGap, longTaskMax: Math.max(0, ...value.longTasks), } }) - expect(stats.rafMaxGap).toBeLessThan(500) - expect(stats.longTaskMax).toBeLessThan(500) + expect(stats.rafMaxGap, JSON.stringify(stats)).toBeLessThan(500) + expect(stats.longTaskMax, JSON.stringify(stats)).toBeLessThan(500) }) function minimalPdf() { diff --git a/contracts/openapi.json b/contracts/openapi.json index 1547cc2c1..8eb979931 100644 --- a/contracts/openapi.json +++ b/contracts/openapi.json @@ -1 +1 @@ -{"openapi":"3.1.0","info":{"title":"OpenAPI definition","version":"v0"},"servers":[{"url":"http://localhost"}],"paths":{"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/pack/{itemKey}":{"put":{"tags":["assistant-asset-tool-controller"],"summary":"Update actor-derived Pack progress after explicit confirmation","operationId":"updateAssistantPackProgress","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"itemKey","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackProgressRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PackToolResult"}}}}}}},"/api/assistant/messages/{messageId}/feedback":{"put":{"tags":["assistant-controller"],"summary":"Create or replace feedback on an owned Assistant answer","operationId":"setAssistantAnswerFeedback","parameters":[{"name":"messageId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnswerFeedbackRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssistantAnswerFeedbackView"}}}}}},"delete":{"tags":["assistant-controller"],"summary":"Remove feedback from an owned Assistant answer","operationId":"deleteAssistantAnswerFeedback","parameters":[{"name":"messageId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"No Content"}}}},"/api/assistant/conversations/{conversationId}/model":{"put":{"tags":["assistant-controller"],"summary":"Select one allowed model for an owned Assistant conversation","operationId":"selectAssistantConversationModel","parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelectAssistantModelRequest"}}},"required":true},"responses":{"204":{"description":"No Content"}}}},"/api/assets/{assetId}/skill-draft":{"put":{"tags":["asset-registry-controller"],"summary":"Replace the package of one mutable Skill draft","operationId":"replaceSkillDraftPackage","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"expectedLockVersion","in":"query","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/skill-activation":{"get":{"tags":["asset-registry-controller"],"summary":"Read the current user's opt-in state for one visible Skill","operationId":"getSkillActivation","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SkillActivationView"}}}}}},"put":{"tags":["asset-registry-controller"],"summary":"Enable or disable one visible Skill for the current user","operationId":"setSkillActivation","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SkillActivationRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SkillActivationView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/pack-progress/{itemKey}":{"put":{"tags":["asset-consumption-controller"],"summary":"Idempotently update actor-derived progress for one accessible Pack item","operationId":"setCapabilityPackProgress","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"itemKey","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackProgressRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PackJourney"}}}}}}},"/api/assets/{assetId}/draft":{"put":{"tags":["asset-registry-controller"],"summary":"Update a mutable Asset draft","operationId":"updateAssetDraft","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAssetDraftRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/admin/source-principals/{principalId}/mapping":{"put":{"tags":["admin-source-access-controller"],"summary":"Confirm a principal maps to an internal user","operationId":"confirmAdminSourceMapping","parameters":[{"name":"principalId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmMappingRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminSourcePrincipalResponse"}}}}}},"delete":{"tags":["admin-source-access-controller"],"summary":"Revoke a principal's active mapping","operationId":"revokeAdminSourceMapping","parameters":[{"name":"principalId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminSourcePrincipalResponse"}}}}}}},"/api/admin/source-connections/identity-trust":{"put":{"tags":["admin-source-access-controller"],"summary":"Record the identity trust for a connection","operationId":"setAdminSourceConnectionTrust","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdentityTrustRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminSourceConnectionResponse"}}}}}}},"/api/admin/connectors/{sourceSystem}/{connectionKey}":{"put":{"tags":["admin-connector-controller"],"summary":"Record how a connection is crawled","operationId":"configureAdminConnection","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigureConnectionRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminConnectionResponse"}}}}}}},"/api/admin/connectors/{sourceSystem}/{connectionKey}/credential":{"put":{"tags":["admin-connector-controller"],"summary":"Store a credential for a connection","operationId":"setAdminConnectionCredential","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorCredentialRequest"}}},"required":true},"responses":{"204":{"description":"No Content"}}},"delete":{"tags":["admin-connector-controller"],"summary":"Forget a connection's stored credential","operationId":"forgetAdminConnectionCredential","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"}}}},"/api/admin/ai/routes/{workload}":{"put":{"tags":["admin-ai-model-controller"],"summary":"Set an editable organization AI route","operationId":"setAdminAiRoute","parameters":[{"name":"workload","in":"path","required":true,"schema":{"type":"string","enum":["ASSISTANT_CHAT","PROMPT_EXECUTION","KEYWORD_PLANNING","GRAPH_EXTRACTION","QUERY_EMBEDDING","DOCUMENT_EMBEDDING"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetRouteRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RouteResponse"}}}}}},"delete":{"tags":["admin-ai-model-controller"],"summary":"Restore the deployment default for an editable AI route","operationId":"clearAdminAiRoute","parameters":[{"name":"workload","in":"path","required":true,"schema":{"type":"string","enum":["ASSISTANT_CHAT","PROMPT_EXECUTION","KEYWORD_PLANNING","GRAPH_EXTRACTION","QUERY_EMBEDDING","DOCUMENT_EMBEDDING"]}}],"responses":{"204":{"description":"No Content"}}}},"/api/admin/ai/gateways/{profileId}":{"put":{"tags":["admin-ai-model-controller"],"summary":"Update an organization AI gateway","operationId":"updateAdminAiGateway","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGatewayRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GatewayResponse"}}}}}},"delete":{"tags":["admin-ai-model-controller"],"summary":"Disable an unused organization AI gateway","operationId":"disableAdminAiGateway","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"No Content"}}}},"/api/admin/ai/gateways/{profileId}/credential":{"put":{"tags":["admin-ai-model-controller"],"summary":"Set or rotate an organization AI gateway credential","operationId":"setAdminAiGatewayCredential","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatewayCredentialRequest"}}},"required":true},"responses":{"204":{"description":"No Content"}}}},"/api/admin/ai/gateways/{profileId}/assistant-models":{"put":{"tags":["admin-ai-model-controller"],"summary":"Replace additional chat models allowed on the active Assistant gateway","operationId":"replaceAdminAssistantModels","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssistantModelsRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssistantModelResponse"}}}}}}}},"/api/sources":{"get":{"tags":["source-controller"],"summary":"List sources visible to the current user","operationId":"listSources","parameters":[{"name":"knowledgeSpaceId","in":"query","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"classification","in":"query","required":false,"schema":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","RESTRICTED"]}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["PROCESSING","READY","ATTENTION"]}},{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":25}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SourcePageResponse"}}}}}},"post":{"tags":["source-controller"],"summary":"Upload a source for asynchronous ingestion","operationId":"uploadSource","parameters":[{"name":"classification","in":"query","required":false,"schema":{"type":"string","default":"CONFIDENTIAL","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","RESTRICTED"]}},{"name":"knowledgeSpaceId","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SourceResponse"}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/curations/suppressions":{"post":{"tags":["knowledge-graph-management-controller"],"summary":"Delete an effective graph identity without deleting evidence","operationId":"suppressGraphIdentity","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressIdentityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CuratedEntity"},{"$ref":"#/components/schemas/CuratedRelation"},{"$ref":"#/components/schemas/IdentityAlias"},{"$ref":"#/components/schemas/IdentitySuppression"}]}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/curations/relations":{"post":{"tags":["knowledge-graph-management-controller"],"summary":"Create or edit a governed graph relation","operationId":"curateGraphRelation","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CurateRelationRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CuratedEntity"},{"$ref":"#/components/schemas/CuratedRelation"},{"$ref":"#/components/schemas/IdentityAlias"},{"$ref":"#/components/schemas/IdentitySuppression"}]}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/curations/entities":{"post":{"tags":["knowledge-graph-management-controller"],"summary":"Create or edit a governed graph entity","operationId":"curateGraphEntity","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CurateEntityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CuratedEntity"},{"$ref":"#/components/schemas/CuratedRelation"},{"$ref":"#/components/schemas/IdentityAlias"},{"$ref":"#/components/schemas/IdentitySuppression"}]}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/curations/aliases":{"post":{"tags":["knowledge-graph-management-controller"],"summary":"Merge graph identities through a reversible alias","operationId":"mergeGraphIdentity","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AliasIdentityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CuratedEntity"},{"$ref":"#/components/schemas/CuratedRelation"},{"$ref":"#/components/schemas/IdentityAlias"},{"$ref":"#/components/schemas/IdentitySuppression"}]}}}}}}},"/api/knowledge-assets/{knowledgeAssetId}/graph-index":{"post":{"tags":["knowledge-asset-lifecycle-controller"],"summary":"Ensure graph indexing uses the current processing profile","operationId":"ensureKnowledgeAssetGraphIndex","parameters":[{"name":"knowledgeAssetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Accepted","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GraphIndexJobView"}}}}}}},"/api/knowledge-assets/graph-jobs/{jobId}/resume":{"post":{"tags":["knowledge-asset-lifecycle-controller"],"summary":"Resume unfinished graph indexing","operationId":"resumeGraphIndexJob","parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Accepted","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GraphIndexJobView"}}}}}}},"/api/knowledge-assets/graph-jobs/{jobId}/cancel":{"post":{"tags":["knowledge-asset-lifecycle-controller"],"summary":"Cancel queued or in-flight graph indexing","operationId":"cancelGraphIndexJob","parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GraphIndexJobView"}}}}}}},"/api/assistant/tools/knowledge-search":{"post":{"tags":["assistant-asset-tool-controller"],"summary":"Search canonical permission-aware Knowledge and return citation references","operationId":"searchAssistantKnowledge","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeSearchRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/KnowledgeResult"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/prompt-run":{"post":{"tags":["assistant-asset-tool-controller"],"summary":"Run an exact Prompt release after explicit external-provider confirmation","operationId":"runAssistantPrompt","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptRunRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptRunToolResult"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/prompt-render":{"post":{"tags":["assistant-asset-tool-controller"],"summary":"Render an exact Prompt release after variable validation","operationId":"renderAssistantPrompt","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptRenderRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptRenderToolResult"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/pack":{"get":{"tags":["assistant-asset-tool-controller"],"summary":"Read an actor-scoped exact Pack journey","operationId":"readAssistantPack","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PackToolResult"}}}}}},"post":{"tags":["assistant-asset-tool-controller"],"summary":"Start an exact Pack after explicit state-change confirmation","operationId":"startAssistantPack","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmedActionRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PackToolResult"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/fork":{"post":{"tags":["assistant-asset-tool-controller"],"summary":"Fork an exact release after explicit draft-creation confirmation","operationId":"forkAssistantAssetRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForkRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ForkResult"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/feedback":{"post":{"tags":["assistant-asset-tool-controller"],"summary":"Submit feedback against an exact release after explicit confirmation","operationId":"submitAssistantAssetFeedback","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/FeedbackResult"}}}}}}},"/api/assistant/chat":{"post":{"tags":["assistant-controller"],"summary":"Stream an answer from permission-verified knowledge","operationId":"streamAssistantChat","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssistantChatRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ServerSentEventString"}}}}}}}},"/api/assets":{"get":{"tags":["asset-registry-controller"],"summary":"List released or authoring Assets visible to the actor","operationId":"listAssets","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetSummary"}}}}}}},"post":{"tags":["asset-registry-controller"],"summary":"Create an Asset and its mutable draft","operationId":"createAsset","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAssetRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/withdrawal":{"post":{"tags":["asset-registry-controller"],"summary":"Withdraw every Release and retire an owned Asset","operationId":"withdrawAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetAvailabilityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/submissions":{"post":{"tags":["asset-registry-controller"],"summary":"Submit an immutable Asset revision for review","operationId":"submitAssetRevision","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitAssetRevisionRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/skill-releases":{"post":{"tags":["asset-registry-controller"],"summary":"Publish a Skill draft as an immutable release","operationId":"publishSkillRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishSkillReleaseRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/shares":{"post":{"tags":["asset-registry-controller"],"summary":"Share an Asset with one Viewer or Editor audience","operationId":"shareAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareAssetRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}},"delete":{"tags":["asset-registry-controller"],"summary":"Remove one Viewer or Editor share","operationId":"unshareAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareAssetRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/role-assignments":{"post":{"tags":["asset-registry-controller"],"summary":"Assign an accountable role on an Asset","operationId":"assignAssetRole","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignAssetRoleRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/reviews/{reviewCaseId}/decisions":{"post":{"tags":["asset-registry-controller"],"summary":"Record a decision against an exact revision digest","operationId":"decideAssetReview","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"reviewCaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetReviewDecisionRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/releases":{"post":{"tags":["asset-registry-controller"],"summary":"Publish an approved immutable Asset revision","operationId":"publishAssetRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishAssetReleaseRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/work-instruction/acknowledgement":{"post":{"tags":["asset-consumption-controller"],"summary":"Idempotently acknowledge an exact Work Instruction release","operationId":"acknowledgeWorkInstruction","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/WorkInstructionView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/withdrawal":{"post":{"tags":["asset-registry-controller"],"summary":"Withdraw an Asset release from new use","operationId":"withdrawAssetRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetAvailabilityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/prompt/runs":{"post":{"tags":["asset-consumption-controller"],"summary":"Run an exact Prompt release through the provider-neutral AI gateway","operationId":"runPromptRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptRunRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptRunResult"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/prompt/render":{"post":{"tags":["asset-consumption-controller"],"summary":"Deterministically render an exact authorized Prompt release","operationId":"renderPromptRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptVariablesRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptRenderResult"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/prompt/evaluations":{"post":{"tags":["asset-consumption-controller"],"summary":"Run the bounded evaluation cases pinned in a Prompt release","operationId":"evaluatePromptRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptEvaluationResult"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/pack-assignment":{"post":{"tags":["asset-consumption-controller"],"summary":"Start or resume an exact authorized Capability Pack release","operationId":"startCapabilityPack","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PackJourney"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/forks":{"post":{"tags":["asset-consumption-controller"],"summary":"Fork an exact authorized release into a new mutable draft","operationId":"forkAssetRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForkReleaseRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/deprecation":{"post":{"tags":["asset-registry-controller"],"summary":"Deprecate an Asset release","operationId":"deprecateAssetRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetAvailabilityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/prompt/evaluation-comparisons":{"post":{"tags":["asset-consumption-controller"],"summary":"Compare bounded evaluation results for two exact Prompt releases","operationId":"comparePromptReleases","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptComparisonRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptEvaluationComparison"}}}}}}},"/api/assets/{assetId}/ownership-transfer":{"post":{"tags":["asset-registry-controller"],"summary":"Transfer Asset ownership to another user","operationId":"transferAssetOwnership","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferAssetOwnershipRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/ownership-recovery":{"post":{"tags":["asset-registry-controller"],"summary":"Recover an owner-vacant Asset as an administrator","operationId":"recoverAssetOwnership","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferAssetOwnershipRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/emergency-withdrawal":{"post":{"tags":["asset-registry-controller"],"summary":"Emergency-withdraw every Release from an Asset as an administrator","operationId":"emergencyWithdrawAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetAvailabilityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/direct-releases":{"post":{"tags":["asset-registry-controller"],"summary":"Publish the current working copy as an immutable Release","operationId":"publishAssetDraft","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishSkillReleaseRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/skills":{"post":{"tags":["asset-registry-controller"],"summary":"Validate and import one Agent Skill package","operationId":"importSkillPackage","parameters":[{"name":"namespace","in":"query","required":true,"schema":{"type":"string"}},{"name":"knowledgeSpaceId","in":"query","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"classification","in":"query","required":false,"schema":{"type":"string","default":"INTERNAL","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","RESTRICTED"]}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/skills/inspections":{"post":{"tags":["asset-registry-controller"],"summary":"Validate and inspect one Agent Skill package without storing it","operationId":"inspectSkillPackage","requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SkillPackageInspection"}}}}}}},"/api/assets/skills/github/preview":{"post":{"tags":["asset-registry-controller"],"summary":"Discover and validate Skills at one GitHub repository revision","operationId":"previewGitHubSkills","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GitHubSkillSourceRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/Preview"}}}}}}},"/api/assets/skills/github/import":{"post":{"tags":["asset-registry-controller"],"summary":"Import selected Skills from an exact GitHub commit","operationId":"importGitHubSkills","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GitHubSkillImportRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ImportResult"}}}}}}},"/api/asset-delivery/{assetId}/releases/{releaseId}/prompt-render":{"post":{"tags":["asset-delivery-controller"],"summary":"Deterministically render an exact authorized Prompt release","operationId":"renderReleasedPrompt","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptVariablesRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptRenderResult"}}}}}}},"/api/admin/roles/{role}/members":{"post":{"tags":["admin-role-controller"],"summary":"Assign a user to a role","operationId":"assignAdminRole","parameters":[{"name":"role","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignRoleRequest"}}},"required":true},"responses":{"204":{"description":"No Content"}}}},"/api/admin/provisioning/connections":{"get":{"tags":["admin-provisioning-controller"],"summary":"List SCIM provisioning connections","operationId":"listProvisioningConnections","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ConnectionResponse"}}}}}}},"post":{"tags":["admin-provisioning-controller"],"summary":"Create a disabled SCIM connection","operationId":"createProvisioningConnection","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConnectionRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ConnectionResponse"}}}}}}},"/api/admin/provisioning/connections/{connectionId}/credentials":{"get":{"tags":["admin-provisioning-controller"],"summary":"List SCIM credential metadata","operationId":"listProvisioningCredentials","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CredentialResponse"}}}}}}},"post":{"tags":["admin-provisioning-controller"],"summary":"Issue a one-time SCIM credential","operationId":"issueProvisioningCredential","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueCredentialRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IssuedCredentialResponse"}}}}}}},"/api/admin/provisioning/connections/{connectionId}/credentials/{credentialId}/rotate":{"post":{"tags":["admin-provisioning-controller"],"summary":"Rotate a SCIM credential with bounded overlap","operationId":"rotateProvisioningCredential","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"credentialId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueCredentialRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IssuedCredentialResponse"}}}}}}},"/api/admin/knowledge-spaces":{"get":{"tags":["admin-knowledge-space-controller"],"summary":"List Knowledge Spaces and the grants stored against them","operationId":"listAdminKnowledgeSpaces","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminKnowledgeSpaceResponse"}}}}}}},"post":{"tags":["admin-knowledge-space-controller"],"summary":"Create a Knowledge Space","operationId":"createAdminKnowledgeSpace","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKnowledgeSpaceRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminKnowledgeSpaceResponse"}}}}}}},"/api/admin/knowledge-spaces/{knowledgeSpaceId}/grants":{"post":{"tags":["admin-knowledge-space-controller"],"summary":"Grant a subject access to a Knowledge Space","operationId":"grantAdminKnowledgeSpaceAccess","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantKnowledgeSpaceAccessRequest"}}},"required":true},"responses":{"204":{"description":"No Content"}}},"delete":{"tags":["admin-knowledge-space-controller"],"summary":"Revoke a subject's access to a Knowledge Space","operationId":"revokeAdminKnowledgeSpaceAccess","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"relation","in":"query","required":true,"schema":{"type":"string"}},{"name":"kind","in":"query","required":true,"schema":{"type":"string","enum":["ORGANIZATION","DEPARTMENT","DEPARTMENT_MANAGERS","ROLE","USER"]}},{"name":"subjectId","in":"query","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"role","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"}}}},"/api/admin/invitations":{"get":{"tags":["admin-invitation-controller"],"summary":"List invited addresses and their status","operationId":"listAdminInvitations","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminInvitationResponse"}}}}}}},"post":{"tags":["admin-invitation-controller"],"summary":"Expect an address to sign in","operationId":"createAdminInvitation","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvitationRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminInvitationResponse"}}}}}}},"/api/admin/connectors/{sourceSystem}/{connectionKey}/test":{"post":{"tags":["admin-connector-controller"],"summary":"Check a connection's stored credential","operationId":"testAdminConnection","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminConnectorProbeResponse"}}}}}}},"/api/admin/connectors/{sourceSystem}/{connectionKey}/crawl":{"post":{"tags":["admin-connector-controller"],"summary":"Ask for a content crawl on the next poll","operationId":"requestAdminConnectionCrawl","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Accepted"}}}},"/api/admin/connectors/{sourceSystem}/test":{"post":{"tags":["admin-connector-controller"],"summary":"Check a credential without storing it","operationId":"testAdminConnectorCredential","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorCredentialRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminConnectorProbeResponse"}}}}}}},"/api/admin/ai/gateways":{"get":{"tags":["admin-ai-model-controller"],"summary":"List organization AI gateway profiles","operationId":"listAdminAiGateways","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GatewayResponse"}}}}}}},"post":{"tags":["admin-ai-model-controller"],"summary":"Connect an organization AI gateway","operationId":"createAdminAiGateway","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGatewayRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GatewayResponse"}}}}}}},"/api/admin/ai/gateways/{profileId}/test":{"post":{"tags":["admin-ai-model-controller"],"summary":"Test a stored organization AI gateway","operationId":"testStoredAdminAiGateway","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProbeResponse"}}}}}}},"/api/admin/ai/gateways/test":{"post":{"tags":["admin-ai-model-controller"],"summary":"Test an AI gateway credential without storing it","operationId":"testAdminAiGateway","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestGatewayRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProbeResponse"}}}}}}},"/api/admin/access/explain":{"post":{"tags":["admin-permission-controller"],"summary":"Answer whether a user holds a permission on one resource, and by which derivation","operationId":"explainAdminAccess","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExplainAccessRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ExplainAccessResponse"}}}}}}},"/api/assistant/conversations/{conversationId}":{"delete":{"tags":["assistant-controller"],"summary":"Delete the current actor's conversation transcript","operationId":"deleteAssistantConversation","parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"No Content"}}},"patch":{"tags":["assistant-controller"],"summary":"Rename the current actor's conversation","operationId":"renameAssistantConversation","parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameConversationRequest"}}},"required":true},"responses":{"204":{"description":"No Content"}}}},"/api/admin/users/{userId}":{"patch":{"tags":["admin-user-controller"],"summary":"Change a user's clearance, department, or activation","operationId":"updateAdminUser","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAdminUserRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminUserResponse"}}}}}}},"/api/admin/provisioning/connections/{connectionId}/state":{"patch":{"tags":["admin-provisioning-controller"],"summary":"Compare-and-set SCIM connection state","operationId":"updateProvisioningConnectionState","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStateRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ConnectionResponse"}}}}}}},"/api/sources/{sourceId}/content":{"get":{"tags":["source-content-controller"],"summary":"Stream permission-verified current source evidence","operationId":"readSourceContent","parameters":[{"name":"sourceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StreamingResponseBody"}}}}}}},"/api/session":{"get":{"tags":["browser-session-controller"],"summary":"Read the current browser session","operationId":"getBrowserSession","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}}}}},"/api/session/csrf":{"get":{"tags":["browser-session-controller"],"summary":"Issue a CSRF token for browser mutations","operationId":"getBrowserCsrfToken","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CsrfResponse"}}}}}}},"/api/organization/context":{"get":{"tags":["organization-context-controller"],"operationId":"context","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/OrganizationContextResponse"}}}}}}},"/api/me":{"get":{"tags":["me-controller"],"summary":"Read the current user's governed profile","operationId":"getMe","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}}}}},"/api/knowledge/search":{"get":{"tags":["knowledge-search-controller"],"summary":"Search permission-verified knowledge evidence","operationId":"searchKnowledge","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/KnowledgeSearchResponse"}}}}}}},"/api/knowledge/catalog":{"get":{"tags":["knowledge-catalog-controller"],"summary":"List current permission-verified Knowledge versions for composition","operationId":"listKnowledgeCatalog","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeCatalogItem"}}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/export":{"get":{"tags":["knowledge-graph-management-controller"],"summary":"Export only graph evidence visible to the current user","operationId":"exportKnowledgeGraph","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","default":"JSON","enum":["JSON","CSV","MARKDOWN","TEXT"]}},{"name":"X-Request-Id","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string"}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/explorer":{"get":{"tags":["knowledge-graph-explorer-controller"],"summary":"Read a bounded permission-filtered graph view","operationId":"exploreKnowledgeGraph","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"entityLimit","in":"query","required":false,"schema":{"type":"integer","format":"int32"}},{"name":"maxDepth","in":"query","required":false,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/KnowledgeGraphView"}}}}}}},"/api/knowledge-spaces/visible":{"get":{"tags":["knowledge-space-controller"],"summary":"List Knowledge Spaces visible to the current user","operationId":"listVisibleKnowledgeSpaces","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeSpaceResponse"}}}}}}}},"/api/knowledge-spaces/upload-targets":{"get":{"tags":["knowledge-space-controller"],"summary":"List Knowledge Spaces where the current user may add knowledge","operationId":"listKnowledgeSpaceUploadTargets","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeSpaceResponse"}}}}}}}},"/api/knowledge-assets/graph-jobs/{jobId}":{"get":{"tags":["knowledge-asset-lifecycle-controller"],"summary":"Read graph indexing lifecycle status","operationId":"getGraphIndexJob","parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GraphIndexJobView"}}}}}}},"/api/health":{"get":{"tags":["health-controller"],"operationId":"health","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}}}}},"/api/citations/{chunkId}/excerpt":{"get":{"tags":["citation-content-controller"],"summary":"Read a bounded permission-verified citation excerpt","operationId":"readCitationExcerpt","parameters":[{"name":"chunkId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CitationEvidenceExcerpt"}}}}}}},"/api/citations/{chunkId}/content":{"get":{"tags":["citation-content-controller"],"summary":"Stream permission-verified source evidence","operationId":"readCitationContent","parameters":[{"name":"chunkId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StreamingResponseBody"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/work-instruction":{"get":{"tags":["assistant-asset-tool-controller"],"summary":"Guide an exact Work Instruction release","operationId":"followAssistantWorkInstruction","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/WorkInstructionToolResult"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/prompt-form":{"get":{"tags":["assistant-asset-tool-controller"],"summary":"Resolve the variables required by an exact Prompt release","operationId":"prepareAssistantPrompt","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptFormResult"}}}}}}},"/api/assistant/tools/asset-recommendations":{"get":{"tags":["assistant-asset-tool-controller"],"summary":"Recommend exact usable Asset releases without leaking denied candidates","operationId":"recommendAssistantAssets","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecommendationResult"}}}}}}},"/api/assistant/starters":{"get":{"tags":["assistant-controller"],"summary":"List supported prompts for starting an Assistant conversation","operationId":"listAssistantStarters","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssistantStarterPrompt"}}}}}}}},"/api/assistant/model-options":{"get":{"tags":["assistant-controller"],"summary":"List server-governed Assistant model choices for the current route","operationId":"getAssistantModelOptions","parameters":[{"name":"conversationId","in":"query","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssistantModelOptionsResponse"}}}}}}},"/api/assistant/messages/{messageId}/citations":{"get":{"tags":["assistant-controller"],"summary":"Hydrate currently authorized citations for one owned Assistant answer","operationId":"getAssistantMessageCitations","parameters":[{"name":"messageId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssistantCitationResponse"}}}}}}}},"/api/assistant/conversations":{"get":{"tags":["assistant-controller"],"summary":"List the current actor's conversations by recent activity","operationId":"listAssistantConversations","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssistantConversationSummary"}}}}}}}},"/api/assistant/conversations/{conversationId}/messages":{"get":{"tags":["assistant-controller"],"summary":"Replay a tenant- and actor-scoped full conversation transcript","operationId":"getAssistantConversationHistory","parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssistantConversationMessageView"}}}}}}}},"/api/assets/{assetId}":{"get":{"tags":["asset-registry-controller"],"summary":"Read an Asset's authoring and governance history","operationId":"getAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/work-instruction":{"get":{"tags":["asset-consumption-controller"],"summary":"Follow an exact authorized Work Instruction release","operationId":"followWorkInstruction","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/WorkInstructionView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/skill-manifest":{"get":{"tags":["asset-consumption-controller"],"summary":"Read the browser install contract for one exact authorized Skill release","operationId":"getSkillInstallContract","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SkillInstallManifest"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/pack-journey":{"get":{"tags":["asset-consumption-controller"],"summary":"Read an actor-scoped Capability Pack journey","operationId":"getCapabilityPackJourney","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PackJourney"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/pack-definition":{"get":{"tags":["asset-consumption-controller"],"summary":"Read the ordered authorized items pinned by a Capability Pack release","operationId":"getCapabilityPackDefinition","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CapabilityPackDefinition"}}}}}}},"/api/assets/{assetId}/released":{"get":{"tags":["asset-registry-controller"],"summary":"Read released Asset content without exposing its working copy or governance history","operationId":"getReleasedAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetReleasedView"}}}}}}},"/api/assets/{assetId}/governance-actions":{"get":{"tags":["asset-registry-controller"],"summary":"Read the current actor's available Governance actions","operationId":"getAssetGovernanceActions","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetGovernanceActions"}}}}}}},"/api/assets/skills/github/connections":{"get":{"tags":["asset-registry-controller"],"summary":"List approved GitHub connections available for private Skill import","operationId":"listGitHubSkillConnections","parameters":[{"name":"knowledgeSpaceId","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ConnectionOption"}}}}}}}},"/api/assets/owned":{"get":{"tags":["asset-registry-controller"],"summary":"List Assets currently owned by the actor","operationId":"listOwnedAssets","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"RECENTLY_UPDATED","enum":["RECENTLY_UPDATED","NAME"]}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":1}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":24}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetSummaryPage"}}}}}}},"/api/assets/catalog":{"get":{"tags":["asset-consumption-controller"],"summary":"List exact usable Asset releases authorized for the current actor","operationId":"listAssetCatalog","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"RECENTLY_RELEASED","enum":["RECENTLY_RELEASED","NAME"]}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":1}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":24}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetRecommendationPage"}}}}}}},"/api/asset-delivery":{"get":{"tags":["asset-delivery-controller"],"summary":"Search exact released Assets authorized for the current actor","operationId":"searchReleasedAssets","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetRecommendation"}}}}}}}},"/api/asset-delivery/{assetId}":{"get":{"tags":["asset-delivery-controller"],"summary":"Read the latest usable immutable release for an Asset","operationId":"getLatestReleasedAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetDeliveryRelease"}}}}}}},"/api/asset-delivery/{assetId}/releases/{releaseId}":{"get":{"tags":["asset-delivery-controller"],"summary":"Read one exact usable immutable Asset release","operationId":"getReleasedAsset_1","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetDeliveryRelease"}}}}}}},"/api/asset-delivery/{assetId}/releases/{releaseId}/skill-package":{"get":{"tags":["asset-delivery-controller"],"summary":"Stream the verified package for one exact usable Skill release","operationId":"downloadReleasedSkillPackage","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StreamingResponseBody"}}}}}}},"/api/asset-delivery/{assetId}/releases/{releaseId}/skill-manifest":{"get":{"tags":["asset-delivery-controller"],"summary":"Read the install manifest for one exact usable Skill release","operationId":"getReleasedSkillManifest","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SkillInstallManifest"}}}}}}},"/api/asset-delivery/{assetId}/releases/{releaseId}/relations":{"get":{"tags":["asset-delivery-controller"],"summary":"Resolve only independently authorized relations of an exact release","operationId":"resolveReleasedAssetRelations","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetRelationResolution"}}}}}}},"/api/asset-delivery/{assetId}/releases/{releaseId}/pack":{"get":{"tags":["asset-delivery-controller"],"summary":"Read a Pack definition with independently authorized pinned items","operationId":"getReleasedCapabilityPack","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CapabilityPackDefinition"}}}}}}},"/api/asset-delivery/skills/{namespace}/{slug}/versions/{version}/manifest":{"get":{"tags":["asset-delivery-controller"],"summary":"Resolve an exact usable Skill release by coordinate and version","operationId":"resolveReleasedSkillManifest","parameters":[{"name":"namespace","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"version","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SkillInstallManifest"}}}}}}},"/api/admin/users":{"get":{"tags":["admin-user-controller"],"summary":"List internal users with their sign-in and mapping status","operationId":"listAdminUsers","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminUserResponse"}}}}}}}},"/api/admin/users/{userId}/permissions":{"get":{"tags":["admin-permission-controller"],"summary":"Resolve a user's organization permissions as the engine currently answers them","operationId":"listAdminUserPermissions","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/EffectivePermissionResponse"}}}}}}},"/api/admin/source-principals":{"get":{"tags":["admin-source-access-controller"],"summary":"List observed principals and their mapping","operationId":"listAdminSourcePrincipals","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminSourcePrincipalResponse"}}}}}}}},"/api/admin/source-groups":{"get":{"tags":["admin-source-access-controller"],"summary":"List source groups with their sealed membership","operationId":"listAdminSourceGroups","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminSourceGroupResponse"}}}}}}}},"/api/admin/source-connections":{"get":{"tags":["admin-source-access-controller"],"summary":"List observed connections and their trust level","operationId":"listAdminSourceConnections","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminSourceConnectionResponse"}}}}}}}},"/api/admin/roles":{"get":{"tags":["admin-role-controller"],"summary":"List roles and who is assigned to them","operationId":"listAdminRoles","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminRoleListResponse"}}}}}}},"/api/admin/knowledge-spaces/grant-options":{"get":{"tags":["admin-knowledge-space-controller"],"summary":"List the subject shapes each Knowledge Space relation accepts","operationId":"listAdminKnowledgeSpaceGrantOptions","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeSpaceGrantOptionResponse"}}}}}}}},"/api/admin/connectors/{sourceSystem}":{"get":{"tags":["admin-connector-controller"],"summary":"List a source's connections and their crawl settings","operationId":"listAdminConnections","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminConnectionResponse"}}}}}}}},"/api/admin/connectors/{sourceSystem}/{connectionKey}/scopes":{"get":{"tags":["admin-connector-controller"],"summary":"List what a connection can be pointed at","operationId":"listAdminConnectionScopes","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminConnectorScopeResponse"}}}}}}}},"/api/admin/connectors/{sourceSystem}/{connectionKey}/activity":{"get":{"tags":["admin-connector-controller"],"summary":"Read what a connection has crawled and what went wrong","operationId":"getAdminConnectionActivity","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminConnectionActivityResponse"}}}}}}},"/api/admin/connectors/sources":{"get":{"tags":["admin-connector-controller"],"summary":"List the sources this deployment can ingest","operationId":"listAdminConnectorSources","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminConnectorSourceResponse"}}}}}}}},"/api/admin/ai/routes":{"get":{"tags":["admin-ai-model-controller"],"summary":"List effective organization AI routes","operationId":"listAdminAiRoutes","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RouteResponse"}}}}}}}},"/api/admin/ai/providers":{"get":{"tags":["admin-ai-model-controller"],"summary":"List provider presets implemented by this deployment","operationId":"listAdminAiProviderPresets","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProviderPresetResponse"}}}}}}}},"/api/admin/ai/index-settings":{"get":{"tags":["admin-ai-model-controller"],"summary":"Read immutable deployment-managed embedding settings","operationId":"getAdminAiIndexSettings","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IndexSettingsResponse"}}}}}}},"/api/sources/{sourceId}":{"delete":{"tags":["source-controller"],"summary":"Retire a ready manual-upload document","operationId":"deleteSource","parameters":[{"name":"sourceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/KnowledgeAssetRef"}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/curations/{curationId}":{"delete":{"tags":["knowledge-graph-management-controller"],"summary":"Reverse a graph curation record","operationId":"deactivateGraphCuration","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"curationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"authorizationGeneration","in":"query","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"reason","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/api/knowledge-assets/{knowledgeAssetId}":{"delete":{"tags":["knowledge-asset-lifecycle-controller"],"summary":"Retire a Knowledge Asset and remove its derived graph","operationId":"deleteKnowledgeAsset","parameters":[{"name":"knowledgeAssetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/KnowledgeAssetRef"}}}}}}},"/api/admin/roles/{role}/members/{userId}":{"delete":{"tags":["admin-role-controller"],"summary":"Remove a user from a role","operationId":"revokeAdminRole","parameters":[{"name":"role","in":"path","required":true,"schema":{"type":"string"}},{"name":"userId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"No Content"}}}},"/api/admin/provisioning/connections/{connectionId}/credentials/{credentialId}":{"delete":{"tags":["admin-provisioning-controller"],"summary":"Immediately revoke a SCIM credential","operationId":"revokeProvisioningCredential","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"credentialId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"No Content"}}}},"/api/admin/invitations/{invitationId}":{"delete":{"tags":["admin-invitation-controller"],"summary":"Withdraw an invitation that has not been used","operationId":"revokeAdminInvitation","parameters":[{"name":"invitationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"No Content"}}}}},"components":{"schemas":{"PackProgressRequest":{"type":"object","properties":{"completed":{"type":"boolean"},"confirmed":{"type":"boolean"}}},"Item":{"type":"object","properties":{"key":{"type":"string"},"required":{"type":"boolean"},"order":{"type":"integer","format":"int32"},"kind":{"type":"string"},"resourceId":{"type":"string","format":"uuid"},"pinnedVersionId":{"type":"string","format":"uuid"},"title":{"type":"string"},"versionLabel":{"type":"string"},"availability":{"type":"string","enum":["AVAILABLE","DEPRECATED","WITHDRAWN"]},"completed":{"type":"boolean"},"completedAt":{"type":"string","format":"date-time"}}},"PackJourney":{"type":"object","properties":{"assignmentId":{"type":"string","format":"uuid"},"packAssetId":{"type":"string","format":"uuid"},"packReleaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"},"title":{"type":"string"},"versionLabel":{"type":"string"},"purpose":{"type":"string","enum":["ROLE_ONBOARDING","HANDOVER","ROLE_ENABLEMENT"]},"audience":{"type":"string"},"expectedOutcome":{"type":"string"},"status":{"type":"string","enum":["IN_PROGRESS","COMPLETED"]},"accessGap":{"type":"boolean"},"completedAccessibleItems":{"type":"integer","format":"int32"},"items":{"type":"array","items":{"$ref":"#/components/schemas/Item"}},"startedAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}},"PackToolResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"journey":{"$ref":"#/components/schemas/PackJourney"}}},"AnswerFeedbackRequest":{"type":"object","properties":{"sentiment":{"type":"string","enum":["HELPFUL","NOT_HELPFUL"]}},"required":["sentiment"]},"AssistantAnswerFeedbackView":{"type":"object","properties":{"messageId":{"type":"string","format":"uuid"},"sentiment":{"type":"string","enum":["HELPFUL","NOT_HELPFUL"]},"updatedAt":{"type":"string","format":"date-time"}}},"SelectAssistantModelRequest":{"type":"object","properties":{"modelActivationId":{"type":"string","format":"uuid"}}},"AssetView":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]},"namespace":{"type":"string"},"slug":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"portfolioState":{"type":"string","enum":["DRAFT_ONLY","ACTIVE","SUNSETTING","RETIRED"]},"authorizationReady":{"type":"boolean"},"ownerUserId":{"type":"string","format":"uuid"},"sharingState":{"type":"string","enum":["PRIVATE","SHARED","ORGANIZATION"]},"draft":{"$ref":"#/components/schemas/Draft"},"revisions":{"type":"array","items":{"$ref":"#/components/schemas/Revision"}},"reviews":{"type":"array","items":{"$ref":"#/components/schemas/Review"}},"releases":{"type":"array","items":{"$ref":"#/components/schemas/Release"}},"ownershipHealth":{"$ref":"#/components/schemas/OwnershipHealth"},"roleAssignments":{"type":"array","items":{"$ref":"#/components/schemas/RoleAssignment"}}}},"AvailabilityEvent":{"type":"object","properties":{"availability":{"type":"string","enum":["AVAILABLE","DEPRECATED","WITHDRAWN"]},"reason":{"type":"string"},"changedByUserId":{"type":"string","format":"uuid"},"effectiveAt":{"type":"string","format":"date-time"}}},"Decision":{"type":"object","properties":{"reviewerUserId":{"type":"string","format":"uuid"},"decision":{"type":"string","enum":["REQUEST_CHANGES","REJECT","APPROVE","CANCEL"]},"comment":{"type":"string"},"decidedAt":{"type":"string","format":"date-time"}}},"Draft":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"lockVersion":{"type":"integer","format":"int64"},"title":{"type":"string"},"summary":{"type":"string"},"classification":{"type":"string"},"schemaVersion":{"type":"string"},"payload":{"type":"string"},"editedByUserId":{"type":"string","format":"uuid"},"updatedAt":{"type":"string","format":"date-time"}}},"OwnershipHealth":{"type":"object","properties":{"ownerPresent":{"type":"boolean"},"backupOwnerPresent":{"type":"boolean"},"orphaned":{"type":"boolean"},"continuityAtRisk":{"type":"boolean"}}},"Release":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"revisionId":{"type":"string","format":"uuid"},"sequence":{"type":"integer","format":"int64"},"versionLabel":{"type":"string"},"publicationMode":{"type":"string","enum":["REVIEWED","DIRECT"]},"title":{"type":"string"},"summary":{"type":"string"},"classification":{"type":"string"},"schemaVersion":{"type":"string"},"payload":{"type":"string"},"digest":{"type":"string"},"releasedByUserId":{"type":"string","format":"uuid"},"releasedAt":{"type":"string","format":"date-time"},"availability":{"type":"string","enum":["AVAILABLE","DEPRECATED","WITHDRAWN"]},"availabilityHistory":{"type":"array","items":{"$ref":"#/components/schemas/AvailabilityEvent"}}}},"Review":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"revisionId":{"type":"string","format":"uuid"},"revisionDigest":{"type":"string"},"state":{"type":"string","enum":["IN_REVIEW","CHANGES_REQUESTED","REJECTED","CANCELLED","APPROVED"]},"policyVersion":{"type":"string"},"requestedByUserId":{"type":"string","format":"uuid"},"createdAt":{"type":"string","format":"date-time"},"resolvedAt":{"type":"string","format":"date-time"},"decisions":{"type":"array","items":{"$ref":"#/components/schemas/Decision"}}}},"Revision":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"sequence":{"type":"integer","format":"int64"},"title":{"type":"string"},"summary":{"type":"string"},"classification":{"type":"string"},"schemaVersion":{"type":"string"},"payload":{"type":"string"},"digest":{"type":"string"},"changeNote":{"type":"string"},"createdByUserId":{"type":"string","format":"uuid"},"createdAt":{"type":"string","format":"date-time"}}},"RoleAssignment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"principalType":{"type":"string"},"principalId":{"type":"string"},"role":{"type":"string","enum":["OWNER","BACKUP_OWNER","STEWARD","VIEWER","EDITOR","REVIEWER","PUBLISHER"]},"validFrom":{"type":"string","format":"date-time"},"validUntil":{"type":"string","format":"date-time"},"assignedByUserId":{"type":"string","format":"uuid"},"projectedAt":{"type":"string","format":"date-time"}}},"SkillActivationRequest":{"type":"object","properties":{"enabled":{"type":"boolean"}}},"SkillActivationView":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"enabled":{"type":"boolean"}}},"UpdateAssetDraftRequest":{"type":"object","properties":{"expectedLockVersion":{"type":"integer","format":"int64"},"title":{"type":"string"},"summary":{"type":"string"},"classification":{"type":"string"},"schemaVersion":{"type":"string"},"payload":{"type":"string"}}},"ConfirmMappingRequest":{"type":"object","properties":{"appUserId":{"type":"string","format":"uuid"}}},"AdminSourceMappingResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"appUserId":{"type":"string","format":"uuid"},"appUserName":{"type":"string"},"appUserEmail":{"type":"string"},"method":{"type":"string","enum":["IDP_JOIN","SSO_EMAIL_JOIN","SELF_CLAIM","ADMIN_CONFIRMED"]},"status":{"type":"string","enum":["ACTIVE","REVOKED"]},"evidence":{"type":"string"},"verifiedAt":{"type":"string","format":"date-time"}}},"AdminSourcePrincipalResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"sourceSystem":{"type":"string"},"sourceConnectionKey":{"type":"string"},"nativePrincipalId":{"type":"string"},"kind":{"type":"string","enum":["SOURCE_USER","SOURCE_GROUP"]},"observedEmail":{"type":"string"},"observedDisplayName":{"type":"string"},"ssoVerified":{"type":"boolean"},"lastSeenAt":{"type":"string","format":"date-time"},"mapping":{"$ref":"#/components/schemas/AdminSourceMappingResponse"}}},"IdentityTrustRequest":{"type":"object","properties":{"sourceSystem":{"type":"string"},"sourceConnectionKey":{"type":"string"},"identityTrust":{"type":"string","enum":["UNTRUSTED","SSO_VERIFIED"]}}},"AdminSourceConnectionResponse":{"type":"object","properties":{"sourceSystem":{"type":"string"},"sourceConnectionKey":{"type":"string"},"identityTrust":{"type":"string","enum":["UNTRUSTED","SSO_VERIFIED"]},"trustDecidedByUserId":{"type":"string","format":"uuid"},"trustDecidedAt":{"type":"string","format":"date-time"},"userCount":{"type":"integer","format":"int32"},"mappedUserCount":{"type":"integer","format":"int32"},"unmappedUserCount":{"type":"integer","format":"int32"},"groupCount":{"type":"integer","format":"int32"},"lastSeenAt":{"type":"string","format":"date-time"}}},"ConfigureConnectionRequest":{"type":"object","properties":{"crawlEnabled":{"type":"boolean"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"actorUserId":{"type":"string","format":"uuid"},"sourceConfig":{"type":"object","additionalProperties":{}},"contentCrawlIntervalSeconds":{"type":"integer","format":"int64"}}},"AdminConnectionResponse":{"type":"object","properties":{"sourceSystem":{"type":"string"},"sourceConnectionKey":{"type":"string"},"identityTrust":{"type":"string","enum":["UNTRUSTED","SSO_VERIFIED"]},"crawlEnabled":{"type":"boolean"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"actorUserId":{"type":"string","format":"uuid"},"sourceConfig":{"type":"object","additionalProperties":{}},"contentCrawlIntervalSeconds":{"type":"integer","format":"int64"},"credentialSet":{"type":"boolean"},"credentialSetByUserId":{"type":"string","format":"uuid"},"credentialSetAt":{"type":"string","format":"date-time"},"configuredByUserId":{"type":"string","format":"uuid"},"configuredAt":{"type":"string","format":"date-time"}}},"ConnectorCredentialRequest":{"type":"object","properties":{"credential":{"type":"string"}}},"SetRouteRequest":{"type":"object","properties":{"gatewayProfileId":{"type":"string","format":"uuid"},"modelId":{"type":"string"},"openAiReasoningEffort":{"type":"string","enum":["NONE","LOW","MEDIUM","HIGH","XHIGH","MAX"]}}},"RouteResponse":{"type":"object","properties":{"workload":{"type":"string","enum":["ASSISTANT_CHAT","PROMPT_EXECUTION","KEYWORD_PLANNING","GRAPH_EXTRACTION","QUERY_EMBEDDING","DOCUMENT_EMBEDDING"]},"gatewayKey":{"type":"string"},"gatewayProfileId":{"type":"string","format":"uuid"},"modelId":{"type":"string"},"openAiReasoningEffort":{"type":"string","enum":["NONE","LOW","MEDIUM","HIGH","XHIGH","MAX"]},"source":{"type":"string"},"editable":{"type":"boolean"},"version":{"type":"integer","format":"int64"},"lifecycleNote":{"type":"string"}}},"UpdateGatewayRequest":{"type":"object","properties":{"displayName":{"type":"string"},"baseUrl":{"type":"string"},"requestTimeoutSeconds":{"type":"integer","format":"int32"},"supportsOpenAiReasoningEffort":{"type":"boolean"},"credential":{"type":"string"}}},"AssistantModelResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"modelId":{"type":"string"},"displayName":{"type":"string"}}},"GatewayResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"gatewayKey":{"type":"string"},"displayName":{"type":"string"},"preset":{"type":"string","enum":["OPENAI","ANTHROPIC","NINE_ROUTER","OPENROUTER","LITELLM","OLLAMA","OPENAI_COMPATIBLE"]},"category":{"type":"string","enum":["DIRECT_PROVIDER","GATEWAY_ROUTER","SELF_HOSTED_CUSTOM"]},"protocol":{"type":"string","enum":["OPENAI_COMPATIBLE","ANTHROPIC_MESSAGES"]},"supportsOpenAiReasoningEffort":{"type":"boolean"},"baseUrl":{"type":"string"},"requestTimeoutSeconds":{"type":"integer","format":"int32"},"enabled":{"type":"boolean"},"version":{"type":"integer","format":"int64"},"credentialSet":{"type":"boolean"},"credentialSetByUserId":{"type":"string","format":"uuid"},"credentialSetAt":{"type":"string","format":"date-time"},"assistantModels":{"type":"array","items":{"$ref":"#/components/schemas/AssistantModelResponse"}}}},"GatewayCredentialRequest":{"type":"object","properties":{"credential":{"type":"string"}}},"AssistantModelDefinitionRequest":{"type":"object","properties":{"modelId":{"type":"string"},"displayName":{"type":"string"}}},"AssistantModelsRequest":{"type":"object","properties":{"models":{"type":"array","items":{"$ref":"#/components/schemas/AssistantModelDefinitionRequest"}}}},"SourceResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"sourceSystem":{"type":"string"},"aclAuthority":{"type":"string"},"status":{"type":"string"},"classification":{"type":"string"},"fileName":{"type":"string"},"mediaType":{"type":"string"},"contentLength":{"type":"integer","format":"int64"},"failureCode":{"type":"string"},"failureMessage":{"type":"string"},"knowledgeAssetId":{"type":"string","format":"uuid"},"knowledgeSpaceKey":{"type":"string"},"knowledgeSpaceName":{"type":"string"},"owningDepartmentName":{"type":"string"},"uploadedByName":{"type":"string"},"publicationComplete":{"type":"boolean"},"contentAvailable":{"type":"boolean"},"deletionAllowed":{"type":"boolean"},"embeddingProfileKey":{"type":"string"},"embeddingProvider":{"type":"string"},"embeddingModel":{"type":"string"},"embeddingDimensions":{"type":"integer","format":"int32"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"SuppressIdentityRequest":{"type":"object","properties":{"idempotencyKey":{"type":"string"},"reason":{"type":"string"},"authorizationGeneration":{"type":"integer","format":"int64"},"kind":{"type":"string","enum":["ENTITY","RELATION"]},"identityId":{"type":"string","format":"uuid"}}},"CuratedEntity":{"allOf":[{"$ref":"#/components/schemas/GraphCurationRecord"},{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"namespace":{"$ref":"#/components/schemas/ProjectionNamespace"},"entity":{"$ref":"#/components/schemas/GraphIdentityRef"},"name":{"type":"string"},"type":{"type":"string"},"description":{"type":"string"},"governingEvidence":{"$ref":"#/components/schemas/EvidenceReference"},"provenance":{"$ref":"#/components/schemas/CurationProvenance"}}}]},"CuratedRelation":{"allOf":[{"$ref":"#/components/schemas/GraphCurationRecord"},{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"namespace":{"$ref":"#/components/schemas/ProjectionNamespace"},"relation":{"$ref":"#/components/schemas/GraphIdentityRef"},"sourceEntity":{"$ref":"#/components/schemas/GraphIdentityRef"},"targetEntity":{"$ref":"#/components/schemas/GraphIdentityRef"},"type":{"type":"string"},"keywords":{"type":"array","items":{"type":"string"}},"description":{"type":"string"},"weight":{"type":"number","format":"double"},"governingEvidence":{"$ref":"#/components/schemas/EvidenceReference"},"provenance":{"$ref":"#/components/schemas/CurationProvenance"}}}]},"CurationProvenance":{"type":"object","properties":{"actorUserId":{"type":"string","format":"uuid"},"authorizationModelId":{"type":"string"},"aclGeneration":{"type":"integer","format":"int64"},"curatedAt":{"type":"string","format":"date-time"},"reason":{"type":"string"}}},"EvidenceReference":{"type":"object","properties":{"organizationId":{"type":"string","format":"uuid"},"knowledgeAssetId":{"type":"string","format":"uuid"},"sourceRevisionId":{"type":"string","format":"uuid"},"chunkId":{"type":"string","format":"uuid"},"aclSnapshotId":{"type":"string","format":"uuid"},"aclGeneration":{"type":"integer","format":"int64"},"chunk":{"type":"boolean"}}},"GraphCurationRecord":{},"GraphIdentityRef":{"type":"object","properties":{"kind":{"type":"string","enum":["ENTITY","RELATION"]},"id":{"type":"string","format":"uuid"}}},"IdentityAlias":{"allOf":[{"$ref":"#/components/schemas/GraphCurationRecord"},{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"namespace":{"$ref":"#/components/schemas/ProjectionNamespace"},"source":{"$ref":"#/components/schemas/GraphIdentityRef"},"target":{"$ref":"#/components/schemas/GraphIdentityRef"},"provenance":{"$ref":"#/components/schemas/CurationProvenance"}}}]},"IdentitySuppression":{"allOf":[{"$ref":"#/components/schemas/GraphCurationRecord"},{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"namespace":{"$ref":"#/components/schemas/ProjectionNamespace"},"identity":{"$ref":"#/components/schemas/GraphIdentityRef"},"provenance":{"$ref":"#/components/schemas/CurationProvenance"}}}]},"ProjectionNamespace":{"type":"object","properties":{"organizationId":{"type":"string","format":"uuid"},"workspace":{"type":"string"},"collection":{"type":"string"}}},"CurateRelationRequest":{"type":"object","properties":{"idempotencyKey":{"type":"string"},"reason":{"type":"string"},"authorizationGeneration":{"type":"integer","format":"int64"},"relationId":{"type":"string","format":"uuid"},"sourceEntityId":{"type":"string","format":"uuid"},"targetEntityId":{"type":"string","format":"uuid"},"type":{"type":"string"},"keywords":{"type":"array","items":{"type":"string"}},"description":{"type":"string"},"weight":{"type":"number","format":"double"},"evidence":{"$ref":"#/components/schemas/EvidenceRequest"}}},"EvidenceRequest":{"type":"object","properties":{"knowledgeAssetId":{"type":"string","format":"uuid"},"sourceRevisionId":{"type":"string","format":"uuid"},"chunkId":{"type":"string","format":"uuid"},"aclSnapshotId":{"type":"string","format":"uuid"},"aclGeneration":{"type":"integer","format":"int64"}}},"CurateEntityRequest":{"type":"object","properties":{"idempotencyKey":{"type":"string"},"reason":{"type":"string"},"authorizationGeneration":{"type":"integer","format":"int64"},"entityId":{"type":"string","format":"uuid"},"name":{"type":"string"},"type":{"type":"string"},"description":{"type":"string"},"evidence":{"$ref":"#/components/schemas/EvidenceRequest"}}},"AliasIdentityRequest":{"type":"object","properties":{"idempotencyKey":{"type":"string"},"reason":{"type":"string"},"authorizationGeneration":{"type":"integer","format":"int64"},"kind":{"type":"string","enum":["ENTITY","RELATION"]},"sourceIdentityId":{"type":"string","format":"uuid"},"targetIdentityId":{"type":"string","format":"uuid"}}},"GraphIndexJobView":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"knowledgeAssetId":{"type":"string","format":"uuid"},"knowledgeAssetVersionId":{"type":"string","format":"uuid"},"sourceRevisionId":{"type":"string","format":"uuid"},"projectionGeneration":{"type":"integer","format":"int64"},"graphProcessingProfileId":{"type":"string","format":"uuid"},"graphProcessingProfileSha256":{"type":"string"},"status":{"type":"string"},"attempt":{"type":"integer","format":"int32"},"cancellationRequested":{"type":"boolean"},"cancellationRequestedAt":{"type":"string","format":"date-time"},"lastErrorCode":{"type":"string"},"lastErrorMessage":{"type":"string"},"completedAt":{"type":"string","format":"date-time"}}},"KnowledgeSearchRequest":{"type":"object","properties":{"query":{"type":"string"},"requestId":{"type":"string"}}},"KnowledgeCitation":{"type":"object","properties":{"chunkId":{"type":"string","format":"uuid"},"knowledgeAssetId":{"type":"string","format":"uuid"},"sourceRevisionId":{"type":"string","format":"uuid"},"title":{"type":"string"},"startPage":{"type":"integer","format":"int32"},"endPage":{"type":"integer","format":"int32"},"heading":{"type":"string"}}},"KnowledgeResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"requestId":{"type":"string"},"citations":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeCitation"}}}},"PromptRunRequest":{"type":"object","properties":{"variables":{"type":"object","additionalProperties":{}},"knowledgeQuery":{"type":"string"},"requestId":{"type":"string"},"confirmedExternalProvider":{"type":"boolean"}}},"AiRoute":{"type":"object","properties":{"gatewayId":{"type":"string"},"modelId":{"type":"string"},"openAiReasoningEffort":{"type":"string","enum":["NONE","LOW","MEDIUM","HIGH","XHIGH","MAX"]}}},"PromptCitation":{"type":"object","properties":{"chunkId":{"type":"string","format":"uuid"},"knowledgeAssetId":{"type":"string","format":"uuid"},"sourceRevisionId":{"type":"string","format":"uuid"},"title":{"type":"string"},"startPage":{"type":"integer","format":"int32"},"endPage":{"type":"integer","format":"int32"},"heading":{"type":"string"}}},"PromptRunResult":{"type":"object","properties":{"runId":{"type":"string","format":"uuid"},"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"},"modelRoute":{"$ref":"#/components/schemas/AiRoute"},"output":{"type":"string"},"citations":{"type":"array","items":{"$ref":"#/components/schemas/PromptCitation"}},"durationMillis":{"type":"integer","format":"int64"}}},"PromptRunToolResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"result":{"$ref":"#/components/schemas/PromptRunResult"}}},"PromptRenderRequest":{"type":"object","properties":{"variables":{"type":"object","additionalProperties":{}}}},"PromptRenderResult":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"},"systemInstruction":{"type":"string"},"userPrompt":{"type":"string"},"sensitiveVariables":{"type":"array","items":{"type":"string"}},"inputShapeDigest":{"type":"string"}}},"PromptRenderToolResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"result":{"$ref":"#/components/schemas/PromptRenderResult"}}},"ConfirmedActionRequest":{"type":"object","properties":{"confirmed":{"type":"boolean"}}},"ForkRequest":{"type":"object","properties":{"namespace":{"type":"string"},"slug":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"confirmed":{"type":"boolean"}}},"ForkResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"asset":{"$ref":"#/components/schemas/AssetView"}}},"FeedbackRequest":{"type":"object","properties":{"type":{"type":"string","enum":["HELPFUL","OUTDATED","INCORRECT","OTHER"]},"comment":{"type":"string"},"confirmed":{"type":"boolean"}}},"FeedbackResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"feedbackId":{"type":"string","format":"uuid"}}},"AssistantChatRequest":{"type":"object","properties":{"message":{"type":"string","maxLength":1000,"minLength":0},"limit":{"type":"integer","format":"int32"},"conversationId":{"type":"string","format":"uuid"},"modelActivationId":{"type":"string","format":"uuid"}},"required":["message"]},"ServerSentEventString":{},"AssetDraftRequest":{"type":"object","properties":{"title":{"type":"string"},"summary":{"type":"string"},"classification":{"type":"string"},"schemaVersion":{"type":"string"},"payload":{"type":"string"}}},"CreateAssetRequest":{"type":"object","properties":{"type":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK"]},"namespace":{"type":"string"},"slug":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"draft":{"$ref":"#/components/schemas/AssetDraftRequest"}}},"AssetAvailabilityRequest":{"type":"object","properties":{"reason":{"type":"string"}}},"SubmitAssetRevisionRequest":{"type":"object","properties":{"changeNote":{"type":"string"}}},"PublishSkillReleaseRequest":{"type":"object","properties":{"versionLabel":{"type":"string"}}},"ShareAssetRequest":{"type":"object","properties":{"principalType":{"type":"string"},"principalId":{"type":"string"},"role":{"type":"string","enum":["OWNER","BACKUP_OWNER","STEWARD","VIEWER","EDITOR","REVIEWER","PUBLISHER"]}}},"AssignAssetRoleRequest":{"type":"object","properties":{"principalType":{"type":"string"},"principalId":{"type":"string"},"role":{"type":"string","enum":["OWNER","BACKUP_OWNER","STEWARD","VIEWER","EDITOR","REVIEWER","PUBLISHER"]}}},"AssetReviewDecisionRequest":{"type":"object","properties":{"decision":{"type":"string","enum":["REQUEST_CHANGES","REJECT","APPROVE","CANCEL"]},"comment":{"type":"string"}}},"PublishAssetReleaseRequest":{"type":"object","properties":{"revisionId":{"type":"string","format":"uuid"},"versionLabel":{"type":"string"}}},"Step":{"type":"object","properties":{"key":{"type":"string"},"title":{"type":"string"},"instruction":{"type":"string"},"expectedResult":{"type":"string"},"check":{"type":"string"},"escalation":{"type":"string"},"prohibitedActions":{"type":"array","items":{"type":"string"}},"relatedAssetIds":{"type":"array","items":{"type":"string","format":"uuid"}},"relatedKnowledgeVersionIds":{"type":"array","items":{"type":"string","format":"uuid"}}}},"WorkInstructionSpec":{"type":"object","properties":{"purpose":{"type":"string"},"audience":{"type":"string"},"prerequisites":{"type":"array","items":{"type":"string"}},"completionOutcome":{"type":"string"},"responsibleRole":{"type":"string"},"steps":{"type":"array","items":{"$ref":"#/components/schemas/Step"}}}},"WorkInstructionView":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"},"title":{"type":"string"},"versionLabel":{"type":"string"},"instruction":{"$ref":"#/components/schemas/WorkInstructionSpec"},"acknowledged":{"type":"boolean"},"acknowledgedAt":{"type":"string","format":"date-time"}}},"PromptVariablesRequest":{"type":"object","properties":{"variables":{"type":"object","additionalProperties":{}}}},"CaseResult":{"type":"object","properties":{"name":{"type":"string"},"passed":{"type":"boolean"},"failedAssertions":{"type":"array","items":{"type":"string"}},"promptRunId":{"type":"string","format":"uuid"}}},"PromptEvaluationResult":{"type":"object","properties":{"evaluationId":{"type":"string","format":"uuid"},"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"},"passedCases":{"type":"integer","format":"int32"},"totalCases":{"type":"integer","format":"int32"},"cases":{"type":"array","items":{"$ref":"#/components/schemas/CaseResult"}}}},"ForkReleaseRequest":{"type":"object","properties":{"namespace":{"type":"string"},"slug":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"}}},"PromptComparisonRequest":{"type":"object","properties":{"baselineReleaseId":{"type":"string","format":"uuid"},"candidateReleaseId":{"type":"string","format":"uuid"}}},"PromptEvaluationComparison":{"type":"object","properties":{"baseline":{"$ref":"#/components/schemas/PromptEvaluationResult"},"candidate":{"$ref":"#/components/schemas/PromptEvaluationResult"},"passedCaseDelta":{"type":"integer","format":"int32"}}},"TransferAssetOwnershipRequest":{"type":"object","properties":{"nextOwnerUserId":{"type":"string","format":"uuid"}},"required":["nextOwnerUserId"]},"FileEntry":{"type":"object","properties":{"path":{"type":"string"},"size":{"type":"integer","format":"int64"},"sha256":{"type":"string"}}},"SkillPackageInspection":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"license":{"type":"string"},"compatibility":{"type":"string"},"allowedTools":{"type":"string"},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"instructions":{"type":"string"},"sha256":{"type":"string"},"contentLength":{"type":"integer","format":"int64"},"files":{"type":"array","items":{"$ref":"#/components/schemas/FileEntry"}}}},"GitHubSkillSourceRequest":{"type":"object","properties":{"repository":{"type":"string","maxLength":512,"minLength":0},"revision":{"type":"string"},"subpath":{"type":"string"},"connectionKey":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"}},"required":["knowledgeSpaceId","repository"]},"Preview":{"type":"object","properties":{"repository":{"type":"string"},"revision":{"type":"string"},"visibility":{"type":"string","enum":["PUBLIC","PRIVATE"]},"skills":{"type":"array","items":{"$ref":"#/components/schemas/PreviewItem"}}}},"PreviewItem":{"type":"object","properties":{"path":{"type":"string"},"importable":{"type":"boolean"},"name":{"type":"string"},"description":{"type":"string"},"fileCount":{"type":"integer","format":"int32"},"errorCode":{"type":"string"},"errorMessage":{"type":"string"}}},"GitHubSkillImportRequest":{"type":"object","properties":{"source":{"$ref":"#/components/schemas/GitHubSkillSourceRequest"},"paths":{"type":"array","items":{"type":"string","minLength":1},"maxItems":20,"minItems":1},"namespace":{"type":"string","maxLength":128,"minLength":0},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","RESTRICTED"]}},"required":["namespace","paths","source"]},"ImportItem":{"type":"object","properties":{"path":{"type":"string"},"imported":{"type":"boolean"},"asset":{"$ref":"#/components/schemas/AssetView"},"errorCode":{"type":"string"},"errorMessage":{"type":"string"}}},"ImportResult":{"type":"object","properties":{"repository":{"type":"string"},"revision":{"type":"string"},"visibility":{"type":"string","enum":["PUBLIC","PRIVATE"]},"skills":{"type":"array","items":{"$ref":"#/components/schemas/ImportItem"}}}},"AssignRoleRequest":{"type":"object","properties":{"userId":{"type":"string","format":"uuid"}}},"CreateConnectionRequest":{"type":"object","properties":{"alias":{"type":"string"},"providerProfile":{"type":"string","enum":["GENERIC_SCIM","MICROSOFT_ENTRA","OKTA"]}}},"ConnectionResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"alias":{"type":"string"},"providerProfile":{"type":"string","enum":["GENERIC_SCIM","MICROSOFT_ENTRA","OKTA"]},"configurationStatus":{"type":"string"},"operationalState":{"type":"string","enum":["DISABLED","VALIDATING","ENABLED","READ_ONLY","SUSPENDED"]},"usersEnabled":{"type":"boolean"},"groupsEnabled":{"type":"boolean"},"version":{"type":"integer","format":"int64"}}},"IssueCredentialRequest":{"type":"object","properties":{"usersScope":{"type":"boolean"},"groupsScope":{"type":"boolean"}}},"IssuedCredentialResponse":{"type":"object","properties":{"credentialId":{"type":"string","format":"uuid"},"token":{"type":"string"},"publicTokenId":{"type":"string"},"usersScope":{"type":"boolean"},"groupsScope":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"}}},"CreateKnowledgeSpaceRequest":{"type":"object","properties":{"name":{"type":"string"},"audienceMode":{"type":"string","enum":["ORGANIZATION","DEPARTMENT","RESTRICTED_CUSTOM"]},"departmentId":{"type":"string","format":"uuid"}}},"AdminKnowledgeSpaceResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string"},"name":{"type":"string"},"audienceMode":{"type":"string","enum":["ORGANIZATION","DEPARTMENT","RESTRICTED_CUSTOM"]},"audienceVersion":{"type":"integer","format":"int64"},"departmentId":{"type":"string","format":"uuid"},"active":{"type":"boolean"},"grants":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeSpaceGrantResponse"}},"grantsComplete":{"type":"boolean"},"policyVersion":{"type":"string"}}},"KnowledgeSpaceGrantResponse":{"type":"object","properties":{"relation":{"type":"string"},"subject":{"type":"string"},"effective":{"type":"boolean"}}},"GrantKnowledgeSpaceAccessRequest":{"type":"object","properties":{"relation":{"type":"string"},"kind":{"type":"string","enum":["ORGANIZATION","DEPARTMENT","DEPARTMENT_MANAGERS","ROLE","USER"]},"subjectId":{"type":"string","format":"uuid"},"role":{"type":"string"}}},"CreateInvitationRequest":{"type":"object","properties":{"email":{"type":"string"},"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]},"departmentId":{"type":"string","format":"uuid"}}},"AdminInvitationResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]},"departmentId":{"type":"string","format":"uuid"},"status":{"type":"string"},"invitedAt":{"type":"string","format":"date-time"},"acceptedAt":{"type":"string","format":"date-time"},"acceptedAppUserId":{"type":"string","format":"uuid"}}},"AdminConnectorProbeResponse":{"type":"object","properties":{"authenticated":{"type":"boolean"},"connectionKey":{"type":"string"},"accountName":{"type":"string"},"identityName":{"type":"string"},"canReadContent":{"type":"boolean"},"errorCode":{"type":"string"}}},"CreateGatewayRequest":{"type":"object","properties":{"gatewayKey":{"type":"string"},"displayName":{"type":"string"},"preset":{"type":"string","enum":["OPENAI","ANTHROPIC","NINE_ROUTER","OPENROUTER","LITELLM","OLLAMA","OPENAI_COMPATIBLE"]},"category":{"type":"string","enum":["DIRECT_PROVIDER","GATEWAY_ROUTER","SELF_HOSTED_CUSTOM"]},"protocol":{"type":"string","enum":["OPENAI_COMPATIBLE","ANTHROPIC_MESSAGES"]},"baseUrl":{"type":"string"},"requestTimeoutSeconds":{"type":"integer","format":"int32"},"supportsOpenAiReasoningEffort":{"type":"boolean"},"credential":{"type":"string"}}},"ModelRef":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"}}},"ProbeResponse":{"type":"object","properties":{"authenticated":{"type":"boolean"},"models":{"type":"array","items":{"$ref":"#/components/schemas/ModelRef"}},"errorCode":{"type":"string"}}},"TestGatewayRequest":{"type":"object","properties":{"preset":{"type":"string","enum":["OPENAI","ANTHROPIC","NINE_ROUTER","OPENROUTER","LITELLM","OLLAMA","OPENAI_COMPATIBLE"]},"protocol":{"type":"string","enum":["OPENAI_COMPATIBLE","ANTHROPIC_MESSAGES"]},"baseUrl":{"type":"string"},"requestTimeoutSeconds":{"type":"integer","format":"int32"},"credential":{"type":"string"}}},"ExplainAccessRequest":{"type":"object","properties":{"userId":{"type":"string","format":"uuid"},"permission":{"type":"string"},"resourceType":{"type":"string"},"resourceId":{"type":"string","format":"uuid"}}},"AccessBlockResponse":{"type":"object","properties":{"branch":{"type":"string"},"kind":{"type":"string"},"detail":{"type":"string"}}},"AccessStepResponse":{"type":"object","properties":{"object":{"type":"string"},"relation":{"type":"string"},"kind":{"type":"string"}}},"AclProvenanceResponse":{"type":"object","properties":{"authority":{"type":"string"},"origin":{"type":"string"},"generation":{"type":"integer","format":"int64"},"capturedAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean"}}},"ExplainAccessResponse":{"type":"object","properties":{"state":{"type":"string","enum":["ALLOWED","DENIED","UNKNOWN"]},"reasonCode":{"type":"string"},"path":{"type":"array","items":{"$ref":"#/components/schemas/AccessStepResponse"}},"blockedBy":{"type":"array","items":{"$ref":"#/components/schemas/AccessBlockResponse"}},"provenance":{"$ref":"#/components/schemas/AclProvenanceResponse"},"evaluationKind":{"type":"string"},"relationshipState":{"type":"string","enum":["ALLOWED","DENIED","UNKNOWN"]},"relationshipReasonCode":{"type":"string"},"contentPolicyState":{"type":"string","enum":["ALLOWED","DENIED","UNKNOWN"]},"contentPolicyReasonCode":{"type":"string"},"resource":{"$ref":"#/components/schemas/ResourceSummaryResponse"},"policyVersion":{"type":"string"},"evaluatedAt":{"type":"string","format":"date-time"}}},"ResourceSummaryResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string"},"label":{"type":"string"},"contextLabel":{"type":"string"},"classification":{"type":"string"}}},"RenameConversationRequest":{"type":"object","properties":{"title":{"type":"string","maxLength":120,"minLength":0}},"required":["title"]},"UpdateAdminUserRequest":{"type":"object","properties":{"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]},"active":{"type":"boolean"},"departmentId":{"type":["string","null"],"format":"uuid","description":"Omit to keep the current department; send null to clear it"}}},"AdminUserResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string"},"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]},"departmentId":{"type":"string","format":"uuid"},"active":{"type":"boolean"},"signInLinked":{"type":"boolean"},"mappedPrincipalCount":{"type":"integer","format":"int32"}}},"UpdateStateRequest":{"type":"object","properties":{"expectedVersion":{"type":"integer","format":"int64"},"expectedState":{"type":"string","enum":["DISABLED","VALIDATING","ENABLED","READ_ONLY","SUSPENDED"]},"nextState":{"type":"string","enum":["DISABLED","VALIDATING","ENABLED","READ_ONLY","SUSPENDED"]}}},"SourcePageResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SourceResponse"}},"nextCursor":{"type":"string"},"pageSize":{"type":"integer","format":"int32"},"total":{"type":"integer","format":"int64"},"statusCounts":{"$ref":"#/components/schemas/SourceStatusCountsResponse"}}},"SourceStatusCountsResponse":{"type":"object","properties":{"processing":{"type":"integer","format":"int64"},"ready":{"type":"integer","format":"int64"},"attention":{"type":"integer","format":"int64"}}},"StreamingResponseBody":{},"SessionResponse":{"type":"object","properties":{"authenticated":{"type":"boolean"},"name":{"type":"string"},"email":{"type":"string"},"userId":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"departmentId":{"type":"string","format":"uuid"},"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]},"canManageMembers":{"type":"boolean"}}},"CsrfResponse":{"type":"object","properties":{"headerName":{"type":"string"},"parameterName":{"type":"string"},"token":{"type":"string"}}},"DepartmentResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"OrganizationContextResponse":{"type":"object","properties":{"organizationId":{"type":"string","format":"uuid"},"departments":{"type":"array","items":{"$ref":"#/components/schemas/DepartmentResponse"}},"users":{"type":"array","items":{"$ref":"#/components/schemas/UserResponse"}}}},"UserResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"departmentId":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string"},"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]}}},"MeResponse":{"type":"object","properties":{"userId":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string"},"departmentId":{"type":"string","format":"uuid"},"departmentName":{"type":"string"},"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]}}},"KnowledgeEvidenceResponse":{"type":"object","properties":{"citationId":{"type":"string","format":"uuid"},"knowledgeAssetId":{"type":"string","format":"uuid"},"title":{"type":"string"},"content":{"type":"string"},"sourceUri":{"type":"string"},"startPage":{"type":"integer","format":"int32"},"endPage":{"type":"integer","format":"int32"},"heading":{"type":"string"},"relevanceScore":{"type":"number","format":"double"}}},"KnowledgeSearchResponse":{"type":"object","properties":{"requestId":{"type":"string"},"evidence":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeEvidenceResponse"}}}},"KnowledgeCatalogItem":{"type":"object","properties":{"knowledgeAssetId":{"type":"string","format":"uuid"},"knowledgeVersionId":{"type":"string","format":"uuid"},"versionNumber":{"type":"integer","format":"int64"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"title":{"type":"string"},"language":{"type":"string"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","RESTRICTED"]},"contentDigest":{"type":"string"}}},"Entity":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"type":{"type":"string"},"description":{"type":"string"},"citationChunkIds":{"type":"array","items":{"type":"string","format":"uuid"}},"governingEvidence":{"$ref":"#/components/schemas/EvidenceReference"}}},"KnowledgeGraphView":{"type":"object","properties":{"knowledgeSpaceId":{"type":"string","format":"uuid"},"authorizationGeneration":{"type":"integer","format":"int64"},"canCurate":{"type":"boolean"},"entities":{"type":"array","items":{"$ref":"#/components/schemas/Entity"}},"relations":{"type":"array","items":{"$ref":"#/components/schemas/Relation"}},"truncated":{"type":"boolean"}}},"Relation":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"sourceEntityId":{"type":"string","format":"uuid"},"targetEntityId":{"type":"string","format":"uuid"},"type":{"type":"string"},"description":{"type":"string"},"weight":{"type":"number","format":"double"},"keywords":{"type":"array","items":{"type":"string"}},"citationChunkIds":{"type":"array","items":{"type":"string","format":"uuid"}},"governingEvidence":{"$ref":"#/components/schemas/EvidenceReference"}}},"KnowledgeSpaceResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string"},"name":{"type":"string"},"departmentId":{"type":"string","format":"uuid"}}},"CitationEvidenceExcerpt":{"type":"object","properties":{"title":{"type":"string"},"heading":{"type":"string"},"startPage":{"type":"integer","format":"int32"},"endPage":{"type":"integer","format":"int32"},"excerpt":{"type":"string"},"truncated":{"type":"boolean"},"presentationKind":{"type":"string","enum":["PDF","MARKDOWN","PLAIN_TEXT","IMAGE","DOWNLOAD"]}}},"WorkInstructionToolResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"instruction":{"$ref":"#/components/schemas/WorkInstructionView"}}},"AssistantReleaseRef":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"}}},"PromptFormResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"release":{"$ref":"#/components/schemas/AssistantReleaseRef"},"objective":{"type":"string"},"audience":{"type":"string"},"variables":{"type":"array","items":{"$ref":"#/components/schemas/Variable"}},"outputContract":{"type":"object","additionalProperties":{}},"knowledgeRequirements":{"type":"array","items":{"type":"string"}},"knownLimitations":{"type":"string"}}},"Variable":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string","enum":["STRING","INTEGER","NUMBER","BOOLEAN","STRING_LIST"]},"required":{"type":"boolean"},"defaultValue":{},"sensitive":{"type":"boolean"},"pattern":{"type":"string"},"allowedValues":{"type":"array","items":{"type":"string"}}}},"AssetRecommendation":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]},"namespace":{"type":"string"},"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"portfolioState":{"type":"string","enum":["DRAFT_ONLY","ACTIVE","SUNSETTING","RETIRED"]},"ownerUserId":{"type":"string","format":"uuid"},"sharingState":{"type":"string","enum":["PRIVATE","SHARED","ORGANIZATION"]},"releaseId":{"type":"string","format":"uuid"},"versionLabel":{"type":"string"},"releaseDigest":{"type":"string"},"availability":{"type":"string","enum":["AVAILABLE","DEPRECATED","WITHDRAWN"]},"releasedAt":{"type":"string","format":"date-time"}}},"RecommendationResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"recommendations":{"type":"array","items":{"$ref":"#/components/schemas/AssetRecommendation"}}}},"AssistantStarterPrompt":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"prompt":{"type":"string"}}},"AssistantModelOptionResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"gatewayLabel":{"type":"string"},"provider":{"type":"string"},"modelId":{"type":"string"},"displayName":{"type":"string"},"defaultChoice":{"type":"boolean"}}},"AssistantModelOptionsResponse":{"type":"object","properties":{"selectedModelActivationId":{"type":"string","format":"uuid"},"options":{"type":"array","items":{"$ref":"#/components/schemas/AssistantModelOptionResponse"}}}},"AssistantCitationResponse":{"type":"object","properties":{"citationNumber":{"type":"integer","format":"int32"},"sourceId":{"type":"string"},"title":{"type":"string"},"heading":{"type":"string"},"startPage":{"type":"integer","format":"int32"},"endPage":{"type":"integer","format":"int32"},"excerptUrl":{"type":"string"},"contentUrl":{"type":"string"}}},"AssistantConversationSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"lastActivityAt":{"type":"string","format":"date-time"},"messageCount":{"type":"integer","format":"int64"}}},"AssistantConversationMessageView":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"role":{"type":"string","enum":["USER","ASSISTANT"]},"content":{"type":"string"},"sequence":{"type":"integer","format":"int64"},"occurredAt":{"type":"string","format":"date-time"},"feedback":{"type":"string","enum":["HELPFUL","NOT_HELPFUL"]}}},"AssetSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]},"namespace":{"type":"string"},"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"portfolioState":{"type":"string","enum":["DRAFT_ONLY","ACTIVE","SUNSETTING","RETIRED"]},"ownerUserId":{"type":"string","format":"uuid"},"sharingState":{"type":"string","enum":["PRIVATE","SHARED","ORGANIZATION"]},"updatedAt":{"type":"string","format":"date-time"}}},"File":{"type":"object","properties":{"path":{"type":"string"},"size":{"type":"integer","format":"int64"},"sha256":{"type":"string"}}},"SkillInstallManifest":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"namespace":{"type":"string"},"slug":{"type":"string"},"coordinate":{"type":"string"},"version":{"type":"string"},"publicationMode":{"type":"string","enum":["REVIEWED","DIRECT"]},"title":{"type":"string"},"description":{"type":"string"},"releaseDigest":{"type":"string"},"packageDigest":{"type":"string"},"packageLength":{"type":"integer","format":"int64"},"mediaType":{"type":"string"},"license":{"type":"string"},"compatibility":{"type":"string"},"allowedTools":{"type":"string"},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"files":{"type":"array","items":{"$ref":"#/components/schemas/File"}}}},"CapabilityPackDefinition":{"type":"object","properties":{"packAssetId":{"type":"string","format":"uuid"},"packReleaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"},"title":{"type":"string"},"versionLabel":{"type":"string"},"purpose":{"type":"string","enum":["ROLE_ONBOARDING","HANDOVER","ROLE_ENABLEMENT"]},"audience":{"type":"string"},"prerequisites":{"type":"array","items":{"type":"string"}},"expectedOutcome":{"type":"string"},"completionCriteria":{"type":"array","items":{"type":"string"}},"reviewDate":{"type":"string"},"owner":{"type":"string"},"accessGap":{"type":"boolean"},"items":{"type":"array","items":{"$ref":"#/components/schemas/Item"}}}},"AssetReleasedView":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]},"namespace":{"type":"string"},"slug":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"portfolioState":{"type":"string","enum":["DRAFT_ONLY","ACTIVE","SUNSETTING","RETIRED"]},"ownerUserId":{"type":"string","format":"uuid"},"sharingState":{"type":"string","enum":["PRIVATE","SHARED","ORGANIZATION"]},"releases":{"type":"array","items":{"$ref":"#/components/schemas/Release"}}}},"AssetGovernanceActions":{"type":"object","properties":{"canEdit":{"type":"boolean"},"canSubmitReview":{"type":"boolean"},"canReview":{"type":"boolean"},"canApprove":{"type":"boolean"},"canRequestChanges":{"type":"boolean"},"canReject":{"type":"boolean"},"canCancel":{"type":"boolean"},"canPublish":{"type":"boolean"},"canPublishSkill":{"type":"boolean"},"canWithdraw":{"type":"boolean"},"canManageSharing":{"type":"boolean"},"canTransferOwnership":{"type":"boolean"},"canPublishDirect":{"type":"boolean"},"canOpenGovernance":{"type":"boolean"}}},"ConnectionOption":{"type":"object","properties":{"key":{"type":"string"}}},"AssetSummaryPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AssetSummary"}},"total":{"type":"integer","format":"int64"},"page":{"type":"integer","format":"int32"},"pageSize":{"type":"integer","format":"int32"},"totalPages":{"type":"integer","format":"int32"},"sort":{"type":"string","enum":["RECENTLY_UPDATED","NAME"]}}},"AssetRecommendationPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AssetRecommendation"}},"total":{"type":"integer","format":"int64"},"page":{"type":"integer","format":"int32"},"pageSize":{"type":"integer","format":"int32"},"totalPages":{"type":"integer","format":"int32"},"sort":{"type":"string","enum":["RECENTLY_RELEASED","NAME"]}}},"AssetDeliveryRelease":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]},"namespace":{"type":"string"},"slug":{"type":"string"},"versionLabel":{"type":"string"},"publicationMode":{"type":"string","enum":["REVIEWED","DIRECT"]},"title":{"type":"string"},"summary":{"type":"string"},"classification":{"type":"string"},"schemaVersion":{"type":"string"},"payload":{"type":"string"},"digest":{"type":"string"},"availability":{"type":"string","enum":["AVAILABLE","DEPRECATED","WITHDRAWN"]},"releasedAt":{"type":"string","format":"date-time"}}},"AssetRelationResolution":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"accessGap":{"type":"boolean"},"relations":{"type":"array","items":{"$ref":"#/components/schemas/Relation"}}}},"EffectivePermissionResponse":{"type":"object","properties":{"userId":{"type":"string","format":"uuid"},"permissions":{"type":"object","additionalProperties":{"type":"string","enum":["ALLOWED","DENIED","UNKNOWN"]}},"evaluatedAt":{"type":"string","format":"date-time"}}},"AdminSourceGroupMemberResponse":{"type":"object","properties":{"principalId":{"type":"string","format":"uuid"},"nativePrincipalId":{"type":"string"},"observedDisplayName":{"type":"string"},"observedEmail":{"type":"string"},"appUserId":{"type":"string","format":"uuid"},"appUserName":{"type":"string"}}},"AdminSourceGroupResponse":{"type":"object","properties":{"principalId":{"type":"string","format":"uuid"},"sourceSystem":{"type":"string"},"sourceConnectionKey":{"type":"string"},"nativePrincipalId":{"type":"string"},"observedDisplayName":{"type":"string"},"membershipSnapshotId":{"type":"string","format":"uuid"},"membershipGeneration":{"type":"integer","format":"int64"},"sealedAt":{"type":"string","format":"date-time"},"members":{"type":"array","items":{"$ref":"#/components/schemas/AdminSourceGroupMemberResponse"}}}},"AdminRoleListResponse":{"type":"object","properties":{"roles":{"type":"array","items":{"$ref":"#/components/schemas/AdminRoleResponse"}},"complete":{"type":"boolean"},"policyVersion":{"type":"string"}}},"AdminRoleResponse":{"type":"object","properties":{"role":{"type":"string"},"assignees":{"type":"array","items":{"type":"string"}}}},"CredentialResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"publicTokenId":{"type":"string"},"verifierKeyVersion":{"type":"integer","format":"int32"},"usersScope":{"type":"boolean"},"groupsScope":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"},"overlapEndsAt":{"type":"string","format":"date-time"},"revokedAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"KnowledgeSpaceGrantOptionResponse":{"type":"object","properties":{"relation":{"type":"string"},"kinds":{"type":"array","items":{"type":"string","enum":["ORGANIZATION","DEPARTMENT","DEPARTMENT_MANAGERS","ROLE","USER"]}},"roles":{"type":"array","items":{"type":"string"}}}},"AdminConnectorScopeResponse":{"type":"object","properties":{"key":{"type":"string"},"displayName":{"type":"string"},"reachable":{"type":"boolean"},"admissible":{"type":"boolean"},"instruction":{"type":"string"}}},"AdminComponentCheckpointResponse":{"type":"object","properties":{"component":{"type":"string"},"observedCursor":{"type":"string"},"captureStatus":{"type":"string"},"incompleteReason":{"type":"string"},"observedAt":{"type":"string","format":"date-time"},"lastSuccessfulCursor":{"type":"string"},"lastSuccessfulAt":{"type":"string","format":"date-time"}}},"AdminConnectionActivityResponse":{"type":"object","properties":{"sourceSystem":{"type":"string"},"sourceConnectionKey":{"type":"string"},"objectsTotal":{"type":"integer","format":"int64"},"objectsActive":{"type":"integer","format":"int64"},"objectsArchived":{"type":"integer","format":"int64"},"lastObjectAt":{"type":"string","format":"date-time"},"lastCrawlAt":{"type":"string","format":"date-time"},"componentCheckpoints":{"type":"array","items":{"$ref":"#/components/schemas/AdminComponentCheckpointResponse"}},"recentAttempts":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrawlAttemptResponse"}}}},"AdminCrawlAttemptResponse":{"type":"object","properties":{"outcome":{"type":"string"},"objectsMaterialized":{"type":"integer","format":"int32"},"objectsRotated":{"type":"integer","format":"int32"},"objectsRematerialized":{"type":"integer","format":"int32"},"objectsRetired":{"type":"integer","format":"int32"},"objectsFailed":{"type":"integer","format":"int32"},"errorCode":{"type":"string"},"errorMessage":{"type":"string"},"attemptedAt":{"type":"string","format":"date-time"}}},"AdminConnectorSourceResponse":{"type":"object","properties":{"sourceSystem":{"type":"string"},"displayName":{"type":"string"}}},"ProviderPresetResponse":{"type":"object","properties":{"preset":{"type":"string","enum":["OPENAI","ANTHROPIC","NINE_ROUTER","OPENROUTER","LITELLM","OLLAMA","OPENAI_COMPATIBLE"]},"displayName":{"type":"string"},"vendorName":{"type":"string"},"category":{"type":"string","enum":["DIRECT_PROVIDER","GATEWAY_ROUTER","SELF_HOSTED_CUSTOM"]},"protocol":{"type":"string","enum":["OPENAI_COMPATIBLE","ANTHROPIC_MESSAGES"]},"defaultBaseUrl":{"type":"string"},"baseUrlEditable":{"type":"boolean"}}},"IndexSettingsResponse":{"type":"object","properties":{"embeddingProvider":{"type":"string"},"embeddingModel":{"type":"string"},"dimensions":{"type":"integer","format":"int32"},"distanceMetric":{"type":"string"},"managementMode":{"type":"string"},"editable":{"type":"boolean"},"lifecycleNote":{"type":"string"}}},"KnowledgeAssetRef":{"type":"object","properties":{"knowledgeAssetId":{"type":"string","format":"uuid"},"knowledgeAssetVersionId":{"type":"string","format":"uuid"},"normalizedRecordId":{"type":"string","format":"uuid"},"rawSourceObjectId":{"type":"string","format":"uuid"},"sourceAclSnapshotId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["PENDING","ACTIVE","RETIRED"]}}}}}} \ No newline at end of file +{"openapi":"3.1.0","info":{"title":"OpenAPI definition","version":"v0"},"servers":[{"url":"http://localhost"}],"paths":{"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/pack/{itemKey}":{"put":{"tags":["assistant-asset-tool-controller"],"summary":"Update actor-derived Pack progress after explicit confirmation","operationId":"updateAssistantPackProgress","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"itemKey","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackProgressRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PackToolResult"}}}}}}},"/api/assistant/messages/{messageId}/feedback":{"put":{"tags":["assistant-controller"],"summary":"Create or replace feedback on an owned Assistant answer","operationId":"setAssistantAnswerFeedback","parameters":[{"name":"messageId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnswerFeedbackRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssistantAnswerFeedbackView"}}}}}},"delete":{"tags":["assistant-controller"],"summary":"Remove feedback from an owned Assistant answer","operationId":"deleteAssistantAnswerFeedback","parameters":[{"name":"messageId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"No Content"}}}},"/api/assistant/conversations/{conversationId}/model":{"put":{"tags":["assistant-controller"],"summary":"Select one allowed model for an owned Assistant conversation","operationId":"selectAssistantConversationModel","parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelectAssistantModelRequest"}}},"required":true},"responses":{"204":{"description":"No Content"}}}},"/api/assets/{assetId}/skill-draft":{"put":{"tags":["asset-registry-controller"],"summary":"Replace the package of one mutable Skill draft","operationId":"replaceSkillDraftPackage","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"expectedLockVersion","in":"query","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/skill-activation":{"get":{"tags":["asset-registry-controller"],"summary":"Read the current user's opt-in state for one visible Skill","operationId":"getSkillActivation","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SkillActivationView"}}}}}},"put":{"tags":["asset-registry-controller"],"summary":"Enable or disable one visible Skill for the current user","operationId":"setSkillActivation","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SkillActivationRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SkillActivationView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/pack-progress/{itemKey}":{"put":{"tags":["asset-consumption-controller"],"summary":"Idempotently update actor-derived progress for one accessible Pack item","operationId":"setCapabilityPackProgress","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"itemKey","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackProgressRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PackJourney"}}}}}}},"/api/assets/{assetId}/draft":{"put":{"tags":["asset-registry-controller"],"summary":"Update a mutable Asset draft","operationId":"updateAssetDraft","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAssetDraftRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/admin/source-principals/{principalId}/mapping":{"put":{"tags":["admin-source-access-controller"],"summary":"Confirm a principal maps to an internal user","operationId":"confirmAdminSourceMapping","parameters":[{"name":"principalId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmMappingRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminSourcePrincipalResponse"}}}}}},"delete":{"tags":["admin-source-access-controller"],"summary":"Revoke a principal's active mapping","operationId":"revokeAdminSourceMapping","parameters":[{"name":"principalId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminSourcePrincipalResponse"}}}}}}},"/api/admin/source-connections/identity-trust":{"put":{"tags":["admin-source-access-controller"],"summary":"Record the identity trust for a connection","operationId":"setAdminSourceConnectionTrust","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdentityTrustRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminSourceConnectionResponse"}}}}}}},"/api/admin/connectors/{sourceSystem}/{connectionKey}":{"put":{"tags":["admin-connector-controller"],"summary":"Record how a connection is crawled","operationId":"configureAdminConnection","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigureConnectionRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminConnectionResponse"}}}}}}},"/api/admin/connectors/{sourceSystem}/{connectionKey}/credential":{"put":{"tags":["admin-connector-controller"],"summary":"Store a credential for a connection","operationId":"setAdminConnectionCredential","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorCredentialRequest"}}},"required":true},"responses":{"204":{"description":"No Content"}}},"delete":{"tags":["admin-connector-controller"],"summary":"Forget a connection's stored credential","operationId":"forgetAdminConnectionCredential","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"}}}},"/api/admin/ai/routes/{workload}":{"put":{"tags":["admin-ai-model-controller"],"summary":"Set an editable organization AI route","operationId":"setAdminAiRoute","parameters":[{"name":"workload","in":"path","required":true,"schema":{"type":"string","enum":["ASSISTANT_CHAT","PROMPT_EXECUTION","KEYWORD_PLANNING","GRAPH_EXTRACTION","QUERY_EMBEDDING","DOCUMENT_EMBEDDING"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetRouteRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RouteResponse"}}}}}},"delete":{"tags":["admin-ai-model-controller"],"summary":"Restore the deployment default for an editable AI route","operationId":"clearAdminAiRoute","parameters":[{"name":"workload","in":"path","required":true,"schema":{"type":"string","enum":["ASSISTANT_CHAT","PROMPT_EXECUTION","KEYWORD_PLANNING","GRAPH_EXTRACTION","QUERY_EMBEDDING","DOCUMENT_EMBEDDING"]}}],"responses":{"204":{"description":"No Content"}}}},"/api/admin/ai/gateways/{profileId}":{"put":{"tags":["admin-ai-model-controller"],"summary":"Update an organization AI gateway","operationId":"updateAdminAiGateway","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGatewayRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GatewayResponse"}}}}}},"delete":{"tags":["admin-ai-model-controller"],"summary":"Disable an unused organization AI gateway","operationId":"disableAdminAiGateway","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"No Content"}}}},"/api/admin/ai/gateways/{profileId}/credential":{"put":{"tags":["admin-ai-model-controller"],"summary":"Set or rotate an organization AI gateway credential","operationId":"setAdminAiGatewayCredential","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatewayCredentialRequest"}}},"required":true},"responses":{"204":{"description":"No Content"}}}},"/api/admin/ai/gateways/{profileId}/assistant-models":{"put":{"tags":["admin-ai-model-controller"],"summary":"Replace additional chat models allowed on the active Assistant gateway","operationId":"replaceAdminAssistantModels","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssistantModelsRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssistantModelResponse"}}}}}}}},"/api/sources":{"get":{"tags":["source-controller"],"summary":"List sources visible to the current user","operationId":"listSources","parameters":[{"name":"knowledgeSpaceId","in":"query","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"classification","in":"query","required":false,"schema":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","RESTRICTED"]}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["PROCESSING","READY","ATTENTION"]}},{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":25}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SourcePageResponse"}}}}}},"post":{"tags":["source-controller"],"summary":"Upload a source for asynchronous ingestion","operationId":"uploadSource","parameters":[{"name":"classification","in":"query","required":false,"schema":{"type":"string","default":"CONFIDENTIAL","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","RESTRICTED"]}},{"name":"knowledgeSpaceId","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SourceResponse"}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/curations/suppressions":{"post":{"tags":["knowledge-graph-management-controller"],"summary":"Delete an effective graph identity without deleting evidence","operationId":"suppressGraphIdentity","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressIdentityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CuratedEntity"},{"$ref":"#/components/schemas/CuratedRelation"},{"$ref":"#/components/schemas/IdentityAlias"},{"$ref":"#/components/schemas/IdentitySuppression"}]}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/curations/relations":{"post":{"tags":["knowledge-graph-management-controller"],"summary":"Create or edit a governed graph relation","operationId":"curateGraphRelation","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CurateRelationRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CuratedEntity"},{"$ref":"#/components/schemas/CuratedRelation"},{"$ref":"#/components/schemas/IdentityAlias"},{"$ref":"#/components/schemas/IdentitySuppression"}]}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/curations/entities":{"post":{"tags":["knowledge-graph-management-controller"],"summary":"Create or edit a governed graph entity","operationId":"curateGraphEntity","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CurateEntityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CuratedEntity"},{"$ref":"#/components/schemas/CuratedRelation"},{"$ref":"#/components/schemas/IdentityAlias"},{"$ref":"#/components/schemas/IdentitySuppression"}]}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/curations/aliases":{"post":{"tags":["knowledge-graph-management-controller"],"summary":"Merge graph identities through a reversible alias","operationId":"mergeGraphIdentity","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AliasIdentityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CuratedEntity"},{"$ref":"#/components/schemas/CuratedRelation"},{"$ref":"#/components/schemas/IdentityAlias"},{"$ref":"#/components/schemas/IdentitySuppression"}]}}}}}}},"/api/knowledge-assets/{knowledgeAssetId}/graph-index":{"post":{"tags":["knowledge-asset-lifecycle-controller"],"summary":"Ensure graph indexing uses the current processing profile","operationId":"ensureKnowledgeAssetGraphIndex","parameters":[{"name":"knowledgeAssetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Accepted","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GraphIndexJobView"}}}}}}},"/api/knowledge-assets/graph-jobs/{jobId}/resume":{"post":{"tags":["knowledge-asset-lifecycle-controller"],"summary":"Resume unfinished graph indexing","operationId":"resumeGraphIndexJob","parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Accepted","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GraphIndexJobView"}}}}}}},"/api/knowledge-assets/graph-jobs/{jobId}/cancel":{"post":{"tags":["knowledge-asset-lifecycle-controller"],"summary":"Cancel queued or in-flight graph indexing","operationId":"cancelGraphIndexJob","parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GraphIndexJobView"}}}}}}},"/api/assistant/tools/knowledge-search":{"post":{"tags":["assistant-asset-tool-controller"],"summary":"Search canonical permission-aware Knowledge and return citation references","operationId":"searchAssistantKnowledge","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeSearchRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/KnowledgeResult"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/prompt-run":{"post":{"tags":["assistant-asset-tool-controller"],"summary":"Run an exact Prompt release after explicit external-provider confirmation","operationId":"runAssistantPrompt","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptRunRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptRunToolResult"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/prompt-render":{"post":{"tags":["assistant-asset-tool-controller"],"summary":"Render an exact Prompt release after variable validation","operationId":"renderAssistantPrompt","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptRenderRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptRenderToolResult"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/pack":{"get":{"tags":["assistant-asset-tool-controller"],"summary":"Read an actor-scoped exact Pack journey","operationId":"readAssistantPack","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PackToolResult"}}}}}},"post":{"tags":["assistant-asset-tool-controller"],"summary":"Start an exact Pack after explicit state-change confirmation","operationId":"startAssistantPack","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmedActionRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PackToolResult"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/fork":{"post":{"tags":["assistant-asset-tool-controller"],"summary":"Fork an exact release after explicit draft-creation confirmation","operationId":"forkAssistantAssetRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForkRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ForkResult"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/feedback":{"post":{"tags":["assistant-asset-tool-controller"],"summary":"Submit feedback against an exact release after explicit confirmation","operationId":"submitAssistantAssetFeedback","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/FeedbackResult"}}}}}}},"/api/assistant/evidence":{"post":{"tags":["assistant-controller"],"summary":"Upload one governed file and bind it to an Assistant conversation","operationId":"uploadAssistantEvidence","parameters":[{"name":"conversationId","in":"query","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"knowledgeSpaceId","in":"query","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"classification","in":"query","required":false,"schema":{"type":"string","default":"CONFIDENTIAL","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","RESTRICTED"]}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssistantEvidenceBindingView"}}}}}}},"/api/assistant/chat":{"post":{"tags":["assistant-controller"],"summary":"Stream an answer from permission-verified knowledge","operationId":"streamAssistantChat","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssistantChatRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ServerSentEventString"}}}}}}}},"/api/assets":{"get":{"tags":["asset-registry-controller"],"summary":"List released or authoring Assets visible to the actor","operationId":"listAssets","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetSummary"}}}}}}},"post":{"tags":["asset-registry-controller"],"summary":"Create an Asset and its mutable draft","operationId":"createAsset","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAssetRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/withdrawal":{"post":{"tags":["asset-registry-controller"],"summary":"Withdraw every Release and retire an owned Asset","operationId":"withdrawAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetAvailabilityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/submissions":{"post":{"tags":["asset-registry-controller"],"summary":"Submit an immutable Asset revision for review","operationId":"submitAssetRevision","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitAssetRevisionRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/skill-releases":{"post":{"tags":["asset-registry-controller"],"summary":"Publish a Skill draft as an immutable release","operationId":"publishSkillRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishSkillReleaseRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/shares":{"post":{"tags":["asset-registry-controller"],"summary":"Share an Asset with one Viewer or Editor audience","operationId":"shareAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareAssetRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}},"delete":{"tags":["asset-registry-controller"],"summary":"Remove one Viewer or Editor share","operationId":"unshareAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareAssetRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/role-assignments":{"post":{"tags":["asset-registry-controller"],"summary":"Assign an accountable role on an Asset","operationId":"assignAssetRole","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignAssetRoleRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/reviews/{reviewCaseId}/decisions":{"post":{"tags":["asset-registry-controller"],"summary":"Record a decision against an exact revision digest","operationId":"decideAssetReview","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"reviewCaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetReviewDecisionRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/releases":{"post":{"tags":["asset-registry-controller"],"summary":"Publish an approved immutable Asset revision","operationId":"publishAssetRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishAssetReleaseRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/work-instruction/acknowledgement":{"post":{"tags":["asset-consumption-controller"],"summary":"Idempotently acknowledge an exact Work Instruction release","operationId":"acknowledgeWorkInstruction","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/WorkInstructionView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/withdrawal":{"post":{"tags":["asset-registry-controller"],"summary":"Withdraw an Asset release from new use","operationId":"withdrawAssetRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetAvailabilityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/prompt/runs":{"post":{"tags":["asset-consumption-controller"],"summary":"Run an exact Prompt release through the provider-neutral AI gateway","operationId":"runPromptRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptRunRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptRunResult"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/prompt/render":{"post":{"tags":["asset-consumption-controller"],"summary":"Deterministically render an exact authorized Prompt release","operationId":"renderPromptRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptVariablesRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptRenderResult"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/prompt/evaluations":{"post":{"tags":["asset-consumption-controller"],"summary":"Run the bounded evaluation cases pinned in a Prompt release","operationId":"evaluatePromptRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptEvaluationResult"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/pack-assignment":{"post":{"tags":["asset-consumption-controller"],"summary":"Start or resume an exact authorized Capability Pack release","operationId":"startCapabilityPack","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PackJourney"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/forks":{"post":{"tags":["asset-consumption-controller"],"summary":"Fork an exact authorized release into a new mutable draft","operationId":"forkAssetRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForkReleaseRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/deprecation":{"post":{"tags":["asset-registry-controller"],"summary":"Deprecate an Asset release","operationId":"deprecateAssetRelease","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetAvailabilityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/prompt/evaluation-comparisons":{"post":{"tags":["asset-consumption-controller"],"summary":"Compare bounded evaluation results for two exact Prompt releases","operationId":"comparePromptReleases","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptComparisonRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptEvaluationComparison"}}}}}}},"/api/assets/{assetId}/ownership-transfer":{"post":{"tags":["asset-registry-controller"],"summary":"Transfer Asset ownership to another user","operationId":"transferAssetOwnership","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferAssetOwnershipRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/ownership-recovery":{"post":{"tags":["asset-registry-controller"],"summary":"Recover an owner-vacant Asset as an administrator","operationId":"recoverAssetOwnership","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferAssetOwnershipRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/emergency-withdrawal":{"post":{"tags":["asset-registry-controller"],"summary":"Emergency-withdraw every Release from an Asset as an administrator","operationId":"emergencyWithdrawAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetAvailabilityRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/direct-releases":{"post":{"tags":["asset-registry-controller"],"summary":"Publish the current working copy as an immutable Release","operationId":"publishAssetDraft","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishSkillReleaseRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/skills":{"post":{"tags":["asset-registry-controller"],"summary":"Validate and import one Agent Skill package","operationId":"importSkillPackage","parameters":[{"name":"namespace","in":"query","required":true,"schema":{"type":"string"}},{"name":"knowledgeSpaceId","in":"query","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"classification","in":"query","required":false,"schema":{"type":"string","default":"INTERNAL","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","RESTRICTED"]}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/skills/inspections":{"post":{"tags":["asset-registry-controller"],"summary":"Validate and inspect one Agent Skill package without storing it","operationId":"inspectSkillPackage","requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SkillPackageInspection"}}}}}}},"/api/assets/skills/github/preview":{"post":{"tags":["asset-registry-controller"],"summary":"Discover and validate Skills at one GitHub repository revision","operationId":"previewGitHubSkills","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GitHubSkillSourceRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/Preview"}}}}}}},"/api/assets/skills/github/import":{"post":{"tags":["asset-registry-controller"],"summary":"Import selected Skills from an exact GitHub commit","operationId":"importGitHubSkills","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GitHubSkillImportRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ImportResult"}}}}}}},"/api/asset-delivery/{assetId}/releases/{releaseId}/prompt-render":{"post":{"tags":["asset-delivery-controller"],"summary":"Deterministically render an exact authorized Prompt release","operationId":"renderReleasedPrompt","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptVariablesRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptRenderResult"}}}}}}},"/api/admin/roles/{role}/members":{"post":{"tags":["admin-role-controller"],"summary":"Assign a user to a role","operationId":"assignAdminRole","parameters":[{"name":"role","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignRoleRequest"}}},"required":true},"responses":{"204":{"description":"No Content"}}}},"/api/admin/provisioning/connections":{"get":{"tags":["admin-provisioning-controller"],"summary":"List SCIM provisioning connections","operationId":"listProvisioningConnections","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ConnectionResponse"}}}}}}},"post":{"tags":["admin-provisioning-controller"],"summary":"Create a disabled SCIM connection","operationId":"createProvisioningConnection","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConnectionRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ConnectionResponse"}}}}}}},"/api/admin/provisioning/connections/{connectionId}/credentials":{"get":{"tags":["admin-provisioning-controller"],"summary":"List SCIM credential metadata","operationId":"listProvisioningCredentials","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CredentialResponse"}}}}}}},"post":{"tags":["admin-provisioning-controller"],"summary":"Issue a one-time SCIM credential","operationId":"issueProvisioningCredential","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueCredentialRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IssuedCredentialResponse"}}}}}}},"/api/admin/provisioning/connections/{connectionId}/credentials/{credentialId}/rotate":{"post":{"tags":["admin-provisioning-controller"],"summary":"Rotate a SCIM credential with bounded overlap","operationId":"rotateProvisioningCredential","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"credentialId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueCredentialRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IssuedCredentialResponse"}}}}}}},"/api/admin/knowledge-spaces":{"get":{"tags":["admin-knowledge-space-controller"],"summary":"List Knowledge Spaces and the grants stored against them","operationId":"listAdminKnowledgeSpaces","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminKnowledgeSpaceResponse"}}}}}}},"post":{"tags":["admin-knowledge-space-controller"],"summary":"Create a Knowledge Space","operationId":"createAdminKnowledgeSpace","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKnowledgeSpaceRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminKnowledgeSpaceResponse"}}}}}}},"/api/admin/knowledge-spaces/{knowledgeSpaceId}/grants":{"post":{"tags":["admin-knowledge-space-controller"],"summary":"Grant a subject access to a Knowledge Space","operationId":"grantAdminKnowledgeSpaceAccess","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantKnowledgeSpaceAccessRequest"}}},"required":true},"responses":{"204":{"description":"No Content"}}},"delete":{"tags":["admin-knowledge-space-controller"],"summary":"Revoke a subject's access to a Knowledge Space","operationId":"revokeAdminKnowledgeSpaceAccess","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"relation","in":"query","required":true,"schema":{"type":"string"}},{"name":"kind","in":"query","required":true,"schema":{"type":"string","enum":["ORGANIZATION","DEPARTMENT","DEPARTMENT_MANAGERS","ROLE","USER"]}},{"name":"subjectId","in":"query","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"role","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"}}}},"/api/admin/invitations":{"get":{"tags":["admin-invitation-controller"],"summary":"List invited addresses and their status","operationId":"listAdminInvitations","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminInvitationResponse"}}}}}}},"post":{"tags":["admin-invitation-controller"],"summary":"Expect an address to sign in","operationId":"createAdminInvitation","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvitationRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminInvitationResponse"}}}}}}},"/api/admin/connectors/{sourceSystem}/{connectionKey}/test":{"post":{"tags":["admin-connector-controller"],"summary":"Check a connection's stored credential","operationId":"testAdminConnection","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminConnectorProbeResponse"}}}}}}},"/api/admin/connectors/{sourceSystem}/{connectionKey}/crawl":{"post":{"tags":["admin-connector-controller"],"summary":"Ask for a content crawl on the next poll","operationId":"requestAdminConnectionCrawl","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Accepted"}}}},"/api/admin/connectors/{sourceSystem}/test":{"post":{"tags":["admin-connector-controller"],"summary":"Check a credential without storing it","operationId":"testAdminConnectorCredential","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorCredentialRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminConnectorProbeResponse"}}}}}}},"/api/admin/ai/gateways":{"get":{"tags":["admin-ai-model-controller"],"summary":"List organization AI gateway profiles","operationId":"listAdminAiGateways","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GatewayResponse"}}}}}}},"post":{"tags":["admin-ai-model-controller"],"summary":"Connect an organization AI gateway","operationId":"createAdminAiGateway","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGatewayRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GatewayResponse"}}}}}}},"/api/admin/ai/gateways/{profileId}/test":{"post":{"tags":["admin-ai-model-controller"],"summary":"Test a stored organization AI gateway","operationId":"testStoredAdminAiGateway","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProbeResponse"}}}}}}},"/api/admin/ai/gateways/test":{"post":{"tags":["admin-ai-model-controller"],"summary":"Test an AI gateway credential without storing it","operationId":"testAdminAiGateway","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestGatewayRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProbeResponse"}}}}}}},"/api/admin/access/explain":{"post":{"tags":["admin-permission-controller"],"summary":"Answer whether a user holds a permission on one resource, and by which derivation","operationId":"explainAdminAccess","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExplainAccessRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ExplainAccessResponse"}}}}}}},"/api/assistant/conversations/{conversationId}":{"delete":{"tags":["assistant-controller"],"summary":"Delete the current actor's conversation transcript","operationId":"deleteAssistantConversation","parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"No Content"}}},"patch":{"tags":["assistant-controller"],"summary":"Rename the current actor's conversation","operationId":"renameAssistantConversation","parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameConversationRequest"}}},"required":true},"responses":{"204":{"description":"No Content"}}}},"/api/admin/users/{userId}":{"patch":{"tags":["admin-user-controller"],"summary":"Change a user's clearance, department, or activation","operationId":"updateAdminUser","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAdminUserRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminUserResponse"}}}}}}},"/api/admin/provisioning/connections/{connectionId}/state":{"patch":{"tags":["admin-provisioning-controller"],"summary":"Compare-and-set SCIM connection state","operationId":"updateProvisioningConnectionState","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStateRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ConnectionResponse"}}}}}}},"/api/sources/{sourceId}/content":{"get":{"tags":["source-content-controller"],"summary":"Stream permission-verified current source evidence","operationId":"readSourceContent","parameters":[{"name":"sourceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StreamingResponseBody"}}}}}}},"/api/session":{"get":{"tags":["browser-session-controller"],"summary":"Read the current browser session","operationId":"getBrowserSession","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}}}}},"/api/session/csrf":{"get":{"tags":["browser-session-controller"],"summary":"Issue a CSRF token for browser mutations","operationId":"getBrowserCsrfToken","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CsrfResponse"}}}}}}},"/api/organization/context":{"get":{"tags":["organization-context-controller"],"operationId":"context","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/OrganizationContextResponse"}}}}}}},"/api/me":{"get":{"tags":["me-controller"],"summary":"Read the current user's governed profile","operationId":"getMe","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}}}}},"/api/knowledge/search":{"get":{"tags":["knowledge-search-controller"],"summary":"Search permission-verified knowledge evidence","operationId":"searchKnowledge","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/KnowledgeSearchResponse"}}}}}}},"/api/knowledge/catalog":{"get":{"tags":["knowledge-catalog-controller"],"summary":"List current permission-verified Knowledge versions for composition","operationId":"listKnowledgeCatalog","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeCatalogItem"}}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/export":{"get":{"tags":["knowledge-graph-management-controller"],"summary":"Export only graph evidence visible to the current user","operationId":"exportKnowledgeGraph","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","default":"JSON","enum":["JSON","CSV","MARKDOWN","TEXT"]}},{"name":"X-Request-Id","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string"}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/explorer":{"get":{"tags":["knowledge-graph-explorer-controller"],"summary":"Read a bounded permission-filtered graph view","operationId":"exploreKnowledgeGraph","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"entityLimit","in":"query","required":false,"schema":{"type":"integer","format":"int32"}},{"name":"maxDepth","in":"query","required":false,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/KnowledgeGraphView"}}}}}}},"/api/knowledge-spaces/visible":{"get":{"tags":["knowledge-space-controller"],"summary":"List Knowledge Spaces visible to the current user","operationId":"listVisibleKnowledgeSpaces","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeSpaceResponse"}}}}}}}},"/api/knowledge-spaces/upload-targets":{"get":{"tags":["knowledge-space-controller"],"summary":"List Knowledge Spaces where the current user may add knowledge","operationId":"listKnowledgeSpaceUploadTargets","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeSpaceResponse"}}}}}}}},"/api/knowledge-assets/graph-jobs/{jobId}":{"get":{"tags":["knowledge-asset-lifecycle-controller"],"summary":"Read graph indexing lifecycle status","operationId":"getGraphIndexJob","parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GraphIndexJobView"}}}}}}},"/api/health":{"get":{"tags":["health-controller"],"operationId":"health","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}}}}},"/api/citations/{chunkId}/excerpt":{"get":{"tags":["citation-content-controller"],"summary":"Read a bounded permission-verified citation excerpt","operationId":"readCitationExcerpt","parameters":[{"name":"chunkId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CitationEvidenceExcerpt"}}}}}}},"/api/citations/{chunkId}/content":{"get":{"tags":["citation-content-controller"],"summary":"Stream permission-verified source evidence","operationId":"readCitationContent","parameters":[{"name":"chunkId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StreamingResponseBody"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/work-instruction":{"get":{"tags":["assistant-asset-tool-controller"],"summary":"Guide an exact Work Instruction release","operationId":"followAssistantWorkInstruction","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/WorkInstructionToolResult"}}}}}}},"/api/assistant/tools/assets/{assetId}/releases/{releaseId}/prompt-form":{"get":{"tags":["assistant-asset-tool-controller"],"summary":"Resolve the variables required by an exact Prompt release","operationId":"prepareAssistantPrompt","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PromptFormResult"}}}}}}},"/api/assistant/tools/asset-recommendations":{"get":{"tags":["assistant-asset-tool-controller"],"summary":"Recommend exact usable Asset releases without leaking denied candidates","operationId":"recommendAssistantAssets","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RecommendationResult"}}}}}}},"/api/assistant/starters":{"get":{"tags":["assistant-controller"],"summary":"List supported prompts for starting an Assistant conversation","operationId":"listAssistantStarters","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssistantStarterPrompt"}}}}}}}},"/api/assistant/model-options":{"get":{"tags":["assistant-controller"],"summary":"List server-governed Assistant model choices for the current route","operationId":"getAssistantModelOptions","parameters":[{"name":"conversationId","in":"query","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssistantModelOptionsResponse"}}}}}}},"/api/assistant/messages/{messageId}/citations":{"get":{"tags":["assistant-controller"],"summary":"Hydrate currently authorized citations for one owned Assistant answer","operationId":"getAssistantMessageCitations","parameters":[{"name":"messageId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssistantCitationResponse"}}}}}}}},"/api/assistant/conversations":{"get":{"tags":["assistant-controller"],"summary":"List the current actor's conversations by recent activity","operationId":"listAssistantConversations","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssistantConversationSummary"}}}}}}}},"/api/assistant/conversations/{conversationId}/messages":{"get":{"tags":["assistant-controller"],"summary":"Replay a tenant- and actor-scoped full conversation transcript","operationId":"getAssistantConversationHistory","parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssistantConversationMessageView"}}}}}}}},"/api/assistant/conversations/{conversationId}/evidence":{"get":{"tags":["assistant-controller"],"summary":"List governed file bindings for one owned conversation","operationId":"listAssistantEvidence","parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssistantEvidenceBindingView"}}}}}}}},"/api/assistant/conversations/{conversationId}/evidence/{bindingId}":{"get":{"tags":["assistant-controller"],"summary":"Read the active-engine preparation state of one owned binding","operationId":"getAssistantEvidence","parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"bindingId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssistantEvidenceBindingView"}}}}}}},"/api/assets/{assetId}":{"get":{"tags":["asset-registry-controller"],"summary":"Read an Asset's authoring and governance history","operationId":"getAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/work-instruction":{"get":{"tags":["asset-consumption-controller"],"summary":"Follow an exact authorized Work Instruction release","operationId":"followWorkInstruction","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/WorkInstructionView"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/skill-manifest":{"get":{"tags":["asset-consumption-controller"],"summary":"Read the browser install contract for one exact authorized Skill release","operationId":"getSkillInstallContract","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SkillInstallManifest"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/pack-journey":{"get":{"tags":["asset-consumption-controller"],"summary":"Read an actor-scoped Capability Pack journey","operationId":"getCapabilityPackJourney","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PackJourney"}}}}}}},"/api/assets/{assetId}/releases/{releaseId}/pack-definition":{"get":{"tags":["asset-consumption-controller"],"summary":"Read the ordered authorized items pinned by a Capability Pack release","operationId":"getCapabilityPackDefinition","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CapabilityPackDefinition"}}}}}}},"/api/assets/{assetId}/released":{"get":{"tags":["asset-registry-controller"],"summary":"Read released Asset content without exposing its working copy or governance history","operationId":"getReleasedAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetReleasedView"}}}}}}},"/api/assets/{assetId}/governance-actions":{"get":{"tags":["asset-registry-controller"],"summary":"Read the current actor's available Governance actions","operationId":"getAssetGovernanceActions","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetGovernanceActions"}}}}}}},"/api/assets/skills/github/connections":{"get":{"tags":["asset-registry-controller"],"summary":"List approved GitHub connections available for private Skill import","operationId":"listGitHubSkillConnections","parameters":[{"name":"knowledgeSpaceId","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ConnectionOption"}}}}}}}},"/api/assets/owned":{"get":{"tags":["asset-registry-controller"],"summary":"List Assets currently owned by the actor","operationId":"listOwnedAssets","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"RECENTLY_UPDATED","enum":["RECENTLY_UPDATED","NAME"]}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":1}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":24}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetSummaryPage"}}}}}}},"/api/assets/catalog":{"get":{"tags":["asset-consumption-controller"],"summary":"List exact usable Asset releases authorized for the current actor","operationId":"listAssetCatalog","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"RECENTLY_RELEASED","enum":["RECENTLY_RELEASED","NAME"]}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":1}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":24}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetRecommendationPage"}}}}}}},"/api/asset-delivery":{"get":{"tags":["asset-delivery-controller"],"summary":"Search exact released Assets authorized for the current actor","operationId":"searchReleasedAssets","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetRecommendation"}}}}}}}},"/api/asset-delivery/{assetId}":{"get":{"tags":["asset-delivery-controller"],"summary":"Read the latest usable immutable release for an Asset","operationId":"getLatestReleasedAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetDeliveryRelease"}}}}}}},"/api/asset-delivery/{assetId}/releases/{releaseId}":{"get":{"tags":["asset-delivery-controller"],"summary":"Read one exact usable immutable Asset release","operationId":"getReleasedAsset_1","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetDeliveryRelease"}}}}}}},"/api/asset-delivery/{assetId}/releases/{releaseId}/skill-package":{"get":{"tags":["asset-delivery-controller"],"summary":"Stream the verified package for one exact usable Skill release","operationId":"downloadReleasedSkillPackage","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StreamingResponseBody"}}}}}}},"/api/asset-delivery/{assetId}/releases/{releaseId}/skill-manifest":{"get":{"tags":["asset-delivery-controller"],"summary":"Read the install manifest for one exact usable Skill release","operationId":"getReleasedSkillManifest","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SkillInstallManifest"}}}}}}},"/api/asset-delivery/{assetId}/releases/{releaseId}/relations":{"get":{"tags":["asset-delivery-controller"],"summary":"Resolve only independently authorized relations of an exact release","operationId":"resolveReleasedAssetRelations","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AssetRelationResolution"}}}}}}},"/api/asset-delivery/{assetId}/releases/{releaseId}/pack":{"get":{"tags":["asset-delivery-controller"],"summary":"Read a Pack definition with independently authorized pinned items","operationId":"getReleasedCapabilityPack","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CapabilityPackDefinition"}}}}}}},"/api/asset-delivery/skills/{namespace}/{slug}/versions/{version}/manifest":{"get":{"tags":["asset-delivery-controller"],"summary":"Resolve an exact usable Skill release by coordinate and version","operationId":"resolveReleasedSkillManifest","parameters":[{"name":"namespace","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"version","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SkillInstallManifest"}}}}}}},"/api/admin/users":{"get":{"tags":["admin-user-controller"],"summary":"List internal users with their sign-in and mapping status","operationId":"listAdminUsers","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminUserResponse"}}}}}}}},"/api/admin/users/{userId}/permissions":{"get":{"tags":["admin-permission-controller"],"summary":"Resolve a user's organization permissions as the engine currently answers them","operationId":"listAdminUserPermissions","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/EffectivePermissionResponse"}}}}}}},"/api/admin/source-principals":{"get":{"tags":["admin-source-access-controller"],"summary":"List observed principals and their mapping","operationId":"listAdminSourcePrincipals","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminSourcePrincipalResponse"}}}}}}}},"/api/admin/source-groups":{"get":{"tags":["admin-source-access-controller"],"summary":"List source groups with their sealed membership","operationId":"listAdminSourceGroups","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminSourceGroupResponse"}}}}}}}},"/api/admin/source-connections":{"get":{"tags":["admin-source-access-controller"],"summary":"List observed connections and their trust level","operationId":"listAdminSourceConnections","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminSourceConnectionResponse"}}}}}}}},"/api/admin/roles":{"get":{"tags":["admin-role-controller"],"summary":"List roles and who is assigned to them","operationId":"listAdminRoles","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminRoleListResponse"}}}}}}},"/api/admin/knowledge-spaces/grant-options":{"get":{"tags":["admin-knowledge-space-controller"],"summary":"List the subject shapes each Knowledge Space relation accepts","operationId":"listAdminKnowledgeSpaceGrantOptions","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeSpaceGrantOptionResponse"}}}}}}}},"/api/admin/connectors/{sourceSystem}":{"get":{"tags":["admin-connector-controller"],"summary":"List a source's connections and their crawl settings","operationId":"listAdminConnections","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminConnectionResponse"}}}}}}}},"/api/admin/connectors/{sourceSystem}/{connectionKey}/scopes":{"get":{"tags":["admin-connector-controller"],"summary":"List what a connection can be pointed at","operationId":"listAdminConnectionScopes","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminConnectorScopeResponse"}}}}}}}},"/api/admin/connectors/{sourceSystem}/{connectionKey}/activity":{"get":{"tags":["admin-connector-controller"],"summary":"Read what a connection has crawled and what went wrong","operationId":"getAdminConnectionActivity","parameters":[{"name":"sourceSystem","in":"path","required":true,"schema":{"type":"string"}},{"name":"connectionKey","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminConnectionActivityResponse"}}}}}}},"/api/admin/connectors/sources":{"get":{"tags":["admin-connector-controller"],"summary":"List the sources this deployment can ingest","operationId":"listAdminConnectorSources","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminConnectorSourceResponse"}}}}}}}},"/api/admin/ai/routes":{"get":{"tags":["admin-ai-model-controller"],"summary":"List effective organization AI routes","operationId":"listAdminAiRoutes","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RouteResponse"}}}}}}}},"/api/admin/ai/providers":{"get":{"tags":["admin-ai-model-controller"],"summary":"List provider presets implemented by this deployment","operationId":"listAdminAiProviderPresets","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProviderPresetResponse"}}}}}}}},"/api/admin/ai/index-settings":{"get":{"tags":["admin-ai-model-controller"],"summary":"Read immutable deployment-managed embedding settings","operationId":"getAdminAiIndexSettings","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IndexSettingsResponse"}}}}}}},"/api/sources/{sourceId}":{"delete":{"tags":["source-controller"],"summary":"Retire a ready manual-upload document","operationId":"deleteSource","parameters":[{"name":"sourceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/KnowledgeAssetRef"}}}}}}},"/api/knowledge-spaces/{knowledgeSpaceId}/graph/curations/{curationId}":{"delete":{"tags":["knowledge-graph-management-controller"],"summary":"Reverse a graph curation record","operationId":"deactivateGraphCuration","parameters":[{"name":"knowledgeSpaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"curationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"authorizationGeneration","in":"query","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"reason","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/api/knowledge-assets/{knowledgeAssetId}":{"delete":{"tags":["knowledge-asset-lifecycle-controller"],"summary":"Retire a Knowledge Asset and remove its derived graph","operationId":"deleteKnowledgeAsset","parameters":[{"name":"knowledgeAssetId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/KnowledgeAssetRef"}}}}}}},"/api/admin/roles/{role}/members/{userId}":{"delete":{"tags":["admin-role-controller"],"summary":"Remove a user from a role","operationId":"revokeAdminRole","parameters":[{"name":"role","in":"path","required":true,"schema":{"type":"string"}},{"name":"userId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"No Content"}}}},"/api/admin/provisioning/connections/{connectionId}/credentials/{credentialId}":{"delete":{"tags":["admin-provisioning-controller"],"summary":"Immediately revoke a SCIM credential","operationId":"revokeProvisioningCredential","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"credentialId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"No Content"}}}},"/api/admin/invitations/{invitationId}":{"delete":{"tags":["admin-invitation-controller"],"summary":"Withdraw an invitation that has not been used","operationId":"revokeAdminInvitation","parameters":[{"name":"invitationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"No Content"}}}}},"components":{"schemas":{"PackProgressRequest":{"type":"object","properties":{"completed":{"type":"boolean"},"confirmed":{"type":"boolean"}}},"Item":{"type":"object","properties":{"key":{"type":"string"},"required":{"type":"boolean"},"order":{"type":"integer","format":"int32"},"kind":{"type":"string"},"resourceId":{"type":"string","format":"uuid"},"pinnedVersionId":{"type":"string","format":"uuid"},"title":{"type":"string"},"versionLabel":{"type":"string"},"availability":{"type":"string","enum":["AVAILABLE","DEPRECATED","WITHDRAWN"]},"completed":{"type":"boolean"},"completedAt":{"type":"string","format":"date-time"}}},"PackJourney":{"type":"object","properties":{"assignmentId":{"type":"string","format":"uuid"},"packAssetId":{"type":"string","format":"uuid"},"packReleaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"},"title":{"type":"string"},"versionLabel":{"type":"string"},"purpose":{"type":"string","enum":["ROLE_ONBOARDING","HANDOVER","ROLE_ENABLEMENT"]},"audience":{"type":"string"},"expectedOutcome":{"type":"string"},"status":{"type":"string","enum":["IN_PROGRESS","COMPLETED"]},"accessGap":{"type":"boolean"},"completedAccessibleItems":{"type":"integer","format":"int32"},"items":{"type":"array","items":{"$ref":"#/components/schemas/Item"}},"startedAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}},"PackToolResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"journey":{"$ref":"#/components/schemas/PackJourney"}}},"AnswerFeedbackRequest":{"type":"object","properties":{"sentiment":{"type":"string","enum":["HELPFUL","NOT_HELPFUL"]}},"required":["sentiment"]},"AssistantAnswerFeedbackView":{"type":"object","properties":{"messageId":{"type":"string","format":"uuid"},"sentiment":{"type":"string","enum":["HELPFUL","NOT_HELPFUL"]},"updatedAt":{"type":"string","format":"date-time"}}},"SelectAssistantModelRequest":{"type":"object","properties":{"modelActivationId":{"type":"string","format":"uuid"}}},"AssetView":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]},"namespace":{"type":"string"},"slug":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"portfolioState":{"type":"string","enum":["DRAFT_ONLY","ACTIVE","SUNSETTING","RETIRED"]},"authorizationReady":{"type":"boolean"},"ownerUserId":{"type":"string","format":"uuid"},"sharingState":{"type":"string","enum":["PRIVATE","SHARED","ORGANIZATION"]},"draft":{"$ref":"#/components/schemas/Draft"},"revisions":{"type":"array","items":{"$ref":"#/components/schemas/Revision"}},"reviews":{"type":"array","items":{"$ref":"#/components/schemas/Review"}},"releases":{"type":"array","items":{"$ref":"#/components/schemas/Release"}},"ownershipHealth":{"$ref":"#/components/schemas/OwnershipHealth"},"roleAssignments":{"type":"array","items":{"$ref":"#/components/schemas/RoleAssignment"}}}},"AvailabilityEvent":{"type":"object","properties":{"availability":{"type":"string","enum":["AVAILABLE","DEPRECATED","WITHDRAWN"]},"reason":{"type":"string"},"changedByUserId":{"type":"string","format":"uuid"},"effectiveAt":{"type":"string","format":"date-time"}}},"Decision":{"type":"object","properties":{"reviewerUserId":{"type":"string","format":"uuid"},"decision":{"type":"string","enum":["REQUEST_CHANGES","REJECT","APPROVE","CANCEL"]},"comment":{"type":"string"},"decidedAt":{"type":"string","format":"date-time"}}},"Draft":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"lockVersion":{"type":"integer","format":"int64"},"title":{"type":"string"},"summary":{"type":"string"},"classification":{"type":"string"},"schemaVersion":{"type":"string"},"payload":{"type":"string"},"editedByUserId":{"type":"string","format":"uuid"},"updatedAt":{"type":"string","format":"date-time"}}},"OwnershipHealth":{"type":"object","properties":{"ownerPresent":{"type":"boolean"},"backupOwnerPresent":{"type":"boolean"},"orphaned":{"type":"boolean"},"continuityAtRisk":{"type":"boolean"}}},"Release":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"revisionId":{"type":"string","format":"uuid"},"sequence":{"type":"integer","format":"int64"},"versionLabel":{"type":"string"},"publicationMode":{"type":"string","enum":["REVIEWED","DIRECT"]},"title":{"type":"string"},"summary":{"type":"string"},"classification":{"type":"string"},"schemaVersion":{"type":"string"},"payload":{"type":"string"},"digest":{"type":"string"},"releasedByUserId":{"type":"string","format":"uuid"},"releasedAt":{"type":"string","format":"date-time"},"availability":{"type":"string","enum":["AVAILABLE","DEPRECATED","WITHDRAWN"]},"availabilityHistory":{"type":"array","items":{"$ref":"#/components/schemas/AvailabilityEvent"}}}},"Review":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"revisionId":{"type":"string","format":"uuid"},"revisionDigest":{"type":"string"},"state":{"type":"string","enum":["IN_REVIEW","CHANGES_REQUESTED","REJECTED","CANCELLED","APPROVED"]},"policyVersion":{"type":"string"},"requestedByUserId":{"type":"string","format":"uuid"},"createdAt":{"type":"string","format":"date-time"},"resolvedAt":{"type":"string","format":"date-time"},"decisions":{"type":"array","items":{"$ref":"#/components/schemas/Decision"}}}},"Revision":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"sequence":{"type":"integer","format":"int64"},"title":{"type":"string"},"summary":{"type":"string"},"classification":{"type":"string"},"schemaVersion":{"type":"string"},"payload":{"type":"string"},"digest":{"type":"string"},"changeNote":{"type":"string"},"createdByUserId":{"type":"string","format":"uuid"},"createdAt":{"type":"string","format":"date-time"}}},"RoleAssignment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"principalType":{"type":"string"},"principalId":{"type":"string"},"role":{"type":"string","enum":["OWNER","BACKUP_OWNER","STEWARD","VIEWER","EDITOR","REVIEWER","PUBLISHER"]},"validFrom":{"type":"string","format":"date-time"},"validUntil":{"type":"string","format":"date-time"},"assignedByUserId":{"type":"string","format":"uuid"},"projectedAt":{"type":"string","format":"date-time"}}},"SkillActivationRequest":{"type":"object","properties":{"enabled":{"type":"boolean"}}},"SkillActivationView":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"enabled":{"type":"boolean"}}},"UpdateAssetDraftRequest":{"type":"object","properties":{"expectedLockVersion":{"type":"integer","format":"int64"},"title":{"type":"string"},"summary":{"type":"string"},"classification":{"type":"string"},"schemaVersion":{"type":"string"},"payload":{"type":"string"}}},"ConfirmMappingRequest":{"type":"object","properties":{"appUserId":{"type":"string","format":"uuid"}}},"AdminSourceMappingResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"appUserId":{"type":"string","format":"uuid"},"appUserName":{"type":"string"},"appUserEmail":{"type":"string"},"method":{"type":"string","enum":["IDP_JOIN","SSO_EMAIL_JOIN","SELF_CLAIM","ADMIN_CONFIRMED"]},"status":{"type":"string","enum":["ACTIVE","REVOKED"]},"evidence":{"type":"string"},"verifiedAt":{"type":"string","format":"date-time"}}},"AdminSourcePrincipalResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"sourceSystem":{"type":"string"},"sourceConnectionKey":{"type":"string"},"nativePrincipalId":{"type":"string"},"kind":{"type":"string","enum":["SOURCE_USER","SOURCE_GROUP"]},"observedEmail":{"type":"string"},"observedDisplayName":{"type":"string"},"ssoVerified":{"type":"boolean"},"lastSeenAt":{"type":"string","format":"date-time"},"mapping":{"$ref":"#/components/schemas/AdminSourceMappingResponse"}}},"IdentityTrustRequest":{"type":"object","properties":{"sourceSystem":{"type":"string"},"sourceConnectionKey":{"type":"string"},"identityTrust":{"type":"string","enum":["UNTRUSTED","SSO_VERIFIED"]}}},"AdminSourceConnectionResponse":{"type":"object","properties":{"sourceSystem":{"type":"string"},"sourceConnectionKey":{"type":"string"},"identityTrust":{"type":"string","enum":["UNTRUSTED","SSO_VERIFIED"]},"trustDecidedByUserId":{"type":"string","format":"uuid"},"trustDecidedAt":{"type":"string","format":"date-time"},"userCount":{"type":"integer","format":"int32"},"mappedUserCount":{"type":"integer","format":"int32"},"unmappedUserCount":{"type":"integer","format":"int32"},"groupCount":{"type":"integer","format":"int32"},"lastSeenAt":{"type":"string","format":"date-time"}}},"ConfigureConnectionRequest":{"type":"object","properties":{"crawlEnabled":{"type":"boolean"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"actorUserId":{"type":"string","format":"uuid"},"sourceConfig":{"type":"object","additionalProperties":{}},"contentCrawlIntervalSeconds":{"type":"integer","format":"int64"}}},"AdminConnectionResponse":{"type":"object","properties":{"sourceSystem":{"type":"string"},"sourceConnectionKey":{"type":"string"},"identityTrust":{"type":"string","enum":["UNTRUSTED","SSO_VERIFIED"]},"crawlEnabled":{"type":"boolean"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"actorUserId":{"type":"string","format":"uuid"},"sourceConfig":{"type":"object","additionalProperties":{}},"contentCrawlIntervalSeconds":{"type":"integer","format":"int64"},"credentialSet":{"type":"boolean"},"credentialSetByUserId":{"type":"string","format":"uuid"},"credentialSetAt":{"type":"string","format":"date-time"},"configuredByUserId":{"type":"string","format":"uuid"},"configuredAt":{"type":"string","format":"date-time"}}},"ConnectorCredentialRequest":{"type":"object","properties":{"credential":{"type":"string"}}},"SetRouteRequest":{"type":"object","properties":{"gatewayProfileId":{"type":"string","format":"uuid"},"modelId":{"type":"string"},"openAiReasoningEffort":{"type":"string","enum":["NONE","LOW","MEDIUM","HIGH","XHIGH","MAX"]}}},"RouteResponse":{"type":"object","properties":{"workload":{"type":"string","enum":["ASSISTANT_CHAT","PROMPT_EXECUTION","KEYWORD_PLANNING","GRAPH_EXTRACTION","QUERY_EMBEDDING","DOCUMENT_EMBEDDING"]},"gatewayKey":{"type":"string"},"gatewayProfileId":{"type":"string","format":"uuid"},"modelId":{"type":"string"},"openAiReasoningEffort":{"type":"string","enum":["NONE","LOW","MEDIUM","HIGH","XHIGH","MAX"]},"source":{"type":"string"},"editable":{"type":"boolean"},"version":{"type":"integer","format":"int64"},"lifecycleNote":{"type":"string"}}},"UpdateGatewayRequest":{"type":"object","properties":{"displayName":{"type":"string"},"baseUrl":{"type":"string"},"requestTimeoutSeconds":{"type":"integer","format":"int32"},"supportsOpenAiReasoningEffort":{"type":"boolean"},"credential":{"type":"string"}}},"AssistantModelResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"modelId":{"type":"string"},"displayName":{"type":"string"}}},"GatewayResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"gatewayKey":{"type":"string"},"displayName":{"type":"string"},"preset":{"type":"string","enum":["OPENAI","ANTHROPIC","NINE_ROUTER","OPENROUTER","LITELLM","OLLAMA","OPENAI_COMPATIBLE"]},"category":{"type":"string","enum":["DIRECT_PROVIDER","GATEWAY_ROUTER","SELF_HOSTED_CUSTOM"]},"protocol":{"type":"string","enum":["OPENAI_COMPATIBLE","ANTHROPIC_MESSAGES"]},"supportsOpenAiReasoningEffort":{"type":"boolean"},"baseUrl":{"type":"string"},"requestTimeoutSeconds":{"type":"integer","format":"int32"},"enabled":{"type":"boolean"},"version":{"type":"integer","format":"int64"},"credentialSet":{"type":"boolean"},"credentialSetByUserId":{"type":"string","format":"uuid"},"credentialSetAt":{"type":"string","format":"date-time"},"assistantModels":{"type":"array","items":{"$ref":"#/components/schemas/AssistantModelResponse"}}}},"GatewayCredentialRequest":{"type":"object","properties":{"credential":{"type":"string"}}},"AssistantModelDefinitionRequest":{"type":"object","properties":{"modelId":{"type":"string"},"displayName":{"type":"string"}}},"AssistantModelsRequest":{"type":"object","properties":{"models":{"type":"array","items":{"$ref":"#/components/schemas/AssistantModelDefinitionRequest"}}}},"SourceResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"sourceSystem":{"type":"string"},"aclAuthority":{"type":"string"},"status":{"type":"string"},"classification":{"type":"string"},"fileName":{"type":"string"},"mediaType":{"type":"string"},"contentLength":{"type":"integer","format":"int64"},"failureCode":{"type":"string"},"failureMessage":{"type":"string"},"knowledgeAssetId":{"type":"string","format":"uuid"},"knowledgeSpaceKey":{"type":"string"},"knowledgeSpaceName":{"type":"string"},"owningDepartmentName":{"type":"string"},"uploadedByName":{"type":"string"},"publicationComplete":{"type":"boolean"},"contentAvailable":{"type":"boolean"},"deletionAllowed":{"type":"boolean"},"embeddingProfileKey":{"type":"string"},"embeddingProvider":{"type":"string"},"embeddingModel":{"type":"string"},"embeddingDimensions":{"type":"integer","format":"int32"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"SuppressIdentityRequest":{"type":"object","properties":{"idempotencyKey":{"type":"string"},"reason":{"type":"string"},"authorizationGeneration":{"type":"integer","format":"int64"},"kind":{"type":"string","enum":["ENTITY","RELATION"]},"identityId":{"type":"string","format":"uuid"}}},"CuratedEntity":{"allOf":[{"$ref":"#/components/schemas/GraphCurationRecord"},{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"namespace":{"$ref":"#/components/schemas/ProjectionNamespace"},"entity":{"$ref":"#/components/schemas/GraphIdentityRef"},"name":{"type":"string"},"type":{"type":"string"},"description":{"type":"string"},"governingEvidence":{"$ref":"#/components/schemas/EvidenceReference"},"provenance":{"$ref":"#/components/schemas/CurationProvenance"}}}]},"CuratedRelation":{"allOf":[{"$ref":"#/components/schemas/GraphCurationRecord"},{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"namespace":{"$ref":"#/components/schemas/ProjectionNamespace"},"relation":{"$ref":"#/components/schemas/GraphIdentityRef"},"sourceEntity":{"$ref":"#/components/schemas/GraphIdentityRef"},"targetEntity":{"$ref":"#/components/schemas/GraphIdentityRef"},"type":{"type":"string"},"keywords":{"type":"array","items":{"type":"string"}},"description":{"type":"string"},"weight":{"type":"number","format":"double"},"governingEvidence":{"$ref":"#/components/schemas/EvidenceReference"},"provenance":{"$ref":"#/components/schemas/CurationProvenance"}}}]},"CurationProvenance":{"type":"object","properties":{"actorUserId":{"type":"string","format":"uuid"},"authorizationModelId":{"type":"string"},"aclGeneration":{"type":"integer","format":"int64"},"curatedAt":{"type":"string","format":"date-time"},"reason":{"type":"string"}}},"EvidenceReference":{"type":"object","properties":{"organizationId":{"type":"string","format":"uuid"},"knowledgeAssetId":{"type":"string","format":"uuid"},"sourceRevisionId":{"type":"string","format":"uuid"},"chunkId":{"type":"string","format":"uuid"},"aclSnapshotId":{"type":"string","format":"uuid"},"aclGeneration":{"type":"integer","format":"int64"},"chunk":{"type":"boolean"}}},"GraphCurationRecord":{},"GraphIdentityRef":{"type":"object","properties":{"kind":{"type":"string","enum":["ENTITY","RELATION"]},"id":{"type":"string","format":"uuid"}}},"IdentityAlias":{"allOf":[{"$ref":"#/components/schemas/GraphCurationRecord"},{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"namespace":{"$ref":"#/components/schemas/ProjectionNamespace"},"source":{"$ref":"#/components/schemas/GraphIdentityRef"},"target":{"$ref":"#/components/schemas/GraphIdentityRef"},"provenance":{"$ref":"#/components/schemas/CurationProvenance"}}}]},"IdentitySuppression":{"allOf":[{"$ref":"#/components/schemas/GraphCurationRecord"},{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"namespace":{"$ref":"#/components/schemas/ProjectionNamespace"},"identity":{"$ref":"#/components/schemas/GraphIdentityRef"},"provenance":{"$ref":"#/components/schemas/CurationProvenance"}}}]},"ProjectionNamespace":{"type":"object","properties":{"organizationId":{"type":"string","format":"uuid"},"workspace":{"type":"string"},"collection":{"type":"string"}}},"CurateRelationRequest":{"type":"object","properties":{"idempotencyKey":{"type":"string"},"reason":{"type":"string"},"authorizationGeneration":{"type":"integer","format":"int64"},"relationId":{"type":"string","format":"uuid"},"sourceEntityId":{"type":"string","format":"uuid"},"targetEntityId":{"type":"string","format":"uuid"},"type":{"type":"string"},"keywords":{"type":"array","items":{"type":"string"}},"description":{"type":"string"},"weight":{"type":"number","format":"double"},"evidence":{"$ref":"#/components/schemas/EvidenceRequest"}}},"EvidenceRequest":{"type":"object","properties":{"knowledgeAssetId":{"type":"string","format":"uuid"},"sourceRevisionId":{"type":"string","format":"uuid"},"chunkId":{"type":"string","format":"uuid"},"aclSnapshotId":{"type":"string","format":"uuid"},"aclGeneration":{"type":"integer","format":"int64"}}},"CurateEntityRequest":{"type":"object","properties":{"idempotencyKey":{"type":"string"},"reason":{"type":"string"},"authorizationGeneration":{"type":"integer","format":"int64"},"entityId":{"type":"string","format":"uuid"},"name":{"type":"string"},"type":{"type":"string"},"description":{"type":"string"},"evidence":{"$ref":"#/components/schemas/EvidenceRequest"}}},"AliasIdentityRequest":{"type":"object","properties":{"idempotencyKey":{"type":"string"},"reason":{"type":"string"},"authorizationGeneration":{"type":"integer","format":"int64"},"kind":{"type":"string","enum":["ENTITY","RELATION"]},"sourceIdentityId":{"type":"string","format":"uuid"},"targetIdentityId":{"type":"string","format":"uuid"}}},"GraphIndexJobView":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"knowledgeAssetId":{"type":"string","format":"uuid"},"knowledgeAssetVersionId":{"type":"string","format":"uuid"},"sourceRevisionId":{"type":"string","format":"uuid"},"projectionGeneration":{"type":"integer","format":"int64"},"graphProcessingProfileId":{"type":"string","format":"uuid"},"graphProcessingProfileSha256":{"type":"string"},"status":{"type":"string"},"attempt":{"type":"integer","format":"int32"},"cancellationRequested":{"type":"boolean"},"cancellationRequestedAt":{"type":"string","format":"date-time"},"lastErrorCode":{"type":"string"},"lastErrorMessage":{"type":"string"},"completedAt":{"type":"string","format":"date-time"}}},"KnowledgeSearchRequest":{"type":"object","properties":{"query":{"type":"string"},"requestId":{"type":"string"}}},"KnowledgeCitation":{"type":"object","properties":{"chunkId":{"type":"string","format":"uuid"},"knowledgeAssetId":{"type":"string","format":"uuid"},"sourceRevisionId":{"type":"string","format":"uuid"},"title":{"type":"string"},"startPage":{"type":"integer","format":"int32"},"endPage":{"type":"integer","format":"int32"},"heading":{"type":"string"}}},"KnowledgeResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"requestId":{"type":"string"},"citations":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeCitation"}}}},"PromptRunRequest":{"type":"object","properties":{"variables":{"type":"object","additionalProperties":{}},"knowledgeQuery":{"type":"string"},"requestId":{"type":"string"},"confirmedExternalProvider":{"type":"boolean"}}},"AiRoute":{"type":"object","properties":{"gatewayId":{"type":"string"},"modelId":{"type":"string"},"openAiReasoningEffort":{"type":"string","enum":["NONE","LOW","MEDIUM","HIGH","XHIGH","MAX"]}}},"PromptCitation":{"type":"object","properties":{"chunkId":{"type":"string","format":"uuid"},"knowledgeAssetId":{"type":"string","format":"uuid"},"sourceRevisionId":{"type":"string","format":"uuid"},"title":{"type":"string"},"startPage":{"type":"integer","format":"int32"},"endPage":{"type":"integer","format":"int32"},"heading":{"type":"string"}}},"PromptRunResult":{"type":"object","properties":{"runId":{"type":"string","format":"uuid"},"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"},"modelRoute":{"$ref":"#/components/schemas/AiRoute"},"output":{"type":"string"},"citations":{"type":"array","items":{"$ref":"#/components/schemas/PromptCitation"}},"durationMillis":{"type":"integer","format":"int64"}}},"PromptRunToolResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"result":{"$ref":"#/components/schemas/PromptRunResult"}}},"PromptRenderRequest":{"type":"object","properties":{"variables":{"type":"object","additionalProperties":{}}}},"PromptRenderResult":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"},"systemInstruction":{"type":"string"},"userPrompt":{"type":"string"},"sensitiveVariables":{"type":"array","items":{"type":"string"}},"inputShapeDigest":{"type":"string"}}},"PromptRenderToolResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"result":{"$ref":"#/components/schemas/PromptRenderResult"}}},"ConfirmedActionRequest":{"type":"object","properties":{"confirmed":{"type":"boolean"}}},"ForkRequest":{"type":"object","properties":{"namespace":{"type":"string"},"slug":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"confirmed":{"type":"boolean"}}},"ForkResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"asset":{"$ref":"#/components/schemas/AssetView"}}},"FeedbackRequest":{"type":"object","properties":{"type":{"type":"string","enum":["HELPFUL","OUTDATED","INCORRECT","OTHER"]},"comment":{"type":"string"},"confirmed":{"type":"boolean"}}},"FeedbackResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"feedbackId":{"type":"string","format":"uuid"}}},"AssistantEvidenceBindingView":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"conversationId":{"type":"string","format":"uuid"},"sourceObjectId":{"type":"string","format":"uuid"},"sourceRevisionId":{"type":"string","format":"uuid"},"knowledgeAssetId":{"type":"string","format":"uuid"},"title":{"type":"string"},"fileName":{"type":"string"},"status":{"type":"string","enum":["PROCESSING","INDEXING","READY","FAILED","UNAVAILABLE"]},"failureCode":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"AssistantChatRequest":{"type":"object","properties":{"message":{"type":"string","maxLength":1000,"minLength":0},"limit":{"type":"integer","format":"int32"},"conversationId":{"type":"string","format":"uuid"},"modelActivationId":{"type":"string","format":"uuid"},"evidenceBindingIds":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":3,"minItems":0}},"required":["message"]},"ServerSentEventString":{},"AssetDraftRequest":{"type":"object","properties":{"title":{"type":"string"},"summary":{"type":"string"},"classification":{"type":"string"},"schemaVersion":{"type":"string"},"payload":{"type":"string"}}},"CreateAssetRequest":{"type":"object","properties":{"type":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK"]},"namespace":{"type":"string"},"slug":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"draft":{"$ref":"#/components/schemas/AssetDraftRequest"}}},"AssetAvailabilityRequest":{"type":"object","properties":{"reason":{"type":"string"}}},"SubmitAssetRevisionRequest":{"type":"object","properties":{"changeNote":{"type":"string"}}},"PublishSkillReleaseRequest":{"type":"object","properties":{"versionLabel":{"type":"string"}}},"ShareAssetRequest":{"type":"object","properties":{"principalType":{"type":"string"},"principalId":{"type":"string"},"role":{"type":"string","enum":["OWNER","BACKUP_OWNER","STEWARD","VIEWER","EDITOR","REVIEWER","PUBLISHER"]}}},"AssignAssetRoleRequest":{"type":"object","properties":{"principalType":{"type":"string"},"principalId":{"type":"string"},"role":{"type":"string","enum":["OWNER","BACKUP_OWNER","STEWARD","VIEWER","EDITOR","REVIEWER","PUBLISHER"]}}},"AssetReviewDecisionRequest":{"type":"object","properties":{"decision":{"type":"string","enum":["REQUEST_CHANGES","REJECT","APPROVE","CANCEL"]},"comment":{"type":"string"}}},"PublishAssetReleaseRequest":{"type":"object","properties":{"revisionId":{"type":"string","format":"uuid"},"versionLabel":{"type":"string"}}},"Step":{"type":"object","properties":{"key":{"type":"string"},"title":{"type":"string"},"instruction":{"type":"string"},"expectedResult":{"type":"string"},"check":{"type":"string"},"escalation":{"type":"string"},"prohibitedActions":{"type":"array","items":{"type":"string"}},"relatedAssetIds":{"type":"array","items":{"type":"string","format":"uuid"}},"relatedKnowledgeVersionIds":{"type":"array","items":{"type":"string","format":"uuid"}}}},"WorkInstructionSpec":{"type":"object","properties":{"purpose":{"type":"string"},"audience":{"type":"string"},"prerequisites":{"type":"array","items":{"type":"string"}},"completionOutcome":{"type":"string"},"responsibleRole":{"type":"string"},"steps":{"type":"array","items":{"$ref":"#/components/schemas/Step"}}}},"WorkInstructionView":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"},"title":{"type":"string"},"versionLabel":{"type":"string"},"instruction":{"$ref":"#/components/schemas/WorkInstructionSpec"},"acknowledged":{"type":"boolean"},"acknowledgedAt":{"type":"string","format":"date-time"}}},"PromptVariablesRequest":{"type":"object","properties":{"variables":{"type":"object","additionalProperties":{}}}},"CaseResult":{"type":"object","properties":{"name":{"type":"string"},"passed":{"type":"boolean"},"failedAssertions":{"type":"array","items":{"type":"string"}},"promptRunId":{"type":"string","format":"uuid"}}},"PromptEvaluationResult":{"type":"object","properties":{"evaluationId":{"type":"string","format":"uuid"},"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"},"passedCases":{"type":"integer","format":"int32"},"totalCases":{"type":"integer","format":"int32"},"cases":{"type":"array","items":{"$ref":"#/components/schemas/CaseResult"}}}},"ForkReleaseRequest":{"type":"object","properties":{"namespace":{"type":"string"},"slug":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"}}},"PromptComparisonRequest":{"type":"object","properties":{"baselineReleaseId":{"type":"string","format":"uuid"},"candidateReleaseId":{"type":"string","format":"uuid"}}},"PromptEvaluationComparison":{"type":"object","properties":{"baseline":{"$ref":"#/components/schemas/PromptEvaluationResult"},"candidate":{"$ref":"#/components/schemas/PromptEvaluationResult"},"passedCaseDelta":{"type":"integer","format":"int32"}}},"TransferAssetOwnershipRequest":{"type":"object","properties":{"nextOwnerUserId":{"type":"string","format":"uuid"}},"required":["nextOwnerUserId"]},"FileEntry":{"type":"object","properties":{"path":{"type":"string"},"size":{"type":"integer","format":"int64"},"sha256":{"type":"string"}}},"SkillPackageInspection":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"license":{"type":"string"},"compatibility":{"type":"string"},"allowedTools":{"type":"string"},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"instructions":{"type":"string"},"sha256":{"type":"string"},"contentLength":{"type":"integer","format":"int64"},"files":{"type":"array","items":{"$ref":"#/components/schemas/FileEntry"}}}},"GitHubSkillSourceRequest":{"type":"object","properties":{"repository":{"type":"string","maxLength":512,"minLength":0},"revision":{"type":"string"},"subpath":{"type":"string"},"connectionKey":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"}},"required":["knowledgeSpaceId","repository"]},"Preview":{"type":"object","properties":{"repository":{"type":"string"},"revision":{"type":"string"},"visibility":{"type":"string","enum":["PUBLIC","PRIVATE"]},"skills":{"type":"array","items":{"$ref":"#/components/schemas/PreviewItem"}}}},"PreviewItem":{"type":"object","properties":{"path":{"type":"string"},"importable":{"type":"boolean"},"name":{"type":"string"},"description":{"type":"string"},"fileCount":{"type":"integer","format":"int32"},"errorCode":{"type":"string"},"errorMessage":{"type":"string"}}},"GitHubSkillImportRequest":{"type":"object","properties":{"source":{"$ref":"#/components/schemas/GitHubSkillSourceRequest"},"paths":{"type":"array","items":{"type":"string","minLength":1},"maxItems":20,"minItems":1},"namespace":{"type":"string","maxLength":128,"minLength":0},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","RESTRICTED"]}},"required":["namespace","paths","source"]},"ImportItem":{"type":"object","properties":{"path":{"type":"string"},"imported":{"type":"boolean"},"asset":{"$ref":"#/components/schemas/AssetView"},"errorCode":{"type":"string"},"errorMessage":{"type":"string"}}},"ImportResult":{"type":"object","properties":{"repository":{"type":"string"},"revision":{"type":"string"},"visibility":{"type":"string","enum":["PUBLIC","PRIVATE"]},"skills":{"type":"array","items":{"$ref":"#/components/schemas/ImportItem"}}}},"AssignRoleRequest":{"type":"object","properties":{"userId":{"type":"string","format":"uuid"}}},"CreateConnectionRequest":{"type":"object","properties":{"alias":{"type":"string"},"providerProfile":{"type":"string","enum":["GENERIC_SCIM","MICROSOFT_ENTRA","OKTA"]}}},"ConnectionResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"alias":{"type":"string"},"providerProfile":{"type":"string","enum":["GENERIC_SCIM","MICROSOFT_ENTRA","OKTA"]},"configurationStatus":{"type":"string"},"operationalState":{"type":"string","enum":["DISABLED","VALIDATING","ENABLED","READ_ONLY","SUSPENDED"]},"usersEnabled":{"type":"boolean"},"groupsEnabled":{"type":"boolean"},"version":{"type":"integer","format":"int64"}}},"IssueCredentialRequest":{"type":"object","properties":{"usersScope":{"type":"boolean"},"groupsScope":{"type":"boolean"}}},"IssuedCredentialResponse":{"type":"object","properties":{"credentialId":{"type":"string","format":"uuid"},"token":{"type":"string"},"publicTokenId":{"type":"string"},"usersScope":{"type":"boolean"},"groupsScope":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"}}},"CreateKnowledgeSpaceRequest":{"type":"object","properties":{"name":{"type":"string"},"audienceMode":{"type":"string","enum":["ORGANIZATION","DEPARTMENT","RESTRICTED_CUSTOM"]},"departmentId":{"type":"string","format":"uuid"}}},"AdminKnowledgeSpaceResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string"},"name":{"type":"string"},"audienceMode":{"type":"string","enum":["ORGANIZATION","DEPARTMENT","RESTRICTED_CUSTOM"]},"audienceVersion":{"type":"integer","format":"int64"},"departmentId":{"type":"string","format":"uuid"},"active":{"type":"boolean"},"grants":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeSpaceGrantResponse"}},"grantsComplete":{"type":"boolean"},"policyVersion":{"type":"string"}}},"KnowledgeSpaceGrantResponse":{"type":"object","properties":{"relation":{"type":"string"},"subject":{"type":"string"},"effective":{"type":"boolean"}}},"GrantKnowledgeSpaceAccessRequest":{"type":"object","properties":{"relation":{"type":"string"},"kind":{"type":"string","enum":["ORGANIZATION","DEPARTMENT","DEPARTMENT_MANAGERS","ROLE","USER"]},"subjectId":{"type":"string","format":"uuid"},"role":{"type":"string"}}},"CreateInvitationRequest":{"type":"object","properties":{"email":{"type":"string"},"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]},"departmentId":{"type":"string","format":"uuid"}}},"AdminInvitationResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]},"departmentId":{"type":"string","format":"uuid"},"status":{"type":"string"},"invitedAt":{"type":"string","format":"date-time"},"acceptedAt":{"type":"string","format":"date-time"},"acceptedAppUserId":{"type":"string","format":"uuid"}}},"AdminConnectorProbeResponse":{"type":"object","properties":{"authenticated":{"type":"boolean"},"connectionKey":{"type":"string"},"accountName":{"type":"string"},"identityName":{"type":"string"},"canReadContent":{"type":"boolean"},"errorCode":{"type":"string"}}},"CreateGatewayRequest":{"type":"object","properties":{"gatewayKey":{"type":"string"},"displayName":{"type":"string"},"preset":{"type":"string","enum":["OPENAI","ANTHROPIC","NINE_ROUTER","OPENROUTER","LITELLM","OLLAMA","OPENAI_COMPATIBLE"]},"category":{"type":"string","enum":["DIRECT_PROVIDER","GATEWAY_ROUTER","SELF_HOSTED_CUSTOM"]},"protocol":{"type":"string","enum":["OPENAI_COMPATIBLE","ANTHROPIC_MESSAGES"]},"baseUrl":{"type":"string"},"requestTimeoutSeconds":{"type":"integer","format":"int32"},"supportsOpenAiReasoningEffort":{"type":"boolean"},"credential":{"type":"string"}}},"ModelRef":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"}}},"ProbeResponse":{"type":"object","properties":{"authenticated":{"type":"boolean"},"models":{"type":"array","items":{"$ref":"#/components/schemas/ModelRef"}},"errorCode":{"type":"string"}}},"TestGatewayRequest":{"type":"object","properties":{"preset":{"type":"string","enum":["OPENAI","ANTHROPIC","NINE_ROUTER","OPENROUTER","LITELLM","OLLAMA","OPENAI_COMPATIBLE"]},"protocol":{"type":"string","enum":["OPENAI_COMPATIBLE","ANTHROPIC_MESSAGES"]},"baseUrl":{"type":"string"},"requestTimeoutSeconds":{"type":"integer","format":"int32"},"credential":{"type":"string"}}},"ExplainAccessRequest":{"type":"object","properties":{"userId":{"type":"string","format":"uuid"},"permission":{"type":"string"},"resourceType":{"type":"string"},"resourceId":{"type":"string","format":"uuid"}}},"AccessBlockResponse":{"type":"object","properties":{"branch":{"type":"string"},"kind":{"type":"string"},"detail":{"type":"string"}}},"AccessStepResponse":{"type":"object","properties":{"object":{"type":"string"},"relation":{"type":"string"},"kind":{"type":"string"}}},"AclProvenanceResponse":{"type":"object","properties":{"authority":{"type":"string"},"origin":{"type":"string"},"generation":{"type":"integer","format":"int64"},"capturedAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean"}}},"ExplainAccessResponse":{"type":"object","properties":{"state":{"type":"string","enum":["ALLOWED","DENIED","UNKNOWN"]},"reasonCode":{"type":"string"},"path":{"type":"array","items":{"$ref":"#/components/schemas/AccessStepResponse"}},"blockedBy":{"type":"array","items":{"$ref":"#/components/schemas/AccessBlockResponse"}},"provenance":{"$ref":"#/components/schemas/AclProvenanceResponse"},"evaluationKind":{"type":"string"},"relationshipState":{"type":"string","enum":["ALLOWED","DENIED","UNKNOWN"]},"relationshipReasonCode":{"type":"string"},"contentPolicyState":{"type":"string","enum":["ALLOWED","DENIED","UNKNOWN"]},"contentPolicyReasonCode":{"type":"string"},"resource":{"$ref":"#/components/schemas/ResourceSummaryResponse"},"policyVersion":{"type":"string"},"evaluatedAt":{"type":"string","format":"date-time"}}},"ResourceSummaryResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string"},"label":{"type":"string"},"contextLabel":{"type":"string"},"classification":{"type":"string"}}},"RenameConversationRequest":{"type":"object","properties":{"title":{"type":"string","maxLength":120,"minLength":0}},"required":["title"]},"UpdateAdminUserRequest":{"type":"object","properties":{"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]},"active":{"type":"boolean"},"departmentId":{"type":["string","null"],"format":"uuid","description":"Omit to keep the current department; send null to clear it"}}},"AdminUserResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string"},"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]},"departmentId":{"type":"string","format":"uuid"},"active":{"type":"boolean"},"signInLinked":{"type":"boolean"},"mappedPrincipalCount":{"type":"integer","format":"int32"}}},"UpdateStateRequest":{"type":"object","properties":{"expectedVersion":{"type":"integer","format":"int64"},"expectedState":{"type":"string","enum":["DISABLED","VALIDATING","ENABLED","READ_ONLY","SUSPENDED"]},"nextState":{"type":"string","enum":["DISABLED","VALIDATING","ENABLED","READ_ONLY","SUSPENDED"]}}},"SourcePageResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SourceResponse"}},"nextCursor":{"type":"string"},"pageSize":{"type":"integer","format":"int32"},"total":{"type":"integer","format":"int64"},"statusCounts":{"$ref":"#/components/schemas/SourceStatusCountsResponse"}}},"SourceStatusCountsResponse":{"type":"object","properties":{"processing":{"type":"integer","format":"int64"},"ready":{"type":"integer","format":"int64"},"attention":{"type":"integer","format":"int64"}}},"StreamingResponseBody":{},"SessionResponse":{"type":"object","properties":{"authenticated":{"type":"boolean"},"name":{"type":"string"},"email":{"type":"string"},"userId":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"departmentId":{"type":"string","format":"uuid"},"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]},"canManageMembers":{"type":"boolean"}}},"CsrfResponse":{"type":"object","properties":{"headerName":{"type":"string"},"parameterName":{"type":"string"},"token":{"type":"string"}}},"DepartmentResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"OrganizationContextResponse":{"type":"object","properties":{"organizationId":{"type":"string","format":"uuid"},"departments":{"type":"array","items":{"$ref":"#/components/schemas/DepartmentResponse"}},"users":{"type":"array","items":{"$ref":"#/components/schemas/UserResponse"}}}},"UserResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"departmentId":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string"},"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]}}},"MeResponse":{"type":"object","properties":{"userId":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string"},"departmentId":{"type":"string","format":"uuid"},"departmentName":{"type":"string"},"clearance":{"type":"string","enum":["STANDARD","EXECUTIVE"]}}},"KnowledgeEvidenceResponse":{"type":"object","properties":{"citationId":{"type":"string","format":"uuid"},"knowledgeAssetId":{"type":"string","format":"uuid"},"title":{"type":"string"},"content":{"type":"string"},"sourceUri":{"type":"string"},"startPage":{"type":"integer","format":"int32"},"endPage":{"type":"integer","format":"int32"},"heading":{"type":"string"},"relevanceScore":{"type":"number","format":"double"}}},"KnowledgeSearchResponse":{"type":"object","properties":{"requestId":{"type":"string"},"evidence":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeEvidenceResponse"}}}},"KnowledgeCatalogItem":{"type":"object","properties":{"knowledgeAssetId":{"type":"string","format":"uuid"},"knowledgeVersionId":{"type":"string","format":"uuid"},"versionNumber":{"type":"integer","format":"int64"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"title":{"type":"string"},"language":{"type":"string"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","RESTRICTED"]},"contentDigest":{"type":"string"}}},"Entity":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"type":{"type":"string"},"description":{"type":"string"},"citationChunkIds":{"type":"array","items":{"type":"string","format":"uuid"}},"governingEvidence":{"$ref":"#/components/schemas/EvidenceReference"}}},"KnowledgeGraphView":{"type":"object","properties":{"knowledgeSpaceId":{"type":"string","format":"uuid"},"authorizationGeneration":{"type":"integer","format":"int64"},"canCurate":{"type":"boolean"},"entities":{"type":"array","items":{"$ref":"#/components/schemas/Entity"}},"relations":{"type":"array","items":{"$ref":"#/components/schemas/Relation"}},"truncated":{"type":"boolean"}}},"Relation":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"sourceEntityId":{"type":"string","format":"uuid"},"targetEntityId":{"type":"string","format":"uuid"},"type":{"type":"string"},"description":{"type":"string"},"weight":{"type":"number","format":"double"},"keywords":{"type":"array","items":{"type":"string"}},"citationChunkIds":{"type":"array","items":{"type":"string","format":"uuid"}},"governingEvidence":{"$ref":"#/components/schemas/EvidenceReference"}}},"KnowledgeSpaceResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string"},"name":{"type":"string"},"departmentId":{"type":"string","format":"uuid"}}},"CitationEvidenceExcerpt":{"type":"object","properties":{"title":{"type":"string"},"heading":{"type":"string"},"startPage":{"type":"integer","format":"int32"},"endPage":{"type":"integer","format":"int32"},"excerpt":{"type":"string"},"truncated":{"type":"boolean"},"presentationKind":{"type":"string","enum":["PDF","MARKDOWN","PLAIN_TEXT","IMAGE","DOWNLOAD"]}}},"WorkInstructionToolResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"instruction":{"$ref":"#/components/schemas/WorkInstructionView"}}},"AssistantReleaseRef":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"}}},"PromptFormResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"release":{"$ref":"#/components/schemas/AssistantReleaseRef"},"objective":{"type":"string"},"audience":{"type":"string"},"variables":{"type":"array","items":{"$ref":"#/components/schemas/Variable"}},"outputContract":{"type":"object","additionalProperties":{}},"knowledgeRequirements":{"type":"array","items":{"type":"string"}},"knownLimitations":{"type":"string"}}},"Variable":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string","enum":["STRING","INTEGER","NUMBER","BOOLEAN","STRING_LIST"]},"required":{"type":"boolean"},"defaultValue":{},"sensitive":{"type":"boolean"},"pattern":{"type":"string"},"allowedValues":{"type":"array","items":{"type":"string"}}}},"AssetRecommendation":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]},"namespace":{"type":"string"},"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"portfolioState":{"type":"string","enum":["DRAFT_ONLY","ACTIVE","SUNSETTING","RETIRED"]},"ownerUserId":{"type":"string","format":"uuid"},"sharingState":{"type":"string","enum":["PRIVATE","SHARED","ORGANIZATION"]},"releaseId":{"type":"string","format":"uuid"},"versionLabel":{"type":"string"},"releaseDigest":{"type":"string"},"availability":{"type":"string","enum":["AVAILABLE","DEPRECATED","WITHDRAWN"]},"releasedAt":{"type":"string","format":"date-time"}}},"RecommendationResult":{"type":"object","properties":{"traceId":{"type":"string","format":"uuid"},"recommendations":{"type":"array","items":{"$ref":"#/components/schemas/AssetRecommendation"}}}},"AssistantStarterPrompt":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"prompt":{"type":"string"}}},"AssistantModelOptionResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"gatewayLabel":{"type":"string"},"provider":{"type":"string"},"modelId":{"type":"string"},"displayName":{"type":"string"},"defaultChoice":{"type":"boolean"}}},"AssistantModelOptionsResponse":{"type":"object","properties":{"selectedModelActivationId":{"type":"string","format":"uuid"},"options":{"type":"array","items":{"$ref":"#/components/schemas/AssistantModelOptionResponse"}}}},"AssistantCitationResponse":{"type":"object","properties":{"citationNumber":{"type":"integer","format":"int32"},"sourceId":{"type":"string"},"title":{"type":"string"},"heading":{"type":"string"},"startPage":{"type":"integer","format":"int32"},"endPage":{"type":"integer","format":"int32"},"excerptUrl":{"type":"string"},"contentUrl":{"type":"string"}}},"AssistantConversationSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"lastActivityAt":{"type":"string","format":"date-time"},"messageCount":{"type":"integer","format":"int64"}}},"AssistantConversationMessageView":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"role":{"type":"string","enum":["USER","ASSISTANT"]},"content":{"type":"string"},"sequence":{"type":"integer","format":"int64"},"occurredAt":{"type":"string","format":"date-time"},"feedback":{"type":"string","enum":["HELPFUL","NOT_HELPFUL"]}}},"AssetSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]},"namespace":{"type":"string"},"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"portfolioState":{"type":"string","enum":["DRAFT_ONLY","ACTIVE","SUNSETTING","RETIRED"]},"ownerUserId":{"type":"string","format":"uuid"},"sharingState":{"type":"string","enum":["PRIVATE","SHARED","ORGANIZATION"]},"updatedAt":{"type":"string","format":"date-time"}}},"File":{"type":"object","properties":{"path":{"type":"string"},"size":{"type":"integer","format":"int64"},"sha256":{"type":"string"}}},"SkillInstallManifest":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"namespace":{"type":"string"},"slug":{"type":"string"},"coordinate":{"type":"string"},"version":{"type":"string"},"publicationMode":{"type":"string","enum":["REVIEWED","DIRECT"]},"title":{"type":"string"},"description":{"type":"string"},"releaseDigest":{"type":"string"},"packageDigest":{"type":"string"},"packageLength":{"type":"integer","format":"int64"},"mediaType":{"type":"string"},"license":{"type":"string"},"compatibility":{"type":"string"},"allowedTools":{"type":"string"},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"files":{"type":"array","items":{"$ref":"#/components/schemas/File"}}}},"CapabilityPackDefinition":{"type":"object","properties":{"packAssetId":{"type":"string","format":"uuid"},"packReleaseId":{"type":"string","format":"uuid"},"releaseDigest":{"type":"string"},"title":{"type":"string"},"versionLabel":{"type":"string"},"purpose":{"type":"string","enum":["ROLE_ONBOARDING","HANDOVER","ROLE_ENABLEMENT"]},"audience":{"type":"string"},"prerequisites":{"type":"array","items":{"type":"string"}},"expectedOutcome":{"type":"string"},"completionCriteria":{"type":"array","items":{"type":"string"}},"reviewDate":{"type":"string"},"owner":{"type":"string"},"accessGap":{"type":"boolean"},"items":{"type":"array","items":{"$ref":"#/components/schemas/Item"}}}},"AssetReleasedView":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]},"namespace":{"type":"string"},"slug":{"type":"string"},"knowledgeSpaceId":{"type":"string","format":"uuid"},"portfolioState":{"type":"string","enum":["DRAFT_ONLY","ACTIVE","SUNSETTING","RETIRED"]},"ownerUserId":{"type":"string","format":"uuid"},"sharingState":{"type":"string","enum":["PRIVATE","SHARED","ORGANIZATION"]},"releases":{"type":"array","items":{"$ref":"#/components/schemas/Release"}}}},"AssetGovernanceActions":{"type":"object","properties":{"canEdit":{"type":"boolean"},"canSubmitReview":{"type":"boolean"},"canReview":{"type":"boolean"},"canApprove":{"type":"boolean"},"canRequestChanges":{"type":"boolean"},"canReject":{"type":"boolean"},"canCancel":{"type":"boolean"},"canPublish":{"type":"boolean"},"canPublishSkill":{"type":"boolean"},"canWithdraw":{"type":"boolean"},"canManageSharing":{"type":"boolean"},"canTransferOwnership":{"type":"boolean"},"canPublishDirect":{"type":"boolean"},"canOpenGovernance":{"type":"boolean"}}},"ConnectionOption":{"type":"object","properties":{"key":{"type":"string"}}},"AssetSummaryPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AssetSummary"}},"total":{"type":"integer","format":"int64"},"page":{"type":"integer","format":"int32"},"pageSize":{"type":"integer","format":"int32"},"totalPages":{"type":"integer","format":"int32"},"sort":{"type":"string","enum":["RECENTLY_UPDATED","NAME"]}}},"AssetRecommendationPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AssetRecommendation"}},"total":{"type":"integer","format":"int64"},"page":{"type":"integer","format":"int32"},"pageSize":{"type":"integer","format":"int32"},"totalPages":{"type":"integer","format":"int32"},"sort":{"type":"string","enum":["RECENTLY_RELEASED","NAME"]}}},"AssetDeliveryRelease":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["PROMPT_TEMPLATE","WORK_INSTRUCTION","CAPABILITY_PACK","SKILL"]},"namespace":{"type":"string"},"slug":{"type":"string"},"versionLabel":{"type":"string"},"publicationMode":{"type":"string","enum":["REVIEWED","DIRECT"]},"title":{"type":"string"},"summary":{"type":"string"},"classification":{"type":"string"},"schemaVersion":{"type":"string"},"payload":{"type":"string"},"digest":{"type":"string"},"availability":{"type":"string","enum":["AVAILABLE","DEPRECATED","WITHDRAWN"]},"releasedAt":{"type":"string","format":"date-time"}}},"AssetRelationResolution":{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"},"releaseId":{"type":"string","format":"uuid"},"accessGap":{"type":"boolean"},"relations":{"type":"array","items":{"$ref":"#/components/schemas/Relation"}}}},"EffectivePermissionResponse":{"type":"object","properties":{"userId":{"type":"string","format":"uuid"},"permissions":{"type":"object","additionalProperties":{"type":"string","enum":["ALLOWED","DENIED","UNKNOWN"]}},"evaluatedAt":{"type":"string","format":"date-time"}}},"AdminSourceGroupMemberResponse":{"type":"object","properties":{"principalId":{"type":"string","format":"uuid"},"nativePrincipalId":{"type":"string"},"observedDisplayName":{"type":"string"},"observedEmail":{"type":"string"},"appUserId":{"type":"string","format":"uuid"},"appUserName":{"type":"string"}}},"AdminSourceGroupResponse":{"type":"object","properties":{"principalId":{"type":"string","format":"uuid"},"sourceSystem":{"type":"string"},"sourceConnectionKey":{"type":"string"},"nativePrincipalId":{"type":"string"},"observedDisplayName":{"type":"string"},"membershipSnapshotId":{"type":"string","format":"uuid"},"membershipGeneration":{"type":"integer","format":"int64"},"sealedAt":{"type":"string","format":"date-time"},"members":{"type":"array","items":{"$ref":"#/components/schemas/AdminSourceGroupMemberResponse"}}}},"AdminRoleListResponse":{"type":"object","properties":{"roles":{"type":"array","items":{"$ref":"#/components/schemas/AdminRoleResponse"}},"complete":{"type":"boolean"},"policyVersion":{"type":"string"}}},"AdminRoleResponse":{"type":"object","properties":{"role":{"type":"string"},"assignees":{"type":"array","items":{"type":"string"}}}},"CredentialResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"publicTokenId":{"type":"string"},"verifierKeyVersion":{"type":"integer","format":"int32"},"usersScope":{"type":"boolean"},"groupsScope":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"},"overlapEndsAt":{"type":"string","format":"date-time"},"revokedAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"KnowledgeSpaceGrantOptionResponse":{"type":"object","properties":{"relation":{"type":"string"},"kinds":{"type":"array","items":{"type":"string","enum":["ORGANIZATION","DEPARTMENT","DEPARTMENT_MANAGERS","ROLE","USER"]}},"roles":{"type":"array","items":{"type":"string"}}}},"AdminConnectorScopeResponse":{"type":"object","properties":{"key":{"type":"string"},"displayName":{"type":"string"},"reachable":{"type":"boolean"},"admissible":{"type":"boolean"},"instruction":{"type":"string"}}},"AdminComponentCheckpointResponse":{"type":"object","properties":{"component":{"type":"string"},"observedCursor":{"type":"string"},"captureStatus":{"type":"string"},"incompleteReason":{"type":"string"},"observedAt":{"type":"string","format":"date-time"},"lastSuccessfulCursor":{"type":"string"},"lastSuccessfulAt":{"type":"string","format":"date-time"}}},"AdminConnectionActivityResponse":{"type":"object","properties":{"sourceSystem":{"type":"string"},"sourceConnectionKey":{"type":"string"},"objectsTotal":{"type":"integer","format":"int64"},"objectsActive":{"type":"integer","format":"int64"},"objectsArchived":{"type":"integer","format":"int64"},"lastObjectAt":{"type":"string","format":"date-time"},"lastCrawlAt":{"type":"string","format":"date-time"},"componentCheckpoints":{"type":"array","items":{"$ref":"#/components/schemas/AdminComponentCheckpointResponse"}},"recentAttempts":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrawlAttemptResponse"}}}},"AdminCrawlAttemptResponse":{"type":"object","properties":{"outcome":{"type":"string"},"objectsMaterialized":{"type":"integer","format":"int32"},"objectsRotated":{"type":"integer","format":"int32"},"objectsRematerialized":{"type":"integer","format":"int32"},"objectsRetired":{"type":"integer","format":"int32"},"objectsFailed":{"type":"integer","format":"int32"},"errorCode":{"type":"string"},"errorMessage":{"type":"string"},"attemptedAt":{"type":"string","format":"date-time"}}},"AdminConnectorSourceResponse":{"type":"object","properties":{"sourceSystem":{"type":"string"},"displayName":{"type":"string"}}},"ProviderPresetResponse":{"type":"object","properties":{"preset":{"type":"string","enum":["OPENAI","ANTHROPIC","NINE_ROUTER","OPENROUTER","LITELLM","OLLAMA","OPENAI_COMPATIBLE"]},"displayName":{"type":"string"},"vendorName":{"type":"string"},"category":{"type":"string","enum":["DIRECT_PROVIDER","GATEWAY_ROUTER","SELF_HOSTED_CUSTOM"]},"protocol":{"type":"string","enum":["OPENAI_COMPATIBLE","ANTHROPIC_MESSAGES"]},"defaultBaseUrl":{"type":"string"},"baseUrlEditable":{"type":"boolean"}}},"IndexSettingsResponse":{"type":"object","properties":{"embeddingProvider":{"type":"string"},"embeddingModel":{"type":"string"},"dimensions":{"type":"integer","format":"int32"},"distanceMetric":{"type":"string"},"managementMode":{"type":"string"},"editable":{"type":"boolean"},"lifecycleNote":{"type":"string"}}},"KnowledgeAssetRef":{"type":"object","properties":{"knowledgeAssetId":{"type":"string","format":"uuid"},"knowledgeAssetVersionId":{"type":"string","format":"uuid"},"normalizedRecordId":{"type":"string","format":"uuid"},"rawSourceObjectId":{"type":"string","format":"uuid"},"sourceAclSnapshotId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["PENDING","ACTIVE","RETIRED"]}}}}}} \ No newline at end of file diff --git a/core/src/main/java/com/orgmemory/core/assistant/AssistantConversationService.java b/core/src/main/java/com/orgmemory/core/assistant/AssistantConversationService.java index ca5223a94..2be73bd3d 100644 --- a/core/src/main/java/com/orgmemory/core/assistant/AssistantConversationService.java +++ b/core/src/main/java/com/orgmemory/core/assistant/AssistantConversationService.java @@ -2,6 +2,7 @@ import com.orgmemory.core.organization.CurrentActor; import com.orgmemory.core.ai.AssistantModelSelectionRef; +import com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection; import com.orgmemory.core.shared.error.BusinessErrorExposure; import com.orgmemory.core.shared.error.BusinessNotFoundException; import com.orgmemory.core.shared.error.BusinessValidationException; @@ -24,6 +25,7 @@ public class AssistantConversationService implements AssistantTranscriptContext private final AssistantConversationMessageRepository messages; private final AssistantMessageCitationRepository citations; private final AssistantAnswerFeedbackRepository answerFeedback; + private final AssistantEvidenceService evidence; private final Clock clock; AssistantConversationService( @@ -31,11 +33,13 @@ public class AssistantConversationService implements AssistantTranscriptContext AssistantConversationMessageRepository messages, AssistantMessageCitationRepository citations, AssistantAnswerFeedbackRepository answerFeedback, + AssistantEvidenceService evidence, Clock clock) { this.conversations = conversations; this.messages = messages; this.citations = citations; this.answerFeedback = answerFeedback; + this.evidence = evidence; this.clock = clock; } @@ -51,6 +55,22 @@ public AssistantTurnRef beginTurn( UUID requestedId, String userMessage, AssistantModelSelectionRef modelSelection) { + return beginTurnWithEvidence( + actor, + requestedId, + userMessage, + modelSelection, + List.of()) + .turn(); + } + + @Transactional + public AssistantEvidenceTurnClaim beginTurnWithEvidence( + CurrentActor actor, + UUID requestedId, + String userMessage, + AssistantModelSelectionRef modelSelection, + List evidenceBindingIds) { String validUserMessage = requireUserMessage(userMessage); Instant now = clock.instant(); AssistantConversation conversation; @@ -67,16 +87,34 @@ public AssistantTurnRef beginTurn( } conversation.selectModel(modelSelection); UUID turnId = UUID.randomUUID(); - messages.save(new AssistantConversationMessage( - UUID.randomUUID(), + UUID userMessageId = UUID.randomUUID(); + AssistantConversationMessage userTurn = new AssistantConversationMessage( + userMessageId, conversation.getId(), turnId, actor.organizationId(), actor.userId(), AssistantConversationRole.USER, validUserMessage, - now)); - return new AssistantTurnRef(conversation.getId(), turnId); + now); + List requestedEvidence = evidenceBindingIds == null + ? List.of() + : evidenceBindingIds; + var evidenceSelection = KnowledgeEvidenceSelection.unrestricted(); + if (requestedEvidence.isEmpty()) { + messages.save(userTurn); + } else { + messages.saveAndFlush(userTurn); + evidenceSelection = evidence.claimForTurn( + actor, + conversation.getId(), + turnId, + userMessageId, + requestedEvidence); + } + return new AssistantEvidenceTurnClaim( + new AssistantTurnRef(conversation.getId(), turnId), + evidenceSelection); } @Override diff --git a/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceAnswerabilityPort.java b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceAnswerabilityPort.java new file mode 100644 index 000000000..987bf92be --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceAnswerabilityPort.java @@ -0,0 +1,30 @@ +package com.orgmemory.core.assistant; + +import com.orgmemory.core.knowledge.evidence.GovernedEvidenceRef; + +/** Active Assistant engine readiness, separate from Source processing readiness. */ +public interface AssistantEvidenceAnswerabilityPort { + + Answerability evaluate(GovernedEvidenceRef source); + + record Answerability(State state, String failureCode) { + + public enum State { + INDEXING, + READY, + FAILED + } + + public static Answerability indexing() { + return new Answerability(State.INDEXING, null); + } + + public static Answerability ready() { + return new Answerability(State.READY, null); + } + + public static Answerability failed(String failureCode) { + return new Answerability(State.FAILED, failureCode); + } + } +} diff --git a/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceBinding.java b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceBinding.java new file mode 100644 index 000000000..d4a9ded65 --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceBinding.java @@ -0,0 +1,66 @@ +package com.orgmemory.core.assistant; + +import com.orgmemory.core.shared.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import java.util.Objects; +import java.util.UUID; + +@Entity +@Table(name = "assistant_evidence_bindings") +class AssistantEvidenceBinding extends BaseEntity { + + @Column(name = "organization_id", nullable = false, updatable = false) + private UUID organizationId; + + @Column(name = "conversation_id", nullable = false, updatable = false) + private UUID conversationId; + + @Column(name = "created_by_user_id", nullable = false, updatable = false) + private UUID createdByUserId; + + @Column(name = "source_object_id", nullable = false, updatable = false) + private UUID sourceObjectId; + + @Column(name = "source_revision_id", nullable = false, updatable = false) + private UUID sourceRevisionId; + + protected AssistantEvidenceBinding() { + } + + AssistantEvidenceBinding( + UUID id, + UUID organizationId, + UUID conversationId, + UUID createdByUserId, + UUID sourceObjectId, + UUID sourceRevisionId) { + super(Objects.requireNonNull(id, "id")); + this.organizationId = Objects.requireNonNull(organizationId, "organizationId"); + this.conversationId = Objects.requireNonNull(conversationId, "conversationId"); + this.createdByUserId = Objects.requireNonNull(createdByUserId, "createdByUserId"); + this.sourceObjectId = Objects.requireNonNull(sourceObjectId, "sourceObjectId"); + this.sourceRevisionId = Objects.requireNonNull(sourceRevisionId, "sourceRevisionId"); + } + + UUID organizationId() { + return organizationId; + } + + UUID conversationId() { + return conversationId; + } + + UUID createdByUserId() { + return createdByUserId; + } + + UUID sourceObjectId() { + return sourceObjectId; + } + + UUID sourceRevisionId() { + return sourceRevisionId; + } +} diff --git a/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceBindingRepository.java b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceBindingRepository.java new file mode 100644 index 000000000..903e46825 --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceBindingRepository.java @@ -0,0 +1,31 @@ +package com.orgmemory.core.assistant; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; + +interface AssistantEvidenceBindingRepository + extends JpaRepository { + + Optional + findByIdAndOrganizationIdAndConversationIdAndCreatedByUserId( + UUID id, + UUID organizationId, + UUID conversationId, + UUID createdByUserId); + + List + findAllByIdInAndOrganizationIdAndConversationIdAndCreatedByUserId( + Collection ids, + UUID organizationId, + UUID conversationId, + UUID createdByUserId); + + List + findAllByOrganizationIdAndConversationIdAndCreatedByUserIdOrderByCreatedAtAsc( + UUID organizationId, + UUID conversationId, + UUID createdByUserId); +} diff --git a/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceBindingView.java b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceBindingView.java new file mode 100644 index 000000000..2010becb4 --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceBindingView.java @@ -0,0 +1,17 @@ +package com.orgmemory.core.assistant; + +import java.time.Instant; +import java.util.UUID; + +public record AssistantEvidenceBindingView( + UUID id, + UUID conversationId, + UUID sourceObjectId, + UUID sourceRevisionId, + UUID knowledgeAssetId, + String title, + String fileName, + AssistantEvidenceStatus status, + String failureCode, + Instant createdAt) { +} diff --git a/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceService.java b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceService.java new file mode 100644 index 000000000..c702e3d26 --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceService.java @@ -0,0 +1,296 @@ +package com.orgmemory.core.assistant; + +import com.orgmemory.core.authorization.EffectiveAuthorizationService; +import com.orgmemory.core.authorization.PermissionKey; +import com.orgmemory.core.authorization.ResourceRef; +import com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection; +import com.orgmemory.core.knowledge.evidence.GovernedEvidenceQuery; +import com.orgmemory.core.knowledge.evidence.GovernedEvidenceRef; +import com.orgmemory.core.knowledge.evidence.GovernedFileUploadResult; +import com.orgmemory.core.organization.CurrentActor; +import com.orgmemory.core.shared.error.BusinessConflictException; +import com.orgmemory.core.shared.error.BusinessErrorExposure; +import com.orgmemory.core.shared.error.BusinessNotFoundException; +import com.orgmemory.core.shared.error.BusinessValidationException; +import java.time.Clock; +import java.time.Instant; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class AssistantEvidenceService { + + private static final PermissionKey CAN_VIEW = PermissionKey.of("can_view"); + private static final String ASSET_TYPE = "knowledge_asset"; + + private final AssistantConversationRepository conversations; + private final AssistantEvidenceBindingRepository bindings; + private final AssistantTurnEvidenceBindingRepository turnBindings; + private final GovernedEvidenceQuery sources; + private final EffectiveAuthorizationService authorization; + private final AssistantEvidenceAnswerabilityPort answerability; + private final Clock clock; + + AssistantEvidenceService( + AssistantConversationRepository conversations, + AssistantEvidenceBindingRepository bindings, + AssistantTurnEvidenceBindingRepository turnBindings, + GovernedEvidenceQuery sources, + EffectiveAuthorizationService authorization, + AssistantEvidenceAnswerabilityPort answerability, + Clock clock) { + this.conversations = conversations; + this.bindings = bindings; + this.turnBindings = turnBindings; + this.sources = sources; + this.authorization = authorization; + this.answerability = answerability; + this.clock = clock; + } + + @Transactional + public AssistantEvidenceBindingView bindUploaded( + CurrentActor actor, + UUID requestedConversationId, + GovernedFileUploadResult upload) { + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(upload, "upload"); + Instant now = clock.instant(); + AssistantConversation conversation; + if (requestedConversationId == null) { + conversation = conversations.save(new AssistantConversation( + UUID.randomUUID(), + actor.organizationId(), + actor.userId(), + upload.fileName(), + now)); + } else { + conversation = conversations + .findForUpdateByIdAndOrganizationIdAndActorUserId( + requestedConversationId, + actor.organizationId(), + actor.userId()) + .orElseThrow(AssistantEvidenceService::notFound); + conversation.touch(now); + } + AssistantEvidenceBinding binding = bindings.save(new AssistantEvidenceBinding( + UUID.randomUUID(), + actor.organizationId(), + conversation.getId(), + actor.userId(), + upload.sourceObjectId(), + upload.sourceRevisionId())); + return evaluate(actor, binding); + } + + @Transactional(readOnly = true) + public AssistantEvidenceBindingView get( + CurrentActor actor, + UUID conversationId, + UUID bindingId) { + requireConversation(actor, conversationId); + return evaluate(actor, requireBinding(actor, conversationId, bindingId)); + } + + @Transactional(readOnly = true) + public List list( + CurrentActor actor, + UUID conversationId) { + requireConversation(actor, conversationId); + return bindings + .findAllByOrganizationIdAndConversationIdAndCreatedByUserIdOrderByCreatedAtAsc( + actor.organizationId(), + conversationId, + actor.userId()) + .stream() + .map(binding -> evaluate(actor, binding)) + .toList(); + } + + void requireUploadConversation( + CurrentActor actor, + UUID conversationId) { + if (conversationId != null) { + requireConversation(actor, conversationId); + } + } + + @Transactional + KnowledgeEvidenceSelection claimForTurn( + CurrentActor actor, + UUID conversationId, + UUID turnId, + UUID userMessageId, + List requestedBindingIds) { + List bindingIds = requestedBindingIds == null + ? List.of() + : List.copyOf(requestedBindingIds); + if (bindingIds.isEmpty()) { + return KnowledgeEvidenceSelection.unrestricted(); + } + if (bindingIds.size() > KnowledgeEvidenceSelection.MAXIMUM_ITEMS + || new LinkedHashSet<>(bindingIds).size() != bindingIds.size()) { + throw new BusinessValidationException( + "assistant.evidence-selection-invalid", + "Select between one and three distinct files"); + } + List found = bindings + .findAllByIdInAndOrganizationIdAndConversationIdAndCreatedByUserId( + bindingIds, + actor.organizationId(), + conversationId, + actor.userId()); + Map byId = new LinkedHashMap<>(); + found.forEach(binding -> byId.put(binding.getId(), binding)); + if (byId.size() != bindingIds.size()) { + throw notFound(); + } + + java.util.ArrayList items = new java.util.ArrayList<>(); + java.util.ArrayList claims = new java.util.ArrayList<>(); + for (int index = 0; index < bindingIds.size(); index++) { + AssistantEvidenceBinding binding = byId.get(bindingIds.get(index)); + Evaluation evaluation = evaluation(actor, binding); + if (evaluation.view().status() != AssistantEvidenceStatus.READY + || evaluation.source() == null) { + throw new BusinessConflictException( + "assistant.evidence-not-ready", + "Selected file evidence is not ready"); + } + GovernedEvidenceRef source = evaluation.source(); + items.add(new KnowledgeEvidenceSelection.Item( + binding.getId(), + source.sourceObjectId(), + source.sourceRevisionId(), + source.knowledgeAssetId())); + claims.add(new AssistantTurnEvidenceBinding( + UUID.randomUUID(), + turnId, + userMessageId, + binding.getId(), + actor.organizationId(), + conversationId, + actor.userId(), + index + 1)); + } + turnBindings.saveAll(claims); + return KnowledgeEvidenceSelection.restricted(items); + } + + private Evaluation evaluation(CurrentActor actor, AssistantEvidenceBinding binding) { + GovernedEvidenceRef source = sources.find( + actor.organizationId(), + binding.sourceObjectId(), + binding.sourceRevisionId()) + .orElse(null); + if (source == null) { + return unavailable(binding, null); + } + AssistantEvidenceStatus status; + String failureCode = source.failureCode(); + if (!source.sourceActive() || !source.latestRevision()) { + status = AssistantEvidenceStatus.UNAVAILABLE; + } else if (source.processingState() == GovernedEvidenceRef.ProcessingState.FAILED + || source.processingState() == GovernedEvidenceRef.ProcessingState.QUARANTINED) { + status = AssistantEvidenceStatus.FAILED; + } else if (source.processingState() != GovernedEvidenceRef.ProcessingState.READY + || !source.currentRevision() + || source.knowledgeAssetId() == null) { + status = AssistantEvidenceStatus.PROCESSING; + } else if (!authorization.authorize( + actor.organizationId(), + actor.principal(), + CAN_VIEW, + ResourceRef.of( + actor.organizationId(), + ASSET_TYPE, + source.knowledgeAssetId())) + .allowed()) { + status = AssistantEvidenceStatus.UNAVAILABLE; + failureCode = null; + } else { + AssistantEvidenceAnswerabilityPort.Answerability engine = answerability.evaluate(source); + status = switch (engine.state()) { + case INDEXING -> AssistantEvidenceStatus.INDEXING; + case READY -> AssistantEvidenceStatus.READY; + case FAILED -> AssistantEvidenceStatus.FAILED; + }; + failureCode = engine.failureCode(); + } + return new Evaluation(view(binding, source, status, failureCode), source); + } + + private AssistantEvidenceBindingView evaluate( + CurrentActor actor, + AssistantEvidenceBinding binding) { + return evaluation(actor, binding).view(); + } + + private static Evaluation unavailable( + AssistantEvidenceBinding binding, + GovernedEvidenceRef source) { + return new Evaluation( + view( + binding, + source, + AssistantEvidenceStatus.UNAVAILABLE, + null), + source); + } + + private static AssistantEvidenceBindingView view( + AssistantEvidenceBinding binding, + GovernedEvidenceRef source, + AssistantEvidenceStatus status, + String failureCode) { + return new AssistantEvidenceBindingView( + binding.getId(), + binding.conversationId(), + binding.sourceObjectId(), + binding.sourceRevisionId(), + source == null ? null : source.knowledgeAssetId(), + source == null ? "Unavailable evidence" : source.title(), + source == null ? "Unavailable file" : source.fileName(), + status, + failureCode, + binding.getCreatedAt()); + } + + private AssistantEvidenceBinding requireBinding( + CurrentActor actor, + UUID conversationId, + UUID bindingId) { + return bindings.findByIdAndOrganizationIdAndConversationIdAndCreatedByUserId( + Objects.requireNonNull(bindingId, "bindingId"), + actor.organizationId(), + Objects.requireNonNull(conversationId, "conversationId"), + actor.userId()) + .orElseThrow(AssistantEvidenceService::notFound); + } + + private void requireConversation(CurrentActor actor, UUID conversationId) { + conversations.findByIdAndOrganizationIdAndActorUserId( + Objects.requireNonNull(conversationId, "conversationId"), + actor.organizationId(), + actor.userId()) + .orElseThrow(AssistantEvidenceService::notFound); + } + + private static BusinessNotFoundException notFound() { + return new BusinessNotFoundException( + "assistant.evidence-not-found", + "Assistant evidence not found", + BusinessErrorExposure.OPAQUE_RESOURCE); + } + + private record Evaluation( + AssistantEvidenceBindingView view, + GovernedEvidenceRef source) { + } +} diff --git a/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceStatus.java b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceStatus.java new file mode 100644 index 000000000..9420d9b5c --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceStatus.java @@ -0,0 +1,9 @@ +package com.orgmemory.core.assistant; + +public enum AssistantEvidenceStatus { + PROCESSING, + INDEXING, + READY, + FAILED, + UNAVAILABLE +} diff --git a/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceTurnClaim.java b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceTurnClaim.java new file mode 100644 index 000000000..2d75c1364 --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceTurnClaim.java @@ -0,0 +1,14 @@ +package com.orgmemory.core.assistant; + +import com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection; +import java.util.Objects; + +public record AssistantEvidenceTurnClaim( + AssistantTurnRef turn, + KnowledgeEvidenceSelection selection) { + + public AssistantEvidenceTurnClaim { + Objects.requireNonNull(turn, "turn"); + Objects.requireNonNull(selection, "selection"); + } +} diff --git a/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceUploadService.java b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceUploadService.java new file mode 100644 index 000000000..ea9f2d1ef --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/assistant/AssistantEvidenceUploadService.java @@ -0,0 +1,46 @@ +package com.orgmemory.core.assistant; + +import com.orgmemory.core.knowledge.evidence.GovernedFileUpload; +import com.orgmemory.core.knowledge.evidence.GovernedFileUploadCommand; +import com.orgmemory.core.knowledge.evidence.GovernedFileUploadResult; +import com.orgmemory.core.organization.CurrentActor; +import com.orgmemory.core.permission.KnowledgeClassification; +import java.io.InputStream; +import java.util.Objects; +import java.util.UUID; +import org.springframework.stereotype.Service; + +/** Multipart orchestration that creates one governed Source and then its binding. */ +@Service +public class AssistantEvidenceUploadService { + + private final GovernedFileUpload sourceUploads; + private final AssistantEvidenceService evidence; + + AssistantEvidenceUploadService( + GovernedFileUpload sourceUploads, + AssistantEvidenceService evidence) { + this.sourceUploads = sourceUploads; + this.evidence = evidence; + } + + public AssistantEvidenceBindingView upload( + CurrentActor actor, + UUID conversationId, + UUID knowledgeSpaceId, + KnowledgeClassification classification, + String fileName, + long contentLength, + InputStream content) { + evidence.requireUploadConversation(actor, conversationId); + GovernedFileUploadResult uploaded = sourceUploads.upload( + new GovernedFileUploadCommand( + Objects.requireNonNull(actor, "actor"), + fileName, + contentLength, + classification, + knowledgeSpaceId), + content); + return evidence.bindUploaded(actor, conversationId, uploaded); + } +} diff --git a/core/src/main/java/com/orgmemory/core/assistant/AssistantService.java b/core/src/main/java/com/orgmemory/core/assistant/AssistantService.java index 2ff706e5d..152d8a13d 100644 --- a/core/src/main/java/com/orgmemory/core/assistant/AssistantService.java +++ b/core/src/main/java/com/orgmemory/core/assistant/AssistantService.java @@ -11,6 +11,7 @@ import com.orgmemory.core.assistant.observability.AssistantTurnObservationDocumentation; import com.orgmemory.core.assistant.observability.DefaultAssistantTurnObservationConvention; import com.orgmemory.core.knowledge.search.PermissionAwareKnowledgeSearch; +import com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection; import com.orgmemory.core.knowledge.search.RetrievedKnowledgeEvidence; import com.orgmemory.core.organization.CurrentActor; import io.micrometer.observation.Observation; @@ -143,6 +144,26 @@ public AssistantTurn startTurn( String conversationId, AssistantModelRouteAuthority routeAuthority, long startedAtNanos) { + return startTurn( + actor, + question, + requestedLimit, + requestId, + conversationId, + routeAuthority, + startedAtNanos, + KnowledgeEvidenceSelection.unrestricted()); + } + + public AssistantTurn startTurn( + CurrentActor actor, + String question, + Integer requestedLimit, + String requestId, + String conversationId, + AssistantModelRouteAuthority routeAuthority, + long startedAtNanos, + KnowledgeEvidenceSelection evidenceSelection) { AssistantTurnObservationContext context = new AssistantTurnObservationContext( actor.organizationId(), engine, startedAtNanos); Observation observation = AssistantTurnObservationDocumentation.TURN @@ -150,8 +171,28 @@ public AssistantTurn startTurn( .start(); try { - var search = retrieval.search(actor, question, requestedLimit, requestId); + KnowledgeEvidenceSelection selection = evidenceSelection == null + ? KnowledgeEvidenceSelection.unrestricted() + : evidenceSelection; + var search = selection.restricted() + ? retrieval.search( + actor, + question, + requestedLimit, + requestId, + selection) + : retrieval.search( + actor, + question, + requestedLimit, + requestId); context.retrievalCompletedAt(System.nanoTime()); + if (!selection.missingSourceObjectIds(search.evidence()).isEmpty()) { + throw new AssistantUnavailableException( + "Selected file evidence is unavailable", + null, + "assistant_evidence_unavailable"); + } if (search.evidence().isEmpty()) { context.foundNoEvidence(System.nanoTime()); observation.stop(); diff --git a/core/src/main/java/com/orgmemory/core/assistant/AssistantTurnEvidenceBinding.java b/core/src/main/java/com/orgmemory/core/assistant/AssistantTurnEvidenceBinding.java new file mode 100644 index 000000000..35f56d45f --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/assistant/AssistantTurnEvidenceBinding.java @@ -0,0 +1,59 @@ +package com.orgmemory.core.assistant; + +import com.orgmemory.core.shared.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import java.util.Objects; +import java.util.UUID; + +@Entity +@Table(name = "assistant_turn_evidence_bindings") +class AssistantTurnEvidenceBinding extends BaseEntity { + + @Column(name = "turn_id", nullable = false, updatable = false) + private UUID turnId; + + @Column(name = "user_message_id", nullable = false, updatable = false) + private UUID userMessageId; + + @Column(name = "binding_id", nullable = false, updatable = false) + private UUID bindingId; + + @Column(name = "organization_id", nullable = false, updatable = false) + private UUID organizationId; + + @Column(name = "conversation_id", nullable = false, updatable = false) + private UUID conversationId; + + @Column(name = "actor_user_id", nullable = false, updatable = false) + private UUID actorUserId; + + @Column(nullable = false, updatable = false) + private int ordinal; + + protected AssistantTurnEvidenceBinding() { + } + + AssistantTurnEvidenceBinding( + UUID id, + UUID turnId, + UUID userMessageId, + UUID bindingId, + UUID organizationId, + UUID conversationId, + UUID actorUserId, + int ordinal) { + super(Objects.requireNonNull(id, "id")); + if (ordinal < 1 || ordinal > 3) { + throw new IllegalArgumentException("evidence ordinal must be between one and three"); + } + this.turnId = Objects.requireNonNull(turnId, "turnId"); + this.userMessageId = Objects.requireNonNull(userMessageId, "userMessageId"); + this.bindingId = Objects.requireNonNull(bindingId, "bindingId"); + this.organizationId = Objects.requireNonNull(organizationId, "organizationId"); + this.conversationId = Objects.requireNonNull(conversationId, "conversationId"); + this.actorUserId = Objects.requireNonNull(actorUserId, "actorUserId"); + this.ordinal = ordinal; + } +} diff --git a/core/src/main/java/com/orgmemory/core/assistant/AssistantTurnEvidenceBindingRepository.java b/core/src/main/java/com/orgmemory/core/assistant/AssistantTurnEvidenceBindingRepository.java new file mode 100644 index 000000000..175772e5a --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/assistant/AssistantTurnEvidenceBindingRepository.java @@ -0,0 +1,8 @@ +package com.orgmemory.core.assistant; + +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; + +interface AssistantTurnEvidenceBindingRepository + extends JpaRepository { +} diff --git a/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedEvidenceQuery.java b/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedEvidenceQuery.java new file mode 100644 index 000000000..8c8f66aab --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedEvidenceQuery.java @@ -0,0 +1,13 @@ +package com.orgmemory.core.knowledge.evidence; + +import java.util.Optional; +import java.util.UUID; + +/** Resolves one exact organization-scoped Source revision without exporting persistence. */ +public interface GovernedEvidenceQuery { + + Optional find( + UUID organizationId, + UUID sourceObjectId, + UUID sourceRevisionId); +} diff --git a/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedEvidenceRef.java b/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedEvidenceRef.java new file mode 100644 index 000000000..d439d213f --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedEvidenceRef.java @@ -0,0 +1,46 @@ +package com.orgmemory.core.knowledge.evidence; + +import java.util.Objects; +import java.util.UUID; + +public record GovernedEvidenceRef( + UUID organizationId, + UUID knowledgeSpaceId, + UUID sourceObjectId, + UUID sourceRevisionId, + ProcessingState processingState, + boolean sourceActive, + boolean latestRevision, + boolean currentRevision, + UUID knowledgeAssetId, + UUID knowledgeAssetVersionId, + String title, + String fileName, + String failureCode) { + + public GovernedEvidenceRef { + Objects.requireNonNull(organizationId, "organizationId"); + Objects.requireNonNull(knowledgeSpaceId, "knowledgeSpaceId"); + Objects.requireNonNull(sourceObjectId, "sourceObjectId"); + Objects.requireNonNull(sourceRevisionId, "sourceRevisionId"); + Objects.requireNonNull(processingState, "processingState"); + title = Objects.requireNonNull(title, "title"); + fileName = Objects.requireNonNull(fileName, "fileName"); + } + + public boolean readyAndCurrent() { + return sourceActive + && latestRevision + && currentRevision + && processingState == ProcessingState.READY + && knowledgeAssetId != null + && knowledgeAssetVersionId != null; + } + + public enum ProcessingState { + PROCESSING, + READY, + FAILED, + QUARANTINED + } +} diff --git a/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedFileUpload.java b/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedFileUpload.java new file mode 100644 index 000000000..79b008cf2 --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedFileUpload.java @@ -0,0 +1,11 @@ +package com.orgmemory.core.knowledge.evidence; + +import java.io.InputStream; + +/** Registers bytes through the canonical governed Source pipeline. */ +public interface GovernedFileUpload { + + GovernedFileUploadResult upload( + GovernedFileUploadCommand command, + InputStream content); +} diff --git a/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedFileUploadCommand.java b/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedFileUploadCommand.java new file mode 100644 index 000000000..b8a59b85a --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedFileUploadCommand.java @@ -0,0 +1,18 @@ +package com.orgmemory.core.knowledge.evidence; + +import com.orgmemory.core.organization.CurrentActor; +import com.orgmemory.core.permission.KnowledgeClassification; +import java.util.Objects; +import java.util.UUID; + +public record GovernedFileUploadCommand( + CurrentActor actor, + String fileName, + long contentLength, + KnowledgeClassification classification, + UUID knowledgeSpaceId) { + + public GovernedFileUploadCommand { + Objects.requireNonNull(actor, "actor"); + } +} diff --git a/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedFileUploadResult.java b/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedFileUploadResult.java new file mode 100644 index 000000000..442103691 --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/knowledge/evidence/GovernedFileUploadResult.java @@ -0,0 +1,18 @@ +package com.orgmemory.core.knowledge.evidence; + +import java.util.Objects; +import java.util.UUID; + +public record GovernedFileUploadResult( + UUID sourceObjectId, + UUID sourceRevisionId, + UUID knowledgeSpaceId, + String fileName) { + + public GovernedFileUploadResult { + Objects.requireNonNull(sourceObjectId, "sourceObjectId"); + Objects.requireNonNull(sourceRevisionId, "sourceRevisionId"); + Objects.requireNonNull(knowledgeSpaceId, "knowledgeSpaceId"); + fileName = Objects.requireNonNull(fileName, "fileName"); + } +} diff --git a/core/src/main/java/com/orgmemory/core/knowledge/evidence/package-info.java b/core/src/main/java/com/orgmemory/core/knowledge/evidence/package-info.java new file mode 100644 index 000000000..609bf97c7 --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/knowledge/evidence/package-info.java @@ -0,0 +1,5 @@ +/** + * Channel-neutral governed file registration and exact evidence identity contracts. + */ +@org.springframework.modulith.NamedInterface("evidence") +package com.orgmemory.core.knowledge.evidence; diff --git a/core/src/main/java/com/orgmemory/core/knowledge/graph/GraphEvidenceAnswerability.java b/core/src/main/java/com/orgmemory/core/knowledge/graph/GraphEvidenceAnswerability.java new file mode 100644 index 000000000..2d7f83c09 --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/knowledge/graph/GraphEvidenceAnswerability.java @@ -0,0 +1,23 @@ +package com.orgmemory.core.knowledge.graph; + +/** Active GraphRAG projection state for one exact Source revision. */ +public record GraphEvidenceAnswerability(State state, String failureCode) { + + public enum State { + INDEXING, + READY, + FAILED + } + + public static GraphEvidenceAnswerability indexing() { + return new GraphEvidenceAnswerability(State.INDEXING, null); + } + + public static GraphEvidenceAnswerability ready() { + return new GraphEvidenceAnswerability(State.READY, null); + } + + public static GraphEvidenceAnswerability failed(String failureCode) { + return new GraphEvidenceAnswerability(State.FAILED, failureCode); + } +} diff --git a/core/src/main/java/com/orgmemory/core/knowledge/graph/GraphEvidenceAnswerabilityQuery.java b/core/src/main/java/com/orgmemory/core/knowledge/graph/GraphEvidenceAnswerabilityQuery.java new file mode 100644 index 000000000..2bbf25f95 --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/knowledge/graph/GraphEvidenceAnswerabilityQuery.java @@ -0,0 +1,68 @@ +package com.orgmemory.core.knowledge.graph; + +import com.orgmemory.core.knowledge.asset.KnowledgeProjectionNamespaces; +import com.orgmemory.core.knowledge.evidence.GovernedEvidenceRef; +import com.orgmemory.graphrag.storage.ProjectionPublicationStore; +import java.util.Objects; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** Proves that the exact bound revision is visible to the active GraphRAG profile. */ +@Service +public class GraphEvidenceAnswerabilityQuery { + + private final GraphProcessingProfileResolver profileResolver; + private final GraphProcessingProfileRepository profiles; + private final GraphIndexJobRepository jobs; + private final ProjectionPublicationStore publications; + + GraphEvidenceAnswerabilityQuery( + GraphProcessingProfileResolver profileResolver, + GraphProcessingProfileRepository profiles, + GraphIndexJobRepository jobs, + ProjectionPublicationStore publications) { + this.profileResolver = profileResolver; + this.profiles = profiles; + this.jobs = jobs; + this.publications = publications; + } + + @Transactional(readOnly = true) + public GraphEvidenceAnswerability evaluate(GovernedEvidenceRef source) { + Objects.requireNonNull(source, "source"); + if (!source.readyAndCurrent()) { + return GraphEvidenceAnswerability.indexing(); + } + var desiredProfile = profileResolver.current(); + var profile = profiles.findByCanonicalSha256(desiredProfile.canonicalSha256()) + .map(PersistedGraphProcessingProfile::toRef) + .orElse(null); + if (profile == null) { + return GraphEvidenceAnswerability.indexing(); + } + GraphIndexJob job = jobs.findByKnowledgeAssetVersionIdAndGraphProcessingProfileId( + source.knowledgeAssetVersionId(), profile.id()) + .orElse(null); + if (job == null) { + return GraphEvidenceAnswerability.indexing(); + } + if (job.getStatus() == GraphIndexJobStatus.FAILED + || job.getStatus() == GraphIndexJobStatus.CANCELLED + || job.getStatus() == GraphIndexJobStatus.SUPERSEDED) { + return GraphEvidenceAnswerability.failed(job.getLastErrorCode()); + } + if (job.getStatus() != GraphIndexJobStatus.SUCCEEDED) { + return GraphEvidenceAnswerability.indexing(); + } + var namespace = KnowledgeProjectionNamespaces.forSpace( + source.organizationId(), source.knowledgeSpaceId()); + var exact = publications.published(namespace, job.getProjectionGeneration()); + var current = publications.current(namespace); + if (exact.isEmpty() + || current.isEmpty() + || current.orElseThrow().generation() < job.getProjectionGeneration()) { + return GraphEvidenceAnswerability.indexing(); + } + return GraphEvidenceAnswerability.ready(); + } +} diff --git a/core/src/main/java/com/orgmemory/core/knowledge/graph/package-info.java b/core/src/main/java/com/orgmemory/core/knowledge/graph/package-info.java index f00e010b4..fe72cc756 100644 --- a/core/src/main/java/com/orgmemory/core/knowledge/graph/package-info.java +++ b/core/src/main/java/com/orgmemory/core/knowledge/graph/package-info.java @@ -13,6 +13,7 @@ "authorization", "knowledge.acl", "knowledge.asset", + "knowledge::evidence", "knowledge.retrieval", "knowledge.sourceledger", "knowledge.space", diff --git a/core/src/main/java/com/orgmemory/core/knowledge/retrieval/DefaultCanonicalHybridKnowledgeSearch.java b/core/src/main/java/com/orgmemory/core/knowledge/retrieval/DefaultCanonicalHybridKnowledgeSearch.java index 501cd6e14..d0282b975 100644 --- a/core/src/main/java/com/orgmemory/core/knowledge/retrieval/DefaultCanonicalHybridKnowledgeSearch.java +++ b/core/src/main/java/com/orgmemory/core/knowledge/retrieval/DefaultCanonicalHybridKnowledgeSearch.java @@ -1,6 +1,7 @@ package com.orgmemory.core.knowledge.retrieval; import com.orgmemory.core.knowledge.search.RetrievedKnowledgeEvidence; +import com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection; import com.orgmemory.core.knowledge.search.SecureKnowledgeSearchResult; import com.orgmemory.core.knowledge.sourceledger.SourceCitationUri; @@ -19,6 +20,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -81,6 +83,36 @@ public SecureKnowledgeSearchResult search( String query, Integer requestedLimit, String suppliedRequestId) { + return searchWithinTransaction( + actor, + query, + requestedLimit, + suppliedRequestId, + KnowledgeEvidenceSelection.unrestricted()); + } + + @Transactional(readOnly = true) + @Override + public SecureKnowledgeSearchResult search( + CurrentActor actor, + String query, + Integer requestedLimit, + String suppliedRequestId, + KnowledgeEvidenceSelection selection) { + return searchWithinTransaction( + actor, + query, + requestedLimit, + suppliedRequestId, + Objects.requireNonNull(selection, "selection")); + } + + private SecureKnowledgeSearchResult searchWithinTransaction( + CurrentActor actor, + String query, + Integer requestedLimit, + String suppliedRequestId, + KnowledgeEvidenceSelection selection) { String requestId = requestId(suppliedRequestId); String normalizedQuery = normalizeQuery(query); int limit = validateLimit(requestedLimit); @@ -101,7 +133,10 @@ public SecureKnowledgeSearchResult search( unavailable.policyVersion(), unavailable); } - Set authorizedAssetIds = evidenceScope.allAssetIds(); + Set authorizedAssetIds = new LinkedHashSet<>(evidenceScope.allAssetIds()); + if (selection.restricted()) { + authorizedAssetIds.retainAll(selection.assetIds()); + } if (authorizedAssetIds.isEmpty()) { audit.record(searchAudit( actor, @@ -133,6 +168,7 @@ public SecureKnowledgeSearchResult search( normalizedQuery, lexical, authorizedAssetIds, + selection, evidenceScope.authorizationModelId()); validateCandidateSet( actor, @@ -140,6 +176,7 @@ public SecureKnowledgeSearchResult search( normalizedQuery, semantic, authorizedAssetIds, + selection, evidenceScope.authorizationModelId()); List ranked = fuse(lexical, semantic).stream() @@ -217,6 +254,17 @@ public SecureKnowledgeSearchResult search( "CANONICAL_RECHECK_DENIED")); continue; } + if (!selection.contains( + canonical.knowledgeAssetId(), + canonical.sourceObjectId(), + canonical.sourceRevisionId())) { + throw searchAuthorization.unavailable( + actor, + requestId, + normalizedQuery, + "RETRIEVAL_EVIDENCE_SELECTION_BOUNDARY_VIOLATION", + evidenceScope.authorizationModelId()); + } auditCommands.add(evidenceAudit( actor, requestId, @@ -254,10 +302,15 @@ private void validateCandidateSet( String query, List candidates, Set authorizedAssetIds, + KnowledgeEvidenceSelection selection, String policyVersion) { boolean invalid = candidates.stream() .anyMatch(candidate -> !actor.organizationId().equals(candidate.organizationId()) - || !authorizedAssetIds.contains(candidate.knowledgeAssetId())); + || !authorizedAssetIds.contains(candidate.knowledgeAssetId()) + || !selection.contains( + candidate.knowledgeAssetId(), + candidate.sourceObjectId(), + candidate.sourceRevisionId())); if (invalid) { throw searchAuthorization.unavailable( actor, diff --git a/core/src/main/java/com/orgmemory/core/knowledge/retrieval/DefaultGraphRagKnowledgeRetrievalService.java b/core/src/main/java/com/orgmemory/core/knowledge/retrieval/DefaultGraphRagKnowledgeRetrievalService.java index 7b67e93ea..0cb8eefae 100644 --- a/core/src/main/java/com/orgmemory/core/knowledge/retrieval/DefaultGraphRagKnowledgeRetrievalService.java +++ b/core/src/main/java/com/orgmemory/core/knowledge/retrieval/DefaultGraphRagKnowledgeRetrievalService.java @@ -6,6 +6,7 @@ import com.orgmemory.core.authorization.RelationshipAuthorizationSetPort; import com.orgmemory.core.authorization.ResourceRef; import com.orgmemory.core.knowledge.asset.KnowledgeProjectionNamespaces; +import com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection; import com.orgmemory.core.knowledge.search.RetrievedKnowledgeEvidence; import com.orgmemory.core.knowledge.search.SecureKnowledgeSearchResult; import com.orgmemory.core.knowledge.search.VerifiedKnowledgeGrounding; @@ -201,7 +202,26 @@ public SecureKnowledgeSearchResult search( actor, normalizedQuery, requestId(suppliedRequestId), - policy.contextOptions(limit)) + policy.contextOptions(limit), + KnowledgeEvidenceSelection.unrestricted()) + .result(); + } + + @Override + public SecureKnowledgeSearchResult search( + CurrentActor actor, + String query, + Integer requestedLimit, + String suppliedRequestId, + KnowledgeEvidenceSelection selection) { + String normalizedQuery = normalizeQuery(query); + int limit = validateLimit(requestedLimit); + return runRetrieval( + actor, + normalizedQuery, + requestId(suppliedRequestId), + policy.contextOptions(limit), + Objects.requireNonNull(selection, "selection")) .result(); } @@ -216,12 +236,14 @@ public RetrievalObservation observe( actor, normalizedQuery, observationRequestId(requestId, "-keyword"), - observationOptions(LightRagQueryMode.MIX, DIAGNOSTIC_TOP_K)); + observationOptions(LightRagQueryMode.MIX, DIAGNOSTIC_TOP_K), + KnowledgeEvidenceSelection.unrestricted()); RetrievalRun bypass = runRetrieval( actor, normalizedQuery, observationRequestId(requestId, "-bypass"), - observationOptions(LightRagQueryMode.NAIVE, RECALL_TOP_K)); + observationOptions(LightRagQueryMode.NAIVE, RECALL_TOP_K), + KnowledgeEvidenceSelection.unrestricted()); KeywordPlan keywordPlan = keywordSeeded.keywords(); return new RetrievalObservation( documents(keywordSeeded.result()), @@ -236,7 +258,8 @@ private RetrievalRun runRetrieval( CurrentActor actor, String normalizedQuery, String requestId, - LightRagQueryRequest.Options options) { + LightRagQueryRequest.Options options, + KnowledgeEvidenceSelection selection) { Objects.requireNonNull(actor, "actor"); UUID operationId = UUID.randomUUID(); long startedAt = System.nanoTime(); @@ -260,7 +283,8 @@ private RetrievalRun runRetrieval( requestId, authorizationModelId, operationId, - 0); + 0, + selection); emit( operationId, actor.organizationId(), @@ -365,14 +389,17 @@ private RetrievalRun search( String requestId, String authorizationModelId, UUID operationId, - int attempt) { + int attempt, + KnowledgeEvidenceSelection selection) { ResolvedKnowledgeEvidenceScope initial = - resolve( + restrict( + resolve( actor, authorizationModelId, requestId, query, - operationId); + operationId), + selection); if (initial.allAssetIds().isEmpty()) { audit.record(searchAuthorization.command( actor, @@ -443,12 +470,14 @@ private RetrievalRun search( } ResolvedKnowledgeEvidenceScope current = - resolve( + restrict( + resolve( actor, authorizationModelId, requestId, query, - operationId); + operationId), + selection); if (!sameAuthorizationScope(initial, current)) { return retryOrFail( actor, @@ -458,6 +487,7 @@ private RetrievalRun search( authorizationModelId, operationId, attempt, + selection, "AUTHORIZATION_SCOPE_CHANGED"); } @@ -496,8 +526,20 @@ private RetrievalRun search( authorizationModelId, operationId, attempt, + selection, "CANONICAL_EVIDENCE_CHANGED"); } + if (verified.stream().anyMatch(candidate -> !selection.contains( + candidate.knowledgeAssetId(), + candidate.sourceObjectId(), + candidate.sourceRevisionId()))) { + throw searchAuthorization.unavailable( + actor, + requestId, + query, + "RETRIEVAL_EVIDENCE_SELECTION_BOUNDARY_VIOLATION", + current.authorizationModelId()); + } LightRagGroundingAssembler.PreparedGrounding rendered = engine.renderGrounding( query, @@ -1041,6 +1083,7 @@ private RetrievalRun retryOrFail( String authorizationModelId, UUID operationId, int attempt, + KnowledgeEvidenceSelection selection, String reason) { if (attempt == 0) { return search( @@ -1050,7 +1093,8 @@ private RetrievalRun retryOrFail( requestId, authorizationModelId, operationId, - 1); + 1, + selection); } throw searchAuthorization.unavailable( actor, @@ -1060,6 +1104,14 @@ private RetrievalRun retryOrFail( authorizationModelId); } + private static ResolvedKnowledgeEvidenceScope restrict( + ResolvedKnowledgeEvidenceScope scope, + KnowledgeEvidenceSelection selection) { + return selection.restricted() + ? scope.restrictTo(selection.assetIds()) + : scope; + } + private void emitRerank( UUID operationId, UUID organizationId, diff --git a/core/src/main/java/com/orgmemory/core/knowledge/retrieval/ResolvedKnowledgeEvidenceScope.java b/core/src/main/java/com/orgmemory/core/knowledge/retrieval/ResolvedKnowledgeEvidenceScope.java index dd809768f..4259eba1f 100644 --- a/core/src/main/java/com/orgmemory/core/knowledge/retrieval/ResolvedKnowledgeEvidenceScope.java +++ b/core/src/main/java/com/orgmemory/core/knowledge/retrieval/ResolvedKnowledgeEvidenceScope.java @@ -52,6 +52,31 @@ public Set knowledgeSpaceIds() { return assetIdsByKnowledgeSpace.keySet(); } + public ResolvedKnowledgeEvidenceScope restrictTo(Set permittedAssetIds) { + Set permitted = Set.copyOf(Objects.requireNonNull( + permittedAssetIds, + "permittedAssetIds")); + Map> restrictedAssets = new LinkedHashMap<>(); + Map restrictedGenerations = new LinkedHashMap<>(); + assetIdsByKnowledgeSpace.forEach((spaceId, assetIds) -> { + LinkedHashSet intersection = new LinkedHashSet<>(assetIds); + intersection.retainAll(permitted); + if (!intersection.isEmpty()) { + restrictedAssets.put(spaceId, Set.copyOf(intersection)); + restrictedGenerations.put(spaceId, aclGenerationByKnowledgeSpace.get(spaceId)); + } + }); + return new ResolvedKnowledgeEvidenceScope( + organizationId, + actorUserId, + actorDepartmentId, + actorExecutive, + authorizationModelId, + evaluatedAt, + restrictedAssets, + restrictedGenerations); + } + public SecureKnowledgeRetrievalStore.RetrievalScope toRetrievalScope() { return new SecureKnowledgeRetrievalStore.RetrievalScope( organizationId, diff --git a/core/src/main/java/com/orgmemory/core/knowledge/search/KnowledgeEvidenceSelection.java b/core/src/main/java/com/orgmemory/core/knowledge/search/KnowledgeEvidenceSelection.java new file mode 100644 index 000000000..45d6b0ce2 --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/knowledge/search/KnowledgeEvidenceSelection.java @@ -0,0 +1,92 @@ +package com.orgmemory.core.knowledge.search; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * Optional second retrieval ceiling for one Assistant turn. + * + *

Actor authorization remains the first ceiling. A restricted selection can + * only narrow it and carries immutable Source/revision/Asset identity so a + * later revision or a graph neighbor cannot silently replace the uploaded + * evidence. + */ +public record KnowledgeEvidenceSelection(boolean restricted, List items) { + + public static final int MAXIMUM_ITEMS = 3; + + public KnowledgeEvidenceSelection { + items = List.copyOf(Objects.requireNonNull(items, "items")); + if (!restricted && !items.isEmpty()) { + throw new IllegalArgumentException("an unrestricted selection has no items"); + } + if (restricted && (items.isEmpty() || items.size() > MAXIMUM_ITEMS)) { + throw new IllegalArgumentException("a restricted selection requires between one and three items"); + } + if (new LinkedHashSet<>(items.stream().map(Item::bindingId).toList()).size() != items.size()) { + throw new IllegalArgumentException("binding ids must be unique"); + } + if (new LinkedHashSet<>(items.stream().map(Item::sourceObjectId).toList()).size() != items.size()) { + throw new IllegalArgumentException("selected Source objects must be unique"); + } + } + + public static KnowledgeEvidenceSelection unrestricted() { + return new KnowledgeEvidenceSelection(false, List.of()); + } + + public static KnowledgeEvidenceSelection restricted(List items) { + return new KnowledgeEvidenceSelection(true, items); + } + + public Set assetIds() { + return Set.copyOf(items.stream().map(Item::knowledgeAssetId).toList()); + } + + public boolean contains( + UUID knowledgeAssetId, + UUID sourceObjectId, + UUID sourceRevisionId) { + return !restricted || items.stream().anyMatch(item -> + item.knowledgeAssetId().equals(knowledgeAssetId) + && item.sourceObjectId().equals(sourceObjectId) + && item.sourceRevisionId().equals(sourceRevisionId)); + } + + public Set missingSourceObjectIds(List evidence) { + if (!restricted) { + return Set.of(); + } + Set found = evidence.stream() + .filter(item -> contains( + item.knowledgeAssetId(), + item.sourceObjectId(), + item.sourceRevisionId())) + .map(RetrievedKnowledgeEvidence::sourceObjectId) + .collect(java.util.stream.Collectors.toSet()); + LinkedHashSet missing = new LinkedHashSet<>(); + for (Item item : items) { + if (!found.contains(item.sourceObjectId())) { + missing.add(item.sourceObjectId()); + } + } + return Set.copyOf(missing); + } + + public record Item( + UUID bindingId, + UUID sourceObjectId, + UUID sourceRevisionId, + UUID knowledgeAssetId) { + + public Item { + Objects.requireNonNull(bindingId, "bindingId"); + Objects.requireNonNull(sourceObjectId, "sourceObjectId"); + Objects.requireNonNull(sourceRevisionId, "sourceRevisionId"); + Objects.requireNonNull(knowledgeAssetId, "knowledgeAssetId"); + } + } +} diff --git a/core/src/main/java/com/orgmemory/core/knowledge/search/PermissionAwareKnowledgeSearch.java b/core/src/main/java/com/orgmemory/core/knowledge/search/PermissionAwareKnowledgeSearch.java index 4de8bacb1..42b9558db 100644 --- a/core/src/main/java/com/orgmemory/core/knowledge/search/PermissionAwareKnowledgeSearch.java +++ b/core/src/main/java/com/orgmemory/core/knowledge/search/PermissionAwareKnowledgeSearch.java @@ -10,4 +10,24 @@ SecureKnowledgeSearchResult search( String query, Integer requestedLimit, String requestId); + + /** + * Applies an optional caller-supplied narrowing ceiling. Implementations + * must override restricted selection; silently broadening it to ordinary + * organizational search would be an authorization-boundary failure. + */ + default SecureKnowledgeSearchResult search( + CurrentActor actor, + String query, + Integer requestedLimit, + String requestId, + KnowledgeEvidenceSelection selection) { + KnowledgeEvidenceSelection required = selection == null + ? KnowledgeEvidenceSelection.unrestricted() + : selection; + if (required.restricted()) { + throw new IllegalStateException("selected evidence is not supported by this retrieval engine"); + } + return search(actor, query, requestedLimit, requestId); + } } diff --git a/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceLedgerGovernedEvidenceQuery.java b/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceLedgerGovernedEvidenceQuery.java new file mode 100644 index 000000000..b7c598e2e --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceLedgerGovernedEvidenceQuery.java @@ -0,0 +1,68 @@ +package com.orgmemory.core.knowledge.sourceledger; + +import com.orgmemory.core.knowledge.evidence.GovernedEvidenceQuery; +import com.orgmemory.core.knowledge.evidence.GovernedEvidenceRef; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +class SourceLedgerGovernedEvidenceQuery implements GovernedEvidenceQuery { + + private final SourceObjectRepository sources; + private final SourceRevisionRepository revisions; + + SourceLedgerGovernedEvidenceQuery( + SourceObjectRepository sources, + SourceRevisionRepository revisions) { + this.sources = sources; + this.revisions = revisions; + } + + @Override + public Optional find( + UUID organizationId, + UUID sourceObjectId, + UUID sourceRevisionId) { + UUID organization = Objects.requireNonNull(organizationId, "organizationId"); + SourceObject source = sources.findByIdAndOrganizationId( + Objects.requireNonNull(sourceObjectId, "sourceObjectId"), + organization) + .orElse(null); + SourceRevision revision = revisions.findByIdAndOrganizationId( + Objects.requireNonNull(sourceRevisionId, "sourceRevisionId"), + organization) + .orElse(null); + if (source == null + || revision == null + || !source.getId().equals(revision.getSourceObjectId())) { + return Optional.empty(); + } + return Optional.of(new GovernedEvidenceRef( + organization, + source.getKnowledgeSpaceId(), + source.getId(), + revision.getId(), + state(revision.getStatus()), + source.getStatus() == SourceObjectStatus.ACTIVE, + revision.getId().equals(source.getLatestRevisionId()), + revision.getId().equals(source.getCurrentRevisionId()), + revision.getKnowledgeAssetId(), + revision.getKnowledgeAssetVersionId(), + source.getTitle(), + revision.getFileName(), + revision.getFailureCode())); + } + + private static GovernedEvidenceRef.ProcessingState state(SourceRevisionStatus status) { + return switch (status) { + case READY -> GovernedEvidenceRef.ProcessingState.READY; + case FAILED -> GovernedEvidenceRef.ProcessingState.FAILED; + case QUARANTINED -> GovernedEvidenceRef.ProcessingState.QUARANTINED; + default -> GovernedEvidenceRef.ProcessingState.PROCESSING; + }; + } +} diff --git a/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceLedgerGovernedFileUpload.java b/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceLedgerGovernedFileUpload.java new file mode 100644 index 000000000..cc8a58dbc --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceLedgerGovernedFileUpload.java @@ -0,0 +1,36 @@ +package com.orgmemory.core.knowledge.sourceledger; + +import com.orgmemory.core.knowledge.evidence.GovernedFileUpload; +import com.orgmemory.core.knowledge.evidence.GovernedFileUploadCommand; +import com.orgmemory.core.knowledge.evidence.GovernedFileUploadResult; +import java.io.InputStream; +import org.springframework.stereotype.Service; + +@Service +class SourceLedgerGovernedFileUpload implements GovernedFileUpload { + + private final SourceUploadService uploads; + + SourceLedgerGovernedFileUpload(SourceUploadService uploads) { + this.uploads = uploads; + } + + @Override + public GovernedFileUploadResult upload( + GovernedFileUploadCommand command, + InputStream content) { + SourceUploadResult result = uploads.uploadWithIdentity( + new CreateUploadSourceCommand( + command.actor(), + command.fileName(), + command.contentLength(), + command.classification(), + command.knowledgeSpaceId()), + content); + return new GovernedFileUploadResult( + result.summary().id(), + result.sourceRevisionId(), + result.knowledgeSpaceId(), + result.summary().fileName()); + } +} diff --git a/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadRegistrationService.java b/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadRegistrationService.java index 337661860..9fdebad43 100644 --- a/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadRegistrationService.java +++ b/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadRegistrationService.java @@ -36,7 +36,7 @@ public class SourceUploadRegistrationService { } @Transactional - SourceSummary register( + SourceUploadResult register( UUID sourceId, UUID revisionId, UUID blobId, @@ -67,12 +67,15 @@ SourceSummary register( : provenance.departmentNames( actor.organizationId(), java.util.List.of(targetSpace.departmentId())) .get(targetSpace.departmentId()); - return SourceQueryService.summary( - source, - revision, - null, - targetSpace, - owningDepartmentName, - actor.name()); + return new SourceUploadResult( + SourceQueryService.summary( + source, + revision, + null, + targetSpace, + owningDepartmentName, + actor.name()), + revision.getId(), + targetSpace.id()); } } diff --git a/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadResult.java b/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadResult.java new file mode 100644 index 000000000..024b664dc --- /dev/null +++ b/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadResult.java @@ -0,0 +1,17 @@ +package com.orgmemory.core.knowledge.sourceledger; + +import java.util.Objects; +import java.util.UUID; + +/** Immutable identity returned to governed channels that bind an upload. */ +record SourceUploadResult( + SourceSummary summary, + UUID sourceRevisionId, + UUID knowledgeSpaceId) { + + public SourceUploadResult { + Objects.requireNonNull(summary, "summary"); + Objects.requireNonNull(sourceRevisionId, "sourceRevisionId"); + Objects.requireNonNull(knowledgeSpaceId, "knowledgeSpaceId"); + } +} diff --git a/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadService.java b/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadService.java index 2ae8df82e..ce5a51b58 100644 --- a/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadService.java +++ b/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadService.java @@ -40,6 +40,12 @@ public class SourceUploadService { } public SourceSummary upload(CreateUploadSourceCommand command, InputStream content) { + return uploadWithIdentity(command, content).summary(); + } + + SourceUploadResult uploadWithIdentity( + CreateUploadSourceCommand command, + InputStream content) { validate(command, content); CurrentActor actor = command.actor(); SourceKnowledgeSpaceRef targetSpace = knowledgeSpaces.requireUploadTarget( diff --git a/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/package-info.java b/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/package-info.java index 93d9dc8e9..9ecd8a625 100644 --- a/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/package-info.java +++ b/core/src/main/java/com/orgmemory/core/knowledge/sourceledger/package-info.java @@ -12,6 +12,7 @@ @org.springframework.modulith.ApplicationModule( allowedDependencies = { "knowledge.acl", + "knowledge::evidence", "knowledge::storage", "organization", "permission", diff --git a/core/src/main/resources/db/migration/V32__assistant_governed_file_evidence.sql b/core/src/main/resources/db/migration/V32__assistant_governed_file_evidence.sql new file mode 100644 index 000000000..a87d0b0a5 --- /dev/null +++ b/core/src/main/resources/db/migration/V32__assistant_governed_file_evidence.sql @@ -0,0 +1,87 @@ +-- Bind governed Source revisions to one owned Assistant conversation, then +-- snapshot the ordered selection on the USER message that began a turn. + +CREATE TABLE public.assistant_evidence_bindings ( + id uuid NOT NULL, + organization_id uuid NOT NULL, + conversation_id uuid NOT NULL, + created_by_user_id uuid NOT NULL, + source_object_id uuid NOT NULL, + source_revision_id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + version bigint NOT NULL, + CONSTRAINT assistant_evidence_bindings_pkey PRIMARY KEY (id), + CONSTRAINT uq_assistant_evidence_binding_scope + UNIQUE (id, organization_id, conversation_id, created_by_user_id), + CONSTRAINT fk_assistant_evidence_binding_conversation + FOREIGN KEY (conversation_id, organization_id, created_by_user_id) + REFERENCES public.assistant_conversations( + id, organization_id, actor_user_id + ) + ON DELETE CASCADE, + CONSTRAINT fk_assistant_evidence_binding_source + FOREIGN KEY (source_object_id, organization_id) + REFERENCES public.source_objects(id, organization_id), + CONSTRAINT fk_assistant_evidence_binding_revision + FOREIGN KEY (source_revision_id, organization_id, source_object_id) + REFERENCES public.source_revisions(id, organization_id, source_object_id) +); + +CREATE INDEX idx_assistant_evidence_binding_conversation + ON public.assistant_evidence_bindings ( + organization_id, created_by_user_id, conversation_id, created_at, id + ); + +ALTER TABLE public.assistant_conversation_messages + ADD CONSTRAINT uq_assistant_message_turn_scope_role + UNIQUE (id, turn_id, conversation_id, organization_id, actor_user_id, role); + +CREATE TABLE public.assistant_turn_evidence_bindings ( + id uuid NOT NULL, + turn_id uuid NOT NULL, + user_message_id uuid NOT NULL, + user_message_role varchar(16) NOT NULL DEFAULT 'USER', + binding_id uuid NOT NULL, + organization_id uuid NOT NULL, + conversation_id uuid NOT NULL, + actor_user_id uuid NOT NULL, + ordinal integer NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + version bigint NOT NULL, + CONSTRAINT assistant_turn_evidence_bindings_pkey PRIMARY KEY (id), + CONSTRAINT chk_assistant_turn_evidence_ordinal CHECK (ordinal BETWEEN 1 AND 3), + CONSTRAINT chk_assistant_turn_evidence_user_role CHECK (user_message_role = 'USER'), + CONSTRAINT uq_assistant_turn_evidence_ordinal UNIQUE (turn_id, ordinal), + CONSTRAINT uq_assistant_turn_evidence_binding UNIQUE (turn_id, binding_id), + CONSTRAINT fk_assistant_turn_evidence_binding + FOREIGN KEY (binding_id, organization_id, conversation_id, actor_user_id) + REFERENCES public.assistant_evidence_bindings( + id, organization_id, conversation_id, created_by_user_id + ) + ON DELETE CASCADE, + CONSTRAINT fk_assistant_turn_evidence_user_message + FOREIGN KEY ( + user_message_id, + turn_id, + conversation_id, + organization_id, + actor_user_id, + user_message_role + ) + REFERENCES public.assistant_conversation_messages( + id, + turn_id, + conversation_id, + organization_id, + actor_user_id, + role + ) + ON DELETE CASCADE +); + +CREATE INDEX idx_assistant_turn_evidence_conversation + ON public.assistant_turn_evidence_bindings ( + organization_id, actor_user_id, conversation_id, turn_id, ordinal + ); diff --git a/core/src/test/java/com/orgmemory/core/ModulithVerificationTests.java b/core/src/test/java/com/orgmemory/core/ModulithVerificationTests.java index 78efb7c89..0b630596b 100644 --- a/core/src/test/java/com/orgmemory/core/ModulithVerificationTests.java +++ b/core/src/test/java/com/orgmemory/core/ModulithVerificationTests.java @@ -170,6 +170,7 @@ void sourceLedgerIsAClosedNestedModule() { assertEquals( Set.of( "knowledge.acl", + "knowledge::evidence", "knowledge::storage", "organization", "permission", @@ -727,6 +728,7 @@ void knowledgeGraphIsAClosedNestedModule() { "authorization", "knowledge.acl", "knowledge.asset", + "knowledge::evidence", "knowledge.retrieval", "knowledge.sourceledger", "knowledge.space", @@ -885,6 +887,7 @@ void knowledgeAssetConsumerSurfaceDoesNotGainNewTypes() { Set.of( "com.orgmemory.core.knowledge.retrieval.DefaultAuthorizationResourceDirectory", "com.orgmemory.core.knowledge.graph.GraphIndexingCoordinator", + "com.orgmemory.core.knowledge.graph.GraphEvidenceAnswerabilityQuery", "com.orgmemory.core.knowledge.graph.GraphIndexJobQueue", "com.orgmemory.core.knowledge.graph.GraphIndexLifecycleService", "com.orgmemory.core.knowledge.graph.KnowledgeGraphExplorerService", @@ -1080,18 +1083,40 @@ void searchIsAnExactExplicitKnowledgeInterface() { assertEquals( Set.of( "com.orgmemory.core.knowledge.search.PermissionAwareKnowledgeSearch", + "com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection", + "com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection$Item", "com.orgmemory.core.knowledge.search.RetrievedKnowledgeEvidence", "com.orgmemory.core.knowledge.search.SecureKnowledgeSearchResult", "com.orgmemory.core.knowledge.search.VerifiedKnowledgeGrounding"), exposedTypes); } + @Test + void governedEvidenceIsAnExactExplicitKnowledgeInterface() { + var knowledge = modules.getModuleByName("knowledge").orElseThrow(); + var evidence = knowledge.getNamedInterfaces().getByName("evidence").orElseThrow(); + var exposedTypes = evidence.asJavaClasses() + .map(type -> type.getName()) + .collect(TreeSet::new, Set::add, Set::addAll); + + assertEquals( + Set.of( + "com.orgmemory.core.knowledge.evidence.GovernedEvidenceQuery", + "com.orgmemory.core.knowledge.evidence.GovernedEvidenceRef", + "com.orgmemory.core.knowledge.evidence.GovernedEvidenceRef$ProcessingState", + "com.orgmemory.core.knowledge.evidence.GovernedFileUpload", + "com.orgmemory.core.knowledge.evidence.GovernedFileUploadCommand", + "com.orgmemory.core.knowledge.evidence.GovernedFileUploadResult"), + exposedTypes); + } + @Test void topLevelSearchConsumersUseOnlyTheParentSearchInterface() { var searchConsumerTypes = Set.of( "com.orgmemory.core.assetregistry.prompt.PromptExecutionService", "com.orgmemory.core.assistant.AssistantAssetToolService", "com.orgmemory.core.assistant.AssistantCitation", + "com.orgmemory.core.assistant.AssistantEvidenceService", "com.orgmemory.core.assistant.AssistantPromptFactory", "com.orgmemory.core.assistant.AssistantService"); var dependencies = modules.stream() @@ -1113,6 +1138,8 @@ void topLevelSearchConsumersUseOnlyTheParentSearchInterface() { assertEquals( Set.of( "com.orgmemory.core.knowledge.search.PermissionAwareKnowledgeSearch", + "com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection", + "com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection$Item", "com.orgmemory.core.knowledge.search.RetrievedKnowledgeEvidence", "com.orgmemory.core.knowledge.search.SecureKnowledgeSearchResult", "com.orgmemory.core.knowledge.search.VerifiedKnowledgeGrounding"), diff --git a/core/src/test/java/com/orgmemory/core/assistant/AssistantConversationServiceTests.java b/core/src/test/java/com/orgmemory/core/assistant/AssistantConversationServiceTests.java index 57d11e1b5..3d0f7cbcc 100644 --- a/core/src/test/java/com/orgmemory/core/assistant/AssistantConversationServiceTests.java +++ b/core/src/test/java/com/orgmemory/core/assistant/AssistantConversationServiceTests.java @@ -53,6 +53,7 @@ void setUp() { messages, citations, answerFeedback, + mock(AssistantEvidenceService.class), Clock.fixed(NOW, ZoneOffset.UTC)); when(conversations.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(messages.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); diff --git a/core/src/test/java/com/orgmemory/core/assistant/AssistantEvidenceServiceTests.java b/core/src/test/java/com/orgmemory/core/assistant/AssistantEvidenceServiceTests.java new file mode 100644 index 000000000..f47abb11c --- /dev/null +++ b/core/src/test/java/com/orgmemory/core/assistant/AssistantEvidenceServiceTests.java @@ -0,0 +1,258 @@ +package com.orgmemory.core.assistant; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.orgmemory.core.authorization.AuthorizationDecision; +import com.orgmemory.core.authorization.EffectiveAuthorizationService; +import com.orgmemory.core.knowledge.evidence.GovernedEvidenceQuery; +import com.orgmemory.core.knowledge.evidence.GovernedEvidenceRef; +import com.orgmemory.core.organization.CurrentActor; +import com.orgmemory.core.shared.error.BusinessConflictException; +import com.orgmemory.core.shared.error.BusinessNotFoundException; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneOffset; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class AssistantEvidenceServiceTests { + + private final AssistantConversationRepository conversations = + mock(AssistantConversationRepository.class); + private final AssistantEvidenceBindingRepository bindings = + mock(AssistantEvidenceBindingRepository.class); + private final AssistantTurnEvidenceBindingRepository turnBindings = + mock(AssistantTurnEvidenceBindingRepository.class); + private final GovernedEvidenceQuery sources = + mock(GovernedEvidenceQuery.class); + private final EffectiveAuthorizationService authorization = + mock(EffectiveAuthorizationService.class); + private final AssistantEvidenceAnswerabilityPort answerability = + mock(AssistantEvidenceAnswerabilityPort.class); + private final CurrentActor actor = new CurrentActor( + UUID.randomUUID(), + UUID.randomUUID(), + UUID.randomUUID(), + "Laura", + "laura@example.test"); + private final UUID conversationId = UUID.randomUUID(); + private final UUID bindingId = UUID.randomUUID(); + private final UUID sourceObjectId = UUID.randomUUID(); + private final UUID sourceRevisionId = UUID.randomUUID(); + private final UUID assetId = UUID.randomUUID(); + private AssistantEvidenceService service; + private AssistantEvidenceBinding binding; + + @BeforeEach + void setUp() { + service = new AssistantEvidenceService( + conversations, + bindings, + turnBindings, + sources, + authorization, + answerability, + Clock.fixed(Instant.parse("2026-08-10T00:00:00Z"), ZoneOffset.UTC)); + binding = new AssistantEvidenceBinding( + bindingId, + actor.organizationId(), + conversationId, + actor.userId(), + sourceObjectId, + sourceRevisionId); + } + + @Test + void claimsOnlyTheExactReadyRevisionAndPersistsTheTurnSelection() { + GovernedEvidenceRef source = readySource(); + when(bindings.findAllByIdInAndOrganizationIdAndConversationIdAndCreatedByUserId( + List.of(bindingId), + actor.organizationId(), + conversationId, + actor.userId())) + .thenReturn(List.of(binding)); + when(sources.find(actor.organizationId(), sourceObjectId, sourceRevisionId)) + .thenReturn(Optional.of(source)); + when(authorization.authorize(any(), any(), any(), any())) + .thenReturn(AuthorizationDecision.allow("model-1")); + when(answerability.evaluate(source)) + .thenReturn(AssistantEvidenceAnswerabilityPort.Answerability.ready()); + + var selection = service.claimForTurn( + actor, + conversationId, + UUID.randomUUID(), + UUID.randomUUID(), + List.of(bindingId)); + + assertTrue(selection.restricted()); + assertEquals(List.of(bindingId), selection.items().stream() + .map(item -> item.bindingId()) + .toList()); + assertTrue(selection.contains(assetId, sourceObjectId, sourceRevisionId)); + verify(turnBindings).saveAll(any()); + } + + @Test + void refusesASelectedBindingWhenTheActiveEngineIsStillIndexing() { + GovernedEvidenceRef source = readySource(); + when(bindings.findAllByIdInAndOrganizationIdAndConversationIdAndCreatedByUserId( + any(), any(), any(), any())) + .thenReturn(List.of(binding)); + when(sources.find(actor.organizationId(), sourceObjectId, sourceRevisionId)) + .thenReturn(Optional.of(source)); + when(authorization.authorize(any(), any(), any(), any())) + .thenReturn(AuthorizationDecision.allow("model-1")); + when(answerability.evaluate(source)) + .thenReturn(AssistantEvidenceAnswerabilityPort.Answerability.indexing()); + + assertThrows( + BusinessConflictException.class, + () -> service.claimForTurn( + actor, + conversationId, + UUID.randomUUID(), + UUID.randomUUID(), + List.of(bindingId))); + } + + @Test + void revokedAccessBecomesUnavailableAndCannotBeClaimed() { + GovernedEvidenceRef source = readySource(); + when(bindings.findByIdAndOrganizationIdAndConversationIdAndCreatedByUserId( + bindingId, + actor.organizationId(), + conversationId, + actor.userId())) + .thenReturn(Optional.of(binding)); + when(conversations.findByIdAndOrganizationIdAndActorUserId( + conversationId, + actor.organizationId(), + actor.userId())) + .thenReturn(Optional.of(mock(AssistantConversation.class))); + when(sources.find(actor.organizationId(), sourceObjectId, sourceRevisionId)) + .thenReturn(Optional.of(source)); + when(authorization.authorize(any(), any(), any(), any())) + .thenReturn(AuthorizationDecision.deny("RELATIONSHIP_DENIED", "model-1")); + + assertEquals( + AssistantEvidenceStatus.UNAVAILABLE, + service.get(actor, conversationId, bindingId).status()); + } + + @Test + void anotherConversationBindingIsOpaqueNotFound() { + when(conversations.findByIdAndOrganizationIdAndActorUserId( + conversationId, + actor.organizationId(), + actor.userId())) + .thenReturn(Optional.of(mock(AssistantConversation.class))); + + assertThrows( + BusinessNotFoundException.class, + () -> service.get(actor, conversationId, bindingId)); + } + + @Test + void anotherOrganizationIsOpaqueNotFound() { + CurrentActor otherOrganizationActor = new CurrentActor( + UUID.randomUUID(), + UUID.randomUUID(), + actor.userId(), + "Laura", + "laura@example.test"); + + assertThrows( + BusinessNotFoundException.class, + () -> service.get(otherOrganizationActor, conversationId, bindingId)); + } + + @Test + void reportsProcessingFailedAndRetiredSourceStatesBeforeAnswerability() { + when(conversations.findByIdAndOrganizationIdAndActorUserId( + conversationId, + actor.organizationId(), + actor.userId())) + .thenReturn(Optional.of(mock(AssistantConversation.class))); + when(bindings.findByIdAndOrganizationIdAndConversationIdAndCreatedByUserId( + bindingId, + actor.organizationId(), + conversationId, + actor.userId())) + .thenReturn(Optional.of(binding)); + when(sources.find(actor.organizationId(), sourceObjectId, sourceRevisionId)) + .thenReturn(Optional.of(source( + GovernedEvidenceRef.ProcessingState.PROCESSING, + true, + true, + true, + null, + null))) + .thenReturn(Optional.of(source( + GovernedEvidenceRef.ProcessingState.FAILED, + true, + true, + true, + null, + "parse_failed"))) + .thenReturn(Optional.of(source( + GovernedEvidenceRef.ProcessingState.READY, + false, + true, + true, + assetId, + null))); + + assertEquals( + AssistantEvidenceStatus.PROCESSING, + service.get(actor, conversationId, bindingId).status()); + assertEquals( + AssistantEvidenceStatus.FAILED, + service.get(actor, conversationId, bindingId).status()); + assertEquals( + AssistantEvidenceStatus.UNAVAILABLE, + service.get(actor, conversationId, bindingId).status()); + } + + private GovernedEvidenceRef readySource() { + return source( + GovernedEvidenceRef.ProcessingState.READY, + true, + true, + true, + assetId, + null); + } + + private GovernedEvidenceRef source( + GovernedEvidenceRef.ProcessingState state, + boolean active, + boolean latest, + boolean current, + UUID knowledgeAssetId, + String failureCode) { + return new GovernedEvidenceRef( + actor.organizationId(), + UUID.randomUUID(), + sourceObjectId, + sourceRevisionId, + state, + active, + latest, + current, + knowledgeAssetId, + UUID.randomUUID(), + "Policy", + "policy.pdf", + failureCode); + } +} diff --git a/core/src/test/java/com/orgmemory/core/assistant/AssistantEvidenceUploadServiceTests.java b/core/src/test/java/com/orgmemory/core/assistant/AssistantEvidenceUploadServiceTests.java new file mode 100644 index 000000000..0690fcbfc --- /dev/null +++ b/core/src/test/java/com/orgmemory/core/assistant/AssistantEvidenceUploadServiceTests.java @@ -0,0 +1,100 @@ +package com.orgmemory.core.assistant; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import com.orgmemory.core.knowledge.evidence.GovernedFileUpload; +import com.orgmemory.core.knowledge.evidence.GovernedFileUploadResult; +import com.orgmemory.core.organization.CurrentActor; +import com.orgmemory.core.permission.KnowledgeClassification; +import com.orgmemory.core.shared.error.BusinessNotFoundException; +import java.io.ByteArrayInputStream; +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +class AssistantEvidenceUploadServiceTests { + + @Test + void delegatesBytesToTheGovernedFilePortBeforeCreatingTheBinding() { + GovernedFileUpload uploads = mock(GovernedFileUpload.class); + AssistantEvidenceService evidence = mock(AssistantEvidenceService.class); + CurrentActor actor = new CurrentActor( + UUID.randomUUID(), + UUID.randomUUID(), + UUID.randomUUID(), + "Laura", + "laura@example.test"); + UUID conversationId = UUID.randomUUID(); + UUID spaceId = UUID.randomUUID(); + GovernedFileUploadResult uploaded = new GovernedFileUploadResult( + UUID.randomUUID(), + UUID.randomUUID(), + spaceId, + "policy.pdf"); + AssistantEvidenceBindingView expected = mock(AssistantEvidenceBindingView.class); + when(uploads.upload(any(), any())).thenReturn(uploaded); + when(evidence.bindUploaded(actor, conversationId, uploaded)).thenReturn(expected); + + AssistantEvidenceBindingView actual = new AssistantEvidenceUploadService( + uploads, + evidence) + .upload( + actor, + conversationId, + spaceId, + KnowledgeClassification.CONFIDENTIAL, + "policy.pdf", + 4, + new ByteArrayInputStream(new byte[] {1, 2, 3, 4})); + + assertSame(expected, actual); + ArgumentCaptor command = + ArgumentCaptor.forClass( + com.orgmemory.core.knowledge.evidence.GovernedFileUploadCommand.class); + verify(uploads).upload(command.capture(), any()); + assertSame(actor, command.getValue().actor()); + verify(evidence).bindUploaded(actor, conversationId, uploaded); + var order = inOrder(evidence, uploads); + order.verify(evidence).requireUploadConversation(actor, conversationId); + order.verify(uploads).upload(any(), any()); + order.verify(evidence).bindUploaded(actor, conversationId, uploaded); + } + + @Test + void rejectsAnOpaqueConversationBeforeCreatingAGovernedSource() { + GovernedFileUpload uploads = mock(GovernedFileUpload.class); + AssistantEvidenceService evidence = mock(AssistantEvidenceService.class); + CurrentActor actor = new CurrentActor( + UUID.randomUUID(), + UUID.randomUUID(), + UUID.randomUUID(), + "Laura", + "laura@example.test"); + UUID conversationId = UUID.randomUUID(); + doThrow(new BusinessNotFoundException( + "assistant.evidence-not-found", + "Assistant evidence not found")) + .when(evidence) + .requireUploadConversation(actor, conversationId); + + org.junit.jupiter.api.Assertions.assertThrows( + BusinessNotFoundException.class, + () -> new AssistantEvidenceUploadService(uploads, evidence).upload( + actor, + conversationId, + UUID.randomUUID(), + KnowledgeClassification.CONFIDENTIAL, + "policy.pdf", + 4, + new ByteArrayInputStream(new byte[] {1, 2, 3, 4}))); + + verifyNoInteractions(uploads); + } +} diff --git a/core/src/test/java/com/orgmemory/core/assistant/AssistantServiceTests.java b/core/src/test/java/com/orgmemory/core/assistant/AssistantServiceTests.java index b3884a4db..18db93c3a 100644 --- a/core/src/test/java/com/orgmemory/core/assistant/AssistantServiceTests.java +++ b/core/src/test/java/com/orgmemory/core/assistant/AssistantServiceTests.java @@ -8,6 +8,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -15,6 +16,7 @@ import com.orgmemory.core.ai.ChatGenerationRequest; import com.orgmemory.core.ai.ChatModelPort; import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearch; +import com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection; import com.orgmemory.core.knowledge.search.RetrievedKnowledgeEvidence; import com.orgmemory.core.knowledge.search.SecureKnowledgeSearchResult; import com.orgmemory.core.knowledge.search.VerifiedKnowledgeGrounding; @@ -111,6 +113,47 @@ void streamsOnlyPermissionVerifiedEvidenceToTheModel() { .contains(actor.organizationId().toString())); } + @Test + void refusesGenerationWhenAnySelectedFileHasNoUsableEvidence() { + RetrievedKnowledgeEvidence returned = evidence(); + UUID missingSource = UUID.randomUUID(); + var selection = KnowledgeEvidenceSelection.restricted(List.of( + new KnowledgeEvidenceSelection.Item( + UUID.randomUUID(), + returned.sourceObjectId(), + returned.sourceRevisionId(), + returned.knowledgeAssetId()), + new KnowledgeEvidenceSelection.Item( + UUID.randomUUID(), + missingSource, + UUID.randomUUID(), + UUID.randomUUID()))); + when(retrieval.search( + actor, + "Compare both files", + 5, + "request-selected", + selection)) + .thenReturn(new SecureKnowledgeSearchResult( + "request-selected", + List.of(returned))); + + AssistantUnavailableException failure = assertThrows( + AssistantUnavailableException.class, + () -> service.startTurn( + actor, + "Compare both files", + 5, + "request-selected", + CONVERSATION_ID, + null, + System.nanoTime(), + selection)); + + assertEquals("assistant_evidence_unavailable", failure.failureCode()); + verifyNoInteractions(chat); + } + @Test void exposesCitationsOnlyForEvidenceIncludedInThePromptBudget() { List evidence = IntStream.range(0, 6) diff --git a/core/src/test/java/com/orgmemory/core/knowledge/graph/GraphEvidenceAnswerabilityQueryTests.java b/core/src/test/java/com/orgmemory/core/knowledge/graph/GraphEvidenceAnswerabilityQueryTests.java new file mode 100644 index 000000000..b974cfc6e --- /dev/null +++ b/core/src/test/java/com/orgmemory/core/knowledge/graph/GraphEvidenceAnswerabilityQueryTests.java @@ -0,0 +1,80 @@ +package com.orgmemory.core.knowledge.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.orgmemory.core.knowledge.evidence.GovernedEvidenceRef; +import com.orgmemory.graphrag.processing.GraphProcessingProfile; +import com.orgmemory.graphrag.storage.ProjectionPublicationStore; +import com.orgmemory.graphrag.storage.ProjectionSnapshot; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +class GraphEvidenceAnswerabilityQueryTests { + + @Test + void sourceReadyStillIndexesUntilTheExactGraphGenerationIsPublished() { + GraphProcessingProfileResolver resolver = mock(GraphProcessingProfileResolver.class); + GraphProcessingProfile desired = mock(GraphProcessingProfile.class); + GraphProcessingProfileRepository profiles = mock(GraphProcessingProfileRepository.class); + PersistedGraphProcessingProfile persisted = mock(PersistedGraphProcessingProfile.class); + GraphProcessingProfileRef profile = mock(GraphProcessingProfileRef.class); + GraphIndexJobRepository jobs = mock(GraphIndexJobRepository.class); + GraphIndexJob job = mock(GraphIndexJob.class); + ProjectionPublicationStore publications = mock(ProjectionPublicationStore.class); + UUID profileId = UUID.randomUUID(); + UUID assetVersionId = UUID.randomUUID(); + when(desired.canonicalSha256()).thenReturn("a".repeat(64)); + when(resolver.current()).thenReturn(desired); + when(profiles.findByCanonicalSha256("a".repeat(64))) + .thenReturn(Optional.of(persisted)); + when(persisted.toRef()).thenReturn(profile); + when(profile.id()).thenReturn(profileId); + when(jobs.findByKnowledgeAssetVersionIdAndGraphProcessingProfileId( + assetVersionId, profileId)) + .thenReturn(Optional.of(job)); + when(job.getStatus()).thenReturn(GraphIndexJobStatus.SUCCEEDED); + when(job.getProjectionGeneration()).thenReturn(7L); + GraphEvidenceAnswerabilityQuery query = new GraphEvidenceAnswerabilityQuery( + resolver, profiles, jobs, publications); + + GovernedEvidenceRef source = readySource(assetVersionId); + assertEquals( + GraphEvidenceAnswerability.State.INDEXING, + query.evaluate(source).state()); + + ProjectionSnapshot exact = mock(ProjectionSnapshot.class); + ProjectionSnapshot current = mock(ProjectionSnapshot.class); + when(current.generation()).thenReturn(7L); + when(publications.published(anyNamespace(), org.mockito.ArgumentMatchers.eq(7L))) + .thenReturn(Optional.of(exact)); + when(publications.current(anyNamespace())).thenReturn(Optional.of(current)); + + assertEquals( + GraphEvidenceAnswerability.State.READY, + query.evaluate(source).state()); + } + + private static com.orgmemory.graphrag.storage.ProjectionNamespace anyNamespace() { + return org.mockito.ArgumentMatchers.any(); + } + + private static GovernedEvidenceRef readySource(UUID assetVersionId) { + return new GovernedEvidenceRef( + UUID.randomUUID(), + UUID.randomUUID(), + UUID.randomUUID(), + UUID.randomUUID(), + GovernedEvidenceRef.ProcessingState.READY, + true, + true, + true, + UUID.randomUUID(), + assetVersionId, + "Policy", + "policy.pdf", + null); + } +} diff --git a/core/src/test/java/com/orgmemory/core/knowledge/retrieval/CanonicalHybridKnowledgeSearchTests.java b/core/src/test/java/com/orgmemory/core/knowledge/retrieval/CanonicalHybridKnowledgeSearchTests.java index a66679eda..dd9cbe6b9 100644 --- a/core/src/test/java/com/orgmemory/core/knowledge/retrieval/CanonicalHybridKnowledgeSearchTests.java +++ b/core/src/test/java/com/orgmemory/core/knowledge/retrieval/CanonicalHybridKnowledgeSearchTests.java @@ -18,6 +18,7 @@ import com.orgmemory.core.knowledge.retrieval.QueryEmbedding; import com.orgmemory.core.knowledge.retrieval.QueryEmbeddingPort; import com.orgmemory.core.knowledge.search.SecureKnowledgeSearchResult; +import com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection; import com.orgmemory.core.organization.CurrentActor; import com.orgmemory.core.organization.OrgMemoryAccessDeniedException; import com.orgmemory.core.permission.PermissionAuditService; @@ -136,6 +137,42 @@ void candidateOutsideListObjectsBoundaryFailsClosed() { () -> service.search(actor, "leave policy", 10, "request-3")); } + @Test + void selectedEvidenceNarrowsCandidatesBeforeScoringAndRejectsExactRevisionDrift() { + UUID otherAsset = UUID.randomUUID(); + SecureRetrievalCandidate selected = candidate(UUID.randomUUID(), assetId); + when(evidenceScopes.resolve(actor, MODEL_ID)) + .thenReturn(scope(Set.of(assetId, otherAsset))); + var selection = KnowledgeEvidenceSelection.restricted(List.of( + new KnowledgeEvidenceSelection.Item( + UUID.randomUUID(), + selected.sourceObjectId(), + selected.sourceRevisionId(), + selected.knowledgeAssetId()))); + store.lexical = List.of(candidate(UUID.randomUUID(), otherAsset)); + + assertThrows( + KnowledgeRetrievalUnavailableException.class, + () -> service.search( + actor, + "leave policy", + 10, + "request-selected", + selection)); + + assertEquals(List.of(assetId), store.lastScope.authorizedAssetIds()); + + store.lexical = List.of(candidate(UUID.randomUUID(), assetId)); + assertThrows( + KnowledgeRetrievalUnavailableException.class, + () -> service.search( + actor, + "leave policy", + 10, + "request-revision-drift", + selection)); + } + @Test void crossTenantCandidateFailsClosedEvenWhenItsAssetIdWasListed() { store.lexical = List.of(candidate(UUID.randomUUID(), assetId, UUID.randomUUID())); @@ -387,6 +424,7 @@ private static final class StubStore extends SecureKnowledgeRetrievalStore { private List lexical = List.of(); private final List semantic = List.of(); private List rechecked = List.of(); + private RetrievalScope lastScope; private StubStore() { super(null); @@ -394,6 +432,7 @@ private StubStore() { @Override List lexical(RetrievalScope scope, String query, int candidateLimit) { + lastScope = scope; return lexical; } diff --git a/core/src/test/java/com/orgmemory/core/knowledge/retrieval/GraphRagKnowledgeRetrievalServiceTests.java b/core/src/test/java/com/orgmemory/core/knowledge/retrieval/GraphRagKnowledgeRetrievalServiceTests.java index 082183ffd..e325afc8e 100644 --- a/core/src/test/java/com/orgmemory/core/knowledge/retrieval/GraphRagKnowledgeRetrievalServiceTests.java +++ b/core/src/test/java/com/orgmemory/core/knowledge/retrieval/GraphRagKnowledgeRetrievalServiceTests.java @@ -25,6 +25,7 @@ import com.orgmemory.core.knowledge.retrieval.EmbeddingProfileRegistry; import com.orgmemory.core.knowledge.retrieval.KnowledgeEmbeddingProperties; import com.orgmemory.core.knowledge.search.SecureKnowledgeSearchResult; +import com.orgmemory.core.knowledge.search.KnowledgeEvidenceSelection; import com.orgmemory.core.organization.CurrentActor; import com.orgmemory.core.permission.PermissionAuditCommand; import com.orgmemory.core.permission.PermissionAuditService; @@ -92,6 +93,44 @@ class GraphRagKnowledgeRetrievalServiceTests { private static final Instant NOW = Instant.parse("2026-07-24T00:00:00Z"); + @Test + void selectedEvidenceIsTheScopeUsedByGraphSeedAndExpansion() { + CurrentActor actor = actor(); + KnowledgeEvidenceScopeResolver scopes = mock(KnowledgeEvidenceScopeResolver.class); + when(scopes.resolve(actor, MODEL_ID)) + .thenReturn(scope(Set.of(ASSET_ID, SECOND_ASSET_ID), 1L)); + LightRagQueryEngine engine = mock(LightRagQueryEngine.class); + LightRagPreparedQuery prepared = preparedQueryPlan(); + when(engine.prepare(any())).thenReturn(prepared); + when(engine.executePrepared(any(), any(), any())).thenReturn(noResults()); + var selection = KnowledgeEvidenceSelection.restricted(List.of( + new KnowledgeEvidenceSelection.Item( + UUID.randomUUID(), + UUID.randomUUID(), + REVISION_ID, + ASSET_ID))); + + service( + scopes, + mock(RelationshipAuthorizationSetPort.class), + mock(SecureKnowledgeRetrievalStore.class), + engine, + GraphRagRetrievalPolicy.defaults(), + mock(PermissionAuditService.class), + mock(GraphRagEventSink.class)) + .search( + actor, + "Summarize this file", + 10, + "request-selected", + selection); + + verify(engine).executePrepared( + argThat(request -> request.scope().authorizedAssetIds().equals(Set.of(ASSET_ID))), + any(), + any()); + } + @Test void multipleSpacesPrepareOneLogicalQueryBeforeSnapshotRetrieval() { CurrentActor actor = new CurrentActor( diff --git a/core/src/test/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadServiceTests.java b/core/src/test/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadServiceTests.java index ce16e9c67..885b22c80 100644 --- a/core/src/test/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadServiceTests.java +++ b/core/src/test/java/com/orgmemory/core/knowledge/sourceledger/SourceUploadServiceTests.java @@ -57,7 +57,7 @@ void derivesTheStoredMediaTypeFromTheAllowlistedExtension() { any(), any(), any())) - .thenReturn(mock(SourceSummary.class)); + .thenReturn(mock(SourceUploadResult.class)); SourceUploadService service = new SourceUploadService( objects, registrations, From 267c74f06910efe6a0ec6928dfbdbfaa392e987e Mon Sep 17 00:00:00 2001 From: kl3inIT Date: Mon, 10 Aug 2026 18:28:19 +0700 Subject: [PATCH 05/10] docs(assistant): record governed file evidence --- ARCHITECTURE.md | 14 +- .../reference/api-reference/assistant.mdx | 17 +- apps/docs/generated/openapi.public.json | 207 ++++++++++++++++++ ...ned-source-bindings-for-assistant-files.md | 68 ++++++ .../plan.md | 18 +- docs/specs/domains/assistant-and-mcp.md | 40 +++- docs/specs/domains/knowledge-ingestion.md | 14 +- docs/tests/domains/assistant-and-mcp.md | 11 +- docs/tests/domains/knowledge-ingestion.md | 12 +- 9 files changed, 386 insertions(+), 15 deletions(-) create mode 100644 docs/decisions/0038-use-governed-source-bindings-for-assistant-files.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c07f2c5f5..cf569d77d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -163,7 +163,11 @@ Executive facts and resolve organization/department existence without exposing Organization persistence or roles. Source Ledger resolves tenant-scoped ready revision plus validated blob state through `SourceCitationEvidenceQuery`, so citation opening consumes immutable evidence rather than revision/blob -persistence. Asset has no direct dependency on Retrieval and is a closed +persistence. Parent Knowledge also exposes the exact `knowledge::evidence` +named interface for governed byte registration and exact Source/revision state. +Source Ledger implements it through the canonical upload and query services; +Assistant consumes it without importing Source Ledger persistence, parsing, or +processing types. Asset has no direct dependency on Retrieval and is a closed nested module with an exact outgoing dependency allowlist. Parent Knowledge exposes the stable permission-aware search contract, immutable evidence, secure result, and verified grounding as @@ -331,6 +335,14 @@ an in-flight turn may finish under its request snapshot and is bounded by the configured turn timeout. Missing, unknown, stale, unsupported, changed, or denied retrieval decisions fail closed. +Assistant governed-file turns add an immutable `KnowledgeEvidenceSelection` +after actor authorization. Canonical retrieval intersects the selected Assets +before ranking; GraphRAG carries the same ceiling through seed, expansion, +closure verification, and citation output. The selection pins exact binding, +Source, revision, and Asset identity, and every selected Source must contribute +usable final evidence before generation. Upload remains the ordinary durable +Source pipeline; the API never parses or embeds the multipart bytes. + ACL evidence is sealed and append-only. ACL rotation appends a new generation and compare-and-set advances the current head. The current head has a 24-hour freshness requirement; the ingestion snapshot remains a historical ceiling. diff --git a/apps/docs/content/docs/reference/api-reference/assistant.mdx b/apps/docs/content/docs/reference/api-reference/assistant.mdx index 175ff7b43..d1556c751 100644 --- a/apps/docs/content/docs/reference/api-reference/assistant.mdx +++ b/apps/docs/content/docs/reference/api-reference/assistant.mdx @@ -46,6 +46,9 @@ _openapi: - depth: 2 title: Submit feedback against an exact release after explicit confirmation url: '#submit-feedback-against-an-exact-release-after-explicit-confirmation' + - depth: 2 + title: Upload one governed file and bind it to an Assistant conversation + url: '#upload-one-governed-file-and-bind-it-to-an-assistant-conversation' - depth: 2 title: Stream an answer from permission-verified knowledge url: '#stream-an-answer-from-permission-verified-knowledge' @@ -79,6 +82,12 @@ _openapi: - depth: 2 title: Replay a tenant- and actor-scoped full conversation transcript url: '#replay-a-tenant--and-actor-scoped-full-conversation-transcript' + - depth: 2 + title: List governed file bindings for one owned conversation + url: '#list-governed-file-bindings-for-one-owned-conversation' + - depth: 2 + title: Read the active-engine preparation state of one owned binding + url: '#read-the-active-engine-preparation-state-of-one-owned-binding' structuredData: headings: - content: Update actor-derived Pack progress after explicit confirmation @@ -105,6 +114,8 @@ _openapi: id: fork-an-exact-release-after-explicit-draft-creation-confirmation - content: Submit feedback against an exact release after explicit confirmation id: submit-feedback-against-an-exact-release-after-explicit-confirmation + - content: Upload one governed file and bind it to an Assistant conversation + id: upload-one-governed-file-and-bind-it-to-an-assistant-conversation - content: Stream an answer from permission-verified knowledge id: stream-an-answer-from-permission-verified-knowledge - content: Rename the current actor's conversation @@ -127,6 +138,10 @@ _openapi: id: list-the-current-actors-conversations-by-recent-activity - content: Replay a tenant- and actor-scoped full conversation transcript id: replay-a-tenant--and-actor-scoped-full-conversation-transcript + - content: List governed file bindings for one owned conversation + id: list-governed-file-bindings-for-one-owned-conversation + - content: Read the active-engine preparation state of one owned binding + id: read-the-active-engine-preparation-state-of-one-owned-binding contents: [] --- @@ -139,7 +154,7 @@ export default function Layout(props) { return ( <> {props.children} - + ); } diff --git a/apps/docs/generated/openapi.public.json b/apps/docs/generated/openapi.public.json index 203223fc8..f94585028 100644 --- a/apps/docs/generated/openapi.public.json +++ b/apps/docs/generated/openapi.public.json @@ -1634,6 +1634,80 @@ } } }, + "/api/assistant/evidence": { + "post": { + "tags": [ + "Assistant" + ], + "summary": "Upload one governed file and bind it to an Assistant conversation", + "operationId": "uploadAssistantEvidence", + "parameters": [ + { + "name": "conversationId", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "knowledgeSpaceId", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "classification", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "CONFIDENTIAL", + "enum": [ + "PUBLIC", + "INTERNAL", + "CONFIDENTIAL", + "RESTRICTED" + ] + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "file": { + "type": "string", + "format": "binary" + } + }, + "required": [ + "file" + ] + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/AssistantEvidenceBindingView" + } + } + } + } + } + } + }, "/api/assistant/chat": { "post": { "tags": [ @@ -4455,6 +4529,82 @@ } } }, + "/api/assistant/conversations/{conversationId}/evidence": { + "get": { + "tags": [ + "Assistant" + ], + "summary": "List governed file bindings for one owned conversation", + "operationId": "listAssistantEvidence", + "parameters": [ + { + "name": "conversationId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssistantEvidenceBindingView" + } + } + } + } + } + } + } + }, + "/api/assistant/conversations/{conversationId}/evidence/{bindingId}": { + "get": { + "tags": [ + "Assistant" + ], + "summary": "Read the active-engine preparation state of one owned binding", + "operationId": "getAssistantEvidence", + "parameters": [ + { + "name": "conversationId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "bindingId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/AssistantEvidenceBindingView" + } + } + } + } + } + } + }, "/api/assets/{assetId}": { "get": { "tags": [ @@ -7726,6 +7876,54 @@ } } }, + "AssistantEvidenceBindingView": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "conversationId": { + "type": "string", + "format": "uuid" + }, + "sourceObjectId": { + "type": "string", + "format": "uuid" + }, + "sourceRevisionId": { + "type": "string", + "format": "uuid" + }, + "knowledgeAssetId": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "PROCESSING", + "INDEXING", + "READY", + "FAILED", + "UNAVAILABLE" + ] + }, + "failureCode": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + } + }, "AssistantChatRequest": { "type": "object", "properties": { @@ -7745,6 +7943,15 @@ "modelActivationId": { "type": "string", "format": "uuid" + }, + "evidenceBindingIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "maxItems": 3, + "minItems": 0 } }, "required": [ diff --git a/docs/decisions/0038-use-governed-source-bindings-for-assistant-files.md b/docs/decisions/0038-use-governed-source-bindings-for-assistant-files.md new file mode 100644 index 000000000..50c4d89bb --- /dev/null +++ b/docs/decisions/0038-use-governed-source-bindings-for-assistant-files.md @@ -0,0 +1,68 @@ +# 0038 — Use governed Source bindings for Assistant files + +Status: accepted +Date: 2026-08-10 + +## Context + +Assistant needed file evidence in the composer after the reusable document +parser shipped. A direct-provider or transient attachment lane would require a +second authorization, retention, deletion, parsing, egress, replay, and citation +model. Treating `SourceRevision.READY` as sufficient also did not match the +default GraphRAG engine: a revision may be ready while its exact graph projection +is not yet published. Finally, ordinary permission filtering alone could allow a +graph neighbor or organizational search result outside the selected files to +enter a “summarize these files” turn. + +## Decision + +The first Assistant file path publishes durable governed Knowledge. A multipart +use case delegates bytes through the parent-owned `knowledge::evidence` port to +Source Ledger's canonical upload pipeline. Assistant persists only a binding to +the exact Source object and revision, scoped by organization, conversation, and +creator. It exposes upload, list, and get-status operations; it does not expose a +bind-existing-Source-by-ID operation. + +Turn submission atomically persists at most three distinct ordered bindings +beside the initiating USER message. Claim time rechecks exact lifecycle, +authorization, integrity, and active-engine answerability. Canonical retrieval +requires current READY chunks. GraphRAG additionally requires the exact Asset +version's current-profile projection generation to be published. + +A selected turn is selection-only. `KnowledgeEvidenceSelection` narrows the +already-authorized scope before ranking and remains a hard ceiling through graph +seed, expansion, closure verification, and citation output. Every selected +Source must yield usable final evidence before generation. Retry reuses the same +ordered binding identities. + +## Independent challenge + +The increment challenge returned `REVISE`. It accepted governed Source reuse and +the separate Assistant binding aggregate, but required active-engine readiness, +selection-constrained retrieval, and all-selected-files answerability. Those +conditions are part of this decision and the implementation. + +## Rejected alternatives + +- Send bytes or provider file handles directly to the model. This bypasses + canonical authorization, citation, processing identity, and egress policy. +- Add a transient TTL attachment lane in the first delivery. It is a legitimate + later product, but only with its own explicit retention, malware/DLP, replay, + deletion, tool-access, and promotion contracts. +- Bind any existing Source ID supplied by the browser. Possession of an opaque ID + is not authority, and the MVP needs no second association workflow. +- Treat a READY Source as GraphRAG-ready. This can enable submit before the exact + projection serving the turn exists. +- Apply selection only after retrieval. That permits non-selected graph evidence + to influence expansion, budgets, or the model before it is filtered. + +## Consequences + +- Assistant reuses ingestion without becoming a parser or Source lifecycle + owner; Worker remains the sole production parser caller. +- Composer uploads are durable and visible under the selected Knowledge Space's + audience and lifecycle, which the UI discloses before upload. +- Removing a chip removes only the pending turn selection, not the governed + Source. Retirement and later access revocation use existing Knowledge rules. +- Images, OCR, archives, email recursion, direct provider uploads, generated + files, and transient attachments remain closed. diff --git a/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md b/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md index 0c84f48f2..409e3d262 100644 --- a/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md +++ b/docs/increments/active/2026-08-10-assistant-file-evidence/plan.md @@ -21,41 +21,41 @@ increment owns only governed Assistant file evidence. ## 2. Governed Assistant evidence -- [ ] Define the Assistant evidence-binding aggregate and Flyway migration with +- [x] Define the Assistant evidence-binding aggregate and Flyway migration with organization/conversation/creator/source/revision identity, ordering, and immutable turn association. -- [ ] Add a core use case that delegates bytes to `SourceUploadService`; do not +- [x] Add a core use case that delegates bytes to `SourceUploadService`; do not duplicate object storage, content-type, Space, classification, or permission validation. Binding creation exists only inside this multipart use case; do not add a bind-existing-Source-by-ID endpoint. -- [ ] Add status projection derived from Source processing state and a turn-claim +- [x] Add status projection derived from Source processing state and a turn-claim validator that rechecks organization, conversation, actor authorization, current revision, evidence integrity, and answerability under the configured engine. For GraphRAG, require the selected Asset's projection batch to be published, not merely `SourceRevision.READY`. -- [ ] Add internal `KnowledgeEvidenceSelection` to the configured retrieval +- [x] Add internal `KnowledgeEvidenceSelection` to the configured retrieval engine. Make binding turns selection-only; intersect selected Asset/Source IDs before scoring, retain the ceiling through graph expansion and closure verification, reject non-selected citations, apply the global budget to the selected subset, and require usable evidence from every selected binding. -- [ ] Extend OpenAPI and the composer with upload progress, Space/classification +- [x] Extend OpenAPI and the composer with upload progress, Space/classification choice, at-most-three selection, processing/failed states, retry-safe ordered binding submission, and the existing document viewer. Disclose that upload publishes to the selected Space audience and disable attachment for actors with no `can_create_asset` Space. -- [ ] Keep images, direct provider uploads, transient attachments, OCR, archives, +- [x] Keep images, direct provider uploads, transient attachments, OCR, archives, MSG/EML recursion, and generated files closed. ## 3. Verification and consolidation -- [ ] Cover cross-organization ID probing, another conversation's binding, +- [x] Cover cross-organization ID probing, another conversation's binding, revoked access between upload and send, unready/failed/retired revisions, retry identity, selected-source retrieval, citation recheck, and prompt injection fixtures. -- [ ] Cover the READY/projection gap, graph expansion into a non-selected Asset, +- [x] Cover the READY/projection gap, graph expansion into a non-selected Asset, one selected file returning no usable evidence, no-Space UX, and the absence of any bind-existing-Source endpoint. -- [ ] Cover parser capability/admission mismatches and prove no parsing or +- [x] Cover parser capability/admission mismatches and prove no parsing or embedding occurs on the API request thread. - [ ] Reconcile `assistant-and-mcp` and `knowledge-ingestion` specs and test matrices, refresh `Source:`/`Reconciled:`, add a durable decision, complete diff --git a/docs/specs/domains/assistant-and-mcp.md b/docs/specs/domains/assistant-and-mcp.md index 8dd45fc94..7cc47791b 100644 --- a/docs/specs/domains/assistant-and-mcp.md +++ b/docs/specs/domains/assistant-and-mcp.md @@ -2,6 +2,9 @@ Source: `core/src/main/java/com/orgmemory/core/assistant`, `core/src/main/java/com/orgmemory/core/ai`, +`core/src/main/java/com/orgmemory/core/knowledge/evidence`, +`core/src/main/java/com/orgmemory/core/knowledge/search`, +`core/src/main/java/com/orgmemory/core/knowledge/retrieval`, `core/src/main/java/com/orgmemory/core/assetregistry/skill`, `integrations/ai-model-gateways`, `apps/api/src/main/java/com/orgmemory/api/assistant`, @@ -10,7 +13,7 @@ Source: `core/src/main/java/com/orgmemory/core/assistant`, `apps/web/src/components/ai-elements/model-selector.tsx`, and `apps/web/src/components/ai-elements/prompt-input.tsx`. -Reconciled: `2026-08-06-assistant-prompt-input-alignment (b83d45d9)`. +Reconciled: `2026-08-10-assistant-file-evidence (5aee7535)`. ## Current Behavior @@ -58,6 +61,40 @@ object-storage URLs. Every open performs one fresh canonical authorization and integrity check; missing, changed, and denied citations are wire-equivalent opaque `404` responses. +The composer can publish at most three files as durable governed Knowledge for +one pending turn. The actor chooses a currently writable Knowledge Space and a +classification; the Assistant upload use case validates any supplied owned +conversation before side effects, then delegates the bytes to the canonical +Source upload port. Source Ledger remains the owner of format admission, +per-format limits, object storage, immutable Source/revision identity, and the +asynchronous ingestion job. Assistant stores only an organization-, actor-, and +conversation-scoped binding to that exact Source revision. It has no parser +caller, transient attachment store, provider file handle, bind-existing-Source +endpoint, OCR lane, image admission, or request-thread parsing path. The browser +states that upload publishes to the selected Space audience; an actor with no +`can_create_asset` Space cannot open the attachment flow. + +Binding presentation is derived rather than independently mutable. Source +processing produces `PROCESSING`, terminal parser states produce `FAILED`, and +retirement, revision drift, missing evidence, or revoked access produce +`UNAVAILABLE`. A canonical-engine binding becomes `READY` with its current READY +chunks. Under the default GraphRAG engine it remains `INDEXING` until the exact +Asset version's current graph profile job succeeded and its projection +generation is published. Ownership and tenant probes remain opaque not-found. + +A turn carrying bindings persists their distinct ordered IDs beside the same +USER message and turn identity in one transaction. Claim time rechecks +organization, conversation, creator, exact Source/revision, current read +authorization, lifecycle, integrity, and active-engine answerability. The +resulting `KnowledgeEvidenceSelection` is an internal second ceiling after actor +authorization: canonical retrieval intersects it before scoring, GraphRAG uses +it for seed and expansion scope, and both reapply exact Asset/Source/revision +identity during closure and citation checks. No non-selected organizational +Knowledge may enter a binding turn, and every selected file must contribute +usable final evidence before generation. The browser retains the exact ordered +selection on failure or abort and clears it only after a successful turn, so a +retry cannot silently resolve to a newer revision. + Every turn is observed on its own surface, `orgmemory.assistant.turn`, rather than through `GraphRagEventSink`: generation runs above an engine-neutral retrieval interface, so a GraphRAG stage would mislabel a canonical-engine turn. @@ -327,3 +364,4 @@ than filtered per actor. General chat-turn idempotency remains unimplemented. - [0006](../../decisions/0006-ai-tasks-route-through-provider-adapters.md) - [0008](../../decisions/0008-worker-owns-ingestion-and-derived-indexes.md) - [0032](../../decisions/0032-conversation-model-selection-is-bound-to-admin-route-authority.md) +- [0038](../../decisions/0038-use-governed-source-bindings-for-assistant-files.md) diff --git a/docs/specs/domains/knowledge-ingestion.md b/docs/specs/domains/knowledge-ingestion.md index 485795e67..029104ba3 100644 --- a/docs/specs/domains/knowledge-ingestion.md +++ b/docs/specs/domains/knowledge-ingestion.md @@ -11,7 +11,7 @@ Source: `core/src/main/java/com/orgmemory/core/knowledge`, `integrations/document-parsing-spring-ai/src/main`, `integrations/connectors/src/main`, and `contracts/connector`. -Reconciled: `2026-08-10-ingestion-coverage (c0c12728)`. +Reconciled: `2026-08-10-assistant-file-evidence (5aee7535)`. ## Current Behavior @@ -36,6 +36,16 @@ tenant-scoped ready revision and validated evidence blob into immutable citation metadata, while revision/blob entities, repositories, and lifecycle enums remain internal to the closed module. +Parent Knowledge exposes a general `knowledge::evidence` named interface for +product channels that need to register governed bytes or resolve an exact +Source/revision without importing Source Ledger internals. Source Ledger adapts +that interface to its existing upload validation, object storage, revision, and +job registration services and maps lifecycle state into a channel-neutral +reference. Assistant is its first consumer. It still does not call the parser or +processing engine: the worker remains the sole production parser caller, and +all heavy parsing, chunking, embedding, and projection publication stays on the +durable ingestion path. + The Documents list uses Source Object ids as its stable browser identity and reads row state from each object's latest revision, including staged revisions that have not been published. The permission-visible and owner-visible Source @@ -480,3 +490,5 @@ implemented as rebuildable projections over this canonical ledger. - [0005](../../decisions/0005-secure-java-graph-kernel.md) - [0008](../../decisions/0008-worker-owns-ingestion-and-derived-indexes.md) - [0009](../../decisions/0009-dynamic-source-acl-ceiling.md) +- [0037](../../decisions/0037-separate-parser-capability-from-knowledge-admission.md) +- [0038](../../decisions/0038-use-governed-source-bindings-for-assistant-files.md) diff --git a/docs/tests/domains/assistant-and-mcp.md b/docs/tests/domains/assistant-and-mcp.md index 9f893f031..2a59bc7a6 100644 --- a/docs/tests/domains/assistant-and-mcp.md +++ b/docs/tests/domains/assistant-and-mcp.md @@ -2,20 +2,29 @@ Source: `core/src/test/java/com/orgmemory/core/assistant`, `core/src/test/java/com/orgmemory/core/ai`, +`core/src/test/java/com/orgmemory/core/knowledge/graph`, +`core/src/test/java/com/orgmemory/core/knowledge/retrieval`, `integrations/ai-model-gateways/src/test`, +`apps/api/src/test/java/com/orgmemory/api/OpenApiContractTests.java`, `apps/api/src/test/java/com/orgmemory/api/assistant`, `apps/mcp/src/test/java/com/orgmemory/mcp`, and `apps/web/src/features/assistant`, `apps/web/src/components/ai-elements/prompt-input.test.tsx`, plus `apps/web/test/e2e/assistant-pipeline.spec.ts`. -Reconciled: `2026-08-06-assistant-prompt-input-alignment (b83d45d9)`. +Reconciled: `2026-08-10-assistant-file-evidence (5aee7535)`. | Behavior | Evidence | Status | | --- | --- | --- | | API context boots without provider key | `OrgMemoryApiContextLoadTests` | covered | | Assistant sends only permission-verified evidence to the model | `AssistantServiceTests#streamsOnlyPermissionVerifiedEvidenceToTheModel` | covered | | Assistant consumes only the parent `knowledge::search` contract and never imports Retrieval implementation | `ModulithVerificationTests#topLevelSearchConsumersUseOnlyTheParentSearchInterface`, `#assistantAndAssetRegistryDoNotDependOnRetrievalImplementation` | covered | +| Assistant upload validates an existing conversation before side effects, delegates bytes to the general governed Source port, and creates only an exact Source/revision binding | `AssistantEvidenceUploadServiceTests`, `AssistantEvidenceServiceTests#claimsOnlyTheExactReadyRevisionAndPersistsTheTurnSelection` | covered | +| Cross-organization and other-conversation binding probes remain opaque, while revoked, processing, failed, retired, and active-engine-indexing states fail closed | `AssistantEvidenceServiceTests`, `GraphEvidenceAnswerabilityQueryTests#sourceReadyStillIndexesUntilTheExactGraphGenerationIsPublished` | covered | +| A selected turn is limited to three distinct ordered bindings, persists the USER-message association, and refuses generation unless every selected file yields usable evidence | `AssistantChatRequestValidationTests#limitsOneTurnToThreeEvidenceBindings`, `AssistantConversationServiceTests`, `AssistantServiceTests#refusesGenerationWhenAnySelectedFileHasNoUsableEvidence` | covered | +| Canonical retrieval rejects exact revision drift before scoring/context; GraphRAG keeps the selected Asset set through seed and expansion | `CanonicalHybridKnowledgeSearchTests#selectedEvidenceNarrowsCandidatesBeforeScoringAndRejectsExactRevisionDrift`, `GraphRagKnowledgeRetrievalServiceTests#selectedEvidenceIsTheScopeUsedByGraphSeedAndExpansion` | covered | +| Product OpenAPI exposes upload/list/get only and no bind-existing-Source evidence route | `OpenApiContractTests#assistantEvidenceContractHasNoBindExistingSourceEndpoint`, `#theCommittedProductContractDescribesTheLiveProductApi` | covered | +| Composer discloses durable Space publication, disables attachment with no writable Space, waits for READY, sends ordered IDs, retains them across a failed retry, and clears only on success | `assistant-evidence.test.ts`, `assistant-pipeline.spec.ts#uploads governed evidence and submits its ordered binding with the turn`, `#keeps the exact governed binding across a failed turn retry`, `#keeps governed attachment closed when no upload Space is authorized` | covered | | Empty authorized retrieval returns bounded same-language user-perspective wording plus one escalation sentence, with no model call or citations | `AssistantServiceTests#answersInVietnameseWithoutCallingTheModelWhenNoAccessibleEvidenceExists`, `#answersInEnglishWithoutCallingTheModelWhenNoAccessibleEvidenceExists` | covered | | Canonical and already-verified LightRAG prompts share permission-safe no-answer, adjacent-information, exact-citation, and direct-voice rules while preserving injection boundaries | `AssistantPromptFactoryTests#encodesPermissionSafeNoAnswerAndExactCitationBehavior`, `AssistantServiceTests#usesTheAlreadyVerifiedLightRagPromptWithoutRebuildingIt` | covered | | Evidence-scope disclosure renders below Assistant answers but not user messages | `assistant-answer.test.tsx` | covered | diff --git a/docs/tests/domains/knowledge-ingestion.md b/docs/tests/domains/knowledge-ingestion.md index 2ef1b9556..0654ff016 100644 --- a/docs/tests/domains/knowledge-ingestion.md +++ b/docs/tests/domains/knowledge-ingestion.md @@ -1,6 +1,7 @@ # Knowledge Ingestion Coverage Source: `core/src/test/java/com/orgmemory/core/knowledge`, +`core/src/test/java/com/orgmemory/core/assistant`, `apps/api/src/test/java/com/orgmemory/api/knowledge`, `apps/api/src/test/java/com/orgmemory/api/source`, `apps/web/src/features/sources`, `apps/web/test/e2e`, @@ -11,7 +12,7 @@ Source: `core/src/test/java/com/orgmemory/core/knowledge`, `integrations/document-parsing-spring-ai/src/test`, `integrations/connectors/src/test`. -Reconciled: `2026-08-10-ingestion-coverage (c0c12728)`. +Reconciled: `2026-08-10-assistant-file-evidence (5aee7535)`. Evidence class: `apps/api/src/test/java/com/orgmemory/api/knowledge/KnowledgeIngestionIntegrationTests.java`. @@ -68,6 +69,15 @@ Evidence classes: `KnowledgeContentTypeTests`, `SourceUploadServiceTests`, and | An extension outside the closed policy and a file exceeding its format limit are rejected before evidence is persisted | `KnowledgeContentTypeTests`, `SourceUploadServiceTests`, `source-upload-dialog.test.ts` | | Browser users can select a spreadsheet and an HTML export and submit each as a governed multipart upload | `document-actions.spec.ts#uploads a spreadsheet and an HTML export through the governed document dialog` | +## Governed Evidence Boundary Coverage + +| Behavior | Automated evidence | +| --- | --- | +| `knowledge::evidence` is an exact named interface; Assistant and Graph consume it without opening Source Ledger or importing its persistence types | `ModulithVerificationTests#knowledgeEvidenceNamedInterfaceIsExact`, `#topLevelSearchConsumersUseOnlyTheParentSearchInterface` | +| Assistant bytes pass through the existing governed upload service and no Assistant parser/embedding path exists on the API request thread | `AssistantEvidenceUploadServiceTests`, `SourceUploadServiceTests` | +| Existing conversation ownership is checked before Source creation, while Source Ledger remains authoritative for Space, classification, format, limit, storage, revision, and job identity | `AssistantEvidenceUploadServiceTests#rejectsAnOpaqueConversationBeforeCreatingAGovernedSource`, `#delegatesBytesToTheGovernedFilePortBeforeCreatingTheBinding`, `SourceUploadServiceTests` | +| Exact tenant-scoped Source/revision lifecycle maps to processing, failed, unavailable, and ready binding states without granting access | `AssistantEvidenceServiceTests#reportsProcessingFailedAndRetiredSourceStatesBeforeAnswerability`, `#revokedAccessBecomesUnavailableAndCannotBeClaimed` | + ## Documents View And Retirement Coverage Evidence classes: `SourceQueryServiceTests`, `SourceListingIntegrationTests`, From 03f923f50d590a704239dae5e59c40f13efcd6fe Mon Sep 17 00:00:00 2001 From: kl3inIT Date: Mon, 10 Aug 2026 19:38:37 +0700 Subject: [PATCH 06/10] fix(assistant): refine governed evidence composer --- ...-08-10-assistant-governed-file-evidence.md | 20 +++++++++++ .../api/assistant/AssistantChatRequest.java | 2 +- .../AssistantChatRequestValidationTests.java | 6 ++-- apps/docs/generated/openapi.public.json | 2 +- .../assistant/assistant-draft-storage.test.ts | 8 ++--- .../assistant-message-constraints.ts | 2 +- .../assistant/components/assistant-page.tsx | 19 +++++------ apps/web/test/e2e/assistant-pipeline.spec.ts | 33 +++++++++---------- contracts/openapi.json | 2 +- 9 files changed, 54 insertions(+), 40 deletions(-) create mode 100644 .tegami/2026-08-10-assistant-governed-file-evidence.md diff --git a/.tegami/2026-08-10-assistant-governed-file-evidence.md b/.tegami/2026-08-10-assistant-governed-file-evidence.md new file mode 100644 index 000000000..0ebf00340 --- /dev/null +++ b/.tegami/2026-08-10-assistant-governed-file-evidence.md @@ -0,0 +1,20 @@ +--- +packages: + orgmemory: minor +subject: Use governed files as Assistant evidence +--- + +## Features + +- Upload up to three supported documents from the Assistant composer, publish + them to a chosen Knowledge Space, and wait for governed ingestion before use. +- Keep the exact ordered file selection across a failed retry and cite the same + permission-verified evidence used for the answer. + +## Security + +- Recheck conversation ownership, current Source revision, actor access, and + active retrieval-engine readiness before each selected-file turn. +- Keep selected files as a hard retrieval ceiling through graph expansion and + citation output; direct provider files and transient attachment bypasses + remain unavailable. diff --git a/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantChatRequest.java b/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantChatRequest.java index def61d842..d68e65d9b 100644 --- a/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantChatRequest.java +++ b/apps/api/src/main/java/com/orgmemory/api/assistant/AssistantChatRequest.java @@ -6,7 +6,7 @@ import java.util.UUID; record AssistantChatRequest( - @NotBlank @Size(max = 1_000) String message, + @NotBlank @Size(max = 8_000) String message, Integer limit, UUID conversationId, UUID modelActivationId, diff --git a/apps/api/src/test/java/com/orgmemory/api/assistant/AssistantChatRequestValidationTests.java b/apps/api/src/test/java/com/orgmemory/api/assistant/AssistantChatRequestValidationTests.java index f7a02b80b..8d758325a 100644 --- a/apps/api/src/test/java/com/orgmemory/api/assistant/AssistantChatRequestValidationTests.java +++ b/apps/api/src/test/java/com/orgmemory/api/assistant/AssistantChatRequestValidationTests.java @@ -29,9 +29,9 @@ void enforcesTheMessageLimitBoundary() { try (var factory = Validation.buildDefaultValidatorFactory()) { var validator = factory.getValidator(); var accepted = validator.validate( - new AssistantChatRequest("a".repeat(1_000), null, null, null)); + new AssistantChatRequest("a".repeat(8_000), null, null, null)); var rejected = validator.validate( - new AssistantChatRequest("a".repeat(1_001), null, null, null)); + new AssistantChatRequest("a".repeat(8_001), null, null, null)); assertEquals(0, accepted.size()); assertEquals(1, rejected.size()); @@ -88,7 +88,7 @@ void rejectsAnOversizedMessageBeforeOpeningTheStreamOrCreatingATurn() throws Exc mvc.perform(post("/api/assistant/chat") .contentType(APPLICATION_JSON) .accept(TEXT_EVENT_STREAM) - .content("{\"message\":\"" + "a".repeat(1_001) + "\"}")) + .content("{\"message\":\"" + "a".repeat(8_001) + "\"}")) .andExpect(status().isBadRequest()); verifyNoInteractions( diff --git a/apps/docs/generated/openapi.public.json b/apps/docs/generated/openapi.public.json index f94585028..32e60b14d 100644 --- a/apps/docs/generated/openapi.public.json +++ b/apps/docs/generated/openapi.public.json @@ -7929,7 +7929,7 @@ "properties": { "message": { "type": "string", - "maxLength": 1000, + "maxLength": 8000, "minLength": 0 }, "limit": { diff --git a/apps/web/src/features/assistant/assistant-draft-storage.test.ts b/apps/web/src/features/assistant/assistant-draft-storage.test.ts index ebb5eae94..04efd9aee 100644 --- a/apps/web/src/features/assistant/assistant-draft-storage.test.ts +++ b/apps/web/src/features/assistant/assistant-draft-storage.test.ts @@ -24,15 +24,15 @@ describe("assistant draft storage", () => { it("bounds drafts persisted by an older client", () => { sessionStorage.setItem( "orgmemory:assistant-draft:v1:actor-a:new", - "x".repeat(1_100), + "x".repeat(8_100), ) - expect(readAssistantDraft("actor-a")).toHaveLength(1_000) + expect(readAssistantDraft("actor-a")).toHaveLength(8_000) }) it("caps drafts at the server message limit and clears lifecycle scopes", () => { - const bounded = writeAssistantDraft("actor-a", "conversation-1", "x".repeat(1_100)) - expect(bounded).toHaveLength(1_000) + const bounded = writeAssistantDraft("actor-a", "conversation-1", "x".repeat(8_100)) + expect(bounded).toHaveLength(8_000) clearAssistantDraft("actor-a", "conversation-1") expect(readAssistantDraft("actor-a", "conversation-1")).toBe("") diff --git a/apps/web/src/features/assistant/assistant-message-constraints.ts b/apps/web/src/features/assistant/assistant-message-constraints.ts index fb4531947..755f3fcbf 100644 --- a/apps/web/src/features/assistant/assistant-message-constraints.ts +++ b/apps/web/src/features/assistant/assistant-message-constraints.ts @@ -1 +1 @@ -export const ASSISTANT_MESSAGE_MAX_CHARACTERS = 1_000 +export const ASSISTANT_MESSAGE_MAX_CHARACTERS = 8_000 diff --git a/apps/web/src/features/assistant/components/assistant-page.tsx b/apps/web/src/features/assistant/components/assistant-page.tsx index e27a00f74..48f36b854 100644 --- a/apps/web/src/features/assistant/components/assistant-page.tsx +++ b/apps/web/src/features/assistant/components/assistant-page.tsx @@ -377,6 +377,9 @@ function AssistantModelPicker({ const selected = options.find((option) => option.id === selectedId) ?? options.find((option) => option.defaultChoice) + const selectedName = selected?.defaultChoice + ? selected.modelId ?? selected.displayName + : selected?.displayName ?? selected?.modelId const groups = Object.entries( options.reduce>((current, option) => { const gateway = option.gatewayLabel ?? "Organization models" @@ -392,7 +395,7 @@ function AssistantModelPicker({ type="button" size="sm" disabled={disabled || loading || options.length === 0} - aria-label={`Choose model${selected?.displayName ? `, current model ${selected.displayName}` : ""}`} + aria-label={`Choose model${selectedName ? `, current model ${selectedName}` : ""}`} className="max-w-48 rounded-full px-2.5 text-content-secondary hover:text-content-primary" > {selected?.provider && selected.provider !== "custom" ? ( @@ -401,7 +404,7 @@ function AssistantModelPicker({