From c5948f26fc99c95bf484b04c39b74941a524d8a7 Mon Sep 17 00:00:00 2001 From: Vojtech <119944107+cvrysanek@users.noreply.github.com> Date: Mon, 18 May 2026 17:13:21 +0400 Subject: [PATCH] fix(api): harden API surface before Swagger (issue #336) (#339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(api): harden API surface before Swagger — 9 findings from issue #336 ADV-001: POST /api/sync/table-subscriptions now checks can_access() per table entry, matching the gate already on POST /api/sync/settings. ADV-002: GET /webhooks/jira/health gated behind require_admin; jira_domain removed from response to prevent anonymous info disclosure. ADV-003: GET /api/version no longer exposes commit_sha or schema_version. ADV-005: /docs, /redoc, /openapi.json now require a valid session via custom FastAPI routes (docs_url=None, redoc_url=None, openapi_url=None). ADV-006: /cli/ and /webhooks/ added to _API_PATH_PREFIXES so future auth-gated routes there return JSON 401 not an HTML redirect. ADV-007: GET /api/catalog/tables wired to CatalogTablesResponse model. ADV-008: TableSubscriptionUpdate.tables capped at max_length=500. ADV-009: GET /api/users and GET /auth/admin/tokens accept limit/offset (default 1000, max 10000); repositories updated accordingly. Tests: 11 new regression tests in TestApiHardening336; test_jira_webhooks fixture updated with seeded admin user; OpenAPI snapshot regenerated. * fix(test): update test_journey_jira health check to use admin auth after ADV-002 gate * fix(security): close /auth/bootstrap auth-bypass + BREAKING markers on ADV-002/003/005 Reviewer-flagged regression introduced by ADV-009's pagination on UserRepository.list_all(): the silent default LIMIT 1000 broke the bootstrap check at app/auth/router.py and the startup no-password warning at app/main.py — both call list_all() with no args and depend on exhaustive enumeration. On an instance with >1000 users where no password-holder lands in the email-sorted first page, [u for u in list_all() if u.get('password_hash')] becomes empty → bootstrap re-opens → an unauthenticated caller can claim admin via /auth/bootstrap. Real auth-bypass on a security-sensitive boot path. Fix: - src/repositories/users.py: list_all() restored to no-arg, returns EVERY row (no LIMIT). Comment explicitly warns against re-adding pagination here. API-surface pagination moved to a new list_paginated(limit, offset) method with its own docstring. - app/api/users.py: GET /api/users now calls list_paginated(). Existing query-param validation (limit <= 10000) preserved. Regression guards in tests/test_security.py::TestApiHardening336: - test_users_list_all_returns_every_row_no_silent_limit asserts list_all() takes no params other than self (via inspect.signature) so a future cleanup can't accidentally re-add limit/offset. - test_users_list_paginated_is_separate_method asserts the paginated variant is a distinct method, not an overload. CHANGELOG: added **BREAKING** markers per CLAUDE.md release discipline to three pre-existing ADV bullets that are observable breaking changes for external consumers: - ADV-002 (webhook health going from anonymous to admin-only) - ADV-003 (/api/version dropping commit_sha + schema_version) - ADV-005 (/docs, /redoc, /openapi.json going from anonymous to session-required) * release: 0.54.25 — API hardening before Swagger (ADV-001..009) + bootstrap-bypass regression fix --------- Co-authored-by: ZdenekSrotyr --- CHANGELOG.md | 30 + app/api/catalog.py | 18 +- app/api/health.py | 2 - app/api/jira_webhooks.py | 8 +- app/api/sync.py | 21 +- app/api/tokens.py | 6 +- app/api/users.py | 6 +- app/main.py | 32 +- pyproject.toml | 2 +- src/repositories/access_tokens.py | 6 +- src/repositories/users.py | 33 +- tests/snapshots/openapi.json | 11950 +++++++++++++++++++++++++++- tests/test_jira_webhooks.py | 72 +- tests/test_journey_jira.py | 6 +- tests/test_security.py | 173 + 15 files changed, 12045 insertions(+), 320 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3242880..f498321 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,36 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C ## [Unreleased] +## [0.54.25] — 2026-05-18 + +### Fixed +- `POST /api/sync/table-subscriptions` now enforces the same RBAC gate as + `POST /api/sync/settings` — authenticated users can no longer subscribe to + tables they have no `resource_grants` row for (ADV-001, issue #336). +- **BREAKING:** `GET /webhooks/jira/health` is now admin-only; `jira_domain` + removed from the response to prevent anonymous information disclosure + (ADV-002). Uptime monitors that polled this endpoint anonymously must now + attach an admin PAT or switch to `/api/health` (which remains public). +- **BREAKING:** `GET /api/version` no longer exposes `commit_sha` or + `schema_version` — only `version`, `channel`, `image_tag`, `deployed_at` + remain (ADV-003). Deploy scripts / dashboards scraping the removed fields + must either authenticate against a (separate, forthcoming) admin endpoint + or read them from the GHCR image labels. +- **BREAKING:** `/docs`, `/redoc`, and `/openapi.json` now require a valid + session — the full admin API surface is no longer visible to + unauthenticated requests (ADV-005). CLI tools generating client code from + the schema must attach a PAT or use an authenticated browser session. + +### Changed +- `/cli/` and `/webhooks/` prefixes added to `_API_PATH_PREFIXES` so any + future auth-gated endpoint under those paths returns JSON `401` rather than + an HTML redirect (ADV-006). +- `GET /api/users` and `GET /auth/admin/tokens` accept `limit` (default 1000, + max 10 000) and `offset` query parameters; `POST /api/sync/table-subscriptions` + now rejects `tables` dicts with more than 500 entries (ADV-008, ADV-009). +- `GET /api/catalog/tables` now has a typed `response_model` (`CatalogTablesResponse`) + so Swagger generates an accurate schema for that endpoint (ADV-007). + ### Internal - Added `TestFullLifecycleFromInstaller` integration test class (`tests/test_store_entity_versions.py`) covering the full diff --git a/app/api/catalog.py b/app/api/catalog.py index ba79814..562ea9d 100644 --- a/app/api/catalog.py +++ b/app/api/catalog.py @@ -1,8 +1,10 @@ """Catalog endpoints — table profiles, metrics.""" import json +from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException +from pydantic import BaseModel import duckdb from app.auth.dependencies import get_current_user, _get_db @@ -13,6 +15,20 @@ from src.rbac import can_access_table router = APIRouter(prefix="/api/catalog", tags=["catalog"]) +class CatalogTableItem(BaseModel): + id: str + name: str + description: Optional[str] = None + source_type: Optional[str] = None + sync_strategy: Optional[str] = None + query_mode: str = "local" + + +class CatalogTablesResponse(BaseModel): + tables: List[CatalogTableItem] + count: int + + @router.get("/profile/{table_name}") async def get_table_profile( table_name: str, @@ -40,7 +56,7 @@ async def get_table_profile( return profile -@router.get("/tables") +@router.get("/tables", response_model=CatalogTablesResponse) async def list_catalog_tables( user: dict = Depends(get_current_user), conn: duckdb.DuckDBPyConnection = Depends(_get_db), diff --git a/app/api/health.py b/app/api/health.py index 653dc5a..af10706 100644 --- a/app/api/health.py +++ b/app/api/health.py @@ -491,7 +491,5 @@ async def version_info(): "version": os.environ.get("AGNES_VERSION", "dev"), "channel": os.environ.get("RELEASE_CHANNEL", "dev"), "image_tag": os.environ.get("AGNES_TAG", "unknown"), - "commit_sha": os.environ.get("AGNES_COMMIT_SHA", "unknown"), - "schema_version": SCHEMA_VERSION, "deployed_at": _DEPLOYED_AT, } diff --git a/app/api/jira_webhooks.py b/app/api/jira_webhooks.py index dc0511c..9e54601 100644 --- a/app/api/jira_webhooks.py +++ b/app/api/jira_webhooks.py @@ -11,11 +11,12 @@ import json import logging from datetime import datetime, timezone -from fastapi import APIRouter, Request, Response +from fastapi import APIRouter, Depends, Request, Response from fastapi.responses import JSONResponse import re +from app.auth.access import require_admin as _require_admin from connectors.jira.service import Config, get_jira_service from connectors.jira.validation import is_valid_issue_key, safe_join_under @@ -182,13 +183,12 @@ async def receive_jira_webhook(request: Request) -> Response: @router.get("/jira/health") -async def jira_webhook_health() -> dict: - """Health check for Jira webhook endpoint.""" +async def jira_webhook_health(user: dict = Depends(_require_admin)) -> dict: + """Health check for Jira webhook endpoint. Admin-only: exposes secret presence.""" jira_service = get_jira_service() return { "status": "ok", "configured": jira_service.is_configured(), "webhook_secret_set": bool(Config.JIRA_WEBHOOK_SECRET), - "jira_domain": Config.JIRA_DOMAIN or "(not set)", } diff --git a/app/api/sync.py b/app/api/sync.py index 828ad01..9e35ab0 100644 --- a/app/api/sync.py +++ b/app/api/sync.py @@ -12,7 +12,7 @@ from pathlib import Path from typing import Any, Optional, List from fastapi import APIRouter, Body, Depends, HTTPException, BackgroundTasks -from pydantic import BaseModel +from pydantic import BaseModel, Field import duckdb from app.auth.access import require_admin @@ -1025,7 +1025,7 @@ async def update_sync_settings( class TableSubscriptionUpdate(BaseModel): table_mode: str = "all" # "all" or "explicit" - tables: dict = {} # {table_name: bool} + tables: dict = Field(default_factory=dict, max_length=500) # {table_name: bool} @router.get("/table-subscriptions") @@ -1045,8 +1045,21 @@ async def update_table_subscriptions( user: dict = Depends(get_current_user), conn: duckdb.DuckDBPyConnection = Depends(_get_db), ): - """Update per-table subscription preferences.""" + """Update per-table subscription preferences. + + Mirrors the RBAC gate in POST /settings: a table can only be subscribed + to when the user holds a resource_grants row for it (or is Admin). This + prevents an authenticated user from subscribing to tables they cannot read. + """ + from app.auth.access import can_access + from app.resource_types import ResourceType + repo = SyncSettingsRepository(conn) + results = {} for table_name, enabled in request.tables.items(): + if not can_access(user["id"], ResourceType.TABLE.value, table_name, conn): + results[table_name] = {"error": "no permission"} + continue repo.set_dataset_enabled(user["id"], table_name, enabled) - return {"table_mode": request.table_mode, "updated": len(request.tables)} + results[table_name] = {"enabled": enabled} + return {"table_mode": request.table_mode, "updated": results} diff --git a/app/api/tokens.py b/app/api/tokens.py index e13dd07..055be71 100644 --- a/app/api/tokens.py +++ b/app/api/tokens.py @@ -6,7 +6,7 @@ from datetime import datetime, timezone, timedelta from typing import Optional, List import duckdb -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel from app.auth.access import require_admin @@ -207,10 +207,12 @@ async def revoke_token( @admin_router.get("", response_model=List[AdminTokenItem]) async def admin_list_tokens( + limit: int = Query(default=1000, ge=1, le=10000), + offset: int = Query(default=0, ge=0), user: dict = Depends(require_admin), conn: duckdb.DuckDBPyConnection = Depends(_get_db), ): - return [_row_to_admin_item(r) for r in AccessTokenRepository(conn).list_all_with_user()] + return [_row_to_admin_item(r) for r in AccessTokenRepository(conn).list_all_with_user(limit=limit, offset=offset)] @admin_router.delete("/{token_id}", status_code=204) diff --git a/app/api/users.py b/app/api/users.py index 8d53d11..bbca67e 100644 --- a/app/api/users.py +++ b/app/api/users.py @@ -7,7 +7,7 @@ from datetime import datetime, timezone from typing import Optional, List import duckdb -from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi import APIRouter, Depends, HTTPException, Query, Request from pydantic import BaseModel from argon2 import PasswordHasher @@ -228,10 +228,12 @@ def _set_admin_membership( @router.get("", response_model=List[UserResponse]) async def list_users( + limit: int = Query(default=1000, ge=1, le=10000), + offset: int = Query(default=0, ge=0), user: dict = Depends(require_admin), conn: duckdb.DuckDBPyConnection = Depends(_get_db), ): - return [_to_response(u, conn) for u in UserRepository(conn).list_all()] + return [_to_response(u, conn) for u in UserRepository(conn).list_paginated(limit=limit, offset=offset)] @router.get("/{user_id}", response_model=UserResponse) diff --git a/app/main.py b/app/main.py index 0021a69..bc9976f 100644 --- a/app/main.py +++ b/app/main.py @@ -36,7 +36,7 @@ setup_logging("app") from app.version import APP_VERSION, MIN_COMPAT_CLI_VERSION -from fastapi import FastAPI +from fastapi import Depends, FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import RedirectResponse from fastapi.staticfiles import StaticFiles @@ -354,6 +354,12 @@ def create_app() -> FastAPI: description="Data distribution platform for AI analytical systems", version=APP_VERSION, lifespan=lifespan, + # Swagger UI / OpenAPI JSON gated behind authentication — custom + # routes added below before the web_router catch-all. Setting these + # to None disables FastAPI's default unauthenticated endpoints. + docs_url=None, + redoc_url=None, + openapi_url=None, # Intentionally NOT debug=DEBUG: FastAPI's debug=True installs # Starlette's ServerErrorMiddleware which intercepts unhandled # Exceptions and renders a plain-HTML traceback BEFORE our @@ -691,6 +697,27 @@ def create_app() -> FastAPI: from a2wsgi import WSGIMiddleware app.mount("/marketplace.git", WSGIMiddleware(make_git_wsgi_app())) + # Authenticated Swagger / ReDoc / OpenAPI JSON — requires a valid session + # so the full admin API surface is not visible to unauthenticated callers. + # Must be registered before web_router (catch-all). /openapi.json is also + # added to _API_PATH_PREFIXES below so auth failures return JSON 401 + # rather than an HTML redirect. + from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html + from fastapi.responses import HTMLResponse as _HTMLResponse + from app.auth.dependencies import get_current_user as _get_current_user + + @app.get("/docs", include_in_schema=False, response_class=_HTMLResponse) + async def swagger_ui(user: dict = Depends(_get_current_user)): + return get_swagger_ui_html(openapi_url="/openapi.json", title="Agnes API") + + @app.get("/redoc", include_in_schema=False, response_class=_HTMLResponse) + async def redoc_ui(user: dict = Depends(_get_current_user)): + return get_redoc_html(openapi_url="/openapi.json", title="Agnes API — ReDoc") + + @app.get("/openapi.json", include_in_schema=False) + async def openapi_spec(user: dict = Depends(_get_current_user)): + return app.openapi() + # Web UI router (must be last — has catch-all routes) app.include_router(web_router) @@ -699,6 +726,9 @@ def create_app() -> FastAPI: _API_PATH_PREFIXES: tuple[str, ...] = ( "/api/", "/auth/", + "/cli/", + "/openapi.json", + "/webhooks/", "/marketplace.zip", "/marketplace.git", "/marketplace/", diff --git a/pyproject.toml b/pyproject.toml index d5f0c31..c175b4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "agnes-the-ai-analyst" -version = "0.54.24" +version = "0.54.25" description = "Agnes — AI Data Analyst platform for AI analytical systems" requires-python = ">=3.11,<3.14" license = "MIT" diff --git a/src/repositories/access_tokens.py b/src/repositories/access_tokens.py index 3021713..cef6970 100644 --- a/src/repositories/access_tokens.py +++ b/src/repositories/access_tokens.py @@ -60,7 +60,7 @@ class AccessTokenRepository: columns = [desc[0] for desc in self.conn.description] return [dict(zip(columns, r)) for r in rows] - def list_all_with_user(self) -> List[Dict[str, Any]]: + def list_all_with_user(self, limit: int = 1000, offset: int = 0) -> List[Dict[str, Any]]: """Admin view: all tokens joined with the owning user's email. Returns dict rows including every column of `personal_access_tokens` @@ -73,7 +73,9 @@ class AccessTokenRepository: FROM personal_access_tokens t LEFT JOIN users u ON u.id = t.user_id ORDER BY t.created_at DESC - """ + LIMIT ? OFFSET ? + """, + [limit, offset], ).fetchall() if not rows: return [] diff --git a/src/repositories/users.py b/src/repositories/users.py index 20a8a1e..43f8530 100644 --- a/src/repositories/users.py +++ b/src/repositories/users.py @@ -25,12 +25,43 @@ class UserRepository: return self._row_to_dict(result) def list_all(self) -> List[Dict[str, Any]]: - results = self.conn.execute("SELECT * FROM users ORDER BY email").fetchall() + """Return EVERY user row. Used by bootstrap-lock + startup + warning paths that need to inspect the whole table (see + ``app/auth/router.py::bootstrap`` and ``app/main.py``'s + no-password-set warning). Do NOT add a LIMIT here — the + bootstrap check ``[u for u in list_all() if u.get('password_hash')]`` + re-opens the endpoint if any password-holder gets paginated + out, which would let an unauthenticated caller claim admin + on instances with >LIMIT users. API-surface pagination uses + ``list_paginated()`` below. + """ + results = self.conn.execute( + "SELECT * FROM users ORDER BY email" + ).fetchall() if not results: return [] columns = [desc[0] for desc in self.conn.description] return [dict(zip(columns, row)) for row in results] + def list_paginated(self, limit: int = 1000, offset: int = 0) -> List[Dict[str, Any]]: + """Paginated user listing for the admin API surface (#336 + ADV-009). Safe to bound — callers explicitly opt into the + windowed shape and the API enforces ``limit <= 10000`` at + the Query()-validation layer. Do not call from bootstrap / + startup paths that need exhaustive enumeration; use + ``list_all()`` for those. + """ + results = self.conn.execute( + "SELECT * FROM users ORDER BY email LIMIT ? OFFSET ?", [limit, offset] + ).fetchall() + if not results: + return [] + columns = [desc[0] for desc in self.conn.description] + return [dict(zip(columns, row)) for row in results] + + def count_all(self) -> int: + return self.conn.execute("SELECT COUNT(*) FROM users").fetchone()[0] + def create( self, id: str, diff --git a/tests/snapshots/openapi.json b/tests/snapshots/openapi.json index 1bbb7a8..cc59ffe 100644 --- a/tests/snapshots/openapi.json +++ b/tests/snapshots/openapi.json @@ -55,6 +55,83 @@ "title": "AdminActionRequest", "type": "object" }, + "AdminInitialWorkspaceResponse": { + "description": "Admin-facing view; surfaces sync state + has_token (no secret leak).", + "properties": { + "branch": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Branch" + }, + "configured": { + "default": false, + "title": "Configured", + "type": "boolean" + }, + "file_count": { + "default": 0, + "title": "File Count", + "type": "integer" + }, + "has_token": { + "default": false, + "title": "Has Token", + "type": "boolean" + }, + "last_commit_sha": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Last Commit Sha" + }, + "last_error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Last Error" + }, + "last_synced_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Last Synced At" + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Url" + } + }, + "title": "AdminInitialWorkspaceResponse", + "type": "object" + }, "AdminTokenItem": { "description": "Admin list row: adds owner identity + last IP for incident response.", "properties": { @@ -147,6 +224,99 @@ "title": "AdminTokenItem", "type": "object" }, + "AnalystInitialWorkspaceResponse": { + "description": "PAT-authed analyst view; what ``agnes init`` consumes.", + "properties": { + "configured": { + "default": false, + "title": "Configured", + "type": "boolean" + }, + "files": { + "default": [], + "items": { + "type": "string" + }, + "title": "Files", + "type": "array" + }, + "synced": { + "default": false, + "title": "Synced", + "type": "boolean" + }, + "synced_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Synced At" + }, + "template_sha": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Template Sha" + }, + "template_source": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Template Source" + } + }, + "title": "AnalystInitialWorkspaceResponse", + "type": "object" + }, + "AppliedRequest": { + "description": "CLI audit event after the analyst's workspace has been extracted.", + "properties": { + "files_created": { + "default": 0, + "title": "Files Created", + "type": "integer" + }, + "files_overwritten": { + "default": 0, + "title": "Files Overwritten", + "type": "integer" + }, + "mode": { + "title": "Mode", + "type": "string" + }, + "template_sha": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Template Sha" + } + }, + "required": [ + "mode" + ], + "title": "AppliedRequest", + "type": "object" + }, "BannerResponse": { "properties": { "content": { @@ -203,6 +373,109 @@ "title": "BatchActionRequest", "type": "object" }, + "Body_create_entity_api_store_entities_post": { + "properties": { + "category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Category" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "docs": { + "default": [], + "items": { + "contentMediaType": "application/octet-stream", + "type": "string" + }, + "title": "Docs", + "type": "array" + }, + "file": { + "contentMediaType": "application/octet-stream", + "title": "File", + "type": "string" + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "photo": { + "anyOf": [ + { + "contentMediaType": "application/octet-stream", + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Photo" + }, + "type": { + "title": "Type", + "type": "string" + }, + "video_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Url" + } + }, + "required": [ + "file", + "type" + ], + "title": "Body_create_entity_api_store_entities_post", + "type": "object" + }, + "Body_import_bundle_api_store_import_bundle_post": { + "properties": { + "file": { + "contentMediaType": "application/octet-stream", + "title": "File", + "type": "string" + }, + "mode": { + "default": "merge", + "title": "Mode", + "type": "string" + } + }, + "required": [ + "file" + ], + "title": "Body_import_bundle_api_store_import_bundle_post", + "type": "object" + }, "Body_import_metrics_api_admin_metrics_import_post": { "properties": { "file": { @@ -240,6 +513,25 @@ "title": "Body_password_login_web_auth_password_login_web_post", "type": "object" }, + "Body_preview_entity_api_store_entities_preview_post": { + "properties": { + "file": { + "contentMediaType": "application/octet-stream", + "title": "File", + "type": "string" + }, + "type": { + "title": "Type", + "type": "string" + } + }, + "required": [ + "file", + "type" + ], + "title": "Body_preview_entity_api_store_entities_preview_post", + "type": "object" + }, "Body_reset_confirm_auth_password_reset_confirm_post": { "properties": { "confirm_password": { @@ -323,6 +615,91 @@ "title": "Body_setup_request_auth_password_setup_request_post", "type": "object" }, + "Body_update_entity_api_store_entities__entity_id__put": { + "properties": { + "category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Category" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "file": { + "anyOf": [ + { + "contentMediaType": "application/octet-stream", + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File" + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "photo": { + "anyOf": [ + { + "contentMediaType": "application/octet-stream", + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Photo" + }, + "type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Type" + }, + "video_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Url" + } + }, + "title": "Body_update_entity_api_store_entities__entity_id__put", + "type": "object" + }, "Body_upload_artifact_api_upload_artifacts_post": { "properties": { "file": { @@ -397,6 +774,122 @@ "title": "BulkUpdateRequest", "type": "object" }, + "CatalogTableItem": { + "properties": { + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "query_mode": { + "default": "local", + "title": "Query Mode", + "type": "string" + }, + "source_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Source Type" + }, + "sync_strategy": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Sync Strategy" + } + }, + "required": [ + "id", + "name" + ], + "title": "CatalogTableItem", + "type": "object" + }, + "CatalogTablesResponse": { + "properties": { + "count": { + "title": "Count", + "type": "integer" + }, + "tables": { + "items": { + "$ref": "#/components/schemas/CatalogTableItem" + }, + "title": "Tables", + "type": "array" + } + }, + "required": [ + "tables", + "count" + ], + "title": "CatalogTablesResponse", + "type": "object" + }, + "CategoriesResponse": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/CategoryEntry" + }, + "title": "Items", + "type": "array" + } + }, + "required": [ + "items" + ], + "title": "CategoriesResponse", + "type": "object" + }, + "CategoryEntry": { + "properties": { + "count": { + "title": "Count", + "type": "integer" + }, + "icon_key": { + "title": "Icon Key", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + } + }, + "required": [ + "name", + "count", + "icon_key" + ], + "title": "CategoryEntry", + "type": "object" + }, "ClaudeMdResponse": { "properties": { "content": { @@ -466,6 +959,30 @@ "title": "ColumnMetadataSave", "type": "object" }, + "CommandEntry": { + "properties": { + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "name": { + "title": "Name", + "type": "string" + } + }, + "required": [ + "name" + ], + "title": "CommandEntry", + "type": "object" + }, "ConfigureRequest": { "properties": { "allowed_domain": { @@ -723,6 +1240,28 @@ ], "title": "Branch" }, + "curator_email": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Curator Email" + }, + "curator_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Curator Name" + }, "description": { "anyOf": [ { @@ -783,6 +1322,22 @@ "name": { "title": "Name", "type": "string" + }, + "scope": { + "default": "general", + "title": "Scope", + "type": "string" + }, + "ttl_seconds": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Ttl Seconds" } }, "required": [ @@ -859,6 +1414,66 @@ "title": "CreateUserRequest", "type": "object" }, + "CuratedPlugin": { + "properties": { + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "enabled": { + "title": "Enabled", + "type": "boolean" + }, + "is_system": { + "default": false, + "title": "Is System", + "type": "boolean" + }, + "manifest_name": { + "title": "Manifest Name", + "type": "string" + }, + "marketplace_id": { + "title": "Marketplace Id", + "type": "string" + }, + "marketplace_slug": { + "title": "Marketplace Slug", + "type": "string" + }, + "plugin_name": { + "title": "Plugin Name", + "type": "string" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Version" + } + }, + "required": [ + "marketplace_id", + "marketplace_slug", + "plugin_name", + "manifest_name", + "enabled" + ], + "title": "CuratedPlugin", + "type": "object" + }, "DatasetSettingRequest": { "properties": { "dataset": { @@ -906,6 +1521,25 @@ "title": "DeployScriptRequest", "type": "object" }, + "DocEntry": { + "description": "One doc file shipped alongside a Store entity. URL points at the\nserving endpoint (``/api/store/entities//docs/``).", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "url": { + "title": "Url", + "type": "string" + } + }, + "required": [ + "name", + "url" + ], + "title": "DocEntry", + "type": "object" + }, "EditRequest": { "properties": { "content": { @@ -982,6 +1616,25 @@ "title": "EffectiveAccessResponse", "type": "object" }, + "FileEntry": { + "description": "One file in a plugin / skill / agent bundle. Path is relative to the\nbundle root; size is bytes (rendered with ``humanbytes`` on the client).", + "properties": { + "path": { + "title": "Path", + "type": "string" + }, + "size": { + "title": "Size", + "type": "integer" + } + }, + "required": [ + "path", + "size" + ], + "title": "FileEntry", + "type": "object" + }, "GrantResponse": { "properties": { "assigned_at": { @@ -1165,6 +1818,41 @@ "title": "HTTPValidationError", "type": "object" }, + "HookEntry": { + "properties": { + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "event": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Event" + }, + "name": { + "title": "Name", + "type": "string" + } + }, + "required": [ + "name" + ], + "title": "HookEntry", + "type": "object" + }, "HybridQueryRequest": { "properties": { "register_bq": { @@ -1186,6 +1874,435 @@ "title": "HybridQueryRequest", "type": "object" }, + "ImportBundleResponse": { + "properties": { + "errors": { + "default": [], + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Errors", + "type": "array" + }, + "imported": { + "title": "Imported", + "type": "integer" + }, + "replaced": { + "title": "Replaced", + "type": "integer" + }, + "skipped": { + "title": "Skipped", + "type": "integer" + }, + "stub_users_created": { + "title": "Stub Users Created", + "type": "integer" + } + }, + "required": [ + "imported", + "replaced", + "skipped", + "stub_users_created" + ], + "title": "ImportBundleResponse", + "type": "object" + }, + "InnerDetailResponse": { + "properties": { + "body": { + "title": "Body", + "type": "string" + }, + "bundle_size": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Bundle Size" + }, + "category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Category" + }, + "cover_photo_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Cover Photo Url" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "description_long_html": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description Long Html" + }, + "display_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Display Name" + }, + "docs": { + "default": [], + "items": { + "$ref": "#/components/schemas/DocEntry" + }, + "title": "Docs", + "type": "array" + }, + "files": { + "default": [], + "items": { + "$ref": "#/components/schemas/FileEntry" + }, + "title": "Files", + "type": "array" + }, + "invocation": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Invocation" + }, + "kind": { + "enum": [ + "skill", + "agent" + ], + "title": "Kind", + "type": "string" + }, + "manifest_name": { + "default": "", + "title": "Manifest Name", + "type": "string" + }, + "marketplace_id": { + "title": "Marketplace Id", + "type": "string" + }, + "marketplace_name": { + "default": "", + "title": "Marketplace Name", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "parent_author_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Parent Author Name" + }, + "parent_display_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Parent Display Name" + }, + "parent_stack_count": { + "default": 0, + "title": "Parent Stack Count", + "type": "integer" + }, + "parent_updated_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Parent Updated At" + }, + "plugin_name": { + "title": "Plugin Name", + "type": "string" + }, + "relpath": { + "title": "Relpath", + "type": "string" + }, + "sample_interaction": { + "anyOf": [ + { + "$ref": "#/components/schemas/SampleInteraction" + }, + { + "type": "null" + } + ] + }, + "tagline": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Tagline" + }, + "telemetry": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Telemetry" + }, + "use_cases": { + "default": [], + "items": { + "$ref": "#/components/schemas/UseCase" + }, + "title": "Use Cases", + "type": "array" + }, + "video_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Url" + }, + "when_to_use_html": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "When To Use Html" + } + }, + "required": [ + "marketplace_id", + "plugin_name", + "kind", + "name", + "body", + "relpath" + ], + "title": "InnerDetailResponse", + "type": "object" + }, + "InnerItemSummary": { + "properties": { + "cover_photo_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Cover Photo Url" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "detail_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Detail Url" + }, + "distinct_users_30d": { + "default": 0, + "title": "Distinct Users 30D", + "type": "integer" + }, + "invocations_30d": { + "default": 0, + "title": "Invocations 30D", + "type": "integer" + }, + "name": { + "title": "Name", + "type": "string" + }, + "parent_stack_count": { + "default": 0, + "title": "Parent Stack Count", + "type": "integer" + }, + "trend_pct": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Trend Pct" + } + }, + "required": [ + "name" + ], + "title": "InnerItemSummary", + "type": "object" + }, + "InstallActionResponse": { + "properties": { + "installed": { + "title": "Installed", + "type": "boolean" + } + }, + "required": [ + "installed" + ], + "title": "InstallActionResponse", + "type": "object" + }, + "InstallResponse": { + "properties": { + "entity_id": { + "title": "Entity Id", + "type": "string" + }, + "installed": { + "title": "Installed", + "type": "boolean" + } + }, + "required": [ + "entity_id", + "installed" + ], + "title": "InstallResponse", + "type": "object" + }, + "ItemListResponse": { + "properties": { + "available_sorts": { + "default": [ + "recent", + "most_used", + "most_adopted" + ], + "items": { + "type": "string" + }, + "title": "Available Sorts", + "type": "array" + }, + "items": { + "items": { + "$ref": "#/components/schemas/MarketplaceItem" + }, + "title": "Items", + "type": "array" + }, + "page": { + "title": "Page", + "type": "integer" + }, + "page_size": { + "title": "Page Size", + "type": "integer" + }, + "total": { + "title": "Total", + "type": "integer" + } + }, + "required": [ + "items", + "total", + "page", + "page_size" + ], + "title": "ItemListResponse", + "type": "object" + }, "LocalMdRequest": { "properties": { "content": { @@ -1230,6 +2347,220 @@ "title": "MagicLinkVerify", "type": "object" }, + "MarketplaceItem": { + "properties": { + "added": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Added" + }, + "category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Category" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "detail_url": { + "title": "Detail Url", + "type": "string" + }, + "display_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Display Name" + }, + "distinct_users_30d": { + "default": 0, + "title": "Distinct Users 30D", + "type": "integer" + }, + "distinct_users_7d": { + "default": 0, + "title": "Distinct Users 7D", + "type": "integer" + }, + "id": { + "title": "Id", + "type": "string" + }, + "installed": { + "default": false, + "title": "Installed", + "type": "boolean" + }, + "invocations_30d": { + "default": 0, + "title": "Invocations 30D", + "type": "integer" + }, + "invocations_7d": { + "default": 0, + "title": "Invocations 7D", + "type": "integer" + }, + "is_system": { + "default": false, + "title": "Is System", + "type": "boolean" + }, + "is_viewer_owner": { + "default": false, + "title": "Is Viewer Owner", + "type": "boolean" + }, + "marketplace_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Marketplace Name" + }, + "marketplace_slug": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Marketplace Slug" + }, + "name": { + "title": "Name", + "type": "string" + }, + "owner": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Owner" + }, + "photo_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Photo Url" + }, + "source": { + "enum": [ + "curated", + "flea" + ], + "title": "Source", + "type": "string" + }, + "stack_count": { + "default": 0, + "title": "Stack Count", + "type": "integer" + }, + "tagline": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Tagline" + }, + "trend_pct": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Trend Pct" + }, + "type": { + "enum": [ + "skill", + "agent", + "plugin" + ], + "title": "Type", + "type": "string" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Version" + }, + "visibility_status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Visibility Status" + } + }, + "required": [ + "id", + "source", + "name", + "type", + "detail_url" + ], + "title": "MarketplaceItem", + "type": "object" + }, "MarketplaceResponse": { "properties": { "branch": { @@ -1243,6 +2574,28 @@ ], "title": "Branch" }, + "curator_email": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Curator Email" + }, + "curator_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Curator Name" + }, "description": { "anyOf": [ { @@ -1340,6 +2693,41 @@ "title": "MarketplaceResponse", "type": "object" }, + "McpEntry": { + "properties": { + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "name": { + "title": "Name", + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Type" + } + }, + "required": [ + "name" + ], + "title": "McpEntry", + "type": "object" + }, "MemberResponse": { "properties": { "active": { @@ -1598,6 +2986,101 @@ "title": "MetricCreate", "type": "object" }, + "MyStackResponse": { + "properties": { + "curated": { + "items": { + "$ref": "#/components/schemas/CuratedPlugin" + }, + "title": "Curated", + "type": "array" + }, + "store": { + "items": { + "$ref": "#/components/schemas/StoreInstallEntry" + }, + "title": "Store", + "type": "array" + } + }, + "required": [ + "curated", + "store" + ], + "title": "MyStackResponse", + "type": "object" + }, + "NewsBody": { + "properties": { + "content": { + "default": "", + "title": "Content", + "type": "string" + }, + "intro": { + "default": "", + "title": "Intro", + "type": "string" + } + }, + "title": "NewsBody", + "type": "object" + }, + "OkResponse": { + "properties": { + "ok": { + "default": true, + "title": "Ok", + "type": "boolean" + } + }, + "title": "OkResponse", + "type": "object" + }, + "OnboardedRequest": { + "properties": { + "onboarded": { + "default": true, + "title": "Onboarded", + "type": "boolean" + }, + "source": { + "default": "agnes_init", + "enum": [ + "agnes_init", + "self_acknowledged", + "self_unmark" + ], + "title": "Source", + "type": "string" + } + }, + "title": "OnboardedRequest", + "type": "object" + }, + "OwnerOption": { + "properties": { + "display_name": { + "title": "Display Name", + "type": "string" + }, + "entity_count": { + "title": "Entity Count", + "type": "integer" + }, + "user_id": { + "title": "User Id", + "type": "string" + } + }, + "required": [ + "user_id", + "display_name", + "entity_count" + ], + "title": "OwnerOption", + "type": "object" + }, "PasswordLoginRequest": { "properties": { "email": { @@ -1728,6 +3211,360 @@ "title": "PersonalFlagRequest", "type": "object" }, + "PluginDetailResponse": { + "description": "Unified detail response for a plugin (curated *or* flea).\n\nFrontend-side switching is purely cosmetic \u2014 `source` controls the\nCurated/Flea pill, photo URL fallback path, and owner label.", + "properties": { + "agents": { + "default": [], + "items": { + "$ref": "#/components/schemas/InnerItemSummary" + }, + "title": "Agents", + "type": "array" + }, + "author_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Author Name" + }, + "bundle_size": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Bundle Size" + }, + "category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Category" + }, + "commands": { + "default": [], + "items": { + "$ref": "#/components/schemas/CommandEntry" + }, + "title": "Commands", + "type": "array" + }, + "cover_photo_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Cover Photo Url" + }, + "curator_email": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Curator Email" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "description_long_html": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description Long Html" + }, + "display_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Display Name" + }, + "docs": { + "default": [], + "items": { + "$ref": "#/components/schemas/DocEntry" + }, + "title": "Docs", + "type": "array" + }, + "entity_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Entity Id" + }, + "files": { + "default": [], + "items": { + "$ref": "#/components/schemas/FileEntry" + }, + "title": "Files", + "type": "array" + }, + "homepage": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Homepage" + }, + "hooks": { + "default": [], + "items": { + "$ref": "#/components/schemas/HookEntry" + }, + "title": "Hooks", + "type": "array" + }, + "install_count": { + "default": 0, + "title": "Install Count", + "type": "integer" + }, + "installed": { + "default": false, + "title": "Installed", + "type": "boolean" + }, + "is_system": { + "default": false, + "title": "Is System", + "type": "boolean" + }, + "manifest_name": { + "title": "Manifest Name", + "type": "string" + }, + "marketplace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Marketplace Id" + }, + "marketplace_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Marketplace Name" + }, + "mcps": { + "default": [], + "items": { + "$ref": "#/components/schemas/McpEntry" + }, + "title": "Mcps", + "type": "array" + }, + "owner_display": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Owner Display" + }, + "plugin_name": { + "title": "Plugin Name", + "type": "string" + }, + "released_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Released At" + }, + "sample_interaction": { + "anyOf": [ + { + "$ref": "#/components/schemas/SampleInteraction" + }, + { + "type": "null" + } + ] + }, + "skills": { + "default": [], + "items": { + "$ref": "#/components/schemas/InnerItemSummary" + }, + "title": "Skills", + "type": "array" + }, + "source": { + "enum": [ + "curated", + "flea" + ], + "title": "Source", + "type": "string" + }, + "stack_count": { + "default": 0, + "title": "Stack Count", + "type": "integer" + }, + "submission_status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Submission Status" + }, + "tagline": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Tagline" + }, + "telemetry": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Telemetry" + }, + "updated_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Updated At" + }, + "use_cases": { + "default": [], + "items": { + "$ref": "#/components/schemas/UseCase" + }, + "title": "Use Cases", + "type": "array" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Version" + }, + "video_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Url" + }, + "visibility_status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Visibility Status" + } + }, + "required": [ + "source", + "plugin_name", + "manifest_name" + ], + "title": "PluginDetailResponse", + "type": "object" + }, "PluginResponse": { "properties": { "author_name": { @@ -1774,6 +3611,11 @@ ], "title": "Homepage" }, + "is_system": { + "default": false, + "title": "Is System", + "type": "boolean" + }, "name": { "title": "Name", "type": "string" @@ -1816,6 +3658,100 @@ "title": "PluginResponse", "type": "object" }, + "PreviewComponent": { + "properties": { + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "file": { + "title": "File", + "type": "string" + }, + "issues": { + "default": [], + "items": {}, + "title": "Issues", + "type": "array" + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "ok": { + "title": "Ok", + "type": "boolean" + }, + "type": { + "title": "Type", + "type": "string" + } + }, + "required": [ + "type", + "file", + "ok" + ], + "title": "PreviewComponent", + "type": "object" + }, + "PreviewResponse": { + "properties": { + "components": { + "default": [], + "items": { + "$ref": "#/components/schemas/PreviewComponent" + }, + "title": "Components", + "type": "array" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "type": { + "title": "Type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "PreviewResponse", + "type": "object" + }, "QueryRequest": { "properties": { "limit": { @@ -1864,6 +3800,42 @@ "title": "QueryResponse", "type": "object" }, + "RegisterRequest": { + "description": "Upsert payload \u2014 fields not provided are left untouched on update.\n\n``token = None`` means \"leave existing PAT alone\".\n``token = \"\"`` means \"clear PAT\".\n``token = \"ghp_...\"`` means \"set/rotate PAT\".", + "properties": { + "branch": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Branch" + }, + "token": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Token" + }, + "url": { + "title": "Url", + "type": "string" + } + }, + "required": [ + "url" + ], + "title": "RegisterRequest", + "type": "object" + }, "RegisterTableRequest": { "properties": { "bucket": { @@ -1899,10 +3871,76 @@ ], "title": "Folder" }, + "incremental_column": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Incremental Column" + }, + "incremental_window_days": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Incremental Window Days" + }, + "initial_load_chunk_days": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Initial Load Chunk Days" + }, + "max_history_days": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max History Days" + }, "name": { "title": "Name", "type": "string" }, + "partition_by": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Partition By" + }, + "partition_granularity": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Partition Granularity" + }, "primary_key": { "anyOf": [ { @@ -1975,10 +4013,24 @@ }, "sync_strategy": { "default": "full_refresh", - "deprecated": true, - "description": "DEPRECATED: catalog/profiler metadata only. No extractor reads this field; every sync is a full overwrite regardless of value. profiler.is_partitioned() consumes it for parquet-layout detection. Field stays for back-compat; will be removed in a future major release.", + "description": "Per-table extraction strategy. v26+: drives the Keboola extractor's dispatcher in connectors/keboola/extractor.py. Allowed values: 'full_refresh' (default; full table dump on each sync), 'incremental' (Storage API changedSince + primary-key dedup merge), 'partitioned' (per-partition parquet files keyed by partition_by column, per-partition merge for daily updates, chunked initial load). Pre-v26 this field was inert; existing rows default to 'full_refresh' so behavior is unchanged unless an admin opts a table in to incremental/partitioned.", "title": "Sync Strategy", "type": "string" + }, + "where_filters": { + "anyOf": [ + { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Where Filters" } }, "required": [ @@ -2042,6 +4094,30 @@ "title": "RunScriptRequest", "type": "object" }, + "SampleInteraction": { + "description": "Example dialog shown in the \"Sample interaction\" section.\n\n``assistant`` is markdown \u2014 the API pre-renders to safe HTML in\n``assistant_html`` so the template can inject without a client-side\nmarkdown lib. ``assistant`` stays as the source for copy-paste / future\nre-rendering needs.", + "properties": { + "assistant": { + "title": "Assistant", + "type": "string" + }, + "assistant_html": { + "title": "Assistant Html", + "type": "string" + }, + "user": { + "title": "User", + "type": "string" + } + }, + "required": [ + "user", + "assistant", + "assistant_html" + ], + "title": "SampleInteraction", + "type": "object" + }, "ServerConfigUpdateRequest": { "description": "Patch payload for POST /api/admin/server-config.\n\nOnly the sections listed in `_EDITABLE_SECTIONS` are accepted; anything\nelse is rejected with 400. `confirm_danger` must be true if the patch\ntouches any danger-zone section (auth.*, server.*).", "properties": { @@ -2077,6 +4153,289 @@ "title": "SetPasswordRequest", "type": "object" }, + "StoreEntityListResponse": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/StoreEntityResponse" + }, + "title": "Items", + "type": "array" + }, + "limit": { + "title": "Limit", + "type": "integer" + }, + "skip": { + "title": "Skip", + "type": "integer" + }, + "total": { + "title": "Total", + "type": "integer" + } + }, + "required": [ + "items", + "total", + "skip", + "limit" + ], + "title": "StoreEntityListResponse", + "type": "object" + }, + "StoreEntityResponse": { + "properties": { + "category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Category" + }, + "created_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Created At" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "doc_paths": { + "default": [], + "items": { + "type": "string" + }, + "title": "Doc Paths", + "type": "array" + }, + "file_size": { + "default": 0, + "title": "File Size", + "type": "integer" + }, + "id": { + "title": "Id", + "type": "string" + }, + "install_count": { + "default": 0, + "title": "Install Count", + "type": "integer" + }, + "invocation_name": { + "title": "Invocation Name", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "owner_display_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Owner Display Name" + }, + "owner_user_id": { + "title": "Owner User Id", + "type": "string" + }, + "owner_username": { + "title": "Owner Username", + "type": "string" + }, + "photo_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Photo Url" + }, + "type": { + "title": "Type", + "type": "string" + }, + "updated_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Updated At" + }, + "version": { + "title": "Version", + "type": "string" + }, + "video_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Url" + }, + "visibility_status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Visibility Status" + } + }, + "required": [ + "id", + "type", + "name", + "version", + "owner_user_id", + "owner_username", + "invocation_name" + ], + "title": "StoreEntityResponse", + "type": "object" + }, + "StoreInstallEntry": { + "properties": { + "category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Category" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "entity_id": { + "title": "Entity Id", + "type": "string" + }, + "install_count": { + "title": "Install Count", + "type": "integer" + }, + "installed_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Installed At" + }, + "invocation_name": { + "title": "Invocation Name", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "owner_user_id": { + "title": "Owner User Id", + "type": "string" + }, + "owner_username": { + "title": "Owner Username", + "type": "string" + }, + "photo_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Photo Url" + }, + "type": { + "title": "Type", + "type": "string" + }, + "version": { + "title": "Version", + "type": "string" + }, + "visibility_status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Visibility Status" + } + }, + "required": [ + "entity_id", + "type", + "name", + "version", + "owner_user_id", + "owner_username", + "invocation_name", + "install_count" + ], + "title": "StoreInstallEntry", + "type": "object" + }, "SyncSettingsUpdate": { "properties": { "datasets": { @@ -2091,6 +4450,40 @@ "title": "SyncSettingsUpdate", "type": "object" }, + "SystemFlagResponse": { + "description": "Return shape of the mark/unmark_system endpoints.", + "properties": { + "affected_groups": { + "default": 0, + "title": "Affected Groups", + "type": "integer" + }, + "affected_users": { + "default": 0, + "title": "Affected Users", + "type": "integer" + }, + "is_system": { + "title": "Is System", + "type": "boolean" + }, + "marketplace_id": { + "title": "Marketplace Id", + "type": "string" + }, + "plugin_name": { + "title": "Plugin Name", + "type": "string" + } + }, + "required": [ + "marketplace_id", + "plugin_name", + "is_system" + ], + "title": "SystemFlagResponse", + "type": "object" + }, "TableSubscriptionUpdate": { "properties": { "table_mode": { @@ -2100,7 +4493,7 @@ }, "tables": { "additionalProperties": true, - "default": {}, + "maxProperties": 500, "title": "Tables", "type": "object" } @@ -2108,53 +4501,6 @@ "title": "TableSubscriptionUpdate", "type": "object" }, - "TemplateGetResponse": { - "properties": { - "content": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Content" - }, - "default": { - "title": "Default", - "type": "string" - }, - "updated_at": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Updated At" - }, - "updated_by": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Updated By" - } - }, - "required": [ - "content", - "default" - ], - "title": "TemplateGetResponse", - "type": "object" - }, "TemplatePreviewRequest": { "properties": { "content": { @@ -2185,6 +4531,19 @@ "title": "TemplatePutRequest", "type": "object" }, + "ToggleRequest": { + "properties": { + "enabled": { + "title": "Enabled", + "type": "boolean" + } + }, + "required": [ + "enabled" + ], + "title": "ToggleRequest", + "type": "object" + }, "TokenListItem": { "properties": { "created_at": { @@ -2341,6 +4700,28 @@ ], "title": "Branch" }, + "curator_email": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Curator Email" + }, + "curator_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Curator Name" + }, "description": { "anyOf": [ { @@ -2413,6 +4794,50 @@ ], "title": "Description" }, + "incremental_column": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Incremental Column" + }, + "incremental_window_days": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Incremental Window Days" + }, + "initial_load_chunk_days": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Initial Load Chunk Days" + }, + "max_history_days": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max History Days" + }, "name": { "anyOf": [ { @@ -2424,6 +4849,28 @@ ], "title": "Name" }, + "partition_by": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Partition By" + }, + "partition_granularity": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Partition Granularity" + }, "primary_key": { "anyOf": [ { @@ -2515,9 +4962,23 @@ "type": "null" } ], - "deprecated": true, - "description": "DEPRECATED: catalog/profiler metadata only. See RegisterTableRequest.sync_strategy.", + "description": "v26+: drives the Keboola extractor dispatcher. PUT-shape requires a value if sent. See RegisterTableRequest.sync_strategy.", "title": "Sync Strategy" + }, + "where_filters": { + "anyOf": [ + { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Where Filters" } }, "title": "UpdateTableRequest", @@ -2551,6 +5012,30 @@ "title": "UpdateUserRequest", "type": "object" }, + "UseCase": { + "description": "One \"When to use it\" example from marketplace-metadata.json.\nCurator-authored: friendly title + 1-2-sentence description + the\nliteral prompt the user would paste into Claude Code.", + "properties": { + "description": { + "title": "Description", + "type": "string" + }, + "prompt": { + "title": "Prompt", + "type": "string" + }, + "title": { + "title": "Title", + "type": "string" + } + }, + "required": [ + "title", + "description", + "prompt" + ], + "title": "UseCase", + "type": "object" + }, "UserMembershipResponse": { "properties": { "added_at": { @@ -2774,13 +5259,130 @@ ], "title": "VoteRequest", "type": "object" + }, + "_OverrideRequest": { + "properties": { + "reason": { + "maxLength": 2000, + "minLength": 4, + "title": "Reason", + "type": "string" + } + }, + "required": [ + "reason" + ], + "title": "_OverrideRequest", + "type": "object" + }, + "app__api__claude_md__TemplateGetResponse": { + "properties": { + "content": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Content" + }, + "default": { + "title": "Default", + "type": "string" + }, + "legacy_strings_detected": { + "default": [], + "items": { + "type": "string" + }, + "title": "Legacy Strings Detected", + "type": "array" + }, + "updated_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Updated At" + }, + "updated_by": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Updated By" + } + }, + "required": [ + "content", + "default" + ], + "title": "TemplateGetResponse", + "type": "object" + }, + "app__api__welcome__TemplateGetResponse": { + "properties": { + "content": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Content" + }, + "default": { + "title": "Default", + "type": "string" + }, + "updated_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Updated At" + }, + "updated_by": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Updated By" + } + }, + "required": [ + "content", + "default" + ], + "title": "TemplateGetResponse", + "type": "object" } } }, "info": { "description": "Data distribution platform for AI analytical systems", "title": "AI Data Analyst", - "version": "2.0.0" + "version": "0.54.16" }, "openapi": "3.1.0", "paths": { @@ -2835,48 +5437,19 @@ }, "/activity-center": { "get": { - "operationId": "activity_center_activity_center_get", - "parameters": [ - { - "in": "header", - "name": "authorization", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Authorization" - } - } - ], + "description": "Legacy URL \u2014 redirect to /admin/activity.", + "operationId": "activity_center_redirect_activity_center_get", "responses": { "200": { "content": { - "text/html": { - "schema": { - "type": "string" - } + "application/json": { + "schema": {} } }, "description": "Successful Response" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" } }, - "summary": "Activity Center", + "summary": "Activity Center Redirect", "tags": [ "web" ] @@ -2932,6 +5505,56 @@ ] } }, + "/admin/activity": { + "get": { + "description": "Unified observability page \u2014 KPI cards, faceted filter bar, full\naudit_log table with sort/search/saved-views. All data loads\nclient-side from /api/admin/observability/* + /api/admin/activity.", + "operationId": "admin_activity_admin_activity_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Activity", + "tags": [ + "web" + ] + } + }, "/admin/agent-prompt": { "get": { "operationId": "admin_agent_prompt_page_admin_agent_prompt_get", @@ -2981,6 +5604,56 @@ ] } }, + "/admin/corporate-memory": { + "get": { + "description": "Curated Memory review queue \u2014 admin-only.\n\nThe governance surface paired with the user-facing ``/corporate-memory``\npage: pending items awaiting review, contradictions, duplicate\ncandidates, and the audit trail. Reached from the Admin nav dropdown.", + "operationId": "corporate_memory_admin_admin_corporate_memory_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Corporate Memory Admin", + "tags": [ + "web" + ] + } + }, "/admin/grants": { "get": { "description": "Backward-compat redirect for the page's previous URL.", @@ -3162,6 +5835,104 @@ ] } }, + "/admin/news": { + "get": { + "description": "Admin authoring surface \u2014 current published banner, draft editor,\nversions table. JS hits the /api/admin/news/* endpoints for the\nwrite paths.", + "operationId": "admin_news_editor_admin_news_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin News Editor", + "tags": [ + "web" + ] + } + }, + "/admin/scheduler-runs": { + "get": { + "description": "Scheduler runs is now a filter on the unified Activity page, not a\nstandalone view \u2014 see the unification done in the platform-telemetry\nepic. Keep the URL as a 308 so existing bookmarks land on the right\npre-filtered view.", + "operationId": "admin_scheduler_runs_redirect_admin_scheduler_runs_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Scheduler Runs Redirect", + "tags": [ + "web" + ] + } + }, "/admin/server-config": { "get": { "description": "Server configuration editor \u2014 instance.yaml fields grouped by section.\n\nShell-only page. The form is populated client-side from\nGET /api/admin/server-config (which redacts secrets) and submitted\nsection-by-section to POST /api/admin/server-config. Auth/server\nsections require an explicit confirmation dialog before save (see\n``_DANGER_SECTIONS`` in the API). Saves trigger the \"restart required\"\nbanner \u2014 hot-reload is out of scope for #91.", @@ -3212,6 +5983,365 @@ ] } }, + "/admin/sessions": { + "get": { + "description": "Global Sessions browser \u2014 every collected session JSONL across all\nusers. The list page is a shell; data loads client-side via\n/api/admin/sessions/{list,kpis,facets}.", + "operationId": "admin_sessions_page_admin_sessions_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Sessions Page", + "tags": [ + "web" + ] + } + }, + "/admin/sessions/{username}/{session_file}": { + "get": { + "description": "Session transcript viewer. Username + session_file are revalidated by\nthe API route (regex + path-escape guard) when /transcript is fetched;\nhere we just render the shell.", + "operationId": "admin_session_detail_admin_sessions__username___session_file__get", + "parameters": [ + { + "in": "path", + "name": "username", + "required": true, + "schema": { + "title": "Username", + "type": "string" + } + }, + { + "in": "path", + "name": "session_file", + "required": true, + "schema": { + "title": "Session File", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Session Detail", + "tags": [ + "web" + ] + } + }, + "/admin/store/submissions": { + "get": { + "description": "Triage page for flea-market guardrail submissions.\n\nLists every submission row newest-first with the inline-check verdicts,\nLLM findings, and override action buttons. Server-side render keeps the\npage accessible without JS for the read-only inspect path; mutating\nactions (override, retry, delete) hit the JSON admin endpoints under\n``/api/admin/store/submissions``.\n\nFilters AND together; URL is bookmarkable. Pagination via ``skip`` /\n``limit`` (default 50, clamped to [1, 200] for the UI page-size\nselector).", + "operationId": "admin_store_submissions_page_admin_store_submissions_get", + "parameters": [ + { + "in": "query", + "name": "status", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Status" + } + }, + { + "in": "query", + "name": "submitter", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Submitter" + } + }, + { + "in": "query", + "name": "type", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Type" + } + }, + { + "in": "query", + "name": "name", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + } + }, + { + "in": "query", + "name": "version", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Version" + } + }, + { + "in": "query", + "name": "sort", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Sort" + } + }, + { + "in": "query", + "name": "order", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Order" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 50, + "title": "Limit", + "type": "integer" + } + }, + { + "in": "query", + "name": "skip", + "required": false, + "schema": { + "default": 0, + "title": "Skip", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Store Submissions Page", + "tags": [ + "web" + ] + } + }, + "/admin/store/submissions/{submission_id}": { + "get": { + "description": "Per-submission detail with full verdict + override + retry actions.", + "operationId": "admin_store_submission_detail_page_admin_store_submissions__submission_id__get", + "parameters": [ + { + "in": "path", + "name": "submission_id", + "required": true, + "schema": { + "title": "Submission Id", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Store Submission Detail Page", + "tags": [ + "web" + ] + } + }, "/admin/tables": { "get": { "operationId": "admin_tables_admin_tables_get", @@ -3261,9 +6391,59 @@ ] } }, + "/admin/telemetry": { + "get": { + "description": "Interactive Telemetry page \u2014 filter / group-by / search on usage_events.\n\nAll data loads client-side from /api/admin/telemetry/* (facets, kpis,\nquery) so the page state lives in the URL and the server doesn't\npreload a fixed window's snapshot.", + "operationId": "admin_telemetry_page_admin_telemetry_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Telemetry Page", + "tags": [ + "web" + ] + } + }, "/admin/tokens": { "get": { - "description": "Admin \u2014 list of ALL tokens for incident response + offboarding.\n\nAdmin-only. No create form here (admins mint their own PATs via /tokens).\nURL param ?user= pre-fills the owner filter (deep-link from\n/admin/users \"Tokens\" action).", + "description": "Admin \u2014 list of ALL tokens for incident response + offboarding.\n\nAdmin-only. No create form here (admins mint their own PATs via /me/profile).\nURL param ?user= pre-fills the owner filter (deep-link from\n/admin/users \"Tokens\" action).", "operationId": "admin_tokens_page_admin_tokens_get", "parameters": [ { @@ -3311,6 +6491,54 @@ ] } }, + "/admin/usage": { + "get": { + "description": "Legacy URL \u2014 308 to /admin/telemetry. The page was renamed in the\nplatform-telemetry epic to match what's actually shown (tool/skill\ninvocations from session JSONLs). Old bookmarks land on the right\nplace without breaking.", + "operationId": "admin_usage_redirect_admin_usage_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Usage Redirect", + "tags": [ + "web" + ] + } + }, "/admin/users": { "get": { "description": "Admin page for user management.", @@ -3521,6 +6749,488 @@ ] } }, + "/api/admin/activity": { + "get": { + "operationId": "activity_timeline_api_admin_activity_get", + "parameters": [ + { + "in": "query", + "name": "since_minutes", + "required": false, + "schema": { + "default": 1440, + "maximum": 43200, + "minimum": 1, + "title": "Since Minutes", + "type": "integer" + } + }, + { + "in": "query", + "name": "user_id", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "User Id" + } + }, + { + "in": "query", + "name": "action_prefix", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Action Prefix" + } + }, + { + "in": "query", + "name": "resource", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Resource" + } + }, + { + "in": "query", + "name": "result_pattern", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Result Pattern" + } + }, + { + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Q" + } + }, + { + "in": "query", + "name": "cursor_ts", + "required": false, + "schema": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Cursor Ts" + } + }, + { + "in": "query", + "name": "cursor_id", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Cursor Id" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 50, + "maximum": 200, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Activity Timeline", + "tags": [ + "activity" + ] + } + }, + "/api/admin/activity/health": { + "get": { + "operationId": "activity_health_api_admin_activity_health_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Activity Health", + "tags": [ + "activity" + ] + } + }, + "/api/admin/activity/sync": { + "get": { + "operationId": "activity_sync_api_admin_activity_sync_get", + "parameters": [ + { + "in": "query", + "name": "since_minutes", + "required": false, + "schema": { + "default": 1440, + "maximum": 43200, + "minimum": 1, + "title": "Since Minutes", + "type": "integer" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 100, + "maximum": 500, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Activity Sync", + "tags": [ + "activity" + ] + } + }, + "/api/admin/bigquery/test-connection": { + "post": { + "description": "Run `SELECT 1 AS ok` against BigQuery via the configured BqAccess.\n\nReturns 200 with `{ok, billing_project, data_project, elapsed_ms}` on\nsuccess. Maps known failure modes:\n\n- `BqAccessError(not_configured)` \u2192 400 with the typed detail\n- `BqAccessError` (other kinds) \u2192 502 with the typed detail\n- `concurrent.futures.TimeoutError` \u2192 504 with `kind=\"timeout\"` and\n best-effort `cancel_job` invoked", + "operationId": "test_connection_api_admin_bigquery_test_connection_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Test Connection", + "tags": [ + "admin" + ] + } + }, + "/api/admin/cache-warmup/run": { + "post": { + "operationId": "warmup_run_api_admin_cache_warmup_run_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Warmup Run" + } + }, + "/api/admin/cache-warmup/status": { + "get": { + "operationId": "warmup_status_api_admin_cache_warmup_status_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Warmup Status" + } + }, + "/api/admin/cache-warmup/stream": { + "get": { + "operationId": "warmup_stream_api_admin_cache_warmup_stream_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Warmup Stream" + } + }, "/api/admin/configure": { "post": { "description": "Configure data source and instance settings via API.\n\nWrites config to instance.yaml and persists secrets to .env_overlay.\nAI agents and the /setup wizard use this instead of manual file editing.", @@ -3581,9 +7291,19 @@ }, "/api/admin/discover-and-register": { "post": { - "description": "Discover tables from configured source and auto-register them.\n\nCombines discover-tables + register-table into one call.\nSkips already-registered tables. Used by /setup wizard and AI agents.", + "description": "Discover tables from configured source and auto-register them.\n\nCombines discover-tables + register-table into one call. Already-\nregistered rows are NEVER overwritten \u2014 admin edits to bucket /\nsource_table win. The response surfaces a ``drift`` array listing\nany rows where discovery would have written different coordinates\nthan what's in the registry, so operators can audit divergence\nafter a Keboola-side bucket rename / table move.\n\nQuery params:\n - ``dry_run=true`` returns the plan without writing anything.\n Lists ``would_register``, ``drift``, and ``invalid`` so an\n operator can decide whether to proceed (or, in the drift case,\n which side they want to fix).\n\nUsed by /setup wizard and AI agents.", "operationId": "discover_and_register_api_admin_discover_and_register_post", "parameters": [ + { + "in": "query", + "name": "dry_run", + "required": false, + "schema": { + "default": false, + "title": "Dry Run", + "type": "boolean" + } + }, { "in": "header", "name": "authorization", @@ -4356,6 +8076,213 @@ ] } }, + "/api/admin/initial-workspace": { + "delete": { + "description": "Remove the ``initial_workspace:`` config + PAT.\n\nOptional ``?purge=true`` also wipes ``${DATA_DIR}/initial-workspace/``\nfrom disk. Default leaves the working copy in place so an admin can\ninspect / re-register the same URL without re-cloning.", + "operationId": "admin_delete_api_admin_initial_workspace_delete", + "parameters": [ + { + "in": "query", + "name": "purge", + "required": false, + "schema": { + "default": false, + "title": "Purge", + "type": "boolean" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "204": { + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Delete", + "tags": [ + "initial_workspace" + ] + }, + "get": { + "description": "Return the current ``initial_workspace:`` config + sync state.", + "operationId": "admin_get_api_admin_initial_workspace_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminInitialWorkspaceResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Get", + "tags": [ + "initial_workspace" + ] + }, + "post": { + "description": "Register / update the template repo.\n\nOnly ``url``, ``branch``, and ``token_env`` land in the YAML overlay.\nSync state (``last_synced_at`` / ``last_commit_sha`` / ``last_error``)\nis written by ``POST .../sync``, not here \u2014 saving a config change\nshould NOT silently invalidate the existing sync state.", + "operationId": "admin_post_api_admin_initial_workspace_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminInitialWorkspaceResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Post", + "tags": [ + "initial_workspace" + ] + } + }, + "/api/admin/initial-workspace/sync": { + "post": { + "description": "Manual \"Sync now\" \u2014 clone or fast-forward the template repo, then\npersist ``last_synced_at`` + ``last_commit_sha`` (or ``last_error``)\nback to the YAML overlay.\n\nReturns ``{action, commit_sha, file_count, path}`` on success or\nsurfaces a ``400`` with the validation / git error so the admin sees\nit in the Sync-now modal. The error payload uses the typed-``kind``\nshape the CLI's error renderer already understands.", + "operationId": "admin_sync_api_admin_initial_workspace_sync_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Sync", + "tags": [ + "initial_workspace" + ] + } + }, "/api/admin/metadata/{table_id}": { "get": { "description": "Return column metadata for a table.", @@ -4708,6 +8635,755 @@ ] } }, + "/api/admin/news/current": { + "get": { + "description": "Latest published version (or {published: false} if none).", + "operationId": "get_current_api_admin_news_current_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Get Current", + "tags": [ + "news" + ] + } + }, + "/api/admin/news/draft": { + "get": { + "description": "Active draft. 404 if none \u2014 UI shows 'create new draft' button.", + "operationId": "get_draft_api_admin_news_draft_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Get Draft", + "tags": [ + "news" + ] + }, + "put": { + "description": "Upsert the active draft. Sanitizes both fields BEFORE writing.\n\nOptimistic-lock: when `expected_version` is supplied (query string),\nthe request fails with 409 unless the active draft is at that\nversion. Pass `expected_version=0` when you intend to create the\nfirst draft and want the call to fail if another admin already\nstarted one.", + "operationId": "put_draft_api_admin_news_draft_put", + "parameters": [ + { + "in": "query", + "name": "expected_version", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Expected Version" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewsBody" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Put Draft", + "tags": [ + "news" + ] + } + }, + "/api/admin/news/preview": { + "post": { + "description": "Sanitize candidate intro + content and return \u2014 no DB writes.", + "operationId": "post_preview_api_admin_news_preview_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewsBody" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Post Preview", + "tags": [ + "news" + ] + } + }, + "/api/admin/news/publish": { + "post": { + "description": "Publish the active draft.\n\nWhen `expected_version` is supplied (query string), the request\nfails with 409 unless the active draft is at that version. Use\nthis when reviewing a specific draft before flipping it live so\na concurrent admin's edit doesn't slip through under your name.", + "operationId": "post_publish_api_admin_news_publish_post", + "parameters": [ + { + "in": "query", + "name": "expected_version", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Expected Version" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Post Publish", + "tags": [ + "news" + ] + } + }, + "/api/admin/news/unpublish/{version}": { + "post": { + "operationId": "post_unpublish_api_admin_news_unpublish__version__post", + "parameters": [ + { + "in": "path", + "name": "version", + "required": true, + "schema": { + "title": "Version", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Post Unpublish", + "tags": [ + "news" + ] + } + }, + "/api/admin/news/versions": { + "get": { + "operationId": "list_versions_api_admin_news_versions_get", + "parameters": [ + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 50, + "title": "Limit", + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "required": false, + "schema": { + "default": 0, + "title": "Offset", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "List Versions", + "tags": [ + "news" + ] + } + }, + "/api/admin/news/versions/{version}": { + "get": { + "operationId": "get_version_api_admin_news_versions__version__get", + "parameters": [ + { + "in": "path", + "name": "version", + "required": true, + "schema": { + "title": "Version", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Get Version", + "tags": [ + "news" + ] + } + }, + "/api/admin/observability/facets": { + "get": { + "description": "Return the distinct facet values present in `audit_log` for the\nselected window, each with a count. The UI uses these to populate the\nfilter dropdowns \u2014 so an admin sees only users/actions that actually\nexist, not a free-text guess.\n\nCounts are capped at 50 per facet (largest first). 50 is comfortable in\na dropdown; tighter windows usually have <20 anyway.", + "operationId": "facets_api_admin_observability_facets_get", + "parameters": [ + { + "in": "query", + "name": "since_minutes", + "required": false, + "schema": { + "default": 1440, + "maximum": 43200, + "minimum": 1, + "title": "Since Minutes", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Facets", + "tags": [ + "observability" + ] + } + }, + "/api/admin/observability/kpis": { + "get": { + "description": "Four KPIs for the top-bar cards: events, active users, error rate, p95.", + "operationId": "kpis_api_admin_observability_kpis_get", + "parameters": [ + { + "in": "query", + "name": "since_minutes", + "required": false, + "schema": { + "default": 1440, + "maximum": 43200, + "minimum": 1, + "title": "Since Minutes", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Kpis", + "tags": [ + "observability" + ] + } + }, + "/api/admin/observability/views": { + "get": { + "operationId": "list_views_api_admin_observability_views_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "List Views", + "tags": [ + "observability" + ] + }, + "post": { + "operationId": "save_view_api_admin_observability_views_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "title": "Payload", + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Save View", + "tags": [ + "observability" + ] + } + }, + "/api/admin/observability/views/{view_id}": { + "delete": { + "operationId": "delete_view_api_admin_observability_views__view_id__delete", + "parameters": [ + { + "in": "path", + "name": "view_id", + "required": true, + "schema": { + "title": "View Id", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Delete View", + "tags": [ + "observability" + ] + } + }, "/api/admin/register-table": { "post": { "description": "Register a new table in the system.\n\nBehavior by source_type:\n- **bigquery**: validates BQ-specific shape (dataset / source_table /\n identifier safety / project_id format), forces query_mode='remote' and\n profile_after_sync=False, then synchronously rebuilds extract.duckdb +\n master views with a wall-clock budget. Returns 200 with the view name\n on success, 202 on budget overrun (rebuild continues in a\n BackgroundTask), or 500 if the synchronous rebuild ran but reported\n an error (e.g. auth failure, missing project, unsafe identifier).\n- other source types: insert-only, no post-register hook. Returns 201.\n\nDefined as a plain ``def`` (not ``async def``) so FastAPI runs it in a\nthreadpool \u2014 the synchronous-materialize path waits on\n``threading.Event.wait()``, which would otherwise block the asyncio\nevent loop and stall every other request for up to ``_BQ_SYNC_REGISTER_\nTIMEOUT_S``. ``Depends(...)``, ``BackgroundTasks``, and\n``JSONResponse`` all work the same in sync handlers; the rest of the\nadmin module mixes both styles already.\n\nThe route does NOT carry a default ``status_code`` \u2014 each branch returns\nits own JSONResponse with the right code. A blanket ``status_code=201``\non the decorator would mislead OpenAPI consumers about the BQ branch.\n\nAlways: 409 on view-name collision against the existing registry, audit\nlog entry on success.", @@ -4886,7 +9562,7 @@ }, "/api/admin/registry/{table_id}": { "delete": { - "description": "Unregister a table from the system.\n\nFor BQ rows, schedules a background rebuild so the dropped row's\nmaster view is removed from analytics.duckdb (rather than hanging\naround until the next scheduled sync).\n\nFor materialized rows, also removes the canonical parquet at\n`${DATA_DIR}/extracts//data/.parquet` and clears\nthe matching `sync_state` row. Without these two cleanups, the\nmanifest endpoint kept advertising the dropped table to `da sync`\n(sync_state-driven) and the orchestrator's next rebuild could\nresurrect a master view from the leftover parquet (E2E sub-agent\nfinding 2026-05-01).", + "description": "Unregister a table from the system.\n\nFor BQ rows, schedules a background rebuild so the dropped row's\nmaster view is removed from analytics.duckdb (rather than hanging\naround until the next scheduled sync).\n\nFor materialized rows, also removes the canonical parquet at\n`${DATA_DIR}/extracts//data/.parquet` and clears\nthe matching `sync_state` row. Without these two cleanups, the\nmanifest endpoint kept advertising the dropped table to `agnes pull`\n(sync_state-driven) and the orchestrator's next rebuild could\nresurrect a master view from the leftover parquet (E2E sub-agent\nfinding 2026-05-01).", "operationId": "unregister_table_api_admin_registry__table_id__delete", "parameters": [ { @@ -5056,6 +9732,302 @@ ] } }, + "/api/admin/run-blocked-purge": { + "post": { + "description": "Trigger the TTL purge of blocked bundle bytes.\n\nWraps :func:`src.store_guardrails.purge.purge_blocked_bundles`. The\nscheduler service hits this endpoint daily (under\n``SCHEDULER_API_TOKEN`` like the corporate-memory + verification\njobs); admins can also run it on demand from the UI.", + "operationId": "run_blocked_purge_api_admin_run_blocked_purge_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Run Blocked Purge", + "tags": [ + "admin" + ] + } + }, + "/api/admin/run-bq-metadata-refresh": { + "post": { + "description": "Refresh metadata for every remote BQ row in the registry.\n\nCalled by the scheduler at ``SCHEDULER_BQ_METADATA_REFRESH_INTERVAL``\n(default 4 h). Single-flight guarded: if a refresh is already\nrunning (e.g. operator clicked \"Re-warm all\" while a scheduler tick\nis in flight, or two scheduler containers raced during an upgrade),\nthe second caller gets ``409 already_running`` with the in-flight\n``run_id`` + ``started_at`` so they can correlate against logs.\nThe scheduler treats 409 as a no-op success.\n\nBounded concurrency within a run (default 4, override via\n``AGNES_BQ_METADATA_REFRESH_CONCURRENCY``) so a deployment with\nmany remote tables doesn't fan out to dozens of parallel BQ jobs.", + "operationId": "run_bq_metadata_refresh_api_admin_run_bq_metadata_refresh_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Run Bq Metadata Refresh" + } + }, + "/api/admin/run-corporate-memory": { + "post": { + "description": "Trigger the corporate-memory catalog refresh from the scheduler.\n\nReads all CLAUDE.local.md files, sends them through the LLM with the\nexisting catalog, and writes an updated catalog to knowledge.json.", + "operationId": "run_corporate_memory_api_admin_run_corporate_memory_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Run Corporate Memory", + "tags": [ + "admin" + ] + } + }, + "/api/admin/run-reap-stuck-reviews": { + "post": { + "description": "Trigger the stuck-review reaper.\n\nWraps :func:`src.store_guardrails.reaper.reap_stuck_llm_reviews`.\nThe scheduler hits this every 15 minutes; admins can run it on\ndemand if a worker crash is suspected. Flips any\n``status='pending_llm'`` row older than the configured grace to\n``review_error`` so the queue stops growing indefinitely.", + "operationId": "run_reap_stuck_reviews_api_admin_run_reap_stuck_reviews_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Run Reap Stuck Reviews", + "tags": [ + "admin" + ] + } + }, + "/api/admin/run-session-collector": { + "post": { + "description": "Trigger the session-collector job from the scheduler.\n\nWalks /home/*/user/sessions/*.jsonl and copies new files into\n/data/user_sessions//. Idempotent \u2014 already-collected files\nare skipped.", + "operationId": "run_session_collector_api_admin_run_session_collector_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Run Session Collector", + "tags": [ + "admin" + ] + } + }, + "/api/admin/run-session-processor": { + "post": { + "description": "Trigger one session-pipeline processor against /data/user_sessions/*.\n\nReplaces the per-processor /run-* endpoints with a single parametrized\nentry. The scheduler invokes this once per registered processor on its\nown cadence; processors are independent (one slow / failing processor\ncan't block any other).\n\nReturns 400 if `processor` is unknown. The verification processor\nrequires an LLM extractor \u2014 if the instance has no ai: config and no\nANTHROPIC_API_KEY / LLM_API_KEY, it won't appear in the registry and\nthe call returns 400 the same as a misspelled name.", + "operationId": "run_session_processor_api_admin_run_session_processor_post", + "parameters": [ + { + "description": "Processor name (e.g. 'verification', 'usage')", + "in": "query", + "name": "processor", + "required": true, + "schema": { + "description": "Processor name (e.g. 'verification', 'usage')", + "title": "Processor", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Run Session Processor", + "tags": [ + "admin" + ] + } + }, "/api/admin/server-config": { "get": { "description": "Return the current instance.yaml with secrets redacted.\n\nUsed by the /admin/server-config UI to prefill its form. The redacted\npayload mirrors the actual file shape, so the UI doesn't need to know\nthe schema \u2014 it iterates over the editable sections and renders the\nfields it finds. Empty sections still show in the response so the form\nknows to render their headers.", @@ -5160,6 +10132,1840 @@ ] } }, + "/api/admin/sessions/facets": { + "get": { + "operationId": "facets_api_admin_sessions_facets_get", + "parameters": [ + { + "in": "query", + "name": "since_minutes", + "required": false, + "schema": { + "default": 10080, + "maximum": 525600, + "minimum": 1, + "title": "Since Minutes", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Facets", + "tags": [ + "admin-sessions" + ] + } + }, + "/api/admin/sessions/kpis": { + "get": { + "operationId": "kpis_api_admin_sessions_kpis_get", + "parameters": [ + { + "in": "query", + "name": "since_minutes", + "required": false, + "schema": { + "default": 10080, + "maximum": 525600, + "minimum": 1, + "title": "Since Minutes", + "type": "integer" + } + }, + { + "in": "query", + "name": "username", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Username" + } + }, + { + "in": "query", + "name": "model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model" + } + }, + { + "in": "query", + "name": "only_errors", + "required": false, + "schema": { + "default": false, + "title": "Only Errors", + "type": "boolean" + } + }, + { + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Q" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Kpis", + "tags": [ + "admin-sessions" + ] + } + }, + "/api/admin/sessions/list": { + "get": { + "operationId": "list_sessions_api_admin_sessions_list_get", + "parameters": [ + { + "in": "query", + "name": "since_minutes", + "required": false, + "schema": { + "default": 10080, + "maximum": 525600, + "minimum": 1, + "title": "Since Minutes", + "type": "integer" + } + }, + { + "in": "query", + "name": "username", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Username" + } + }, + { + "in": "query", + "name": "model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model" + } + }, + { + "in": "query", + "name": "only_errors", + "required": false, + "schema": { + "default": false, + "title": "Only Errors", + "type": "boolean" + } + }, + { + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Q" + } + }, + { + "in": "query", + "name": "sort", + "required": false, + "schema": { + "default": "started_at:desc", + "title": "Sort", + "type": "string" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 50, + "maximum": 200, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "required": false, + "schema": { + "default": 0, + "maximum": 50000, + "minimum": 0, + "title": "Offset", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "List Sessions", + "tags": [ + "admin-sessions" + ] + } + }, + "/api/admin/sessions/{username}/{session_file}/download": { + "get": { + "description": "Stream a single JSONL straight from disk. Path-safety guarded the\nsame way as ``/transcript``. Audit-logged.", + "operationId": "download_api_admin_sessions__username___session_file__download_get", + "parameters": [ + { + "in": "path", + "name": "username", + "required": true, + "schema": { + "title": "Username", + "type": "string" + } + }, + { + "in": "path", + "name": "session_file", + "required": true, + "schema": { + "title": "Session File", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Download", + "tags": [ + "admin-sessions" + ] + } + }, + "/api/admin/sessions/{username}/{session_file}/transcript": { + "get": { + "operationId": "transcript_api_admin_sessions__username___session_file__transcript_get", + "parameters": [ + { + "in": "path", + "name": "username", + "required": true, + "schema": { + "title": "Username", + "type": "string" + } + }, + { + "in": "path", + "name": "session_file", + "required": true, + "schema": { + "title": "Session File", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Transcript", + "tags": [ + "admin-sessions" + ] + } + }, + "/api/admin/store/submissions": { + "get": { + "description": "List flea-market guardrail submissions newest-first.\n\nAll filters AND together. ``status`` is comma-separated\n(e.g. ``blocked_inline,blocked_llm``). ``submitter`` matches\n``submitter_id`` exactly. ``type`` is one of ``skill`` / ``agent`` /\n``plugin``. ``name`` and ``version`` are case-insensitive substrings.\n``limit`` clamped to [1, 500].", + "operationId": "admin_list_store_submissions_api_admin_store_submissions_get", + "parameters": [ + { + "in": "query", + "name": "status", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Status" + } + }, + { + "in": "query", + "name": "submitter", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Submitter" + } + }, + { + "in": "query", + "name": "type", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Type" + } + }, + { + "in": "query", + "name": "name", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + } + }, + { + "in": "query", + "name": "version", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Version" + } + }, + { + "in": "query", + "name": "sort", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Sort" + } + }, + { + "in": "query", + "name": "order", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Order" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 100, + "title": "Limit", + "type": "integer" + } + }, + { + "in": "query", + "name": "skip", + "required": false, + "schema": { + "default": 0, + "title": "Skip", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin List Store Submissions", + "tags": [ + "admin" + ] + } + }, + "/api/admin/store/submissions/{submission_id}": { + "delete": { + "description": "Hard-delete a submission record + its linked bundle (if any).\n\nUse this for spam / accidental uploads after override-publish is the\nwrong call. The audit_log row preserves what was deleted in case\ntriage needs the evidence trail later.", + "operationId": "admin_delete_store_submission_api_admin_store_submissions__submission_id__delete", + "parameters": [ + { + "in": "path", + "name": "submission_id", + "required": true, + "schema": { + "title": "Submission Id", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Delete Store Submission", + "tags": [ + "admin" + ] + }, + "get": { + "operationId": "admin_get_store_submission_api_admin_store_submissions__submission_id__get", + "parameters": [ + { + "in": "path", + "name": "submission_id", + "required": true, + "schema": { + "title": "Submission Id", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Get Store Submission", + "tags": [ + "admin" + ] + } + }, + "/api/admin/store/submissions/{submission_id}/bundle.zip": { + "get": { + "description": "Stream the on-disk bundle as a fresh ZIP for admin inspection.\n\nRequired by the forensic use case: admin needs to inspect what a\nsubmitter actually tried to upload (not just the verdict). Bundle\nmust still be on disk \u2014 TTL purge nulls ``entity_id`` and removes\nthe directory, in which case this returns 410.", + "operationId": "admin_download_store_submission_bundle_api_admin_store_submissions__submission_id__bundle_zip_get", + "parameters": [ + { + "in": "path", + "name": "submission_id", + "required": true, + "schema": { + "title": "Submission Id", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Download Store Submission Bundle", + "tags": [ + "admin" + ] + } + }, + "/api/admin/store/submissions/{submission_id}/override": { + "post": { + "description": "Force-publish a previously-blocked submission.\n\nFlips the submission to ``status='overridden'`` and the linked\nstore_entities row to ``visibility_status='approved'``. Audit row\ncaptures who, why, and the verdict that was overridden so the next\ntime this submission shows up, the trail is intact.", + "operationId": "admin_override_store_submission_api_admin_store_submissions__submission_id__override_post", + "parameters": [ + { + "in": "path", + "name": "submission_id", + "required": true, + "schema": { + "title": "Submission Id", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/_OverrideRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Override Store Submission", + "tags": [ + "admin" + ] + } + }, + "/api/admin/store/submissions/{submission_id}/rescan": { + "post": { + "description": "Re-run **all** guardrail checks (inline + LLM) against the current\nbundle.\n\nDifferent from ``/retry``: rescan starts from scratch (re-runs the\ndeterministic inline checks too) and is allowed regardless of\ncurrent status. Use when check rules have changed and a previously-\napproved entity might now fail (or vice versa).\n\nEffects:\n * inline checks run sync; verdict written to ``inline_checks``\n * on inline fail \u2192 ``status='blocked_inline'``, entity hidden\n * on inline pass \u2192 ``status='pending_llm'``, LLM call scheduled,\n entity visibility flipped to ``pending`` until verdict lands\n * audit_log entry recorded for both outcomes \u2014 admin sees the\n rescan in the detail-page activity timeline\n * audit row recorded\n\nRequires the bundle to still be on disk. Inline-blocked submissions\nwhose bundle was rolled back (no ``entity_id``) cannot be rescanned \u2014\nnothing to scan.", + "operationId": "admin_rescan_store_submission_api_admin_store_submissions__submission_id__rescan_post", + "parameters": [ + { + "in": "path", + "name": "submission_id", + "required": true, + "schema": { + "title": "Submission Id", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Rescan Store Submission", + "tags": [ + "admin" + ] + } + }, + "/api/admin/store/submissions/{submission_id}/retry": { + "post": { + "description": "Re-queue the LLM review for a submission.\n\nEligible statuses:\n * ``review_error`` \u2014 LLM call failed, admin retrying after the\n underlying issue (rate limit, timeout, transient outage) clears.\n * ``blocked_llm`` \u2014 admin disagrees with the prior verdict; rerun\n from a clean slate (review rules may have shifted since).\n * ``pending_llm`` \u2014 submission was held when the LLM provider had\n no credentials in env (fail-CLOSED matrix: intent True + not\n ready). Admin sets the key and re-fires from here.\n\nOnly valid when the original submission's plugin tree is still on\ndisk \u2014 for inline-blocked rows the bundle was deleted at POST time.", + "operationId": "admin_retry_store_submission_api_admin_store_submissions__submission_id__retry_post", + "parameters": [ + { + "in": "path", + "name": "submission_id", + "required": true, + "schema": { + "title": "Submission Id", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Admin Retry Store Submission", + "tags": [ + "admin" + ] + } + }, + "/api/admin/telemetry/ask": { + "post": { + "description": "Translate a natural-language question to SELECT-only SQL via Anthropic + execute.\n\nReturns the generated SQL even when validation rejects it, so the\nadmin sees what the LLM tried.", + "operationId": "ask_usage_api_admin_telemetry_ask_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "title": "Payload", + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Ask Usage", + "tags": [ + "admin-telemetry" + ] + } + }, + "/api/admin/telemetry/export": { + "get": { + "description": "Stream usage_events filtered by since/until/user_id/source.\n\nCSV: standard library `csv.writer`, one row per event with all columns.\nJSON: streaming NDJSON (one JSON object per line) \u2014 easier to pipe + tail.\nParquet: DuckDB `COPY (SELECT ...) TO '.parquet'` then stream the file.", + "operationId": "export_usage_api_admin_telemetry_export_get", + "parameters": [ + { + "in": "query", + "name": "format", + "required": false, + "schema": { + "default": "csv", + "enum": [ + "csv", + "json", + "parquet" + ], + "title": "Format", + "type": "string" + } + }, + { + "description": "ISO date or datetime; events with occurred_at >= since", + "in": "query", + "name": "since", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "ISO date or datetime; events with occurred_at >= since", + "title": "Since" + } + }, + { + "description": "ISO date or datetime; events with occurred_at < until", + "in": "query", + "name": "until", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "ISO date or datetime; events with occurred_at < until", + "title": "Until" + } + }, + { + "description": "Filter to a single user_id", + "in": "query", + "name": "user_id", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter to a single user_id", + "title": "User Id" + } + }, + { + "in": "query", + "name": "source", + "required": false, + "schema": { + "anyOf": [ + { + "enum": [ + "curated", + "flea", + "builtin" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Source" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Export Usage", + "tags": [ + "admin-telemetry" + ] + } + }, + "/api/admin/telemetry/facets": { + "get": { + "description": "Distinct values present in usage_events for the selected window so the\nUI dropdowns are closed-set instead of free-text guesses.", + "operationId": "usage_facets_api_admin_telemetry_facets_get", + "parameters": [ + { + "in": "query", + "name": "since_minutes", + "required": false, + "schema": { + "default": 10080, + "maximum": 525600, + "minimum": 1, + "title": "Since Minutes", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Usage Facets", + "tags": [ + "admin-telemetry" + ] + } + }, + "/api/admin/telemetry/kpis": { + "get": { + "description": "Four headline numbers, scoped to the same filters as /query.\n\nThe cards on /admin/usage echo these as clickable quick-filters, so the\nserver applies the same WHERE the table will see \u2014 otherwise the cards\nand the table tell different stories at the same time.", + "operationId": "usage_kpis_api_admin_telemetry_kpis_get", + "parameters": [ + { + "in": "query", + "name": "since_minutes", + "required": false, + "schema": { + "default": 10080, + "maximum": 525600, + "minimum": 1, + "title": "Since Minutes", + "type": "integer" + } + }, + { + "in": "query", + "name": "username", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Username" + } + }, + { + "in": "query", + "name": "tool_name", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Tool Name" + } + }, + { + "in": "query", + "name": "source", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Source" + } + }, + { + "in": "query", + "name": "event_type", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Event Type" + } + }, + { + "in": "query", + "name": "only_errors", + "required": false, + "schema": { + "default": false, + "title": "Only Errors", + "type": "boolean" + } + }, + { + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Q" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Usage Kpis", + "tags": [ + "admin-telemetry" + ] + } + }, + "/api/admin/telemetry/prune": { + "post": { + "description": "Delete usage_events older than USAGE_EVENTS_RETENTION_DAYS.\n\nDefault retention: env var unset or ``0`` \u2192 no pruning (forever).\nDaily rollup tables untouched \u2014 they're tiny and lossy-by-design.", + "operationId": "prune_usage_api_admin_telemetry_prune_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Prune Usage", + "tags": [ + "admin-telemetry" + ] + } + }, + "/api/admin/telemetry/query": { + "get": { + "description": "Filtered + optionally grouped read against usage_events.\n\n`group_by` \u2208 {None, 'day', 'username', 'tool_name', 'source', 'ref_id'}.\nWhen grouped, returns one bucket per row with `invocations`,\n`distinct_users`, `distinct_sessions`, `errors`. When ungrouped, returns\nthe raw event rows.\n\n`sort` syntax: `:`. For grouped queries the valid\ncolumns are `bucket`, `invocations`, `distinct_users`, `distinct_sessions`,\n`errors`. For ungrouped queries: `occurred_at`. Unknown sort keys fall\nback to a safe default rather than 400 \u2014 UIs evolve faster than this\nendpoint.", + "operationId": "usage_query_api_admin_telemetry_query_get", + "parameters": [ + { + "in": "query", + "name": "since_minutes", + "required": false, + "schema": { + "default": 10080, + "maximum": 525600, + "minimum": 1, + "title": "Since Minutes", + "type": "integer" + } + }, + { + "in": "query", + "name": "username", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Username" + } + }, + { + "in": "query", + "name": "tool_name", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Tool Name" + } + }, + { + "in": "query", + "name": "source", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Source" + } + }, + { + "in": "query", + "name": "event_type", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Event Type" + } + }, + { + "in": "query", + "name": "only_errors", + "required": false, + "schema": { + "default": false, + "title": "Only Errors", + "type": "boolean" + } + }, + { + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Q" + } + }, + { + "in": "query", + "name": "group_by", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Group By" + } + }, + { + "in": "query", + "name": "sort", + "required": false, + "schema": { + "default": "invocations:desc", + "title": "Sort", + "type": "string" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 100, + "maximum": 500, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "required": false, + "schema": { + "default": 0, + "maximum": 50000, + "minimum": 0, + "title": "Offset", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Usage Query", + "tags": [ + "admin-telemetry" + ] + } + }, + "/api/admin/telemetry/reprocess": { + "post": { + "description": "Force re-extraction of all sessions for the usage processor.\n\nDELETEs:\n - session_processor_state WHERE processor_name='usage' (so the next\n scheduler tick re-scans every JSONL) + processor_name='marketplace_rollup_30d'\n (forces 30d window rebuild on the next tick)\n - usage_events\n - usage_session_summary\n - usage_tool_daily (legacy)\n - usage_marketplace_item_daily\n - usage_marketplace_item_window\n\nVerification processor's state untouched (composite PK isolates each processor).\nAudit-logged with deleted-row counts.", + "operationId": "reprocess_usage_api_admin_telemetry_reprocess_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Reprocess Usage", + "tags": [ + "admin-telemetry" + ] + } + }, + "/api/admin/telemetry/summary": { + "get": { + "description": "Compute six summaries:\n- top_tools: list[{tool_name, invocations, source}]\n- top_users: list[{username, tool_calls}]\n- error_rate: list[{tool_name, invocations, errors, rate}]\n- dau_series: list[{day, active_users}] \u2014 30 entries even when window=7d (sparkline likes 30)\n- dau_avg: float\n- slow_actions: list[{action, p50, p95, p99, max_ms, n}]", + "operationId": "usage_summary_api_admin_telemetry_summary_get", + "parameters": [ + { + "in": "query", + "name": "window", + "required": false, + "schema": { + "default": "7d", + "enum": [ + "7d", + "30d", + "all" + ], + "title": "Window", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Usage Summary", + "tags": [ + "admin-telemetry" + ] + } + }, + "/api/admin/users/{user_id}/activity": { + "get": { + "description": "List audit_log rows for a specific user.\n\nResolves user_id to the user record (404 if not found), filters audit_log\non the user_id field, returns paginated rows newest first.", + "operationId": "list_user_activity_api_admin_users__user_id__activity_get", + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true, + "schema": { + "title": "User Id", + "type": "string" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 50, + "maximum": 200, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "required": false, + "schema": { + "default": 0, + "minimum": 0, + "title": "Offset", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "List User Activity", + "tags": [ + "admin" + ] + } + }, "/api/admin/users/{user_id}/effective-access": { "get": { "description": "List resources the user effectively has access to, with which group\ngrants each one. ``is_admin`` reflects the real Admin-group check but\nno longer short-circuits the response \u2014 admins get the same explicit\ngrant breakdown as everyone else, so the admin viewing a target user\ncan see precisely what's been granted via which group rather than a\nflat \"Full access\" pill that hides the wiring.\n\nNote: actual authorization at runtime still gives Admin-group members\ngod-mode (see ``app.auth.access.is_user_admin``); this endpoint is a\ndebugging/audit view of the explicit grant graph, not the enforcement\nsurface.", @@ -5410,6 +12216,209 @@ ] } }, + "/api/admin/users/{user_id}/sessions": { + "get": { + "description": "Return a paginated session list for *user_id*.\n\nEach row joins ``usage_session_summary`` (preferred, ``processed=true``)\nwith a filesystem scan of ``${SESSION_DATA_DIR}//*.jsonl`` so\nthe response surfaces sessions even when the UsageProcessor hasn't run yet\n(``processed=false`` for those rows).\n\n``processed=false`` rows carry only: ``session_file``, ``session_id``\n(extracted from the filename when possible), ``started_at`` (file mtime),\nand zeroed-out counters.", + "operationId": "list_user_sessions_api_admin_users__user_id__sessions_get", + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true, + "schema": { + "title": "User Id", + "type": "string" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 50, + "maximum": 200, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "required": false, + "schema": { + "default": 0, + "minimum": 0, + "title": "Offset", + "type": "integer" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "List User Sessions", + "tags": [ + "admin" + ] + } + }, + "/api/admin/users/{user_id}/sessions/download-all": { + "get": { + "description": "Stream a ZIP of every *.jsonl under the user's session directory.\n\nReturns 404 when the directory doesn't exist.\nReturns 200 + empty ZIP when the directory exists but has no JSONL files.", + "operationId": "download_all_sessions_api_admin_users__user_id__sessions_download_all_get", + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true, + "schema": { + "title": "User Id", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Download All Sessions", + "tags": [ + "admin" + ] + } + }, + "/api/admin/users/{user_id}/sessions/{session_file}/download": { + "get": { + "description": "Stream the raw JSONL for a single session.\n\nPath-traversal is guarded by three layers:\n1. ``safe_name = Path(session_file).name`` \u2014 strips any ``../`` etc.\n2. The name must match ``^[A-Za-z0-9._-]+\\.jsonl$``.\n3. ``path.resolve()`` must still be under the session directory.", + "operationId": "download_session_api_admin_users__user_id__sessions__session_file__download_get", + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true, + "schema": { + "title": "User Id", + "type": "string" + } + }, + { + "in": "path", + "name": "session_file", + "required": true, + "schema": { + "title": "Session File", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Download Session", + "tags": [ + "admin" + ] + } + }, "/api/admin/welcome-template": { "delete": { "operationId": "admin_reset_template_api_admin_welcome_template_delete", @@ -5476,7 +12485,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TemplateGetResponse" + "$ref": "#/components/schemas/app__api__welcome__TemplateGetResponse" } } }, @@ -5680,7 +12689,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TemplateGetResponse" + "$ref": "#/components/schemas/app__api__claude_md__TemplateGetResponse" } } }, @@ -6017,7 +13026,9 @@ "200": { "content": { "application/json": { - "schema": {} + "schema": { + "$ref": "#/components/schemas/CatalogTablesResponse" + } } }, "description": "Successful Response" @@ -6039,9 +13050,66 @@ ] } }, + "/api/data/{table_id}/check-access": { + "get": { + "description": "Lightweight RBAC probe used by Caddy's ``forward_auth`` directive\nto gate file_server-served parquet downloads without involving the\napp's request workers in the bulk byte transfer.\n\nReturns HTTP 204 No Content when the caller has read access to\n``table_id``; HTTP 403 (via ``can_access_table`` returning False)\notherwise. Caddy treats 2xx as authorized and forwards the request\nto its own ``file_server`` block; non-2xx is returned to the client\nverbatim.\n\nWhy a separate endpoint and not just ``HEAD /download``: ``HEAD`` on\nthe FileResponse-based ``download`` handler still opens the file and\nruns stat() to populate Content-Length / ETag. ``forward_auth`` calls\nthis endpoint on every request, so the per-call cost matters; a pure\nRBAC check is ~1 ms while a HEAD path involves filesystem walks\n(``rglob`` for the parquet across source subdirs).", + "operationId": "check_access_api_data__table_id__check_access_get", + "parameters": [ + { + "in": "path", + "name": "table_id", + "required": true, + "schema": { + "title": "Table Id", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Check Access", + "tags": [ + "data" + ] + } + }, "/api/data/{table_id}/download": { "get": { - "description": "Stream a parquet file for download. Supports ETag for caching.", + "description": "Stream a parquet file for download. Supports ETag for caching.\n\nOn Caddy-fronted deployments the matching Caddyfile rule intercepts\n``GET /api/data/{table_id}/download``, calls ``check-access`` via\n``forward_auth``, and serves the parquet directly via ``file_server``\n\u2014 bypassing this handler entirely. This handler stays as the\ncanonical fallback for non-Caddy deployments (dev `docker compose\nup`, alternative reverse proxies, direct :8000 access) where the\nbulk transfer goes through uvicorn.", "operationId": "download_table_api_data__table_id__download_get", "parameters": [ { @@ -6096,6 +13164,74 @@ ] } }, + "/api/debug/throw": { + "get": { + "description": "Deliberate-crash route for verifying observability wiring.\n\nGated by ``DEBUG=1`` \u2014 returns 404 in production. Always raises after\nthe auth dependency resolves, so ``request.state.user`` is populated\nby the time the unhandled-exception handler captures the event. Use\nto confirm that PostHog receives the exception with full user context\n(``distinct_id``, ``user_id``, ``user_email``) and not just\n``request_id``.\n\nOptional query params let you pick the exception type and message:\n /api/debug/throw?kind=ValueError&msg=hello", + "operationId": "debug_throw_api_debug_throw_get", + "parameters": [ + { + "in": "query", + "name": "kind", + "required": false, + "schema": { + "default": "RuntimeError", + "title": "Kind", + "type": "string" + } + }, + { + "in": "query", + "name": "msg", + "required": false, + "schema": { + "default": "intentional debug throw", + "title": "Msg", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Debug Throw", + "tags": [ + "health" + ] + } + }, "/api/health": { "get": { "description": "Minimal health check for load balancers / compose healthcheck. No auth required.", @@ -6121,6 +13257,18 @@ "description": "Structured health check with deployment metadata. Requires authentication.", "operationId": "health_check_detailed_api_health_detailed_get", "parameters": [ + { + "description": "Comma-separated list of optional checks to include. Recognised values: `schema` (DB schema version against the expected migration). The default response omits these because they're rarely actionable on a healthy instance and add noise to `agnes diagnose` output (issue #204). Pass `?include=schema` to get the legacy behavior.", + "in": "query", + "name": "include", + "required": false, + "schema": { + "default": "", + "description": "Comma-separated list of optional checks to include. Recognised values: `schema` (DB schema version against the expected migration). The default response omits these because they're rarely actionable on a healthy instance and add noise to `agnes diagnose` output (issue #204). Pass `?include=schema` to get the legacy behavior.", + "title": "Include", + "type": "string" + } + }, { "in": "header", "name": "authorization", @@ -6164,6 +13312,1178 @@ ] } }, + "/api/initial-workspace": { + "get": { + "description": "Status probe consumed by ``agnes init``. Always 200.\n\nReturns ``configured: false`` when no template is registered (CLI then\nfalls through to the existing default flow). Returns ``configured:\ntrue, synced: false`` when registered but never synced (or last sync\nfailed); CLI shows a typed error pointing at /admin/server-config.\nReturns full metadata + manifest when configured + synced.", + "operationId": "analyst_status_api_initial_workspace_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnalystInitialWorkspaceResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Analyst Status", + "tags": [ + "initial_workspace" + ] + } + }, + "/api/initial-workspace.zip": { + "get": { + "description": "Return the zip of the cloned template tree (sans ``.git/``).\n\nWrites a server-side ``initial_workspace.fetch_started`` audit row so\nwe have an authoritative event the analyst's PAT-holder cannot spoof\n(the matching ``initial_workspace.applied`` event from\n``POST /applied`` is best-effort).\n\n404 when not configured (the CLI status probe should have caught\nthis; defense in depth). 503 when configured but never synced \u2014 the\nCLI then surfaces a typed error pointing at \"Sync now\".", + "operationId": "analyst_zip_api_initial_workspace_zip_get", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Analyst Zip", + "tags": [ + "initial_workspace" + ] + } + }, + "/api/initial-workspace/applied": { + "post": { + "description": "Best-effort audit event from ``agnes init`` confirming the\nanalyst's workspace has been extracted + sentinel written.\n\nThe authoritative anchor is the server-side\n``initial_workspace.fetch_started`` event written by ``GET .../zip`` \u2014\na fetch_started without a matching applied = the analyst downloaded\nbut never confirmed extraction (useful signal for operators).", + "operationId": "analyst_applied_api_initial_workspace_applied_post", + "parameters": [ + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppliedRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Analyst Applied", + "tags": [ + "initial_workspace" + ] + } + }, + "/api/marketplace/categories": { + "get": { + "description": "Per-tab category list with non-zero counts.\n\nSource of categories: union of ``STORE_CATEGORIES`` and any non-empty\n``marketplace_plugins.category`` values in the caller's RBAC scope.\nCategories with zero matching items are omitted (the frontend hides\nthem this way).", + "operationId": "list_categories_api_marketplace_categories_get", + "parameters": [ + { + "in": "query", + "name": "tab", + "required": false, + "schema": { + "default": "curated", + "enum": [ + "curated", + "flea", + "my" + ], + "title": "Tab", + "type": "string" + } + }, + { + "in": "query", + "name": "type", + "required": false, + "schema": { + "anyOf": [ + { + "enum": [ + "skill", + "agent", + "plugin" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Type" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CategoriesResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "List Categories", + "tags": [ + "marketplace" + ] + } + }, + "/api/marketplace/curated/{marketplace_id}/{plugin_name}": { + "get": { + "description": "Return the curated plugin detail + inner skill/agent/command/hook/mcp list.\n\nThe 403 guard fires before this body runs (via ``require_resource_access``).\nA second ``get_current_user`` dependency is included so we still have the\ncaller's user dict for the ``installed`` flag.", + "operationId": "curated_detail_api_marketplace_curated__marketplace_id___plugin_name__get", + "parameters": [ + { + "in": "path", + "name": "marketplace_id", + "required": true, + "schema": { + "title": "Marketplace Id", + "type": "string" + } + }, + { + "in": "path", + "name": "plugin_name", + "required": true, + "schema": { + "title": "Plugin Name", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PluginDetailResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Curated Detail", + "tags": [ + "marketplace" + ] + } + }, + "/api/marketplace/curated/{marketplace_id}/{plugin_name}/agent/{agent_name}": { + "get": { + "operationId": "curated_agent_detail_api_marketplace_curated__marketplace_id___plugin_name__agent__agent_name__get", + "parameters": [ + { + "in": "path", + "name": "marketplace_id", + "required": true, + "schema": { + "title": "Marketplace Id", + "type": "string" + } + }, + { + "in": "path", + "name": "plugin_name", + "required": true, + "schema": { + "title": "Plugin Name", + "type": "string" + } + }, + { + "in": "path", + "name": "agent_name", + "required": true, + "schema": { + "title": "Agent Name", + "type": "string" + } + }, + { + "in": "header", + "name": "authorization", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InnerDetailResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Curated Agent Detail", + "tags": [ + "marketplace" + ] + } + }, + "/api/marketplace/curated/{marketplace_id}/{plugin_name}/asset/{path}": { + "get": { + "description": "Serve an internal image asset from the cloned marketplace working tree.\n\nPaths are repo-root-relative \u2014 ``{path}`` may be e.g.\n``.agnes/cover.png`` or ``plugins/foo/icon.png``.\n\n**Auth model: login-only, no per-plugin RBAC.** Cover photos are\ncurator-designed marketing visuals \u2014 they exist specifically to be seen\nand carry no PII / source / secrets. The previous\n``require_resource_access`` check serialized every image request through\na DuckDB join under ``_system_db_lock``, making the /marketplace grid\n(12-20 cover photos per render) pay N round-trips of auth+RBAC cost in\nsequence. Login (``get_current_user``) still required \u2192 no\nunauthenticated public access. See CHANGELOG entry under \"Security\" for\nthe threat-model rationale.\n\n**Image-only by contract.** The endpoint is the source of cover photos\nreferenced from ``marketplace-metadata.json`` and from inner skill / agent\ncards. A curator who could land an arbitrary file in the cloned repo\n(HTML, JS, SVG with inline ``